Parallel CI Causes Test Data Collisions
Short answer
Enabling --workers=4 without isolation turns one green spec into four races on SAVE20, test@company.com, and cart id 123. Fix before adding shards: runId on every seed route, unique emails per worker, and probe Assert—not serial suites or global DB wipes.
Part of Common E2E testing gotchas.
Symptom
- Suite was green serial; red after parallel CI or
fullyParallel: true - Intermittent "email already registered", "coupon already redeemed", "insufficient inventory"
- Worker 3 passes, worker 1 fails on the same spec—swap on retry
- Local laptop green, GitHub Actions red at
--workers=4
Root cause
Parallel workers share mutable identifiers in staging:
- Global promo codes and gift cards
- Fixed test emails and phone numbers
- Hard-coded SKUs with quantity 1
- Shared
storageStateand admin sessions (auth pollution)
Playwright parallelism is correct—the Arrange layer assumed a single runner. See world-state mutation.
Fix: namespaced Arrange per worker
const runId = `w${test.info().workerIndex}-${test.info().parallelIndex}-${Date.now()}`;
test.beforeEach(async ({ request }) => {
await request.post('/api/test/reset-world', { data: { runId } });
});
test('applies fresh coupon', async ({ page, request }) => {
const { code } = await request.post('/api/test/seed-coupon', {
data: { runId, discountPercent: 20 },
}).then(r => r.json());
await page.goto(`/checkout?run=${runId}`);
await page.getByLabel('Promo code').fill(code);
await page.getByRole('button', { name: 'Apply' }).click();
await expect.poll(async () => {
const probe = await request.get(`/api/test/probe-cart?runId=${runId}`).then(r => r.json());
return probe.discountCents;
}).toBeGreaterThan(0);
});
Each worker operates in its own runId universe—collisions disappear without test.describe.serial.
Build the harness: seed routes and probe Assert. Wire CI: GitHub Actions parallel.
Reproduce before you shard
| Step | Command / action |
|---|---|
| Local parallel | npx playwright test --workers=4 |
| Single file | npx playwright test checkout.spec.ts --workers=4 |
| Isolate worker | DEBUG=pw:api npx playwright test --workers=1 to confirm Arrange bug |
Do not add GitHub Actions shards until --workers=4 is green locally—sharding multiplies collision surface without fixing roots.
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
test.describe.serial for whole suite | Hides debt; slow CI | runId isolation |
| Nightly DB reset only | PRs still race | Per-run seed routes |
| Random suffix in spec only | DB row not keyed | Server accepts runId on insert |
| Raise timeout to 120s | Masks contention | Unique entities per worker |
| One shared staging coupon | First worker wins | Mint coupon in seed route |
TestChimp workflow
/testchimp init adds reset-world, seed-*, and probe-* routes with runId conventions your agents reuse. /testchimp test on PRs extends Arrange when new specs need worker-safe data—markdown plans document which entities must be namespaced.
Related
- World-state mutation
- Hardcoded test data flakes
- E2E foundations
- Flaky E2E playbook
- Playwright parallelization
- Playwright sharding
Frequently asked questions
How many workers should we use in CI?
Start with 4 locally until green, then match CPU on GitHub runners. More workers without runId isolation only increases collision rate.
Is test.describe.serial ever OK?
Rarely—for true singleton resources you cannot namespace (legacy mainframe). Default to isolation; serial is a last resort with a ticket explaining why.
Do we need a database per worker?
Usually no—runId columns or schemas in shared staging are enough. Containers per job work but cost more; pick based on infra budget.
Sharding vs workers—what is the difference?
Workers run tests concurrently in one job; shards split the suite across jobs. Both require isolation—sharding does not fix shared coupons.
Flakes started after merging a new spec—why?
New spec may use a hard-coded email or coupon the rest of the suite already consumes. Audit Arrange for fixed identifiers.
Can storageState cause parallel collisions?
Yes—one admin session across workers causes role and tenant bleed. See shared auth state gotcha; seed per worker or use API session headers.
Does TestChimp help with parallel CI setup?
/testchimp init scaffolds runId seed and probe routes—the same harness agents extend when /testchimp test adds specs on PRs. You keep Playwright sharding; data layer becomes safe.
Should we disable fullyParallel?
Disabling parallelism slows CI without fixing the bug. Fix Arrange; keep fullyParallel for independent specs.
Apply these patterns in your repo
Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.