Cypress 12’s breaking changes are primarily driven by a significant shift in how it handles dependencies and its internal architecture, specifically impacting plugin loading and configuration.

Common Causes and Fixes

  1. cypress.config.ts instead of cypress.json:

    • Diagnosis: Cypress 12 defaults to cypress.config.ts (or .js) for configuration. If you’re still using cypress.json, Cypress won’t load your configuration.
    • Command: Run ls cypress.config.ts or ls cypress.config.js in your project root.
    • Fix:
      • If you have a cypress.json, rename it to cypress.config.ts (or .js).
      • If you have specific TypeScript types you want to leverage, create cypress.config.ts and migrate your JSON content. For example, if cypress.json had:
        {
          "baseUrl": "http://localhost:3000",
          "viewportWidth": 1280,
          "viewportHeight": 720
        }
        
        Your cypress.config.ts should look like:
        import { defineConfig } from 'cypress';
        
        export default defineConfig({
          e2e: {
            baseUrl: 'http://localhost:3000',
            setupNodeEvents(on, config) {
              // implement node event listeners here
            },
          },
          viewportWidth: 1280,
          viewportHeight: 720,
        });
        
    • Why it works: Cypress 12 prioritizes the .config.ts and .config.js files for its primary configuration, offering better type safety and extensibility. The e2e key within defineConfig is now the standard for End-to-End specific configurations.
  2. Plugin API Changes (setupNodeEvents):

    • Diagnosis: The setupNodeEvents function signature in your cypress.config.ts (or .js) has changed. It no longer directly receives the config object as the second argument. Instead, you access and modify the configuration within the setupNodeEvents function.
    • Command: Inspect your cypress.config.ts file for the setupNodeEvents function.
    • Fix:
      • Before (Cypress < 12):
        // cypress/plugins/index.ts (or similar)
        module.exports = (on, config) => {
          // modify config here
          return config;
        };
        
      • After (Cypress 12+):
        // cypress.config.ts
        import { defineConfig } from 'cypress';
        
        export default defineConfig({
          e2e: {
            setupNodeEvents(on, config) {
              // `config` object is now passed into setupNodeEvents
              // Example: Add an environment variable
              config.env.MY_CUSTOM_VAR = 'some-value';
              return config; // MUST return the config object
            },
          },
        });
        
    • Why it works: This change standardizes configuration loading. The setupNodeEvents function is now the single point of entry for modifying the Cypress configuration object before tests run, ensuring consistency and predictability.
  3. Deprecated cypress.env.json:

    • Diagnosis: Cypress 12 deprecates cypress.env.json for defining environment variables. These variables are now expected to be managed via the env object in cypress.config.ts or through system environment variables.
    • Command: Check your project for a cypress.env.json file.
    • Fix:
      • Move the key-value pairs from cypress.env.json into the env object within your cypress.config.ts.
      • Before (Cypress < 12):
        // cypress.env.json
        {
          "API_URL": "http://localhost:5000",
          "USER_EMAIL": "test@example.com"
        }
        
      • After (Cypress 12+):
        // cypress.config.ts
        import { defineConfig } from 'cypress';
        
        export default defineConfig({
          e2e: {
            setupNodeEvents(on, config) {
              config.env.API_URL = process.env.API_URL || 'http://localhost:5000'; // Example with fallback to system env var
              config.env.USER_EMAIL = 'test@example.com';
              return config;
            },
          },
          // Or directly if not using setupNodeEvents for this:
          // env: {
          //   API_URL: 'http://localhost:5000',
          //   USER_EMAIL: 'test@example.com',
          // },
        });
        
    • Why it works: Consolidating configuration and environment variables into cypress.config.ts or system environment variables simplifies management and reduces the number of configuration files Cypress needs to parse.
  4. browser Field in cypress.config.ts:

    • Diagnosis: The browser field, which previously allowed specifying a default browser directly in cypress.json, is now part of the e2e object in cypress.config.ts.
    • Command: Look for a top-level browser key in your cypress.json (if you haven’t migrated yet) or cypress.config.ts.
    • Fix:
      • If you had:
        // cypress.json
        {
          "browser": "chrome"
        }
        
      • Migrate to:
        // cypress.config.ts
        import { defineConfig } from 'cypress';
        
        export default defineConfig({
          e2e: {
            // ... other e2e config
            browser: 'chrome', // Or 'firefox', 'edge', etc.
          },
        });
        
    • Why it works: This change groups all End-to-End specific configurations under the e2e namespace, providing a clearer hierarchical structure for your Cypress settings.
  5. component Configuration Moved:

    • Diagnosis: If you use Cypress for Component Testing, the component configuration object has also moved to be a child of the defineConfig export, similar to e2e.
    • Command: Check your cypress.config.ts for a top-level component object and ensure it’s nested correctly.
    • Fix:
      • Before (Cypress < 12, if cypress.json was used for some parts):
        // cypress.json
        {
          "component": {
            "devServer": {
              "framework": "react",
              "bundler": "webpack"
            }
          }
        }
        
      • After (Cypress 12+):
        // cypress.config.ts
        import { defineConfig } from 'cypress';
        
        export default defineConfig({
          // ... e2e config
          component: {
            devServer: {
              framework: 'react',
              bundler: 'webpack',
            },
          },
        });
        
    • Why it works: This mirrors the e2e configuration structure, creating a consistent way to define settings for different testing types within a single configuration file.
  6. Node.js Version Requirement:

    • Diagnosis: Cypress 12 requires Node.js v16 or higher. If your CI environment or local machine is running an older version, Cypress will fail to initialize.
    • Command: Run node -v in your terminal.
    • Fix: Update your Node.js installation to v16 or a later LTS version (e.g., v18, v20). This often involves using a Node Version Manager (NVM) like nvm or fnm.
      • Example using nvm:
        nvm install 18 # Installs Node.js v18
        nvm use 18     # Sets v18 as the current version
        node -v        # Verify installation
        
    • Why it works: Newer Node.js versions include updated JavaScript runtimes and APIs that Cypress leverages for its internal operations and plugin execution.

After fixing these, you’ll likely encounter EACCES permission errors if your node_modules directory has incorrect ownership, requiring you to sudo chown -R $(whoami) node_modules or clear and reinstall dependencies.

Want structured learning?

Take the full Cypress course →