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

Posted

技术标签:

【中文标题】Cypress 在自定义命令中加载环境变量【英文标题】:Cypress load environment variables in custom commands 【发布时间】: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) 设置的变量?

【问题讨论】:

【参考方案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.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, 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() 钩子很不幸,尽管该过程会运行多次。非常感谢您的帮助! ?

以上是关于Cypress 在自定义命令中加载环境变量的主要内容,如果未能解决你的问题,请参考以下文章

在自定义适配器的列表视图项中加载不同的图像

在自定义 ASP.NET Core 配置提供程序中停止 SqlDependency

从 Jenkins 管道运行脚本时,如何通过 ssh 在远程 AIX 机器中加载环境变量?

如何在字幕文件中加载外部或自定义字体

如何在自定义布局中加入 CSS 六边形元素?

Cypress 系列之----03自定义命令Custom Commands