Ghi lại và phát lại lời gọi API trong test Playwright, kể cả request phía máy chủ

Ghi lại một lần.
Phát lại mãi mãi.

Ghi lại các response API mà ứng dụng của bạn nhận được trong một lần chạy Playwright cục bộ, kể cả WebSocket, rồi phát lại chúng trên CI với backend đã tắt.

npm i -D test-proxy-recorder Star trên GitHub

v2.0.1 · MIT · TypeScript · Node ≥ 20 · SSR của Next.js & TanStack Start, SPA, extension Chrome, WebSocket

REC · MODE=record · local run

saved todos__adds-a-todo.mock.json

REPLAY · MODE=replay · CI

200 OK · 3 ms · from disk

Xem nó ghi lại, rồi phát lại

Một lần chạy Playwright ghi các response thật ra đĩa; lật sang phát lại và cùng bộ test đó pass với backend đã tắt.

Hai cách ghi lại

Request xuất phát từ hai nơi, nên có hai cách ghi lại chúng, và tiến trình proxy chạy trong cả hai trường hợp. Mọi thứ gửi tới proxy (fetch phía máy chủ, lời gọi trình duyệt qua một base URL API dùng chung với máy chủ, WebSocket) được phát lại từ file .mock.json của test. Lời gọi trình duyệt tới các host khác khớp với pattern url trong fixture của bạn được phát lại từ file .har của test.

Proxy

.mock.json

SSR của Next.js / TanStack Start → proxy → API thật

Cho các ứng dụng full-stack nơi máy chủ gọi API.

Nằm giữa máy chủ của bạn và API. Ghi lại các request phía máy chủ: fetch từ Server Component và SSR, route handler, bất cứ thứ gì backend-for-frontend của bạn gọi.

Các request phía máy chủ của mỗi test mang theo id của test đó, nên các test chạy song song không bao giờ dùng chung một bản ghi.

HAR

.har

trình duyệt → HAR intercept → API thật

Cho SPA, extension, và các ứng dụng chỉ chạy trên trình duyệt.

Chặn ngay trong trình duyệt và lưu một file HTTP Archive (HAR). Ghi lại các lời gọi fetch phía client, traffic API của extension Chrome, analytics và API bên thứ ba, với điều kiện chúng khớp với pattern url trong fixture của bạn.

So với routeFromHAR thuần, nó thêm một file HAR cho mỗi test và loại bỏ bí mật khỏi các header auth.

Một bản ghi trông như thế nào

Mỗi test một file
Mỗi test ghi ra file riêng, đặt tên theo test, nên npx playwright test -g "adds a todo" chỉ ghi lại lần nữa đúng test đó. Proxy ghi lại từng test một, nên hãy ghi lại với một worker; còn phát lại thì chạy song song.
Id của test
Playwright gửi x-test-rcrd-id kèm mọi request mà một test thực hiện, và registerProxyFetch() sao chép nó sang các fetch của chính máy chủ bạn. Proxy đọc nó để trả lời mỗi test từ file riêng của test đó, ngay cả khi các test phát lại song song.
Bí mật
Các header Authorization, Cookie và Set-Cookie trở thành [REDACTED] trong file .mock.json trước khi file được ghi ra đĩa, và trong file .har khi lần chạy kết thúc. Bí mật nằm bên trong body của response cần pattern do bạn tự thêm. Loại bỏ bí mật →
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
}
]
}
Một phần của một bản ghi thật, đầy đủ là 2.6 KB: request POST từ một test, với giá trị header Authorization đã được loại bỏ. Toàn bộ file còn chứa request GET theo sau, mọi header, và một dấu thời gian cho mỗi request.

Vị trí của nó

Các công cụ mocking giỏi ở những việc khác nhau. Sự kết hợp bên dưới — ghi lại traffic thật trên cả SSR, trình duyệt, và WebSockets, không cần mock viết tay — chính là khoảng trống các công cụ khác để lại.

So sánh tính năng giữa test-proxy-recorder với Playwright routeFromHAR, MSW, Polly.js, playwright-network-cache, và Mocky Balboa.
Tính năngtest-proxy-recorderPlaywright routeFromHARMSWPolly.jsplaywright-network-cacheMocky Balboa
Ghi lại traffic thậtCóCóKhôngCóCóKhông
Phía máy chủ (SSR)CóKhôngCóMột phầnKhôngCó
Phía trình duyệtCóCóCóCóCóCó
WebSocketCóKhôngCóKhôngKhôngKhông
Thuần PlaywrightCóCóCóKhôngCóCó
Được bảo trìCóCóCóKhôngCóCó

Polly.js ghi lại bên trong tiến trình mà nó chạy, nên với các request phía máy chủ, nó phải chạy bên trong máy chủ ứng dụng của bạn. MSW và Mocky Balboa cũng mock các request phía máy chủ, nhưng bạn phải tự viết các response. Bản so sánh đầy đủ, kèm nguồn và khi nào nên chọn công cụ khác, nằm trong tài liệu.

Đổi lại, lần thiết lập đầu tiên phức tạp hơn so với các công cụ chỉ chạy trên trình duyệt: một tiến trình proxy chạy cạnh các test của bạn, và với các request phía máy chủ, base URL API của ứng dụng trỏ về proxy trong lúc chạy test.

Hoạt động với auth provider thật của bạn

Đăng nhập qua Cognito, Auth0, Clerk, hoặc WorkOS — thật sự, trong mỗi lần chạy. Chỉ API của ứng dụng bạn được ghi lại; auth vẫn trực tiếp, dữ liệu của bạn chuyển sang offline.

// 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' });
});

Bắt đầu nhanh

Dựng mọi thứ bằng một lệnh, trỏ API của bạn về proxy, rồi ghi lại và commit. Ứng dụng chỉ chạy trên trình duyệt? init sẽ bỏ qua bước SSR giúp bạn.

Những gì thay đổi trong repo của bạn

  • Trong bộ test của bạn: một Playwright fixture, tiến trình proxy (chỉ chạy trong lúc test), và các bản ghi được commit vào git.
  • Trong ứng dụng của bạn, chỉ cho các request phía máy chủ: trỏ base URL API về proxy trong lúc chạy test, và gọi registerProxyFetch() một lần, trong app/layout.tsx trên Next.js hoặc src/router.tsx trên TanStack Start. Nó sao chép id của test vào mỗi fetch phía máy chủ và không làm gì trong production trừ khi TEST_PROXY_RECORDER_ENABLED được đặt.
  • Với prompt cho agent: @tanstack/intent thêm hướng dẫn nạp các skill của thư viện này vào file cấu hình của agent, chẳng hạn CLAUDE.md.
  • Đang test một môi trường đã deploy? HAR ghi lại các lời gọi trình duyệt của nó mà không cần thay đổi ứng dụng, nhưng bản thân các trang vẫn được tải từ máy chủ đó. Để phát lại cả các lời gọi phía máy chủ, hãy chạy ứng dụng trên CI cạnh proxy.

Dán đoạn này vào Claude Code, Cursor hoặc một coding agent khác. Trước tiên nó chạy @tanstack/intent, công cụ thêm hướng dẫn vào file cấu hình của agent, chẳng hạn CLAUDE.md, để agent nạp các skill thiết lập của thư viện này. Sau đó nó cài package, tìm URL backend của bạn trong cấu hình của ứng dụng, chạy init và hoàn tất phần kết nối dựa trên output của lệnh đó.

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.

Hướng dẫn thiết lập đầy đủ →

Câu hỏi về phát lại

Request được khớp như thế nào?
Proxy khớp một lời gọi phía máy chủ theo method, path và query string chính xác của nó, không theo body, và trả về các lời gọi lặp lại theo đúng thứ tự chúng được ghi lại. Lời gọi trình duyệt theo quy tắc HAR của Playwright, vốn so sánh cả body của POST. Một query string thay đổi mỗi lần chạy, chẳng hạn một dấu thời gian, sẽ không bao giờ khớp, và không có tùy chọn nào để bỏ qua một tham số, nên hãy giữ cố định các giá trị như vậy trong lúc test. Cách phát lại khớp request →
Nếu thiếu bản ghi thì sao?
Proxy trả về 404 và nêu tên request cùng test. Một lời gọi được thực hiện nhiều lần hơn số lần nó được ghi lại sẽ nhận lại response cuối cùng, kèm một cảnh báo trong log của proxy. Trong trình duyệt, Playwright hủy một lời gọi khớp với pattern url mà fixture của bạn truyền cho before() nhưng không có trong HAR; các lời gọi khác đi ra mạng.
Khi nào phát lại gọi tới API thật?
Phát lại chỉ gọi tới API thật sau khi proxy rời chế độ phát lại, và proxy giữ một chế độ chung cho mọi test đang chạy. Nó rời chế độ phát lại 120 giây sau khi test cuối cùng bắt đầu, hoặc khi một hook theo từng test gọi teardown(). Khi không có backend nào chạy trên CI, một lời gọi như vậy sẽ thất bại thay vì pass. Timeout của session và teardown →
Điều gì xảy ra khi API thay đổi?
Với một API client có kiểu, việc cập nhật client theo một trường đã thay đổi sẽ làm các test phát lại dùng trường đó thất bại, vì các bản ghi vẫn giữ trường cũ. Điều đó chỉ cho bạn thấy thay đổi, và ghi lại lần nữa sẽ sửa các test với ít công sức hơn so với chỉnh sửa các mock viết tay.
Làm sao để test lỗi và các trường hợp biên?
Trong trình duyệt, một page.route() được đăng ký sau before() sẽ được ưu tiên hơn bản ghi. Còn response phía máy chủ thì được chỉnh sửa trong file .mock.json. Ép một response →

Ngừng viết mock bằng tay

API của bạn đã cho sẵn câu trả lời đúng. Hãy ghi lại chúng.

npm i -D test-proxy-recorder Star trên GitHub

Nếu nó giúp bạn tiết kiệm một buổi chiều, một star chỉ mất một giây — đó là cách người tiếp theo tìm thấy nó, và nó báo cho một maintainer độc lập rằng hãy tiếp tục xây dựng. Gặp trục trặc hay có ý tưởng? Mở một issue hoặc tham gia Discord.