Why E2E tests and where they fit
An end-to-end (E2E) test starts the whole application in a real browser and does what a user does: opens a page, types, clicks, reads the result. It catches problems that unit tests cannot see: a broken script path, a form that is not wired to its handler, a button covered by another element.
| Kind | What it checks | Speed | Typical tool |
|---|---|---|---|
| Unit | One function or class | milliseconds | vitest |
| Integration | Several modules or a database together | hundreds of milliseconds | vitest, supertest |
| E2E | The whole app in a browser | seconds | @playwright/test |
The higher the level, the more realistic the test and the more expensive it is: slower, harder to debug and more likely to fail for reasons unrelated to your code. That is why the shape is a pyramid. Test every branch of the logic with unit tests (see Unit tests with Vitest), and keep E2E tests for a handful of critical paths: sign up, add to cart, pay.
💡 A good E2E candidate is a flow that makes or loses money or that would be embarrassing to break. A tooltip colour is not.
Installing Playwright and configuring it
Playwright is a test runner (@playwright/test) that drives Chromium, Firefox and WebKit. Install it as a dev dependency and download a browser:
npm init -y
npm i -D @playwright/test http-server
npx playwright install chromiumThe configuration lives in playwright.config.js:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
retries: process.env.CI ? 2 : 0,
forbidOnly: !!process.env.CI,
use: {
baseURL: 'http://localhost:4173',
trace: 'on-first-retry',
},
webServer: {
command: 'npx http-server ./public -p 4173 -s',
url: 'http://localhost:4173',
reuseExistingServer: !process.env.CI,
},
});testDiris where the*.spec.jsfiles live.webServerstarts the app before the tests and waits untilurlanswers;reuseExistingServerlets you keep a dev server running locally.baseURLlets tests writepage.goto('/').retriesre-runs a failed test, but only on CI.forbidOnlyfails the build if someone leavestest.onlyin the code.