Cypress v10 Environment Variables

How to pass the user values to Cypress v10+ test runner.

Cypress tests have two different sets of values: the config values and the user values. The config values are controlling the test runner itself: the spec pattern, the viewport resolution, even the baseUrl is the config value (which is why you need to set it using the config block, see the video How to correctly use the baseUrl to visit a site in Cypress). The user values are everything else: the username and the password to enter into the site under the test, the API key to use with the cy.request command, the item name to search for. These values are called in Cypress-speak "env values" and you can access them using the Cypress.env command.

Cypress v10 has merged the plugins file with the cypress.json file into a single cypress.config.js file. By adding the component testing into the mix, the config file is confusing: where should we add our user values? Should they go into the top-level env object? Or be under the e2e property? What if we put them into both places? And how can we control these values using the setupNodeEvents callback? Let's start playing with the source code in bahmutov/cypress-env-v10-example repo:

cypress.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { defineConfig } = require('cypress')

module.exports = defineConfig({
// our project does not use fixture files
fixturesFolder: false,
env: {
// user values block 1
level: 1,
root: true,
},
e2e: {
// our project does not need the support file
supportFile: false,
env: {
// user values block 2
level: 2,
e2e: true,
},
setupNodeEvents(on, config) {
// can we control the "env" object here?
},
},
})

So what do we see for the above configuration? Launch the Test Runner and look at the "Settings" tab right under the "Specs" tab. Look at the "Project Settings". Scroll down to the "Resolved configuration", which is a large object. We see only the environment variables from the e2e block. The top level config object is meant only for the test runner's own configuration properties.

The resolved Cypress env object

So here is our first find: no root level env block.

You should place the env object in the e2e object

Custom logic

What about the setupNodeEvents callback? What can it do for our user values? Notice that it gets the config passed as the second argument. Let's print the config.env to the terminal.

1
2
3
4
5
6
setupNodeEvents(on, config) {
// can we control the "env" object here?
console.log(config.env)
},
// prints
{ level: 2, e2e: true }

Great, so whatever you see in the resolved project settings is also passed into the setupNodeEvents callback. But there is more: you can change the config.env object in that callback. You can remove / change / add new values to that object! Just remember to return the updated config object from the setupNodeEvents callback (I forget to return it regularly, and then curse myself hours later when I notice the mistake).

The cypress.config.js file runs from a Node (Electron) Cypress process, thus it can access your local environment, unlike the spec or support files that run in the browser. Thus we can access the process environment variables, or read local files, etc. For example, let's say we want to pass the username and the password to our tests. We could use the process environment variables to do this securely:

1
$ USERNAME=Joe PASSWORD=Smart12345 npx cypress open

Here is what we could do in our cypress.config.js file to use the USERNAME and the PASSWORD values

1
2
3
4
5
6
7
8
9
10
setupNodeEvents(on, config) {
// set any additional user values
config.env.registered = true
config.env.user = {
name: process.env.USERNAME,
password: process.env.PASSWORD,
}
// REMEMBER TO RETURN THE CHANGED CONFIG OBJECT
return config
},

Open the Test Runner with USERNAME=Joe PASSWORD=Smart12345 npx cypress open command and see the resolved config; it now includes the e2e.env object plus the values added by the setupNodeEvents callback

The resolved env object with inserted values

💡 Windows OS has its own syntax for setting the process environment variables. No matter what your operating system is, I recommend using as-a to inject such values.

Read JSON file

Since cypress.config.js file is JavaScript, you can code up reading JSON file (or any other type of file) and add those values to the config.env object to be returned from setupNodeEvents function:

1
2
3
4
5
6
7
8
9
10
const { readFileSync } = require('fs')
setupNodeEvents(on, config) {
const text = readFileSync('./settings.json')
const values = JSON.parse(text)
config.env = {
...values
}
// REMEMBER TO RETURN THE CHANGED CONFIG OBJECT
return config
},

Tip: you can even control the JSON file to load by passing it via --env <name> parameter

1
2
3
4
5
6
7
8
9
10
11
const { readFileSync } = require('fs')
setupNodeEvents(on, config) {
const envName = config.env.name
const text = readFileSync(`./${envName}.json`)
const values = JSON.parse(text)
config.env = {
...values
}
// REMEMBER TO RETURN THE CHANGED CONFIG OBJECT
return config
},

You can read the environment values for QA environment from qa.json file by using cypress run --env name=qa

Cypress prefix

Instead of reading the process.env values in the setupNodeEvents callback, we can let Cypress automatically read these values. Cypress is nice enough to automatically read any process environment values that start with the CYPRESS_ prefix, and if the name does not match any built-in config values, it is added to the env object automatically. Let's remove our custom logic from the cypress.config.js file

cypress.config.js
1
2
3
4
// go back to printing the parsed `config.env` object
setupNodeEvents(on, config) {
console.log(config.env)
}

We could have achieved the same env object by opening Cypress with the following process environment variables

1
2
3
4
5
6
7
8
9
10
$ CYPRESS_registered=true \
CYPRESS_user='{"name":"Joe","password":"Smart12345"}' \
npx cypress open

{
level: 2,
e2e: true,
user: { name: 'Joe', password: 'Smart12345' },
registered: true
}

Cypress automatically parses such variables, making sure they have the right type, and any JSON stringified objects are parsed into the JS objects.

Set env variable from the plugin

Some of the plugins like @bahmutov/code-coverage use the config.env object to set the variable to let the browser code know the plugin has been registered:

1
2
3
4
5
6
7
8
9
10
11
12
13
// plugin code
module.exports = registerPluginX(on, config) {
// init the plugin Node code
config.env.registeredX = true
return config
}
// your Cypress config file
setupNodeEvents(on, config) {
require('cypress-plugin-x')(on, config)
// IMPORTANT: return the config
// with the "env" object changed by the plugin
return config
}

Recommended reading