Q36 of 42 · Playwright
What's your strategy for handling extremely long E2E user journeys (10+ steps)?
Short answer
Short answer: Don't write one mega-test. Decompose into independent stages — each verifiable from a known starting state via API setup. Reserve one canary test for the full end-to-end happy path; let per-stage tests cover the depth. Fixtures + `storageState` + API seeding make 'jump to step 7' cheap.
Detail
A 10+ step journey written as a single test is brittle: failure at step 8 means you've already invested 7 steps' worth of time, the failure message is far from the cause, and re-running for debugging is expensive.
Decomposition pattern:
Identify natural breakpoints. A 12-step "place an order" journey decomposes into: register, add to cart (×3 items), apply coupon, checkout, fill shipping, fill payment, confirm, view confirmation. Eight breakpoints.
Set up state via API at each starting point.
POST /api/users,POST /api/cart,POST /api/coupons/apply— whatever lets you land at a specific step without going through the UI.One canary for the full happy path. Runs less frequently (PR-to-main or nightly) and asserts the journey is composable end-to-end.
Per-stage tests for the depth — each starts from the API-prepared state for its stage, exercises that step's UI, and asserts the next state.
// canary.spec.ts — runs nightly
test('full checkout happy path', async ({ page }) => {
// 12 steps; only one test, only the golden path
});
// stage-shipping.spec.ts — runs every PR
test.beforeEach(async ({ page, request }) => {
await request.post('/api/test-setup/cart-with-items', { data: { items: [...] } });
await page.goto('/checkout/shipping');
});
test('shipping form validates postcode', async ({ page }) => { /* ... */ });
test('shipping form rejects PO boxes', async ({ page }) => { /* ... */ });
test('shipping form accepts Saturday delivery option', async ({ page }) => { /* ... */ });
Why this scales:
- Faster feedback per stage.
- Failures localised to the actual stage.
- Test files mirror the user journey; new tester finds them by name.
- CI parallelism cleaner — stages are independent.
Watch-outs:
- Test setup endpoints (
/api/test-setup/...) only exist in non-prod environments. Gate them behind an env flag and a backdoor token. - Stage independence. If stage 7 mutates global state stage 5 expects, isolation breaks. Treat stages as composable but state-isolated; reset between tests.
- Don't decompose to dust. If a single stage is genuinely two clicks, don't split into two specs. The right granularity is "one logical decision the user makes".
The senior signal: distinguishing the canary's role (sanity end-to-end) from per-stage depth, and the API-setup discipline that makes decomposition cheap.