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.jsoncypress.jsonRelated:
cypress.json and cypress.env.jsonpluginsFile to <projectRoot>/cypress.jscypress.jscypress.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.envThis 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.jsextension seems to be a convention in JS libraries/frameworks (webpack.config.js,babel.config.js,vue.config.js, etc.).