Playwright: end-to-end tests
A real browser, locators, auto-waiting and what's worth testing E2E.
Updated
What Playwright is
Playwright is a tool for end-to-end (E2E) tests: it starts a real browser (Chromium, Firefox, WebKit), opens your app and uses it like a person — navigates, fills in forms, clicks — then checks the result.
It's the only kind of test that checks everything together: the frontend, the backend, the database, routing, cookies. For async Server Components and Server Actions in Next, it's the recommended option.
import { expect, test } from '@playwright/test'
test('the user solves an exercise', async ({ page }) => {
await page.goto('/en/js/functions')
await page.getByRole('button', { name: 'Check' }).first().click()
await expect(page.getByText('Solved')).toBeVisible()
})Locators — how you find elements
| Locator | When |
|---|---|
page.getByRole('button', { name: 'Submit' }) |
the default — just like in Testing Library |
page.getByLabel('Email') |
form fields |
page.getByText('Welcome') |
visible text |
page.getByTestId('cart-total') |
when nothing semantic works |
page.locator('.css') |
the last option — fragile |
Auto-waiting — why you don't need sleeps
Playwright automatically waits for an element to be visible, enabled and stable before clicking, and expect(...) retries the check until it passes or the timeout expires (5s by default).
await expect(page.getByText('Saved')).toBeVisible() // retries until it appears
await page.waitForTimeout(2000) // ✗ never a fixed "sleep"Fixed sleeps make tests slow and flaky (sometimes they pass, sometimes they don't).
What you run E2E — and what you don't
| E2E | Better as unit / integration |
|---|---|
| critical flows: sign-up, login, checkout | every variant of a validation |
| the integration with the real backend | pure logic (calculations) |
| redirects, cookies, auth | a component's UI states |
E2E tests are slower and more expensive to maintain — a few, on the flows that bring in money or that block users if they break.
Useful tools
npx playwright codegen localhost:3000 # you record the steps, Playwright writes the code
npx playwright test --ui # a visual mode, step by step
npx playwright show-report # an HTML report with screenshots and tracesThe trace viewer: for a test that failed in CI you see every step, the DOM, the requests and the console — debugging without reproducing it locally.
In CI
You start the app (npm run build && npm run start) and run the tests against it — or directly against each PR's preview deploy on Vercel.
Summary
- Playwright = a real browser, the real app, steps like a user.
- Locators by role / label / text; auto-waiting instead of sleeps.
- Few E2E tests, on the critical flows.