Playwright
End-to-End Testing Framework
Microsoft의 크로스 브라우저 E2E 테스트 프레임워크. Chromium, Firefox, WebKit 지원. Auto-wait, Trace Viewer로 안정적인 테스트 작성.
End-to-End Testing Framework
Microsoft의 크로스 브라우저 E2E 테스트 프레임워크. Chromium, Firefox, WebKit 지원. Auto-wait, Trace Viewer로 안정적인 테스트 작성.
Playwright는 Microsoft에서 개발한 오픈소스 E2E(End-to-End) 테스트 자동화 프레임워크입니다. 웹 애플리케이션의 실제 사용자 시나리오를 시뮬레이션하여 브라우저 기반 테스트를 자동화합니다. 단순한 브라우저 자동화 도구를 넘어 현대적인 웹 애플리케이션 테스트에 필요한 모든 기능을 제공하며, TypeScript, JavaScript, Python, .NET, Java 등 다양한 언어를 지원합니다.
Playwright는 Google의 Puppeteer 개발팀 출신 엔지니어들이 Microsoft로 이직한 후 2020년에 만들었습니다. Puppeteer의 장점을 계승하면서 크로스 브라우저 지원 부재, 테스트 안정성 문제 등 기존 도구들의 한계를 해결하기 위해 설계되었습니다. Puppeteer가 Chrome/Chromium에 한정되었던 것과 달리, Playwright는 처음부터 모든 주요 브라우저를 목표로 개발되었습니다.
Playwright의 핵심 강점은 크로스 브라우저 지원입니다. 단일 API로 Chromium(Chrome, Edge), Firefox, WebKit(Safari) 세 가지 브라우저 엔진에서 동일한 테스트를 실행할 수 있습니다. 또한 모바일 에뮬레이션을 지원하여 iPhone, Android 기기 환경에서의 테스트도 가능합니다. 이를 통해 "내 컴퓨터에서는 잘 되는데"라는 크로스 브라우저 호환성 문제를 CI/CD 파이프라인에서 조기에 발견할 수 있습니다.
실무에서 Playwright가 사랑받는 이유는 Auto-wait와 Trace Viewer 같은 기능 덕분입니다. Auto-wait는 요소가 클릭 가능한 상태가 될 때까지 자동으로 대기하여 flaky 테스트(간헐적 실패)를 크게 줄입니다. Trace Viewer는 테스트 실행 과정을 타임라인, 스크린샷, 네트워크 요청과 함께 시각적으로 보여주어 실패 원인을 쉽게 디버깅할 수 있게 합니다. 이 외에도 네트워크 요청 가로채기, 여러 탭/창 제어, iframe 처리 등 복잡한 시나리오도 간단하게 구현할 수 있습니다.
// Playwright 기본 테스트 작성 예제
import { test, expect } from '@playwright/test';
// 테스트 스위트: 로그인 기능
test.describe('로그인 기능', () => {
// 기본 로그인 테스트
test('유효한 자격증명으로 로그인 성공', async ({ page }) => {
// 1. 로그인 페이지로 이동
await page.goto('https://example.com/login');
// 2. 이메일 입력 (Auto-wait: 요소가 준비될 때까지 자동 대기)
await page.fill('[data-testid="email"]', 'user@example.com');
// 3. 비밀번호 입력
await page.fill('[data-testid="password"]', 'securePassword123');
// 4. 로그인 버튼 클릭
await page.click('button[type="submit"]');
// 5. 대시보드로 리다이렉트 확인
await expect(page).toHaveURL('/dashboard');
// 6. 환영 메시지 확인
await expect(
page.locator('[data-testid="welcome-message"]')
).toContainText('환영합니다');
});
// 실패 케이스 테스트
test('잘못된 비밀번호로 에러 메시지 표시', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'wrongPassword');
await page.click('button[type="submit"]');
// 에러 메시지가 표시되는지 확인
await expect(
page.locator('.error-message')
).toBeVisible();
});
});
// 페이지 객체 패턴 (Page Object Model)
// 테스트 코드의 재사용성과 유지보수성을 높이는 설계 패턴
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
// 선택자를 한 곳에서 관리
this.emailInput = page.getByTestId('email');
this.passwordInput = page.getByTestId('password');
this.submitButton = page.getByRole('button', { name: '로그인' });
this.errorMessage = page.locator('.error-message');
}
// 페이지 이동 메서드
async goto() {
await this.page.goto('/login');
}
// 로그인 액션 메서드
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
// 검증 메서드
async expectError(message: string) {
await expect(this.errorMessage).toContainText(message);
}
}
// ===== 테스트에서 사용 =====
// login.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
test('페이지 객체 패턴 사용 예시', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('test@example.com', 'password123');
// UI가 변경되어도 LoginPage 클래스만 수정하면 됨
});
QA 리드 김과장:
"프론트엔드 리뉴얼 후 수동 회귀 테스트에 시간이 너무 많이 걸립니다. E2E 자동화 도구를 도입하려는데, Cypress랑 Playwright 중 어떤 게 좋을까요?"
프론트엔드 이대리:
"저는 Playwright를 추천합니다. 저희 서비스가 Safari 사용자가 많은데, Playwright는 WebKit을 네이티브로 지원해요. Cypress는 실험적 지원 수준이고요. 또한 Playwright의 Auto-wait 기능이 flaky 테스트를 크게 줄여줍니다."
시니어 개발자 박차장:
"Playwright 동의합니다. 제가 이전 회사에서 둘 다 써봤는데, Playwright의 Trace Viewer가 테스트 실패 디버깅에 정말 유용해요. 타임라인에서 각 단계의 스크린샷과 네트워크 요청을 바로 확인할 수 있어서 문제 원인 파악이 빠릅니다."
QA 리드 김과장:
"테스트 작성 난이도는 어떤가요? QA 팀에서도 테스트를 작성해야 하거든요."
프론트엔드 이대리:
"Playwright는 Codegen이라는 기능이 있어요. 브라우저에서 직접 조작하면 코드를 자동 생성해줍니다. QA 분들도 이걸로 시작하면 러닝커브가 낮을 거예요. 페이지 객체 패턴으로 구조화하면 유지보수도 편하고요."
면접관:
"Playwright로 E2E 테스트를 작성해본 경험이 있다고 하셨는데, 가장 어려웠던 점은 무엇이었나요?"
지원자:
"초기에 flaky 테스트로 고생했습니다. 동적 콘텐츠 로딩 시 타이밍 이슈가 있었는데요. waitForSelector 대신 Playwright의 Auto-wait와 expect 단언문의 자동 재시도 기능을 활용하니까 안정성이 크게 개선됐습니다."
면접관:
"Page Object 패턴은 사용해보셨나요? 어떤 장점이 있었나요?"
지원자:
"네, 모든 페이지를 POM으로 구조화했습니다. UI가 변경되어도 Page 클래스만 수정하면 테스트 코드는 그대로 동작해서 유지보수가 편했고, 로그인, 검색 같은 공통 동작을 재사용할 수 있어서 테스트 작성 속도도 빨라졌습니다."
DevOps 최대리:
"Playwright 테스트를 GitHub Actions에 통합하려고 하는데, 설정이 복잡하지 않나요? 브라우저 설치도 필요하고..."
백엔드 정대리:
"생각보다 간단해요. Playwright가 공식 GitHub Action을 제공해서 playwright-action만 추가하면 브라우저 캐싱까지 자동으로 처리됩니다. Docker 이미지도 공식으로 제공하고요."
DevOps 최대리:
"테스트 실행 시간이 오래 걸리면 PR 머지가 지연될 텐데, 병렬 실행은 가능한가요?"
백엔드 정대리:
"네, --workers 옵션으로 병렬 실행 가능하고, GitHub Actions의 matrix 전략과 --shard 옵션을 조합하면 여러 러너에 테스트를 분산시킬 수 있어요. 저희는 Chromium, Firefox, WebKit 테스트를 각각 다른 러너에서 동시에 돌려서 전체 시간을 3분의 1로 줄였습니다."
DevOps 최대리:
"테스트 실패 시 리포트는 어떻게 확인하죠?"
백엔드 정대리:
"Playwright는 HTML 리포터를 기본 제공해요. CI에서 테스트 실패 시 Trace 파일을 아티팩트로 업로드하면, 다운받아서 Trace Viewer로 바로 분석 가능합니다. 실패한 시점의 스크린샷, DOM 스냅샷까지 다 볼 수 있어요."
각 테스트는 독립적으로 실행되어야 합니다. 테스트 간 상태 공유(로그인 세션, DB 데이터)는 예상치 못한 실패를 유발합니다. beforeEach에서 상태를 초기화하고, storageState 옵션으로 인증 상태를 관리하세요. 테스트 순서에 의존하지 않도록 설계해야 합니다.
CSS 클래스나 DOM 구조에 의존하는 선택자는 UI 변경 시 쉽게 깨집니다. data-testid 속성이나 getByRole(), getByText() 같은 사용자 관점 선택자를 권장합니다. Playwright의 Locator는 재시도 로직이 내장되어 있어 안정적이지만, XPath처럼 복잡한 경로 선택자는 피하세요.
기본 타임아웃(30초)이 모든 상황에 적합하지 않습니다. 느린 API 응답이나 애니메이션이 있는 경우 테스트별로 적절한 타임아웃을 설정하세요. 단, 타임아웃을 무작정 늘리기보다 실제 성능 문제를 해결하는 것이 우선입니다. waitForSelector 대신 expect의 자동 재시도를 활용하세요.