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 @ordersalone - 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.comuser - One promo code
SAVE20in 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.
- Arrange: POST /api/test/reset-world with unique runId per worker; seed entities keyed to runId only.
- Act: Test A completes checkout; Test B opens orders page with different runId.
- 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
| Approach | Prefer when |
|---|---|
| Isolation (seed per run) | Default—parallel CI, fast feedback |
| Teardown after spec | Legacy DB without easy namespacing—slower, still racy under parallel |
| Shared DB reset job | Nightly only—not per PR |
Full build guide: seed routes and probe Assert.
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
| Shared staging user | Session overwrite | Seed unique user per runId |
| Hard-coded coupon | Exhausted by worker 2 | Mint coupon in seed route |
| Assert UI list length only | Pagination hides rows | Probe count by runId |
| Serial suite to mask bug | Hides isolation debt | Fix 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.
Related
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.