Cypress 在自定义命令中加载环境变量

问题描述

我正在构建 Next.js 应用程序并使用 Cypress 编写我的测试。我在本地使用 .env.local file 配置环境变量。在 CI 管道中,它们是正常定义的。

我正在尝试在 Cypress 中编写一个自定义命令来加密 cypress/support/command.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;
    });
  },);

当此命令运行时,它会失败,因为 encryptSession 使用了一个 TOKEN_SECRET 环境变量,而 Cypress 不会加载该变量。

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);
}

我怎样才能让 Cypress 从那个文件加载环境变量,如果它在那里(= 仅在本地,因为变量是在 CI 中定义的 - 它应该正常检测管道中的其他变量,所以相当于检测一个已用 export MY_VAR=foo) 设置的变量?

解决方法

Cypress.env,但您想在 process.env 上设置令牌,这看起来与 Cypress 版本不完全协调。

我知道任何带有前缀为 CYPRESS_ 的键的 process.env 都以 Cypress.env() 结束,但您想朝相反的方向前进。

我会使用一个任务来让你访问文件系统和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
    },})

在任何测试之前调用任务,在 /cypress/support/index.jsbefore() 或自定义命令中。

在自定义命令中

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;
        });
    })
  });

深入研究 @hapi/iron 的代码,有一个对 crypto 的调用,它是一个 Node 库,因此您可能需要将整个 encryptSession(session) 调用移动到一个任务中才能完成工作。

import { encryptSession } from 'utils/sessions';

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

      const contents = fs.readFileSync('.env.local',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
    },})

打电话给

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

在哪里运行上面的cy.task

我想您只需要在每个测试会话中运行一次(以便为多个规范文件设置),在这种情况下,调用它的位置位于 /cypress/ 中的 before() 内支持/index.js

把它放在那里的缺点是它有点隐藏,所以我个人会把它放在每个规范文件顶部的 before() 中。

fs.readFileSync 中的时间开销很小,但与等待页面加载等相比,它是最小的。

,

Steve 的回答实际上帮助我在 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,{
    encryptSession: (session: {
      issuer: string;
      publicAddress: string;
      email: string;
    }) => encryptSession(session),});
};

export default pluginConfig;

然后在 cypress/support/commands.ts 中。


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

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