Stub window.open

📝 Read blog post Return A Fake Window Objectopen in new window.

Return synthetic window object

The application is using window.openopen in new window to get a WindowProxy from the browser and set its location property to navigate.

<button id="open">Open window</button>
<script>
  document
    .getElementById('open')
    .addEventListener('click', () => {
      const win = window.open(
        '',
        '2nd-window',
        'popup,width=300,height=300',
      )
      if (win) {
        win.location = 'https://acme.co'
      }
    })
</script>

Before clicking on the button, stub the window.open method and return an object. The application simply sets the property location inside that object.

// our own object we will return to the application
// instead of opening the real window
const winProxy = {}
cy.window().then((win) => {
  cy.stub(win, 'open').as('open').returns(winProxy)
})

With the method stub ready, click on the button

cy.contains('button', 'Open window').click()

Confirm the window.open method call.

cy.get('@open').should(
  'have.been.calledOnceWithExactly',
  '',
  '2nd-window',
  'popup,width=300,height=300',
)

Tip: if you do not know some of the parameters of the method call, you can use Sinon type placeholders:

cy.get('@open').should(
  'have.been.calledOnceWithExactly',
  '',
  '2nd-window',
  Cypress.sinon.match.string,
)

You can even get the call arguments and check them individually

cy.get('@open')
  .should('have.been.calledOnce')
  .its('firstCall.args')
  // destructure the arguments array into named parameters
  .then(([url, target, params]) => {
    expect(url, 'url').to.equal('')
    expect(target, 'target').to.include('window')
    expect(params, 'dimensions')
      .to.match(/width=\d+/)
      .and.to.match(/height=\d+/)
  })

If you want to verify the parsed width and height, capture the numbers using a regular expression. Let's confirm the popup's width is between 250 and 350 pixels.

cy.get('@open')
  .should('have.been.calledOnce')
  .its('firstCall.args.2')
  .invoke('match', /width=(?<width>\d+)/)
  .its('groups.width')
  .then(Number)
  .should('be.within', 250, 350)

The application should have set the window.location property to the expected domain

cy.wrap(winProxy).should(
  'have.property',
  'location',
  'https://acme.co',
)

Window navigates after a delay

Note: the syntax cy.wrap(winProxy).should(...) retries. Even if the application sets the win.location property after a delay, the test still passes.

<button id="open">Open window</button>
<script>
  document
    .getElementById('open')
    .addEventListener('click', () => {
      const win = window.open(
        '',
        '2nd-window',
        'popup,width=300,height=300',
      )
      if (win) {
        setTimeout(() => {
          win.location = 'https://acme.co'
        }, 1500)
      }
    })
</script>
// our own object we will return to the application
// instead of opening the real window
const winProxy = {}
cy.window().then((win) => {
  cy.stub(win, 'open').as('open').returns(winProxy)
})
cy.contains('button', 'Open window').click()
// confirm the winProxy object gets the location property
cy.wrap(winProxy).should(
  'have.property',
  'location',
  'https://acme.co',
)