Pass Values Between Cypress Tests

Use Cypress.env object to pass values from one test to another, if you really need to.

This blog post is continuation of the Avoid Cypress Pyramid of Doom blog post where we extracted two numbers from the page and then compared their sum to the result shown on the page. My final solution was to use beforeEach hook and aliases to write the test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
describe('Use beforeEach hook and number values', () => {
beforeEach(() => {
cy.visit('public/index.html')
cy.get('[name=a]')
.should('have.prop', 'valueAsNumber')
.as('a')
// log the number to the Command Log
.should('be.finite')
cy.get('[name=b]')
.should('have.prop', 'valueAsNumber')
.as('b')
.should('be.finite')
})

it('has values set', function () {
cy.contains('#result', this.a + this.b)
})
})

The test grabs both numbers

🎁 You can find the source code for this blog post in the repo bahmutov/cypress-multiple-aliases.

Possible solutions

Hmm, but what if getting the values from the application is complicated? We don't want to put the logic into the beforeEach hook. Instead we want to write two tests: one test to get the numbers, another test to confirm the displayed sum:

1
2
3
4
5
6
7
it('has two number inputs', () => {
// get two numbers from the input fields
})

it('shows the sum of inputs', () => {
// confirm the result is the sum of two input fields
})

This approach introduced a dependency between the tests. You always have to run the first test before running the second one. Let's say we can live with this. How do we pass the actual values a and b? Hmm, we cannot use in the second test aliases set in the first test; all aliases are reset before each test.

1
2
3
4
5
6
7
8
9
10
11
12
// 🚨 INCORRECT, NOT GOING TO WORK
it('has two number inputs', () => {
// get two numbers from the input fields
cy.wrap(1).as('a')
cy.wrap(2).as('b')
})

it('shows the sum of inputs', function () {
// confirm the result is the sum of two input fields
// 🚨 this.a and this.b are undefined
cy.contains('#result', this.a + this.b)
})

We could store values found in the first test in the variables in the common scope

1
2
3
4
5
6
7
8
9
10
11
// will work, if both tests can access a and b
let a, b
it('has two number inputs', () => {
// get two numbers from the input fields
cy.wrap(1).then(x => a = x)
cy.wrap(2).then(x => b = x)
})

it('shows the sum of inputs', () => {
cy.contains('#result', a + b)
})

I don't like this approach. Each variable requires boilerplate code and can easily be modified by any code in between.

Use Cypress.env

Here is an alternative approach: use the Cypress.env() object. You can synchronously set and get values from this object from any place in your tests (even outside the tests).

1
2
3
4
5
6
// set "name: Joe"
Cypress.env('name', 'Joe')

it('has a name', () => {
expect(Cypress.env('name')).to.equal('Joe')
})

The entire Cypress.env object is reset when the spec starts running. Thus any values we put there remain there until the spec is re-run

1
2
3
4
5
6
7
8
9
10
11
12
// when spec starts, Cypress.env only has
// values set by the config or CYPRESS_ environment variables
expect(Cypress.env('name')).to.be.undefined

it('sets the name', () => {
// set "name: Joe"
Cypress.env('name', 'Joe')
})

it('has a name', () => {
expect(Cypress.env('name')).to.equal('Joe')
})

We can pass values without declaring each variable. In our spec

cypress/e2e/two-tests.cy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
describe('pass value from one test to another', () => {
beforeEach(() => {
cy.visit('public/index.html')
})

it('has two number inputs', () => {
cy.get('[name=a]')
.should('have.prop', 'valueAsNumber')
// log the number to the Command Log
.should('be.finite')
.then((a) => Cypress.env('a', a))
cy.get('[name=b]')
.should('have.prop', 'valueAsNumber')
.should('be.finite')
.then((b) => Cypress.env('b', b))
})

it('has values set', () => {
cy.contains('#result', Cypress.env('a') + Cypress.env('b'))
})
})

Pass values through Cypress.env object

Cypress-map

I have a collection of Cypress query commands for Cypress v12+ in my plugin cypress-map. I have cy.asEnv query command that you can use to save the current subject in the Cypress.env object.

1
2
3
# install the plugin
$ npm i -D cypress-map
+ [email protected]

Here is the updated spec file

cypress/e2e/two-tests-map.cy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'cypress-map'

describe('pass value from one test to another', () => {
beforeEach(() => {
cy.visit('public/index.html')
})

it('has two number inputs', () => {
cy.get('[name=a]')
.should('have.prop', 'valueAsNumber')
// log the number to the Command Log
.should('be.finite')
.asEnv('a')
cy.get('[name=b]')
.should('have.prop', 'valueAsNumber')
.should('be.finite')
.asEnv('b')
})

it('has values set', () => {
cy.contains('#result', Cypress.env('a') + Cypress.env('b'))
})
})

Using cy.asEnv query command from the cypress-map plugin

If you click on the command "asEnv " the saved value (the subject) is printed to the DevTools console. Since the values are available synchronously, you can check them at the start of the second test to ensure they are ready to be used.

1
2
3
4
5
6
it('has values set', () => {
// check saved values
expect(Cypress.env('a'), 'a').to.be.within(1, 10)
expect(Cypress.env('b'), 'b').to.be.within(1, 10)
cy.contains('#result', Cypress.env('a') + Cypress.env('b'))
})

Checking Cypress.env values at the start of the second test

See also