Single chain of commands
Cypress test acts like a human user interacting with the web page. A human cannot click two buttons at once - the user can only do one action at a time. Thus the test runner always runs a single command at a time. A command has to fully finish before the next command starts. All commands are queued up at the start of the test, and then execute one by one.
1 | cy.get('selector') // first command |
At the start of the test all commands are "read" and added to the queue
1 | # Queue of commands |
Then the test starts running, reading the commands one by one from the queue and executing them.
👀 You can see the queue by using the plugin cypress-command-chain, read the blog post Visualize Cypress Command Queue.
Dynamic command chain
A command can add other Cypress commands by inserting them into the command chain. For example, here is a conditional test that clicks on the element IF it finds the parent element:
1 | cy.get('selector') |
Here is the command chain of known commands at the start of the test:
1 | # Queue of commands |
That's it. Cypress does not know what is inside the callback function yet. The test starts running, executes the GET "selector"
command, and passes the jQuery object (which could be empty) to the "THEN callback" command. Cypress then adds new commands depending on the if ($el.length)
condition. If there are no elements, it simply finishes the callback, completes the THEN callback
commands and finishes the test. But if there is an element there, Cypress queues up the new commands:
1 | # Updated queue of commands |
Cypress completes the "THEN callback" command and moves on to the next command in the queue "LOG Found ...", etc.
Out-of-band commands
Cypress commands like "THEN callback" can add new commands to the queue. What about if some other test code tries to add a new command? For example, what if we do the following
1 | cy.wait(10_000) // sleep 10 seconds |
You will get an error, Cypress does not know where this command is meant to join the queue.
The same can happen when you try to add new commands from Cypress.on
or cy.intercept(..., req => ...)
callbacks.
1 | cy.intercept('/ajax', req => { |
Nope, the callback res => { ... }
happens while some other Cypress command is running and it cannot insert the cy.log
correctly.
Use the cy.then
command instead
Instead of using setTimeout...
you can use cy.then to add or insert new Cypress commands into the queue.
1 | cy.log('first').then(() => { |
Adding new Cypress commands from cy.then
is super useful for conditional testing and recursive tests.
Add your own queue
You can even store all the data for the commands you want to run and then schedule them for later using the cy.then
command. For example, if you want to print the network data from the server, instead of trying to use cy.log(...)
from the cy.intercept
callback, store the response data and then print it later:
1 | cy.intercept('/ajax', req => { |
Hope it helps.