본문으로 건너뛰기
Previous
Next
Playwright Component Testing 완벽 가이드 — 컴포넌트 단위 테스트

Playwright Component Testing 완벽 가이드 — 컴포넌트 단위 테스트

Playwright Component Testing 완벽 가이드 — 컴포넌트 단위 테스트

이 글의 핵심

Playwright Component Testing은 E2E와 단위 테스트 사이에서 컴포넌트를 실제 브라우저 엔진으로 마운트해 검증하는 방식입니다. 이 글에서는 프레임워크별 설정, 상호작용·스냅샷·MSW·CI까지 한 번에 정리합니다.

이 글의 핵심

Playwright Component Testing(CT)은 단일 컴포넌트를 실제 브라우저에서 마운트하고, Playwright의 Locator·Assertion으로 상태와 스타일까지 검증하는 테스트 방식입니다. 전체 앱을 띄우는 E2E보다 범위가 좁고, DOM만 흉내 내는 jsdom 기반 테스트보다 브라우저 동작에 가깝습니다.

이 글에서는 핵심 개념, React·Vue·Svelte별 설정, 마운트와 사용자 상호작용, 스냅샷·비주얼 회귀, MSW(Mock Service Worker), CI/CD, 그리고 Cypress CT와의 비교까지 실무 관점에서 정리합니다.


1. 왜 컴포넌트 테스트인가

프론트엔드 테스트는 보통 다음 층으로 나뉩니다.

구분대표 도구강점한계
단위·통합(가상 DOM)Vitest, Jest + Testing Library빠르고 피드백이 즉각적레이아웃·실제 CSS·브라우저 차이 반영이 어려움
컴포넌트(실제 브라우저)Playwright CT, Cypress CT픽셀·스크롤·포커스 등 브라우저 특성 반영E2E보다는 가볍지만 순수 단위보다 느림
E2EPlaywright, Cypress사용자 여정 전체 검증느리고 플레이키(flaky)에 민감

Playwright CT는 “한 화면의 한 덩어리”에 집중합니다. 예를 들어 폼 한 블록, 모달, 데이터 테이블 행 단위처럼 재사용 컴포넌트의 회귀를 막기에 적합합니다. 반면 라우팅·결제·권한 플로우 전체는 E2E가 여전히 필요합니다.

공식 문서에서도 컴포넌트 테스트는 실험적(experimental) 기능으로 명시되어 있으며, 시맨틱 버전을 엄격히 따르지 않을 수 있으므로 버전 고정·릴리즈 노트 확인이 중요합니다.


2. 핵심 개념

2.1 마운트(Mount)와 Locator

Playwright CT의 중심은 mount 픽스처입니다. 테스트 코드에서 JSX/Vue/Svelte 컴포넌트를 넘기면, 내부적으로 테스트용 HTML 셸(playwright/index.html의 루트)에 붙습니다. 반환값은 그 컴포넌트 루트를 가리키는 Locator에 가깝게 동작하여, click, fill, screenshot 등 기존 Playwright API를 그대로 씁니다.

즉, “컴포넌트 = 축소된 페이지”로 두고 E2E와 동일한 사고로 assertion을 구성할 수 있습니다.

2.2 진입 스크립트 playwright/index.*

번들러(Vite 등)로 컴포넌트 테스트 앱을 띄울 때, 전역 스타일·테마·i18n·MSW 같은 “앱 전체에서 한 번만 필요한 것”은 playwright/index.tsx(또는 index.ts)에서 주입합니다. 프로덕션 main.tsx와 최대한 동일하게 맞추면 환경 편차로 인한 헛실패를 줄일 수 있습니다.

2.3 제약 사항

문서에 따르면, mount에 복잡한 Node 객체·살아 있는 콜백을 그대로 넘기는 방식은 제한됩니다. 테스트에서는 직렬화 가능한 props간단한 이벤트 핸들러 위주로 설계하는 것이 안전합니다. 내부 상태가 복잡하면 래퍼 컴포넌트로 테스트 전용 진입점을 두는 패턴이 흔합니다.


3. 시작하기: 공통 흐름

프로젝트에 처음 붙일 때는 공식 스캐폴딩을 권장합니다.

npm init playwright@latest -- --ct

생성되는 핵심 파일은 대략 다음과 같습니다.

  • playwright/index.html#root(또는 문서가 요구하는 id)가 있는 엔트리 HTML
  • playwright/index.tsx — 전역 Provider, 스타일, MSW 등
  • playwright-ct.config.ts — CT 전용 Playwright 설정

실행 스크립트는 보통 package.jsontest-ct 형태로 추가됩니다.

npm run test-ct

이후 프레임워크에 맞는 패키지를 선택해 테스트 파일에서 import합니다.


4. React 설정

4.1 패키지

npm install -D @playwright/experimental-ct-react @playwright/test

4.2 테스트 예시

// Button.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './Button';

test('클릭 시 라벨이 바뀐다', async ({ mount }) => {
  const component = await mount(<Button label="전송" />);
  await component.getByRole('button', { name: '전송' }).click();
  await expect(component.getByRole('button')).toHaveText('완료');
});

설명: mount로 트리를 올린 뒤, 반환된 Locator 기준으로 역할·텍스트 위주의 쿼리를 쓰면 접근성과 안정성이 좋아집니다. E2E에서 권장하는 패턴과 동일합니다.

4.3 Vite 연동

Playwright는 컴포넌트 번들에 Vite를 사용합니다. 기존 앱의 vite.config를 그대로 재사용하지는 않을 수 있으므로, 공식 문서대로 playwright-ct.config.tsuse.ctViteConfig에 경로 별칭(@/ 등)·플러그인을 복사해 맞춥니다. 플러그인을 직접 나열하기 시작하면 프레임워크 플러그인(예: @vitejs/plugin-react)도 함께 선언해야 하는 점에 유의합니다.


5. Vue 설정

5.1 패키지

npm install -D @playwright/experimental-ct-vue @playwright/test

5.2 테스트 예시

// Counter.spec.ts
import { test, expect } from '@playwright/experimental-ct-vue';
import Counter from './Counter.vue';

test('증가 버튼', async ({ mount }) => {
  const component = await mount(Counter, {
    props: { initial: 0 },
  });
  await component.getByRole('button', { name: '증가' }).click();
  await expect(component.locator('[data-testid="count"]')).toHaveText('1');
});

설명: Vue는 mount의 두 번째 인자로 props·slots 등을 넘기는 패턴이 문서화되어 있습니다. SFC 단위로 끊어 테스트할 때는 props를 최소화하고, 스토어·라우터는 Provider 또는 테스트 더블로 치환하는 편이 디버깅에 유리합니다.


6. Svelte 설정

6.1 패키지

npm install -D @playwright/experimental-ct-svelte @playwright/test

6.2 테스트 예시

// Alert.spec.ts
import { test, expect } from '@playwright/experimental-ct-svelte';
import Alert from './Alert.svelte';

test('닫기 클릭 시 사라진다', async ({ mount }) => {
  const component = await mount(Alert, {
    props: { message: '저장됨' },
  });
  await expect(component.getByText('저장됨')).toBeVisible();
  await component.getByRole('button', { name: '닫기' }).click();
  await expect(component.getByText('저장됨')).toBeHidden();
});

설명: Svelte도 Vue와 유사하게 컴포넌트와 props를 넘깁니다. Svelte 5 룬 등 최신 문법을 쓸 경우 번들러·플러그인 버전을 프로덕션과 맞추는 것이 중요합니다.


7. 마운트와 상호작용

7.1 사용자 이벤트

click, dblclick, fill, press, hover, focus 등은 E2E와 동일합니다. 자동 대기 덕분에 타이밍 이슈를 수동 waitForTimeout으로 풀기보다, 역할·레이블 기반 Locator와 assertion을 조합하는 편이 유지보수에 낫습니다.

7.2 beforeMount로 주입

테스트마다 다른 Context·Provider·전역 스토어 초기값이 필요하면, 프레임워크별 beforeMount으로 래핑하는 방식을 사용합니다. 한 컴포넌트가 여러 테마를 지원한다면, 테스트 케이스별로 테마 Provider만 갈아끼우는 패턴이 깔끔합니다.

7.3 비동기 데이터

데이터 패칭 컴포넌트는 MSW로 응답을 고정하거나, props로 이미 받은 데이터만 렌더링하는 “프레젠테이션 컴포넌트”로 쪼개 CT 대상을 좁히는 전략이 효과적입니다. 그렇지 않으면 네트워크·캐시 타이밍 때문에 flaky가 생기기 쉽습니다.


8. 스냅샷과 비주얼 회귀 테스트

8.1 스크린샷 assertion

Playwright의 toHaveScreenshot()픽셀 단위 비교에 쓰입니다. 컴포넌트 단위에서는 해당 Locator 범위만 캡처해 노이즈를 줄입니다.

test('카드 비주얼 회귀', async ({ mount }) => {
  const component = await mount(<PricingCard plan="pro" />);
  await expect(component).toHaveScreenshot('pricing-pro.png');
});

설명: 최초 실행 시 기준 이미지가 생성되고, 이후 변경 시 diff가 납니다. OS·폰트·서브픽셀 차이가 있으므로 CI에서는 동일한 Docker 이미지·폰트 설치를 맞추거나, 허용 오차 옵션을 조정해야 합니다.

8.2 DOM 스냅샷

텍스트 구조만 보고 싶다면 스크린샷 대신 스냅샷 matcher(프로젝트에서 허용하는 경우)나, 접근성 트리 기반 assertion을 조합하기도 합니다. 다만 UI 라이브러리 업그레이드 시 스냅샷이 대량으로 깨질 수 있어 팀 규칙이 필요합니다.

8.3 운영 팁

  • 작은 컴포넌트에 남용하면 이미지 자산이 폭증합니다. 디자인 토큰이 바뀌면 광범위하게 영향을 받는 컴포넌트 위주로 쓰는 것이 좋습니다.
  • 스토리북(Storybook)과 병행할 때는 동일 컴포넌트·동일 상태를 기준으로 어디까지 CT로 할지 역할을 나눕니다.

9. MSW와 목 데이터

9.1 왜 MSW인가

실제 API를 두면 속도·안정성·데이터 민감도 문제가 생깁니다. MSW는 브라우저에서 요청을 가로채 고정된 JSON을 돌려주므로, 컴포넌트 테스트와 잘 맞습니다.

9.2 권장: router 픽스처와 MSW 핸들러

Playwright 문서에서는 컴포넌트 테스트에 router 픽스처를 두고, router.use(...)에 MSW http 핸들러를 넘기는 방식을 안내합니다. 프로덕션에서 쓰는 handlers 배열을 그대로 재사용하면 앱과 테스트의 목 계약이 일치하기 쉽습니다.

import { http, HttpResponse } from 'msw';
import { handlers } from '../src/mocks/handlers';

test.beforeEach(async ({ router }) => {
  await router.use(...handlers);
});

test('목 데이터로 렌더', async ({ mount }) => {
  const component = await mount(<UserCard userId="1" />);
  await expect(component.getByText('테스트')).toBeVisible();
});

test('한 번만 다른 응답', async ({ mount, router }) => {
  await router.use(
    http.get('/api/user', () => HttpResponse.json({ name: '에러 케이스' })),
  );
  const component = await mount(<UserCard userId="1" />);
  await expect(component.getByText('에러 케이스')).toBeVisible();
});

설명: test.beforeEach에서 공통 핸들러를 깔고, 개별 테스트에서만 router.use로 덮어쓰면 시나리오 분기가 명확합니다. MSW를 msw/browsersetupWorkerplaywright/index.tsx에서 시작하는 팀도 있으나, 공식 예시는 router 기반이 많으므로 도입 시 한 가지 방식으로 통일하는 것이 좋습니다.

9.3 beforeMount와 Provider·라우터

라우터·테마·Pinia 등 마운트 전 래핑playwright/index.tsx에서 @playwright/experimental-ct-react/hooksbeforeMount로 처리합니다. hooksConfig를 타입으로 두고 mount의 두 번째 인자로 넘기면, 테스트마다 스토어 초기값 등을 바꿀 수 있습니다.

// playwright/index.tsx (개념 예시)
import { beforeMount } from '@playwright/experimental-ct-react/hooks';
import { BrowserRouter } from 'react-router-dom';

export type HooksConfig = { enableRouting?: boolean };

beforeMount<HooksConfig>(async ({ App, hooksConfig }) => {
  if (hooksConfig?.enableRouting) {
    return (
      <BrowserRouter>
        <App />
      </BrowserRouter>
    );
  }
});

9.4 Playwright page.route와의 선택

page.route()·router.route()로 응답을 직접 조작할 수도 있습니다. MSW 핸들러는 앱·스토리북과 공유하기 좋고, 인라인 route는 빠른 실험에 유리합니다. 팀 표준을 하나로 정하면 혼선이 줄어듭니다.


10. CI/CD 통합

10.1 기본 명령

CI에서는 보통 헤드리스 모드로 실행합니다.

npx playwright test --reporter=html

컴포넌트 테스트 전용 스크립트가 있다면 그것을 호출합니다. 리포트·트레이스 아티팩트를 저장하면 실패 시 원인 파악이 빨라집니다.

10.2 브라우저 설치

CI 이미지에 Playwright 브라우저 바이너리가 없으면 설치 단계가 필요합니다.

npx playwright install --with-deps chromium

리눅스에서는 시스템 의존성 누락으로 실패하는 경우가 많아 --with-deps가 문서에서 자주 권장됩니다.

10.3 병렬·샤딩

Playwright는 워커 병렬 실행이 강점입니다. 대규모 스위트는 --shard로 잡을 나눠 여러 CI 워커에서 돌리는 방식을 쓸 수 있습니다. 컴포넌트 테스트가 수천 개로 불어나면 스토리 단위 분할과 함께 검토합니다.

10.4 스냅샷 승인 워크플로

디자인 변경이 잦다면 스냅샷 업데이트 PR을 별도로 두고, 시각적 diff 리뷰를 디자이너와 공유하는 프로세스가 안전합니다.


11. Cypress vs Playwright Component Testing

항목Cypress CTPlaywright CT
API 일관성Cypress 전용 체인E2E와 동일한 Playwright API
멀티 브라우저제한적(주로 Chromium 중심 구성)Chromium/WebKit/Firefox 등 선택 용이
생태계·레퍼런스오래됨, 예제 많음상대적으로 신규, 실험적 표기
디버깅타임 트래블 등 독자 UXTrace Viewer·Codegen 등 Playwright 도구
학습 비용Cypress만 익히면 됨E2E를 이미 Playwright로 쓰면 이점 큼

정리: 이미 Playwright로 E2E를 운영 중이면 도구·설정·리포트 통합 측면에서 CT까지 Playwright로 가져가기 쉽습니다. 반면 Cypress에 깊이 최적화된 팀이라면 Cypress CT가 학습 곡선이 완만할 수 있습니다. 결정은 기술 스택 단일화 vs 기존 자산 트레이드오프로 보는 것이 현실적입니다.


12. 트러블슈팅 요약

  • import 경로/별칭 오류: playwright-ct.config.ts의 Vite 설정과 tsconfig paths를 동기화합니다.
  • 스타일이 비어 있음: 글로벌 CSS·테마 Provider가 playwright/index.tsx에 빠졌는지 확인합니다.
  • 폰트·픽셀 diff: CI와 로컬의 폰트 렌더링 차이를 의심하고, 허용치·Docker 기준을 통일합니다.
  • MSW 미적용 요청: 핸들러 누락·base URL 불일치·HTTPS 혼용을 점검합니다.

심화: CT 번들링·격리·커버리지·운영

마운트와 Vite 번들

CT 러너는 테스트와 컴포넌트를 Vite로 묶어 브라우저에 주입합니다. playwright-ct.config.tsctViteConfig로 별칭·플러그인을 맞추지 않으면 프로덕션과 다른 해석이 나와 헛실패가 납니다. mount에 넘기는 props는 직렬화 가능해야 하며, 복잡한 Node 객체는 래퍼 컴포넌트로 분리하는 편이 안전합니다.

격리와 MSW

BrowserContext 수준 격리는 E2E와 유사하게 스토리지·쿠키를 나눌 수 있습니다. MSW는 router 픽스처나 playwright/index.tsx에서 한 방식으로 표준화하는 것이 유지보수에 유리합니다.

커버리지

CT는 브라우저 번들 기준이라 단위 테스트(Vitest)와 커버리지 숫자를 합산하기 까다롭습니다. 디자인 시스템에서는 스크린샷·행동 단언을 우선하고, 비즈니스 로직 커버리지는 Node 테스트에 두는 경우가 많습니다.

프로덕션

CT는 배포 전 CI 용도이며, 실제 트래픽 경로를 대체하지 않습니다. 스테이징 E2E·모니터링과 역할을 나눕니다. 영문 요약은 [Playwright Component Testing Guide (English)](/en/blog/playwright-component-testing-guide/를 참고하십시오.


13. 맺음말

Playwright Component Testing은 실제 브라우저에서 컴포넌트 계약을 검증하는 강력한 도구입니다. 다만 실험적 기능이라 버전 정책을 주시하고, 전역 진입 스크립트·MSW·CI 환경을 프로덕션에 가깝게 맞추는 것이 성공적인 도입의 열쇠입니다. E2E·단위 테스트와 역할을 나누고, 팀에 맞는 비주얼·네트워크 목 전략을 문서화해 두면 유지보수 부담을 크게 줄일 수 있습니다.


영문 가이드

  • [Playwright Component Testing Guide (English)](/en/blog/playwright-component-testing-guide/

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「Playwright Component Testing 완벽 가이드 — 컴포넌트 단위 테스트」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「Playwright Component Testing 완벽 가이드 — 컴포넌트 단위 테스트」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Playwright 컴포넌트 테스트로 UI를 브라우저에서 검증하는 방법. React·Vue·Svelte 설정, 마운트·비주얼 회귀·MSW·CI/CD·Cypress 비교까지 정리합니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

Playwright, Component Testing, E2E, Visual Testing, React 등으로 검색하시면 이 글이 도움이 됩니다.