End-to-End Testing with Playwright

TestingPlaywrightE2EFrontend
Share on LinkedIn

Our Cypress suite took 45 minutes and failed on unrelated tests weekly. We rewrote the critical paths in Playwright — twelve tests covering signup, checkout, and account management. Runtime dropped to four minutes. Failures dropped to near zero because Playwright's auto-waiting eliminated the timing issues that caused 80% of our Cypress flakes.

Playwright is a browser automation framework for end-to-end testing. It controls real browsers programmatically, simulating user interactions and asserting on page state. Unlike unit tests that mock the browser, E2E tests verify the entire stack works together.

Setup and first test

npm init playwright@latest
import { test, expect } from "@playwright/test";

test("complete purchase flow", async ({ page }) => {
  await page.goto("/products/widget");
  await page.getByRole("button", { name: "Add to Cart" }).click();
  await page.getByRole("link", { name: "Cart" }).click();
  await page.getByRole("button", { name: "Checkout" }).click();
  await page.getByLabel("Email").fill("[email protected]");
  await page.getByRole("button", { name: "Pay" }).click();
  await expect(page.getByText("Order confirmed")).toBeVisible();
});

Playwright auto-waits for elements to be visible, enabled, and stable before interacting.

Locator strategies

Prefer user-facing locators: getByRole, getByLabel, getByText, getByTestId. Avoid CSS classes that change with styling.

Network interception

await page.route("**/api/payments", (route) => {
  route.fulfill({ status: 402, body: JSON.stringify({ error: "Card declined" }) });
});

Intercept for error scenarios. Use real APIs for happy-path tests when a test environment is available.

Page Object Model

Encapsulate page interactions to reduce duplication. When the checkout form redesigns, update one file instead of twenty tests.

Auth setup project

Log in once in a setup project, save storage state, reuse across tests — don't click through login in every spec.

Parallel execution

export default defineConfig({
  fullyParallel: true,
  retries: process.env.CI ? 1 : 0,
  use: { baseURL: "http://localhost:3000", trace: "on-first-retry" },
  webServer: { command: "npm run dev", port: 3000 },
});

Test data management

Seed data via API in beforeEach, not through the UI. A test seed endpoint creates known state in milliseconds.

Operational checklist for teams

Assign an owner for each recurring failure mode. Measure baseline before changes and compare after two sprints. Document the fix in the team wiki or runbook so the next engineer does not rediscover it. Schedule a quarterly review of metrics, tooling, and process — what reduced flakes or improved quality last quarter should be doubled down on; what did not work should be retired explicitly rather than left on the books.

Operational checklist for teams

Assign an owner for each recurring failure mode. Measure baseline before changes and compare after two sprints. Document the fix in the team wiki or runbook so the next engineer does not rediscover it. Schedule a quarterly review of metrics, tooling, and process — what reduced flakes or improved quality last quarter should be doubled down on; what did not work should be retired explicitly rather than left on the books.

Operational checklist for teams

Assign an owner for each recurring failure mode. Measure baseline before changes and compare after two sprints. Document the fix in the team wiki or runbook so the next engineer does not rediscover it. Schedule a quarterly review of metrics, tooling, and process — what reduced flakes or improved quality last quarter should be doubled down on; what did not work should be retired explicitly rather than left on the books.

Operational checklist for teams

Assign an owner for each recurring failure mode. Measure baseline before changes and compare after two sprints. Document the fix in the team wiki or runbook so the next engineer does not rediscover it. Schedule a quarterly review of metrics, tooling, and process — what reduced flakes or improved quality last quarter should be doubled down on; what did not work should be retired explicitly rather than left on the books.

Operational checklist for teams

Assign an owner for each recurring failure mode. Measure baseline before changes and compare after two sprints. Document the fix in the team wiki or runbook so the next engineer does not rediscover it. Schedule a quarterly review of metrics, tooling, and process — what reduced flakes or improved quality last quarter should be doubled down on; what did not work should be retired explicitly rather than left on the books.

Common production mistakes

Teams get playwright e2e wrong in predictable ways:

Testing strategy for playwright e2e gives false confidence when mocks return happy paths only, flakey tests are retried until green, and contract tests are never run against staging before deploy.

Debugging and triage workflow

When playwright e2e misbehaves in production, work top-down instead of guessing:

  1. Confirm scope — one tenant, region, or deployment stage? Narrow blast radius before deep diving.
  2. Check recent changes — deploys, flag flips, config pushes, and schema migrations in the last 24 hours.
  3. Compare golden signals — latency, error rate, saturation, and traffic for the affected surface vs. baseline.
  4. Reproduce minimally — smallest input or scenario that triggers the failure; capture traces/logs with correlation IDs.
  5. Fix forward or rollback — if rollback is faster than root-cause during incident, rollback first, postmortem second.
  6. Add a guard — alert, integration test, or circuit breaker so the same class of failure is caught earlier next time.

Document the timeline during triage. Future you (and on-call) will need timestamps, not just conclusions.

Resources

Frequently asked questions

How is Playwright different from Cypress?

Playwright runs tests out-of-process, supports multiple browser contexts and tabs in one test, and has built-in cross-browser support for Chromium, Firefox, and WebKit. Cypress runs inside the browser, which limits multi-tab and multi-origin testing. Playwright's auto-waiting waits for elements to be actionable before interacting. Choose Playwright for cross-browser CI and complex multi-page flows.

How many E2E tests should I write?

Fewer than you think. Cover critical user journeys only: signup, login, core purchase flow, and one happy path per major feature. Aim for 20-50 E2E tests for a medium app, not 500. Everything else should be unit and integration tests.

How do I make Playwright tests less flaky?

Use Playwright's built-in auto-waiting — never add manual sleep(). Mock external APIs with page.route(). Use data-testid attributes for selectors. Run each test with isolated browser context. Seed test data via API before the test, not through the UI.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →