Playwright 테스트의 API 호출을 서버 측 요청까지 기록하고 재생합니다

한 번 기록하세요.
영원히 재생하세요.

로컬에서 Playwright를 실행하는 동안 앱이 받는 API 응답을 WebSocket까지 포함해 기록한 다음, 백엔드를 끈 상태로 CI에서 재생합니다.

npm i -D test-proxy-recorder GitHub에서 스타

v2.0.1 · MIT · TypeScript · Node ≥ 20 · Next.js 및 TanStack Start SSR, SPA, Chrome 확장 프로그램, WebSocket

REC · MODE=record · local run

saved todos__adds-a-todo.mock.json

REPLAY · MODE=replay · CI

200 OK · 3 ms · from disk

기록하고 재생하는 모습을 확인하세요

한 번의 Playwright 실행으로 실제 응답을 디스크에 기록합니다. 재생으로 전환하면 동일한 스위트가 백엔드를 끈 상태로 통과합니다.

두 가지 기록 방식

요청은 두 곳에서 시작되므로 기록 방식도 두 가지이며, 어느 방식에서든 프록시 프로세스가 실행됩니다. 프록시로 보낸 모든 요청(서버 측 fetch, 서버와 공유하는 API 기본 URL을 거치는 브라우저 호출, WebSocket)은 해당 테스트의 .mock.json에서 재생됩니다. 픽스처의 url 패턴과 일치하는, 다른 호스트로 가는 브라우저 호출은 해당 테스트의 .har에서 재생됩니다.

프록시

.mock.json

Next.js / TanStack Start SSR → 프록시 → 실제 API

서버가 API를 호출하는 풀스택 앱용.

서버와 API 사이에 위치합니다. 서버 측 요청(서버 컴포넌트와 SSR의 fetch, 라우트 핸들러, BFF(backend-for-frontend)가 호출하는 모든 것)을 기록합니다.

각 테스트의 서버 측 요청에는 그 테스트의 id가 붙으므로, 병렬로 실행되는 테스트끼리 기록을 공유하지 않습니다.

HAR

.har

브라우저 → HAR 가로채기 → 실제 API

SPA, 확장 프로그램, 브라우저 전용 앱용.

브라우저 자체에서 가로채 HTTP Archive(HAR) 파일로 저장합니다. 클라이언트 측 fetch 호출, Chrome 확장 프로그램 API 트래픽, 분석, 서드파티 API 중 픽스처의 url 패턴과 일치하는 것을 기록합니다.

routeFromHAR만 쓸 때와 달리, 테스트마다 HAR 파일을 하나씩 만들고 인증 헤더를 마스킹합니다.

기록 파일은 이렇게 생겼습니다

테스트당 파일 하나
각 테스트는 테스트 이름을 딴 파일을 각자 작성하므로, npx playwright test -g "adds a todo" 명령으로 그 테스트만 다시 기록할 수 있습니다. 프록시는 한 번에 한 테스트만 기록하므로 워커 하나로 기록하세요. 재생은 병렬로 실행됩니다.
테스트 id
Playwright는 테스트가 보내는 모든 요청에 x-test-rcrd-id 헤더를 붙여 보내고, registerProxyFetch() 함수가 이 헤더를 서버가 직접 보내는 fetch에도 복사합니다. 프록시는 이 헤더를 읽어, 테스트가 병렬로 재생될 때에도 각 테스트에 그 테스트의 파일로 응답합니다.
비밀 정보
Authorization, Cookie, Set-Cookie 헤더는 .mock.json 파일에서는 파일을 쓰기 전에, .har 파일에서는 실행이 끝날 때 [REDACTED]로 바뀝니다. 응답 본문 안의 비밀 정보는 직접 패턴을 추가해야 합니다. 비밀 정보 마스킹 →
e2e/recordings/todos__adds-a-todo.mock.json
{
"id": "todos__adds-a-todo",
"recordings": [
{
"request": {
"method": "POST",
"url": "/todos",
"headers": {
"authorization": "[REDACTED]",
"x-test-rcrd-id": "todos__adds-a-todo"
},
"body": "{\"title\":\"Buy groceries\"}"
},
"response": {
"statusCode": 201,
"body": "{\"id\":7,\"title\":\"Buy groceries\",\"done\":false}"
},
"key": "POST_todos.json",
"sequence": 0
}
]
}
실제 기록의 일부입니다(전체 크기 2.6 KB). 한 테스트의 POST 요청으로, Authorization 헤더가 마스킹되어 있습니다. 전체 파일에는 그 뒤에 이어진 GET 요청, 모든 헤더, 요청마다의 타임스탬프도 들어 있습니다.

어디에 적합한가

목킹 도구들은 각자 다른 작업에 뛰어납니다. 아래 조합(직접 작성한 목(mock) 없이 SSR, 브라우저, WebSocket 전반에서 실제 트래픽을 기록)이 다른 도구들이 남겨 둔 공백입니다.

test-proxy-recorder와 Playwright routeFromHAR, MSW, Polly.js, playwright-network-cache, Mocky Balboa의 기능 비교.
기능test-proxy-recorderPlaywright routeFromHARMSWPolly.jsplaywright-network-cacheMocky Balboa
실제 트래픽 기록예예아니요예예아니요
서버 측(SSR)예아니요예부분아니요예
브라우저 측예예예예예예
WebSocket예아니요예아니요아니요아니요
Playwright 네이티브예예예아니요예예
유지 관리됨예예예아니요예예

Polly.js는 자신이 실행되는 프로세스 안에서 기록하므로, 서버 측 요청을 기록하려면 앱 서버 안에서 실행해야 합니다. MSW와 Mocky Balboa도 서버 측 요청을 모킹하지만, 응답은 직접 작성해야 합니다. 출처와 다른 도구를 선택해야 하는 경우까지 담은 전체 비교는 문서에서 다룹니다.

대신 처음 설정은 브라우저 전용 도구보다 손이 더 갑니다. 테스트 옆에서 프록시 프로세스가 실행되고, 서버 측 요청을 위해서는 테스트 실행 중에 앱의 API 기본 URL이 프록시를 가리켜야 합니다.

실제 인증 공급자와 함께 동작

Cognito, Auth0, Clerk 또는 WorkOS를 통해 실제로, 매 실행마다 로그인하세요. 앱의 API만 기록되며, 인증은 계속 실시간으로 동작하고 데이터는 오프라인으로 재생됩니다.

// e2e/auth.setup.ts — log in for real, once. Never recorded.
import { test as setup } from '@playwright/test';
import { setProxyMode } from 'test-proxy-recorder';
setup('authenticate', async ({ page }) => {
await setProxyMode('transparent'); // login bypasses the recorder
await page.goto('/login');
await page.getByTestId('email').fill(process.env.TEST_EMAIL!);
await page.getByTestId('password').fill(process.env.TEST_PASSWORD!);
await page.getByTestId('signinButton').click();
await page.waitForURL('/dashboard');
// Reused by every test — they start already signed in.
await page.context().storageState({ path: 'e2e/.auth/state.json' });
});

빠른 시작

한 번의 명령으로 모든 것을 스캐폴딩하고, API를 프록시로 연결한 뒤 기록하고 커밋하세요. 브라우저 전용 앱인가요? init 이 SSR 단계를 대신 건너뜁니다.

저장소에서 바뀌는 것

  • 테스트 스위트에는 Playwright 픽스처, 프록시 프로세스(테스트 중에만 실행됩니다), git에 커밋하는 기록이 생깁니다.
  • 앱은 서버 측 요청을 기록할 때만 바뀝니다. 테스트 실행 중에는 API 기본 URL이 프록시를 가리키게 하고, Next.js라면 app/layout.tsx에서, TanStack Start라면 src/router.tsx에서 registerProxyFetch() 함수를 한 번 호출합니다. 이 함수는 각 서버 측 fetch에 테스트 id를 복사하며, TEST_PROXY_RECORDER_ENABLED가 설정되지 않으면 프로덕션에서는 아무 동작도 하지 않습니다.
  • 에이전트 프롬프트를 쓰면 @tanstack/intent가 CLAUDE.md 같은 에이전트 설정 파일에 이 라이브러리의 스킬을 불러오는 방법에 대한 안내를 추가합니다.
  • 배포된 환경을 테스트하나요? HAR은 앱을 바꾸지 않고도 그 환경의 브라우저 호출을 기록하지만, 페이지 자체는 여전히 그 서버에서 불러옵니다. 서버 측 호출까지 재생하려면 CI에서 프록시 옆에 앱을 실행하세요.

이것을 Claude Code, Cursor 또는 다른 코딩 에이전트에 붙여넣으세요. 에이전트는 먼저 @tanstack/intent를 실행하는데, 이 도구는 CLAUDE.md 같은 에이전트 설정 파일에 안내를 추가해 에이전트가 이 라이브러리의 설정 스킬을 불러오게 합니다. 그다음 패키지를 설치하고, 앱 설정에서 백엔드 URL을 찾고, init 명령을 실행한 뒤 그 출력에 따라 연결 작업을 마무리합니다.

Set up test-proxy-recorder for end-to-end tests in this project, then follow the instructions that `init` prints. Run these commands:
npx @tanstack/intent@latest install
npm install --save-dev test-proxy-recorder
Then run init, passing this project's backend API base URL as the target — find it yourself from the app's env/config (the URL the app calls in dev); don't assume the default:
npx test-proxy-recorder init <your-backend-api-url> --port 8100 --dir ./e2e/recordings
Then complete the app-specific steps init prints: point the app's API base URL at the proxy in dev/test only, tag server-side fetches (Next.js), add a smoke test, and verify record → replay.

전체 설정 가이드 →

재생에 관한 질문

요청은 어떻게 매칭되나요?
프록시는 서버 측 호출을 본문이 아니라 메서드, 경로, 정확한 쿼리 문자열로 매칭하고, 반복되는 호출에는 기록된 순서대로 응답합니다. 브라우저 호출은 POST 본문도 비교하는 Playwright의 HAR 규칙을 따릅니다. 타임스탬프처럼 실행할 때마다 바뀌는 쿼리 문자열은 결코 일치하지 않으며, 파라미터를 무시하는 옵션도 없으므로 그런 값은 테스트 중에 고정해 두세요. 재생이 요청을 매칭하는 방식 →
기록이 없으면 어떻게 되나요?
프록시는 요청과 테스트를 명시한 404로 응답합니다. 기록된 횟수보다 더 많이 한 호출은 마지막 응답을 다시 받고, 프록시 로그에 경고가 남습니다. 브라우저에서는 픽스처가 url 옵션으로 before() 함수에 넘기는 패턴과 일치하지만 HAR에 없는 호출을 Playwright가 중단하며, 그 밖의 호출은 네트워크로 나갑니다.
재생은 언제 실제 API에 도달하나요?
재생이 실제 API에 도달하는 것은 프록시가 재생 모드를 벗어난 뒤뿐이며, 프록시는 실행 중인 모든 테스트에 하나의 모드를 유지합니다. 프록시는 마지막 테스트가 시작되고 120초가 지나거나, 테스트별 훅이 teardown()을 호출하면 재생 모드를 벗어납니다. CI에서 실행 중인 백엔드가 없으면 그런 호출은 통과하지 못하고 실패합니다. 세션 시간 초과와 티어다운 →
API가 바뀌면 어떻게 되나요?
타입이 지정된 API 클라이언트를 쓴다면, 바뀐 필드에 맞춰 클라이언트를 갱신할 때 그 필드를 쓰는 재생 테스트가 실패합니다. 기록에는 여전히 이전 필드가 남아 있기 때문입니다. 이 실패가 변경 지점을 알려 주며, 다시 기록하면 직접 작성한 목(mock)을 고치는 것보다 적은 수고로 테스트를 고칠 수 있습니다.
오류와 엣지 케이스는 어떻게 테스트하나요?
브라우저에서는 page.route() 핸들러를 before() 호출 뒤에 등록하면 기록보다 우선 적용됩니다. 서버 측 응답은 .mock.json 파일에서 수정합니다. 응답을 강제로 지정하기 →

직접 목(mock) 작성은 이제 그만

여러분의 API는 이미 올바른 답을 제공합니다. 기록하기만 하면 됩니다.

npm i -D test-proxy-recorder GitHub에서 스타

오후 시간을 아끼셨다면, 스타는 1초면 충분합니다. 다음 사람이 이 도구를 발견하는 방법이자, 1인 메인테이너에게 계속 만들라는 신호가 됩니다. 막히는 부분이나 아이디어가 있으신가요? 이슈를 열거나 또는 Discord에 참여하세요.