Set All Cypress Env Values Using A Single GitHub Actions Secret

You can pass an entire JSON object of values using a single GitHub Actions secret by saving it as cypress.env.json file.

Let's say you need to pass some values to Cypress tests. You should use Cypress environment variables (not to be confused with process environment variables) and read them using Cypress.env command. For example, you could pass login information to your specs, as describe in Keep passwords secret in E2E tests. On CI, you could store the value as a secret, and then pass it to Cypress. The syntax differs, but for example on GitHub Actions you could use something like this:

1
npx cypress run --env userName=${{ secrets.USERNAME }},password=${{ secrets.PASSWORD }}

It all works if you only have a few simple values to pass. If you have multiple values, or if they are complex JSON objects, passing them via command line is bound to break due to parsing and quotes. Luckily, there are other ways, and this blog post shows one good way for GitHub Actions.

🎁 You can find the source code and see the executed GitHub Actions in the repo bahmutov/cypress-env-example.

First, you could put the values into env key of the cypress.json file

cypress.json
1
2
3
4
5
6
7
8
9
10
11
{
"env": {
"person": {
"name": "Jane",
"age": 25
},
"location": {
"city": "San Francisco"
}
}
}

The test in our case simply checks the values.

cypress/integration/spec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
it('has valid env values', () => {
expect(Cypress.env())
.to.be.an('object')
.and.to.include.keys('person', 'location')
cy.wrap(Cypress.env('person'))
.should('have.keys', 'name', 'age')
.its('age')
.should('be.within', 10, 99)
cy.log('**name:** ' + Cypress.env('person').name)
cy.log('**city:** ' + Cypress.env('location').city)

// save a screenshot of the test runner
// to show what the env values were
cy.screenshot('env', { capture: 'runner' })
})

I ran the test locally and it shows the expected values from the cypress.json file

Cypress checked the env val

To better separate Cypress own configuration values from the user's own values, you could move the env object from cypress.json to cypress.env.json file.

cypress.env.json
1
2
3
4
5
6
7
8
9
{
"person": {
"name": "Jane",
"age": 25
},
"location": {
"city": "San Francisco"
}
}
cypress.json
1
{}

The test passes the same way.

Continuous Integration

Now let's move to CI. We probably will use different values when running the tests. I will add these values as a secret. GitHub Actions allow multiline secrets, so I add a complete JSON object there.

Entire JSON we want to use on CI

Now let's write our workflow file. I will skip using my own Cypress reusable workflows and instead will use the plain Cypress GitHub Action.

.github/workflows/ci.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
name: ci
on: [push]
jobs:
tests:
runs-on: ubuntu-20.04
steps:
- name: Checkout 🛎
uses: actions/checkout@v3

- name: Write the cypress.env.json file 📝
# use quotes around the secret, as its value
# is simply inserted as a string into the command
run: |
echo '${{ secrets.CYPRESS_ENV_CI }}' > cypress.env.json

# https://github.com/cypress-io/github-action
- name: Cypress run 🚀
uses: cypress-io/github-action@v3

# you can use Cypress Dashboard to store the screenshots
# or store them on GitHub and download the zip file
# https://github.com/actions/upload-artifact
- name: Save screenshots 🖼
uses: actions/upload-artifact@v3
if: always()
with:
name: cypress-screenshots
path: cypress/screenshots

As you can see, we echo the secret's value and redirect it to the "cypress.env.json" file, overwriting the existing file. Then we run the tests and save the screenshots folder produced by the tests. A typical run finishes successfully.

GitHub Actions run

We download the zip archive with the screenshots by clicking on it. The Test Runner shows the values from the CI secret were used in the test.

The test received the values from the secrets JSON

Pretty sweet.