End-to-End Testing with Playwright
AdvancedE2E tests run the real app in a real browser. They catch what unit tests structurally cannot, and they cost more — so pick the few flows worth that cost.
Overview
A unit test cannot tell you that the login redirect broke, that a Content Security Policy blocked your script, or that the payment flow fails on the third step. Only a real browser hitting a real build can. The trade-off is speed and flakiness, which is why the sensible shape is many unit tests, some component tests, and a handful of E2E tests covering the flows that lose money when broken. Playwright has largely settled this space: auto-waiting removes most flakiness, and it can reuse a saved login session so every test does not sign in from scratch.
A Test, and Auto-Waiting
Locators retry until the element is ready, which is why explicit sleeps are unnecessary and wrong.
import { test, expect } from '@playwright/test'
test('a signed-in user can run a solution', async ({ page }) => {
await page.goto('/dsa/problems/two-sum')
await page.getByRole('button', { name: 'Run' }).click()
await expect(page.getByText('3 of 3 tests passed')).toBeVisible()
})
// A locator is lazy and auto-retries: it waits for the element to
// exist, be visible, be stable and be enabled before acting.
// So this is never needed and hides real timing bugs:
await page.waitForTimeout(2000) // avoid
// Assertions retry too, up to the timeout
await expect(page.getByRole('alert')).toHaveText(/saved/i)
// Locator priority mirrors Testing Library
page.getByRole('button', { name: 'Submit' })
page.getByLabel('Email')
page.getByTestId('editor') // last resortAuth and Isolation
Sign in once, reuse the state, and keep tests independent of each other.
// Global setup: log in once, save the storage state
// auth.setup.ts
await page.goto('/login')
await page.getByLabel('Email').fill(process.env.TEST_EMAIL)
await page.getByLabel('Password').fill(process.env.TEST_PASSWORD)
await page.getByRole('button', { name: 'Sign in' }).click()
await page.context().storageState({ path: 'playwright/.auth/user.json' })
// playwright.config.ts
use: { storageState: 'playwright/.auth/user.json' }
// Every test then starts signed in, with no repeated login.
// Isolation rules that keep a suite maintainable:
// - no test depends on another test having run
// - no test depends on data another test created
// - each test creates and cleans up what it needs
// - use a seeded test database, not production
// Stub third parties you do not control
await page.route('**/api.razorpay.com/**', route =>
route.fulfill({ json: { status: 'captured' } }))Choosing What to Cover
The pyramid, and the flows that justify the cost.
// /\ E2E — few, slow, high confidence
// / \ Component — more, fast, realistic rendering
// /____\ Unit — many, instant, precise
// Worth an E2E test — a break here costs money or trust:
// sign up -> verify -> first lesson
// upgrade to Pro -> payment -> feature unlocks
// solve a problem -> submit -> progress recorded
// admin publishes content -> it appears
// Not worth it: every form field validation, every empty state.
// Those belong in component tests, which run in milliseconds.
// Debugging tools that make E2E bearable
npx playwright test --ui // time-travel through the run
npx playwright codegen localhost:3000
// trace: 'on-first-retry' in config — a full recording of failures,
// which is how you diagnose a test that only fails in CI.Key Points to Remember
- 1E2E tests catch integration, routing, auth and CSP failures that unit tests structurally cannot
- 2Playwright locators and assertions auto-retry — an explicit waitForTimeout hides real timing bugs
- 3Log in once and reuse the saved storage state instead of signing in in every test
- 4Tests must not depend on each other or on data another test created; stub third-party services
- 5Cover only the flows whose breakage costs money or trust — keep the rest in fast component tests
Interview Questions
Sign in to ask AriaWhat does an E2E test catch that a component test cannot?
Why is waitForTimeout considered an anti-pattern in Playwright?
How do you keep an E2E suite from becoming slow and flaky as it grows?
Ask Aria about End-to-End Testing with Playwright
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.