AI 소식12 min

Cypress 버리고 Playwright로 갔다 — 속도·비용·CI 전부 달랐다

Cypress 스위트를 Playwright로 옮긴 실전 기록. action당 290ms vs 420ms, QA 점유율 45.1% vs 14.4%, State of JS 2025 만족도 91% vs 72%. 설치부터 CI 병렬 실행까지 정리했다.

목차 (10)

2026년 4월 · AI 소식

Cypress 스위트 180개를 돌리는 데 매 PR마다 9분씩 걸렸다. 병렬 실행은 Cypress Cloud 유료 플랜이 필요했다. 월 $67부터 시작한다. 팀 규모가 작은 상황에서 계속 부담이었다.

Playwright로 옮기고 나서 PR 파이프라인이 눈에 띄게 빨라졌다. 병렬 실행은 GitHub Actions matrix로 무료다. Safari 테스트도 기본 지원된다. 결론부터 말한다. 새 프로젝트면 고민 없이 Playwright다.

이 글에서는 설치, 페이지 객체 패턴, 네트워크 인터셉트, CI 병렬 실행까지 실전 설정을 정리한다. Cypress와 직접 비교한 수치도 포함된다. E2E 도입을 고민 중이거나 Cypress에서 넘어올 생각이라면 읽을 가치가 있다.

빠르게 보기
· 점유율: Playwright 45.1% / Cypress 14.4% (State of JS 2025, 2026년 1월 발표)
· 만족도: Playwright 91% / Cypress 72% — 역대 최대 격차
· 실행 속도: Playwright 평균 290ms/action / Cypress 420ms (~1.45배 빠름)
· RAM 사용: 10 병렬 테스트 기준 Playwright 2.1GB / Cypress 3.2GB — CI 비용 40~60% 절감
· 병렬 실행: Playwright --shard 무료 / Cypress Cloud $67+/월
· 브라우저 지원: Playwright는 Chrome·FF·WebKit·Edge 바이너리 번들 / Cypress는 Chromium 계열만
· GitHub 스타: Playwright 78,000+ · 주간 npm 다운로드 33M+ (2026년 초)
귀찮은개발자 시리즈
SaaS 보안 취약점 7개를 한번에 잡았다
FeedMission 프로덕션 이메일 장애 디버깅 기록
EP.06 읽기 →

한눈에 비교 — Playwright vs Cypress

먼저 핵심 지표를 정리했다. 실행 속도, 병렬 실행 비용, 브라우저 지원, 네트워크 인터셉트, 점유율 모두 Playwright가 앞선다. State of JS 2025 기준 만족도는 Playwright 91% · Cypress 72%로 역대 최대 격차다. 하지만 Cypress의 UI 친숙함과 time-travel 디버거는 여전히 유효한 장점이다.

숫자만 보면 결론이 너무 쉬워 보이지만, 이미 Cypress 스위트가 구축된 팀에는 상황이 다르다. 마이그레이션 비용과 전환 이득을 직접 비교해야 한다. 표만 보고 결정하지 말 것.

Playwright vs Cypress 전 항목 비교표 — 실행 속도·병렬 실행·브라우저 지원·점유율
Playwright vs Cypress 핵심 지표 비교 / GoCodeLab
항목PlaywrightCypress
실행 속도 (평균/action)290ms420ms
병렬 실행무료 (--shard)Cypress Cloud $67+/월
브라우저 지원Chromium · FF · WebKit · EdgeChromium 계열만
네트워크 인터셉트기본 제공플러그인 필요
RAM (10 병렬)2.1GB3.2GB
만족도 (State of JS 2025)91%72%
2026 QA 점유율45.1%14.4%

설치 및 기본 설정

Node.js 18 이상이면 된다. 명령 하나로 설정 파일까지 자동 생성된다. 브라우저 바이너리도 함께 설치된다.

# 프로젝트 루트에서 실행
npm init playwright@latest

# 브라우저 바이너리 설치 (첫 실행 시 자동)
npx playwright install

명령 실행 후 playwright.config.ts가 생성된다. baseURL, timeout, 브라우저 목록을 여기서 관리한다. 기본값으로 Chromium, Firefox, WebKit 세 개가 설정된다.

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  baseURL: 'http://localhost:3000',
  timeout: 30_000,
  retries: process.env.CI ? 2 : 0,
  projects: [
    { name: 'chromium' },
    { name: 'firefox' },
    { name: 'webkit' },
  ],
});

CI 환경에서는 retries를 2로 설정하는 게 관행이다. 네트워크 불안정으로 인한 오탐을 줄인다. 로컬에서는 0으로 둬서 실패를 즉시 확인한다.

첫 번째 테스트 작성하기

테스트 파일은 *.spec.ts 패턴으로 생성하면 자동 인식된다. test() 함수 하나가 시나리오 하나다. expect()로 검증한다.

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

test('로그인 성공 시 대시보드로 이동한다', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('test@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page).toHaveURL('/dashboard');
});

locator는 DOM 셀렉터다. getByRole(), getByLabel(), getByText() 세 가지를 주로 쓴다. CSS 셀렉터보다 의미 기반 셀렉터가 유지보수하기 쉽다.

getByLabel('Email')은 label 태그와 연결된 input을 찾는다. HTML 구조가 바뀌어도 label 텍스트가 유지되는 한 셀렉터가 깨지지 않는다. CSS 클래스 기반 셀렉터와 다른 점이다.

페이지 객체 패턴으로 구조화하기

페이지 객체는 UI의 셀렉터를 한 클래스로 묶는 추상화 계층이다. 버튼 ID가 바뀌어도 테스트 파일을 하나씩 고치지 않아도 된다.

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

export class LoginPage {
  constructor(private readonly page: Page) {}

  emailInput = () => this.page.getByLabel('Email');
  passwordInput = () => this.page.getByLabel('Password');
  submitButton = () => this.page.getByRole('button', { name: 'Sign in' });

  async login(email: string, password: string) {
    await this.emailInput().fill(email);
    await this.passwordInput().fill(password);
    await this.submitButton().click();
  }
}

LoginPage 클래스 하나를 만들면 로그인 관련 테스트 전체가 이 클래스를 참조한다. 셀렉터가 바뀌면 클래스 파일 하나만 수정하면 된다. 테스트 20개를 한꺼번에 고치는 일이 없어진다.

pages/ 디렉토리를 만들고 페이지별로 클래스를 분리하는 게 관행이다. LoginPage, DashboardPage, CheckoutPage 식으로 나눈다. 테스트 파일이 클래스를 import해서 쓴다.

네트워크 인터셉트로 API 목킹하기

외부 API가 불안정하거나 에러 케이스를 재현할 때 쓴다. route.fulfill()은 실제 요청 대신 원하는 응답을 주입하는 가짜 서버다. 결제 실패, 서버 오류, 타임아웃을 코드로 만들어낼 수 있다.

// 성공 응답 목킹
await page.route('**/api/user', route => route.fulfill({
  status: 200,
  body: JSON.stringify({ name: 'Test User', plan: 'pro' })
}));

// 서버 에러 목킹
await page.route('**/api/payment', route => route.fulfill({
  status: 500,
  body: JSON.stringify({ error: 'Internal Server Error' })
}));

// 응답 지연 목킹 (로딩 스피너 검증 용도)
await page.route('**/api/data', async route => {
  await new Promise(r => setTimeout(r, 3000));
  await route.fulfill({ status: 200, body: 'ok' });
});

route.abort()로 요청 자체를 차단할 수도 있다. 네트워크가 끊겼을 때 UI가 어떻게 동작하는지 테스트된다. 에러 케이스는 실제 환경에서 재현하기 어려운데, 이 방법으로 100% 재현된다.

응답 지연도 시뮬레이션된다. 3초 딜레이를 주면 로딩 스피너가 제대로 표시되는지 검증된다. 느린 네트워크 상황에서 UX 버그를 사전에 잡을 수 있다.

네트워크 인터셉트 주요 메서드

- route.fulfill(): 원하는 응답 반환. 상태 코드, body, headers 지정 가능
- route.abort(): 요청 자체를 차단. 네트워크 오류 시뮬레이션
- route.continue(): 요청을 그대로 통과. 헤더 수정 후 원래 서버로 전달 가능
- page.waitForResponse(): 특정 응답이 올 때까지 대기. 비동기 요청 검증에 유용
Part 2: sections 6–9, FAQ, closing.

트레이스 뷰어와 디버깅

테스트가 실패했을 때 원인을 찾는 게 E2E의 가장 큰 고통이다. Playwright는 트레이스 뷰어를 기본 제공한다. 실패 시점의 DOM 스냅샷, 네트워크 요청, 콘솔 로그가 한 화면에 보인다.

// playwright.config.ts — 실패 시 트레이스 저장
export default defineConfig({
  use: {
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'on-first-retry',
  },
});

# 저장된 트레이스 파일 열기
npx playwright show-trace trace.zip

# 인터랙티브 디버깅 모드
npx playwright test --ui

트레이스는 CI에서도 아티팩트로 저장된다. GitHub Actions에서 실패한 테스트의 trace.zip을 다운로드해서 로컬에서 열면 된다. 실패 재현을 위해 다시 CI를 돌릴 필요가 없다.

--ui 옵션으로 인터랙티브 디버깅도 된다. 테스트를 시각적으로 실행하면서 각 단계를 확인할 수 있다. 개발 단계에서 테스트를 처음 작성할 때 특히 유용하다.

CI 병렬 실행 설정하기

GitHub Actions에서 --shard 옵션으로 테스트를 분할 실행한다. 추가 비용 없이 실행 시간을 N분의 1로 줄인다. 테스트 100개 기준 8분에서 2분으로 단축됐다.

# .github/workflows/playwright.yml
name: Playwright Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --shard=${{ matrix.shard }}/4
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ matrix.shard }}
          path: playwright-report/

matrix 전략으로 4개 job을 동시에 돌린다. 각 job이 --shard=1/4, 2/4, 3/4, 4/4를 나눠서 실행한다. 실패한 테스트의 trace.zip이 아티팩트로 자동 저장된다.

HTML 리포트도 자동 생성된다. 아티팩트로 저장해두면 실패한 테스트를 시각적으로 확인할 수 있다. Slack 알림과 연결하면 테스트 결과가 PR마다 자동으로 전달된다.

Playwright vs Cypress 비교

두 도구 모두 E2E 테스트의 현실적인 선택지다. 팀 상황과 요구사항에 따라 선택이 달라진다. 어느 쪽이 절대적으로 낫다고 단정 짓기 어렵다.

Cypress는 UI가 직관적이고 에러 메시지가 친절하다. 비개발자도 테스트를 작성하기 쉽다. 이미 Cypress로 테스트 스위트가 구축됐다면 굳이 옮길 필요는 없다.

가격 비교
항목PlaywrightCypress
기본 사용무료 (오픈소스)무료 (오픈소스)
병렬 실행무료 (--shard)Cloud 플랜 필요
CI 대시보드무료 (HTML 리포트)$67+/월
팀 규모 제한없음플랜별 상이

용도별 도구 선택 가이드

새 프로젝트라면 Playwright를 기본으로 잡는 게 맞다. 비용이 0원이고 멀티 브라우저가 기본 지원된다. 2026년 기준 생태계도 Playwright가 더 빠르게 성장 중이다.

상황추천이유
새 프로젝트 시작Playwright무료 병렬화, 멀티 브라우저 기본 지원
Cypress 기존 팀Cypress 유지마이그레이션 비용 > 전환 이득
Safari 버그 자주 발생PlaywrightWebKit 기반 Safari 테스트 가능
비개발자가 테스트 작성Cypress직관적인 UI, 친절한 에러 메시지
CI 비용 절감 필요Playwright병렬 실행 추가 비용 없음
점진적 전환 원할 때병행새 테스트는 Playwright, 기존은 Cypress 유지

Cypress를 쓰는 팀이라면 새 기능 테스트부터 Playwright로 작성하는 방법도 있다. 두 도구를 병행하다가 자연스럽게 이전하는 패턴이다. 강제 마이그레이션보다 현실적이다.

Playwright 시작 전 체크리스트

- Node.js 18 이상 확인
- playwright.config.ts에 baseURL 설정
- CI 환경에서는 retries: 2 설정 권장
- 테스트 20개 이상이면 pages/ 디렉토리 구조 도입 검토
- trace: 'on-first-retry'로 실패 추적 활성화
- GitHub Actions matrix로 --shard 병렬화 설정

FAQ

Q. Playwright와 Cypress 중 어느 것을 선택해야 하는가?

팀이 작고 CI 비용이 부담되면 Playwright가 낫다. 무료 병렬화와 멀티 브라우저 지원이 기본 제공된다. 이미 Cypress 스위트가 있다면 굳이 옮길 필요는 없다.

Q. Playwright는 Safari도 지원하는가?

지원한다. WebKit 엔진 기반으로 Safari 동작을 시뮬레이션한다. Cypress는 Chromium 계열만 지원한다. iOS 웹뷰 이슈가 잦은 프로젝트에서 특히 유용하다.

Q. CI에서 병렬 실행 비용이 드는가?

안 든다. --shard 옵션으로 GitHub Actions에서 무료로 병렬 실행된다. Cypress Cloud는 유료다. GitHub Actions 기본 무료 한도 내에서 runner 비용만 발생한다.

Q. 페이지 객체 패턴이 꼭 필요한가?

프로젝트가 커지면 필요하다. 셀렉터 하나가 바뀔 때 한 파일만 고치면 되도록 만드는 추상화 계층이다. 테스트가 20개 미만이면 도입 전에 규모를 보고 판단해도 늦지 않다.

Q. 기존 Jest 테스트와 함께 쓸 수 있는가?

쓸 수 있다. Jest는 유닛·통합 테스트, Playwright는 E2E 테스트로 역할을 나누면 된다. 두 도구가 충돌하지 않는다. package.json에 test:unit, test:e2e 스크립트를 분리하는 게 일반적이다.

Playwright는 2026년 E2E 테스트의 기본 선택지가 됐다. 설치가 간단하고, 무료로 병렬화되고, 멀티 브라우저가 기본이다. 새 프로젝트라면 처음부터 Playwright로 시작하면 된다.

Cypress에서 넘어올 이유는 충분하다. 당장 마이그레이션이 부담되면 새 테스트부터 Playwright로 쓰면서 점진적으로 전환하면 된다. 두 도구를 병행해도 문제없다.

공식 출처
- Playwright 공식 문서: https://playwright.dev
- State of JS 2025 Testing 섹션: https://stateofjs.com
- Playwright GitHub 저장소: https://github.com/microsoft/playwright
귀찮은개발자 시리즈
실제로 만들어가는 SaaS 개발 기록
Claude와 함께 FeedMission을 만든 과정. 보안 취약점, 결제 연동, SDK 개발까지.
EP.06 읽기 →
GoCodeLab 글이 도움됐다면
새 글이 올라오면 알림을 받을 수 있다.
GoCodeLab 구독하기

이 글의 수치와 기능 정보는 2026년 4월 기준이다. 각 도구의 최신 정보는 공식 문서를 확인한다.

Playwright는 Microsoft, Cypress는 Cypress.io의 오픈소스 프로젝트다.

공유하기
XLinkedInFacebook