Choorai
E2E Playwright

E2E 테스트 가이드

Playwright로 실제 브라우저에서 사용자 시나리오를 테스트합니다. 크로스 브라우저 테스트와 시각적 회귀 테스트를 자동화합니다.

왜 Playwright인가?

  • 크로스 브라우저: Chrome, Firefox, Safari, Edge
  • 자동 대기: 요소가 준비될 때까지 자동 대기
  • 강력한 선택자: 텍스트, 역할, 테스트 ID 기반
  • 스크린샷/비디오: 실패 시 자동 캡처

설치 및 설정

프로젝트 초기화

터미널
# 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 test

OS별 차이

스크린샷은 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: 30

Playwright 브라우저 캐싱

CI에서 브라우저 설치 시간을 줄이려면 캐싱 설정을 참고하세요.

디버깅 팁

터미널
# UI 모드 (가장 추천)
npx playwright test --ui

# 디버그 모드 (브레이크포인트 지원)
npx playwright test --debug

# 추적 기록 (trace.zip)
npx playwright test --trace on

# Codegen: 브라우저 조작을 코드로 변환
npx playwright codegen localhost:5173

UI 모드

테스트를 단계별로 실행하고 각 단계의 DOM 상태를 확인할 수 있습니다.

Codegen

브라우저에서 직접 조작하면 자동으로 테스트 코드가 생성됩니다.

베스트 프랙티스

DO

  • ✅ 핵심 사용자 시나리오만 테스트
  • ✅ 의미 있는 선택자 사용
  • ✅ API 모킹으로 안정성 확보
  • ✅ 실패 시 스크린샷 캡처
  • ✅ CI에서 병렬 실행

DON'T

  • ❌ 모든 것을 E2E로 테스트
  • ❌ CSS 선택자에 의존
  • ❌ 고정된 대기 시간 사용
  • ❌ 테스트 간 데이터 공유
  • ❌ 너무 많은 E2E 테스트

테스팅 완료!

테스팅 가이드를 모두 완료했습니다. 이제 학습 로드맵으로 돌아가 다른 주제를 학습하거나, 직접 프로젝트에 테스트를 작성해보세요.

마지막 업데이트: 2026년 2월 22일 · 버전: v0.0.1

피드백 보내기

입력한 내용으로 새 이슈 페이지를 엽니다.

GitHub 이슈로 보내기