7

My program uses printJS which a library that helps format the content of the page for printing. I wanted to write tests with cypress to test that the print preview has been called. Currently I have a button that calls printJS when clicked and since cypress cannot interact with the print preview window, I thought it would be a good idea to stub the call to printJS then write an assertion that it was called once. I understand that this works with window.print() as you can stub that with this code.

cy.visit('http://127.0.0.1/',{
    onBeforeLoad: (win) => {
        cy.stub(win, 'print')
    }
})

Then assert with this

cy.contains('print').click()
cy.window().then((win) => {
    expect(win.print).to.be.calledOnce
})

My old button

<button type="button" class="btn btn-secnodary" onclick="window.print()">
    Print
</button>

But instead I used printJS which means my button now looks like this

<button type="button" onclick="printJS({printable: 'id_preview_modal_body', type: 'html'})" data-dismiss="modal">
    Print
</button>

The javascript gets loaded in as print.min.js which can be found here. I tried to stub the contentwindow but that doesn't seem to work so far. In the code for printJS, the printing happens here

frameElement.contentWindow.print()

from their github page, line 63

The way im stubbing it gives this issue

cy.visit('http://127.0.0.1:8000/notices/new/',{
    onBeforeLoad: (win) => {
        cy.stub(win, 'printJS')
    }
})

Uncaught TypeError: Cannot stub non-existent own property printJS

The assertion also gives this error

cy.window().then((win) => {
    expect(win.printJS).to.be.calledOnce
})

TypeError: [Function: init] is not a spy or a call to a spy!

I think the [Function: init] is a referring to const printJS = print.init from their index.js file. But i don't know how to proceed further in debugging this issue. Any help would be appreciated. Thanks!

  • How are you importing the library (print.js)? – crabbly Dec 08 '18 at 20:10
  • I'm using django and i'm importing a folder with various *.min.js files. printjs.min.js is one of them. It's setup so that when it loads a template such as the one with the button, it loads all the *.min.js files. – Jondar the Templar Dec 10 '18 at 20:36

2 Answers2

5

The problem is the onBeforeLoad hook is called before printJS is initiated, when printJS is imported it invokes it's init() function and overwrites your stub in window.print.

This is stubbing too soon

cy.visit('http://127.0.0.1:8000/notices/new/',{
    onBeforeLoad: (win) => {
        cy.stub(win, 'printJS')
    }
})

Stubbing after component has loaded and printJS is initiated

const printStub

before(function(){

  cy.visit('http://127.0.0.1:8000/notices/new/')

  // maybe wait for loading to complete

  cy.window().then(win => {
    printStub = cy.stub(win, 'printJS')
  })
})

it('stubs printJS', () => {
  cy.contains('button', 'Print').click()
  cy.window().then(win => {
    expect(printStub).to.be.calledOnce
  })
})
Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • Thank you so much for this. Especially the `// maybe wait for loading to complete`. There was more code in terms of testing (such as clicking/filling in inputs etc.) between the cy.visit() and the cy.stub() which I'm assuming made it possible for it to stub properly. I've also tested putting the stub right after the cy.visit and it doesn't work which proves that you're right in terms of waiting. – Jondar the Templar Dec 10 '18 at 21:14
  • Your'e welcome. In my app I also found some variation in *what* to stub (sometimes 'win.printJS' sometimes 'win.print'), depending on how printJS is called (directly in the template or via a javascript function). I can't quite get to a definitive answer - maybe @crabbly can comment. Glad this pattern worked for you. – Richard Matsen Dec 10 '18 at 21:22
  • I definitely can how I stubbed too early considering we load printjs.min.js as a separate file which I assume finishes "sometime" in the future. I think that means if i try to stub too early after I start to load with `cy.visit()`, series of actions could be "start load page"->"stub window"->"finishes loading printJS which overrides the stub". I also tried putting a `.wait(2000)` after `cy.visit()` and had no luck. – Jondar the Templar Dec 10 '18 at 21:24
  • When importing the library as a module, we can mock that import. However, when using the global variable, it may be better to access it through the windows object (or whatever global environment object printJS was exposed to), windows.printJS or win.printJS, like @RichardMatsen mentioned. – crabbly Dec 11 '18 at 02:34
1

This is the solution that worked for me. In my case I had a Print button on the page. That i need to click to open the window. I stubbed the window.

  cy.get('.white-header > .ui-g-12 > .pull-right').then((data) => {
  cy.window().then((win) => {
  cy.stub(win, 'open').returns("Print window is opened")
  data.click()
  expect(win.open).to.be.calledOnce
})
})