最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Cypress load environment variables in custom commands - Stack Overflow

programmeradmin2浏览0评论

I'm building a Next.js app and write my tests using Cypress. I configure the environment variables using a .env.local file locally. In the CI pipeline, they are defined normally.

I'm trying to write a custom mand in Cypress that encrypts a session in cypress/support/mand.ts.

import { encryptSession } from 'utils/sessions';

Cypress.Commands.add(
  'loginWithCookie',
  ({
    issuer = 'some-issuer',
    publicAddress = 'some-address',
    email = 'some-mail',
  } = {}) => {
    const session = { issuer, publicAddress, email };

    return encryptSession(session).then(token => {
      cy.setCookie('my-session-token', token);
      return session;
    });
  },
);

When this mand runs, it fails because encryptSession uses a TOKEN_SECRET environment variable, that Cypress doesn't load.

import Iron from '@hapi/iron';

const TOKEN_SECRET = process.env.TOKEN_SECRET || '';

export function encryptSession(session: Record<string, unknown>) {
  return Iron.seal(session, TOKEN_SECRET, Iron.defaults);
}

How can I get Cypress to load the environment variables from that file, if its there (= only locally because the variables are defined in the CI - it should detect the other variables in the pipeline normally, so the equivalent of detecting a variable that has been set with export MY_VAR=foo)?

I'm building a Next.js app and write my tests using Cypress. I configure the environment variables using a .env.local file locally. In the CI pipeline, they are defined normally.

I'm trying to write a custom mand in Cypress that encrypts a session in cypress/support/mand.ts.

import { encryptSession } from 'utils/sessions';

Cypress.Commands.add(
  'loginWithCookie',
  ({
    issuer = 'some-issuer',
    publicAddress = 'some-address',
    email = 'some-mail',
  } = {}) => {
    const session = { issuer, publicAddress, email };

    return encryptSession(session).then(token => {
      cy.setCookie('my-session-token', token);
      return session;
    });
  },
);

When this mand runs, it fails because encryptSession uses a TOKEN_SECRET environment variable, that Cypress doesn't load.

import Iron from '@hapi/iron';

const TOKEN_SECRET = process.env.TOKEN_SECRET || '';

export function encryptSession(session: Record<string, unknown>) {
  return Iron.seal(session, TOKEN_SECRET, Iron.defaults);
}

How can I get Cypress to load the environment variables from that file, if its there (= only locally because the variables are defined in the CI - it should detect the other variables in the pipeline normally, so the equivalent of detecting a variable that has been set with export MY_VAR=foo)?

Share Improve this question edited Mar 3, 2022 at 15:05 Zoe - Save the data dump 28.3k22 gold badges128 silver badges160 bronze badges asked Feb 6, 2021 at 20:07 J. HestersJ. Hesters 14.8k34 gold badges155 silver badges267 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4 +150

There is Cypress.env, but you want to set the token on process.env which looks like it's not fully coordinated with the Cypress version.

I know that any process.env with a key with prefix of CYPRESS_ ends up in Cypress.env(), but you want to go in the opposite direction.

I would use a task which gives you access to the file system and process.env,

/cypress/plugins/index.js

module.exports = (on, config) => {
  on('task', {
    checkEnvToken :() =>  {
      const contents = fs.readFileSync('.env.local', 'utf8'); // get the whole file
      const envVars = contents.split('\n').filter(v => v);    // split by lines 
                                                              // and remove blanks      
      envVars.forEach(v => {
        const [key, value] = v.trim().split('=');     // split the kv pair
        if (!process.env[key]) {                      // check if already set in CI
          process.env[key] = value;                        
        }
      })
      return null;                                    // required for a task
    },
  })

Call the task ahead of any tests, either in /cypress/support/index.js, or a before(), or in the custom mand.

In the custom mand

Cypress.Commands.add(
  'loginWithCookie',
  ({
    issuer = 'some-issuer',
    publicAddress = 'some-address',
    email = 'some-mail',
  } = {}) => {
    cy.task('checkEnvToken').then(() => {  // wait for task to finish 

      const session = { issuer, publicAddress, email };

      return encryptSession(session).then(token => {
        cy.setCookie('my-session-token', token);
          return session;
        });
    })
  });

Digging into the code for @hapi/iron, there is a call to crypto which is a Node library, so you may need to move the whole encryptSession(session) call into a task to make it work.

import { encryptSession } from 'utils/sessions';

module.exports = (on, config) => {
  on('task', {
    encryptSession: (session) =>  {

      const contents = fs.readFileSync('.env.local', 'utf8'); // get the whole file
      const envVars = contents.split('\n').filter(v => v);    // split by lines 
                                                              // and remove blanks      
      envVars.forEach(v => {
        const [key, value] = v.trim().split('=');     // split the kv pair
        if (!process.env[key]) {                      // check if already set in CI
          process.env[key] = value;                        
        }
      })

      return encryptSession(session);                 // return the token
    },
  })

Call with

cy.task('encryptSession', { issuer, publicAddress, email })
  .then(token => {
    cy.setCookie('my-session-token', token);
  });

Where to run the above cy.task

I guess you only need to run it once per test session (so that it's set for a number of spec files) in which case the place to call it is inside a before() in /cypress/support/index.js.

The downside of placing it there is it's kind of hidden, so personally I'd put it inside a before() at the top of each spec file.

There's a small time overhead in the fs.readFileSync but it's minimal pared to waiting for page loads etc.

Steve's answer actually helped me to end up with this code in cypress/plugins/index.ts.

import dotenv from 'dotenv';

dotenv.config({ path: '.env.local' });

import { encryptSession } from 'utils/sessions';

/**
 * @type {Cypress.PluginConfig}
 */
const pluginConfig: Cypress.PluginConfig = (on, config) => {
  on('task', {
    encryptSession: (session: {
      issuer: string;
      publicAddress: string;
      email: string;
    }) => encryptSession(session),
  });
};

export default pluginConfig;

Then in cypress/support/mands.ts.


Cypress.Commands.add(
  'loginWithCookie',
  ({
    issuer = 'some-issuer',
    publicAddress = 'some-address',
    email = 'some-email',
  } = {}) => {
    const session = { issuer, publicAddress, email };

    return cy.task<string>('encryptSession', session).then(token => {
      return cy.setCookie('my-secret-token', token).then(() => {
        return session;
      });
    });
  },
);
发布评论

评论列表(0)

  1. 暂无评论