Playwright vs Cypress — 2025년 E2E 테스트 도구 비교
나는 Cypress를 3년 넘게 사용했다. 그리고 작년에 Playwright로 전환했다. 멀티브라우저 지원이 결정적이었는데, 이번 글에서는 두 도구의 차이점을 상세히 분석하겠다.
아키텍처의 근본적인 차이
Cypress와 Playwright의 가장 중요한 차이는 아키텍처다. 이것이 성능, 안정성, 기능성에 모두 영향을 미친다.
Cypress: In-browser 방식
Cypress는 테스트 코드가 브라우저 내부에서 실행된다. 즉, 애플리케이션과 같은 프로세스에서 동작한다는 뜻이다.
// Cypress는 브라우저 내부에서 이 코드를 실행
describe('Login Flow', () => {
it('should login successfully', () => {
cy.visit('https://app.example.com/login');
cy.get('input[name=email]').type('user@example.com');
cy.get('input[name=password]').type('password123');
cy.get('button[type=submit]').click();
cy.url().should('include', '/dashboard');
});
});
장점은 애플리케이션의 내부 상태에 직접 접근할 수 있다는 것이다. Redux store, Vue state 등에 접근 가능하다.
Playwright: Out-of-process 방식
Playwright는 테스트 코드가 Node.js 프로세스에서 실행되고, 브라우저와는 웹소켓으로 통신한다.
// Playwright는 Node.js에서 이 코드를 실행하고 브라우저에 명령을 내림
import { test, expect } from '@playwright/test';
test('should login successfully', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.fill('input[name=email]', 'user@example.com');
await page.fill('input[name=password]', 'password123');
await page.click('button[type=submit]');
await expect(page).toHaveURL(/.*dashboard/);
});
장점은 브라우저와 완전히 분리되어 있다는 것이다. 테스트가 안정적이고, 여러 탭을 동시에 제어할 수 있다.
브라우저 지원
이것이 내가 Playwright로 전환한 주된 이유다.
| 기능 | Cypress | Playwright |
|---|---|---|
| Chrome/Edge | ✅ | ✅ |
| Firefox | ✅ | ✅ |
| Safari/WebKit | ❌ | ✅ |
| Mobile Chrome | ❌ | ✅ |
| Mobile Safari | ❌ | ✅ |
Cypress는 Safari를 지원하지 않는다. 이는 iOS 테스트를 할 수 없다는 의미다. 반면 Playwright는 Safari의 렌더링 엔진인 WebKit을 완벽히 지원한다.
// Playwright에서 여러 브라우저 테스트
import { test, expect } from '@playwright/test';
test.describe('Cross-browser tests', () => {
test('Chrome', async ({ page }) => {
// Chromium에서만 실행
});
test('Firefox', async ({ page }) => {
// Firefox에서만 실행
});
test('Safari', async ({ page }) => {
// WebKit에서만 실행
});
test('Mobile', async ({ page }) => {
// 모바일 브라우저 에뮬레이션
await page.setViewportSize({ width: 375, height: 812 });
});
});
속도 벤치마크
간단한 로그인 플로우를 여러 번 실행하는 테스트로 비교해보자.
// 100회 반복 테스트
// Cypress: 약 180초 (테스트당 1.8초)
// Playwright: 약 90초 (테스트당 0.9초)
Playwright가 약 2배 빠르다. 이유는 out-of-process 아키텍처 덕분에 병렬 실행이 더 효율적이기 때문이다. Cypress는 동시에 하나의 브라우저만 제어할 수 있지만, Playwright는 여러 브라우저를 동시에 제어할 수 있다.
// Playwright 병렬 실행
import { test, expect } from '@playwright/test';
test.describe.configure({ mode: 'parallel' });
test('test 1', async ({ page }) => {
// 동시 실행
});
test('test 2', async ({ page }) => {
// 동시 실행
});
test('test 3', async ({ page }) => {
// 동시 실행
});
디버깅 경험
Cypress - Time Travel Debugger
Cypress는 각 명령어 실행 시점의 화면을 스냅샷으로 저장한다. 테스트 결과에서 각 단계를 클릭하면 그 시점의 화면과 DOM을 볼 수 있다.
// Cypress는 이 각 명령어마다 스냅샷을 저장
cy.visit('https://app.example.com');
cy.get('input').type('data'); // 스냅샷 1
cy.get('button').click(); // 스냅샷 2
cy.get('.success-message'); // 스냅샷 3
Playwright - Trace Viewer
Playwright는 전체 테스트 실행을 기록했다가 나중에 재생할 수 있다. 마치 영화를 돌려보는 것처럼 테스트를 추적할 수 있다.
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry',
},
});
// 실행
npx playwright test --trace on
// 보기
npx playwright show-trace trace.zip
또한 Playwright는 inspect 모드에서 요소를 클릭하면 자동으로 선택자를 생성해준다.
npx playwright codegen https://app.example.com
이 명령어를 실행하면 브라우저가 열리고, 내가 클릭하는 모든 행동이 자동으로 코드로 변환된다.
CI 통합
둘 다 GitHub Actions, GitLab CI 등과 잘 통합된다.
# GitHub Actions with Cypress
name: Cypress Tests
on: [push]
jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: cypress-io/github-action@v4
with:
start: npm run dev
# GitHub Actions with Playwright
name: Playwright Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run dev &
- run: npx playwright test
컴포넌트 테스트
최근 두 도구 모두 컴포넌트 테스트를 지원하기 시작했다.
// Cypress Component Test
import { mount } from 'cypress/react';
import LoginForm from './LoginForm';
describe('LoginForm Component', () => {
it('renders correctly', () => {
mount( );
cy.get('input[name=email]').should('be.visible');
});
});
// Playwright Component Test
import { test, expect } from '@playwright/experimental-ct-react';
import LoginForm from './LoginForm';
test('renders correctly', async ({ mount }) => {
const component = await mount( );
await expect(component.locator('input[name=email]')).toBeVisible();
});
API 테스트
Playwright는 API 테스트 기능이 더 강력하다.
// Playwright API 테스트
import { test, expect } from '@playwright/test';
test('API test', async ({ request }) => {
const response = await request.post('https://api.example.com/login', {
data: {
email: 'user@example.com',
password: 'password'
}
});
expect(response.status()).toBe(200);
const json = await response.json();
expect(json.token).toBeTruthy();
});
결론
Cypress를 3년 사용한 나의 평가는 이렇다:
- Cypress의 장점: 학습곡선이 낮고, 개발자 경험이 직관적이며, 디버깅이 쉽다.
- Cypress의 단점: Safari 미지원, 병렬 실행이 비효율적, 아키텍처의 한계가 있다.
- Playwright의 장점: 모든 브라우저 지원, 병렬 실행 효율적, API 테스트 기능이 좋다.
- Playwright의 단점: 초기 학습곡선이 조금 가파르고, 설정이 더 복잡하다.
2025년 기준, Safari를 테스트해야 한다면 Playwright 일택이다. 그렇지 않더라도 성능과 확장성을 고려하면 Playwright를 추천한다.