Context
I am having trouble trying to understand when asynchrony in Cypress should be handled by the developer and when not (because it is handled under the hood).
Consider these 2 tests:
A)
it('stackoverflow example',() => {
cy.get('#soSection').should('contain', 'Stack Overflow').then(() =>{
cy.get('#soButton').click().then(() => {
cy.get('#soSection').should('not.contain', 'Stack Overflow');
});
});
});
B)
it('stackoverflow example',() => {
cy.get('#soSection').should('contain', 'Stack Overflow');
cy.get('#soButton').click();
cy.get('#soSection').should('not.contain', 'Stack Overflow');
});
- #soSection will contain 'Stack Overflow' until we click the button. Therefore, both lines should run someway synchronously.
- (A) uses then which enables you to work with the subject yielded from the previous mand. (B) does not use it.
What I've tried so far
I've run both (A) and (B) tests. Both methods work well and behave the same way.
Doubts
- If we needed to use the previous subject then we should only use the method (A) with chainables. Are there any other cases where we MUST use chainables like in (A) example?
- Look at the 3 lines of code in (B). We expect those 3 lines run in that order to success. However, Cypress claim to run asynchronously. Therefore, how can we be sure those 3 lines will run in the order expected?
Context
I am having trouble trying to understand when asynchrony in Cypress should be handled by the developer and when not (because it is handled under the hood).
Consider these 2 tests:
A)
it('stackoverflow example',() => {
cy.get('#soSection').should('contain', 'Stack Overflow').then(() =>{
cy.get('#soButton').click().then(() => {
cy.get('#soSection').should('not.contain', 'Stack Overflow');
});
});
});
B)
it('stackoverflow example',() => {
cy.get('#soSection').should('contain', 'Stack Overflow');
cy.get('#soButton').click();
cy.get('#soSection').should('not.contain', 'Stack Overflow');
});
- #soSection will contain 'Stack Overflow' until we click the button. Therefore, both lines should run someway synchronously.
- (A) uses then which enables you to work with the subject yielded from the previous mand. (B) does not use it.
What I've tried so far
I've run both (A) and (B) tests. Both methods work well and behave the same way.
Doubts
- If we needed to use the previous subject then we should only use the method (A) with chainables. Are there any other cases where we MUST use chainables like in (A) example?
- Look at the 3 lines of code in (B). We expect those 3 lines run in that order to success. However, Cypress claim to run asynchronously. Therefore, how can we be sure those 3 lines will run in the order expected?
1 Answer
Reset to default 4You should read the page called Introduction to Cypress from the Official documentation, or more generally everything under the "Core Concepts" menu. Most of these topics and concepts are explained there.
Command yields
Your assumption is correct, if you need the previous subject you need to use then
since Cypress is async and doesn't return the subject. Instead it yields it. This is stated as a core concept of subject management:
Cypress mands do not return their subjects, they yield them. Remember: Cypress mands are asynchronous and get queued for execution at a later time. During execution, subjects are yielded from one mand to the next, and a lot of helpful Cypress code runs between each mand to ensure everything is in order.
The subject management section shows yielding examples:
Some methods yield
null
and thus cannot be chained, such ascy.clearCookies()
.Some methods, such as
cy.get()
orcy.contains()
, yield a DOM element, allowing further mands to be chained onto them (assuming they expect a DOM subject) like.click()
or evency.contains()
again.
Each mand yields something different. You can check this in the documentation for each mand. For example, cy.children
's yield section states that it returns a DOM element.
Technically you doesn't even need to add cy.
before each mand, since all of them return cy
making every mand chainable. Usually you only need to use .cy
at the start of a mand chain (like in the beginning inside a then
block). This is more of a code style question.
Command execution
When you run the 3 cy.get
lines the mands themselves are not going to be executed, they are just going to be added to a queue by Cypress. Cypress will track your mands and run them in order. Each mand has a timeout and will wait for the mand condition to be satisfied in that time frame.
As the Commands Are Asynchronous section states:
Cypress mands don’t do anything at the moment they are invoked, but rather enqueue themselves to be run later. This is what we mean when we say Cypress mands are asynchronous.
Technically I should cite the whole page of the documentation because it is really well structured, concise and detailed with a lot of example code. I highly remend reading it.
Summary
In short, with cy.
calls you queue up mands to run for Cypress. If you need to run your own synchronous code, or need the yielded subject you should add a .then()
after the Cypress mand you would like to run that code. You could write all your consecutive mands in a .then()
but it is unnecessary, you just recreate the JS then
Christmas tree of doom AKA the callback hell. So only use then
if you need the yielded subject of the previous mand or want to inject synchronous code. Of course, after some synchronous code (like an if
condition) you need to call cy.
again and they will be queued up inside that then
. Whether you add your remaining cy.
mands inside the then
or one or more levels above, depends on if you need to work on top of the stuff happening inside the given then
block or how you prefer to style your code. Here is an example from the docs modified for demonstration:
it('test', () => {
cy.visit('https://app.')
// these lines will be queued up and will be run by Cypress in order
cy.get('ul>li').eq(4)
cy.get('.nav').contains('About')
// we want to use the .user field's value
cy.get('.user')
.then(($el) => {
// this line evaluates after the .then() executes
let username = $el.text()
// synchronous code to decide which mands to run (queue up)
if (username) {
// "queue up" other mands
cy.contains(username).click()
cy.get('ul>li').eq(2)
} else {
cy.get('My Profile').click()
}
})
// mand that doesn't depend on data inside the `then` block
cy.get('.status').contains('Done')
})