How To Share Variables Between Tests in Cypress
Cypress is a useful tool for creating automated tests for your web application. In this article, learn how Cypress shares variables between tests.
Join the DZone community and get the full member experience.
Join For FreeBest 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.
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); });
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"
The “Cypress.env()”
method in Cypress can be used to obtain environment variables.
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 toNaruto
, and it returns true. We then utilize the wrap to convert the objectHero
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')
}) })
Published at DZone with permission of Hamid Akhtar. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments