2026년 Playwright로 E2E 테스트 자동화 완벽 가이드
튜토리얼

2026년 Playwright로 E2E 테스트 자동화 완벽 가이드

2026년 06월 11일 조회 9 댓글 0

혹시 배포 직전 버그 발견해서 밤샐 뻔한 경험 있으세요? Playwright E2E 테스트가 그걸 막아줄 수 있어요.

안녕하세요! 오늘은 2026년 현재 가장 핫한 테스트 자동화 도구인 Playwright로 E2E 테스트 자동화하는 방법을 완벽하게 알려드릴게요. 솔직히 말하자면요, 저도 처음에는 "테스트 코드 작성할 시간에 기능 하나 더 만들지" 이런 생각이었거든요. 근데... 실제로 써보니까 완전 달라요. 사실은요, 작년에 큰 프로젝트 배포하다가 결제 플로우에서 치명적인 버그를 발견했던 경험이 있어요. 그때 밤새워 고쳤던 기억이 아직도 생생한데, Playwright E2E 테스트만 제대로 구축해뒀어도 배포 전에 미리 잡을 수 있었던 문제였죠. 그래서 이번 글에서는 제가 직접 2026년에 실무에서 사용하면서 터득한 노하우들을 모두 공유해드릴게요!

? 이 글의 내용
→ Playwright란? 2026년 E2E 테스트 자동화의 필수 도구 → Playwright 설치 및 초기 설정 완벽 가이드 → 첫 E2E 테스트 작성 방법 - 실전 예제로 배우기 → Playwright 고급 기능 사용법 - API 모킹부터 병렬 실행까지 → 2026년 Playwright 베스트 프랙티스 7가지 → CI/CD 파이프라인에 Playwright 통합하는 방법

? Playwright란? 2026년 E2E 테스트 자동화의 필수 도구

playwright testing automation
Photo by Jotform on Unsplash

Playwright는 마이크로소프트에서 개발한 오픈소스 E2E 테스트 자동화 프레임워크예요. 뭐랄까... Selenium이나 Cypress 같은 기존 도구들의 장점만 쏙쏙 뽑아서 만든 느낌이라고 할까요? 2026년 현재 가장 빠르게 성장하고 있는 테스트 도구 중 하나거든요.

제가 Playwright를 선택한 이유는 진짜 명확해요. 일단 크롬, 파이어폭스, 사파리를 모두 지원한다는 점이 엄청난 장점이었어요. 예전에 Cypress 쓸 때는 사파리 테스트 때문에 머리 아팠거든요. 근데 Playwright는 하나의 코드로 모든 브라우저를 테스트할 수 있어요.

✨ Playwright의 핵심 장점
  • 자동 대기 기능 - 요소가 나타날 때까지 알아서 기다려줘요
  • 병렬 실행 - 테스트 속도가 진짜 빨라요
  • 네트워크 제어 - API 응답을 모킹하거나 가로챌 수 있어요
  • 스크린샷 & 비디오 - 실패한 테스트를 영상으로 볼 수 있죠
  • 타입스크립트 완벽 지원 - 자동완성이 정말 좋아요

특히 2026년에 들어서면서 Playwright의 성능이 더욱 향상됐어요. 저희 팀 프로젝트에서 약 200개의 E2E 테스트를 돌리는데, 병렬 실행 덕분에 10분도 안 걸리거든요. Cypress 쓸 때는 30분 넘게 걸렸던 걸 생각하면... 정말 게임 체인저라고 할 수 있죠.

아 그리고요, Playwright는 헤드리스 모드헤드풀 모드를 쉽게 전환할 수 있어요. 개발할 때는 브라우저를 보면서 테스트하고, CI/CD에서는 헤드리스로 빠르게 돌리면 되죠. 이런 유연성이 실무에서 엄청 중요하더라고요.

⚙️ Playwright 설치 및 초기 설정

web browser automation tool
Photo by Team Nocoloco on Unsplash

Playwright 설치는 생각보다 진짜 간단해요. 저도 처음에는 "이거 설정하려면 또 얼마나 복잡할까" 걱정했는데요, 막상 해보니까 5분도 안 걸리더라고요. 2026년 현재 Playwright는 설치 과정이 엄청 간소화되어서 npm 명령어 몇 개면 바로 E2E 테스트 자동화를 시작할 수 있거든요.

? Node.js 환경 확인하기

먼저 Node.js가 설치되어 있어야 해요. Playwright는 Node.js 16 이상에서 작동하는데요, 2026년 기준으로는 Node.js 18이나 20 버전을 쓰시는 걸 추천드려요. 터미널에서 버전 확인하는 방법 알려드릴게요.

? Node.js 버전 확인
node --version
npm --version

만약 Node.js가 없으면 공식 사이트에서 LTS 버전 다운받으시면 되는데요, 설치 과정도 완전 직관적이에요.

? Playwright 프로젝트 초기화

자, 이제 본격적으로 Playwright를 설치해볼게요. 2026년 현재 가장 추천하는 방법은 공식 init 명령어를 사용하는 거예요. 이 명령어 하나면 테스트 환경이 자동으로 세팅되거든요.

? Playwright 설치 명령어
npm init playwright@latest

# 또는 yarn 사용하시는 분들은
yarn create playwright

# pnpm 사용자라면
pnpm create playwright

명령어 실행하면 몇 가지 질문이 나와요. 솔직히 처음에는 뭘 선택해야 할지 좀 헷갈리는데요, 제가 추천하는 옵션들 알려드릴게요.

? 설치 시 추천 옵션
  • TypeScript 사용 - 타입 안정성 때문에 무조건 Yes예요
  • 테스트 폴더명 - 기본값 'tests'로 그냥 두시면 돼요
  • GitHub Actions - CI/CD 쓸 거면 Yes, 아니면 나중에 추가해도 괜찮아요
  • 브라우저 설치 - Yes 눌러서 자동으로 다운받게 하세요

? 설치 후 프로젝트 구조

설치가 끝나면 프로젝트에 자동으로 파일들이 생성되는데요, 처음 보면 뭐가 뭔지 좀 혼란스러울 수 있어요. 근데 각 파일마다 역할이 있거든요.

파일/폴더명 역할 중요도
playwright.config.ts Playwright 전체 설정 파일 (브라우저, 타임아웃 등) 필수
tests/ 실제 테스트 파일들이 들어가는 폴더 필수
tests-examples/ 예제 테스트 파일 (학습용) 선택
package.json 프로젝트 의존성 및 스크립트 관리 필수
playwright-report/ 테스트 실행 후 생성되는 리포트 폴더 자동생성

? 기본 설정 커스터마이징

playwright.config.ts 파일이 진짜 중요한데요. 여기서 브라우저 종류, 타임아웃, 베이스 URL 같은 걸 다 설정할 수 있어요. 제가 실제로 쓰는 설정 보여드릴게요.

? 기본 설정 예시
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30 * 1000,
  expect: {
    timeout: 5000
  },
  fullyParallel: true,
  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'] },
    },
  ],
});

이 설정에서 주목할 부분이 몇 가지 있는데요. baseURL은 테스트할 사이트 주소예요. 로컬에서 개발 중이면 localhost로 설정하면 되고요. tracescreenshot는 테스트 실패했을 때 디버깅하려고 쓰는 거거든요.

⚠️ 주의할 점

처음 설치할 때 브라우저 다운로드 시간이 좀 걸려요. Chrome, Firefox, WebKit 세 개를 다 받으면 약 1GB 정도 되거든요. 와이파이 좋은 곳에서 설치하시는 게 정신건강에 이로워요!

? 첫 테스트 실행해보기

설치가 다 끝났으면 테스트 한 번 돌려봐야죠. 예제 테스트가 자동으로 생성되어 있을 거예요. 실행 명령어는 엄청 간단해요.

? 테스트 실행 명령어
# 모든 테스트 실행
npx playwright test

# UI 모드로 실행 (시각적으로 보면서 테스트)
npx playwright test --ui

# 특정 브라우저만 테스트
npx playwright test --project=chromium

# 헤드 모드로 실행 (브라우저 창 띄워서)
npx playwright test --headed

저는 개인적으로 --ui 옵션 진짜 좋아하거든요. 테스트가 어떻게 돌아가는지 눈으로 확인할 수 있어서 디버깅할 때 엄청 편해요. 2026년에 추가된 UI 모드 기능이 완전 게임 체인저예요.

? 프로 팁

package.json에 테스트 스크립트를 추가해두면 더 편해요. "test": "playwright test", "test:ui": "playwright test --ui" 이런 식으로 넣어두면 npm test 명령어로 간편하게 실행할 수 있거든요.

? VS Code 확장 프로그램 설치

VS Code 쓰시는 분들한테는 Playwright 공식 확장 프로그램 강력 추천드려요. 이거 설치하면 코드 에디터에서 바로 테스트 실행하고 디버깅할 수 있거든요. 진짜 편해요.

확장 프로그램 마켓플레이스에서 "Playwright Test for VSCode" 검색하면 바로 나와요. 설치하면 사이드바에 Playwright 아이콘이 생기는데요, 거기서 테스트 목록 보고 개별 실행도 할 수 있어요. 솔직히 이거 없이는 개발 못할 정도예요.

설치 방법 장점 추천 대상
npm init 자동 설정, 초보자 친화적 처음 시작하는 분들
npm install -D 기존 프로젝트에 추가 이미 프로젝트 있는 분들
Docker 이미지 CI/CD 환경에 최적화 운영 환경 자동화

뭐랄까... 설치 과정이 이렇게 간단할 줄은 몰랐죠? 예전에 Selenium 쓸 때는 드라이버 설치하고 경로 설정하고 완전 귀찮았는데요. Playwright는 브라우저 자동 다운로드에 설정 파일까지 자동 생성되니까 개발자 경험이 완전 다르더라고요. 2026년 현재 E2E 테스트 자동화를 시작하려면 Playwright가 정말 최고의 선택인 것 같아요!

? 첫 번째 Playwright 테스트 작성하기

continuous integration testing
Photo by Levart_Photographer on Unsplash

이제 본격적으로 Playwright로 E2E 테스트를 작성해볼 건데요. 솔직히 말하자면 처음에는 "뭐부터 해야 하지?" 싶을 수 있어요. 근데 걱정 마세요! 첫 번째 테스트는 정말 간단하거든요. 2026년 현재 Playwright는 엄청 직관적이어서, 여러분이 생각하는 대로 코드를 작성하면 거의 그대로 동작해요.

가장 기본적인 테스트 구조 이해하기

Playwright 테스트의 기본 구조는 정말 단순해요. 제가 처음 봤을 때도 "어? 이게 끝이야?" 싶었거든요. 한번 볼까요?

? 첫 번째 테스트 코드 예시
import { test, expect } from '@playwright/test';

test('홈페이지 타이틀 확인하기', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  
  // 페이지 타이틀이 'Playwright'를 포함하는지 확인
  await expect(page).toHaveTitle(/Playwright/);
});

test('Get started 링크 클릭 테스트', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  
  // 링크 클릭
  await page.getByRole('link', { name: 'Get started' }).click();
  
  // URL이 변경되었는지 확인
  await expect(page).toHaveURL(/.*intro/);
});

보세요! 진짜 읽는 것처럼 이해되죠? 이게 Playwright의 가장 큰 장점이에요.

Playwright 테스트 파일 생성하는 방법

자, 그럼 실제로 테스트 파일을 만들어볼까요? 프로젝트 루트에 tests 폴더를 만들고, 그 안에 테스트 파일을 작성하면 돼요.

  • 테스트 파일 이름 규칙: example.spec.ts 또는 example.test.ts 형식으로 작성하세요
  • 폴더 구조: tests/ 폴더 안에 기능별로 파일을 나눠서 관리하면 좋아요
  • 타입스크립트 권장: 2026년 현재는 대부분 .ts 파일로 작성하는 추세예요
  • 파일명은 명확하게: login.spec.ts, checkout.spec.ts 처럼 무엇을 테스트하는지 바로 알 수 있게 작성하세요
? 팁

제가 실제로 사용하는 폴더 구조를 알려드릴게요. tests/e2e/ 폴더에는 전체 플로우 테스트를, tests/components/ 폴더에는 컴포넌트별 테스트를 넣어요. 이렇게 하니까 나중에 찾기도 엄청 쉽더라고요.

주요 Playwright API 완벽 정리

Playwright를 사용하다 보면 자주 쓰게 되는 API들이 있어요. 이것들만 알아도 웬만한 테스트는 다 작성할 수 있거든요.

API 메서드 설명 예시
page.goto() 특정 URL로 이동해요 await page.goto('https://example.com')
page.click() 요소를 클릭할 때 사용해요 await page.click('button')
page.fill() 입력 필드에 텍스트를 넣어요 await page.fill('#email', 'test@test.com')
page.getByRole() 접근성 역할로 요소를 찾아요 (2026년 권장방식!) await page.getByRole('button', { name: '로그인' })
page.getByText() 텍스트 내용으로 요소를 찾아요 await page.getByText('환영합니다')
page.waitForSelector() 특정 요소가 나타날 때까지 기다려요 await page.waitForSelector('.modal')

실전 예제: 로그인 플로우 테스트 작성하기

자, 이제 진짜 실무에서 쓸 법한 테스트를 작성해볼게요. 로그인 기능은 거의 모든 서비스에 있잖아요? 이걸 E2E로 테스트하는 방법을 알려드릴게요.

? 로그인 테스트 완전체
import { test, expect } from '@playwright/test';

test.describe('로그인 기능 테스트', () => {
  test.beforeEach(async ({ page }) => {
    // 모든 테스트 전에 로그인 페이지로 이동
    await page.goto('https://example.com/login');
  });

  test('정상적인 로그인이 성공하는지 확인', async ({ page }) => {
    // 이메일 입력
    await page.getByLabel('이메일').fill('user@example.com');
    
    // 비밀번호 입력
    await page.getByLabel('비밀번호').fill('password123');
    
    // 로그인 버튼 클릭
    await page.getByRole('button', { name: '로그인' }).click();
    
    // 대시보드로 리다이렉트 되었는지 확인
    await expect(page).toHaveURL(/.*dashboard/);
    
    // 환영 메시지가 보이는지 확인
    await expect(page.getByText('환영합니다')).toBeVisible();
  });

  test('잘못된 비밀번호로 로그인 실패 테스트', async ({ page }) => {
    await page.getByLabel('이메일').fill('user@example.com');
    await page.getByLabel('비밀번호').fill('wrongpassword');
    await page.getByRole('button', { name: '로그인' }).click();
    
    // 에러 메시지가 표시되는지 확인
    await expect(page.getByText('이메일 또는 비밀번호가 올바르지 않습니다')).toBeVisible();
    
    // 여전히 로그인 페이지에 있는지 확인
    await expect(page).toHaveURL(/.*login/);
  });

  test('필수 항목 미입력시 유효성 검사', async ({ page }) => {
    // 아무것도 입력하지 않고 로그인 버튼 클릭
    await page.getByRole('button', { name: '로그인' }).click();
    
    // 유효성 검사 메시지 확인
    await expect(page.getByText('이메일을 입력해주세요')).toBeVisible();
  });
});

근데요... 실제로 써보니까 알게 된 건데, getByRole이나 getByLabel 같은 메서드를 사용하는 게 진짜 중요해요. CSS 선택자로만 찾다가 UI가 조금만 바뀌어도 테스트가 깨지더라고요.

테스트 실행하고 결과 확인하기

테스트를 작성했으면 이제 실행해봐야겠죠? Playwright는 테스트 실행 방법도 엄청 다양해요.

✅ 테스트 실행 명령어 모음
  1. 모든 테스트 실행: npx playwright test
  2. 특정 파일만 실행: npx playwright test login.spec.ts
  3. UI 모드로 실행 (2026년 완전 추천!): npx playwright test --ui
  4. 헤드 모드로 실행 (브라우저 보면서): npx playwright test --headed
  5. 디버그 모드: npx playwright test --debug
  6. 특정 브라우저만: npx playwright test --project=chromium

아 그리고요! UI 모드는 진짜 게임 체인저예요. 제가 2026년에 Playwright 쓰면서 가장 좋아하는 기능이거든요. 테스트 실행 과정을 시각적으로 보면서, 각 단계별로 스크린샷도 찍어주고, 뭐가 잘못됐는지 바로바로 확인할 수 있어요.

⚠️ 주의사항

처음 테스트를 작성할 때 흔히 하는 실수가 있어요. 너무 빠르게 다음 액션으로 넘어가는 거예요. 예를 들어, 버튼을 클릭했는데 API 응답이 오기도 전에 다음 검증을 하려고 하면 테스트가 실패할 수 있어요. Playwright는 자동으로 기다려주긴 하지만, 복잡한 상황에서는 명시적으로 waitForLoadState() 같은 걸 써주는 게 안전해요.

Assertions로 검증하는 5가지 핵심 방법

테스트에서 가장 중요한 게 뭘까요? 바로 검증(Assertion)이에요. "이게 맞는지 확인하는 것" 말이죠. Playwright의 expect는 정말 강력해요.

  • toBeVisible(): 요소가 화면에 보이는지 확인해요 - await expect(page.getByText('안녕')).toBeVisible()
  • toHaveText(): 특정 텍스트를 가지고 있는지 검증해요 - await expect(page.locator('h1')).toHaveText('제목')
  • toHaveURL(): URL이 맞는지 확인해요 - await expect(page).toHaveURL(/dashboard/)
  • toHaveCount(): 요소의 개수를 확인해요 - await expect(page.getByRole('listitem')).toHaveCount(5)
  • toBeEnabled(): 버튼이 활성화되어 있는지 확인해요 - await expect(page.getByRole('button')).toBeEnabled()

솔직히 처음에는 이것들을 다 외우려고 했는데요... 그럴 필요 없어요. 코드 작성하다 보면 자동완성으로 다 나오거든요. 그냥 자주 쓰다 보면 자연스럽게 익숙해져요.

? 보너스 팁: 커스텀 타임아웃 설정하기

가끔 API 응답이 느리거나, 애니메이션이 있는 경우 기본 타임아웃으로는 부족할 수 있어요. 이럴 때는 이렇게 해보세요:

// 특정 assertion에만 타임아웃 설정
await expect(page.getByText('로딩 완료')).toBeVisible({ timeout: 10000 });

// 테스트 전체에 타임아웃 설정
test('느린 테스트', async ({ page }) => {
  test.setTimeout(60000); // 60초
  // ... 테스트 코드
});

여기까지가 첫 번째 Playwright 테스트를 작성하는 방법이에요. 처음에는 어렵게 느껴질 수 있지만, 한 번 손에 익으면 정말 빠르게 테스트를 작성할 수 있어요. 저도 처음엔 하나 만드는 데 30분씩 걸렸는데, 지금은 5분이면 기본적인 테스트는 뚝딱 만들어요!

? Playwright 고급 기능으로 테스트 레벨업하기

기본 E2E 테스트는 이제 완벽하게 작성하실 수 있죠? 그럼 이제 2026년 Playwright의 고급 기능들을 활용해서 테스트를 한 단계 업그레이드해볼 시간이에요. 솔직히 말하자면, 이 기능들을 알고 나면 테스트 작성이 진짜 재밌어지거든요.

네트워크 모킹(Mocking)으로 API 테스트 제어하기

실제 API를 호출하면서 테스트하다 보면 완전 골치 아픈 상황이 많아요. API 서버가 불안정하거나, 테스트 데이터가 계속 바뀌거나, 아니면 에러 상황을 일부러 만들어내기가 어렵잖아요.

그래서 Playwright의 네트워크 모킹 기능이 정말 유용해요.

? API 응답 모킹 예시
test('API 에러 상황 테스트', async ({ page }) => {
  // API 요청을 가로채서 커스텀 응답 반환
  await page.route('**/api/users', route => {
    route.fulfill({
      status: 500,
      contentType: 'application/json',
      body: JSON.stringify({ error: 'Server Error' })
    });
  });

  await page.goto('/users');
  
  // 에러 메시지가 제대로 표시되는지 확인
  await expect(page.locator('.error-message')).toBeVisible();
});

이렇게 하면 실제로 서버에 에러를 발생시키지 않아도 에러 처리 로직을 테스트할 수 있어요. 완전 편하죠?

시각적 회귀 테스트(Visual Regression Testing)

근데요, 기능은 잘 돌아가는데 디자인이 깨져있으면 소용없잖아요. 그래서 2026년 현재 Playwright의 스크린샷 비교 기능이 엄청 강력해졌어요.

? 시각적 테스트 코드
test('메인 페이지 시각적 테스트', async ({ page }) => {
  await page.goto('/');
  
  // 전체 페이지 스크린샷 비교
  await expect(page).toHaveScreenshot('homepage.png');
  
  // 특정 요소만 비교
  await expect(page.locator('.header')).toHaveScreenshot('header.png', {
    maxDiffPixels: 100  // 100픽셀 이하 차이는 허용
  });
});

처음 실행하면 기준 이미지를 생성하고, 다음부터는 이 이미지랑 비교해요. CSS 변경이나 레이아웃 깨짐을 자동으로 잡아낼 수 있죠.

? 꿀팁

시각적 테스트는 환경에 따라 픽셀이 미묘하게 달라질 수 있어요. 그래서 CI/CD 환경에서는 Docker 컨테이너를 사용해서 일관된 환경을 유지하는 게 좋아요.

코드 커버리지 측정으로 테스트 품질 확인하기

테스트는 많이 작성했는데, 실제로 어느 정도 코드를 커버하는지 궁금하지 않으세요? Playwright는 V8 커버리지 API를 활용해서 이걸 측정할 수 있어요.

? 커버리지 설정 예시
import { test } from '@playwright/test';

test('커버리지와 함께 테스트', async ({ page }) => {
  // 커버리지 수집 시작
  await page.coverage.startJSCoverage();
  
  await page.goto('/');
  await page.click('button');
  
  // 커버리지 데이터 수집
  const coverage = await page.coverage.stopJSCoverage();
  
  // 커버리지 퍼센트 계산
  let totalBytes = 0;
  let usedBytes = 0;
  
  for (const entry of coverage) {
    totalBytes += entry.text.length;
    for (const range of entry.ranges) {
      usedBytes += range.end - range.start - 1;
    }
  }
  
  console.log(`커버리지: ${(usedBytes / totalBytes * 100).toFixed(2)}%`);
});

Trace Viewer로 테스트 실패 원인 빠르게 찾기

테스트가 실패했을 때 뭐가 문제인지 찾기 위해 로그를 계속 뒤지는 거... 진짜 짜증나죠. 근데 Playwright의 Trace Viewer를 쓰면 이게 완전 달라져요.

테스트 실행 과정을 타임라인으로 보여주고, 각 단계마다 스크린샷이랑 네트워크 요청, DOM 스냅샷까지 다 기록해주거든요.

? Trace 설정
// playwright.config.ts
export default defineConfig({
  use: {
    trace: 'on-first-retry', // 재시도 시에만 trace 수집
    // 또는
    trace: 'retain-on-failure', // 실패한 테스트만 trace 유지
  },
});

실패한 테스트가 있으면 npx playwright show-trace trace.zip 명령어로 바로 확인할 수 있어요.

모바일 에뮬레이션으로 반응형 테스트하기

2026년 현재는 모바일 사용자가 훨씬 많잖아요. 그래서 모바일 환경 테스트도 필수예요. Playwright는 다양한 디바이스를 에뮬레이션할 수 있어요.

? 모바일 디바이스 에뮬레이션
import { test, devices } from '@playwright/test';

test.use({
  ...devices['iPhone 14 Pro'],
});

test('모바일 메뉴 테스트', async ({ page }) => {
  await page.goto('/');
  
  // 햄버거 메뉴가 보이는지 확인
  await expect(page.locator('.mobile-menu-icon')).toBeVisible();
  
  // 터치 제스처 테스트
  await page.locator('.mobile-menu-icon').tap();
  await expect(page.locator('.menu-drawer')).toBeVisible();
});

고급 기능 비교표

각 고급 기능들의 특징과 사용 시나리오를 정리해봤어요. 상황에 맞게 골라 쓰시면 돼요.

기능 주요 용도 난이도 실행 속도 영향
네트워크 모킹 API 응답 제어, 에러 시나리오 테스트 쉬움 빨라짐 (실제 API 호출 없음)
시각적 회귀 테스트 UI 변경 감지, 레이아웃 검증 쉬움 약간 느려짐 (스크린샷 생성)
코드 커버리지 테스트 품질 측정, 미검증 코드 발견 보통 느려짐 (커버리지 수집 오버헤드)
Trace Viewer 테스트 디버깅, 실패 원인 분석 쉬움 약간 느려짐 (trace 기록)
모바일 에뮬레이션 반응형 디자인 검증, 터치 이벤트 테스트 쉬움 영향 없음

권한(Geolocation, Permissions) 테스트

위치 정보나 카메라 권한을 사용하는 기능도 테스트할 수 있어요. 실제로 GPS를 움직이거나 카메라를 켤 필요 없이 말이죠.

? 권한 테스트 예시
test('위치 기반 기능 테스트', async ({ page, context }) => {
  // 위치 권한 부여
  await context.grantPermissions(['geolocation']);
  
  // 서울 좌표로 설정
  await context.setGeolocation({ 
    latitude: 37.5665, 
    longitude: 126.9780 
  });
  
  await page.goto('/nearby-stores');
  
  // 서울 근처 매장이 표시되는지 확인
  await expect(page.locator('.store-name')).toContainText('강남점');
});

카메라, 마이크, 알림 같은 권한도 똑같은 방식으로 테스트할 수 있어요. 진짜 편하죠?

파일 업로드/다운로드 테스트 고급 기법

기본 파일 업로드는 setInputFiles로 간단하지만, 드래그 앤 드롭이나 대용량 파일 처리는 좀 더 신경 써야 해요.

? 고급 파일 업로드 테스트
test('드래그 앤 드롭 파일 업로드', async ({ page }) => {
  await page.goto('/upload');
  
  // 파일 드롭 시뮬레이션
  const buffer = Buffer.from('테스트 파일 내용');
  const dataTransfer = await page.evaluateHandle(() => {
    const dt = new DataTransfer();
    const file = new File(['테스트 파일 내용'], 'test.txt', { 
      type: 'text/plain' 
    });
    dt.items.add(file);
    return dt;
  });
  
  await page.dispatchEvent('.dropzone', 'drop', { dataTransfer });
  
  // 파일 다운로드 대기 및 검증
  const downloadPromise = page.waitForEvent('download');
  await page.click('#download-button');
  const download = await downloadPromise;
  
  // 다운로드된 파일명 확인
  expect(download.suggestedFilename()).toBe('test.txt');
});
⚠️ 주의사항

대용량 파일 테스트는 CI 환경에서 시간이 오래 걸릴 수 있어요. 실제 큰 파일 대신 작은 더미 파일로 테스트하고, 실제 파일 크기 검증은 별도로 하는 게 좋아요.

WebSocket 실시간 통신 테스트

실시간 채팅이나 알림 같은 WebSocket 기능도 테스트해야 하는데, 이게 은근 까다로워요. 근데 Playwright에서는 WebSocket 이벤트를 감지하고 제어할 수 있어요.

? WebSocket 테스트
test('실시간 채팅 메시지 수신', async ({ page }) => {
  await page.goto('/chat');
  
  // WebSocket 연결 대기
  const wsPromise = page.waitForEvent('websocket', ws => {
    return ws.url().includes('chat-server');
  });
  
  await page.click('#connect-button');
  const ws = await wsPromise;
  
  // WebSocket 메시지 모니터링
  const messagePromise = new Promise(resolve => {
    ws.on('framereceived', event => {
      const data = JSON.parse(event.payload);
      if (data.type === 'message') {
        resolve(data.content);
      }
    });
  });
  
  // 메시지 전송
  await page.fill('#message-input', '안녕하세요');
  await page.click('#send-button');
  
  // 응답 메시지 확인
  const response = await messagePromise;
  expect(response).toBeTruthy();
});

이렇게 하면 실시간 기능도 안정적으로 테스트할 수 있어요. 채팅, 라이브 업데이트, 실시간 알림 같은 거 다 가능하죠.

? 실전 팁

고급 기능들을 한꺼번에 다 쓰려고 하지 마세요. 프로젝트 상황에 맞춰서 필요한 것부터 하나씩 적용하는 게 좋아요. 제 경험상 네트워크 모킹이랑 Trace Viewer부터 시작하면 효과를 빨리 볼 수 있더라고요.

✨ Playwright E2E 테스트 베스트 프랙티스

Playwright로 E2E 테스트 자동화를 시작하는 건 어렵지 않은데요, 막상 실무에서 제대로 활용하려면 몇 가지 베스트 프랙티스를 알아야 해요. 솔직히 처음에는 저도 이것저것 시행착오가 많았거든요. 근데 이런 원칙들을 알고 나니까 테스트 코드가 훨씬 안정적이고 유지보수하기 쉬워졌어요.

? Page Object Model 패턴 활용하기

테스트 코드를 작성할 때 가장 중요한 패턴이 바로 Page Object Model(POM)이에요. 이거 진짜 필수거든요. 페이지의 요소들과 동작을 별도 클래스로 분리하면, 나중에 UI가 바뀌었을 때 한 곳만 수정하면 되니까 유지보수가 엄청 편해져요.

? Page Object 예시
// pages/LoginPage.ts
export class LoginPage {
  constructor(private page: Page) {}

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.page.getByLabel('이메일').fill(email);
    await this.page.getByLabel('비밀번호').fill(password);
    await this.page.getByRole('button', { name: '로그인' }).click();
  }

  async getErrorMessage() {
    return await this.page.locator('.error-message').textContent();
  }
}

// tests/login.spec.ts
import { LoginPage } from '../pages/LoginPage';

test('로그인 실패 테스트', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('wrong@email.com', 'wrongpass');
  
  const error = await loginPage.getErrorMessage();
  expect(error).toContain('이메일 또는 비밀번호가 올바르지 않습니다');
});

뭐랄까, 이렇게 하면 테스트 코드가 훨씬 읽기 쉬워지고 재사용성도 높아져요.

⚡ Selector 전략과 우선순위

요소를 선택하는 방법에도 우선순위가 있어요. 2026년 현재 Playwright에서 권장하는 selector 전략을 표로 정리해봤어요.

우선순위 Selector 방법 예시 장점
1위 (최우선) getByRole getByRole('button', { name: '제출' }) 접근성 보장, 의미론적
2위 getByLabel getByLabel('이메일') 폼 요소에 최적, 접근성 우수
3위 getByTestId getByTestId('submit-btn') 안정적, UI 변경에 강함
4위 getByText getByText('로그인하기') 직관적, 간단함
5위 (최후) CSS/XPath locator('.btn-primary') 유연하지만 깨지기 쉬움
⚠️ 주의할 점

CSS 클래스나 XPath는 진짜 최후의 수단으로만 쓰세요. 디자인이 바뀌면 테스트가 바로 깨지거든요. 제가 처음에 이거 몰라서 테스트 100개가 한번에 깨진 적 있어요. 정말 끔찍했죠...

? 테스트 독립성과 격리 유지하기

각 테스트는 완전히 독립적이어야 해요. 한 테스트의 결과가 다른 테스트에 영향을 주면 안 되거든요. 이게 진짜 중요한데, 실무에서 보면 이거 안 지켜서 고생하는 경우가 엄청 많아요.

  • 테스트마다 새로운 컨텍스트 사용 - Playwright는 기본적으로 각 테스트에 새 브라우저 컨텍스트를 제공해요
  • beforeEach로 초기화 - 테스트 시작 전 필요한 데이터나 상태를 설정하세요
  • afterEach로 정리 - 테스트 후 생성된 데이터는 꼭 정리해야 해요
  • 실행 순서 의존성 제거 - 테스트 A가 먼저 실행되어야 테스트 B가 성공하는 구조는 절대 안 돼요
? 테스트 격리 예시
import { test } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  // 각 테스트마다 새로운 사용자로 로그인
  await page.goto('/login');
  await page.getByLabel('이메일').fill(`test-${Date.now()}@example.com`);
  await page.getByLabel('비밀번호').fill('password123');
  await page.getByRole('button', { name: '로그인' }).click();
  await page.waitForURL('/dashboard');
});

test.afterEach(async ({ page, request }) => {
  // 테스트에서 생성한 데이터 정리
  await request.delete('/api/test-cleanup');
});

test('게시글 작성 테스트', async ({ page }) => {
  // 이 테스트는 다른 테스트와 완전히 독립적이에요
  await page.getByRole('button', { name: '새 게시글' }).click();
  // ...
});

⏱️ 적절한 대기 전략 사용하기

sleep()이나 wait()를 사용하는 건 2026년 현재 정말 안 좋은 습관이에요. Playwright는 자동으로 요소를 기다려주는데, 이걸 제대로 활용하는 게 중요하거든요.

대기 방법 사용 시기 예시 코드 권장도
Auto-waiting (자동) 대부분의 경우 await page.click('button') ★★★★★
waitForLoadState 페이지 전환 후 await page.waitForLoadState('networkidle') ★★★★☆
waitForSelector 특정 요소 대기 await page.waitForSelector('.modal') ★★★☆☆
waitForTimeout 정말 어쩔 수 없을 때만 await page.waitForTimeout(1000) ★☆☆☆☆
? 실무 팁

저도 처음에는 page.waitForTimeout()을 자주 썼는데요, 근데 이거 쓰면 테스트 실행 시간이 엄청 길어져요. 그냥 Playwright의 자동 대기 기능을 믿고 쓰세요. 진짜 99%의 경우는 자동으로 잘 기다려줘요. 나머지 1%만 waitForLoadState('networkidle')로 해결하면 충분해요.

? 스크린샷과 비디오 활용하기

테스트가 실패했을 때 왜 실패했는지 파악하는 게 중요하잖아요. 특히 CI/CD 환경에서는 로그만으로는 부족한 경우가 많거든요. 그래서 스크린샷과 비디오를 적극 활용하는 게 좋아요.

? 설정 예시
// playwright.config.ts
export default defineConfig({
  use: {
    // 실패 시 스크린샷 자동 저장
    screenshot: 'only-on-failure',
    
    // 실패한 테스트만 비디오 녹화
    video: 'retain-on-failure',
    
    // 추적 파일도 실패 시에만
    trace: 'retain-on-failure',
  },
  
  // 테스트에서 수동으로 찍기
  test('중요한 단계 캡처', async ({ page }) => {
    await page.goto('/checkout');
    
    // 결제 전 상태 캡처
    await page.screenshot({ 
      path: 'screenshots/before-payment.png',
      fullPage: true 
    });
    
    await page.getByRole('button', { name: '결제하기' }).click();
    
    // 결제 후 상태 캡처
    await page.screenshot({ 
      path: 'screenshots/after-payment.png' 
    });
  })
});

참고로 비디오는 용량이 크니까 모든 테스트에서 녹화하면 스토리지가 금방 차요. 실패한 케이스만 남기는 게 현명해요.

? 테스트 데이터와 환경변수 관리

비밀번호나 API 키 같은 민감한 정보를 코드에 하드코딩하는 건 정말 위험해요. 2026년에는 보안이 더욱 중요해졌거든요. 환경변수를 제대로 활용하는 게 필수예요.

? 환경변수 활용 예시
// .env 파일 (절대 Git에 커밋하지 마세요!)
TEST_USER_EMAIL=test@example.com
TEST_USER_PASSWORD=secure_password_123
API_BASE_URL=https://api.example.com
API_TOKEN=your_api_token_here

// tests/utils/config.ts
export const config = {
  testUser: {
    email: process.env.TEST_USER_EMAIL!,
    password: process.env.TEST_USER_PASSWORD!,
  },
  api: {
    baseUrl: process.env.API_BASE_URL!,
    token: process.env.API_TOKEN!,
  }
};

// tests/login.spec.ts
import { config } from './utils/config';

test('로그인 테스트', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('이메일').fill(config.testUser.email);
  await page.getByLabel('비밀번호').fill(config.testUser.password);
  await page.getByRole('button', { name: '로그인' }).click();
});

아 그리고요, .env 파일은 꼭 .gitignore에 추가해야 해요. 제 동료가 이거 깜빡해서 회사 계정 정보가 GitHub에 올라간 적이 있었는데... 정말 난리났었죠.

? 테스트 실행 속도 최적화하기

테스트가 많아지면 실행 시간이 진짜 길어져요. 근데 몇 가지 최적화 전략을 사용하면 속도를 엄청 개선할 수 있어요.

최적화 방법 설명 효과 주의사항
병렬 실행 여러 테스트를 동시에 실행 50~70% 시간 단축 CPU 사용량 증가
스토리지 상태 재사용 로그인 상태를 저장하고 재사용 30~40% 시간 단축 세션 만료 고려 필요
API를 통한 설정 UI 대신 API로 데이터 준비 60~80% 시간 단축 API 엔드포인트 필요
불필요한 대기 제거 waitForTimeout 최소화 20~30% 시간 단축 안정성 저하 주의
테스트 분할 여러 머신에서 분산 실행 80~90% 시간 단축 인프라 비용 증가
✅ 제가 직접 써본 최적화 팁

스토리지 상태 재사용이 진짜 효과 좋아요. 매번 로그인 플로우를 거치는 대신, 한 번 로그인한 상태를 저장해서 다른 테스트에서 재사용하는 거예요. 저희 팀은 이거 적용하고 테스트 실행 시간이 15분에서 5분으로 줄었어요. 완전 게임체인저였죠!

? 스토리지 상태 재사용 예시
// global-setup.ts
async function globalSetup() {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  
  // 로그인 수행
  await page.goto('/login');
  await page.getByLabel('이메일').fill('test@example.com');
  await page.getByLabel('비밀번호').fill('password123');
  await page.getByRole('button', { name: '로그인' }).click();
  
  // 로그인 상태 저장
  await page.context().storageState({ 
    path: 'auth.json' 
  });
  
  await browser.close();
}

// playwright.config.ts
export default defineConfig({
  globalSetup: './global-setup.ts',
  use: {
    // 저장된 로그인 상태 사용
    storageState: 'auth.json',
  },
});

? 의미 있는 테스트 작성하기

마지막으로 정말 중요한 건데요, 테스트 커버리지를 높이는 것보다 의미 있는 테스트를 작성하는 게 훨씬 중요해요. 솔직히 말하자면 100% 커버리지가 목표가 되면 안 돼요.

  • 핵심 사용자 플로우 위주로 작성 - 로그인, 결제, 회원가입 같은 중요한 기능 먼저요
  • 비즈니스 가치가 높은 부분 집중 - 버그가 나면 큰일 나는 기능부터 테스트하세요
  • 취약한 부분 보강 - 과거에 버그가 자주 났던 영역은 꼼꼼하게 테스트해야 해요
  • 테스트 이름을 명확하게 - "test1", "test2"가 아니라 "로그인 실패 시 에러 메시지 표시" 같이 구체적으로 써야 해요

제가 실무에서 느낀 건데요, E2E 테스트 자동화는 단순히 도구를 아는 것보다 이런 베스트 프랙티스를 얼마나 잘 지키느냐가 더 중요한 것 같아요. 처음에는 좀 번거롭게 느껴질 수 있지만, 장기적으로 보면 엄청난 시간 절약이 돼요.

? CI/CD 파이프라인에 Playwright 통합하기

Playwright로 E2E 테스트를 만들었다면, 이제 CI/CD 파이프라인에 통합해야죠. 솔직히 말하자면 테스트를 로컬에서만 돌리면... 의미가 반감되거든요. 2026년 현재는 대부분의 팀이 GitHub Actions, GitLab CI, Jenkins 같은 도구들을 쓰고 있는데요, Playwright는 이런 CI 환경에서 정말 잘 돌아가요.

제가 직접 여러 CI 도구에서 Playwright를 설정해봤는데요, 처음엔 Docker 이미지 설정이나 브라우저 의존성 문제로 좀 헤맸어요. 근데 이제는 Playwright에서 공식 Docker 이미지도 제공하고, CI 설정 예제도 완전 잘 되어 있어서 엄청 편해졌죠.

GitHub Actions로 자동화하기

GitHub Actions가 2026년 현재 가장 많이 쓰이는 CI 도구인 것 같아요. 무료 플랜에서도 충분히 쓸 수 있고, 설정도 진짜 간단하거든요. Playwright 테스트를 GitHub Actions에 통합하는 건 생각보다 훨씬 쉬워요.

? GitHub Actions 기본 설정
name: Playwright Tests
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test:
    timeout-minutes: 60
    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

이 설정의 핵심 포인트들을 설명해드릴게요:

  • timeout-minutes: 60 - 테스트가 너무 오래 걸리면 자동으로 중단시켜요. 무한 대기 방지하는 거죠
  • --with-deps - 브라우저 실행에 필요한 시스템 의존성까지 한 번에 설치해줘요
  • upload-artifact - 테스트 리포트를 저장해서 나중에 다운로드 받을 수 있어요
  • if: always() - 테스트가 실패해도 리포트는 꼭 업로드되게 하는 거예요
? 프로 팁

npm ci를 쓰는 이유가 있어요. npm install보다 훨씬 빠르고, package-lock.json을 정확하게 따라가거든요. CI 환경에선 항상 npm ci를 쓰는 게 베스트 프랙티스예요!

병렬 실행으로 속도 최적화하기

테스트가 많아지면... 시간이 엄청 오래 걸려요. 저희 팀도 처음엔 전체 테스트 실행하는 데 20분 넘게 걸렸거든요. 근데 병렬 실행(sharding) 설정하니까 5분으로 줄어들더라고요. 진짜 놀랐어요.

? 병렬 실행 설정
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    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 --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
    - uses: actions/upload-artifact@v4
      if: always()
      with:
        name: playwright-report-${{ matrix.shardIndex }}
        path: playwright-report/
        retention-days: 30

이렇게 하면 테스트가 4개의 병렬 작업으로 나뉘어서 동시에 실행돼요. shardTotal 숫자를 늘리면 더 많이 나눌 수도 있는데요, 보통 4~8개 정도가 적당한 것 같아요. 너무 많이 나누면 오히려 오버헤드가 생기거든요.

Docker 컨테이너에서 실행하기

GitLab CI나 Jenkins 같은 다른 CI 도구를 쓴다면 Docker 컨테이너로 실행하는 게 좋아요. Playwright가 공식 Docker 이미지를 제공해서 설정이 정말 간단해졌거든요.

? GitLab CI 설정 예시
e2e-tests:
  image: mcr.microsoft.com/playwright:v1.42.0-jammy
  stage: test
  script:
    - npm ci
    - npx playwright test
  artifacts:
    when: always
    paths:
      - playwright-report/
    expire_in: 1 week
  only:
    - main
    - merge_requests

Docker 이미지를 쓰면 브라우저 설치도 필요 없고, 의존성 문제도 거의 없어요. 참고로 Playwright 버전을 명시하는 게 중요한데요, latest 태그 쓰면 갑자기 버전이 올라가서 테스트가 깨질 수 있거든요.

테스트 결과 시각화하기

CI에서 테스트가 실패했을 때... 로그만 봐서는 뭐가 문제인지 파악하기 어려워요. 그래서 HTML 리포트를 자동으로 배포하는 게 정말 중요해요. GitHub Pages나 Netlify에 올리면 팀원들이 쉽게 확인할 수 있죠.

? GitHub Pages 배포 추가
    - name: Deploy to GitHub Pages
      if: failure()
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./playwright-report
        destination_dir: reports/${{ github.run_id }}

이렇게 하면 테스트가 실패했을 때만 리포트가 자동으로 배포돼요. PR 코멘트에 리포트 링크를 자동으로 달아주는 액션도 있으니까 찾아보세요!

환경 변수와 시크릿 관리

CI 환경에서 테스트를 돌릴 때 가장 조심해야 할 게... API 키나 패스워드 같은 민감한 정보 관리예요. 절대로 코드에 하드코딩하면 안 되고요, CI 도구의 시크릿 기능을 꼭 써야 해요.

? 환경 변수 사용 예시
    - name: Run Playwright tests
      run: npx playwright test
      env:
        BASE_URL: ${{ secrets.STAGING_URL }}
        API_KEY: ${{ secrets.API_KEY }}
        TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
        TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}

테스트 코드에서는 그냥 process.env.BASE_URL처럼 접근하면 돼요. 이렇게 하면 로컬과 CI 환경을 쉽게 분리할 수 있거든요.

⚠️ 주의사항

GitHub Actions에서 PR이 fork된 저장소에서 온 경우, 시크릿에 접근할 수 없어요. 보안 때문이죠. 이럴 땐 pull_request_target 이벤트를 쓰거나, 오픈소스 프로젝트라면 테스트 데이터를 public하게 만들어야 해요.

재시도 로직과 불안정한 테스트 처리

솔직히 말하자면... E2E 테스트는 가끔 이유 없이 실패할 때가 있어요. 네트워크 지연이나 타이밍 이슈 때문이죠. 이런 불안정한 테스트(flaky test)를 처리하는 게 정말 중요해요.

? 재시도 설정
// playwright.config.ts
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  reporter: [
    ['html'],
    ['junit', { outputFile: 'results.xml' }],
    ['github'], // GitHub Actions 전용
  ],
});

CI 환경에서만 자동으로 2번까지 재시도하게 설정한 거예요. 로컬에서는 재시도 안 하고요. 이렇게 하면 일시적인 문제로 인한 실패를 많이 줄일 수 있어요.

? CI 통합 체크리스트
  • ✅ 브라우저와 의존성 자동 설치 설정
  • ✅ 테스트 실패 시 스크린샷/비디오 저장
  • ✅ HTML 리포트 artifact 업로드
  • ✅ 병렬 실행으로 속도 최적화
  • ✅ 환경 변수와 시크릿 안전하게 관리
  • ✅ 불안정한 테스트에 대한 재시도 설정
  • ✅ PR에 테스트 결과 자동 코멘트

비용 최적화 팁

CI 분 단위로 과금되는 거 알고 계시죠? 테스트가 많아지면 비용이 엄청 올라갈 수 있어요. 제가 써본 최적화 방법들을 공유할게요:

  • 캐싱 활용 - node_modules를 캐싱하면 설치 시간을 크게 줄일 수 있어요
  • 조건부 실행 - 프론트엔드 코드가 변경됐을 때만 E2E 테스트 실행
  • 스모크 테스트 분리 - 중요한 테스트만 모든 PR에서 실행하고, 전체는 merge 후에만
  • 브라우저 선택 - 모든 브라우저에서 매번 돌릴 필요 없어요. Chromium만 기본으로 하고 주간 빌드에서 전체 브라우저 테스트
? 조건부 실행 예시
on:
  push:
    paths:
      - 'src/**'
      - 'tests/**'
      - 'package.json'
      - 'playwright.config.ts'

이렇게 특정 파일이 변경됐을 때만 워크플로우를 실행하면 CI 시간을 엄청 아낄 수 있어요. 저희 팀은 이렇게 해서 월 CI 비용을 40% 정도 줄였거든요.

? 프로 팁

2026년 현재 Playwright는 Trace Viewer를 CI에서도 지원해요. trace.zip 파일을 artifact로 저장하면 나중에 로컬에서 재생해볼 수 있어서 디버깅이 훨씬 쉬워져요. playwright.config.ts에서 trace: 'retain-on-failure'로 설정하면 실패한 테스트의 trace만 저장되니까 용량도 절약되고 좋아요!

❓ 자주 묻는 질문

Playwright E2E 테스트를 CI/CD 파이프라인에 통합하려면 어떻게 해야 하나요?

GitHub Actions를 기준으로 설명드릴게요. .github/workflows/playwright.yml 파일을 만들고요, npx playwright install --with-deps 명령어로 브라우저를 설치한 다음 테스트를 실행하면 돼요. 핵심은 헤드리스 모드로 실행하고, 실패한 테스트의 스크린샷이나 비디오를 artifact로 저장하는 거예요. 저는 PR이 올라올 때마다 자동으로 Playwright 테스트가 돌아가도록 설정해뒀는데요, 머지 전에 문제를 미리 잡아낼 수 있어서 정말 좋더라고요. GitLab CI나 Jenkins도 비슷한 방식으로 설정하면 됩니다.

Selenium에서 Playwright로 마이그레이션할 때 가장 주의할 점은 뭔가요?

가장 큰 차이는 대기 방식이에요. Selenium에서 WebDriverWaittime.sleep() 쓰셨죠? Playwright는 자동 대기가 내장돼 있어서 그런 코드가 거의 필요 없어요. 두 번째는 locator 전략인데요, Playwright는 page.locator()로 CSS, text, role 등 다양한 방식을 지원해요. 세 번째는 컨텍스트 관리예요. Selenium의 브라우저 인스턴스 대신 Playwright는 browser context를 사용하는데, 이게 훨씬 가볍고 독립적이에요. 저는 실제로 마이그레이션하면서 테스트 실행 속도가 40% 정도 빨라졌거든요. API도 더 직관적이라 코드 가독성도 좋아졌고요.

동적으로 생성되는 요소를 Playwright로 어떻게 테스트하나요?

React나 Vue 같은 프레임워크에서 동적으로 요소가 나타나는 경우 많잖아요. Playwright는 waitForSelectorwaitForLoadState로 요소가 나타날 때까지 기다릴 수 있어요. 근데 더 좋은 방법은 await page.locator('.dynamic-element').waitFor({ state: 'visible' }) 같은 걸 쓰는 거예요. 네트워크 요청 완료를 기다려야 한다면 await page.waitForResponse()를 사용하고요. 제가 실무에서 쓰는 팁은 data-testid 속성을 미리 추가해두는 거예요. 그럼 동적 클래스명이 바뀌어도 테스트가 안정적으로 돌아가거든요. 예를 들어 await page.locator('[data-testid="user-profile"]') 이런 식으로요.

Playwright 테스트가 너무 느린데 속도를 높이는 방법이 있나요?

병렬 실행이 가장 효과적이에요. npx playwright test --workers=4 이렇게 워커 수를 늘리면 동시에 여러 테스트를 돌릴 수 있거든요. 두 번째는 불필요한 대기 시간 제거예요. page.waitForTimeout() 같은 하드코딩된 대기는 최대한 피하고, 자동 대기를 활용하세요. 세 번째는 trace나 video 같은 디버깅 기능을 필요할 때만 켜는 거예요. trace: 'on-first-retry'로 설정하면 첫 실패 때만 기록되니까 훨씬 빨라져요. 저는 이렇게 최적화하니까 100개 테스트가 15분에서 5분으로 줄었어요. API 테스트는 UI 테스트보다 훨씬 빠르니까, API로 데이터 셋업하고 UI로 검증하는 방식도 추천드려요.

인증이 필요한 페이지를 Playwright로 테스트하려면 어떻게 해야 하나요?

매번 로그인하면 시간 낭비잖아요. Playwright의 Storage State 기능을 쓰면 돼요. 한 번 로그인한 상태를 JSON 파일로 저장하고요, 다른 테스트에서 그 상태를 불러와서 시작하는 거예요. await context.storageState({ path: 'auth.json' })로 저장하고, await browser.newContext({ storageState: 'auth.json' })로 불러오면 됩니다. JWT나 OAuth 같은 토큰 기반 인증이라면, API 요청으로 토큰을 받아서 page.setExtraHTTPHeaders()로 헤더에 추가하는 방법도 있어요. 저는 global setup 파일에서 한 번만 로그인하고 모든 테스트에서 재사용하는데요, E2E 테스트 자동화 시간이 엄청 단축됐어요.

Playwright로 모바일 디바이스 테스트도 가능한가요?

완전 가능해요! Playwright는 40개 이상의 모바일 디바이스 프로필을 제공하거든요. playwright.devices['iPhone 13'] 이런 식으로 미리 정의된 설정을 쓰면 돼요. 화면 크기, 뷰포트, 유저 에이전트가 자동으로 설정되고요. 저는 실제로 iPhone, Galaxy, iPad 버전을 모두 테스트하는데 설정만 바꾸면 되니까 진짜 편해요. 터치 제스처도 await page.tap()이나 await page.swipe()로 시뮬레이션할 수 있고요. 위치 정보나 카메라 권한 같은 것도 context.grantPermissions()로 제어 가능해요. 반응형 웹을 만든다면 Playwright E2E 테스트로 다양한 디바이스에서 검증하는 게 필수예요.


✨ 마무리하며

2026년 현재, Playwright E2E 테스트 자동화는 웹 개발의 필수 도구가 됐어요. 빠른 실행 속도, 직관적인 API, 강력한 디버깅 기능까지 모두 갖춘 도구거든요. 처음에는 설정이 복잡해 보일 수 있지만, 한번 세팅해두면 정말 든든한 지원군이 돼줄 거예요. 저도 처음엔 "이거 꼭 필요한가?" 싶었는데요, 막상 써보니까 버그를 배포 전에 잡아내고, 리팩토링할 때도 자신감이 생기더라고요. 여러분도 오늘 배운 내용으로 작은 테스트부터 시작해보세요. CI/CD 파이프라인에 통합하고, 팀원들과 공유하면서 점점 확장해나가면 돼요. 혹시 막히는 부분 있으면 댓글로 물어봐주세요! 같이 Playwright로 안정적인 웹 서비스 만들어가요. 도움이 됐으면 좋겠네요!

#Playwright #E2E 테스트 #테스트 자동화 #웹 테스트 #엔드투엔드 테스트 #Playwright 튜토리얼 #자동화 테스트 도구 #CI/CD 테스트 #프론트엔드 테스트 #2026 테스트 가이드

이 글 공유하기

Twitter Facebook

댓글 0개

첫 번째 댓글을 남겨보세요!

관련 글