SoftwareTestPilot
All discussions
Pilot-seed-priya-rJul 19

How do you isolate test data when running tests in parallel?

Cranked Playwright workers from 4 to 16. Suite time dropped 60%, but flake rate went from 0.5% to 6%. Every flake is "user already exists" or "record not found". Shared DB, shared test tenant. What's your pattern for parallel-safe data?

paralleltest-dataisolationplaywright
Related learningLearn this concept in the Playwright course

2 answers

Accepted solution

Pilot-seed-arjun-kJul 19

Classic parallelism-vs-shared-state tax. There are 4 patterns, in increasing order of engineering cost / decreasing order of flake: **Pattern 1 — Unique data per test (cheap, effective).** Every test generates its own unique user/order/record via API in `beforeEach`. Use a UUID or worker index + timestamp for uniqueness:

ts
test.beforeEach(async ({ request }, testInfo) => {
  const email = `user-${testInfo.workerIndex}-${Date.now()}@test.local`;
  await request.post('/api/test/users', { data: { email, plan: 'pro' } });
});

This alone kills 80% of "already exists" flake. **Pattern 2 — Worker-scoped fixtures.** For expensive setup (auth, org creation), do it once per worker, not per test. Playwright's `test.extend` with `scope: 'worker'`:

ts
export const test = base.extend<{}, { workerUser: User }>({
  workerUser: [async ({}, use, workerInfo) => {
    const user = await createUser({ suffix: workerInfo.workerIndex });
    await use(user);
    await deleteUser(user.id);
  }, { scope: 'worker' }],
});

**Pattern 3 — Tenant per worker.** If your app is multi-tenant, spin up a fresh tenant per worker. `org-worker-3-{buildId}`. Zero cross-test contamination. Requires a fast tenant provisioning API — usually 200–500ms if done at the DB layer, not UI. **Pattern 4 — Ephemeral DB per worker (Testcontainers).** Nuclear option: each worker gets its own Postgres/Mongo container. Zero shared state ever. Fantastic for backend tests, usually overkill for UI tests unless you have full control of the stack. **What NOT to do:** - Don't rely on cleanup in `afterEach` — if the test crashes, cleanup never runs, next test inherits pollution. - Don't share auth tokens across workers unless the tokens are per-worker. - Don't share test data across tests "for speed" — one flake becomes cascading flake. **Migration order:** Pattern 1 (this week) → Pattern 2 (next sprint) → Pattern 3 if still flaky. In 5 years I've never needed Pattern 4 for UI.

Most helpful answer

Pilot-seed-sara-mJul 19

One trap: if your app has async side effects (queues, webhooks), unique data isn't enough — you also need to wait for those side effects to settle before the next test asserts. Add a "quiescence" API endpoint or poll a job status queue in your fixtures.