Q45 of 48 · Cypress
How do you handle environment-specific configuration in Cypress (dev/staging/prod)?
Short answer
Short answer: Use a single `cypress.config.ts` plus per-env overrides via env vars (`CYPRESS_baseUrl`, `CYPRESS_env_*`) or a small env-specific config loader in `setupNodeEvents`. Avoid one config file per environment — it diverges over time. Secrets come from CI vars, not committed files.
Detail
The two anti-patterns to avoid:
- Hard-coded URLs in specs.
cy.visit('https://staging.example.com/login')— spec works in one env only. - Multiple full config files.
cypress.dev.config.ts,cypress.staging.config.ts— they diverge over time, and the differences become hard to track.
The clean approach is one config file with environment-driven overrides:
// cypress.config.ts
export default defineConfig({
e2e: {
baseUrl: process.env.CYPRESS_BASE_URL ?? 'http://localhost:3000',
setupNodeEvents(on, config) {
const env = process.env.CYPRESS_TARGET_ENV ?? 'dev';
// Load env-specific extras from a small JSON file
const envConfig = require(`./cypress.env.${env}.json`);
config.env = { ...config.env, ...envConfig };
return config;
},
},
env: {
apiUrl: process.env.CYPRESS_API_URL ?? 'http://localhost:4000',
},
});
The env-specific JSONs hold non-secret per-env values (feature flags to expect, third-party sandbox URLs):
// cypress.env.staging.json
{
"featureXEnabled": true,
"stripePublicKey": "pk_test_..."
}
Secrets (passwords, API keys for test users) come from CI env vars, never committed:
CYPRESS_password=$STAGING_TEST_PASSWORD npx cypress run
Anything prefixed CYPRESS_ lands in Cypress.env(...) automatically. Anything else is just a Node env var available in setupNodeEvents.
For per-env behaviour in specs:
const env = Cypress.env('targetEnv') ?? 'dev';
it('handles a feature that only exists in staging+', () => {
if (env === 'dev') return;
// ...
});
Better: tag specs and skip via @cypress/grep so the conditional logic isn't inside the spec.
Production testing is its own conversation. Most teams don't run E2E against prod because the side effects (test orders, fake users polluting analytics) are dangerous. If you must, use a dedicated test tenant + read-only smoke tests + clear data isolation.
// EXAMPLE
.github/workflows/cypress-staging.yml
name: cypress-staging
on: [push]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npx cypress run
env:
CYPRESS_BASE_URL: https://staging.example.com
CYPRESS_API_URL: https://api-staging.example.com
CYPRESS_TARGET_ENV: staging
CYPRESS_password: ${{ secrets.STAGING_TEST_PASSWORD }}
CYPRESS_adminEmail: admin+e2e@example.com
CYPRESS_viewerEmail: viewer+e2e@example.com