codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
EDU›Testing strategy and quality gates›Step 5

Step 5

Playwright E2E

0 views

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. Use waitForSelector / 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

← Step 4

testcontainers

Step 6 →

GitHub Actions quality gate