A team I worked with had three copies of their test suite, one per environment. They had drifted apart over a year. A bug fix landed in the staging copy and never reached the production one, and nobody noticed until the same bug shipped twice.
One suite, many environments, is the only version of this that survives.
The base of it you already know: baseUrl in config, overridden at run time.
npx cypress run --config baseUrl=https://staging.example.com
For anything beyond an address, use a config file per environment. cypress/config/staging.json:
{
"baseUrl": "https://staging.example.com",
"env": {
"apiUrl": "https://api.staging.example.com",
"userEmail": "qa@example.com"
}
}
And run with it:
npx cypress run --config-file cypress/config/staging.json
Now the important rule, which is the whole lecture in one line.
⚠️ Your tests must not know which environment they are in.
The moment a spec contains
if (Cypress.env('name') === 'staging'), you have started building the three-copies problem again, just inside one file. Those branches multiply, and eventually a test only really passes in one place while looking like it covers all three.Configuration decides where. Tests decide what. Keep the two apart.
There is one honest exception, and it is worth naming so you recognise it. Some tests really cannot run everywhere: a test that creates real payment records has no business running against production. Handle that by selecting which tests run, not by branching inside them. Tag the spec and exclude it:
npx cypress run --spec "cypress/e2e/smoke/**"
Three practical points from real projects.
Data differs between environments. A test asserting "8 contacts" passes locally with seeded data and fails on staging where the count is different. Assert on your own data, created by the test, not on totals you do not control.
Production usually gets read-only smoke tests. Load the page, log in, check the dashboard renders. You are confirming the deployment worked, not exercising features against real customer data.
Speed differs too. Staging is often slower than your machine. That is a reason to make waits condition-based, never a reason to raise every timeout.
Remember this: one suite, configuration outside the tests, and no environment names in your specs.
Reference: Cypress configuration.