Skip to content
Advertisement

Difficulty accessing window object in Cypress

I’m trying to access the window object of my App in Cypress in the following manner.

cy.url().should('include', '/home').then(async () => {
    const window = await cy.window();
    console.log(window);
});

The above method is not working for me as window is returned as undefined.

However, the answer in this SO post states the following:

Or you can use cy.state(‘window’) which returns the window object synchronously, but this is undocumented and may change in the future.

This method does return the window value successfully.

cy.url().should('include', '/home').then(async () => {
    const window = cy.state('window');
    console.log(window);
});

As the answer suggests, cy.state(‘window’) is undocumented so I would still rather use cy.window(). Is there any reason why it’s returning undefined? (I started learning cypress today.)

Advertisement

Answer

This comes up frequently. Cypress has some documentation stating Commands are not Promises. I did a write up using a custom command to force a Command Chain to behave like a promise, but it is still experimental and nuanced.

First I’ll give your example almost verbatim to what you’re trying to accomplish:

cy.url().should('include', '/home').then(() => {
  cy.window().then(win => {
    console.log(win) // The window of your app, not `window` which is the Cypress window object
  })
})

Your example could be written a number of ways, but maybe explaining a bit how Cypress works will help more.

Cypress has something called “Commands” that return new “Chainers”. It is fluid syntax like JQuery:

// fill and submit form
cy
  .get('#firstname')
  .type('Nicholas')
  .get('#lastname')
  .type('Boll')
  .get('#submit')
  .click()

You can (and should) break up chains to be more like sentences:

// fill and submit form
cy.get('#firstname').type('Nicholas')
cy.get('#lastname').type('Boll')
cy.get('#submit').click()

You might have guessed that all Cypress Chainer commands are asynchronous. They have a .then, but they aren’t actually promises. Cypress commands actually enqueue. Cypress hooks into mocha’s lifecycle to make sure a before, beforeEach, it, afterEach, after block waits until Cypress commands are no longer enqueued before continuing.

Let’s look at this example:

it('should enter the first name', () => {
  cy.get('#firstname').type('Nicholas')
})

What actually happens is Cypress sees the cy.get Command and enqueues the get command with the argument '#firstname'. This immediately (synchronously) returns execution to the test. Cypress then sees the cy.type command with the argument 'Nicholas' and returns immediately to the test. The test is technically done at this point since there is no done callback and no Promise was returned. But Cypress hooks into the lifecycle of mocha and doesn’t release the test until enqueued commands are completed.

Now that we have 2 enqueued commands and the test is waiting for Cypress to release the test, the get command is popped off the queue. Cypress will try to find an element on the page with an id of firstname until it finds it or times out. Assuming it finds the element, it will set a state variable called subject (cy.state('subject'), but don’t rely on that). The next enqueued command type will grab the previous subject and attempt to type each key from the string 'Nicholas' one at a time with a default delay of 50ms until the string is done. Now there are no more enqueued commands and Cypress releases the test and the runner continues to the next test.

That was a bit simplified – Cypress does a lot more to make sure .type only runs on elements that can receive focus and are interactable, etc.

Now, knowing this, you can write your example a bit more simply:

cy.url().should('include', '/home')

// No need for `.then` chaining or async/await. This is an enqueued command
cy.window().then(win => {
  console.log(win)
})
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement