💻 프로그래밍

Playwright

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 처리 등 복잡한 시나리오도 간단하게 구현할 수 있습니다.

💻 코드 예제

login.spec.ts - 기본 E2E 테스트
// 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();
    });
});
pages/LoginPage.ts - 페이지 객체 패턴
// 페이지 객체 패턴 (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 클래스만 수정하면 됨
});

🗣️ 실무 대화 예시

기술 회의 E2E 테스트 도구 선택

QA 리드 김과장:

"프론트엔드 리뉴얼 후 수동 회귀 테스트에 시간이 너무 많이 걸립니다. E2E 자동화 도구를 도입하려는데, Cypress랑 Playwright 중 어떤 게 좋을까요?"

프론트엔드 이대리:

"저는 Playwright를 추천합니다. 저희 서비스가 Safari 사용자가 많은데, Playwright는 WebKit을 네이티브로 지원해요. Cypress는 실험적 지원 수준이고요. 또한 Playwright의 Auto-wait 기능이 flaky 테스트를 크게 줄여줍니다."

시니어 개발자 박차장:

"Playwright 동의합니다. 제가 이전 회사에서 둘 다 써봤는데, Playwright의 Trace Viewer가 테스트 실패 디버깅에 정말 유용해요. 타임라인에서 각 단계의 스크린샷과 네트워크 요청을 바로 확인할 수 있어서 문제 원인 파악이 빠릅니다."

QA 리드 김과장:

"테스트 작성 난이도는 어떤가요? QA 팀에서도 테스트를 작성해야 하거든요."

프론트엔드 이대리:

"Playwright는 Codegen이라는 기능이 있어요. 브라우저에서 직접 조작하면 코드를 자동 생성해줍니다. QA 분들도 이걸로 시작하면 러닝커브가 낮을 거예요. 페이지 객체 패턴으로 구조화하면 유지보수도 편하고요."

기술 면접 E2E 테스트 경험

면접관:

"Playwright로 E2E 테스트를 작성해본 경험이 있다고 하셨는데, 가장 어려웠던 점은 무엇이었나요?"

지원자:

"초기에 flaky 테스트로 고생했습니다. 동적 콘텐츠 로딩 시 타이밍 이슈가 있었는데요. waitForSelector 대신 Playwright의 Auto-wait와 expect 단언문의 자동 재시도 기능을 활용하니까 안정성이 크게 개선됐습니다."

면접관:

"Page Object 패턴은 사용해보셨나요? 어떤 장점이 있었나요?"

지원자:

"네, 모든 페이지를 POM으로 구조화했습니다. UI가 변경되어도 Page 클래스만 수정하면 테스트 코드는 그대로 동작해서 유지보수가 편했고, 로그인, 검색 같은 공통 동작을 재사용할 수 있어서 테스트 작성 속도도 빨라졌습니다."

기술 논의 CI/CD 파이프라인 통합

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 스냅샷까지 다 볼 수 있어요."

⚠️ 주의사항

🔗 관련 용어

📚 더 배우기