Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extend cypress.json config to other configuration files?

In my Cypress 10 project, I have the following config files:

  • cypress/cypress.config.js

  • cypress/config/qa.json (Points at my QA environment).

  • cypress/config/staging.json (Points at my Staging environment).

Everything in the below cypress.config.js file is common across both QA & Staging environments:

const { defineConfig } = require("cypress");
const fs = require('fs-extra')
const readDirRecursive = require('fs-readdir-recursive')
const createBundler = require("@bahmutov/cypress-esbuild-preprocessor")
const preprocessor = require("@badeball/cypress-cucumber-preprocessor")
const createEsbuildPlugin = require("@badeball/cypress-cucumber-preprocessor/esbuild")
const stdLibBrowser = require('node-stdlib-browser')
const plugin = require('node-stdlib-browser/helpers/esbuild/plugin')
const mysql = require('mysql')

function queryTestDb(query, config) {
    // creates a new mysql connection using credentials from cypress.json env's
    const connection = mysql.createConnection(config.env.db)
        // start connection to db
    connection.connect()
        // exec query + disconnect to db as a Promise
    return new Promise((resolve, reject) => {
        connection.query(query, (error, results) => {
            if (error) reject(error)
            else {
                connection.end()
                return resolve(results)
            }
        })
    })
}

async function setupNodeEvents(on, config) {

    await preprocessor.addCucumberPreprocessorPlugin(on, config, {
        omitBeforeRunHandler: true
    })

    on('before:run', () => {
        fs.emptyDirSync('./test-results')
        preprocessor.beforeRunHandler(config)
    })

    on(
        'file:preprocessor',
        createBundler({
            inject: [require.resolve('node-stdlib-browser/helpers/esbuild/shim')],
            define: {
                global: 'global',
                process: 'process',
                Buffer: 'Buffer'
            },
            plugins: [plugin(stdLibBrowser), createEsbuildPlugin.default(config)],
        })
    )

    on('task', {
        readFolder(path) {
            return readDirRecursive(path)
        }
    })

    on('task', {
        queryDb: query => {
            return queryTestDb(query, config)
        }
    })

    return config
}

module.exports = defineConfig({
    defaultCommandTimeout: 30000,
    requestTimeout: 30000,
    responseTimeout: 60000,
    pageLoadTimeout: 90000,
    numTestsKeptInMemory: 1,
    chromeWebSecurity: false,
    experimentalWebKitSupport: false,
    screenshotsFolder: 'test-results/screenshots',
    videosFolder: 'test-results/videos',
    viewportWidth: 1920,
    viewportHeight: 1200,
    watchForFileChanges: false,
    screenshotOnRunFailure: true,
    video: false,
    videoCompression: 8,
    reporter: 'spec',
    reporterOptions: {
        mochaFile: 'test-results/tests-output/result-[hash].xml',
        toConsole: true
    },
    retries: {
        runMode: 1,
        openMode: 0
    },
    e2e: {
        setupNodeEvents,
        specPattern: 'cypress/tests/**/*.feature',
    },
})

Even though a lot of the values will remain the same for QA & Staging, I need to use different config files for them.

Specifically, the e2e.baseUrl & env.db values will be different in these environments. Every other value will be the same in QA & Staging.

Here is what I have tried to do in my qa.json file:

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

const baseConfig = require('../../cypress.config.js')

const fs = require('fs-extra')
const readDirRecursive = require('fs-readdir-recursive')
const createBundler = require("@bahmutov/cypress-esbuild-preprocessor")
const preprocessor = require("@badeball/cypress-cucumber-preprocessor")
const createEsbuildPlugin = require("@badeball/cypress-cucumber-preprocessor/esbuild")
const stdLibBrowser = require('node-stdlib-browser')
const plugin = require('node-stdlib-browser/helpers/esbuild/plugin')
const mysql = require('mysql')

const baseUrl = 'https://qa.com'

const env = {
    db: {
        host: 'myHost',
        user: 'myUser',
        password: 'myPassowrd',
        database: 'myDb',
    }
}

function queryTestDb(query, config) {
    // creates a new mysql connection using credentials from cypress.json env's
    const connection = mysql.createConnection(config.env.db)
        // start connection to db
    connection.connect()
        // exec query + disconnect to db as a Promise
    return new Promise((resolve, reject) => {
        connection.query(query, (error, results) => {
            if (error) reject(error)
            else {
                connection.end()
                return resolve(results)
            }
        })
    })
}

async function setupNodeEvents(on, config) {

    await preprocessor.addCucumberPreprocessorPlugin(on, config, {
        omitBeforeRunHandler: true
    })

    on('before:run', () => {
        fs.emptyDirSync('./test-results')
        preprocessor.beforeRunHandler(config)
    })

    on(
        'file:preprocessor',
        createBundler({
            inject: [require.resolve('node-stdlib-browser/helpers/esbuild/shim')],
            define: {
                global: 'global',
                process: 'process',
                Buffer: 'Buffer'
            },
            plugins: [plugin(stdLibBrowser), createEsbuildPlugin.default(config)],
        })
    )

    on('task', {
        readFolder(path) {
            return readDirRecursive(path)
        }
    })

    on('task', {
        queryDb: query => {
            return queryTestDb(query, config)
        }
    })

    return config
}

module.exports = defineConfig({
    defaultCommandTimeout: 30000,
    requestTimeout: 30000,
    responseTimeout: 60000,
    pageLoadTimeout: 90000,
    numTestsKeptInMemory: 1,
    chromeWebSecurity: false,
    experimentalWebKitSupport: false,
    screenshotsFolder: 'test-results/screenshots',
    videosFolder: 'test-results/videos',
    viewportWidth: 1920,
    viewportHeight: 1200,
    watchForFileChanges: false,
    screenshotOnRunFailure: true,
    video: false,
    videoCompression: 8,
    reporter: 'spec',
    reporterOptions: {
        mochaFile: 'test-results/tests-output/result-[hash].xml',
        toConsole: true
    },
    retries: {
        runMode: 1,
        openMode: 0
    },
    e2e: {
        setupNodeEvents,
        baseUrl: baseUrl,
        specPattern: 'cypress/tests/**/*.feature',
    },
    env: {
        ...baseConfig,     // values from imported file
        ...env,            // add or overwrite with values from above
        
    }
})

As you can see, I've managed to move my baseUrl & env values into qa.json.

However, there are still a lot of duplicate values in cypress.config.js & qa.json.

The command I am using to run the tests is npx cypress open --config-file cypress/config/aqua.js

How can I re-use the queryTestDb() function setupNodeEvents() function & the other config values in cypress.config.js?

like image 489
user9847788 Avatar asked Dec 29 '25 19:12

user9847788


1 Answers

There's a package that does pretty much what you want to do.

cypress-extends

Cypress plugin that adds "extends" support to the configuration file. This allows the config files to remain DRY and avoid duplicating data, but still have multiple files for different scenarios.

// cypress.json
{
  "extends": "./base.json"
}
// base.json
{
  "fixturesFolder": false,
  "supportFile": false,
  "$schema": "https://on.cypress.io/cypress.schema.json"
}

In the docs Extending the Cypress Config File


Cypress v10

With the latest Cypress version, config changes from .json to .js file.

Now you can just import and merge the base.json, but you need to be careful about sub-sections of the comfig

cypress.config.js

const { defineConfig } = require('cypress')
const baseConfig = require('base.json')

module.exports = defineConfig({
  e2e: {
    baseUrl: baseConfig.baseUrl || 'http://localhost:1234'  // example OR 
  }
})

Or maybe this is more general

const { defineConfig } = require('cypress')
const baseConfig = require('base.json')

const e2e = {
  baseUrl: 'http://localhost:1234',
  fixturesFolder: false,
  supportFile: false,
  "$schema": "https://on.cypress.io/cypress.schema.json"
}

module.exports = defineConfig({
  e2e: {
    ...e2e,           // values from above
    ...baseConfig     // add or overwrite from imported file
  }
})

It's more powerful merging in javascript, but also easier to get wrong. Please refer to the Testing Type-Specific Options for a reference to what goes where in the new format.

like image 161
Fody Avatar answered Jan 01 '26 20:01

Fody