End-to-end testing
An end-to-end (E2E) test drives the real application the way a user does:
through the browser, across every layer, to a real result. Each one is slow,
brittle, and costly to maintain, so it has to pay rent. A suite that retests
every field validation through the UI collapses under flakiness and gets
muted. Spend E2E tests only on journeys whose failure would cost real money
or trust, and build them to survive cosmetic change.
Method
- Test journeys, not pages. One test completes a goal a user cares
about, end to end: register, add to cart, pay, see the confirmation.
Field-level rules and error copy belong in faster tests, not a browser
round trip.
- Select by role or test id, never by styling. Target
getByRole("button", {name: "Pay"}) or data-testid="submit-order", not
.btn-primary:nth-child(3). Selectors tied to layout break on every
redesign that changed no behavior.
- Wait on state, never on the clock. Assert the app reached a condition,
await expect(page.getByText("Order placed")).toBeVisible(), never
sleep(3000). Fixed sleeps are the primary engine of E2E flakiness and
slowness at once.
- Seed data through the API, act through the UI. Create the account and
catalog with fast backend calls, then drive only the journey under test
through the browser. Building prerequisites by clicking multiplies both
runtime and failure surface.
- Keep the count small and the paths critical. A handful of journeys run
on merge beats hundreds run nightly and ignored. Rank by revenue and
support-ticket risk, and delete any test whose failure no one would act
on.
- Isolate each run's data. Unique emails and tenant ids per run let tests
execute in parallel without colliding, and let a failed run leave
diagnosable state instead of poisoning the next.
Signals
- Does each test represent a path a real user takes to a real outcome, not a
single widget?
- Would a CSS refactor that preserves behavior leave every test green?
- When one fails, does the trace point at a broken journey rather than a
timing accident?
Boundaries
E2E is the narrow top of the pyramid, not where coverage lives: push edge
cases and validation down to integration-testing and unit-test-design.
Follow the team's chosen runner, Playwright, Cypress, or Selenium, over the
selector and waiting syntax shown here.
1---2name: e2e-testing3description: Write end-to-end tests that earn their cost by covering complete user journeys against stable selectors. Use when protecting a critical path like signup or checkout across the full stack.4---56# End-to-end testing78An end-to-end (E2E) test drives the real application the way a user does:9through the browser, across every layer, to a real result. Each one is slow,10brittle, and costly to maintain, so it has to pay rent. A suite that retests11every field validation through the UI collapses under flakiness and gets12muted. Spend E2E tests only on journeys whose failure would cost real money13or trust, and build them to survive cosmetic change.1415## Method16171. **Test journeys, not pages.** One test completes a goal a user cares18 about, end to end: register, add to cart, pay, see the confirmation.19 Field-level rules and error copy belong in faster tests, not a browser20 round trip.212. **Select by role or test id, never by styling.** Target22 `getByRole("button", {name: "Pay"})` or `data-testid="submit-order"`, not23 `.btn-primary:nth-child(3)`. Selectors tied to layout break on every24 redesign that changed no behavior.253. **Wait on state, never on the clock.** Assert the app reached a condition,26 `await expect(page.getByText("Order placed")).toBeVisible()`, never27 `sleep(3000)`. Fixed sleeps are the primary engine of E2E flakiness and28 slowness at once.294. **Seed data through the API, act through the UI.** Create the account and30 catalog with fast backend calls, then drive only the journey under test31 through the browser. Building prerequisites by clicking multiplies both32 runtime and failure surface.335. **Keep the count small and the paths critical.** A handful of journeys run34 on merge beats hundreds run nightly and ignored. Rank by revenue and35 support-ticket risk, and delete any test whose failure no one would act36 on.376. **Isolate each run's data.** Unique emails and tenant ids per run let tests38 execute in parallel without colliding, and let a failed run leave39 diagnosable state instead of poisoning the next.4041## Signals4243- Does each test represent a path a real user takes to a real outcome, not a44 single widget?45- Would a CSS refactor that preserves behavior leave every test green?46- When one fails, does the trace point at a broken journey rather than a47 timing accident?4849## Boundaries5051E2E is the narrow top of the pyramid, not where coverage lives: push edge52cases and validation down to integration-testing and unit-test-design.53Follow the team's chosen runner, Playwright, Cypress, or Selenium, over the54selector and waiting syntax shown here.