In the promise doc it's described how to make Cypress wait for asynchronous processes from within a Test.
I'm trying to do the following: To set up my test, I have to create some entities in the DB using REST calls. When creating nested entities I have to make several REST calls that have to wait for each other and resolve the before() block of my test when the last call is complete. This chain of async calls results in a nesting of Promises and Chainables. The observed behavior is that only the first level of such scopes get called, the second Chainable is probably waiting for the first one to finish, I don't know.
I wish to have a pattern that enables me to have nesting of async REST calls using Chainables and Promises.
describe(`Demo: Nesting of Chainables and promises doesn't work as expected. #ID=2018-07-11`, () => {
let waited: boolean = false;
const waitNested: (i: number) => Cypress.Chainable<any> = (i: number): any => {
if (i === 0) {
waited = true;
return Cypress.Promise.resolve();
}
return cy.wrap(i).then(() => {
return new Cypress.Promise((resolve: Function): void => {
setTimeout(() => {
waitNested(i - 1).then(() => {
resolve();
});
}, 1000);
});
});
};
it('Wait 0', () => { // Success
waited = false;
waitNested(0)
.then(() => {
expect(waited).to.be.true;
});
});
it('Wait 1', () => { // Success
waited = false;
waitNested(1)
.then(() => {
expect(waited).to.be.true;
});
});
it('Wait wraps', () => { // Success
let x: boolean = false;
cy.wrap(0).then(() => {
cy.wrap(0).then(() => {
cy.wrap(0).then(() => {
x = true;
});
});
})
.then(() => {
expect(x).to.be.true;
});
});
it('Wait 2', () => { // Fail
waited = false;
waitNested(2)
.then(() => {
expect(waited).to.be.true;
});
});
});
Cypress: 3.0.2
OS: Win 10
Browser: Chrome 67
Note: Using Cypress.Promise instead of Promise doesn't make a difference.
This is failing because you've effectively created a deadlock in Cypress where you've intermixed promises and cypress commands, and where the cy.then()
is awaiting a promise which is using a cy.wrap()
which then enqueues another promise which is never resolved.
What's happening is that the inner / nested cy.wrap()
command will not resolve because it is not run because the outer (parent) cy.then
is left awaiting. It's never going to resolve the way you're writing it.
This is why we describe commands as having promise like capabilities, but you're trying to splice two completely different idioms together and it won't work cohesively.
In Cypress you cannot ever race commands against each other, they will be enqueued synchronously, but only ever run asynchronously.
With that said - in every situation we've seen this, there's generally an antipattern found somewhere. In other words, you don't ever need to find yourself in this situation if you're utilizing Cypress in the way it's meant to be utilized.
You mention your use case as needing to wait for several REST calls to seed a database. This is easily achievable out of the box using Cypress itself and you don't have to worry about or manage any of the concurrency with anything.
For instance...
cy.request('https://app.corp.com/seed', { ... })
cy.request('https://app.corp.com/seed', { ... })
cy.request('https://app.corp.com/seed', { ... })
cy.request('https://app.corp.com/seed', { ... })
cy.request('https://app.corp.com/seed', { ... })
Those 5 requests will happen sequentially, not in parallel. You don't even need to do anything special to await them. They will happen one at a time.
Another example...
cy.task('db:seed', arg1, arg2)
cy.task('db:seed', arg1, arg2)
cy.task('db:seed', arg1, arg2)
cy.task('db:seed', arg1, arg2)
cy.task('db:seed', arg1, arg2)
Same principle.
There's no need to ever coordinate multiple commands in Cypress since only one can ever be processing at a time. This avoids the need to synchronize promises which is why these two interfaces are fundamentally incompatible. They just share some qualities that are similar in a few ways.
If you need to get the results of a previous commands yielded subject that's when you use .then()
but not that is the only time it is necessary.
cy.request('...') // first
.then((resp) => {
// use something here
cy.request(..., resp.body.foo) // second
})
cy.request(...) // third
If you need to bypass this mechanism altogether and avoid using Cypress commands we recommend writing a single cy.task
to do everything in node
which then avoids the need to utilize Cypress idioms entirely. There you have ultimate control of whatever it is you'd like to do. Want to race commands together? Go ahead.
cy.task('db:seed', ...)
// inside cypress/plugins/index.js
module.exports = (on, config) => {
on('task', {
'db:seed': (arg1, arg2, arg3) => {
// run a bunch of commands here and coordinate them
// ourselves using Promises to do whatever we want
return Promise.all([
doTheThing(),
doAnotherThing(),
fooBarBaz(),
])
.then(somethingElse)
.catch((err) => { whatever(err) })
}
}
}
This is exactly what I was looking for, thanks a lot!
Most helpful comment
This is failing because you've effectively created a deadlock in Cypress where you've intermixed promises and cypress commands, and where the
cy.then()
is awaiting a promise which is using acy.wrap()
which then enqueues another promise which is never resolved.What's happening is that the inner / nested
cy.wrap()
command will not resolve because it is not run because the outer (parent)cy.then
is left awaiting. It's never going to resolve the way you're writing it.This is why we describe commands as having promise like capabilities, but you're trying to splice two completely different idioms together and it won't work cohesively.
In Cypress you cannot ever race commands against each other, they will be enqueued synchronously, but only ever run asynchronously.
With that said - in every situation we've seen this, there's generally an antipattern found somewhere. In other words, you don't ever need to find yourself in this situation if you're utilizing Cypress in the way it's meant to be utilized.
You mention your use case as needing to wait for several REST calls to seed a database. This is easily achievable out of the box using Cypress itself and you don't have to worry about or manage any of the concurrency with anything.
For instance...
Those 5 requests will happen sequentially, not in parallel. You don't even need to do anything special to await them. They will happen one at a time.
Another example...
Same principle.
There's no need to ever coordinate multiple commands in Cypress since only one can ever be processing at a time. This avoids the need to synchronize promises which is why these two interfaces are fundamentally incompatible. They just share some qualities that are similar in a few ways.
If you need to get the results of a previous commands yielded subject that's when you use
.then()
but not that is the only time it is necessary.If you need to bypass this mechanism altogether and avoid using Cypress commands we recommend writing a single
cy.task
to do everything innode
which then avoids the need to utilize Cypress idioms entirely. There you have ultimate control of whatever it is you'd like to do. Want to race commands together? Go ahead.