cypress-parallel: Run Cypress Tests in Parallel Without Cypress Cloud
Cypress Cloud (formerly Dashboard) offers parallelization as a paid feature. The open-source cypress-parallel package delivers similar speed gains without the subscription. It distributes spec files across multiple CI workers using timing data from previous runs. Here's how to set it up.
What cypress-parallel Does
cypress-parallel reads your spec files, sorts them by recorded duration, and assigns them to N workers so each worker gets roughly equal total runtime. Unlike a simple alphabetical split, timing-based distribution minimizes the slowest worker's runtime.
It doesn't require a central server — each runner independently reads a shared weights file that you persist in CI cache.
Installation
npm install -D cypress-parallelHow It Works
# Run Cypress specs across 4 workers, this machine is worker 0
npx cypress-parallel \
-s cy:run \ <span class="hljs-comment"># npm script that runs Cypress
-t 4 \ <span class="hljs-comment"># total threads
-m 0 \ <span class="hljs-comment"># this machine's index (0-3)
-o <span class="hljs-string">'{"browser":"chrome"}' <span class="hljs-comment"># extra Cypress optionsThe key flags:
-t— total number of parallel workers-m— index of this worker (0-based)-s— npm script that runs Cypress (usuallycy:runorcypress:run)
After each run, cypress-parallel writes spec timing data to cypress/parallel-weights.json. Commit this file to keep timing data between runs.
package.json Setup
{
"scripts": {
"cy:run": "cypress run",
"cy:parallel": "cypress-parallel -s cy:run -t 4 -m"
}
}Call it with the worker index appended: npm run cy:parallel 0, npm run cy:parallel 1, etc.
GitHub Actions Matrix Setup
name: Cypress Tests
on: [push, pull_request]
jobs:
cypress-run:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
index: [0, 1, 2, 3]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Restore Cypress binary cache
uses: actions/cache@v3
with:
path: ~/.cache/Cypress
key: cypress-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Cypress binary
run: npx cypress install
- name: Start dev server
run: npm run start &
# Or use cypress-wait-on: npx wait-on http://localhost:3000
- name: Run Cypress shard ${{ matrix.index }}/4
run: npx cypress-parallel -s cy:run -t 4 -m ${{ matrix.index }}
env:
CYPRESS_BASE_URL: http://localhost:3000
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: cypress-results-${{ matrix.index }}
path: |
cypress/videos/
cypress/screenshots/
cypress/reports/
retention-days: 7
collect:
needs: cypress-run
if: always()
runs-on: ubuntu-latest
steps:
- name: Download all results
uses: actions/download-artifact@v4
with:
pattern: cypress-results-*
merge-multiple: true
path: all-results/
- name: Upload combined results
uses: actions/upload-artifact@v4
with:
name: cypress-combined-results
path: all-results/Persisting Timing Weights
The spec weights file is what makes distribution improve over time. Two strategies:
Strategy 1: Commit to git
git add cypress/parallel-weights.json
git commit -m "chore: update cypress parallel weights"Works well if CI pushes back to the repo, or if weights are generated locally and committed manually.
Strategy 2: CI cache
- name: Restore weights
uses: actions/cache@v3
with:
path: cypress/parallel-weights.json
key: cypress-weights-${{ github.ref }}
restore-keys: cypress-weights-
- name: Run tests
run: npx cypress-parallel -s cy:run -t 4 -m ${{ matrix.index }}
- name: Save weights
uses: actions/cache@v3
with:
path: cypress/parallel-weights.json
key: cypress-weights-${{ github.ref }}Cache strategy works without git access from CI but may miss weights on first run of a new branch.
GitLab CI Setup
cypress-tests:
image: cypress/included:13.0.0
parallel: 4
script:
- npm ci
- |
# GitLab sets CI_NODE_INDEX (1-based) and CI_NODE_TOTAL
WORKER_INDEX=$(( CI_NODE_INDEX - 1 ))
npx cypress-parallel -s cy:run -t $CI_NODE_TOTAL -m $WORKER_INDEX
artifacts:
when: always
paths:
- cypress/videos/
- cypress/screenshots/
- cypress/reports/
expire_in: 1 week
reports:
junit: cypress/reports/junit-*.xmlNote: GitLab's CI_NODE_INDEX is 1-based, but cypress-parallel is 0-based. The $(( CI_NODE_INDEX - 1 )) subtraction handles this.
Generating JUnit Reports
Cypress's built-in reporter doesn't output JUnit. Add cypress-multi-reporters:
npm install -D cypress-multi-reporters mocha-junit-reportercypress.config.js:
const { defineConfig } = require('cypress');
module.exports = defineConfig({
reporter: 'cypress-multi-reporters',
reporterOptions: {
configFile: 'reporter-config.json',
},
e2e: {
// ...
},
});reporter-config.json:
{
"reporterEnabled": "spec, mocha-junit-reporter",
"mochaJunitReporterReporterOptions": {
"mochaFile": "cypress/reports/junit-[hash].xml"
}
}The [hash] in the filename creates unique files per spec — necessary when running in parallel to avoid overwriting.
Filtering Which Specs Run
cypress-parallel supports filtering specs:
# Only run specs matching a pattern
npx cypress-parallel -s cy:run -t 4 -m 0 --spec <span class="hljs-string">"cypress/e2e/checkout/**"
<span class="hljs-comment"># Exclude slow specs from parallel run
npx cypress-parallel -s cy:run -t 4 -m 0 --ignore <span class="hljs-string">"cypress/e2e/heavy/**"Or set in package.json:
{
"scripts": {
"cy:parallel": "cypress-parallel -s cy:run -t 4 -m",
"cy:serial": "cypress run --spec 'cypress/e2e/heavy/**'"
}
}Comparing with Cypress Cloud
| Feature | cypress-parallel | Cypress Cloud |
|---|---|---|
| Cost | Free | Paid ($0-$400+/mo) |
| Timing-based distribution | ✅ | ✅ |
| Real-time dashboard | ❌ | ✅ |
| Automatic load balancing | File-based | Dynamic |
| Video storage | CI artifacts | Cloud |
| Flaky test detection | ❌ | ✅ |
| Setup complexity | Low | Very low |
For teams already paying for CI minutes, cypress-parallel delivers the core parallelization benefit for free. The missing features (real-time dashboard, cloud video storage) matter less for teams with tight budgets.
Troubleshooting
All workers run the same specs: Worker index not being passed correctly. Verify ${{ matrix.index }} is 0-3, not 1-4.
Weights file not found: On first run, cypress-parallel distributes specs alphabetically. Run once to generate the weights file.
Some workers have no specs: Happens when spec count < worker count, or when spec patterns don't match. Check the resolved file list with --verbose flag.
Tests pass locally but fail in CI: Usually a timing issue. Add npx wait-on http://localhost:3000 before running Cypress if your dev server takes time to start.
Summary
cypress-parallel splits Cypress spec files across parallel CI workers using timing data from previous runs. Set up with npm install -D cypress-parallel, add matrix jobs in GitHub Actions (or parallel: N in GitLab), pass the worker index and total as -m and -t flags, and persist cypress/parallel-weights.json to improve distribution over time. No Cypress Cloud subscription required.