How to Run Parallel End-to-End Tests
Run end-to-end tests faster with safe data isolation, Playwright workers and shards, or Cypress Cloud parallelization.
Parallel end-to-end tests are safe only when each test can run in any order without corrupting another test’s state. Start by isolating accounts, records, files and external resources. Then increase concurrency gradually: use local workers on one machine, and shard the suite across CI machines when one runner is no longer fast enough.
The exact commands depend on your test framework. This guide focuses on Playwright Test and Cypress because their official documentation defines different parallelization models.
1. Make tests independent before adding concurrency
Parallel workers expose state assumptions that serial execution hides. Before changing worker counts, inspect whether tests:
- Reuse the same user account, order, project or database row.
- Assume another test has already created or deleted data.
- Write to a shared screenshot, download or report path.
- Modify global settings, feature flags or tenant configuration.
- Use a rate-limited API or external resource without coordination.
- Depend on test order, timing or a previous browser session.
Give each test unique identifiers, or allocate one isolated dataset per worker. Include the worker identity in generated names and filesystem paths. For a resource that truly cannot be accessed concurrently, use a narrow lock around that resource. Do not serialize unrelated tests to hide a data-ownership bug.
Isolation checklist
- Create fresh records in setup and delete them in teardown where practical.
- Use unique emails such as
checkout-${workerIndex}-${random}@example.test. - Use a separate temporary directory for each worker.
- Reset global settings after every test or use isolated tenants.
- Make retries safe: a retried test must not collide with data from its first attempt.
- Run the suite in a different order or with one worker to find hidden dependencies.
2. Run Playwright tests with local workers
Playwright runs tests in separate files in parallel by default. Tests in one file run in order unless you opt that file or project into parallel mode. Set a worker limit that fits the machine’s CPU, memory, browser capacity and test environment.
Command line
npx playwright test --workers 4
The number four is an example, not a universal recommendation. Start lower, record the baseline, and increase it while watching runtime, memory pressure, server load and failure rates.
Configuration
import { defineConfig } from '@playwright/test';
export default defineConfig({
workers: process.env.CI ? 2 : undefined,
retries: process.env.CI ? 1 : 0,
reporter: [['html'], ['blob', { outputDir: 'blob-report' }]],
});
Keep CI and local settings explicit. A developer laptop and a CI runner rarely have the same resources. Retries can help diagnose environmental flakes, but they do not make unsafe tests safe.
Parallel tests within one file
Use file-level parallel mode only when the tests in that describe block own independent state:
import { test, expect } from '@playwright/test';
test.describe.configure({ mode: 'parallel' });
test('user can create a project', async ({ page }) => {
await page.goto('/projects/new');
await page.getByLabel('Name').fill(`project-${Date.now()}-a`);
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.getByText('Project created')).toBeVisible();
});
test('user can archive a project', async ({ page }) => {
await page.goto('/projects');
await page.getByRole('button', { name: 'Archive test project' }).click();
await expect(page.getByText('Archived')).toBeVisible();
});
Fully parallel projects
fullyParallel: true enables test-level parallelism across the project. Tests execute in separate worker processes, so they cannot share mutable global variables or browser state:
import { defineConfig } from '@playwright/test';
export default defineConfig({
fullyParallel: true,
workers: process.env.CI ? 4 : undefined,
});
Choose this only after fixtures, test data and setup code are compatible with process-level isolation. Playwright’s guidance is direct: “Above all, keep your tests isolated from one another.” See the Playwright parallelism documentation.
3. Shard Playwright across CI machines
When one machine is the bottleneck, run multiple CI jobs. Each job receives a shard index:
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3
For example, a GitHub Actions matrix can create three independent jobs:
jobs:
e2e:
strategy:
fail-fast: false
matrix:
shard: [1/3, 2/3, 3/3]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test --shard=${{ matrix.shard }}
Without fullyParallel, Playwright normally distributes whole files. If files contain very different numbers of tests, one shard can finish much later than the others. With fullyParallel: true, Playwright can distribute individual tests for finer balance, provided every test is independent.
Merge shard reports
Write a blob report in every job, upload the artifacts, then merge them in a final job:
npx playwright merge-reports --reporter html ./all-blob-reports
Use the output directory and artifact names from your CI provider consistently. The Playwright sharding documentation covers blob reports and report merging.
4. Parallelize Cypress with recorded Cloud runs
Cypress uses a different model. Its documented parallelization path requires multiple CI machines, a recorded run and the --parallel flag:
npx cypress run --record --key=YOUR_CYPRESS_RECORD_KEY --parallel
Start the same command on each CI machine for the same build. Cypress Cloud assigns available spec files to machines and uses duration data to balance them. It distributes whole spec files, so one unusually long spec can remain the final bottleneck. Read the Cypress parallelization documentation and load-balancing documentation for the current command and recording requirements.
CI considerations for Cypress
- Use the same commit or build identifier on every machine.
- Store the record key as a CI secret.
- Ensure every machine has the same browser and dependency versions.
- Split very long specs when they consistently hold up a machine.
- Do not assume adding machines gives linear speedup; measure the slowest machine’s finish time.
Cypress reports an example of almost 50% time saved when its documented run was distributed across two machines. Treat that as the result of that example, not as a general benchmark.
5. Choose workers, shards or both
| Approach | Best when | Tradeoff |
|---|---|---|
| More workers on one machine | The suite is isolated and the runner has spare CPU and memory. | Browsers and application services contend for resources. |
| Playwright shards | One CI machine cannot finish quickly enough. | File-level splits can be uneven; reports must be merged. |
| Playwright fully parallel shards | Tests are independent and files vary greatly in size. | Fixtures and globals must work across worker processes. |
| Cypress Cloud parallelization | You use recorded Cypress runs and can provision multiple machines. | Specs are the unit of work; a long spec can limit gains. |
| Locks or serial execution | A specific external resource cannot be shared safely. | Only the protected section loses concurrency. |
6. Measure speed, reliability and cost
After each concurrency change, record:
- Total wall-clock time.
- Duration of every worker, shard or CI machine.
- CPU, memory and browser process pressure.
- Application, database and third-party API load.
- First-run failures, retries and failures that disappear when rerun.
- CI minutes and the cost of additional machines or hosted services.
If one job finishes much later than the others, inspect its slow files or specs before adding more machines. If failures increase as workers increase, look first for shared data, filesystem paths, global settings and service rate limits. A shorter total time is not an improvement if it produces unreliable feedback.
7. Troubleshooting parallel test failures
Tests pass with one worker but fail with several
Cause: shared accounts, records, files or global configuration.
Fix: assign unique data per test or worker, isolate paths, reset global state and rerun the smallest failing group with two workers.
Failures change between runs
Cause: order dependence or a race in setup and teardown.
Fix: remove test-to-test assumptions, wait for observable application state instead of arbitrary sleeps, and make cleanup idempotent.
One Playwright shard takes much longer
Cause: whole-file sharding with uneven file durations.
Fix: split large files, enable fullyParallel after proving isolation, or adjust the shard count. Merge blob reports so the combined result remains readable.
One Cypress machine remains busy
Cause: Cypress Cloud balances whole specs and cannot split one long spec.
Fix: identify the long spec in Cloud duration data and divide it into independent specs.
Browsers crash or time out at higher worker counts
Cause: CPU or memory exhaustion, overloaded application services, database connection limits or third-party rate limits.
Fix: lower workers, inspect resource graphs, raise service limits where appropriate and increase concurrency only when the environment has capacity.
Retries hide a real defect
Cause: retrying masks a race or flaky dependency.
Fix: keep retries for diagnosis, collect traces and logs for every attempt, and repair state ownership or synchronization instead of relying on repeated passes.
8. Or skip the browser setup
If your workflow needs screenshots of test environments, staging pages or visual checkpoints, ScreenshotNeo provides a single website screenshot API request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed; response headers identify the page verdict and whether the request was billed.
See the ScreenshotNeo API documentation for all options. A request can run alongside your test workers:
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));
ScreenshotNeo also supports full-page captures with lazy images loaded, element selectors, dark mode, device presets, custom viewports, retina scale, custom CSS and JavaScript, clicks, waits, blocked resources, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call and a usage API. An MCP server exposes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
Free usage includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
9. FAQ
Should I increase workers or add CI machines first?
Increase local workers while the machine and services have capacity. Add CI machines when one runner remains the bottleneck or when the suite needs a shorter feedback window.
Does parallel execution always reduce runtime?
No. Setup overhead, resource contention and uneven test distribution can erase the gain. Measure total time and the slowest worker or shard.
Can I parallelize tests that share one account?
Only if each test’s operations are isolated and order-independent. In most cases, create separate accounts or tenants per test or worker.
Are Playwright shards and Cypress parallelization interchangeable?
No. Playwright assigns shards with its runner and can distribute files or tests. Cypress Cloud requires recorded runs and distributes whole spec files.
What is the safest first experiment?
Run two workers against a small, isolated subset, compare failures and resource usage, then expand gradually.


