Cypress internal commands cy.now and cy.state

Two Cypress internal commands cy.now and cy.state that you definitely should not use.

Cypress does not have a REPL which is unfortunate: sometimes you just want to run a few Cypress commands after the test has finished to see how to extend the test. Yet the only way you can run a command is by adding it to the spec file, saving it, causing Cypress to re-run the entire test. Unless you use tricks like using cypress-data-session plugin or splitting a long test, waiting for the entire test to finish just to check one command is frustrating.

This is where an internal Cypress command cy.now comes in very handy. This is the only place where cy.now is explained in the Cypress docs:

cy.now command documentation

Pretty sweet, isn't it? But if that warning is not strong enough, let me add my own:

🚨 The cy.now and cy.state commands described in this blog post are internal to Cypress Test Runner. They can change without any warning in the future versions, they may not work in all cases, and they can cause your blood pressure to rise to dangerous levels. Please consult a Cypress doctor before using them.

If you are ok with that, let's continue.

cy.now

So let's say we have opened the DevTools console, the test has finished and we want to visit the site again. In the console run cy.now('visit', '/'). The site loads.

Visit the baseUrl using the cy.now command

Let's say we want to check if a selector works. Try using cy.now('get', '.todo') to see what happens.

Calling cy.get to find all elements with class todo

Hmm, which elements did it find? The cy.now returns a promise, so we should print the results to the console using cy.now('get', '.todo').then(console.log).

Logging the resolved elements to the console

There is another way to see the results of the cy.now commands. They are logged to the Cypress Command Log at the end of the last test (excluding pending or skipped tests). The video below shows me expanding the last test and observing the commands I executed using cy.now command.

Cypress Command Log shows the cy.now commands inside the last test

Note that there are no DOM snapshots for cy.now commands, but you can log the command results by clicking on the command itself.

The cy.now command returns a Promise object, not a Cypress chainable object, thus you cannot chain multiple commands together, which makes it hard to build a REPL.

cy.now use case

Building an entire testing suite by relying on cy.now is a bad idea. But using it sparingly might be necessary. For example, in cypress-data-session I have a few static Cypress.* methods to help with debugging the cached data. Like calling a task by name to print all currently existing data sessions in the plugin process - I don't want the user to type cy.now('tast', ...) to list the sessions, instead I exposed static methods:

cypress/plugin/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// in the plugin file we have a task that prints the sessions and returns them
function cypressDataSessionPlugin(on, config) {
const savedValues = {}

function printDataSessions() {
const n = Object.keys(savedValues).length
console.log('%d data session(s)', n)
Object.keys(savedValues).forEach((key) => {
console.log(' %s: %o', key, savedValues[key])
})

return savedValues
}

on('task', {
'dataSession:print': printDataSessions,
})
}
module.exports = cypressDataSessionPlugin
1
2
3
4
5
6
/**
* Prints data sessions stored in the plugin space
*/
Cypress.printSharedDataSessions = () => {
return cy.now('task', 'dataSession:print')
}

Super, the user can run this command at any time from the console to see the sessions. The browser even shows autocomplete for the Cypress methods, which is convenient.

Calling a static Cypress method that calls cy.now

But what if we want to use the same static method during the test?

cy.state

Take the static method Cypress.printSharedDataSessions. Imagine we want to confirm the plugin process has a specific key present. We want to call the task and get the value and chain assertions on it.

1
2
3
4
5
6
7
8
9
10
it('stores the value', () => {
const key = ...
// somewhere inside the test file
// we get the data session from the plugin file
Cypress.printSharedDataSessions()
.should('not.be.empty')
.and('have.property', key)
.should('be.an', 'object')
.should('have.property', 'data', 'test value')
})

Hmm, we need to run cy.task if the tests are running or use cy.now if the user called Cypress.printSharedDataSessions from the browser console. How do we know if the tests are running or not? This is where the internal cy.state command comes in handy. Again, this is a command NOT meant to be used. The Cypress Documentation site has a few places where it mentions it, like this one:

Cypress documentation mentions cy.state as a way to get the test retries counter

The best way to understand what cy.state has is to look at it in the console. It is a grab bag of everything Cypress Test Runner uses internally.

cy.state returns an object referencing all Test Runner internals

There are a lot of things. Often Cypress public commands like cy.get use cy.state('document') and cy.state('window') to get references to the application's document and window objects without using cy.document() and cy.window() commands. In our case cy.state('ctx')._runnable looks like it might do the trick. If there is a test running, it returns an object. After the tests have finished, it returns undefined. Thus we can we use it to determine if we can call cy.task or must use cy.now to call get the info from the plugin process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Returns true if we are currently running a test
*/
function isTestRunning() {
return Boolean(cy.state('ctx')._runnable)
}
/**
* Prints data sessions stored in the plugin space
*/
Cypress.printSharedDataSessions = () => {
if (isTestRunning()) {
return cy.task('dataSession:print')
}
return cy.now('task', 'dataSession:print')
}

One last detail. Normally cy.* methods can only be called inside a test or a hook. The commands cy.now and cy.state are exceptions.

Note: this blog post was linked from Cypress Advent 2021