Best Practices on Sharing State
Sharing state across multiple tests is considered a bad practice. Consider the following passage from Cypress’s “Best Practices” docs:
You only need to do one thing to know whether you’ve coupled your tests incorrectly, or if one test is relying on the state of a previous one. Change
it
toit.only
on the test and refresh the browser. If this test can run by itself and pass - congratulations you have written a good test. If this is not the case, then you should refactor and change your approach.”
As we describe in detail in our regression testing guide, your tests should be as loosely coupled as possible. However, this does mean that for common operations like logging in, you’ll need to execute those same steps in every single test. In this article, we’ll cover how to share browser state across tests without violating the principle described above.
How to Persist Browser State between Cypress tests
Browsers provide a few places to persist the state of your application across requests:
- Cookies
- LocalStorage
- SessionStorage
In this section, you’ll see several methods to save browser state in these locations. You’re going to build a simple web application to test: a web page with buttons that create browser state when clicked.
Setup
This tutorial assumes that you already have the following installed on your local machine:
- Node.js version 12 or greater.
- Cypress version 10 or greater.
- Cypress prerequisites for your operating system.
Let’s begin by creating a directory for this project called cy-state-demo
. After that, open the terminal and navigate
to cy-state-demo
. Inside your terminal, run the following commands:
|
|
Those commands will install cypress and a simple development server for your web page.
Your next task is to build the web application you’ll be testing. First, create a folder called build
inside
cy-state-demo
. Inside build
, create two files: index.html
and script.js
.
If your local development environment is either Linux or Mac, you can run the following commands:
|
|
At this point, the structure of cy-state-demo
should look like this:
Next, open build/index.html
and copy the following code into it:
|
|
This HTML contains two things:
- the markup for the buttons that we’ll use to set state throughout this demo
- a script tag to link the JavaScript.
Next, we need JavaScript to make the buttons do their job. Open script.js
and place the following in it:
|
|
The JavaScript above is a sequence of click event listeners for all the buttons. The listeners set values in the various types of browser storage.
The first two event listeners create two cookies, the third event listener modifies the value of a single cookie, and the final event listeners set keys in LocalStorage and SessionStorage respectively.
Next, start the development server by running the following in your terminal:
|
|
That command should output text that looks like the following:
|
|
While the command is running, you can access the web page at localhost://8080. That’s all the setup you need! Now let’s start building tests..
Persisting State With Cookies
Leave the server running and open a new terminal window. Next, run the following command:
|
|
This will open the Cypress GUI. Select the E2E testing type, and choose your preferred browser for testing. This tutorial will use the Electron browser.
At this point, you should see a screen like this one:
Select the ‘Create new empty spec’ option, and name it cookies.cy.js
. Cypress will create a default passing test for
you. At this point, if you go back to your editor, the structure of your project should look like this:
Open cypress.config.js
, and add the following code into the e2e
object:
|
|
Then open cookies.cy.js
and replace its contents with the following:
|
|
But what does this code do? It starts with a beforeEach
hook that navigates to the web application.
The first it
block clicks the set cookie button and then asserts that the cookie was set.
The second test asserts that Cypress cleared the cookie.
The third test sets the cookie again, and then uses the Cookies.PreserveOnce
method to tell Cypress not to clear that
cookie in the next test.
The fourth test makes sure the cookie was preserved, and the fifth test checks that the cookie was only preserved once.
Note: The PreserveOnce method
is currently deprecated, and will be removed in a future Cypress version. It is
mentioned here for two reasons:
- Some users use older Cypress installations
- Its intended replacement, the Session API, is still experimental.
If you run the file and return to the Cypress GUI, you should see that all tests passed:
There’s another, more permanent way to preserve cookies: Cookies.Default
. Cypress will whitelist any cookies that
match the keys you pass to this function.
In other words, Cypress won’t clear those cookies between tests. This works differently from Cookies.PreserveOnce
,
which will only preserve a cookie for the next test.
Let’s write a few tests to illustrate.
Open the file cypress/support/e2e.js
and paste the following code into it:
|
|
Create a new file in the cypress/e2e
folder called defaultCookie.cy.js
. Put the following code into it:
|
|
This looks like a lot of code, but the tests are a lot like the previous set. The first test uses the button to set a cookie with the key ‘default’, and the next test makes sure that the cookie remains set. After that, the cookie’s value is modified, and the last test checks that the cookie remains unchanged.
Persisting State With Local Storage
Next, we’ll explore how to persist LocalStorage between tests. Your first option is a workaround that comes from this Cypress issue.
It involves creating a couple of custom Cypress commands to store and retrieve the contents of LocalStorage.
Open your cypress/support/commands.js
file and put the following code into it:
|
|
The mSaveLocalStorage
command copies the keys inside localStorage and their values into a plain object which Cypress
won’t clear.
The mRestoreLocalStorage
command iterates over the object you’re using as a store and copies its keys and their values
back into localStorage.
Let’s write a few tests to show how you can use these commands.
Create a new file inside your cypress/e2e
folder called ls.cy.js
, and put these tests in it:
|
|
The first test sets LocalStorage and verifies its contents. The second test asserts that Cypress cleared LocalStorage, then uses the button to set it again. Finally, it saves the current state of LocalStorage with the custom command.
The last test verifies that Cypress cleared LocalStorage again, restores LocalStorage, and then tests that the restoration worked.
Another option for persisting LocalStorage between Cypress tests is to use a plugin. The cypress-localstorage-commands plugin provides several custom Cypress commands. These commands let you save, restore and manipulate LocalStorage. They also let you disable LocalStorage and test your application in that state.
You’re going to write a few tests to show you how to get started with the plugin.
Open a new terminal window and run the following command to install the plugin:
|
|
Next, open cypress/support/commands.js
and add the following line of code:
|
|
Create a new file inside cypress/e2e
called lsPlugin.cy.js
and place the following tests in it:
|
|
The first test starts by setting the value of LocalStorage. It then uses the cy.saveLocalStorage
command provided by
the plugin to store a snapshot of LocalStorage.
The next test asserts that LocalStorage is now empty. The last test uses the cy.restoreLocalStorage
command to restore
the contents of LocalStorage, and test that the restoration worked.
The plugin can do much more than this, but that’s outside the scope of this article. If you’d like to learn more, consult its documentation.
Persisting State With Session Storage
The way Cypress handles SessionStorage can be a bit inconsistent. In some cases, Cypress clears SessionStorage as it should, but there’s also a long-open issue about Cypress not clearing SessionStorage between tests.
If Cypress is clearing sessionStorage and you want to persist it, adapt the localStorage workaround like this:
|
|
If you want to clear sessionStorage between tests, you can do so manually:
|
|
Persist State with the Session API
The Cypress session API, introduced in Cypress 8.2.0, is an API designed to help your tests run faster by allowing you to build up state (in LocalStorage, SessionStorage, and cookies) and cache that state so it can be restored later. The API is currently experimental.
According to the Cypress blog, this is the purpose of cy.session:
While logging in during each test that requires being logged-in is a best practice, the process of logging in can be slow, which people sometimes attempt to work around by logging in just once per spec file in a before hook, or by using the Cypress.Cookies API to persist cookies across tests. However, having tests rely on the state of previous tests is not a best practice, and should be avoided.
The new
cy.session()
command solves this problem by caching and restoring cookies, localStorage and sessionStorage after a successful login. The steps that your login code takes to create the session will only be performed once when it’s called the first time in any given spec file. Subsequent calls will restore the session from cache.
Now that you know what cy.session does, let’s see how to use it.
Because the session API is still experimental, first you need to add the following line to your e2e object in
cypress/cypress.config.js
:
|
|
Note: Amongst other things, enabling the session API means Cypress will clear the page at the beginning of each test, so
cy.visit()
must now be called explicitly at the beginning of each test.
Next, create a file called sessionAPI.cy.js
in the cypress/e2e
folder. Put the following tests in it:
|
|
This code calls cy.session in the BeforeEach hook and passes two arguments to it. The first parameter is the name of the session. The second parameter is the setup function that creates the state cy.session will cache.
In this case, the setup function clicks all the buttons on our web page to set our cookies, localStorage, and sessionStorage. It then asserts that all state was set correctly.
The next two tests are nothing new: Each test asserts that the state is restored after calling cy.visit. The second test exists to show that Cypress cached the state instead of running the setup function again.
Here’s how we know that the setup function is only run once. If you open the cypress GUI and click on the name of each test, you should see something like this:
Then this:
Which shows you that the session was saved and restored from cache.
State management made easy with Reflect
As learned here, Cypress offers several ways to manage browser state in tests. What the different methods have in common is that they all require custom logic and advanced knowledge of Cypress. Thus, managing browser state in Cypress is complex, cumbersome, and error-prone. Now imagine a testing solution that allows you to manage state in tests in a simple and intuitive way, with no code involved. Fortunately for you, it exists and is called Reflect!
Reflect is a no-code testing tool that enables you to easily build and run automated tests for web applications in the cloud. Let’s see what features it offers when it comes to state management.
Assume you want to start a test with some cookies, local storage or session values set. In Cypress, you would have to manually persist them with custom logic. With Refelct, you simply have to specify them in the test creation modal, as shown below:
Setting cookies, localstorage and sessionstorage values has never been easier! After configuring those variables, you can start interacting with the page you want to test in the browser. Reflect will record your actions as you use your site and automatically translates them into a repeatable test you can run at any time.
Drag-and-drops, hovers, file uploads, and state management are just a few of the many features offered by a complete testing platform like Reflect. Try it for free now!
Conclusion
In this article, we went over multiple ways to save and restore browser state in Cypress. You can find the full code for this article here.
Dealing with browser state in Cypress is not easy, and you may be looking for a more complete and intuitive solution. Try out Reflect, a testing tool with built-in support for sharing state across tests.
Happy testing!