【问题标题】:Cypress load environment variables in custom commandsCypress 在自定义命令中加载环境变量
【发布时间】:2021-05-10 20:46:54
【问题描述】:

我正在构建一个 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 环境变量,赛普拉斯不会加载。

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

如何让赛普拉斯从该文件加载环境变量,如果它存在(= 仅在本地,因为变量是在 CI 中定义的 - 它应该正常检测管道中的其他变量,因此相当于检测export MY_VAR=foo) 设置的变量?

【问题讨论】:

    标签: javascript environment-variables cypress dotenv add-custom-command


    【解决方案1】:

    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, config) => {
      on('task', {
        encryptSession: (session: {
          issuer: string;
          publicAddress: string;
          email: string;
        }) => encryptSession(session),
      });
    };
    
    export default pluginConfig;
    

    然后在cypress/support/commands.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;
          });
        });
      },
    );
    

    【讨论】:

    • 看起来不错 - 你能展示它如何适合(假定)插件//index.js 吗?
    【解决方案2】:

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

    我知道任何带有前缀为 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.js、before() 或自定义命令中,在任何测试之前调用任务。

    在自定义命令中

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

    打电话给

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

    在哪里运行上述 cy.task

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

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

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

    【讨论】:

    • 谢谢,这看起来不错。我的问题是,这段代码会只运行一次,还是fs.readFileSync 和其余代码会在每次有人使用loginWithCookie 时运行(它调用cy.task('encryptSession' ...)?如果它每次都运行,有没有办法避免这种情况或者那很好?
    • 我在上面添加了关于运行任务的注释。
    • 在support/index.ts 或.spec 文件中运行fs.readFileSync 会导致fs__WEBPACK_IMPORTED_MODULE_0___default.a.readFileSync is not a function 崩溃。
    • 啊啊我明白了!所以你说的是在before() 钩子中运行cy.loginWithCookie,而不是使用fs 设置环境变量的部分。抱歉,在我的第二条评论中,我想说我尝试将fs 部分放在pluginConfig 的(on, config) 和on('task', ...) 范围之间。
    • 我认为正如你所说,使用before() 钩子很不幸,尽管该过程会运行多次。非常感谢您的帮助! ?
    猜你喜欢
    • 2013-07-28
    • 2015-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多