Cypress Best Practices strongly advocates for keeping the tests independent of each other.
I strongly agree with this advice. We should be able to run each test by itself and run the tests in any order. But sometimes it is simpler to have one test reuse state left by the previous test. In this blog post, I will show I solve this problem in somewhat ok way by using my plugins.
The dependent tests
Let's start with a spec file with two tests.
1 | let projectName |
🎁 You can find the source code for this blog post in the repo bahmutov/cypress-dependent-test-example.
If the tests run together, everything looks good. The second test gets the random piece of data from the first test.
What if we want to run the second test by itself? Even after running the tests together, if we change it
to it.only
the spec reloads and fails - because the entire spec is evaluated again, and the variable definition let projectName
is undefined by default.
1 | let projectName |
How can we "preserve" the value projectName
?
Storing value in Cypress.env object
We can try using Cypress.env
object to store the projectName
value. It does not solve the problem - Cypress v10 resets the Cypress.env()
object back to the initial project settings before each spec.
1 | it('creates item A', () => { |
We need more permanent storage.
Using cypress-plugin-config
To preserve the values across test re-runs I wrote plugin cypress-plugin-config. It stores the values using window.top.cypressPluginConfig
object. Cypress resets the iframes inside of the browser window (one is the spec iframe, another one is the application iframe), but the top window stays.
1 | import { |
Let's say the spec runs both tests. It passes and creates the "project 7373" data item.
Great, now let's run the second test by itself using it.only
1 | it.only('continues working with data created in the previous test', () => { |
Great - the second test still runs even after the spec reloads. The piece of data generated by the first test is still there, retrieved by the function getPluginConfigValue
.
Unfortunately, that is not the end of the story. While the data is there when the spec file changes on disk, or if the user clicks the "Rerun tests" button, if we do the hard browser reload using Cmd-R keys, the top window is reloaded, and our data is wiped away too.
We need an even more permanent data storage that survives the hard browser reload (or even the browser relaunch)
Store data using cypress-data-session
Cypress tests run in the browser. But the initial process that starts the browser runs in Node and the specs can communicate with that process using the powerful cy.task command. The Node process stays running and can store any data for us, see the video Pass Data From One Spec To Another for example. I have implemented storing data on demand in my universal caching plugin cypress-data-session. Here is how we can utilize it to solve our current problem when one test needs to pass some data to another test:
1 | const { defineConfig } = require('cypress') |
1 | import 'cypress-data-session' |
Ok, let's see the second test by itself
1 | it.only('continues working with data created in the previous test', () => { |
Ok, let's do hard browser reload.
Nice - this is it.
A better way
Is there a better way to write this spec to avoid dependency between the tests? Yes, you can even reuse the same data - but each test will create it if necessary:
1 | import 'cypress-data-session' |
When the tests run together, the project data is created by the first test and reused by the second one
If we run the second test exclusively, the same project is fetched from the plugins process automatically.
Finally, if we do hard reload, the data is loaded again from the plugins process.
The two tests are independent from each other, yet the data is cached.
See also
- A better approach to make independent fast tests is described in the post Change E2E Tests From UI To API To App Actions. Also read Crawl Weather Using Cypress post.
- 📝 Avoid Cypress Pyramid of Doom
- 📝 Pass Values Between Cypress Tests