rgoussu@goussu: ~/library/frontend
~/library/frontend cat frontend-testing.md

Frontend testing

# Testing UIs without testing the framework — behavior-first component tests, network mocking at the boundary, E2E for the money paths, and visual regression for the rest.

Conceptsaved 2026-08-08updated 2026-08-09 #frontend#testing#testing-library#playwright#msw

Overview

Frontend tests have a bad reputation because the wrong ones were written for years: snapshot dumps nobody reads, shallow renders asserting implementation details, E2E suites that fail when the CSS changes. The modern consensus inverts the pyramid into Kent C. Dodds's trophy: lean on integration-level component tests that interact with the UI the way a user does, mock the network at the HTTP boundary, keep a thin E2E layer for the flows that pay the bills, and let visual tools cover what assertions can't. The unit of confidence is a user-visible behavior, never a component's internals.

Key points

  • Test what the user sees, not how it's built: Testing Library's guiding principle — query by role/label/text (getByRole('button', {name: /save/i})), interact via user-event, assert on the DOM. If a refactor that preserves behavior breaks the test, the test was wrong.
  • The trophy, not the pyramid: static (TypeScript + lint) at the base, few pure-unit tests (logic extracted from components), a thick middle of component/integration tests, a thin crown of E2E. UI logic lives at the integration level; that's where the return on confidence is.
  • Mock the network, not your modules: MSW intercepts at the HTTP layer, so the whole data path (client, cache, serialization) stays real and the same handlers serve tests, Storybook, and local dev. Mocking your own API client couples tests to implementation.
  • E2E with Playwright for the money paths only: signup, login, checkout, the core loop — real browser, real backend where feasible, auto-waiting assertions, trace viewer for the failures. Every E2E test is an ongoing operational cost; budget them.
  • What assertions can't catch, tools can: visual regression (Chromatic/Percy or Playwright screenshots) for the pixels, jest-axe/axe-core in component tests plus keyboard-walk E2E for accessibility — automated a11y catches the ~30–40% that is machine-checkable, no more.
  • Flakiness is a design smell, not weather: no arbitrary sleep, await visible outcomes, isolate test data, control the clock; a retried-until-green suite is a suite nobody trusts — same discipline as testing strategies at large.

Details

The shape in code

test("saves the article and shows it in the feed", async () => {
  server.use(http.post("/api/articles", () => HttpResponse.json(article)));
  render(<Editor />, { wrapper: AppProviders });
  await user.type(screen.getByRole("textbox", { name: /title/i }), "CQRS in anger");
  await user.click(screen.getByRole("button", { name: /publish/i }));
  expect(await screen.findByRole("heading", { name: /cqrs in anger/i })).toBeVisible();
});

One test: real component tree, real query cache, real router, fake HTTP. This is the trophy's thick middle in one frame.

Tooling map

Vitest (runner, jsdom or browser mode) + Testing Library (queries/interactions) + MSW (network) for the middle; Playwright for E2E and screenshots; Storybook stories reused as test cases (portable stories / play functions) so docs and tests stop diverging.

Practice

  • Test the design system (source) — add behavior tests + jest-axe to the components from the design-system build; per-state stories become the test cases.
  • MSW retrofit (source) — take a fetch-heavy feature and write integration tests with MSW handlers; then break the real API contract on purpose and watch which tests catch it (and which lie).
  • Playwright money-path suite (source) — pick the three flows a real product would die without, automate them with trace-on-failure, wire them into CI as the merge gate.
  • RealWorld full-stack build (source) — milestone 3 is this note as a deliverable: trophy-shaped suite, axe clean, E2E in CI.

Related