E2E Testing with Playwright
Driving a real browser through full user journeys â auto-waiting that kills flakiness, role-based locators that survive refactors, the Page Object Model, and CI integration.
End-to-end tests verify complete user journeys through a real browser against the fully running system â the highest-confidence (and slowest) tests. Playwright makes them reliable with auto-waiting web-first assertions that eliminate timing flakiness, and resilient role-based locators that survive UI refactors. The Page Object Model keeps them maintainable, and they sit at the small tip of the test pyramid.
End-to-end tests drive a real browser through complete user journeys â navigate, fill forms, click, and verify what's on screen â against the fully running application (frontend + backend + database). They give the highest confidence that the system actually works, because they exercise everything together.
Modern web apps are asynchronous, so the classic E2E failure is timing: the test clicks before an element is ready. Playwright's web-first assertions auto-wait â they poll for the element to be visible/actionable up to a timeout â so you never sprinkle fragile, fixed waitForTimeout sleeps that either waste time or fail intermittently.
Tests should find elements the way a user (or screen reader) does: by role, label, text, or test-id â not by brittle CSS/XPath tied to implementation. getByRole('button', { name: 'Sign in' }) survives restyling and refactors that break .btn-primary selectors, and it nudges you toward accessible markup.
The Page Object Model wraps each page's locators and actions in a class, so tests read like user stories and selectors live in one place â change the UI, fix one object. In CI, E2E suites run headless across browsers, capture traces/videos/screenshots on failure, and run in parallel â but stay small (the pyramid's tip) because they're the slowest tests.
Key Concepts
Tests that drive a real browser through full user journeys against the whole running system.
Assertions/actions that poll until an element is ready, eliminating fixed-sleep flakiness.
expect(locator).toBeVisible() etc. â they retry until the condition holds or time out.
Finding elements by accessible role/label/text rather than brittle CSS selectors.
Encapsulating a page's locators and actions in a class so tests are readable and maintainable.
Playwright artifacts captured on failure (DOM snapshots, network, video) for fast debugging in CI.
1import { test, expect } from "@playwright/test";23test("user can sign in and see the dashboard", async ({ page }) => {4 await page.goto("/login");56 // Query by ACCESSIBLE role/label â robust to markup changes,7 // not brittle CSS selectors like ".btn-primary > span:nth-child(2)"8 await page.getByLabel("Email").fill("a@b.com");9 await page.getByLabel("Password").fill("secret");10 await page.getByRole("button", { name: "Sign in" }).click();1112 // Web-first assertion AUTO-WAITS: polls until visible (or times out).13 // No page.waitForTimeout(800) guessing â no flakiness.14 await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();15});1617// Page Object Model keeps tests readable and selectors in one place:18class LoginPage {19 constructor(private page: Page) {}20 async signIn(email: string, pw: string) {21 await this.page.getByLabel("Email").fill(email);22 await this.page.getByLabel("Password").fill(pw);23 await this.page.getByRole("button", { name: "Sign in" }).click();24 }25}
E2E tests are the ultimate proof that critical flows (signup, checkout, login) actually work for users, and Playwright has become the modern standard. Knowing how auto-waiting eliminates flakiness, why role-based locators matter, and how to keep E2E suites small and maintainable is increasingly expected of full-stack and QA engineers.
Common Pitfalls
1The Flaky Suite Nobody Trusted
An older Selenium E2E suite failed randomly ~15% of CI runs. Engineers learned to just hit 'retry', real failures hid among the noise, and eventually the suite was disabled â leaving the critical paths untested.
The tests relied on fixed sleeps (Thread.Sleep / waitForTimeout) to wait for async UI. Too short caused failures on slow runs; too long made the suite crawl. The flakiness destroyed trust in the entire safety net.
Rewrite the critical journeys in Playwright using auto-waiting web-first assertions and role-based locators, with traces captured on failure. Tests wait exactly as long as needed and no more, eliminating timing flakiness, and the suite becomes fast and reliable enough to gate deploys again.
Takeaway: Flaky E2E tests are worse than none â they erode trust. Auto-waiting (not fixed sleeps) and resilient locators are what make a browser suite reliable enough to actually depend on.
2Brittle Selectors Broke on Every Restyle
E2E tests located elements by CSS like div.card > button.btn-primary. A routine design refactor that changed class names broke 40 tests overnight, even though the app worked perfectly for users.
Tests were coupled to implementation details (CSS structure), so cosmetic changes caused mass false failures. Maintenance cost ballooned and discouraged UI improvements.
Switch to user-facing locators â getByRole, getByLabel, getByText, or stable data-testid â and centralize them in Page Objects. Now restyling doesn't touch behavior-based locators, and when markup truly changes, fixing one Page Object updates every test that uses it.
Takeaway: Locate elements the way users and screen readers do (role/label/text), not by CSS structure. Resilient locators plus Page Objects make E2E tests survive UI refactors and slash maintenance.
Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.