Configuring Cypress with Environment Variables
Cypress exposes a dedicated configuration channel for values that change between environments, such as API base URLs, feature flags, or test account credentials. You read these values in a spec with Cypress.env('key'), and they can come from four layered sources: the env block inside cypress.config.js, a cypress.env.json file at the project root, operating system variables prefixed with CYPRESS_ (for example CYPRESS_apiUrl), or the --env flag passed on the command line like npx cypress run --env apiUrl=https://staging.example.com. This lets the same spec file run unmodified against dev, staging, and production.
Cricket analogy: Just as a touring squad's playing eleven changes between a Test match in Perth and an ODI in Mumbai while the underlying strategy stays the same, Cypress.env lets one spec run against different backends without rewriting the plan.
Fixtures for Test Data
Fixtures are static JSON (or other file-type) payloads stored under cypress/fixtures that represent canned data for a test, such as a user profile or a list of products. You load one with cy.fixture('user.json'), which reads the file relative to the fixtures folder and yields its parsed contents to the next chained command. Fixtures are most commonly paired with cy.intercept() to stub network responses deterministically: cy.intercept('GET', '/api/users', { fixture: 'users.json' }) means the test no longer depends on a real backend returning consistent data, which removes a major source of flakiness.
Cricket analogy: It's like a coach using a fixed set of pre-recorded bowling deliveries on a bowling machine to train a batter's cover drive, rather than relying on an inconsistent live bowler each session.
Fixtures can also be aliased for reuse across a test with cy.fixture('order.json').as('orderData'), after which any later command can reference this.orderData inside a function() callback, or you can chain it directly into commands like cy.get('@orderData'). For dynamic scenarios, it is common to load a fixture and then override a couple of fields at runtime, merging it with a value from Cypress.env(), for example setting the userId field to Cypress.env('testUserId') before feeding the object into cy.intercept() as a response body, combining static fixture structure with per-environment dynamic data.
Cricket analogy: It's like a commentator keeping a printed scorecard template (@fixture) on the desk but scribbling in the live run rate each over, blending a fixed structure with fresh numbers.
Config Files per Environment
For larger projects, teams often maintain separate configuration files such as cypress.config.dev.js and cypress.config.staging.js, or use the CYPRESS_CONFIG_FILE / --config-file flag to point the runner at the correct one. Inside each file, the env object can hold environment-specific values like apiUrl, and the setupNodeEvents function can also read process.env at Node runtime to inject secrets that should never be checked into source control. This separation keeps environment differences declarative and auditable instead of scattered across if-statements inside test files.
Cricket analogy: It's like a franchise having separate playing conditions documents for a day match versus a day-night pink-ball Test, each spelling out different rules for the same sport.
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
baseUrl: 'https://app.example.com',
env: {
apiUrl: 'https://api-dev.example.com',
testUserId: 'u_1001',
},
setupNodeEvents(on, config) {
// Inject secrets from the OS at run time, never hardcoded
config.env.apiKey = process.env.CYPRESS_API_KEY;
return config;
},
},
});
// In a spec file:
describe('Order API', () => {
it('loads a fixture and overrides one field with an env var', () => {
cy.fixture('order.json').then((order) => {
order.userId = Cypress.env('testUserId');
cy.intercept('GET', `${Cypress.env('apiUrl')}/orders/1`, order).as('getOrder');
cy.visit('/orders/1');
cy.wait('@getOrder');
cy.contains(order.userId).should('be.visible');
});
});
});
// CLI override:
// npx cypress run --env apiUrl=https://api-staging.example.comCypress resolves environment variables in a defined precedence order, from lowest to highest: values in cypress.config.js's env block, then cypress.env.json, then CYPRESS_-prefixed OS variables, then the --env CLI flag, with the CLI flag always winning if the same key is set in multiple places.
Never commit real secrets like API keys or passwords into cypress.config.js or cypress.env.json if the repository is shared or public. Add cypress.env.json to .gitignore and inject sensitive values through CI/CD secret stores as CYPRESS_-prefixed OS environment variables instead.
- Cypress.env('key') reads values from config env blocks, cypress.env.json, CYPRESS_-prefixed OS vars, or the --env CLI flag.
- The CLI --env flag has the highest precedence and overrides all other sources for the same key.
- Fixtures live in cypress/fixtures and are loaded with cy.fixture(), most often to stub cy.intercept() responses.
- cy.fixture().as('alias') lets you reuse loaded data later via this.alias or cy.get('@alias').
- Fixture data can be merged with Cypress.env() values at runtime for per-environment dynamic test data.
- Separate config files per environment (dev/staging/prod) keep environment differences declarative and out of test logic.
- Secrets must never be committed; use CI secret stores and .gitignore'd cypress.env.json instead.
Practice what you learned
1. Which source of an environment variable takes the highest precedence in Cypress?
2. Where must fixture files be placed by default for cy.fixture('users.json') to find them?
3. What is the most common pairing for a loaded fixture in a Cypress test?
4. How should an OS-level environment variable be named so Cypress.env('apiUrl') can read it?
5. Why should cypress.env.json typically be excluded from version control?
Was this page helpful?
You May Also Like
Component Testing
Understand how Cypress Component Testing mounts individual UI components in a real browser for fast, isolated, high-fidelity assertions.
Handling Authentication in Tests
Learn how to bypass slow UI logins with programmatic authentication and cy.session() caching, and how to handle third-party SSO safely.
Testing File Uploads
Learn how Cypress's built-in selectFile() command tests file inputs and drag-and-drop uploads, and how to verify the resulting network request.