cypress: asynchronous code in beforeEach via 'done' function breaks tests

Current behavior:

Using asynchronous code in the beforeEach handler breaks tests. Tests that use .click, .trigger, etc. will have to use {force: true} or they will fail – the command won’t occur. After (briefly) diving into the cypress code, it looks like the click command’s promise is being set to the canceled state before it has a chance to pass actionability verification; almost as if it is being conflated with the promise from the beforeEach.

Desired behavior:

I should be able to write asynchronous code in the beforeEach callback and use done() to indicate when I’m finished.

Steps to reproduce: (app code and test code)

I’ve set up a repo here that reproduces the problem: https://github.com/fr0/cypress-angular-test

However, the important bit is pretty short:

function timer(duration) {
  return new Promise(accept => {
    setInterval(() => {
      accept();
    }, duration);
  });
}

describe('tests', function () {
  beforeEach(done => {
    timer(1000).then(() => {
      cy.visit('/').then(() => {
        done();
      });
    });
  });
  it('can click the button', () => {
    cy.get('button.clickme').click();  // adding {force: true} here makes this work! without it the click never happens.
    cy.get('.text').should('have.text', 'clicked!');
  });
});

Versions

3.1.4

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 8
  • Comments: 20 (11 by maintainers)

Commits related to this issue

Most upvoted comments

You defenitely need to improve the documentation about cy.wrap()! Currently the doc just simply sais

Yield the object passed into .wrap().

Whereas cy.wrap() is irreplaceable for so many async use cases. For example please document that cy.wrap() waits until the promise given as argument returns:

https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/logging-in__using-app-code/cypress/integration/spec.js

cy.then() exists only for legacy purposes, and it’ll get removed at some point as a breaking change. It should be cy.wrap().then() to match other valid promise signatures.

The correct behavior here is cy.wrap(otherPromise) because cypress needs to be in control of the cy chain.

We actually detect that you’ve incorrectly returned a non-cy Promise and then invoked cy.* commands within it and will print a warning message in the console.

The problem (and edge case here) as to why that message isn’t printing is because you have not returned the outer promise and have instead opted to use a (done) callback.

Because you’re not returning the promise to us, Cypress cannot tell that cy.* commands are being wrapped from an outer promise, and therefore can’t correctly run its command chain.

We likely need to additionally account for (done) and we could probably “assume” this is happening if you don’t return anything from the test, and cy.* commands have not been synchronously queued, but they end up getting enqueued later.

Mocha’s (done) really exists at this point because Promises were not as universal as they are now. There is really no reason to ever use them anymore because utilizing (done) is the same exact thing as return new Promise((resolve, reject) => {})

so ultimately, don’t put cy.visit inside a promise, just do promises inside .then and place cy.visit. Cypress will chain the commands, so visit will happen when previous .then finishes

how do you bootstrap the backend? Maybe use cy.request or cy.task?

@bahmutov , I’m having a somewhat similar problem in my use case. Basically I need to retrieve some data from the server to generate the tests from. In Mocha this can be achieved by using --delay flag. What would be the Cypress way of doing this? Can’t seem to find working solution.

https://github.com/cypress-io/cypress/issues/3114

Thanks!