Free discovery callFree discovery call

Are Your Cypress Tests a Configuration Mess?

A QA Engineer's Guide to Multi-Environment Testing - Part 1

QALast updated: 27 Nov 20246 min read

By Sunčica Kojić

Tired of manually updating test configurations for different environments? Learn how to set up a maintainable, scalable Cypress configuration that your team will thank you for.

The Challenge of Environment Management in Cypress

As a QA engineer, you're likely familiar with this scenario: Your Cypress tests run perfectly in your local environment, but then something breaks in staging. Or perhaps you're managing multiple test suites across development, staging, and production environments, each with their own URLs, credentials, and API endpoints. Sound familiar?

While Cypress provides tools for managing different environments, turning these capabilities into a practical, maintainable configuration requires careful planning. Without the right approach, you might find yourself:

  • Constantly updating hardcoded URLs and credentials
  • Wrestling with merge conflicts from environment-specific changes
  • Struggling to maintain consistency across your CI/CD pipeline
  • Dealing with configuration drift between environments

In this guide (Part 1 of our two-part series), we'll cut through the complexity and show you exactly how to set up a robust Cypress configuration that scales across environments.

Part 2 will deal with additional challenges of testing multi-domain applications.

Let's start with environment configuration, from basic setups to production-grade solutions.

Managing cypress.config.js manually

Let’s look at a simple configuration file below.

For instance, to run tests in a local environment, we must specify a local baseUrl. Transitioning to development or staging requires manually adjusting this value, especially if the staging environment has unique settings, such as basic authentication.

//cypress.config.js

const { defineConfig } = require('cypress');

module.exports = defineConfig({
    e2e: {
        setupNodeEvents() {},
        baseUrl: 'http://localhost:8000',
        // baseUrl: 'https://development.example.com',
        // baseUrl: 'https://staging.example.com',
        supportFile: 'cypress/support/e2e.js',
    },
});

Pros:

  • Quick and Flexible Setup — This method allows for easy setup and quick adjustments.

Cons:

  • Human Error — When configurations are changed manually, mistakes that affect the testing process can easily be made.
  • Merge Conflicts — When multiple team members make changes to the configuration, it can create conflicts that disrupt teamwork.
  • Limited CI/CD Effectiveness — Manual updates can reduce the effectiveness of CI/CD processes by introducing complications that make it harder to maintain smooth, efficient pipelines.
  • Increased Complexity in Testing — While they might seem like a quick fix, manual updates can make the testing process more complicated and less stable overall.

In conclusion, while manually updating configuration files may address immediate testing needs, it ultimately complicates the testing process and undermines stability. Adopting automated configuration management solutions is crucial for building a more robust and adaptable testing environment.

Using separate configuration files for each environment

Separate configuration files can be used to set up different environments in Cypress. For local testing, specify the baseUrl directly in cypress.config.js. To run tests on various environments, we create distinct configuration files in the project’s root directory.

For instance, in the staging environment, we create a file named cypress.staging.js that defines the baseUrl and other environment-specific variables. We do the same for development and production with their respective settings. If staging requires basic authentication, we include the necessary credentials in this configuration file.

//cypress.staging.js

const { defineConfig } = require('cypress');

module.exports = defineConfig({
    e2e: {
        setupNodeEvents() {
        },
        supportFile: 'cypress/support/e2e.js',
        baseUrl: 'https://staging.example.com',
        auth: {
            username: 'your-username',
            password: 'your-password',
        },
    },
});

When the above configuration for each environment is set up, we need to add custom scripts in our package.json file.

//package.json 

"cy:open": "cypress open",
"cy:run": "cypress run",
"cy:run:staging": "cypress run --config-file cypress.staging.js",
"cy:open:staging": "cypress run --config-file cypress.staging.js",

Now you can switch environments by running yarn cy:staging:run or yarn cy:staging:open command in your terminal.

Pros:

  • Better Organization — Keeping configurations separate for each environment helps maintain clarity and organization.
  • Easy Environment Switching You can quickly switch between different environments by referencing the right configuration file, which saves time and reduces the need for manual changes.
  • Consistent Test Execution — With environment-specific settings, tests are more likely to run consistently and as expected across different environments.
  • More Flexible — Having separate configuration files for each environment makes it easier to adapt to changes without affecting other setups.

Cons:

  • More Complexity — Managing multiple configuration files can make things more complicated, especially as the project grows.
  • Risk of Misconfigurations — The more configuration files there are, the greater the chance for misconfigurations, which can lead to unreliable tests.
  • Multiple File Maintenance — Keeping all configurations in sync and up-to-date can be time-consuming, particularly as the number of environments increases.
  • Lack of Clarity — With multiple files to keep track of, it can be hard to know which one to modify, increasing the risk of making mistakes.
  • Troubleshooting Complexity — When tests fail, having multiple configurations to check can make it more difficult to pinpoint the issue.

In summary, using separate configuration files for each Cypress environment improves clarity, enables easy switching between environments, and ensures consistent test execution.

However, this approach can also add complexity, raising the risk of misconfigurations and necessitating diligent maintenance. While it allows for tailored settings, careful management is crucial to avoid issues as the number of environments increases.

Managing environment configurations in a single Cypress configuration file

Running Cypress tests across multiple domains and environments introduced specific challenges, such as managing varying configurations, maintaining consistency across different setups, and ensuring seamless execution across domains.

Existing solutions couldn’t fully address these complexities, so we adjusted our approach to meet these unique demands by customizing our testing framework to ensure reliability, scalability, and reduced maintenance overhead.

The key to implementing this solution is creating a cypress.env.json file, which centralizes environment-specific variables like base URLs, API endpoints, and authentication credentials. This allows us to dynamically adjust the configuration for development, staging, or production tests.

For example, instead of hardcoding the base URL for each test suite, you can simply reference an environment variable that points to the correct URL for the active environment.

//cypressEnvConfig.json

const envConfig = {
  development: {
    default: "https://development.example.com",
  },
  staging: {
    default: "https://staging.example.com",
  },
  production: {
    default: "https://production.example.com",
  },
};
module.exports = { envConfig };

Next, in our configuration file cypress.config.json, we need to implement dynamic configuration based on the CYPRESS_ENV environment variable.

//cypress.config.js

const { defineConfig } = require('cypress');
const { envConfig } = require('./cypressEnvConfig');

module.exports = defineConfig({
    e2e: {
        setupNodeEvents(on, config) {
            const environment = process.env.CYPRESS_ENV || 'local';
            const currentEnvConfig = envConfig[environment];
            config.baseUrl = currentEnvConfig.dahefault;
            config.env.urls = currentEnvConfig;

            return config;
        },
        supportFile: 'cypress/support/e2e.js',
    },
});

Let's break down this setup. The setupNodeEvents function is pivotal in Cypress configuration, ensuring tests are properly set up based on the environment defined by the CYPRESS_ENV variable. If the variable is not specified, it defaults to local.

The function retrieves environment-specific settings from the cypressEnvConfig.json file, including baseURL, and updates the configuration accordingly.

The function then returns the modified config object, ensuring that Cypress tests are executed with the precise configuration tailored to the specified or default environment.

Next, each environment is managed through a dedicated run command that utilizes the CYPRESS_ENV variable to precisely configure the test execution environment.

//package.json
 	     
 	      "cy:open": "cypress open",
        "cy:run": "cypress run",
        "cy:open:development": "CYPRESS_ENV=development yarn cypress open",
        "cy:run:development": "CYPRESS_ENV=development yarn cypress run",
        "cy:open:staging": "CYPRESS_ENV=staging yarn cypress open",
        "cy:run:staging": "CYPRESS_ENV=staging yarn cypress run"

To use the centralized configuration from the cypress.env.json file in your test suite, access the environment variables with the Cypress.env() function. For instance, you can retrieve the default URL by using the following code:

//test.cy.js

const requestUrl = Cypress.env('urls').default;

Pros:

  • Single Source of Truth — By using a single file for environment variables, you avoid handling multiple configuration files, and keeping things organized.
  • Easy to Switch Environments — Environment variables allow dynamic switching, simplifying the process for different environments.
  • Reduced Risk of Errors — Referencing environment variables reduces the chance of accidentally running tests in the wrong environment.
  • Scalability — If you need to add a new environment, you only need to update one file, not create new ones for each environment.
  • Consistency — A single configuration file ensures uniform settings across all environments, reducing discrepancies and improving test reliability.
  • Simplified Maintenance — A single file is easier to maintain than multiple configuration files, reducing the potential for inconsistencies.

Cons:

  • Security Risks — Sensitive data in a single file (e.g., credentials) may expose risks if not secured properly.
  • Configuration Mistakes — Mistakes in the cypress.env.json file can affect all environments if not handled carefully.

This approach allows you to reference the correct URL based on the active environment without hardcoding any values in your tests.

Simplify Environment Management with a Unified Configuration Approach

Handling multiple environments in Cypress is essential for effective testing across various configurations. Relying on manual updates or separate settings can lead to mistakes and make things more complicated, but using one configuration with dynamic environment variables is a more efficient solution.

This method reduces errors and makes it easy to adapt to new environments. It also keeps things simple, so teams can focus on building strong tests and ensuring the app works properly during deployment.

Ultimately, this makes Cypress a powerful tool for end-to-end testing, helping create a faster and more reliable workflow as applications grow.

Continue reading the 2nd part of this series, where we’ll talk about managing multiple domains in Cypress. Just like with environments, testing across different domains can be tricky. But with the right approach, you can handle it easily and keep your tests running smoothly.

Related ArticlesTechnology x Design

View all articlesView all articles
( 01 )Get started

Start a Project