Frontend Labs
⋯

Part XIV — End-to-end testing in depth with Playwright

This Part tests one real application end to end: Support Desk (§63), which has sign-in, sessions, streaming agent replies, actionable UI cards, security headers and rate limits. The suite has 26 tests across three Playwright projects (setup, desktop Chromium, mobile). It runs in about 3 seconds, and it passed 251 out of 251 runs with --repeat-each=10. On the way there it found six real problems, some in the app and some in the tests. Those problems are the most useful part of this Part, and §78 tells each story.

74. A testing strategy for web apps, and how Playwright works

What E2E tests are for

Layer Tool Tests Speed
Unit Vitest / node --test pure functions: the event-loop model (§81), prop validation, reducers ms
Component Vitest Browser Mode, Playwright CT one component in a real browser ~100 ms
API Playwright request, supertest status codes, headers, auth rules, without a page ~10 ms
End-to-end Playwright critical user journeys through the real UI, server and browser ~1 s

E2E tests are the most expensive kind, so spend them on what only they can prove: that sign-in works, that the streaming reply renders, that a refund needs approval, that a cookie is HttpOnly. Pure logic belongs in unit tests. Support Desk's suite mixes UI tests with API tests in the same runner, because some security properties (an IDOR, a missing CSRF token) are API facts and are cheaper to check without a page.

Why Playwright

Playwright is the default choice for new projects in 2026: it is free, it covers all three engines (Chromium, Firefox and WebKit, so Safari behavior is testable on Linux CI), and it auto-waits, which removes most sleeps and flakiness. Cypress is still loved for its in-browser debugging, but it runs inside the page, which limits multi-tab, multi-origin and multi-user tests. WebdriverIO and Selenium speak the W3C WebDriver (and now WebDriver BiDi) protocol, and they make sense when you need real mobile devices or a vendor grid.

How it works under the hood

┌───────────────── Node.js ─────────────────┐            ┌──────────── browser process ────────────┐
│ test runner  ──spawns──► worker 1, 2, … N │  WebSocket │ Chromium: CDP                           │
│   each worker: playwright client library  │ ─── or ───►│ Firefox: Juggler (patched protocol)     │
│   test → fixture → page.click(...)        │    pipe    │ WebKit: Playwright protocol (patched)   │
└───────────────────────────────────────────┘            │  ├─ BrowserContext A  (isolated profile)│
                                                         │  │    └─ Page(s)                        │
                                                         │  └─ BrowserContext B  …                 │
                                                         └─────────────────────────────────────────┘
  • Out of process. Tests run in Node and drive the browser over a protocol. That makes them independent of the page's own event loop (§12), and lets one test control several tabs, origins and users.
  • Browser contexts are incognito-like profiles with their own cookies, storage and cache. Creating one takes milliseconds, so every test gets a fresh one, and that is the basis of test isolation. Two contexts in one test means two users (§77).
  • Workers are separate OS processes running test files in parallel. fullyParallel: true also parallelizes the tests inside a file.
  • Locators are lazy. page.getByRole("button", { name: "Send" }) doesn't look anything up when you create it. Each action resolves it again, so it survives re-renders.
  • Actionability checks. Before click(), Playwright waits until the element is visible, stable (not animating), receives events (not covered by an overlay) and enabled. fill() also waits for it to be editable. This is why Playwright tests rarely need waitForTimeout.
  • Web-first assertions like await expect(locator).toHaveText(...) retry until they pass or time out (5 s by default). A one-off read like expect(await locator.textContent()).toBe(...) does not retry. It is the most common cause of flaky Playwright tests.

Generated from real-life-JS-guide.md · 8 Oct 2026 16:36