Cypress.io How to handle async code

Daniel Słaby picture Daniel Słaby · Apr 23, 2018 · Viewed 19.4k times · Source

I'm in the middle of process of moving our old capybara tests to cypress.io as our application is going SPA way.

In our case we have over 2000 tests covering a lot of features. So common pattern to test feature is to have an user with created and published offer.

On the beginning I wrote case where cypress were going trough page and clicking everything. It worked but I saw that offer create + publish took almost 1,5 minute to finish. And sometimes we need multiple offers. So we have a test which takes 5 minutes and we have 1999 left to rewrite.

We came up with REST API to create offer and user, basically shortcut for test env preparation.

I came to the point where everything is working using async/await. So here's the thing. If I want to use normal async JS code with cypress I get Error: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

Here's how it looks like:

    const faker = require('faker')
    import User from '../../support/User';

    describe('Toggle button for description offer', () => {
      const user = new User({
        first_name: faker.name.firstName(),
        last_name: faker.name.firstName(),
        email: `QA_${faker.internet.email()}`,
        password: 'xxx'
      })
      let offer = null

      before(async () => {
        await user.createOnServer()
        offer = await user.createOffer()
        await offer.publish()
      })

      beforeEach(() => {
        user.login()
        cy.visit(`/offers/${offer.details.id}`)
        cy.get('.offer-description__content button').as('showMoreButton')
      })

      it('XXX', function () {
        ...some test
      })
    })

This snippet works as expected. Firstly it fires before and creates whole env then when it's done it goes further to beforeEach and starts testing.

Now I would like to merge before and beforeEach like

  before(async () => {
    await user.createOnServer()
    offer = await user.createOffer()
    await offer.publish()
    user.login()
    cy.visit(`/offers/${offer.details.id}`)
    cy.get('.offer-description__content button').as('showMoreButton')
  })

Which will fail because of async keyword. Now the question is: how to rewrite it to use async/await and cypress commands together? I tried to rewrite it with normal Promise but It won't work too ...

Any help appreciated.

Answer

Guilherme Lemmi picture Guilherme Lemmi · Apr 23, 2018

Your problem stems from the fact that cypress commands are not promises, although behaving like promises.

I can think of two options:

  • Try to refactor your test code to not use async/await, as these commands don't behave as expected when running your code on cypress (check this bug). Cypress already has a whole way of dealing with async code as it creates a command queue that always run sequentially and in the expected order. That means you could observe the effects of your async code to validate that it happened before moving forward on your test. For instance, if User.createUserOnServer must wait a successful API call, add code to your test that will wait for the request to complete, using cy.server(), cy.route() and cy.wait(), like below:

    cy.server();
    cy.route('POST', '/users/').as('createUser');
    // do something to trigger your request here, like user.createOnServer()
    cy.wait('@createUser', { timeout: 10000});
    
  • Use another third-party library that changes how cypress works with async/await, like cypress-promise. This lib may help you to treat cypress commands as promises that you can await in your before code (read more about it in this article).