튜토리얼

Playwright로 웹 애플리케이션 E2E 테스트 자동화, 실전 가이드

강코의 코딩 일기 2026. 4. 3. 13:19

Playwright를 활용한 웹 애플리케이션 E2E 테스트 자동화의 모든 것을 알려드립니다. 설치부터 실제 테스트 코드 작성, 고급 기능까지 이 가이드로 완벽하게 마스터하고 개발 효율을 높여보세요!

안녕하세요, 개발자 여러분! 웹 애플리케이션을 개발하고 배포하는 과정에서 가장 중요한 것 중 하나가 뭘까요? 바로 품질 보증사용자 경험 아닐까요? 아무리 멋진 기능을 만들어도 버그가 많거나 예상치 못한 동작을 하면 사용자들은 금방 떠나가 버리잖아요. 그런 불상사를 막기 위해 우리는 다양한 종류의 테스트를 진행하는데요.

그중에서도 실제 사용자의 관점에서 애플리케이션 전체의 흐름을 검증하는 E2E(End-to-End) 테스트는 정말 중요하답니다. 하지만 이걸 수동으로 한다는 건 정말 고되고 시간 소모가 심한 일이죠. 그래서 우리는 테스트 자동화 도구를 찾게 되는데요, 이때 빛을 발하는 도구가 바로 Playwright입니다!

오늘은 이 Playwright를 활용해서 웹 애플리케이션 E2E 테스트를 자동화하는 방법에 대해 A부터 Z까지 상세하게 알아보는 시간을 가져볼 거예요. 설치부터 실전 코드 작성, 그리고 고급 기능까지 차근차근 따라오시면 여러분의 개발 과정에 큰 도움이 될 거라고 확신합니다. 자, 그럼 함께 시작해볼까요?

📑 목차

Playwright를 활용한 웹 애플리케이션 E2E 테스트 자동화 실전 가이드 - covid, testing, corona test, covid-19, corona, coronavirus, sars-cov-2, concept, quick test, pcr, pcr-test, covid test, covid, covid, covid, covid, covid, corona, corona, covid test, covid test, covid test

Image by analogicus on Pixabay

웹 애플리케이션 테스트, 왜 E2E가 중요할까요?

우리가 만드는 웹 애플리케이션은 수많은 기능과 사용자 인터랙션으로 구성되어 있죠. 로그인, 회원가입, 상품 구매, 게시글 작성 등 사용자가 웹사이트에서 수행할 수 있는 모든 행동들이 유기적으로 연결되어 있는데요. 개발자 입장에서는 각 기능이 잘 작동하는지 확인하고 싶지만, 사용자는 전체적인 흐름이 문제없이 동작하는지가 훨씬 더 중요하거든요.

E2E 테스트의 개념과 필요성

E2E 테스트는 말 그대로 '끝에서 끝까지' 즉, 사용자의 관점에서 애플리케이션의 모든 구성 요소(프론트엔드, 백엔드, 데이터베이스, 네트워크 등)가 함께 작동하는지 검증하는 테스트를 의미해요. 예를 들어, 사용자가 로그인 버튼을 누르고 아이디와 비밀번호를 입력한 다음, '로그인' 버튼을 클릭했을 때 정상적으로 로그인되고 메인 페이지로 이동하는지, 그리고 로그인한 사용자에게만 보이는 정보가 제대로 표시되는지 등을 확인하는 거죠.

이런 E2E 테스트가 중요한 이유는 다음과 같아요.

  • 실제 사용자 경험 검증: 사용자가 겪을 수 있는 모든 시나리오를 미리 검증해서 실제 사용 환경에서 발생할 수 있는 오류를 최소화할 수 있어요.
  • 시스템 통합성 확인: 프론트엔드와 백엔드, 데이터베이스 등 여러 시스템이 복합적으로 연동될 때 발생할 수 있는 문제를 사전에 발견할 수 있습니다.
  • 비즈니스 로직 검증: 핵심 비즈니스 로직이 의도한 대로 작동하는지 최종적으로 확인할 수 있는 가장 효과적인 방법입니다.
  • 배포 안정성 확보: 새로운 기능 배포나 코드 변경 시에도 기존 기능들이 예상대로 동작하는지 확인하여 배포의 안정성을 높여줍니다.

다른 테스트 유형과의 비교

E2E 테스트 외에도 단위(Unit) 테스트, 통합(Integration) 테스트 등 다양한 테스트 유형이 있는데요, 각각의 목적과 범위가 조금씩 다르답니다. 이를 간단히 비교해 볼까요?

테스트 유형 목적 범위 장점 단점
단위 테스트 (Unit Test) 개별 코드 조각(함수, 메서드)의 기능 검증 가장 작은 단위 (함수, 컴포넌트) 빠른 실행, 문제 지점 특정 용이 통합 시 문제 발견 어려움
통합 테스트 (Integration Test) 여러 단위들의 상호작용 및 통합 검증 모듈, 컴포넌트 간의 연동 단위 간의 인터페이스 문제 발견 E2E보다 범위가 좁음
E2E 테스트 (End-to-End Test) 전체 시스템의 사용자 흐름 검증 애플리케이션 전체 (UI부터 DB까지) 실제 사용자 경험과 가장 유사, 핵심 비즈니스 로직 검증 느린 실행, 높은 유지보수 비용, 실패 시 원인 파악 어려움

보시는 것처럼 각 테스트는 다른 역할을 수행하며, 이들을 적절히 조합하여 테스트 피라미드를 구축하는 것이 이상적이에요. 특히 E2E 테스트는 피라미드의 꼭대기에서 최종적인 사용자 경험을 검증하는 역할을 한답니다.

Playwright, E2E 테스트 자동화의 강력한 도구

E2E 테스트의 중요성은 알겠는데, 그럼 어떤 도구를 사용해야 할까요? 오늘 우리가 집중할 Playwright는 마이크로소프트에서 개발한 오픈소스 웹 테스트 자동화 프레임워크로, 최근 E2E 테스트 자동화 분야에서 엄청난 인기를 얻고 있어요!

Playwright란 무엇인가요?

Playwright는 Chromium(Chrome, Edge), Firefox, WebKit(Safari) 등 모든 주요 브라우저에서 웹 애플리케이션을 테스트할 수 있도록 설계된 자동화 라이브러리예요. Node.js 환경에서 작동하며, JavaScript, TypeScript는 물론 Python, Java, .NET 등 다양한 언어를 지원해서 개발자들이 선호하는 언어로 테스트 코드를 작성할 수 있다는 장점이 있습니다.

Playwright의 주요 특징과 장점

Playwright가 왜 이렇게 핫한 도구로 떠올랐을까요? 그 이유는 바로 강력하고 차별화된 기능들 때문인데요, 몇 가지 핵심 특징을 소개해 드릴게요.

  • 다중 브라우저 지원: Chromium, Firefox, WebKit 등 주요 브라우저를 모두 지원합니다. 심지어 모바일 에뮬레이션까지 가능해서 다양한 환경에서 테스트할 수 있어요.
  • 자동 대기(Auto-wait): 웹 요소가 나타나거나 클릭 가능해질 때까지 자동으로 기다려줍니다. 이 기능 덕분에 테스트 코드에 불필요한 `waitFor` 구문을 줄이고 테스트의 안정성을 크게 높일 수 있습니다.
  • 병렬 실행: 여러 테스트를 동시에, 그리고 여러 브라우저에서 병렬로 실행할 수 있어요. 이는 테스트 시간을 획기적으로 단축시키는 데 큰 도움이 됩니다.
  • 코드 생성기(Codegen): 브라우저에서 직접 조작하는 과정을 Playwright 테스트 코드로 자동으로 기록해 주는 기능이에요. 초보자도 쉽게 테스트 코드를 시작할 수 있고, 반복적인 작업에 매우 유용합니다.
  • 테스트 트레이스 뷰어(Trace Viewer): 테스트가 실패했을 때, 어떤 과정을 거쳐 실패했는지 시각적으로 보여주는 강력한 디버깅 도구입니다. 스크린샷, 비디오, 액션 로그 등을 통해 문제의 원인을 빠르게 파악할 수 있어요.
  • 강력한 Selector: CSS, XPath뿐만 아니라 텍스트, 역할(role), alt 속성 등 사용자 친화적인 Selector를 제공하여 보다 견고하고 읽기 쉬운 테스트 코드를 작성할 수 있습니다.
  • 신뢰성: 브라우저와 직접 통신하여 실제 사용자처럼 정확하게 상호작용합니다. 이로 인해 플래키(flaky) 테스트(실행할 때마다 성공/실패가 달라지는 불안정한 테스트)가 줄어듭니다.

경쟁 도구(Selenium, Cypress)와의 차이점

E2E 테스트 자동화 도구로는 Selenium이나 Cypress도 많이 사용되는데요, Playwright는 이들과 어떤 차이점을 가지고 있을까요? 핵심적인 부분만 비교해 볼게요.

특징 Playwright Cypress Selenium
브라우저 지원 Chromium, Firefox, WebKit (모두 지원) Chromium 계열 (Chrome, Edge, Electron), Firefox (제한적) 모든 주요 브라우저 (WebDriver 필요)
아키텍처 브라우저와 직접 통신 (Node.js 프로세스) 브라우저 내에서 실행 (Node.js 프로세스) WebDriver를 통해 브라우저와 통신
자동 대기 기본 탑재, 매우 강력 기본 탑재, 강력 수동 구현 필요 (Explicit/Implicit Wait)
병렬 실행 기본 지원, 매우 효율적 유료 플러그인 또는 외부 도구 필요 Selenium Grid를 통해 지원
언어 지원 JS/TS, Python, Java, .NET JS/TS 다양한 언어 지원
개발 주체 Microsoft Cypress.io 오픈소스 커뮤니티

Playwright는 이처럼 다중 브라우저 지원, 강력한 자동 대기, 효율적인 병렬 실행 등에서 다른 도구들보다 뛰어난 강점을 가지고 있어, 현대 웹 애플리케이션 E2E 테스트에 매우 적합하다고 볼 수 있습니다.

Playwright 개발 환경 설정부터 첫 테스트까지

자, 이제 Playwright가 얼마나 강력한 도구인지 알았으니 직접 사용해 볼 시간이에요! 개발 환경을 설정하고 간단한 테스트 코드를 작성해 볼까요?

설치 및 초기 프로젝트 구성

Playwright를 시작하는 건 정말 간단해요. Node.js가 설치되어 있다고 가정하고, 새 프로젝트를 생성해 봅시다.


# 1. 새 프로젝트 폴더 생성 및 이동
mkdir playwright-e2e-guide
cd playwright-e2e-guide

# 2. Playwright 설치 및 초기 설정
# 이 명령어를 실행하면 Playwright가 자동으로 의존성 설치, 테스트 파일 생성, 설정 파일 생성까지 해줍니다.
npm init playwright@latest

# 설치 과정에서 다음과 같은 질문이 나올 수 있습니다.
# > Do you want to use TypeScript or JavaScript? (TypeScript/JavaScript)
#   => TypeScript를 추천합니다. (t)
# > Where to put your end-to-end tests? (e.g. tests/)
#   => 기본값(tests/)으로 진행합니다. (Enter)
# > Add a GitHub Actions workflow? (y/n)
#   => 나중에 직접 추가할 수 있으니 (n)을 선택해도 무방합니다. (n)
# > Install Playwright browsers (chromium, firefox, webkit)? (y/n)
#   => (y)를 선택하여 모든 브라우저를 설치합니다. (y)

# 3. 설치가 완료되면, 다음과 같은 파일들이 생성됩니다.
#   - package.json: 프로젝트 정보 및 의존성
#   - playwright.config.ts (또는 .js): Playwright 설정 파일
#   - tests/example.spec.ts (또는 .js): 예시 테스트 파일
#   - tests-examples/: 추가 예시들

이제 `package.json` 파일을 열어보면, Playwright 관련 스크립트가 추가되어 있는 것을 볼 수 있어요.


{
  "name": "playwright-e2e-guide",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "playwright test",
    "test:headed": "playwright test --headed",
    "test:ui": "playwright test --ui"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@playwright/test": "^1.4x.x", // 버전은 다를 수 있습니다.
    "@types/node": "^20.xx.x" // 버전은 다를 수 있습니다.
  }
}

설치 과정에서 생성된 예시 테스트 파일(`tests/example.spec.ts`)을 한번 실행해 볼까요?


npm test

터미널에 테스트 결과가 출력되고, 모든 브라우저에서 테스트가 성공적으로 통과하는 것을 확인할 수 있을 거예요. 만약 브라우저가 뜨는 것을 보고 싶다면 `npm run test:headed` 명령어를 사용하시면 됩니다.

간단한 로그인 테스트 시나리오 구현

이제 우리만의 첫 번째 테스트를 작성해 봅시다. 가상의 로그인 페이지를 테스트한다고 가정해 볼게요. `tests` 폴더 안에 `login.spec.ts` 파일을 만들고 다음 코드를 입력해 보세요.


// tests/login.spec.ts
import { test, expect } from '@playwright/test';

// 테스트 그룹을 정의합니다. '로그인 기능'이라는 이름으로 여러 테스트를 묶을 수 있어요.
test.describe('로그인 기능', () => {

  // 각 테스트 전에 실행되는 부분입니다. 여기서는 특정 페이지로 이동하는 작업을 수행합니다.
  test.beforeEach(async ({ page }) => {
    // 실제 서비스의 로그인 페이지 URL로 변경해주세요.
    await page.goto('https://www.example.com/login'); 
  });

  // '유효한 자격 증명으로 로그인 성공' 테스트 시나리오
  test('유효한 자격 증명으로 로그인 성공', async ({ page }) => {
    // 1. 사용자 이름 입력 필드를 찾아서 값을 입력합니다.
    // Playwright는 사용자 친화적인 Selector를 제공해요. 'Username'이라는 라벨을 가진 input을 찾죠.
    await page.fill('input[name="username"]', 'testuser'); 
    await page.fill('input[name="password"]', 'password123');

    // 2. 로그인 버튼을 클릭합니다.
    // '로그인'이라는 텍스트를 가진 버튼을 찾아서 클릭합니다.
    await page.click('button:has-text("로그인")');

    // 3. 로그인 성공 후 메인 페이지로 이동했는지 검증합니다.
    // URL이 'https://www.example.com/dashboard'로 바뀌었는지 확인합니다.
    await expect(page).toHaveURL('https://www.example.com/dashboard'); 

    // 4. 로그인 성공 메시지나 사용자 이름이 표시되는지 검증합니다.
    // '환영합니다, testuser님!'이라는 텍스트가 페이지에 있는지 확인합니다.
    await expect(page.locator('text=환영합니다, testuser님!')).toBeVisible(); 
  });

  // '잘못된 비밀번호로 로그인 실패' 테스트 시나리오
  test('잘못된 비밀번호로 로그인 실패', async ({ page }) => {
    await page.fill('input[name="username"]', 'testuser');
    await page.fill('input[name="password"]', 'wrongpassword'); // 잘못된 비밀번호
    await page.click('button:has-text("로그인")');

    // '비밀번호가 일치하지 않습니다.'라는 오류 메시지가 표시되는지 검증합니다.
    await expect(page.locator('text=비밀번호가 일치하지 않습니다.')).toBeVisible();
    // 여전히 로그인 페이지에 머물러 있는지 검증합니다.
    await expect(page).toHaveURL('https://www.example.com/login'); 
  });
});

위 코드에서 `https://www.example.com/login`과 `https://www.example.com/dashboard`는 실제 여러분이 테스트할 웹 애플리케이션의 URL로 바꿔주셔야 해요. 그리고 `input[name="username"]`, `button:has-text("로그인")` 같은 Selector들도 여러분의 웹사이트 HTML 구조에 맞게 변경해야 합니다.

이제 이 테스트를 실행해 볼까요?


npm test tests/login.spec.ts

터미널에 테스트 결과가 출력되고, 여러분의 웹 애플리케이션이 위 시나리오대로 동작한다면 두 개의 테스트가 모두 성공하는 것을 볼 수 있을 거예요. 만약 실패한다면, Playwright Trace Viewer를 통해 실패 원인을 시각적으로 분석할 수 있습니다. `playwright show-report` 명령어를 실행해 보세요.

Playwright를 활용한 웹 애플리케이션 E2E 테스트 자동화 실전 가이드 - test, virus, coronavirus, self-test, covid-19, infection, lock down, hygiene, transmission, shutdown, pandemic, test, test, test, test, test, virus, virus, virus, virus, coronavirus, coronavirus, coronavirus, coronavirus, covid-19, covid-19, covid-19, covid-19, pandemic, pandemic

Image by Alexandra_Koch on Pixabay

실전! 복잡한 웹 애플리케이션 테스트 시나리오 작성

간단한 로그인 테스트를 성공적으로 만들어 보셨으니, 이제 좀 더 복잡한 시나리오를 다루는 방법을 알아볼까요? 실제 웹 애플리케이션은 훨씬 다양한 요소와 상호작용으로 이루어져 있으니까요.

요소 선택 및 상호작용 심화

Playwright는 요소를 선택하는 다양한 방법을 제공해서 아주 유연하게 테스트 코드를 작성할 수 있어요.

  • 텍스트 기반 Selector: page.getByText('버튼 텍스트')
  • 역할 기반 Selector: page.getByRole('button', { name: '로그인' })
  • 라벨 기반 Selector: page.getByLabel('사용자 이름')
  • 플레이스홀더 기반 Selector: page.getByPlaceholder('이메일 주소')
  • Alt 텍스트 기반 Selector: page.getByAltText('제품 이미지')
  • 타이틀 기반 Selector: page.getByTitle('장바구니')
  • 데이터 테스트 ID 기반 Selector: page.getByTestId('cart-item-count')

이런 Selector들을 조합하면 CSS SelectorXPath보다 훨씬 더 견고하고 가독성 높은 테스트 코드를 작성할 수 있답니다. 왜냐하면 UI 텍스트나 접근성 속성은 CSS 클래스나 ID보다 변경될 확률이 적기 때문이죠. 예를 들어, 특정 버튼을 클릭할 때:


// CSS Selector
await page.click('#login-button'); 

// 텍스트 기반 Selector (더 견고)
await page.getByText('로그인').click(); 

// 역할 기반 Selector (가장 견고하고 권장)
await page.getByRole('button', { name: '로그인' }).click(); 

또한, 드롭다운 메뉴 선택, 체크박스/라디오 버튼 조작 등 다양한 상호작용도 매우 직관적이에요.


// 드롭다운 메뉴에서 값 선택
await page.selectOption('select#country-select', 'KR'); // 'KR' 값 선택

// 체크박스 클릭 (이미 체크되어 있으면 해제, 아니면 체크)
await page.locator('#agree-checkbox').click(); 

// 체크박스 강제로 체크 (이미 체크되어 있어도 다시 체크)
await page.locator('#agree-checkbox').check(); 

// 라디오 버튼 선택
await page.locator('input[type="radio"][value="optionA"]').check(); 

페이지 객체 모델(POM) 패턴 적용

테스트 코드가 점점 많아지고 복잡해지면 유지보수가 어려워질 수 있어요. 이때 페이지 객체 모델(Page Object Model, POM) 패턴을 적용하면 테스트 코드의 가독성재사용성, 유지보수성을 크게 향상시킬 수 있습니다. POM은 웹 페이지의 각 요소를 객체로 추상화하여 관리하는 방식이에요.

예를 들어, 로그인 페이지를 위한 `LoginPage` 객체를 만들어 볼까요?


// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.usernameInput = page.getByLabel('사용자 이름'); // 또는 page.locator('input[name="username"]')
    this.passwordInput = page.getByLabel('비밀번호'); // 또는 page.locator('input[name="password"]')
    this.loginButton = page.getByRole('button', { name: '로그인' });
    this.errorMessage = page.locator('.error-message'); // 오류 메시지 CSS Selector
  }

  async goto() {
    await this.page.goto('https://www.example.com/login');
  }

  async login(username: string, password: string) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }
}

이제 테스트 코드에서는 `LoginPage` 객체를 생성해서 사용하면 됩니다.


// tests/login.spec.ts (POM 적용)
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage'; // LoginPage 파일 경로에 따라 수정

test.describe('로그인 기능 (POM 적용)', () => {
  let loginPage: LoginPage;

  test.beforeEach(async ({ page }) => {
    loginPage = new LoginPage(page);
    await loginPage.goto();
  });

  test('유효한 자격 증명으로 로그인 성공', async ({ page }) => {
    await loginPage.login('testuser', 'password123');
    await expect(page).toHaveURL('https://www.example.com/dashboard');
    await expect(page.locator('text=환영합니다, testuser님!')).toBeVisible();
  });

  test('잘못된 비밀번호로 로그인 실패', async ({ page }) => {
    await loginPage.login('testuser', 'wrongpassword');
    await expect(loginPage.errorMessage).toBeVisible();
    await expect(loginPage.errorMessage).toHaveText('비밀번호가 일치하지 않습니다.');
    await expect(page).toHaveURL('https://www.example.com/login');
  });
});

이렇게 하면 UI 변경이 발생했을 때 `LoginPage` 파일만 수정하면 되고, 여러 테스트 파일에 분산된 Selector를 한꺼번에 수정할 필요가 없어져서 유지보수 효율성이 크게 높아집니다.

파일 업로드 및 다운로드 테스트

웹 애플리케이션에서는 파일을 업로드하거나 다운로드하는 기능도 흔하게 사용되죠? Playwright는 이런 시나리오도 쉽게 테스트할 수 있도록 지원합니다.

파일 업로드 예시:


// tests/file-upload.spec.ts
import { test, expect } from '@playwright/test';
import * as path from 'path'; // 파일 경로를 다루기 위해 node:path 모듈을 사용합니다.

test('파일 업로드 기능 테스트', async ({ page }) => {
  await page.goto('https://www.example.com/upload'); // 파일 업로드 페이지로 이동

  // 업로드할 파일 경로를 지정합니다. (예: 프로젝트 루트에 test-file.txt 파일을 만드세요)
  const filePath = path.join(__dirname, '..', 'test-file.txt'); 
  // 'test-file.txt' 파일 내용: "이것은 테스트 파일입니다."

  // input[type="file"] 요소를 찾아서 파일을 설정합니다.
  await page.setInputFiles('input[type="file"]', filePath);

  // 업로드 버튼 클릭 (예시)
  await page.getByRole('button', { name: '업로드' }).click();

  // 업로드 성공 메시지 검증
  await expect(page.locator('text=파일이 성공적으로 업로드되었습니다.')).toBeVisible();
  await expect(page.locator('text=test-file.txt')).toBeVisible(); // 업로드된 파일 이름 확인
});

파일 다운로드 예시:


// tests/file-download.spec.ts
import { test, expect } from '@playwright/test';
import * as path from 'path';
import * as fs from 'fs'; // 파일 시스템 모듈을 사용합니다.

test('파일 다운로드 기능 테스트', async ({ page }) => {
  await page.goto('https://www.example.com/download'); // 파일 다운로드 페이지로 이동

  // 다운로드 이벤트 대기
  const [download] = await Promise.all([
    page.waitForEvent('download'), // 다운로드 이벤트가 발생할 때까지 기다립니다.
    page.getByRole('link', { name: '다운로드 파일' }).click() // 다운로드 링크 클릭
  ]);

  // 다운로드된 파일의 정보를 확인합니다.
  expect(download.suggestedFilename()).toBe('document.pdf'); // 예상 파일 이름 확인
  
  // 다운로드된 파일을 임시 경로에 저장합니다.
  const downloadPath = path.join(__dirname, 'temp', download.suggestedFilename() || 'downloaded_file');
  await download.saveAs(downloadPath);

  // 파일이 실제로 저장되었는지 확인합니다.
  expect(fs.existsSync(downloadPath)).toBeTruthy();

  // 필요하다면 다운로드된 파일의 내용을 검증할 수도 있습니다.
  // const fileContent = fs.readFileSync(downloadPath, 'utf-8');
  // expect(fileContent).toContain('Expected content');

  // 테스트 종료 후 임시 파일 삭제 (선택 사항)
  fs.unlinkSync(downloadPath); 
});

파일 업로드와 다운로드 테스트는 실제 사용 시나리오를 완벽하게 재현하여 데이터 처리의 안정성을 검증하는 데 매우 유용하답니다.

Playwright의 고급 기능으로 테스트 효율 높이기

Playwright는 단순히 테스트를 실행하는 것을 넘어, 테스트 개발 및 유지보수 효율을 극대화할 수 있는 다양한 고급 기능들을 제공합니다.

테스트 리포팅 및 디버깅

테스트가 실패했을 때, 왜 실패했는지 빠르게 파악하는 것이 중요하죠. Playwright는 이를 위한 강력한 도구들을 내장하고 있어요.

  • HTML 리포터: 기본적으로 제공되는 HTML 리포터는 테스트 실행 후 생성되는 `playwright-report` 폴더에 시각적인 테스트 결과를 보여줍니다. 각 테스트의 성공/실패 여부, 실행 시간, 에러 메시지 등을 한눈에 확인할 수 있어요. 터미널에서 `playwright show-report` 명령어를 실행하여 웹 브라우저로 리포트를 볼 수 있습니다.
  • 테스트 트레이스 뷰어(Trace Viewer): Playwright의 가장 강력한 디버깅 도구 중 하나입니다. 테스트 실행 시 `trace: 'on'` 옵션을 설정하면, 테스트 과정의 모든 액션, 네트워크 요청, 콘솔 로그, 스크린샷, 비디오 등을 기록합니다. 실패한 테스트를 `playwright show-trace trace.zip` 명령어로 열어보면, 마치 영화를 보듯이 테스트 실패 지점까지의 모든 과정을 되돌려볼 수 있어서 문제 원인 분석 시간을 획기적으로 줄여줍니다.
  • 디버그 모드: `PWDEBUG=1` 환경 변수를 설정하고 테스트를 실행하면 브라우저가 실행된 상태에서 개발자 도구가 열리고, 테스트 코드가 `page.pause()` 지점에서 멈춰요. 이때 개발자 도구를 이용해 수동으로 요소를 조작하거나 Selector를 검증해볼 수 있어서 테스트 코드 작성 및 디버깅에 매우 유용합니다.

// playwright.config.ts 파일에 trace 옵션 추가
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // ... 생략 ...
  use: {
    trace: 'on-first-retry', // 첫 번째 실패 시 트레이스 기록
    // 또는 'on', 'off', 'retain-on-failure' 등
  },
  // ... 생략 ...
});

CI/CD 파이프라인 통합

E2E 테스트 자동화의 진정한 가치는 지속적인 통합/지속적인 배포(CI/CD) 파이프라인에 통합될 때 발휘됩니다. 코드가 변경될 때마다 자동으로 테스트를 실행하고, 문제가 있으면 바로 피드백을 받을 수 있기 때문이죠.

Playwright는 GitHub Actions, GitLab CI, Jenkins 등 다양한 CI/CD 시스템과 쉽게 통합될 수 있도록 설계되었습니다. 간단한 GitHub Actions 워크플로우 예시를 보여드릴게요.


# .github/workflows/playwright.yml
name: Playwright Tests

on:
  push:
    branches: [ main, master ]
  pull_request:
    branches: [ main, master ]

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 18
    - name: Install dependencies
      run: npm ci
    - name: Install Playwright browsers
      run: npx playwright install --with-deps
    - name: Run Playwright tests
      run: npm test
    - uses: actions/upload-artifact@v4
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
        retention-days: 30

이 워크플로우는 코드가 `main` 또는 `master` 브랜치에 푸시되거나 Pull Request가 생성될 때마다 자동으로 Playwright 테스트를 실행합니다. 테스트 결과 리포트도 아티팩트로 저장하여 나중에 확인할 수 있도록 하죠. 이런 자동화를 통해 개발자는 더 빠르게 피드백을 받고, 고품질의 소프트웨어를 지속적으로 제공할 수 있게 됩니다.

병렬 실행 및 브라우저 컨텍스트 활용

테스트 스위트가 커질수록 실행 시간도 길어지기 마련인데요, Playwright는 병렬 실행 기능을 통해 이 문제를 효과적으로 해결합니다.

  • 테스트 병렬 실행: `playwright.config.ts` 파일의 `workers` 옵션을 설정하거나, CLI에서 `--workers` 옵션을 사용해서 동시에 실행할 테스트 스레드 수를 지정할 수 있습니다. 예를 들어, `npx playwright test --workers=4`는 4개의 워커를 사용하여 테스트를 병렬로 실행합니다. 이는 테스트 실행 시간을 수 배 단축시키는 효과를 가져옵니다.
  • 브라우저 컨텍스트: Playwright는 각 테스트마다 격리된 브라우저 컨텍스트를 제공합니다. 이는 각 테스트가 완전히 독립적인 환경에서 실행된다는 의미예요. 예를 들어, 한 테스트에서 로그인한 세션 정보가 다른 테스트에 영향을 주지 않으므로, 테스트 간의 의존성을 줄이고 안정성을 높일 수 있습니다. 또한, `browser.newContext()`를 사용하여 동일한 브라우저 인스턴스 내에서 여러 개의 독립적인 컨텍스트를 생성하여 멀티 유저 시나리오를 테스트하는 것도 가능합니다.

// playwright.config.ts 에서 workers 설정
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // ... 생략 ...
  workers: process.env.CI ? 1 : undefined, // CI 환경에서는 1개, 로컬에서는 Playwright가 자동으로 최적화
  // 또는 특정 개수로 고정: workers: 4,
  // ... 생략 ...
});

이러한 기능들은 대규모 웹 애플리케이션의 E2E 테스트를 더욱 효율적이고 안정적으로 만들 수 있도록 도와줍니다.

Playwright를 활용한 웹 애플리케이션 E2E 테스트 자동화 실전 가이드 - coronavirus, mass testing, covid-19, slovakia, swab test, swab testing, pandemic, new normal, antigen test, man, testing, coronavirus test, medical worker, healthcare professional, healthcare worker, face masks, žilina, slovakia, slovakia, slovakia, swab test, swab test, swab test, swab test, swab test, pandemic, antigen test, antigen test, antigen test, testing, testing, coronavirus test, coronavirus test, medical worker, healthcare professional, healthcare worker, healthcare worker

Image by lukasmilan on Pixabay

Playwright E2E 테스트, 성공적인 도입을 위한 팁

Playwright는 강력한 도구이지만, 그 효과를 극대화하려면 몇 가지 고려해야 할 점들이 있어요. 성공적인 E2E 테스트 도입을 위한 팁들을 공유해 드릴게요.

테스트 전략 수립의 중요성

무작정 모든 것을 E2E 테스트로 자동화하려고 하면 안 됩니다. E2E 테스트는 실행 속도가 느리고 유지보수 비용이 높다는 단점이 있기 때문이에요. 그래서 어떤 부분을 E2E 테스트로 커버할지 전략적으로 결정하는 것이 중요합니다.

  • 핵심 비즈니스 흐름에 집중: 사용자가 제품을 사용하는 가장 중요한 시나리오(예: 회원가입, 로그인, 상품 구매, 주요 기능 사용)에 E2E 테스트를 집중하세요.
  • 테스트 피라미드 활용: 단위 테스트로 대부분의 로직을, 통합 테스트로 모듈 간의 연동을, 그리고 E2E 테스트로 최종 사용자 경험을 검증하는 '테스트 피라미드' 전략을 따르는 것이 좋습니다. E2E 테스트는 피라미드의 가장 작은 부분이어야 해요.
  • 잦은 변경이 없는 부분 우선: UI가 자주 바뀌는 부분보다는 비교적 안정적인 핵심 기능에 E2E 테스트를 적용하는 것이 유지보수 부담을 줄일 수 있습니다.

유지보수와 확장성을 고려한 설계

테스트 코드는 프로덕션 코드만큼이나 중요합니다. 잘 관리되지 않으면 오히려 개발 속도를 저해하는 요소가 될 수 있어요.

  • 페이지 객체 모델(POM) 적극 활용: 앞에서 설명했듯이, POM 패턴은 테스트 코드의 가독성, 재사용성, 유지보수성을 크게 높여줍니다. 페이지별로 객체를 분리하고, 각 객체는 해당 페이지의 요소와 상호작용 메서드를 캡슐화하도록 설계하세요.
  • 의미 있는 Selector 사용: `id`나 `class`가 자주 변경될 수 있는 경우, `data-testid`와 같은 테스트 전용 속성을 사용하거나, Playwright의 `getByRole`, `getByText` 등 사용자 친화적인 Selector를 적극 활용하세요. 이는 UI 변경에 강한 견고한 테스트를 만드는 데 도움이 됩니다.
  • 클린 코드 원칙 적용: 테스트 코드에도 프로덕션 코드와 마찬가지로 클린 코드 원칙(명확한 변수명, 함수명, 적절한 추상화, 중복 제거 등)을 적용해야 합니다.
  • 테스트 케이스의 독립성: 각 테스트 케이스는 다른 테스트 케이스에 의존하지 않고 독립적으로 실행될 수 있도록 설계해야 합니다. `beforeEach`, `afterEach` 훅을 활용하여 테스트 전후 환경을 깔끔하게 정리하는 것이 중요해요.

팀원들과의 협업 및 지식 공유

테스트 자동화는 단순히 한두 명의 개발자가 하는 일이 아니라, 팀 전체의 노력이 필요합니다.

  • 테스트 문화 구축: 개발 초기부터 테스트를 고려하고, 코드 리뷰 시 테스트 코드도 함께 검토하는 문화를 만드세요.
  • 가이드라인 공유: Playwright 사용법, 테스트 코드 작성 규칙, POM 적용 방법 등에 대한 내부 가이드라인을 만들고 팀원들과 공유하여 일관된 코드 품질을 유지하는 것이 중요합니다.
  • 정기적인 테스트 코드 리팩토링: 프로덕션 코드처럼 테스트 코드도 주기적으로 리팩토링하여 최적의 상태를 유지해야 합니다.

이런 노력들이 모여야 Playwright를 통한 E2E 테스트 자동화가 성공적으로 정착하고, 궁극적으로 개발 생산성과 웹 애플리케이션의 품질을 한 단계 더 끌어올릴 수 있을 거예요.

마무리하며: 안정적인 웹 서비스를 위한 Playwright

오늘은 Playwright를 활용한 웹 애플리케이션 E2E 테스트 자동화에 대해 자세히 살펴보았습니다. E2E 테스트의 중요성부터 시작해서 Playwright의 강력한 기능들, 그리고 실제 테스트 코드를 작성하는 방법, 더 나아가 고급 기능 활용성공적인 도입을 위한 팁까지, 정말 많은 내용을 다뤘죠?

Playwright다중 브라우저 지원, 자동 대기, 병렬 실행, 강력한 디버깅 도구 등 현대 웹 테스트 환경에 최적화된 기능들을 제공하며, 여러분의 테스트 자동화 경험을 한층 더 쉽고 즐겁게 만들어 줄 거예요. 처음에는 테스트 코드 작성에 시간이 걸릴 수 있지만, 장기적으로는 버그 발견 시간 단축, 배포 안정성 향상, 그리고 개발팀의 자신감 증대라는 엄청난 이점을 가져다줄 거랍니다.

이 가이드가 여러분의 Playwright 여정에 큰 도움이 되었기를 바랍니다. 혹시 Playwright를 사용하시면서 겪었던 재밌는 경험이나 궁금한 점이 있다면 언제든지 댓글로 남겨주세요! 함께 이야기를 나누면서 더 성장할 수 있을 거예요. 다음에도 유익한 정보로 찾아뵙겠습니다. 감사합니다!

📌 함께 읽으면 좋은 글

  • [기술 리뷰] gRPC와 REST API: 마이크로서비스 통신 방식, 무엇이 최적일까요? 성능, 복잡성, 사용 사례 심층 비교
  • [튜토리얼] Apollo Server와 GraphQL 백엔드 API, 스키마부터 데이터 연동까지 완벽 실습 가이드
  • [튜토리얼] Node.js Socket.IO 실시간 웹 애플리케이션 구축 가이드: 웹소켓 통신부터 확장 전략까지

이 글이 도움이 되셨다면 공감(♥)댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.