Skip to main content

One E2E Test Breaks Another (World-State Mutation)

Short answer

When Test A creates an order and Test B expects an empty list, order-dependent failures are world-state bugs—not Playwright randomness. Fix with per-run seed routes, runId-scoped entities, and probe Assert—never shared staging users or global coupons.

Part of Common E2E testing gotchas.

Symptom

  • Spec passes with --grep @orders alone
  • Full suite fails: “Expected 0 orders, received 3”
  • Parallel CI shows races: duplicate email, coupon already used

Root cause

Tests mutate shared mutable state:

  • One global test@company.com user
  • One promo code SAVE20 in staging DB
  • Orders/carts left behind without isolation

Fix: per-run universe

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('creates order', async ({ page, request }) => {
await request.post('/api/test/seed-cart', { data: { runId, sku: 'SKU-1' } });
await page.goto(`/checkout?run=${runId}`);
// Act ...
const probe = await request.get(`/api/test/probe-orders?runId=${runId}`).then(r => r.json());
expect(probe.count).toBe(1);
});

test('empty state for new user', async ({ page, request }) => {
await request.post('/api/test/seed-user', { data: { runId, orders: 0 } });
await page.goto(`/orders?run=${runId}`);
const probe = await request.get(`/api/test/probe-orders?runId=${runId}`).then(r => r.json());
expect(probe.count).toBe(0);
});

Each spec gets its own runId—Test A’s order never appears in Test B’s probe.

Example scenario

Situation: Test A checks out and creates order #1001. Test B asserts the orders list is empty for a new shopper.

Expected outcome: Each test sees only its own run-scoped data—no cross-test pollution.

Why UI-only automation breaks: UI list is empty because pagination hides rows, while DB still has Test A's order.

  1. Arrange: POST /api/test/reset-world with unique runId per worker; seed entities keyed to runId only.
  2. Act: Test A completes checkout; Test B opens orders page with different runId.
  3. Assert: Probe /api/test/probe-orders?runId=... returns counts matching each test's Arrange—not global table size.

TestChimp workflow: Document isolation rules in markdown plans; /testchimp init scaffolds seed and probe routes; link specs with // @Scenario:.

Same Arrange/Act/Assert pattern as expired-coupon checkout.

Teardown vs isolation

ApproachPrefer when
Isolation (seed per run)Default—parallel CI, fast feedback
Teardown after specLegacy DB without easy namespacing—slower, still racy under parallel
Shared DB reset jobNightly only—not per PR

Full build guide: seed routes and probe Assert.

Anti-patterns

Anti-patternWhy it failsBetter approach
Shared staging userSession overwriteSeed unique user per runId
Hard-coded couponExhausted by worker 2Mint coupon in seed route
Assert UI list length onlyPagination hides rowsProbe count by runId
Serial suite to mask bugHides isolation debtFix Arrange

TestChimp workflow

/testchimp init adds test-only seed/probe routes and fixture patterns. /testchimp test on PRs extends Arrange when new specs need isolation—agents read scenarios in markdown plans, not ad hoc prompts.

Frequently asked questions

Can I use a fresh database per test instead of runId?

Yes if affordable—containers per job work. For most teams, namespaced runId in shared staging is faster and enough for parallel workers.

Should tests clean up after themselves?

Isolation at Arrange is more reliable than teardown under parallel CI. Teardown as backup is fine; do not rely on it alone.

We do not have seed routes yet—minimal first step?

Add POST /api/test/seed-user accepting runId and email suffix. Gate route to non-production. /testchimp init can scaffold this pattern.

Why does order of tests matter?

Order dependency means shared state—tests are coupled. Each spec should Arrange its own universe.

How do probes help empty-state tests?

UI may paginate or cache; probe returns authoritative count for runId-scoped rows.

Does TestChimp replace our fixture layer?

No—it orchestrates Playwright plus seed/probe harness in Git. You own the routes; agents maintain specs linked to markdown scenarios.

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.

Start free on TestChimp · Book a demo