Playwright 완벽 가이드 | E2E 테스트·자동화·CI/CD·Visual Testing
이 글의 핵심
Playwright로 E2E 테스트를 자동화하는 완벽 가이드입니다. 설치부터 테스트 작성, 선택자, API 모킹, CI/CD, Visual Testing까지 실전 예제로 정리했습니다.
실무 경험 공유: 이커머스 플랫폼에 Playwright를 도입하면서, 수동 QA 시간을 80% 줄이고 배포 전 버그를 95% 이상 사전에 발견한 경험을 공유합니다.
들어가며: “수동 테스트가 너무 많아요”
실무 문제 시나리오
시나리오 1: 매번 수동으로 테스트해요
배포 전 수동 테스트에 2시간 걸립니다. Playwright는 5분입니다.
시나리오 2: 브라우저마다 다르게 동작해요
Chrome, Firefox, Safari를 각각 테스트해야 합니다. Playwright는 자동으로 모두 테스트합니다.
시나리오 3: 테스트가 불안정해요
Selenium 테스트가 자주 실패합니다. Playwright는 안정적입니다.
1. Playwright란?
핵심 특징
Playwright는 Microsoft가 만든 E2E 테스트 프레임워크입니다.
주요 장점:
- 크로스 브라우저: Chromium, Firefox, WebKit 지원
- 자동 대기: 요소가 준비될 때까지 자동 대기
- 병렬 실행: 빠른 테스트 실행
- 강력한 선택자: CSS, XPath, Text, Role 등
- API 모킹: 네트워크 요청 가로채기
2. 설치 및 설정
설치
npm init playwright@latest
설정 파일
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
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:3000',
reuseExistingServer: !process.env.CI,
},
});
3. 기본 테스트 작성
첫 번째 테스트
// tests/example.spec.ts
import { test, expect } from '@playwright/test';
test('홈페이지 제목 확인', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My App/);
});
test('로그인 테스트', async ({ page }) => {
await page.goto('/login');
// 입력
await page.fill('input[name="email"]', '[email protected]');
await page.fill('input[name="password"]', 'password123');
// 버튼 클릭
await page.click('button[type="submit"]');
// 리다이렉트 확인
await expect(page).toHaveURL('/dashboard');
// 환영 메시지 확인
await expect(page.locator('h1')).toContainText('Welcome');
});
4. 선택자 (Locators)
다양한 선택자
// CSS 선택자
await page.locator('.submit-button').click();
// Text 선택자
await page.locator('text=Submit').click();
// Role 선택자 (권장)
await page.getByRole('button', { name: 'Submit' }).click();
// Label 선택자
await page.getByLabel('Email').fill('[email protected]');
// Placeholder 선택자
await page.getByPlaceholder('Enter your email').fill('[email protected]');
// Test ID 선택자
await page.getByTestId('submit-button').click();
체이닝
// 부모 → 자식
await page
.locator('.card')
.filter({ hasText: 'Premium' })
.getByRole('button', { name: 'Buy' })
.click();
// 여러 요소 처리
const items = page.locator('.item');
const count = await items.count();
for (let i = 0; i < count; i++) {
const text = await items.nth(i).textContent();
console.log(text);
}
5. 상호작용
클릭 및 입력
// 클릭
await page.click('button');
await page.dblclick('button'); // 더블 클릭
await page.click('button', { button: 'right' }); // 우클릭
// 입력
await page.fill('input', 'text');
await page.type('input', 'text', { delay: 100 }); // 천천히 입력
// 선택
await page.selectOption('select', 'value');
await page.check('input[type="checkbox"]');
await page.uncheck('input[type="checkbox"]');
// 파일 업로드
await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
키보드 및 마우스
// 키보드
await page.keyboard.press('Enter');
await page.keyboard.press('Control+A');
await page.keyboard.type('Hello World');
// 마우스
await page.mouse.move(100, 200);
await page.mouse.click(100, 200);
await page.mouse.wheel(0, 100); // 스크롤
6. 대기 및 검증
자동 대기
// Playwright는 자동으로 대기합니다
await page.click('button'); // 버튼이 클릭 가능할 때까지 대기
// 명시적 대기
await page.waitForSelector('.result');
await page.waitForURL('**/dashboard');
await page.waitForLoadState('networkidle');
Assertions
// 텍스트 검증
await expect(page.locator('h1')).toHaveText('Welcome');
await expect(page.locator('h1')).toContainText('Wel');
// 속성 검증
await expect(page.locator('button')).toBeDisabled();
await expect(page.locator('button')).toBeEnabled();
await expect(page.locator('button')).toBeVisible();
// 개수 검증
await expect(page.locator('.item')).toHaveCount(5);
// URL 검증
await expect(page).toHaveURL(/dashboard/);
// 스크린샷 비교
await expect(page).toHaveScreenshot();
7. API 모킹
네트워크 요청 가로채기
test('API 모킹', async ({ page }) => {
// API 응답 모킹
await page.route('**/api/users', (route) => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
]),
});
});
await page.goto('/users');
// 모킹된 데이터 확인
await expect(page.locator('.user')).toHaveCount(2);
});
요청 대기
test('API 요청 대기', async ({ page }) => {
await page.goto('/');
// API 요청 대기
const responsePromise = page.waitForResponse('**/api/data');
await page.click('button');
const response = await responsePromise;
expect(response.status()).toBe(200);
const data = await response.json();
expect(data).toHaveProperty('id');
});
8. 인증 및 세션
로그인 재사용
// tests/auth.setup.ts
import { test as setup } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', '[email protected]');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
// 세션 저장
await page.context().storageState({ path: authFile });
});
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
9. 실전 예제: 이커머스 테스트
// tests/e-commerce.spec.ts
import { test, expect } from '@playwright/test';
test.describe('이커머스 플로우', () => {
test('상품 검색 → 장바구니 → 결제', async ({ page }) => {
// 1. 홈페이지 방문
await page.goto('/');
// 2. 상품 검색
await page.fill('input[placeholder="Search"]', 'laptop');
await page.press('input[placeholder="Search"]', 'Enter');
// 3. 검색 결과 확인
await expect(page.locator('.product')).toHaveCount(10);
// 4. 첫 번째 상품 클릭
await page.locator('.product').first().click();
// 5. 상품 상세 페이지
await expect(page.locator('h1')).toContainText('Laptop');
// 6. 장바구니 추가
await page.click('button:has-text("Add to Cart")');
// 7. 장바구니 확인
await expect(page.locator('.cart-count')).toHaveText('1');
// 8. 장바구니 페이지로 이동
await page.click('.cart-icon');
await expect(page).toHaveURL(/cart/);
// 9. 결제 진행
await page.click('button:has-text("Checkout")');
// 10. 배송 정보 입력
await page.fill('input[name="name"]', 'John Doe');
await page.fill('input[name="address"]', '123 Main St');
await page.fill('input[name="city"]', 'New York');
await page.fill('input[name="zip"]', '10001');
// 11. 결제 정보 입력
await page.fill('input[name="cardNumber"]', '4242424242424242');
await page.fill('input[name="expiry"]', '12/25');
await page.fill('input[name="cvc"]', '123');
// 12. 주문 완료
await page.click('button:has-text("Place Order")');
// 13. 주문 확인 페이지
await expect(page).toHaveURL(/order-confirmation/);
await expect(page.locator('h1')).toContainText('Thank you');
});
});
10. CI/CD 통합
GitHub Actions
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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 Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
11. Visual Testing
스크린샷 비교
test('비주얼 테스트', async ({ page }) => {
await page.goto('/');
// 전체 페이지 스크린샷
await expect(page).toHaveScreenshot('homepage.png');
// 특정 요소 스크린샷
await expect(page.locator('.hero')).toHaveScreenshot('hero.png');
// 임계값 설정
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixels: 100,
});
});
정리 및 체크리스트
핵심 요약
- Playwright: Microsoft의 E2E 테스트 프레임워크
- 크로스 브라우저: Chromium, Firefox, WebKit 지원
- 자동 대기: 안정적인 테스트
- API 모킹: 네트워크 요청 제어
- Visual Testing: 스크린샷 비교
구현 체크리스트
- Playwright 설치 및 설정
- 기본 테스트 작성
- 선택자 최적화
- API 모킹 구현
- 인증 세션 재사용
- CI/CD 통합
- Visual Testing 추가
같이 보면 좋은 글
- Vitest 완벽 가이드
- GitHub Actions CI/CD 완벽 가이드
- React 18 심화 가이드
이 글에서 다루는 키워드
Playwright, E2E Testing, Testing, Automation, CI/CD, QA, Frontend
자주 묻는 질문 (FAQ)
Q. Playwright vs Cypress, 어떤 게 나은가요?
A. Playwright가 더 빠르고 크로스 브라우저를 지원합니다. Cypress는 DX가 좋지만 Chromium 계열만 지원합니다.
Q. Selenium에서 마이그레이션이 어렵나요?
A. Playwright API가 더 간단합니다. 자동 대기 기능으로 안정성이 크게 향상됩니다.
Q. 모바일 테스트를 할 수 있나요?
A. 네, 모바일 브라우저 에뮬레이션을 지원합니다. 실제 디바이스는 Appium을 사용하세요.
Q. 학습 곡선이 가파른가요?
A. 기본 사용법은 간단합니다. 공식 문서가 훌륭하고 예제가 많습니다.