설치 및 설정
프로젝트 초기화
터미널
# Playwright 설치
npm init playwright@latest
# 또는 기존 프로젝트에 추가
npm install -D @playwright/test
npx playwright install설정 파일
playwright.config.ts
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// 모바일 테스트
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
],
// 테스트 전 서버 시작
webServer: {
command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
});webServer 옵션
webServer 설정으로
테스트 전에 자동으로 개발 서버를 시작할 수 있습니다.
기본 테스트 작성
e2e/todo.spec.ts
// e2e/todo.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Todo App', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('should display the home page', async ({ page }) => {
// 페이지 타이틀 확인
await expect(page).toHaveTitle(/Todo/);
// 메인 헤딩 확인
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});
test('should create a new todo', async ({ page }) => {
// 입력 필드에 텍스트 입력
await page.getByPlaceholder('할 일을 입력하세요').fill('새로운 할 일');
// 추가 버튼 클릭
await page.getByRole('button', { name: '추가' }).click();
// 목록에 추가되었는지 확인
await expect(page.getByText('새로운 할 일')).toBeVisible();
});
test('should toggle todo completion', async ({ page }) => {
// 할 일 생성
await page.getByPlaceholder('할 일을 입력하세요').fill('테스트 할 일');
await page.getByRole('button', { name: '추가' }).click();
// 체크박스 클릭
await page.getByRole('checkbox').first().click();
// 완료 상태 확인 (취소선 스타일)
await expect(page.getByText('테스트 할 일')).toHaveClass(/line-through/);
});
test('should delete a todo', async ({ page }) => {
// 할 일 생성
await page.getByPlaceholder('할 일을 입력하세요').fill('삭제할 할 일');
await page.getByRole('button', { name: '추가' }).click();
// 삭제 버튼 클릭
await page.getByRole('button', { name: '삭제' }).click();
// 목록에서 사라졌는지 확인
await expect(page.getByText('삭제할 할 일')).not.toBeVisible();
});
});테스트 실행
터미널
# 전체 테스트 실행
npx playwright test
# UI 모드로 실행 (디버깅에 유용)
npx playwright test --ui
# 특정 브라우저만
npx playwright test --project=chromium
# 특정 파일만
npx playwright test e2e/todo.spec.ts
# 헤드리스 모드 끄기 (브라우저 보기)
npx playwright test --headed
# 테스트 리포트 열기
npx playwright show-report선택자 베스트 프랙티스
추천
getByRole('button', {name: '추가'})getByText('할 일 목록')getByPlaceholder('입력...')getByTestId('todo-list')
피하기
locator('.btn-primary')locator('#submit-btn')locator('div > button:nth-child(2)')
우선순위
getByRole >
getByText >
getByTestId >
locator
API 모킹
실제 백엔드 없이도 프론트엔드를 테스트할 수 있습니다.
page.route()로 API 응답을 모킹합니다.
e2e/with-mock.spec.ts
// e2e/with-mock.spec.ts
import { test, expect } from '@playwright/test';
test('should display mocked todos', async ({ page }) => {
// API 응답 모킹
await page.route('**/api/v1/todos**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
items: [
{ id: '1', title: 'Mocked Todo 1', is_completed: false },
{ id: '2', title: 'Mocked Todo 2', is_completed: true },
],
total: 2,
}),
});
});
await page.goto('/');
// 모킹된 데이터 확인
await expect(page.getByText('Mocked Todo 1')).toBeVisible();
await expect(page.getByText('Mocked Todo 2')).toBeVisible();
});
test('should handle API error', async ({ page }) => {
// API 에러 모킹
await page.route('**/api/v1/todos**', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Server error' }),
});
});
await page.goto('/');
// 에러 메시지 확인
await expect(page.getByText(/에러|오류|실패/)).toBeVisible();
});시각적 회귀 테스트
스크린샷을 비교하여 UI가 의도치 않게 변경되었는지 감지합니다.
e2e/visual.spec.ts
// e2e/visual.spec.ts
import { test, expect } from '@playwright/test';
test('visual regression test', async ({ page }) => {
await page.goto('/');
// 전체 페이지 스크린샷 비교
await expect(page).toHaveScreenshot('home-page.png');
});
test('component screenshot', async ({ page }) => {
await page.goto('/');
// 특정 컴포넌트만 스크린샷
const todoList = page.locator('[data-testid="todo-list"]');
await expect(todoList).toHaveScreenshot('todo-list.png');
});
test('full page screenshot with interactions', async ({ page }) => {
await page.goto('/');
// 할 일 추가 후 스크린샷
await page.getByPlaceholder('할 일을 입력하세요').fill('스크린샷 테스트');
await page.getByRole('button', { name: '추가' }).click();
await expect(page).toHaveScreenshot('with-todo.png');
});터미널
# 첫 실행: 기준 스크린샷 생성
npx playwright test --update-snapshots
# 이후 실행: 기준과 비교
npx playwright testOS별 차이
스크린샷은 OS별로 렌더링이 다를 수 있습니다.
CI에서 일관된 결과를 위해 Docker를 사용하거나
threshold를 설정하세요.
CI/CD 통합
.github/workflows/e2e.yml
# .github/workflows/e2e.yml
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30Playwright 브라우저 캐싱
CI에서 브라우저 설치 시간을 줄이려면 캐싱 설정을 참고하세요.
디버깅 팁
터미널
# UI 모드 (가장 추천)
npx playwright test --ui
# 디버그 모드 (브레이크포인트 지원)
npx playwright test --debug
# 추적 기록 (trace.zip)
npx playwright test --trace on
# Codegen: 브라우저 조작을 코드로 변환
npx playwright codegen localhost:5173UI 모드
테스트를 단계별로 실행하고 각 단계의 DOM 상태를 확인할 수 있습니다.
Codegen
브라우저에서 직접 조작하면 자동으로 테스트 코드가 생성됩니다.
베스트 프랙티스
DO
- ✅ 핵심 사용자 시나리오만 테스트
- ✅ 의미 있는 선택자 사용
- ✅ API 모킹으로 안정성 확보
- ✅ 실패 시 스크린샷 캡처
- ✅ CI에서 병렬 실행
DON'T
- ❌ 모든 것을 E2E로 테스트
- ❌ CSS 선택자에 의존
- ❌ 고정된 대기 시간 사용
- ❌ 테스트 간 데이터 공유
- ❌ 너무 많은 E2E 테스트
테스팅 완료!
테스팅 가이드를 모두 완료했습니다. 이제 학습 로드맵으로 돌아가 다른 주제를 학습하거나, 직접 프로젝트에 테스트를 작성해보세요.