运行 多个 cypress 实例在本地导致不可预知的错误

Running multiple cypress instances locally results in unpredictable errors

我们的 CI 中有一个功能强大的服务器,我们想利用它并在同一台机器上并行化我们的 cypress 测试套件。我们知道 cypress doesn't encourage it 但它应该是可能的!

我们有一个 bash 脚本,它将所有测试文件分成 n 组和 运行s cypress 在后台新端口上的每个组上:

npx cypress run --spec $specFiles --port $port --headless &

原则上,这应该可行,因为每个进程都会 运行 它的文件在单独的无头浏览器上。但是,如果有 4 个以上的工人,我们会遇到各种错误:

我们正在尽最大努力避免 运行 将每个 cypress 实例作为一个新的 docker 容器来避免额外的 CI 复杂性,但如有必要,我们将深入研究。我们是否遗漏了一些明显的东西?

完整的脚本供参考:

#!/bin/bash

nThreads=3

print_usage() {
  printf "Usage:
    ./run_tests_parallel.sh -n <number of threads to use>
    Defaults to $nThreads threads\n"
  exit 0;
}

while true; do
  case "" in
    -n | --threads ) nThreads=; shift 2 ;;
    -h | --help ) print_usage ; shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

echo Using $nThreads threads

# Return non-zero if any of the subprocesses
# returns non-zero
set -eu

testFiles=`find . -name "*.test.ts" -not -path "./node_modules/*"`

# init testF
testFilesPerThread=()
for (( n=0; n<$nThreads; n++ )); do
    testFilesPerThread+=("")
done

i=0
for testFile in $testFiles; do
    testFilesPerThread[$i]="${testFilesPerThread[$i]} $testFile"
    i=$((($i + 1)%$nThreads))
done

pids=()
for (( i=0; i<${#testFilesPerThread[@]}; i++ )); do
    echo Thread $i has files: ${testFilesPerThread[$i]}
    # strip string and join files with ","
    specFiles=`echo ${testFilesPerThread[$i]} | xargs | tr -s "\ " ","`
    port=$((30001+$i))

    # run tests in background
    npx cypress run --spec $specFiles --port $port --headless &
    pids+=($!)
    echo "Spawned PID ${pids[${#pids[@]}-1]} for thread $i on port $port"
done


for pid in ${pids[@]} ; do
  echo "Waiting for PID $pid."
  wait $pid
done

echo DONE.

我不是专家,但“意外的输入结束”听起来像是发生了文件访问冲突。可能有两个进程试图写入同一个测试工件。

我听说线程数一般不应超过核心数 - 1。在我的 4 核机器上,指定 3 个线程使我的吞吐量比 20 规格增加了大约 15%。

我使用了 NodeJS 脚本来调用 Cypress 模块 API,它允许在每个线程的基础上调整配置以避免文件写入冲突(请参阅 reporterOptions)

const path = require('path')
const fs = require('fs-extra')
const cypress = require('cypress')

const walkSync = function(dir, filelist = []) {
  const files = fs.readdirSync(dir);
  files.forEach(function(item) {
    const itemPath = path.join(dir, item)
    if (fs.statSync(itemPath).isDirectory()) {
      filelist = walkSync(itemPath, filelist);
    }
    else {
      filelist.push(itemPath);
    }
  });
  return filelist;
};
const files = walkSync(path.join(__dirname, '../cypress/integration'))

const groups= 3
const groupSize = Math.ceil(files.length / groups)
const groups = files.reduce((acc, file) => {
  if (!acc.length || acc[acc.length-1].length === groupSize) acc.push([]);
  acc[acc.length-1].push(file)
  return acc
},[])

console.time('process-time')
const promises = groups.map((group, i) => {
  return cypress.run({
    config: {
      video: false,
      screenshotsFolder: `cypress/screenshots/group${i}`,
      reporterOptions: {
        mochaFile: `results/group${i}/my-test-output.xml`,
      }
    },
    spec: group.join(','),
  })
})

Promise.all(promises).then(results => {
  console.timeEnd('process-time')
});

请参阅 Is there any way to run parallel cypress tests on local machine which uses cypress-parallel

正如 Fody 在 中提到的:

sounds like a file access clash has happened

所有这些错误都是由每个单独的后台进程转换我们的打字稿代码引起的竞争条件引起的。有时 cypress 实例会尝试 运行 测试,而另一个进程正在转换代码,这会导致各种错误,包括奇怪的语法错误。

我们决定 运行 现在将每组测试放在自己的容器中,因为这是将每个测试套件隔离在自己的环境中的唯一方法