How to share variables between tests in Cypress
By Hamid Akhtar, Community Contributor - February 20, 2023
For creating automated tests for your web application, Cypress is a fantastic tool. How eager developers are to create Cypress tests is among the most impressive things. People are literally tripping over one another to integrate a tool into their codebase, which says a lot about this tool. End-to-end tests may become far less intimidating for a JavaScript developer as well. Now, how ‘cypress share variables between tests‘ is going to be the focus of our research.
Best practices for sharing variables in Cypress tests
Cypress issues a command in a series. Each link in the chain is connected to the one before it as well as the one after. Cypress will automatically wait for the previous command to finish, ensuring that you don’t encounter race situations. I’ll use one as an example
cy .get('li') .should('have.length', 5) // ensure the previous command retrieves elements .eq(4) // ensure the previous assertion is successful .click() // ensure the previous command is complete
Again, no command will be executed until the previous one has ended. The test fails (often in 4 seconds) if any of the commands are not completed in a timely manner.
Let’s take a second to consider the scenario from a different angle.
it('assigns value to variable', () => { // Executed instantly, outside of chain console.log('>>> log one') let boardId cy.request('/api/boards') .then( response => { console.log('>>> log two') boardId = response.body[0].id }) // Executed instantly, outside of chain console.log('>>> log three') cy.visit('/board/' + boardId) })
We hope this helps to clarify how console.log() functions work. The id variable, however, is another story. The use of it within the chain appears to be the case. Or is it?
Actually, no. Since it is passed as an argument, it is theoretically passed “from outside” and not in the command chain. This variable was set out at the start of the test. In our test, we are instructing Cypress to run the command.visit() with whatever the value of ‘/board/’ + id is.
Think about the “inside chain vs. outside chain” theory. Let’s review the code once more:
it('captures value in variable', () => { // Instant variable declaration, no wait let identifier cy.request('/api/boards') .then( response => { identifier = response.body[0].id }) // Instant variable usage, no wait cy.visit('/board/' + identifier) })
Now that the issue is more evident, let’s examine the many ways we might pass values in our test. Let’s have a look at at least a few of the many solutions that exist for this.
Run Cypress Tests on Real Devices
Step up the desired code in the command chain
Making ensuring that everything in our command chain is included is the simplest solution. The .visit() function must be used inside the command chain in order to use the updated value. The id will be passed with a new value in this manner. This technique works well when you want to quickly pass a single variable because using multiple .then() functions may result in a “pyramid of doom.”
it('holds value in variable', () => { let boardId // initialize variable cy.request('/api/boards') .then( response => { boardId = response.body[0].id // set value cy.visit('/board/' + boardId) // use the newly set value }) })
Separate logic into several tests
You can divide the logic into different tests and use a “setup” it() function to assign your variables before executing it() block to use that variable because Cypress executes it() blocks one at a time. This method may be fairly constrained, though, since each variable update requires a separate block. Not every it() function is now a test, so it’s not the ideal test design either. This may also result in a strange domino effect, when the failure of one test may be a result of the failure of another test.
Using Hooks
Using before() or beforeEach() hooks is a marginally better method of dividing a test. You are more logically dividing your test this way. You have two phases: the preparation process, which is separate from the test, and the execution phase, which is the test itself. This method also has the benefit of giving you explicit information in the error log when a hook fails.
Using aliases and hooks
Aliases are actually a part of the Cypress-bundled Mocha framework, which is used to run tests. Anytime you use the.as() command, an alias will be created in the Mocha context and may be accessed by using this keyword, as demonstrated in the example. It will be a shared variable, allowing you to share variables between tests in the specification. This keyword, however, must be used with the traditional function expression, function(){}, and cannot be used in functions using the arrow expression () =>{}. Take a look at this example.
beforeEach( function() { cy.request('/api/boards') .as('boardData') }) // using it('utilize variable', () => { ... would not work it('utilize variable', function() { cy.visit('/board/' + this.boardData.body[0].id) })
Using cy.task()
A Node.js server process is active in the background behind Cypress. Node can be used, and temporary data can be kept there. If you want, you can even seed a test database! Values from the last run are still present here as long as you don’t close the Cypress console.
Why do you do this? You can give a value to Node.js by using the command cy.task(). A “get” and “set” command must be written. This will be a piece of cake if you are comfortable with getters and setters like you are in Java.
assignUserId: (value) => { return (userIdentifier = value); } retrieveUserId: () => { return userIdentifier; } cy.get('User').then(($userIdentifier) => { cy.task('assignUserId', $userIdentifier); }); cy.task('retrieveUserId').then((userIdentifier) => { cy.get('User').type(userIdentifier); });
Run Cypress Tests on Real Devices
Using Cypress – Fixtures
Cypress fixtures are used to maintain and save the test data for automation. The fixtures folder contains the fixtures for the Cypress project (example.json file). Basically, it makes it easier to import data from external files.
describe('Testing on Browserstack', function () { //part of before hook before(function(){ //retrieve fixture data cy.fixture('sample').then(function(userData){ this.userData = userData }) }) // test case it('Test Scenario 1', function (){ // navigate to URL cy.visit("https://signup.example.com/register/register.php") //data utilized from fixture cy.get(':nth-child(3) > [width="185"] > input') .type(this.userData.userName) cy.get('#mobno').type(this.userData.phoneNumber) }); });
Code reuse is ensured by Cypress, which enables the same test script to be executed against several fixtures files.
Sharing variables between Test Files using Environment Variables
We can create environment variables that the test automation framework can use globally and that all test cases can access. In our project’s cypress.json file, we can store this kind of customized environment variable.
We must specify the key as “env” in the cypress.json file and then set the value because a customized variable is not exposed by default Cypress configurations.
In the real test, we must also use Cypress.env and pass the value declared in the json file in order to access this variable.
describe('ExampleSite Test', function () { // test case it('Scenario A', function (){ // navigate to application using environment variable cy.visit(Cypress.env('siteUrl')) cy.getCookies() cy.setCookie('cookieName', 'cookieValue') }); });
We now know that Cypress is a test automation framework, and much like other test automation frameworks, it must run the same set of tests in a variety of test environments, including DEV, QA, UAT, and others. However, some values or variables, such as the application URL or credentials, could have different values in various test environments. Cypress offers methods for test scripts to access environment variables in order to deal with such circumstances. Environment variables are what Cypress considers to be all the variables within the “env” tag in the config.json file. Below is an example of its syntax:
{ "env": { "api_key": "api_value" } } // Retrieve all the environment variables Cypress.env() // Retrieve a specific environment variable using its key Cypress.env('api_key')
Next, let’s consider a different instance.
{ "env": { "Param1": "Data1", "Param2": "Data2" } } Cypress.env(); // {Param1: "Data1", Param2: "Data2"} Cypress.env("Param1"); // It will return "Data1"
Run Cypress Tests on Real Devices
The “Cypress.env()” method in Cypress can be used to obtain environment variables.
For your Cypress tests, learn how to specify environment variables on BrowserStack.
Cypress Wrap
When utilizing cypress commands like should, type, or click on an object or jquery element, you may first want to wrap it in order to yield the objects that were placed in it and yield its resolved value.
cy.wrap(entity) cy.wrap(entity, configurations) cy.wrap({Character: 'John'}) const Champion = () => { return 'John' } cy.wrap({ name: Champion }).invoke('name').should('eq', 'John')
- Hero is a javascript object, and Cypress cannot be used to interact with it.
- We use the key name passed with the object Hero in the wrap to assert that it should be equal to Naruto, and it returns true. We then utilize the wrap to convert the object Hero into cypress.
describe('Utilizing Wrap Command', () => { it("Wrapping Various Data Types", () => { // Variable let colorStatement = 'Red Or Blue' cy.wrap(colorStatement).should('eq', 'Red Or Blue') // Object let Character = {name: 'Itachi'} cy.wrap(Character).should('have.property', 'name', 'Itachi') // Array let Characters = ['Itachi', 'Sasuke', 'Naruto', 'Sakura'] cy.wrap(Characters).should('include', 'Sakura') }) })
Closing Notes
You’ve now seen a number of strategies that will help you share variables between tests in Cypress. It attempts to get around problems that engineers and developers get into when testing web apps built using React and AngularJS as well. Moreover, Cypress is an efficient, simple, and reliable tool for testing any browser-based application.
However, it is recommended to run Cypress tests on real devices and browsers to take real user conditions into account and ensure more accurate test results. BrowserStack allows users to run tests on 3000+ real devices and browsers for a comprehensive testing experience.
The debugging tools available in each of BrowserStack’s testing solutions make it simple for users to find, track down, and fix bugs. With BrowserStack, users can receive Appium Logs, Network Logs, Text Logs, Screenshots, and a Video Recording of the test run. Similar features are also provided by Automate, App Automate, Live, and App Live to assist the user in outlining the fault precisely. A user may create effective workflows using BrowserStack’s defect-tracking integrations with JIRA, Trello, Github, and Slack.