Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
jennifer-shehane opened this issue Sep 26, 2019 · 13 comments
Closed

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

jennifer-shehane opened this issue Sep 26, 2019 · 13 comments
Assignees
Labels
topic: config file Issues around the configuration file type: feature New feature that does not currently exist

Comments

@jennifer-shehane
Copy link
Member

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 (Overriding baseUrl from cypress.env.json #909), which doesn't work with cypress.json
  • It's not possible to use comments in cypress.json

Related:

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(…)
@benface
Copy link

benface commented Apr 23, 2020

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.).

@terrynguyen255

This comment has been minimized.

@Talank
Copy link

Talank commented Aug 18, 2020

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

@kiranparajuli589
Copy link

kiranparajuli589 commented Aug 18, 2020

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

@daiky00
Copy link

daiky00 commented Aug 25, 2020

I will also agree with this +1

@estefafdez
Copy link

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

@ncjones
Copy link

ncjones commented Oct 29, 2020

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.

@georgelviv

This comment has been minimized.

@jennifer-shehane jennifer-shehane added the type: feature New feature that does not currently exist label Feb 5, 2021
@G-Rath
Copy link

G-Rath commented Mar 12, 2021

Can/should we support TypeScript/CoffeeScript/etc (cypress.ts, cypress.coffee)?

I'd love to be able to have cypress.config.ts, as the ability to type check config is super useful. Supporting it should be pretty straightforward, as you can use ts-node to do the requiring.

Jest does not support it (must be .js or .json)

Jest now does support jest.config.ts, and the implementation is what I'd recommend using.
It's what I used to add support for keywords written in TypeScript to ajv-cli - I'd recommend "borrowing" the code from that PR.

I'm happy to help implement this, and answer questions (I've done this a few times now 😅)

@jennifer-shehane
Copy link
Member Author

@G-Rath We had a PR open for this that we are rethinking #15280

@elevatebart elevatebart self-assigned this Jun 16, 2021
@cypress-bot cypress-bot bot added stage: work in progress stage: needs review The PR code is done & tested, needs review and removed stage: proposal 💡 No work has been done of this issue stage: work in progress stage: needs review The PR code is done & tested, needs review labels Jun 17, 2021
@cypress-bot cypress-bot bot added stage: needs review The PR code is done & tested, needs review and removed stage: waiting labels Jun 29, 2021
@cypress-bot cypress-bot bot added stage: needs review The PR code is done & tested, needs review and removed stage: waiting labels Aug 11, 2021
@cypress-bot cypress-bot bot added stage: work in progress stage: needs review The PR code is done & tested, needs review and removed stage: needs review The PR code is done & tested, needs review stage: work in progress labels Aug 24, 2021
@cypress-bot cypress-bot bot added stage: waiting and removed stage: needs review The PR code is done & tested, needs review labels Sep 8, 2021
@jennifer-shehane jennifer-shehane added the topic: config file Issues around the configuration file label Sep 24, 2021
@smccarthy-godaddy
Copy link

Is this implemented in #18061 ?

#5674 (comment) led me to #17000 (closed but not merged) which led me to #18061 (merged and comment saying it is released in cypress 8.5.0).

@elevatebart
Copy link
Contributor

@smccarthy-godaddy

It will be implemented officially in 10.0.0. You can already test version 10 by going to a commit and following the instructions in the comments.

Here is more info on how to install it

This being said 10.0 is still in alpha, so you might want to avoid using it for critical systems.
I can't wait for this amazing release myself. I hope my answer helps.

@jennifer-shehane
Copy link
Member Author

This was released in 10.0. Closing as resolved.

@cypress-io cypress-io locked as resolved and limited conversation to collaborators Nov 14, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
topic: config file Issues around the configuration file type: feature New feature that does not currently exist
Projects
None yet