Quick Decision Table
Decision table and flowchart mapping what changed to the minimum testing level required, plus a symptom-to-pattern lookup for a failure already in front of you.
Use this table to determine the minimum testing level for your current task:
| What Changed | Minimum Level | Why |
|---|---|---|
| Pure logic / utility function | Level 1 | No DOM or CSS involvement |
| Component props / state | Level 2 | Need simulated DOM to verify output |
| Build config / template / SSG | Level 3 | Need to inspect built output files |
| CSS / layout / visibility | Level 5 | CSS requires real rendering engine |
| Interactive UI flow | Level 4 | Need real browser for user interactions |
| Visual bug report | Level 5 | Must see computed styles + visual result |
| "It's not showing" | Level 5 | Visibility is a visual property |
| "It's still broken" (after test passed) | Next level up | Current level has blind spot for this bug |
| Canvas / photo-editor / zoom-resize surface where L4 is intractable AND L5 cannot reach | Level 6 (final resort) | Neither E2E nor mechanical visual can express the assertion |
| Broken link / missing page / dead image / stale sitemap | Site-integrity gates (L3/L4 hybrid) | Structurally invisible to unit and component tests -- only a built-output/crawl check sees it |
| Deploy config: domain, route, binding id, certificate | Post-deploy smoke test | The failure is not in the repository, so nothing that runs against the repository can see it -- only a request to the deployed origin can |
| Visual regression risk across a refactor | Committed-baseline visual regression | Deterministic pixel-diff gate -- catches shifted/recolored visuals an informal screenshot look would miss |
Warning
"Minimum level" means the lowest level that can reliably catch the bug. Using a lower level gives false confidence -- the test passes, but the bug remains.
Decision Flowchart
The L2-vs-L4 discriminator: "component behavior" alone does not decide the level. If the behavior can be verified with simulated events in jsdom -- a plain dispatched event, no real focus, scroll, navigation, hydration, or other browser API -- it is Level 2. If it needs any of those real browser primitives, jsdom cannot simulate it convincingly enough to trust a passing test, so it is Level 4.
Key Principle: CSS Always Needs Level 5
Any change involving CSS, layout, or visual appearance should default to Level 5. This is because:
Level 1 (unit tests) -- has no DOM at all, cannot process CSS
Level 2 (jsdom) -- has a DOM but no CSS engine;
getComputedStyle()returns default, cascade-less values -- no layout is performed and no stylesheet cascade is applied, so it cannot verify real CSSLevel 3 (build output) -- checks file contents, not rendering
Level 4 (Playwright) -- runs in a real browser, but a spec written the typical way asserts DOM state, not computed style or pixel output
This is a distinction about what the assertion checks, not what the tool can do: Playwright itself can assert computed styles (toHaveCSS) and take screenshots (toHaveScreenshot) just as deterministically as any Level 5 tool. A spec that asserts DOM state is doing Level 4 work; a spec that asserts computed style values or visual output is doing Level 5 work, whichever runner executes it. The rule stands regardless: CSS changes need Level 5-type verification.
Escalation Triggers
Move to the next level when:
Test passes but user says problem persists
You are testing logic but the bug might be visual
Lower-level test confirms data is correct but output looks wrong
You suspect a CSS or layout issue
Multiple lower-level tests pass but the feature does not work in the browser
The L6 Escalation Rule
Escalation to Level 6 (AI-based) is not part of the normal next-level progression. It requires both of these to be true at the same time:
L4 is intractable. Writing a clean E2E for this surface is genuinely infeasible — canvas-driven, multi-camera/zoom, stateful resize transforms, or similar — not just "harder than usual."
L5 cannot reach the assertion. There is no DOM element with a stable bounding rect, computed styles don't apply (the surface is
<canvas>), and screenshot pixel-diff is too noisy.
If only one of the two is true, the right answer is the other level. L6 is the final resort, not "the next thing to try when L5 is hard."
Start from Your Symptom
The table at the top of this page starts from what you changed -- two of its rows (a broken link or stale sitemap, a visual regression across a refactor) are already symptom-shaped, so they aren't repeated below. When you are starting from what you are seeing instead -- a failure already in front of you -- start here. Each row points at the pattern page that covers that symptom.
| Symptom | Start here |
|---|---|
| A flaky E2E test -- passes most of the time, fails without a code change | Flake Root-Cause Catalog & Deflaking Recipe |
| A test reports green, but it never actually ran | Playwright Patterns § Guard against specs that match no project / the test.skip pass-by-skip trap |
EADDRINUSE on a port nothing in your config claims to use | Playwright Patterns § The Hidden Auxiliary-Socket Port Race |
| Not sure whether to test against real D1 or a stub | Backend & Node.js Testing § Mock the Binding, Not the Runtime |
| CI is green, but the published artifact is broken | Publishing-Pipeline Verification |
| A build that worked last month fails today with nothing in your own diff | Scheduled Re-exam & Night Exam § T3 as an External-Dependency Drift Net |
| It works on your machine but breaks on preview or production | Environment-Tiered Testing Against Deployed Targets |
| An absence assertion fails in CI only, and every positive assertion in the same test passes | Negative Assertions vs. Their Own Fixture Data |
After Choosing a Level: Decide Where It Runs
Picking the right testing level answers what the test can see. A second decision remains: where and when does the test run? That is the execution tier — and it is a separate axis.
Execution Tiers — defines T0 (inner loop) through T4 (local heavy lane), when each applies, and the migration rule for moving tests between tiers.
Heavy Test Decision Rule — the per-test procedure for a test that feels too heavy for PR CI: demote, delete, or classify by why it is heavy and assign the matching tier.