Step 5
Playwright E2E
30 min
Playwright E2E
Real browser automation. The de-facto web E2E tool.
1. Install
pnpm create playwright@latest
Chromium · Firefox · WebKit download on first run.
2. First test
import { test, expect } from "@playwright/test";
test("home loads", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/My App/);
await expect(page.locator("h1")).toBeVisible();
});
3. Config
export default defineConfig({
testDir: "./e2e",
timeout: 30_000,
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: process.env.E2E_BASE_URL || "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
},
webServer: {
command: "pnpm dev",
url: "http://localhost:3000",
reuseExistingServer: !process.env.CI,
},
});
trace: "on-first-retry" gives you a full timeline on failure.
4. Auto manifests
import fg from "fast-glob";
import { writeFileSync } from "node:fs";
const files = await fg(["src/app/**/page.tsx", "!src/app/api/**"]);
const routes = files.map(f => "/" + f
.replace(/^src\/app\//, "").replace(/\/page\.tsx$/, "")
.replace(/\/\([^)]+\)/g, "")
.replace(/\/\[(\.{3})?([^\]]+)\]/g, "/:$2"));
writeFileSync("e2e/pages/manifest.json", JSON.stringify({ routes: routes.sort() }, null, 2));
5. Single spec
import manifest from "./manifest.json";
for (const route of manifest.routes) {
test(`page ${route} smoke`, async ({ page }) => {
const url = route.replace(/:id/g, "1").replace(/:slug/g, "test");
const resp = await page.goto(url);
expect(resp?.status()).toBeLessThan(500);
});
}
6. Protect writes
export function skipWriteOnProd() {
if (process.env.E2E_ENV === "PROD") test.skip();
}
Tag write tests @write and set grepInvert: /@write/ in the PROD config.
7. Reuse auth session
// global-setup.ts
await page.context().storageState({ path: ".auth/admin.json" });
projects: [{ name: "admin", use: { storageState: ".auth/admin.json" } }],
8. Debugging
pnpm exec playwright test --debug
pnpm exec playwright test --ui
pnpm exec playwright show-report
trace.zip replays DOM, network, and console.
9. Gotchas
- Clicking without waiting — assert visibility first
waitForTimeout→ flaky. UsewaitForSelector/waitForResponse- CI-only flake → inspect traces
- Parallel DB state collisions → disable parallel or seed per worker
10. MCP scenarios (2026)
Playwright MCP · Chrome DevTools MCP — let AI explore and flag regressions.
Closing
Auto manifest + single-spec iteration removes "added a page, missed a test" for good.
Next
- 06-github-actions