Cypress: Noob question: Why Cypress.env() instead of process.env ?

Created on 19 Nov 2017  路  10Comments  路  Source: cypress-io/cypress

Is there any way of using the node process.env instead of Cypress.env so I can reuse code for custom commands?

Thanks

wontfix question

Most helpful comment

My particular use case is not affected by the decision to use Cypress.env instead of process.env, but I figured I'd share my experience for what I feel is more standard in the node/npm/js community.

In my project, we use dotenv-extended which enables us to provide sensible defaults for DEVs in a .env.default file with overrides placed in a .env file. If you've defined environment variables with export they take precedence over all others. This means our CI server can specify parameters specific to the build while accepting defaults for others. Furthermore, any package that uses the standard process.env node api integrates with this entire process smoothly, as dotenv provides values to process.env.

In our case, the default host name is localhost. The port defaults to 8080, but can be changed. There are also options for whether to use TLS or not.

I am currently overriding cy.visit and cy.request commands to use these variables and generate the baseUrl. In this respect, I fully appreciate the elegant api your team has created to facilitate such customizations. That being said, unless there is a specific use case for creating Cypress.env, I highly recommend using process.env as it _is_ the standard. At the very least, falling back to process.env is a better alternative than no alternative at all.

All 10 comments

Could you give an example of what exactly you're trying to do? Even pseudo code would be fine and why using process.env doesn't currently work for your situation?

I have a module in my javascript code that uses process.env to read the URL and some environment specific constants of my api and handles all my api related code (authentication, headers and stuff). I would like to reuse the same module in my test cases to load data directly from my api and check with the results from running my test cases.

Example of duplication of code, that I could avoid if cypress used process.env
Cypress commands.js:

Cypress.Commands.add("login", (username, password) => {
    if (sessionStorage.access_token) return;

    const body = {
        grant_type: "password",
        client_id: Cypress.env("REACT_APP_OAUTH_CLIENT_ID"),
        client_secret: Cypress.env("REACT_APP_OAUTH_CLIENT_SECRET"),
        username: username,
        password: password,
        scope: "*"
    };

    return axios.post("/oauth/token", body).then(response => {
        const token = response.data.access_token;
        axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
        sessionStorage.access_token = token;
    });
});

My module api.js:

export default {
    auth: {
        login: ({ username, password }) => {
            const body = {
                grant_type: "password",
                client_id: process.env.REACT_APP_OAUTH_CLIENT_ID,
                client_secret: process.env.REACT_APP_OAUTH_CLIENT_SECRET,
                username: username,
                password: password,
                scope: "*"
            };

            return axios
                .post("/oauth/token", body)
                .then(response => response.data);
        },
// ...

Looking at your code - I see some parity but I feel like its different enough to be in separate places. For instance the call to axios should likely be using cy.request in your test code.

I understand that you want the process.env object to match what would be in node. We could technically add this to Cypress, but I'm less of a fan of spending time on one off issues. You could solve this yourself by updating your module to take into account when its in Cypress as opposed to node and write a ternary expression.

With that said - a quick google finds a plugin called https://github.com/hughsk/envify will replace the calls to process.env with string literals. That might work for you. You could also search for an equivalent webpack plugin and swap out the cypress browserify one with cypress webpack preprocessor.

One final note is that we are adding a config event to plugins which would make this easier on your. I imagine you're having to define those REACT env variables (which is a PITA), and using this event it would become easier to programmatically set them in Cypress.

I'm going to close this issue for now - if there are more use cases that are stronger for reproducing process.env we can take another look at this then.

My particular use case is not affected by the decision to use Cypress.env instead of process.env, but I figured I'd share my experience for what I feel is more standard in the node/npm/js community.

In my project, we use dotenv-extended which enables us to provide sensible defaults for DEVs in a .env.default file with overrides placed in a .env file. If you've defined environment variables with export they take precedence over all others. This means our CI server can specify parameters specific to the build while accepting defaults for others. Furthermore, any package that uses the standard process.env node api integrates with this entire process smoothly, as dotenv provides values to process.env.

In our case, the default host name is localhost. The port defaults to 8080, but can be changed. There are also options for whether to use TLS or not.

I am currently overriding cy.visit and cy.request commands to use these variables and generate the baseUrl. In this respect, I fully appreciate the elegant api your team has created to facilitate such customizations. That being said, unless there is a specific use case for creating Cypress.env, I highly recommend using process.env as it _is_ the standard. At the very least, falling back to process.env is a better alternative than no alternative at all.

process.env is not automagically provided in the browser. The same reason it's not available when writing React / Vue / Angular, etc. There is no process object in the browser, and if you want environment variables exported you have to do that in the file:preprocessor event.

You can also easily do this yourself using the configuration API as provided by the plugins file. Simply set config.env = process.env which will set your entire node env for Cypress.

https://docs.cypress.io/api/plugins/configuration-api.html

Thanks for the pointer to the plugin config and the additional clarification as to why Cypress.env exists.

Here's a working example of customizing config using Cypress plugins and dotenv (and therefore ultimately process.env):

https://gist.github.com/ryanhaney/c586a080fa2a90c12328b762ba8266c5

As a note for other readers, you can selectively assign config.env.YOUR_VAR = process.env.YOUR_VAR as you wish.

Thanks!

I'm sorry, guys, maybe it's not a proper place to ask, but still, I have a problem.
I'm trying to use construction config.env.YOUR_VAR = process.env.YOUR_VAR to set the env variable from the global env file but it returns undefined instead of value form the env. Could you take a look what I'm doing wrong? Here is my code:

// plugins/index.js 
module.exports = (on, config) => { 
  console.log(process.env);
  config.env.sharedSecret = process.env.USER_SUPERADMIN_EMAIL;

return config
};
// cypress/integration/secrets_spec.js
describe('Environment variable set in plugin', () => {

    let sharedSecret

    before(() => {
      sharedSecret = Cypress.env('sharedSecret')
    })

    it.only('can be accessed within test.', () => {
      cy.log(Cypress.env('sharedSecret'));
    })
  })

From my support/index.js, config.env = process.env gives me Uncaught ReferenceError: config is not defined. Also console.log(process.env) gives me {} which, when you think about it, was the problem at first anyway given that the errors I was getting when running cypress were SyntaxError: Failed to construct 'WebSocket': The URL 'ws://localhost:undefined/' is invalid. (where the url is looking for process.env)

I ran across this thread with a similar quandary and wanted to share my solution! I'm using Vue and run Cypress through the vue-cli, but the solution should apply universally. In this context, I am using values from my .env (process.env.*) file to supply a Cypress command (cy.login()) for auth0. I am doing this so that I can use variables to run these Cypress tests in a BitBucket CI/CD pipeline:

.env

VUE_APP_AUTH_AUDIENCE=someAudienceValue
... etc.

plugins/index.js

module.exports = (on, config) => {
  return Object.assign({}, config, {
    env: {
      auth_audience: process.env.VUE_APP_AUTH_AUDIENCE,
      auth_url: process.env.VUE_APP_AUTH_URL,
      auth_client_id: process.env.VUE_APP_AUTH_CLIENT_ID,
      auth_client_secret: process.env.VUE_APP_AUTH_CLIENT_SECRET,
      auth_username: process.env.VUE_APP_E2E_USERNAME,
      auth_password: process.env.VUE_APP_E2E_PASSWORD,
    },
    fixturesFolder: 'tests/e2e/fixtures',
    integrationFolder: 'tests/e2e/specs',
    screenshotsFolder: 'tests/e2e/screenshots',
    videosFolder: 'tests/e2e/videos',
    supportFile: 'tests/e2e/support/index.js',
  });
};

https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/
support/commands.js

Cypress.Commands.add('login', (overrides = {}) => {
  Cypress.log({
    name: 'loginViaAuth0',
  });

  const options = {
    method: 'POST',
    url: Cypress.env('auth_url'),
    body: {
      grant_type: 'password',
      username: Cypress.env('auth_username'),
      password: Cypress.env('auth_password'),
      audience: Cypress.env('auth_audience'),
      scope: 'openid profile email',
      client_id: Cypress.env('auth_client_id'),
      client_secret: Cypress.env('auth_client_secret'),
    },
  };
  cy.request(options);
});

Where Cypress.env('auth_username') is config.env['auth_username'] / config.env.auth_username.

In my case I have all my endpoints mapped in a centralised file that I import inside multiple applications.
I didn't wan't to change much of the code there, so keeping process.env instead of Cypress.env was a must.

What worked best to me was adding this at the top of the file:

if (typeof Cypress !== 'undefined') {
  process.env = Cypress.env()
}

In full:

import environment, { env } from 'dynamic-variables'

if (typeof Cypress !== 'undefined') {
  process.env = Cypress.env()
}

module.exports = environment({
  redis: env(process.env.REDIS_CONTAINER),
  backend: {
    graphq: env(process.env.GRAPHQL_SERVER_PUBLIC, process.env.GRAPHQL_SERVER_CONTAINER),
    // 馃寛 Note that backend.rest below is a function! 鈽勶笍
    // As such, it can avoid ReferenceErrors in case the variables in it were not defined
    rest: () => env(process.env.REST_SERVER_PUBLIC, process.env.REST_SERVER_CONTAINER),
  }
})
Was this page helpful?
0 / 5 - 0 ratings

Related issues

brian-mann picture brian-mann  路  3Comments

igorpavlov picture igorpavlov  路  3Comments

carloscheddar picture carloscheddar  路  3Comments

zbigniewkalinowski picture zbigniewkalinowski  路  3Comments

weskor picture weskor  路  3Comments