You 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 commands do not return their subjects, they yield them. Remember: Cypress commands are asynchronous and get queued for execution at a later time. During execution, subjects are yielded from one command to the next, and a lot of helpful Cypress code runs between each command to ensure everything is in order.
The subject management section shows yielding examples:
Some methods yield null
and thus cannot be chained, such as cy.clearCookies()
.
Some methods, such as cy.get()
or cy.contains()
, yield a DOM element, allowing further commands to be chained onto them (assuming they expect a DOM subject) like .click()
or even cy.contains()
again.
Each command yields something different. You can check this in the documentation for each command. 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 command, since all of them return cy
making every command chainable. Usually you only need to use .cy
at the start of a command 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 commands themselves are not going to be executed, they are just going to be added to a queue by Cypress. Cypress will track your commands and run them in order. Each command has a timeout and will wait for the command condition to be satisfied in that time frame.
As the Commands Are Asynchronous section states:
Cypress commands 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 commands 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 recommend reading it.
Summary
In short, with cy.
calls you queue up commands 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 command you would like to run that code. You could write all your consecutive commands 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 command 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.
commands 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.com')
// 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 commands to run (queue up)
if (username) {
// "queue up" other commands
cy.contains(username).click()
cy.get('ul>li').eq(2)
} else {
cy.get('My Profile').click()
}
})
// command that doesn't depend on data inside the `then` block
cy.get('.status').contains('Done')
})