프록시
.mock.jsonNext.js / TanStack Start SSR → 프록시 → 실제 API
서버가 API를 호출하는 풀스택 앱용.
서버와 API 사이에 위치합니다. 서버 측 요청(서버 컴포넌트와 SSR의 fetch, 라우트 핸들러, BFF(backend-for-frontend)가 호출하는 모든 것)을 기록합니다.
각 테스트의 서버 측 요청에는 그 테스트의 id가 붙으므로, 병렬로 실행되는 테스트끼리 기록을 공유하지 않습니다.
한 번 기록하세요.
영원히 재생하세요.
로컬에서 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
saved todos__adds-a-todo.mock.json
REPLAY
200 OK · 3 ms · from disk
한 번의 Playwright 실행으로 실제 응답을 디스크에 기록합니다. 재생으로 전환하면 동일한 스위트가 백엔드를 끈 상태로 통과합니다.
요청은 두 곳에서 시작되므로 기록 방식도 두 가지이며, 어느 방식에서든 프록시 프로세스가 실행됩니다. 프록시로 보낸 모든 요청(서버 측 fetch, 서버와 공유하는 API 기본 URL을 거치는 브라우저 호출, WebSocket)은 해당 테스트의 .mock.json에서 재생됩니다. 픽스처의 url 패턴과 일치하는, 다른 호스트로 가는 브라우저 호출은 해당 테스트의 .har에서 재생됩니다.
.mock.jsonNext.js / TanStack Start SSR → 프록시 → 실제 API
서버가 API를 호출하는 풀스택 앱용.
서버와 API 사이에 위치합니다. 서버 측 요청(서버 컴포넌트와 SSR의 fetch, 라우트 핸들러, BFF(backend-for-frontend)가 호출하는 모든 것)을 기록합니다.
각 테스트의 서버 측 요청에는 그 테스트의 id가 붙으므로, 병렬로 실행되는 테스트끼리 기록을 공유하지 않습니다.
.har브라우저 → HAR 가로채기 → 실제 API
SPA, 확장 프로그램, 브라우저 전용 앱용.
브라우저 자체에서 가로채 HTTP Archive(HAR) 파일로 저장합니다. 클라이언트 측 fetch 호출, Chrome 확장 프로그램 API 트래픽, 분석, 서드파티 API 중 픽스처의 url 패턴과 일치하는 것을 기록합니다.
routeFromHAR만 쓸 때와 달리, 테스트마다 HAR 파일을 하나씩 만들고 인증 헤더를 마스킹합니다.
npx playwright test -g "adds a todo" 명령으로 그 테스트만 다시 기록할 수 있습니다. 프록시는 한 번에 한 테스트만 기록하므로 워커 하나로 기록하세요. 재생은 병렬로 실행됩니다.x-test-rcrd-id 헤더를 붙여 보내고, registerProxyFetch() 함수가 이 헤더를 서버가 직접 보내는 fetch에도 복사합니다. 프록시는 이 헤더를 읽어, 테스트가 병렬로 재생될 때에도 각 테스트에 그 테스트의 파일로 응답합니다.{ "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 } ]}목킹 도구들은 각자 다른 작업에 뛰어납니다. 아래 조합(직접 작성한 목(mock) 없이 SSR, 브라우저, WebSocket 전반에서 실제 트래픽을 기록)이 다른 도구들이 남겨 둔 공백입니다.
| 기능 | test-proxy-recorder | Playwright routeFromHAR | MSW | Polly.js | playwright-network-cache | Mocky 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' });});AWS Cognito 예제 → TanStack Start의 Cognito → 목(mock) 인증(클라우드 계정 불필요) →
한 번의 명령으로 모든 것을 스캐폴딩하고, API를 프록시로 연결한 뒤 기록하고 커밋하세요. 브라우저 전용 앱인가요? init 이 SSR 단계를 대신 건너뜁니다.
registerProxyFetch() 함수를 한 번 호출합니다. 이 함수는 각 서버 측 fetch에 테스트 id를 복사하며, TEST_PROXY_RECORDER_ENABLED가 설정되지 않으면 프로덕션에서는 아무 동작도 하지 않습니다.이것을 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.init 은 프록시 설정, Playwright 픽스처, 전역 티어다운, package.json 스크립트를 작성하고, (Next.js의 경우) SSR fetch 태깅을 루트 레이아웃에 연결합니다. 비파괴적으로 동작합니다.
npm install --save-dev test-proxy-recorder# localhost:3002 is your API, 8100 is the proxy. Flags are optional.npx test-proxy-recorder init http://localhost:3002 --port 8100 --dir ./e2e/recordings init 이 추측할 수 없는 유일한 것은 바로 API 기본 URL을 담고 있는 환경 변수입니다. 레코더가 활성화되어 있으면 프록시를, 그렇지 않으면 실제 백엔드를 가리키세요. 프록시는 프로덕션에서 절대 실행되지 않습니다.
// The proxy during e2e runs, the real backend everywhere else.// TEST_PROXY_RECORDER_ENABLED is 'true' or '1' for the e2e run only.const recorderEnv = process.env.TEST_PROXY_RECORDER_ENABLED ?? '';const API_BASE = ['true', '1'].includes(recorderEnv) ? 'http://localhost:8100' // proxy address from `init` : 'https://api.example.com';
const res = await fetch(`${API_BASE}/todos`);Next.js의 경우 init 은 또한 루트 레이아웃에 registerProxyFetch() 를 추가하여 서버 측 fetch 호출에 태깅합니다. 프로덕션에서는 아무 동작도 하지 않습니다.
브라우저 전용 앱인가요? 앱이 WebSocket을 열지 않는다면 이 단계는 건너뛰세요. WebSocket은 프록시 주소로 연결할 때만 기록됩니다.
MODE = 'record'로 설정하고 실제 API를 대상으로 한 번 실행한 뒤, 'replay' 로 전환하고 커밋하세요. 기록은 git에 보관됩니다. 그것이 CI를 결정적으로 만드는 이유입니다. gitignore에 넣지 마세요.
import { test, expect } from '@playwright/test';import { playwrightProxy } from 'test-proxy-recorder';
// Full-stack: the browser also talks to the proxy, so match its URL.// Browser-only app? Match your real API domain, e.g. /api\.example\.com/.const CLIENT_SIDE_URL = /localhost:8100/;
// 'record' hits the real API and saves responses.// 'replay' serves them from disk — no network needed.const MODE = 'replay' as const;
test.beforeEach(async ({ page }, testInfo) => { await playwrightProxy.before(page, testInfo, MODE, { url: CLIENT_SIDE_URL });});
test('homepage loads', async ({ page }) => { await page.goto('/'); await expect(page.getByText('Welcome')).toBeVisible();});url 옵션으로 before() 함수에 넘기는 패턴과 일치하지만 HAR에 없는 호출을 Playwright가 중단하며, 그 밖의 호출은 네트워크로 나갑니다.teardown()을 호출하면 재생 모드를 벗어납니다. CI에서 실행 중인 백엔드가 없으면 그런 호출은 통과하지 못하고 실패합니다. 세션 시간 초과와 티어다운 →page.route() 핸들러를 before() 호출 뒤에 등록하면 기록보다 우선 적용됩니다. 서버 측 응답은 .mock.json 파일에서 수정합니다. 응답을 강제로 지정하기 →여러분의 API는 이미 올바른 답을 제공합니다. 기록하기만 하면 됩니다.
npm i -D test-proxy-recorder GitHub에서 스타오후 시간을 아끼셨다면, 스타는 1초면 충분합니다. 다음 사람이 이 도구를 발견하는 방법이자, 1인 메인테이너에게 계속 만들라는 신호가 됩니다. 막히는 부분이나 아이디어가 있으신가요? 이슈를 열거나 또는 Discord에 참여하세요.