Simplify Cypress configuration
Why is this important? Why is it a priority?
cypress.json
, cypress.env.json
, pluginsFile
) is confusing and difficult to document wellcypress.json
cypress.json
Related:
cypress.json
and cypress.env.json
pluginsFile
to <projectRoot>/cypress.js
cypress.js
cypress.ts
, cypress.coffee
)?projectId
when a user sets up their project to recordcypress.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'
}
.js
or .json
)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...
now it will be...
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...
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'
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
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.).