Cypress: Proposal: allow configuration file to be 'js', not 'json'

Created on 26 Sep 2019  Ā·  8Comments  Ā·  Source: cypress-io/cypress

Goal

Simplify Cypress configuration

Why

Why is this important? Why is it a priority?

  • Currently, allowing configuration from multiple different places (cypress.json, cypress.env.json, pluginsFile) is confusing and difficult to document well
  • Users need to specify different configuration based on environment (https://github.com/cypress-io/cypress/issues/909), which doesn't work with cypress.json
  • It's not possible to use comments in cypress.json

Related:

  • Unable to use deeply nested configuration (https://github.com/cypress-io/cypress/issues/1736)

Implementation

  • Get rid of cypress.json and cypress.env.json
  • Move the pluginsFile to <projectRoot>/cypress.js
  • All configuration is from what's returned from cypress.js

Questions

  • Can/should we support TypeScript/CoffeeScript/etc (cypress.ts, cypress.coffee)?
  • How do we handle writing the projectId when a user sets up their project to record
  • Should we change the API of cypress.js to be different from the current API of the pluginsFile. Since it's the only way to configure Cypress, it might be good to make the use-case of only using it for configuration easier. Instead of:
module.exports = () => {
  return {
    baseUrl: 'http://localhost:1234'
  }
}

We could allow them to export an object if they don't need to register events:

module.exports = {
  baseUrl: 'http://localhost:1234'
}

Research

How other projects deal with TypeScript/CoffeeScript for config files

Notes

  • I would go with always requiring a function the same way we do with plugins to avoid needing to do error handling and documentation in two different ways

via @brian-mann

Another change is that we will need to resolve default configuration and overrides prior to calling the config function so you can utilize those values.

Before it was...

  • cypress.json
  • env var overrides
  • cli arguments

now it will be...

  • env var overrides
  • cli arguments
  • cypress.js

which means we need to pass the defaults along with the overrides to the function so it can continue to change them.

// something like this
module.exports = (defaults, overrides) => {}
module.exports = (defaultsIncludingOverrides) => {}

// or the jest approach
const { defaultConfigurationOptions } = require('cypress')

module.exports = (overrides) => {}

I think we also need to decide what to do with env and whether or not this should be set as a property of the default configuration or not.

We either need to keep it separate or merge it with the config.

module.exports = (env, config) => {}
module.exports = (envWithConfig) => {}

Right now its confusing because you work with it separately in the driver with Cypress.env(...) but it ends up being a property on config, but is set through a separate CLI flag: --env.

It should be either a separate thing or a part of config.

EDIT: we may not want to change the order of resolving the env + config else this would force the user to always merge the overrides in, else they'd be ignored. That would also force the user to have a valid cypress.js which we want to make optional.

Disregard what I said above about setting overrides. We could go with the defaultConfigurationOptions since that is separate (or perhaps yield that in) but keep resolution to happen downstream.


via @brian-mann

To make matters even more confusing... CYPRESS_ env vars are special in that they...

  1. can override configuration if they match a configuration option
  2. are automatically set onto config.env

This was done a long time ago because without the ability to execute anything in node it was difficult to set environment variables in the traditional way and know that the user wanted them in the driver. It would be a vulnerability to just automatically attach everything on process.* onto Cypress.env().

Anyway with the ability to write configuration, setting env vars becomes much easier because you can just do the ones you care about.

module.exports = (something) => {
  return {
    env: {
      ...process.env,
      someValue: process.env.whatever
    }
  }
}

If we want to allow overriding configuration we could continue to accept env vars but namespace them like CYPRESS_CONFIG_BASE_URL=...

Setting a CYPRESS_FOO would no longer mean anything, but we could also still support a CYPRESS_ENV_FOO=bar which would set { foo: bar }

I'm still conflicted on whether or not to make env a property on config or vice versa or keep them separate.

We could namespace them like this...

module.exports = () => {
  return {
    config: {...},
    env: {...}
  }
}

Which would make more sense per how you work with them in the driver.

But then we'd also need to namespace the events you register, which is weird...

This would potentially work better through for things like projectId which isn't actually part of the configuration of cypress and could live outside of config.


Notes from 2/6/2018:

// get
Cypress.config('baseUrl')
// set
Cypress.config('baseUrl', 'http://localhost:8080')
Cypress.config({
  baseUrl: 'http://localhost:8080'
})

// cypress run --config baseUrl=http://staging.local
// CYPRESS_BASE_URL=http://staging.local cypress run

// CYPRESS_CONFIG_BASE_URL
// CYPRESS_ENV_MY_VAR

// exports.secrets ?
exports.env = () => {
  return {
    foo: 'FOO',
  }
}

// Cypress.secrets('foo') ?
Cypress.env('foo')
Cypress.envVar('foo')

// ORDER:
// CYPRESS_ENV_* VARIABLES
// cypress.js env

const envs = {
  staging: {},
  development: {},
  production: {},
  default: {},
}

// --environment staging|development|production
Cypress.environment // ?
exports.config = (defaultConfig, environment) => {
  // how to differentiate from env above?
  // call it currentEnvironment?

  return {
    ...(envs[environment] || envs.default),

    // data
    // meta
    // user
    // etc
    // other
    custom: {
      testCases: [],

      percy: {
        id: '1234',
      }
    },
  }

  Cypress.config("custom.percy.id")

  // return {
  //   defaults: {

  //   },
  //   development: {
  //     baseUrl: 'http://localhost:8080',
  //   },
  //   production: {
  //     baseUrl: 'http://localhost:8080',
  //   },
  //   staging: {
  //     baseUrl: 'http://localhost:8080',
  //   },
  // }
}

// ORDER:
// default config
// cypress.js config
// CYPRESS_CONFIG_* VARIABLES
// CLI


// exports.hooks ?
// exports.plugins ?
// exports.events ?
exports.backgroundEvents = (on, config, env) => {
  // config and env|secrets are final resolved values
}

exports.projectId = '1234'
  • remove the background file (call it config file?)
  • support a new —env argument or NODE_ENV=?
  • pass in the environment if one is specific in the exported function

    • tag the environment in the dashboard as a filter of the build or groups?

    • where to store the env on Cypress?

    • Cypress.env?

  • remove —env-file, throw if those options are passed
  • add support for arbitrary config.plugins property
  • add a ā€œmigrationā€ error helper

    • list out the problems found, with link to instructions + upgrade notes for each one

    • it looks like you’re trying to upgrade to a newer verison of cypress

    • we’ve listed out the breaking changes you must fix with an explanation of what changes were made and a link to them

  • Cypress.config(ā€˜meta’) // arbitrary storage
  • remove Cypress.env() // just use Cypress.config(ā€˜envVars.whatever’)

    • or Cypress.envVars(…)

proposal šŸ’”

Most helpful comment

Might I suggest the name cypress.config.js? The .config.js extension seems to be a convention in JS libraries/frameworks (webpack.config.js, babel.config.js, vue.config.js, etc.).

All 8 comments

Might I suggest the name cypress.config.js? The .config.js extension seems to be a convention in JS libraries/frameworks (webpack.config.js, babel.config.js, vue.config.js, etc.).

+1

Having a js file for configuration will be really helpful for using environment variables as well. Hope this issue will be addressed soon

Uusing js files as config we can be more dynamic while setting variables like baseUrl. This feature would be so nice to get implemented!

I will also agree with this +1

+1 to this as well! it will be very easy to write and add comments!

It is trivial to combine Mozilla Convict with a Cypress plugin to define flexible, dynamic, Cypress-aware configs such as baseUrl.

cypress/plugins/index.js:

module.exports = (on, cypressConfig) => {
  const config = require('./config').load(cypressConfig);
  return {

    // override any cypress configs here:
    baseUrl: config.get('baseUrl'),

    // make all Convict configs available via Cypress.env():
    env: config.getProperties(),
  };
};

cypress/plugins/config.js:

const convict = require('convict');
const json5 = require('json5');
const fs = require('fs');
const path = require('path');

convict.addParser({ extension: 'json5', parse: json5.parse });

function load(cypressConfig) {
  // pick environment from cypress `--env` param or `cypress_env` environment var:
  const env = cypressConfig.env.env || 'local';
  // define config defaults in Convict schema:
  const schema = {
    baseUrl: {
      format: String,
      default: 'http://localhost:7480',
    },
    someOtherConfig: {
      format: String,
      default: 'bloop',
      env: 'SOME_OTHER_CONFIG',
    },
  };
  return convict(schema)
    // dynamically load config values for current env:
    .loadFile(path.resolve(__dirname, '..', `config-${env}.json5`))
    .validate({ allowed: 'strict' });
}

module.exports = { load };

cypress/config-dev.json5:

{
  baseUrl: 'http://dev.example.com/'
}

Run Cypress:

yarn cypress open --env env=dev

There are some drawbacks with this approach. The Cypress visualization of Config values will show all your overridable config values coming from the "plugin" layer, regardless of how Convict picked them. Cypress will also not auto restart on changes to the custom config files. I think these are acceptable tradeoffs though. You can still define Cypress configs that do not need to change between environments in cypress.json (like viewport etc).

Either JSON5 or YAML are great choices for the environmental config files but any file format can be used.

+1

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bahmutov picture bahmutov  Ā·  69Comments

t-zander picture t-zander  Ā·  125Comments

jennifer-shehane picture jennifer-shehane  Ā·  87Comments

chrisbreiding picture chrisbreiding  Ā·  114Comments

gerardovanegas-eagle picture gerardovanegas-eagle  Ā·  87Comments