test-proxy-recorder 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,702 +3,553 @@
3
3
  [![npm](https://img.shields.io/npm/v/test-proxy-recorder.svg)](https://www.npmjs.com/package/test-proxy-recorder)
4
4
  [![license](https://img.shields.io/github/license/asmyshlyaev177/test-proxy-recorder.svg?style=flat-square)](https://github.com/asmyshlyaev177/test-proxy-recorder/blob/master/LICENSE)
5
5
 
6
- HTTP proxy server for recording and replaying network requests in testing. Works seamlessly with Playwright and other testing frameworks.
6
+ Fast, deterministic Playwright tests without maintaining manual mocks.
7
7
 
8
- ## BETA VERSION
8
+ Records real API responses during test runs and replays them on CI — no backend required. Supports two recording mechanisms depending on where your requests originate:
9
9
 
10
- ## Features
11
-
12
- - **Fast CI/CD Tests**: Record API responses once with real backend, replay them on CI/CD without backend
13
- - **Fast Workflow**: Record real interactions with API instead of mocking every request manually
14
- - **Server Side Rendering**: Can record SSR requests from JS frameworks like Next.js
15
- - **Deterministic Tests**: Same responses every time, no flaky network issues, no need to wire up the whole Backend API for testing
16
- - **WebSocket Support**: Records and replays WebSocket connections
17
-
18
- ## Table of Contents
10
+ ```text
11
+ Record mode Replay mode
19
12
 
20
- - [How It Works](#how-it-works)
21
- - [Complete Setup Guide](#complete-setup-guide)
22
- - [CLI Usage](#cli-usage)
23
- - [Playwright Integration](#playwright-integration)
24
- - [Next.js Integration](#nextjs-integration)
25
- - [Control Endpoint](#control-endpoint)
26
- - [Typical Workflow](#typical-workflow)
27
- - [Recording Format](#recording-format)
28
- - [Troubleshooting](#troubleshooting)
29
- - [API Reference](#api-reference)
13
+ Browser/App ──> Proxy ──> Real API Browser/App ──> Proxy ──> Disk
14
+ │ │
15
+ └──> saves to disk └──> serves saved responses
16
+ (.mock.json) (.mock.json)
17
+ ```
30
18
 
31
- ## How It Works
19
+ | Mechanism | What it records | Use case |
20
+ | --------- | --------------- | -------- |
21
+ | **Proxy** (`.mock.json`) | Server-side requests (SSR fetches from Next.js etc.) | Full-stack apps where the server calls the API |
22
+ | **HAR** (`.har`) | Browser-side requests (browser `fetch`, extensions, SPAs) | SPAs, Chrome extensions, 3rd-party APIs |
32
23
 
33
- The proxy server runs continuously and can switch between three modes per test:
24
+ Both can be used together or independently.
34
25
 
35
- ### 1. Transparent Mode (Default)
26
+ ```text
27
+ Server-side (proxy) Browser-side (HAR)
36
28
 
37
- Passes requests through to the backend without recording or replaying.
29
+ Next.js SSR ──> Proxy ──> Real API Browser ──> HAR intercept ──> Real API
30
+ │ │
31
+ └──> .mock.json └──> .har
32
+ ```
38
33
 
39
- ### 2. Record Mode
34
+ ## Contents
40
35
 
41
- Captures all HTTP requests/responses and WebSocket messages to disk. Each test gets its own recording file based on the test name.
36
+ - [Why](#why)
37
+ - [Full-stack (SSR + browser) Quick Start](#full-stack-ssr--browser-quick-start)
38
+ - [Browser-only / SPA / Extension Quick Start](#browser-only--spa--extension-quick-start)
39
+ - [CLI](#cli)
40
+ - [Example Apps](#example-apps)
41
+ - [Playwright Integration](#playwright-integration)
42
+ - [Next.js Integration](#nextjs-integration)
43
+ - [Control Endpoint](#control-endpoint)
44
+ - [API Reference](#api-reference)
45
+ - [Next.js 16](#nextjs-16)
46
+ - [FAQ](#faq)
47
+ - [AI Agent Skills](#ai-agent-skills)
48
+ - [Roadmap](#roadmap)
49
+ - [Requirements](#requirements)
50
+ - [Contributing](#contributing)
51
+ - [License](#license)
42
52
 
43
- ### 3. Replay Mode
53
+ ---
44
54
 
45
- Replays previously recorded responses from disk instead of hitting the real API. Perfect for fast, deterministic tests.
55
+ ## Why
46
56
 
47
- ## Complete Setup Guide
57
+ - **No backend on CI** — record once against the real API, replay on every CI run
58
+ - **No manual mocks** — capture real interactions instead of hand-writing fixtures
59
+ - **SSR support** — records server-side requests from Next.js and similar frameworks
60
+ - **Browser-side support** — records browser `fetch` calls, Chrome extension API calls, analytics, etc.
61
+ - **Deterministic** — same responses every time, no flaky network
62
+ - **WebSocket support** — records and replays WebSocket connections
48
63
 
49
- ### Step 1: Install Package
64
+ ---
50
65
 
51
- ```bash
52
- npm install --save-dev test-proxy-recorder
53
- ```
66
+ ## Full-stack (SSR + browser) Quick Start
54
67
 
55
- ### Step 2: Add NPM Scripts
68
+ For apps like Next.js where both the server AND the browser make API calls, use both mechanisms together.
56
69
 
57
- Add to `package.json`:
70
+ ### 1. Add scripts to `package.json`
58
71
 
59
72
  ```json
60
73
  {
61
74
  "scripts": {
62
- "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --dir ./e2e/recordings"
75
+ "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --dir ./e2e/recordings",
76
+ "dev:proxy": "concurrently \"npm run proxy\" \"INTERNAL_API_URL=http://localhost:8100 npm run dev\"",
77
+ "serve:proxy": "concurrently \"npm run proxy\" \"INTERNAL_API_URL=http://localhost:8100 npm run serve\""
63
78
  }
64
79
  }
65
80
  ```
66
81
 
67
- **RECOMMENDED**: Use `concurrently` to run proxy and app together:
82
+ > `INTERNAL_API_URL` is the env var your app uses for the API base URL — point it at the proxy instead of the real backend. Replace it with whatever env var your app uses (e.g. `API_URL`, `NEXT_PUBLIC_API_URL`).
83
+ >
84
+ > **Next.js note:** Prefer `build` + `serve` over `dev` for recording/replaying tests. The Next.js dev server is slow and can cause timeouts or flaky recordings.
68
85
 
69
- ```bash
70
- npm install --save-dev concurrently
71
- ```
86
+ ### 2. Write a test
72
87
 
73
- ```json
74
- {
75
- "scripts": {
76
- "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --dir ./e2e/recordings",
77
- "dev:proxy": "concurrently -n \"proxy,app\" -c \"blue,green\" \"npm run proxy\" \"INTERNAL_API_URL=http://localhost:8100 npm run dev\""
78
- }
79
- }
80
- ```
88
+ ```typescript
89
+ import { test, expect } from '@playwright/test';
90
+ import { playwrightProxy } from 'test-proxy-recorder';
81
91
 
82
- ### Step 3: Configure Git for Recordings
92
+ // SSR requests (server proxy) are recorded to .mock.json.
93
+ // Browser requests to the proxy URL are also covered.
94
+ const CLIENT_SIDE_URL = /localhost:8100/;
83
95
 
84
- **CRITICAL**: Recordings must be committed to git for CI/CD replay.
96
+ // Change to 'record' to update recordings.
97
+ const MODE = 'replay' as const;
85
98
 
86
- Create or update your `.gitattributes` file:
99
+ test.beforeEach(async ({ page }, testInfo) => {
100
+ await playwrightProxy.before(page, testInfo, MODE, { url: CLIENT_SIDE_URL });
101
+ });
87
102
 
88
- ```gitattributes
89
- /e2e/recordings/** binary
103
+ test('homepage loads', async ({ page }) => {
104
+ await page.goto('/');
105
+ await expect(page.getByText('Welcome')).toBeVisible();
106
+ });
90
107
  ```
91
108
 
92
- This marks recording files as binary, which causes long mock files to be collapsed/folded in Pull Request diffs for better readability.
109
+ ### 3. Record
93
110
 
94
- **DO NOT** add `e2e/recordings` to `.gitignore`. Recordings need to be versioned in git for CI/CD to use them.
111
+ ```bash
112
+ # Terminal 1
113
+ npm run serve:proxy
95
114
 
96
- **Note**: The recordings directory will be created automatically when you first record a test - no need to create it manually.
115
+ # Terminal 2 .mock.json and .har files are written automatically
116
+ npx playwright test
117
+ ```
97
118
 
98
- ### Step 4: Create Playwright Global Teardown (Recommended)
119
+ ### 4. Switch to replay and commit
99
120
 
100
- Create `e2e/global-teardown.ts`:
121
+ ```bash
122
+ git add e2e/recordings/
123
+ git commit -m "add e2e recordings"
124
+ ```
101
125
 
102
- ```typescript
103
- import { playwrightProxy } from 'test-proxy-recorder';
126
+ ---
104
127
 
105
- async function globalTeardown() {
106
- await playwrightProxy.teardown();
107
- }
128
+ ## Browser-only / SPA / Extension Quick Start
129
+
130
+ If your app or extension makes API calls entirely from the browser (no SSR), you only need the HAR mechanism. No proxy backend is required for the actual recording — the proxy process just provides session management.
108
131
 
109
- export default globalTeardown;
132
+ ### 1. Install
133
+
134
+ ```bash
135
+ npm install --save-dev test-proxy-recorder
110
136
  ```
111
137
 
112
- Update `playwright.config.ts`:
138
+ ### 2. Add the proxy to `playwright.config.ts`
113
139
 
114
140
  ```typescript
115
141
  import { defineConfig } from '@playwright/test';
116
142
 
117
143
  export default defineConfig({
118
- testDir: './e2e',
119
- globalTeardown: './e2e/global-teardown.ts',
120
- // ... rest of config
144
+ webServer: {
145
+ command: 'test-proxy-recorder https://api.example.com --port 8100 --dir ./e2e/recordings',
146
+ url: 'http://localhost:8100/__control',
147
+ reuseExistingServer: true,
148
+ },
121
149
  });
122
150
  ```
123
151
 
124
- ### Step 5: Create Example Test
152
+ > The proxy target (`https://api.example.com`) does not matter for browser-only recording — it is only used if server-side (SSR) requests also need to be proxied. The proxy process must run so its `/__control` endpoint is available for session management.
125
153
 
126
- Create `e2e/example.spec.ts`:
154
+ ### 3. Write a fixture
127
155
 
128
156
  ```typescript
129
- import { test, expect } from '@playwright/test';
157
+ // e2e/fixtures.ts
158
+ import { test as base, type Page, type BrowserContext } from '@playwright/test';
130
159
  import { playwrightProxy } from 'test-proxy-recorder';
131
160
 
132
- test('example test with proxy', async ({ page }, testInfo) => {
133
- // Set proxy mode: 'record' to capture, 'replay' to use recordings
134
- // This automatically sets up page.on('close') for cleanup
135
- await playwrightProxy.before(page, testInfo, 'replay');
161
+ // Match the external API domain your browser makes requests to.
162
+ // In record mode these requests go to the real API and are saved.
163
+ // In replay mode they are served from disk — no network needed.
164
+ const CLIENT_SIDE_URL = /api\.example\.com/;
136
165
 
137
- await page.goto('/');
138
- await expect(page.getByText('Welcome')).toBeVisible();
166
+ // Change to 'record' to hit the real API and update recordings.
167
+ const MODE = 'replay' as const;
168
+
169
+ export const test = base.extend<{ page: Page }>({
170
+ page: async ({ context }, use, testInfo) => {
171
+ const page = await context.newPage();
172
+ await playwrightProxy.before(page, testInfo, MODE, { url: CLIENT_SIDE_URL });
173
+ await use(page);
174
+ },
139
175
  });
140
176
  ```
141
177
 
142
- ### Step 6: Run Tests
143
-
144
- **First run (record mode)**:
178
+ ### 4. Write a test
145
179
 
146
180
  ```typescript
147
- await playwrightProxy.before(page, testInfo, 'record');
181
+ // e2e/my.test.ts
182
+ import { test, expect } from './fixtures';
183
+
184
+ test('homepage loads', async ({ page }) => {
185
+ await page.goto('https://myapp.com/');
186
+ await expect(page.getByText('Welcome')).toBeVisible();
187
+ });
148
188
  ```
149
189
 
150
- **Subsequent runs (replay mode)**:
190
+ ### 5. Record — run once against the real API
151
191
 
152
- ```typescript
153
- await playwrightProxy.before(page, testInfo, 'replay');
192
+ ```bash
193
+ # In fixtures.ts: const MODE = 'record' as const;
194
+ npx playwright test
195
+ # .har files are written to e2e/recordings/ automatically
154
196
  ```
155
197
 
156
- ## CLI Usage
157
-
158
- ### Basic Command
198
+ ### 6. Switch to replay and commit
159
199
 
160
200
  ```bash
161
- test-proxy-recorder <target-url> [options]
201
+ # In fixtures.ts: const MODE = 'replay' as const;
202
+ git add e2e/recordings/
203
+ git commit -m "add e2e recordings"
162
204
  ```
163
205
 
164
- ### CLI Options
206
+ CI now runs without any network access.
165
207
 
166
- - `<target-url>` - Backend API URL (positional argument, required)
167
- - `--port, -p <number>` - Port to listen on (default: 8080)
168
- - `--dir, -d <path>` - Directory to store recordings (default: ./recordings)
169
- - `--help, -h` - Show help
208
+ > Do **not** add `e2e/recordings` to `.gitignore`. Recordings must be in git for CI replay.
209
+ >
210
+ > Add this to `.gitattributes` to collapse large recording files in PR diffs:
211
+ >
212
+ > ```text
213
+ > /e2e/recordings/** binary
214
+ > ```
170
215
 
171
- ### Examples
172
-
173
- ```bash
174
- # Basic usage
175
- test-proxy-recorder http://localhost:8000
216
+ ---
176
217
 
177
- # Custom port and recordings directory
178
- test-proxy-recorder http://localhost:8000 --port 8100 --dir ./mocks
218
+ ## CLI
179
219
 
180
- # Multiple targets (experimental)
181
- test-proxy-recorder http://localhost:8000 http://localhost:9000 --port 8100
220
+ ```bash
221
+ test-proxy-recorder <target-url> [options]
182
222
  ```
183
223
 
184
- ## Playwright Integration
224
+ | Option | Default | Description |
225
+ | -------------- | -------------- | ----------------------------- |
226
+ | `<target-url>` | *(required)* | Backend URL to proxy |
227
+ | `--port, -p` | `8080` | Proxy listen port |
228
+ | `--dir, -d` | `./recordings` | Directory for recording files |
185
229
 
186
- ### Session Identification
230
+ ```bash
231
+ # Examples
232
+ test-proxy-recorder http://localhost:8000
233
+ test-proxy-recorder http://localhost:8000 --port 8100 --dir ./mocks
234
+ ```
187
235
 
188
- The proxy uses a **custom HTTP header** (`x-test-rcrd-id`) to identify recording sessions. This header is automatically set by the `playwrightProxy.before()` method and works seamlessly with Next.js and other server-side rendering frameworks.
236
+ ---
189
237
 
190
- **Cookie fallback**: For backward compatibility, the proxy also supports cookie-based session identification, but the custom header is preferred.
238
+ ## Example Apps
191
239
 
192
- ### Basic Test Structure
240
+ Two full working examples live in [`apps/`](https://github.com/asmyshlyaev177/test-proxy-recorder/tree/master/apps) — one for each recording mechanism. Each has its own README with the full setup and record/replay workflow.
193
241
 
194
- Every test using the proxy should follow this pattern:
242
+ ### Next.js 16 server-side (proxy / `.mock.json`)
195
243
 
196
- ```typescript
197
- import { test } from '@playwright/test';
198
- import { playwrightProxy } from 'test-proxy-recorder';
244
+ [`apps/example-nextjs16`](https://github.com/asmyshlyaev177/test-proxy-recorder/tree/master/apps/example-nextjs16) — a Next.js 16 todo app with a mock backend, proxy, and Playwright e2e tests. Records both SSR fetches (`.mock.json`) and browser fetches (`.har`). See its [README](https://github.com/asmyshlyaev177/test-proxy-recorder/blob/master/apps/example-nextjs16/README.md).
199
245
 
200
- test('test name', async ({ page }, testInfo) => {
201
- // Set mode BEFORE test actions
202
- // This automatically sets the recording ID header and cleanup handler
203
- await playwrightProxy.before(page, testInfo, 'replay');
246
+ ### Chrome extension — browser-side (HAR / `.har`)
204
247
 
205
- // Test code
206
- await page.goto('/page');
207
- // Test assertions...
208
- });
209
- ```
248
+ [`apps/example-extension`](https://github.com/asmyshlyaev177/test-proxy-recorder/tree/master/apps/example-extension) a real Chrome extension that calls X/Twitter's API from a content script; browser requests are recorded to `.har` and replayed offline, with no live API or account needed on CI. See its [README](https://github.com/asmyshlyaev177/test-proxy-recorder/blob/master/apps/example-extension/README.md).
210
249
 
211
- ### Recording vs Replay
250
+ ---
212
251
 
213
- ```typescript
214
- import { test } from '@playwright/test';
215
- import { playwrightProxy } from 'test-proxy-recorder';
252
+ ## Playwright Integration
216
253
 
217
- // Recording mode - captures API responses
218
- test('create user', async ({ page }, testInfo) => {
219
- await playwrightProxy.before(page, testInfo, 'record');
254
+ <details>
255
+ <summary>Show details</summary>
220
256
 
221
- await page.goto('/users/new');
222
- await page.fill('[name="username"]', 'testuser');
223
- await page.click('button[type="submit"]');
224
- });
257
+ ### `playwrightProxy.before(page, testInfo, mode, options?)`
225
258
 
226
- // Replay mode - uses recorded responses
227
- test('create user', async ({ page }, testInfo) => {
228
- await playwrightProxy.before(page, testInfo, 'replay');
259
+ Call this at the start of each test (or in a `beforeEach` / page fixture). It sets the proxy mode for the session and, if `url` is provided, sets up HAR recording for browser-side requests.
229
260
 
230
- await page.goto('/users/new');
231
- await page.fill('[name="username"]', 'testuser');
232
- await page.click('button[type="submit"]');
261
+ ```typescript
262
+ await playwrightProxy.before(page, testInfo, 'replay', {
263
+ // url: pattern for browser-side requests to record/replay via HAR.
264
+ //
265
+ // Use the ACTUAL external API domain — not the proxy URL.
266
+ // Examples:
267
+ // /api\.example\.com/ — your own API
268
+ // /x\.com/ — record all x.com browser traffic (Chrome extension tests)
269
+ // /cognito-.*amazonaws\.com/ — 3rd-party auth
270
+ url: /api\.example\.com/,
233
271
  });
234
272
  ```
235
273
 
236
- ### Test Naming
237
-
238
- Recording files are auto-generated from test names:
274
+ **`url` pattern:** matches the real external domain that the browser calls. In record mode requests go to the real API and are saved to a `.har` file. In replay mode they are served from that file — no network needed. This pattern does **not** point to the proxy (`localhost:8100`).
239
275
 
240
- - Test: `"create a user"`
241
- - File: `create-a-user.mock.json`
276
+ **Exception — full-stack apps:** when the browser also calls `localhost:8100` (because the frontend is configured with the proxy URL as its API base), use `/localhost:8100/` as the pattern.
242
277
 
243
- **Important**: Keep test names stable for replay to work correctly.
278
+ Recording filenames are derived from test names (`"create a user"` `create-a-user.mock.json` / `.har`).
244
279
 
245
- ### Global Teardown (Recommended)
246
-
247
- Create `e2e/global-teardown.ts`:
280
+ ### Global teardown (recommended)
248
281
 
249
282
  ```typescript
283
+ // e2e/global-teardown.ts
250
284
  import { playwrightProxy } from 'test-proxy-recorder';
251
285
 
252
- async function globalTeardown() {
286
+ export default async function globalTeardown() {
253
287
  await playwrightProxy.teardown();
254
288
  }
255
-
256
- export default globalTeardown;
257
289
  ```
258
290
 
259
- Update `playwright.config.ts`:
260
-
261
291
  ```typescript
262
- import { defineConfig } from '@playwright/test';
263
-
292
+ // playwright.config.ts
264
293
  export default defineConfig({
265
- testDir: './e2e',
266
294
  globalTeardown: './e2e/global-teardown.ts',
267
- // ... rest of config
268
- });
269
- ```
270
-
271
- ### Client-Side Recording for 3rd Party APIs
272
-
273
- For applications that make client-side requests to 3rd party services (e.g., AWS Cognito, Stream.io, analytics services), you can use client-side recording to capture these requests directly in the browser using Playwright's HAR (HTTP Archive) format.
274
-
275
- **Why use client-side recording?**
276
- - Server-side proxy cannot intercept requests made directly from the browser to external services
277
- - HAR files are a standard format supported by Playwright and browser dev tools
278
- - Automatically handles CORS and other browser-specific request behaviors
279
-
280
- **Example:**
281
-
282
- ```typescript
283
- import { test } from '@playwright/test';
284
- import { playwrightProxy } from 'test-proxy-recorder';
285
-
286
- test('authentication flow', async ({ page }, testInfo) => {
287
- // Record both server-side (via proxy) and client-side (via HAR) requests
288
- await playwrightProxy.before(
289
- page,
290
- testInfo,
291
- 'replay',
292
- {
293
- // Client-side URL pattern using Playwright's format
294
- url: /cognito-.*amazonaws\.com|\.stream-io-api\.com/,
295
- timeout: 60000 // Optional: custom timeout
296
- }
297
- );
298
-
299
- await page.goto('/login');
300
- // Cognito authentication requests are recorded to HAR files
301
- await page.fill('[name="email"]', 'user@example.com');
302
- await page.click('button[type="submit"]');
303
295
  });
304
296
  ```
305
297
 
306
- **URL Pattern Options:**
307
- ```typescript
308
- // RegExp pattern (recommended for multiple domains)
309
- { url: /cognito-.*amazonaws\.com|\.stream-io-api\.com/ }
310
-
311
- // String glob pattern
312
- { url: 'https://api.example.com/**' }
298
+ ### Recording files
313
299
 
314
- // Specific domain
315
- { url: /api\.external-service\.com/ }
316
- ```
317
-
318
- **Storage:**
319
- Client-side recordings are stored as HAR files alongside server-side recordings:
320
- ```
300
+ ```text
321
301
  e2e/recordings/
322
- ├── my-test.mock.json # Server-side recordings (proxy)
323
- └── my-test.har # Client-side recordings (browser)
302
+ my-test.mock.json # server-side (proxy) — SSR fetches
303
+ my-test.har # client-side (HAR) — browser fetches
324
304
  ```
325
305
 
326
- **Recording vs Replay:**
327
- - **Record mode**: Creates/updates HAR file with actual responses from 3rd party services
328
- - **Replay mode**: Uses recorded HAR file, no network requests made to 3rd party services
306
+ </details>
329
307
 
330
- **Note:** The recordings directory is automatically retrieved from the proxy server, ensuring both server-side and client-side recordings are stored in the same location.
308
+ ---
331
309
 
332
310
  ## Next.js Integration
333
311
 
334
- When testing Next.js applications with server-side rendering (SSR) or API routes, you need to ensure the recording ID header is forwarded to the proxy. The package provides helpers for this.
312
+ <details>
313
+ <summary>Show details</summary>
335
314
 
336
- ### Option 1: Using Next.js Middleware (Recommended)
315
+ SSR frameworks like Next.js make server-side `fetch` calls that go through the proxy without a browser context. The proxy identifies which session those requests belong to via the `x-test-rcrd-id` header — the same header `playwrightProxy.before()` sets on the browser `page`. This header is **only required for SSR** — for browser-only tests the proxy falls back to the globally set session automatically.
337
316
 
338
- Create or update `middleware.ts` in your Next.js project root:
317
+ For SSR requests to carry this header, use one of:
318
+
319
+ ### Middleware (recommended)
339
320
 
340
321
  ```typescript
322
+ // middleware.ts
341
323
  import { NextResponse } from 'next/server';
342
324
  import type { NextRequest } from 'next/server';
343
325
  import { setNextProxyHeaders } from 'test-proxy-recorder/nextjs';
344
326
 
345
327
  export function middleware(request: NextRequest) {
346
328
  const response = NextResponse.next();
347
-
348
- // Forward the recording ID header during tests
349
- // Only runs in non-production or when TEST_PROXY_RECORDER_ENABLED=true
350
- setNextProxyHeaders(request, response);
351
-
329
+ setNextProxyHeaders(request, response); // no-op in production
352
330
  return response;
353
331
  }
354
332
  ```
355
333
 
356
- **Environment Variables:**
357
- - Automatically skipped when `NODE_ENV=production`
358
- - Can be explicitly enabled in production with `TEST_PROXY_RECORDER_ENABLED=true`
359
-
360
- ### Option 2: Manual Header Forwarding in API Routes
361
-
362
- For API routes or server components, manually include the header in fetch requests:
334
+ ### Manual header forwarding
363
335
 
364
336
  ```typescript
365
- // app/api/data/route.ts
366
337
  import { headers } from 'next/headers';
367
338
  import { createHeadersWithRecordingId } from 'test-proxy-recorder/nextjs';
368
339
 
369
- export async function GET() {
370
- const requestHeaders = await headers();
371
-
372
- const response = await fetch('http://localhost:8100/api/data', {
373
- headers: createHeadersWithRecordingId(requestHeaders, {
374
- 'Content-Type': 'application/json',
375
- })
376
- });
377
-
378
- return Response.json(await response.json());
379
- }
340
+ const res = await fetch('http://localhost:8100/api/data', {
341
+ headers: createHeadersWithRecordingId(await headers(), {
342
+ 'Content-Type': 'application/json',
343
+ }),
344
+ });
380
345
  ```
381
346
 
382
- ### Option 3: Using getRecordingId Helper
347
+ </details>
383
348
 
384
- For more control, extract the recording ID and use it manually:
385
-
386
- ```typescript
387
- import { headers } from 'next/headers';
388
- import { getRecordingId, RECORDING_ID_HEADER } from 'test-proxy-recorder/nextjs';
389
-
390
- export async function GET() {
391
- const recordingId = getRecordingId(await headers());
392
-
393
- const response = await fetch('http://localhost:8100/api/data', {
394
- headers: {
395
- 'Content-Type': 'application/json',
396
- ...(recordingId && { [RECORDING_ID_HEADER]: recordingId })
397
- }
398
- });
399
-
400
- return Response.json(await response.json());
401
- }
402
- ```
349
+ ---
403
350
 
404
351
  ## Control Endpoint
405
352
 
406
- The proxy exposes a control endpoint at `/__control` for programmatic mode switching and configuration retrieval.
407
-
408
- ### GET - Retrieve Proxy Configuration
353
+ <details>
354
+ <summary>Show details</summary>
409
355
 
410
- Get the current proxy configuration including recordings directory, mode, and active session ID.
356
+ The proxy exposes `/__control` for programmatic mode switching.
411
357
 
412
- **Via HTTP:**
413
358
  ```bash
359
+ # Get current state
414
360
  curl http://localhost:8100/__control
415
- ```
416
-
417
- **Response:**
418
- ```json
419
- {
420
- "recordingsDir": "/path/to/e2e/recordings",
421
- "mode": "replay",
422
- "id": "my-test-1"
423
- }
424
- ```
425
-
426
- **Via JavaScript:**
427
- ```javascript
428
- const config = await fetch('http://localhost:8100/__control').then(r => r.json());
429
- console.log(config.recordingsDir); // "/path/to/e2e/recordings"
430
- console.log(config.mode); // "replay"
431
- console.log(config.id); // "my-test-1"
432
- ```
433
-
434
- ### POST - Switch Proxy Mode
435
-
436
- **Via HTTP:**
437
- ```bash
438
- # Switch to record mode
439
- curl -X POST http://localhost:8100/__control \
440
- -H "Content-Type: application/json" \
441
- -d '{"mode": "record", "id": "my-test-1", "timeout": 30000}'
442
-
443
- # Switch to replay mode
444
- curl -X POST http://localhost:8100/__control \
445
- -H "Content-Type: application/json" \
446
- -d '{"mode": "replay", "id": "my-test-1"}'
447
361
 
448
- # Switch to transparent mode
362
+ # Switch modes
449
363
  curl -X POST http://localhost:8100/__control \
450
364
  -H "Content-Type: application/json" \
451
- -d '{"mode": "transparent"}'
452
- ```
453
-
454
- **Via JavaScript:**
455
- ```javascript
456
- await fetch('http://localhost:8100/__control', {
457
- method: 'POST',
458
- headers: { 'Content-Type': 'application/json' },
459
- body: JSON.stringify({
460
- mode: 'record',
461
- id: 'my-test-1',
462
- timeout: 30000 // Optional: auto-reset after 30s
463
- })
464
- });
365
+ -d '{"mode": "record", "id": "my-test-1"}'
465
366
  ```
466
367
 
467
- ### Control Request Interface
468
-
469
368
  ```typescript
470
369
  interface ControlRequest {
471
370
  mode: 'transparent' | 'record' | 'replay';
472
- id?: string; // Recording ID, required for record/replay
473
- timeout?: number; // Auto-reset timeout in ms (default: 120000)
474
- }
475
-
476
- interface ControlResponse {
477
- recordingsDir: string;
478
- mode: string;
479
- id?: string;
371
+ id?: string; // required for record/replay
372
+ timeout?: number; // auto-reset timeout in ms (default: 120000)
480
373
  }
481
374
  ```
482
375
 
483
- ## Typical Workflow
376
+ </details>
484
377
 
485
- ### Initial Recording
378
+ ---
486
379
 
487
- 1. Start backend API: `npm run api`
488
- 2. Start proxy and app: `npm run dev:proxy`
489
- 3. Set test to `'record'` mode
490
- 4. Run test: Recordings saved to `./e2e/recordings/` (directory created automatically)
491
- 5. Commit `.mock.json` files to git
492
- 6. Change mode to `'replay'`
380
+ ## API Reference
493
381
 
494
- ### Running with Replay
382
+ <details>
383
+ <summary>Show details</summary>
495
384
 
496
- 1. Start proxy and app: `npm run dev:proxy` (no backend needed!)
497
- 2. Set test to `'replay'` mode
498
- 3. Run test: Uses recorded responses
499
- 4. Tests run fast without backend
385
+ ### `playwrightProxy`
500
386
 
501
- ### Updating Recordings
387
+ ```typescript
388
+ const playwrightProxy: {
389
+ before(
390
+ page: Page,
391
+ testInfo: TestInfo,
392
+ mode: 'record' | 'replay' | 'transparent',
393
+ options?: { url?: string | RegExp; timeout?: number }
394
+ ): Promise<void>;
502
395
 
503
- 1. Start backend API
504
- 2. Set test to `'record'` mode
505
- 3. Run test: Overwrites existing recording
506
- 4. Commit updated `.mock.json` file
396
+ teardown(): Promise<void>;
397
+ };
398
+ ```
507
399
 
508
- ## Recording Format
400
+ ### `setProxyMode`
509
401
 
510
- Recordings are stored in two formats depending on the recording type:
402
+ ```typescript
403
+ function setProxyMode(
404
+ mode: 'record' | 'replay' | 'transparent',
405
+ id?: string,
406
+ timeout?: number
407
+ ): Promise<void>;
408
+ ```
511
409
 
512
- **Server-side recordings** (via proxy): JSON files with `.mock.json` extension
513
- **Client-side recordings** (via HAR): HTTP Archive files with `.har` extension
410
+ ### Next.js helpers (`test-proxy-recorder/nextjs`)
514
411
 
515
- ```text
516
- e2e/recordings/
517
- ├── create-a-user.mock.json # Server-side API calls
518
- ├── create-a-user.har # Client-side 3rd party requests
519
- ├── fetch-users-list.mock.json
520
- └── delete-user.mock.json
412
+ ```typescript
413
+ function setNextProxyHeaders(request: NextRequest, response: NextResponse): void;
414
+ function getRecordingId(headers: NextRequest | Headers): string | null;
415
+ function createHeadersWithRecordingId(
416
+ headers: NextRequest | Headers,
417
+ additional?: Record<string, string>
418
+ ): Record<string, string>;
521
419
  ```
522
420
 
523
- Both file types use the same naming convention based on the test name, making it easy to identify which recordings belong to which test.
421
+ </details>
524
422
 
525
- ## Troubleshooting
423
+ ---
526
424
 
527
- ### Proxy not responding
425
+ ## Next.js 16
528
426
 
529
- **Check if proxy is running**:
427
+ <details>
428
+ <summary>Show details</summary>
530
429
 
531
- ```bash
532
- curl http://localhost:8100/__control
430
+ Next.js 16 uses `proxy.ts` as the middleware entry point (replaces `middleware.ts`). Place it at the project root alongside `next.config.ts`:
431
+
432
+ ```typescript
433
+ // proxy.ts (Next.js 16 middleware convention)
434
+ import { NextResponse } from 'next/server';
435
+ import type { NextRequest } from 'next/server';
436
+ import { setNextProxyHeaders } from 'test-proxy-recorder/nextjs';
437
+
438
+ export function proxy(request: NextRequest) {
439
+ const response = NextResponse.next();
440
+ setNextProxyHeaders(request, response);
441
+ return response;
442
+ }
443
+
444
+ export const config = {
445
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
446
+ };
533
447
  ```
534
448
 
535
- **Check port availability**:
449
+ **package.json scripts** — start services from scripts, not from `playwright.config.ts`:
536
450
 
537
- ```bash
538
- lsof -i :8100
451
+ ```json
452
+ {
453
+ "scripts": {
454
+ "mock": "node mock-backend/server.mjs",
455
+ "proxy": "test-proxy-recorder http://localhost:3002 -p 8100 -d ./e2e/recordings",
456
+ "start:all": "concurrently \"pnpm mock\" \"pnpm proxy\" \"pnpm build && next start --port 3000\""
457
+ }
458
+ }
539
459
  ```
540
460
 
541
- ### No recordings saved
461
+ </details>
542
462
 
543
- - Verify proxy mode is `'record'`
544
- - Check app is using proxy URL (`http://localhost:8100`)
545
- - Verify write permissions on recordings directory
546
- - Check proxy server logs for errors
463
+ ---
547
464
 
548
- ### Test fails in replay mode
465
+ ## FAQ
549
466
 
550
- - Ensure recording exists for this test
551
- - Check test name hasn't changed
552
- - Verify recording file matches expected format
553
- - Re-record if API responses changed
467
+ <details>
468
+ <summary><strong>My parallel replay tests sometimes hit the real backend — why?</strong></summary>
554
469
 
555
- ### Recordings not matching requests
470
+ You're likely calling `playwrightProxy.teardown()` in a per-test hook. It sets the **global** proxy mode to `transparent`, and with `fullyParallel: true` each Playwright worker runs its own `test.afterAll`. If a fast test finishes and calls `teardown()` while a slower test is still running, the proxy flips to transparent mid-test and the remaining requests are forwarded to the real backend instead of being replayed.
556
471
 
557
- - Request URLs must match exactly
558
- - Headers may affect matching (configurable)
559
- - Query parameters must be in same order
560
- - Re-record to capture current API behavior
472
+ ```typescript
473
+ // breaks parallel replay — teardown() affects all sessions globally
474
+ test.afterAll(async () => {
475
+ await playwrightProxy.teardown();
476
+ });
477
+ ```
561
478
 
479
+ **Fix:** omit `test.afterAll`. Session cleanup is automatic via `context.on('close')` → `cleanupSession()`. Use a [global teardown](https://playwright.dev/docs/test-global-setup-teardown) only if you need to reset the proxy after the entire run.
562
480
 
563
- ## API Reference
481
+ </details>
564
482
 
565
- ### ProxyServer Class
483
+ <details>
484
+ <summary><strong>Should I commit recordings to git?</strong></summary>
566
485
 
567
- ```typescript
568
- class ProxyServer {
569
- constructor(targets: string[], recordingsDir: string);
570
- async init(): Promise<void>;
571
- listen(port: number): http.Server;
572
- }
486
+ Yes. Recordings must be in git so CI can replay them with no network — do **not** add `e2e/recordings` to `.gitignore`. To keep large recording files from bloating PR diffs, mark them binary in `.gitattributes`:
487
+
488
+ ```text
489
+ /e2e/recordings/** binary
573
490
  ```
574
491
 
575
- ### Playwright Integration
492
+ </details>
576
493
 
577
- ```typescript
578
- import { playwrightProxy, setProxyMode, RECORDING_ID_HEADER } from 'test-proxy-recorder';
579
- import type { Page } from '@playwright/test';
580
-
581
- // Client-side recording options
582
- interface ClientSideRecordingOptions {
583
- /**
584
- * URL pattern for client-side requests to record/replay
585
- * Uses Playwright's native format (string or RegExp)
586
- * Example: /cognito-.*amazonaws\.com|\.stream-io-api\.com/
587
- * Example: 'https://api.example.com/**'
588
- */
589
- url?: string | RegExp;
590
- }
494
+ <details>
495
+ <summary><strong>Does the proxy <code>&lt;target-url&gt;</code> matter for browser-only (HAR) recording?</strong></summary>
591
496
 
592
- // Main helper for Playwright tests
593
- const playwrightProxy = {
594
- // Set proxy mode before test and configure page with recording ID header
595
- // Supports optional client-side recording for 3rd party APIs
596
- async before(
597
- page: Page,
598
- testInfo: TestInfo,
599
- mode: 'record' | 'replay' | 'transparent',
600
- options?: number | (ClientSideRecordingOptions & { timeout?: number })
601
- ): Promise<void>;
497
+ No. For browser-only recording the target is irrelevant — the proxy process just needs to run so its `/__control` endpoint is available for session management. The target only matters when server-side (SSR) requests are also routed through the proxy.
602
498
 
603
- // Global teardown - switches proxy to transparent mode
604
- // Use in Playwright's globalTeardown configuration
605
- async teardown(): Promise<void>;
606
- };
499
+ </details>
607
500
 
608
- // Direct mode control
609
- async function setProxyMode(
610
- mode: 'record' | 'replay' | 'transparent',
611
- id?: string,
612
- timeout?: number
613
- ): Promise<void>;
501
+ <details>
502
+ <summary><strong>Can I record against the Next.js dev server?</strong></summary>
614
503
 
615
- // Recording ID header constant
616
- const RECORDING_ID_HEADER: string; // 'x-test-rcrd-id'
617
- ```
504
+ Prefer `next build` + `next start` over `next dev` for recording and replaying. The dev server is slow and can cause timeouts or flaky recordings.
618
505
 
619
- **Options Parameter:**
620
- - `number` - Legacy format: timeout in milliseconds
621
- - `ClientSideRecordingOptions & { timeout?: number }` - Object with optional client-side recording and timeout:
622
- - `url?: string | RegExp` - URL pattern for client-side recording (uses Playwright's HAR format)
623
- - `timeout?: number` - Auto-reset timeout in milliseconds
506
+ </details>
624
507
 
625
- ### Next.js Integration
508
+ <details>
509
+ <summary><strong>How do I update a recording?</strong></summary>
626
510
 
627
- **IMPORTANT**: Use the `/nextjs` import path to avoid webpack bundling issues in Next.js:
511
+ Re-run in record mode (set `MODE = 'record'` in your fixture, or `RECORD_MODE=1`) against the real API, then switch back to replay and commit the updated files in `e2e/recordings/`.
628
512
 
629
- ```typescript
630
- import {
631
- setNextProxyHeaders,
632
- getRecordingId,
633
- createHeadersWithRecordingId,
634
- RECORDING_ID_HEADER
635
- } from 'test-proxy-recorder/nextjs';
636
- import type { NextRequest, NextResponse } from 'next/server';
637
-
638
- // Forward recording ID header in Next.js middleware
639
- // Automatically skipped in production unless TEST_PROXY_RECORDER_ENABLED=true
640
- function setNextProxyHeaders(
641
- request: NextRequest,
642
- response: NextResponse
643
- ): void;
644
-
645
- // Get recording ID from request headers
646
- function getRecordingId(
647
- requestHeaders: NextRequest | Headers
648
- ): string | null;
649
-
650
- // Create headers object with recording ID for fetch requests
651
- function createHeadersWithRecordingId(
652
- requestHeaders: NextRequest | Headers,
653
- additionalHeaders?: Record<string, string>
654
- ): Record<string, string>;
655
- ```
513
+ </details>
656
514
 
657
- ### Control Endpoint
515
+ ---
658
516
 
659
- The control endpoint supports both GET and POST methods.
517
+ ## AI Agent Skills
660
518
 
661
- **GET `/__control`** - Retrieve proxy configuration:
519
+ If you use an AI coding agent (Claude Code, Cursor, Copilot, etc.), install the skills for this library so the agent generates correct setup code:
662
520
 
663
- ```typescript
664
- // Response
665
- {
666
- recordingsDir: string; // Path to recordings directory
667
- mode: string; // Current mode: 'transparent' | 'record' | 'replay'
668
- id?: string; // Active recording/replay session ID
669
- }
521
+ ```bash
522
+ npx @tanstack/intent@latest install
670
523
  ```
671
524
 
672
- **POST `/__control`** - Switch proxy mode:
525
+ This adds `test-proxy-recorder` skills to your project. The agent will then know the correct proxy/fixture setup, record vs. replay workflow, and Next.js SSR header patterns without needing guidance.
673
526
 
674
- ```typescript
675
- // Request Body
676
- {
677
- mode: 'transparent' | 'record' | 'replay';
678
- id?: string; // Recording ID (required for record/replay)
679
- timeout?: number; // Auto-reset timeout in ms (default: 120000)
680
- }
527
+ ---
681
528
 
682
- // Response
683
- {
684
- success: boolean;
685
- mode: string;
686
- id: string | null;
687
- timeout: number;
688
- recordingsDir: string;
689
- }
690
- ```
529
+ ## Roadmap
530
+
531
+ First-class integrations on the way:
532
+
533
+ - **TanStack Start**
534
+ - **Remix**
535
+ - **Vite + SSR**
691
536
 
692
- **Note**: Switching to replay mode automatically resets session counters (clears served recordings tracker), allowing replay from the beginning.
537
+ Need one sooner, or a different framework? [Open an issue](https://github.com/asmyshlyaev177/test-proxy-recorder/issues).
538
+
539
+ ---
693
540
 
694
541
  ## Requirements
695
542
 
696
- - Node.js >= 22.0.0
697
- - @playwright/test >= 1.0.0 (for Playwright integration)
543
+ - Node.js >= 20.0.0
544
+ - @playwright/test >= 1.0.0 (peer dependency)
545
+
546
+ ---
698
547
 
699
548
  ## Contributing
700
549
 
701
- Contributions are welcome! Please feel free to submit a Pull Request.
550
+ Contributions welcome! Please submit a Pull Request.
551
+
552
+ ---
702
553
 
703
554
  ## License
704
555