如何在github动作中并行运行柏树测试
#javascript #cypress #githubactions

最近,我正在寻找一种在GitHub Action中并行运行所有柏树测试的方法。我所有的测试都依次进行,花费太长了。

所以,这是我为解决这个问题所做的。

ð基本知识

默认情况下,所有作业在github动作中并行运行,除非您将need语句放置并使一个作业取决于另一个作业。但是,如果您需要并行运行所有作业的任务,则需要使用GitHub Action的matrix功能。

Matrix允许您运行一组变量值,这些值的每种组合将导致并行运行的不同作业或步骤。

例如,

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macOS-latest]
        node-version: [10.x, 12.x, 14.x]

这将创建9个独立的作业,每个作业一个组合。 Github Action Docs中的更多信息。

为每个柏树测试创建并行作业

我们需要为柏树测试的每个规格创建一个矩阵,以便所有测试并行运行。在这里,每个规格代表一个文件中包含的所有测试。

1页创建一个JS文件以找到所有柏树规格。

此文件后来在.github/workflows文件夹中使用。我在cypress/support文件夹中创建了此文件。但是,您可以选择将其放在根或您喜欢的任何地方。

  • 文件名:locateCypressSpecsToRun.js5
const fs = require('fs');
const path = require('path');
const getAllFiles = (dirPath, arrayOfFiles = []) => {
  const files = fs.readdirSync(dirPath, { withFileTypes: true });

  files.forEach((file) => {
    if (file.isDirectory()) {
      arrayOfFiles = getAllFiles(`${dirPath}/${file.name}`, arrayOfFiles);
    } else {
      arrayOfFiles.push(`${path.join(dirPath, '/', file.name)}`);
    }
  });
  return arrayOfFiles;
};

const specs = getAllFiles('cypress/e2e');
process.stdout.write(`${JSON.stringify(specs)}\n`);

恳求注意:即使您在柏树中使用TypeScript,也不能在此处使用.ts文件扩展名,因为我们需要直接使用Node运行此文件。

  • getAllFiles()内部,我将所有测试都位于位置的cypress/e2e路径放置。根据需要对此进行修改。

  • 如果您使用命令node locateCypressSpecsToRun.js运行此文件,则应该看到

//pretty version
specs = [
  'cypress/e2e/test1.cy.ts',
  'cypress/e2e/test2.cy.ts',
  'cypress/e2e/test3.cy.ts',
  'cypress/e2e/test4.cy.ts',
  'cypress/e2e/test5.cy.ts',
];

2页建立矩阵

创建了规格位置的字符串后,是时候构建柏树矩阵了。柏树矩阵需要在yaml文件中的作业内部。

jobs:
  build-cypress-matrix:
    runs-on: ubuntu-latest
    steps:
      - name: checkout code
        uses: actions/checkout@v3
      - id: set-matrix
        run: echo "specs=$(node cypress/support/locateCypressSpecsToRun.js)" >> $GITHUB_OUTPUT
    outputs:
      specs: ${{ steps.set-matrix.outputs.specs }}
  • 解释:runspecs

    • run: echo "specs=$(node cypress/support/locateCypressSpecsToRun.js)" >> $GITHUB_OUTPUT

      此行使用运行键指定要运行的shell命令。在这种情况下,该命令为echo,然后是一个在cypress/support/locateCypressSpecsToRun.js中运行node.js脚本的字符串。脚本的输出是通过用$()围绕命令捕获的,并存储在称为Specs的外壳变量中。 >>操作员将命令的输出附加到一个名为$GITHUB_OUTPUT的文件,该文件是github操作中的预定义环境变量,可捕获步骤的输出。


      您可以阅读有关$GITHUB_OUTPUT here的更多信息。

    • outputsspecs: ${{ steps.set-matrix.outputs.specs }}

      此行使用输出密钥来定义称为Set-Matrix的步骤的输出。输出称为规格,其值设置为使用${{ steps.set-matrix.outputs.specs }}语法在运行命令中设置的规格变量的值。这允许工作流中的后续步骤访问规格变量的值。

      为了简化,我们正在分配specs: specs

3页要制作其他工作所需的矩阵。

确保在运行测试之前完成build-cypress-matrix作业。因此,使用Action的needs语句。例如,您可以做这样的事情。

regression:
    needs: [notify-slack-on-start, build-cypress-matrix]
    # rest of the code ......

在这种情况下,还需要notify-slack-on-start运行regression Job

4页定义矩阵

在您想要并行运行的作业中,添加预先构建的柏树测试矩阵。请参阅最后的specs:线。

regression:
    needs: [notify-slack-on-start, build-cypress-matrix]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      max-parallel: 64
      matrix:
        config:
          [
            {
              type: 'desktop',
              config: 'viewportWidth=1920,viewportHeight=1080',
            },
          ]
        browser: [chrome, firefox, edge, safari]
        specs: ${{ fromJson(needs.build-cypress-matrix.outputs.specs) }}
  • 在这种情况下,我定义了三个矩阵。

    • browser:我想要测试的所有浏览器
    • 一个适合config:浏览器的大小
    • 最后specs

      • fromJson:此功能将JSON字符串转换为对象,请阅读有关此GitHub Action函数here的更多信息。它用于将输出从build-cypress-matrix作业转换为对象。
      • needs:此功能用于表明当前作业取决于另一个作业的输出。在这种情况下,它用于表明回归作业需要build-cypress-matrix作业的输出。

      因此,${{fromJson(needs.build-cypress-matrix.outputs.specs).specs}}首先使用needs获取build-cypress-matrix作业的输出。然后,fromJson函数将此输出从JSON字符串转换为对象,该对象具有称为specs的属性,该属性包含要运行的Cypress Spec文件列表。最后,从对象中提取specs属性,并用作matrix作业中specs字段的值。

5接£使用矩阵

最后,像浏览器或操作系统一样使用规格矩阵。

- name: My Awesome Cypress Tests
        uses: cypress-io/github-action@v5
        with:
          browser: ${{ matrix.browser }}
          config: ${{ matrix.config.config }}
          spec: ${{ matrix.specs }}

ð€示例

这是一个完整的工作流程。

# sample document. 
  name: Testing Parallel Runs

on:
  workflow_dispatch:

env:
  SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL_CYPRESS }}
  SLACK_ICON: https://github.githubassets.com/images/modules/site/features/actions-icon-actions.svg
  SLACK_CHANNEL: cypress-notifications

jobs:
  notify-slack-on-start:
    runs-on: ubuntu-latest
    steps:
      - uses: rtCamp/action-slack-notify@v2.0.2
        name: notify slack on run start
        env:
          SLACK_TITLE: My Awesome Cypress Tests
          SLACK_COLOR: good
          SLACK_MESSAGE: 'Automation has started :white_check_mark:'

  build-cypress-matrix:
    runs-on: ubuntu-latest
    steps:
      - name: checkout code
        uses: actions/checkout@v3
      - id: set-matrix
        run: echo "specs=$(node cypress/support/locateCypressSpecsToRun.js)" >> $GITHUB_OUTPUT
    outputs:
      specs: ${{ steps.set-matrix.outputs.specs }}

  regression:
    needs: [notify-slack-on-start, build-cypress-matrix]
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      max-parallel: 64
      matrix:
        config:
          [
            {
              type: 'desktop',
              config: 'viewportWidth=1920,viewportHeight=1080',
            },
          ]
        browser: [chrome]
        specs: ${{ fromJson(needs.build-cypress-matrix.outputs.specs) }}
    env:
      NPM_TOKEN: ${{secrets.NPM_TOKEN}}
      CYPRESS_PASSWORD: ${{secrets.CYPRESS_PASSWORD}}
      CYPRESS_configFile: prod
# add other env as necessary

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: copy npmtoken to npmrc
        run: cp .npmtoken .npmrc
# this step is only necessary if you are using private npm packages and need to provide .npmrc to github runner. 

      - name: My Awesome Cypress Tests
        uses: cypress-io/github-action@v5
        with:
          browser: ${{ matrix.browser }}
          config: ${{ matrix.config.config }}
          spec: ${{ matrix.specs }}

      - name: Generate report
        if: failure()
        uses: actions/upload-artifact@v2
        with:
          name: Failed on ${{matrix.browser}}-${{matrix.config.type}}
          path: |
            cypress/report/*
            cypress/screenshots/*
            cypress/videos/*

      - uses: rtCamp/action-slack-notify@v2.0.2
        name: notify slack on failure
        if: failure()
        env:
          SLACK_TITLE: Test(s) failed while running on ${{matrix.config.type}}
          SLACK_COLOR: danger
          SLACK_MESSAGE: ':failed: :computer-rage: Automation Failure on Prod! <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>'

  notify-slack-on-completion:
    needs: regression
    runs-on: ubuntu-latest
    steps:
      - uses: rtCamp/action-slack-notify@v2.0.2
        name: notify slack on run completion
        env:
          SLACK_TITLE: My Awesome Cypress Tests
          SLACK_COLOR: good
          SLACK_MESSAGE: ':pass: :white_check_mark: Automation Successful on Prod! <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>'

ð结论

最后,我想分享这种模式可用于木偶,嘲笑和其他框架。这种方法的主要好处是,如果付费墙落后,您就不必为并行运行能力付费。另外,它有助于减少您需要等待所有工作完成的时间。