5단계
Playwright E2E
30 분
Playwright E2E
실제 브라우저를 돌리는 자동화. 웹 E2E 사실상 표준.
1. 설치
pnpm create playwright@latest
# TypeScript · e2e 디렉토리 · github actions workflow 기본
첫 실행 시 Chromium · Firefox · WebKit 다운로드 (~300MB).
2. 첫 테스트
// e2e/smoke.spec.ts
import { test, expect } from "@playwright/test";
test("홈 페이지 로드", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/My App/);
await expect(page.locator("h1")).toBeVisible();
});
test("로그인 플로우", async ({ page }) => {
await page.goto("/login");
await page.fill('input[name="email"]', "test@example.com");
await page.fill('input[name="password"]', "secret");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/dashboard");
});
3. playwright.config.ts
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
timeout: 30_000,
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: [["html", { outputFolder: "playwright-report" }]],
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" 로 실패 시 상세 타임라인 저장. 디버깅 최강.
4. 매니페스트 자동 생성
페이지 100 개를 일일이 spec 에 적기는 비현실. 파일시스템에서 라우트 수집:
// e2e/pages/generate-manifest.ts
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. 단일 spec 순회
// e2e/pages/all-pages.spec.ts
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);
await expect(page.locator("body")).toBeVisible();
});
}
새 페이지 추가 → 생성 스크립트 재실행 → 자동 테스트 추가.
6. 쓰기 테스트 — PROD 보호
// e2e/setup/skip-write-on-prod.ts
import { test } from "@playwright/test";
export function skipWriteOnProd() {
if (process.env.E2E_ENV === "PROD") test.skip();
}
test("delete user @write", async ({ request }) => {
skipWriteOnProd();
// ...
});
config 에 grepInvert: /@write/ (PROD 전용 project) 로 2 중 보호.
7. 인증 세션 재사용
로그인을 매 테스트에서 반복하면 느림. storageState 로 세션 캐시.
// e2e/setup/global-setup.ts
import { chromium } from "@playwright/test";
export default async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("http://localhost:3000/login");
// ... 로그인 ...
await page.context().storageState({ path: ".auth/admin.json" });
await browser.close();
};
playwright.config.ts:
projects: [{
name: "admin",
use: { storageState: ".auth/admin.json" },
}],
8. 디버깅
pnpm exec playwright test --debug # Playwright Inspector
pnpm exec playwright test --ui # 대화형 UI
pnpm exec playwright show-report # HTML report
trace.zip 을 열면 DOM · 네트워크 · 콘솔이 영상처럼 재생.
9. 자주 걸리는 자리
- 요소 대기 없이 click —
await expect(locator).toBeVisible()먼저 page.waitForTimeout(2000)— flaky.waitForSelector·waitForResponse로- CI 에서만 flaky — trace 저장 후 이미지 · 네트워크 확인
- 병렬 테스트 간섭 — DB 상태 공유면
fullyParallel: false또는 per-worker 시드
10. MCP 시나리오 (2026)
Playwright MCP · Chrome DevTools MCP 로 AI 에게 탐색 · 회귀 위임. 사람 필수 테스트 · AI 지원 탐색의 결합.
하고픈 말
매니페스트 자동 생성 + 단일 spec 순회 패턴만 잡으면 "새 페이지 = 테스트 누락" 공포가 사라집니다.
Next
- 06-github-actions