Visualize Cypress Command Queue

How to see the scheduled test command using cypress-command-chain plugin.

You can watch my video "Cypress Command Chain Plugin Introduction" that covers the contents of this blog post.

The problem

A lot of times, people new to Cypress get an unexpected result while trying to print a value. For example, the following (incorrect) test is supposed to print the number from the page.

1
2
3
4
5
6
7
8
9
10
11
12
// INCORRECT, does not print "n"
it('prints a number of projects', () => {
let n
cy.visit('/')
cy.get('#projects-count')
.invoke('text')
.should('match', /^\d+/)
.then((text) => {
n = Number(text.split(' ')[0])
})
cy.log('number', n)
})

Cy.log does not print "n"

Hmm, why is the n variable empty?

Cypress command queue

When Cypress runs through the test, it first queues all commands, and then starts executing them. Each command with its arguments is added to the list. It is almost like instructions you might write down and give to a human tester to run later. The above code would be something like this if I ask you to test the page:

  • visit the index page
  • get the element with id "project-count"
  • confirm the text matches the regular expression /^\d+/
  • convert the text to a number
  • print the string "number" and undefined
  • START the test

Wait, why is the last instruction says to print "number" and undefined? Because these are the parameters the test uses when calling cy.log('number', n). At the moment of the call, the value of n is still undefined. It will be set much later.

Visualize the command queue

To better show the queued up commands and make it clear, I have written the cypress-command-chain plugin. Cypress emits events every time a command is queued, started, and ended. You can see these events yourself:

1
2
3
Cypress.on('command:enqueued', (command) => {
console.log('command enqueued %s with args %o', command.name, command.args)
})

By inspect the command.args you can see the arguments at the moment cy.log('number', n) but before the test starts running.

Arguments passed with each command when they were queued

Cypress Command Log shows the current test command and all finished commands. I have written the plugin cypress-command-chain to show all enqueued commands. This makes it clear which commands are scheduled to run and their arguments. The plugin even warns you if any of the arguments have undefined value, since it is a sign of a problem.

cypress-command-chain plugin shows the commands and their arguments

The fix

From the command queue, we see that we call cy.log('number', n) too soon. We need to call the log command after the value of n is set, which happens inside the .then(...) callback.

1
2
3
4
5
6
7
8
9
10
it('prints a number of projects', () => {
cy.visit('/')
cy.get('#projects-count')
.invoke('text')
.should('match', /^\d+/)
.then((text) => {
const n = Number(text.split(' ')[0])
cy.log('number', n)
})
})

The test goes by too quickly, let's add cy.wait just to slow it down so we can see the Cypress command queue in action.

1
2
3
4
5
6
7
8
9
10
11
it('prints a number of projects', () => {
cy.visit('/')
cy.get('#projects-count')
.invoke('text')
.wait(3000)
.should('match', /^\d+/)
.then((text) => {
const n = Number(text.split(' ')[0])
cy.log('number', n)
})
})

Notice how the cy.log command is added later when the value of "n" becomes known

Now you can see that the test starts with a queue of commands ending at .then cb() command. The test runner does not know about the cy.log command yet. Only when it reaches the .then cb() callback function, it runs the cy.log and inserts it into the queue, and then continues executing the commands. When cy.log is called inside the .then callback, the value of n is known, so the command enqueued shows "number", 355 as expected.

Tip: cy.then command might confuse some people, since it reminds them of JavaScript promises. This is why I suggest renaming it to cy.later for clarity.

Chains

Cypress command queue and fluent syntax make it simpler to "pass the value forward" instead of getting into a variable. Thus the above test could be written as:

1
2
3
4
5
6
7
8
9
10
11
12
13
it('prints a number of projects', () => {
cy.visit('/')
cy.get('#projects-count')
.invoke('text')
.wait(5000)
.should('match', /^\d+/)
.invoke('split', ' ')
.its(0)
.then(Number)
.then((n) => {
cy.log('number', n)
})
})

The above fluent chain shows nicely

The command chain for the above test

The best approach to writing concise tests like this in my opinion is to see more examples. Read my Cypress blogs and watch the Cypress Tips & Tricks YouTube videos to learn and check out my collection of commands and recipes at glebbahmutov.com/cypress-examples/.

See also