没有 Cypress 仪表板的 Cypress 并行化

Posted

技术标签:

【中文标题】没有 Cypress 仪表板的 Cypress 并行化【英文标题】:Cypress Parallelisation Without Cypress Dashboard 【发布时间】:2020-09-10 09:17:01 【问题描述】:

有没有一种方法可以在不访问 cypress 仪表板的情况下运行并行 cypress 执行程序?

我正在尝试让 cypress 并行运行我的测试,但您似乎必须有互联网连接才能访问 cypress 仪表板来记录测试。

任何人都知道我如何解决这个问题并在不依赖 Cypress Dashboard 或特定 CI 工具(如 Circle CI)的情况下并行运行测试?

提前致谢!

【问题讨论】:

【参考方案1】:

我创建了一个Github Action,灵感来自Bielik 的答案,除其他功能外,它还允许指定测试运行器的数量。

执行由两部分组成: 首先,准备/安装步骤将测试拆分为规范(最好结合Cypress Dashbaord 的需求):

...
- uses: tgamauf/cypress-parallel@v1

这提供了两个输出变量integration-tests,如果配置了component-tests,则可以在测试步骤中使用:

    strategy:
      fail-fast: false
      matrix:
        spec: $ fromJson(needs.install.outputs.integration-tests) 
...
      - name: Execute tests
        uses: cypress-io/github-action@v2
        with:
          # We have already installed all dependencies above
          install: false
          # Use the spec provided to this worker to execute the test 
          spec: $ matrix.spec 

【讨论】:

【参考方案2】:

所有这些答案看起来都很好,但我想要一些非常简单的东西,可以与 GitLab 一起使用,不需要额外的外部服务。我想出了一个非常简单的 Cypress 包装器:

const path = require('path');
const fs = require('fs');
const childProcess = require('child_process');
const glob = require('glob-promise');

async function getIntegrationDirectory() 
    let integrationFolder = 'cypress/integration';
    let cypressJson = path.join(process.cwd(), 'cypress.json');
    try 
        await fs.promises.access(cypressJson, fs.constants.F_OK);
        let cypressConfig = require(cypressJson);
        integrationFolder = cypressConfig.integrationFolder ?? integrationFolder;
    
    catch (err) 
        // Ignore if file does not exist
    
    return integrationFolder;


async function main() 
    let nodeIndex = parseInt(process.env.CI_NODE_INDEX ?? 1, 10);
    let totalNodes = parseInt(process.env.CI_NODE_TOTAL ?? 1, 10);
    let integrationFolder = await getIntegrationDirectory();
    let specs = await glob(integrationFolder + '/**/*.js');
    let start = (nodeIndex - 1) * specs.length / totalNodes | 0;
    let end = nodeIndex * specs.length / totalNodes | 0;
    let specsToRun = specs.slice(start, end);
    let prefix = `Parallel Cypress: Worker $nodeIndex of $totalNodes`;
    if (!specsToRun.length) 
        console.log(`$prefix, no specs to run, $specs.length total`);
        return;
    
    console.log(`$prefix, running specs $start + 1 to $end of $specs.length total`);
    let args = process.argv.slice(2)
        .concat(['--spec', specsToRun.join(',')]);
    let child = childProcess.spawn('cypress', args, stdio: 'inherit', shell: true);
    await new Promise((resolve, reject) => 
        child.on('exit', exitCode => 
            if (exitCode) 
                reject(new Error(`Child process exited with code $exitCode`));
            
            resolve();
        );
    );


main().catch(err => 
    console.error(err);
    process.exit(1);
);

它需要安装glob-promiseglob

它与 GitLab 的 parallel keyword 完美集成,只需极少修改即可与其他 CI 运行器一起使用。

要使用它,只需将 parallel: 4 添加到 GitLab CI 作业,然后调用 node parallelCypress.js <some options>。然后它将扫描测试并将它们拆分为可用的并行作业,然后使用 --spec 选项调用 cypress,以便每个作业只运行一部分测试。

还有很多的改进空间,特别是因为并非所有规范文件都包含相同数量的测试,并且并非所有测试都具有相似的运行时。

【讨论】:

【参考方案3】:

使用 GitHub Actions 我设计了一个可能有点矫枉过正的解决方案,但不需要 Cypress Dashboard 并且仍然允许用户并行运行他们的 e2e 测试。解决方案在私人项目中,所以我不能分享链接,但我可以给你简化的代码 sn-ps,希望足以让你开始。

我们的想法是使用 GitHub Actions 在单独的机器上运行每个规范。您在安装作业的某处运行此脚本(记得全局输出):

import * as core from '@actions/core';
import * as glob from 'glob';

export function setE2E() 
  const specs = getAllSpecFiles();

  core.info(JSON.stringify( specs , null, 2));

  core.setOutput('e2e-files', specs);


function getAllSpecFiles() 
  let projectRoot; // fill
  let pattern; // fill eg. "./src/integration/**/*.spec.ts"

  return glob.sync(pattern, 
    cwd: projectRoot,
    dot: true,
    silent: true,
    follow: true,
    nodir: true,
  );

这将从您的 cypress 项目中获取所有 spec 文件。 然后在您的 e2e 工作中:

    strategy:
      fail-fast: false
      matrix:
        spec: $ fromJson(needs.install.outputs.e2e-files) 
...
    - name: Run Spec
      run: npx cypress run --spec $ matrix.spec 

这将为您的 cypress 项目中的每个规范生成单独的机器。正如我所说,这有点矫枉过正,但配置和完成工作非常简单。

【讨论】:

【参考方案4】:

如果您使用 Github Actions,我创建了这个并行运行测试的工作流,每次并行运行最多 15 个规范文件,因此在添加新测试时它会自动扩展。它可能对您或其他人有所帮助。这也使应用程序在 https 模式下运行,但如果不需要,您可以删除这些行。

https://pastebin.com/ubx8BdUn

# This is a basic workflow to help you get started with Actions
name: Project
 
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
  push:
    branches: [master, preprod, staging]
  pull_request:
    branches: [master, preprod, staging]
 
jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      # will contain a json string with an array of n elements, each being a string of spec files delimited by ,
      test-chunks: $ steps['set-test-chunks'].outputs['test-chunks'] 
      # json string with ids to use in the next job matrix depending on how many elements are in the above array, eg: [0,1]
      test-chunk-ids: $ steps['set-test-chunk-ids'].outputs['test-chunk-ids'] 
    steps:
      - uses: actions/checkout@v2
      - id: set-test-chunks
        name: Set Chunks
        # get all spec files from the integration directory, group them to be at most 15 at a time and transform them to json
        run: echo "::set-output name=test-chunks::$(find cypress/integration -type f -name "*.spec.js" | xargs -n15 | tr ' ' ',' | jq -R . | jq -s -cM .)"
      - id: set-test-chunk-ids
        name: Set Chunk IDs
        # get the number of elements from the above array as an array of indexes
        run: echo "::set-output name=test-chunk-ids::$(echo $CHUNKS | jq -cM 'to_entries | map(.key)')"
        env:
          CHUNKS: $ steps['set-test-chunks'].outputs['test-chunks'] 
 
  tests:
    needs:
      - setup
    runs-on: ubuntu-latest
    container:
      # use cypress image, since just using node 12 doesn't work currently for some reason, gives node-sass error
      image: cypress/browsers:node12.13.0-chrome78-ff70
      options: "--ipc=host" # fix for a cypress bug
    name: test (chunk $ matrix.chunk )
    strategy:
      matrix:
        # will be for eg chunk: [0,1]
        chunk: $ fromJson(needs.setup.outputs['test-chunk-ids']) 
    steps:
      - name: Checkout
        uses: actions/checkout@v2
 
      - name: Add domain to hosts file
        run: echo "127.0.0.1 your.domain" | tee -a /etc/hosts
 
      # cache cypress and node_modules for faster operation
      -  uses: actions/cache@v2
         with:
           path: '~/.cache/Cypress'
           key: $ runner.os -cypress-$ hashFiles('**/yarn.lock') 
 
      - uses: actions/cache@v2
        with:
          path: '**/node_modules'
          key: $ runner.os -modules-docker-$ hashFiles('**/yarn.lock') 
 
      # in case cache is not valid, install the dependencies
      - run: yarn --frozen-lockfile
      - run: yarn run cypress install
 
      # run the frontend server in background and wait for it to be available
      - run: PORT=443 HTTPS=true yarn ci-start &
      - run: npx wait-on https://your.domain --timeout 180000
 
      # the cypress docker doesn't contain jq, and we need it for easier parsing of json array string.
      # This could be improved in the future, but only adds ~2s to the build time
      - run: apt-get install jq -y
 
      - name: Run Cypress
        run: SPECS=$(echo $CHUNKS | jq -cMr '.[$ matrix.chunk ] | @text') && yarn cypress:ci --spec $SPECS
        env:
          NODE_TLS_REJECT_UNAUTHORIZED: 0
          CHUNKS: $ needs.setup.outputs['test-chunks'] 
 
  testsall:
    if: $ always() 
    runs-on: ubuntu-latest
    name: Tests All
    needs: tests
    steps:
      - name: Check tests matrix status
        if: $ needs.tests.result != 'success' 
        run: exit 1

【讨论】:

【参考方案5】:

Testery.io 是一个基于云的测试平台,支持并行运行赛普拉斯测试。您可以注册免费计划以并行运行多达 5 个测试,将执行集成到您的 ci/cd 系统中,并在平台上查看结果。如果您选择付费计划,您还可以并行运行 15-30 次测试:https://testery.io/pricing。

【讨论】:

【参考方案6】:

我创建了orchestrator 工具来执行此操作。它允许您部署任意数量的 cypress docker 容器,并在它们之间拆分所有规范,最后,它会生成一个漂亮的 html 报告。

它是开源的,免费使用,您可以将它与 Jenkins、TravisCI、github 操作或任何其他 CI 一起使用。

【讨论】:

【参考方案7】:

如果使用 GitLab,您始终可以通过 .gitlab-ci.yml 文件手动拆分作业,如下所示:

 1-job:
   stage: acceptance-test
   script:
     - npm install
     - npm i -g wait-on
     - wait-on -t 60000 -i 5000 http://yourbuild
     - npm run cypress -- --config baseUrl=http://yourbuild --spec ./**/yourspec1

 2-job:
   stage: acceptance-test
   script:
     - npm install
     - npm i -g wait-on
     - wait-on -t 60000 -i 5000 http://yourbuild
     - npm run cypress -- --config baseUrl=http://yourbuild --spec ./**/yourspec2

【讨论】:

【参考方案8】:

看看这个免费的解决方案https://github.com/agoldis/sorry-cypress 我只使用 docker-compose 中的 director 服务来让测试并行运行,举个简单的例子:

version: '3.7'
networks:
  default:
    external:
      name: bridge

services:
  cypress:
    container_name: cypress
    build:
      context: ../
      dockerfile: ./docker/cy.Dockerfile
    links:
      - director
    ports:
      - '5555:5555'
    network_mode: bridge

  cypress2:
    container_name: cypress2
    build:
      context: ../
      dockerfile: ./docker/cy.Dockerfile
    links:
      - director
    ports:
      - '5556:5556'
    network_mode: bridge

  mongo:
    image: mongo:4.0
    network_mode: bridge
    ports:
      - 27017:27017

  director:
    image: agoldis/sorry-cypress-director:latest
    environment:
      MONGODB_URI: "mongodb://mongo:27017"
    network_mode: bridge
    ports:
      - 1234:1234
    depends_on:
      - mongo

【讨论】:

如果您使用stateless 或内存选项,我相信您甚至可能不需要mongodb? github.com/agoldis/sorry-cypress#drivers @floydrose 可以解释您为什么使用两个 cypress 服务?【参考方案9】:

尝试在 Buddy CI/CD 中添加 Cypress 操作。这样,您将在每次推送时构建和测试您的应用程序(并将它们部署到您想要的任何地方)。我认为 Buddy 应该帮助你解决这个问题。

enter image description here

【讨论】:

【参考方案10】:

你可以试试这个cypress-parallel-specs-locally

【讨论】:

这将运行并行进程,但测试将按顺序执行。

以上是关于没有 Cypress 仪表板的 Cypress 并行化的主要内容,如果未能解决你的问题,请参考以下文章

在 Browserstack 中运行后发布到 Cypress Dashboard

从项目的根目录移动 cypress 文件夹

Cypress系列- 如何学习 Cypress

Cypress系列- 如何学习 Cypress

使用 cypress-cucumber-preprocessor 将现有的 cypress 测试转换为黄瓜风格的 bdd。第二个场景没有被选中

初识cypress