test-proxy-recorder 0.3.3 → 0.3.5

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,58 +3,36 @@
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
+ An HTTP proxy that records real API responses during test runs and replays them on CI -- no backend required. Instead of hand-writing mock fixtures, just run your tests once against the real API and commit the recordings. Supports Next.js and SSR.
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
19
-
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)
30
-
31
- ## How It Works
32
-
33
- The proxy server runs continuously and can switch between three modes per test:
34
-
35
- ### 1. Transparent Mode (Default)
36
-
37
- Passes requests through to the backend without recording or replaying.
38
-
39
- ### 2. Record Mode
10
+ ```
11
+ Record mode Replay mode
40
12
 
41
- Captures all HTTP requests/responses and WebSocket messages to disk. Each test gets its own recording file based on the test name.
13
+ Browser/App ──> Proxy ──> Real API Browser/App ──> Proxy ──> Disk
14
+ │ │
15
+ └──> saves to disk └──> serves saved responses
16
+ (.mock.json) (.mock.json)
17
+ ```
42
18
 
43
- ### 3. Replay Mode
19
+ ## Why
44
20
 
45
- Replays previously recorded responses from disk instead of hitting the real API. Perfect for fast, deterministic tests.
21
+ - **No backend on CI** -- record once against the real API, replay on every CI run
22
+ - **No manual mocks** -- capture real interactions instead of hand-writing fixtures
23
+ - **SSR support** -- records server-side requests from Next.js and similar frameworks
24
+ - **Deterministic** -- same responses every time, no flaky network
25
+ - **WebSocket support** -- records and replays WebSocket connections
46
26
 
47
- ## Complete Setup Guide
27
+ ## Quick Start
48
28
 
49
- ### Step 1: Install Package
29
+ ### 1. Install
50
30
 
51
31
  ```bash
52
32
  npm install --save-dev test-proxy-recorder
53
33
  ```
54
34
 
55
- ### Step 2: Add NPM Scripts
56
-
57
- Add to `package.json`:
35
+ ### 2. Add scripts to `package.json`
58
36
 
59
37
  ```json
60
38
  {
@@ -64,641 +42,319 @@ Add to `package.json`:
64
42
  }
65
43
  ```
66
44
 
67
- **RECOMMENDED**: Use `concurrently` to run proxy and app together:
68
-
69
- ```bash
70
- npm install --save-dev concurrently
71
- ```
72
-
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
- ```
81
-
82
- ### Step 3: Configure Git for Recordings
83
-
84
- **CRITICAL**: Recordings must be committed to git for CI/CD replay.
85
-
86
- Create or update your `.gitattributes` file:
87
-
88
- ```gitattributes
89
- /e2e/recordings/** binary
90
- ```
91
-
92
- This marks recording files as binary, which causes long mock files to be collapsed/folded in Pull Request diffs for better readability.
93
-
94
- **DO NOT** add `e2e/recordings` to `.gitignore`. Recordings need to be versioned in git for CI/CD to use them.
95
-
96
- **Note**: The recordings directory will be created automatically when you first record a test - no need to create it manually.
97
-
98
- ### Step 4: Create Playwright Global Teardown (Recommended)
99
-
100
- Create `e2e/global-teardown.ts`:
101
-
102
- ```typescript
103
- import { playwrightProxy } from 'test-proxy-recorder';
104
-
105
- async function globalTeardown() {
106
- await playwrightProxy.teardown();
107
- }
108
-
109
- export default globalTeardown;
110
- ```
111
-
112
- Update `playwright.config.ts`:
113
-
114
- ```typescript
115
- import { defineConfig } from '@playwright/test';
116
-
117
- export default defineConfig({
118
- testDir: './e2e',
119
- globalTeardown: './e2e/global-teardown.ts',
120
- // ... rest of config
121
- });
122
- ```
123
-
124
- ### Step 5: Create Example Test
125
-
126
- Create `e2e/example.spec.ts`:
45
+ > **Tip:** Use `concurrently` to run proxy + app together.
46
+ > `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. Use proxy address for dev/test and real backend for production environment.
47
+ > Replace it with whatever env var your app uses (e.g. `API_URL`, `NEXT_PUBLIC_API_URL`).
48
+ >
49
+ > ```json
50
+ > {
51
+ > "scripts": {
52
+ > "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --dir ./e2e/recordings",
53
+ > "dev:proxy": "concurrently \"npm run proxy\" \"INTERNAL_API_URL=http://localhost:8100 npm run dev\"",
54
+ > "serve:proxy": "concurrently \"npm run proxy\" \"INTERNAL_API_URL=http://localhost:8100 npm run serve\""
55
+ > }
56
+ > }
57
+ > ```
58
+ >
59
+ > **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.
60
+
61
+ ### 3. Write a test
127
62
 
128
63
  ```typescript
129
64
  import { test, expect } from '@playwright/test';
130
65
  import { playwrightProxy } from 'test-proxy-recorder';
131
66
 
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');
67
+ test('homepage loads', async ({ page }, testInfo) => {
68
+ await playwrightProxy.before(page, testInfo, 'record'); // first run: record
69
+ // await playwrightProxy.before(page, testInfo, 'replay'); // later: replay
136
70
 
137
71
  await page.goto('/');
138
72
  await expect(page.getByText('Welcome')).toBeVisible();
139
73
  });
140
74
  ```
141
75
 
142
- ### Step 6: Run Tests
76
+ ### 4. Run
143
77
 
144
- **First run (record mode)**:
78
+ Start the proxy + app first (e.g. `npm run serve:proxy`), then run tests in a separate terminal:
145
79
 
146
- ```typescript
147
- await playwrightProxy.before(page, testInfo, 'record');
80
+ ```bash
81
+ # Terminal 1 -- start proxy and app
82
+ npm run serve:proxy
83
+
84
+ # Terminal 2 -- run tests
85
+ npx playwright test
148
86
  ```
149
87
 
150
- **Subsequent runs (replay mode)**:
88
+ ### 5. Commit recordings to git
151
89
 
152
- ```typescript
153
- await playwrightProxy.before(page, testInfo, 'replay');
90
+ ```bash
91
+ # .gitattributes -- collapse long mock files in PR diffs
92
+ /e2e/recordings/** binary
154
93
  ```
155
94
 
156
- ## CLI Usage
95
+ > Do **not** add `e2e/recordings` to `.gitignore`. Recordings must be in git for CI replay.
157
96
 
158
- ### Basic Command
97
+ ---
98
+
99
+ ## CLI
159
100
 
160
101
  ```bash
161
102
  test-proxy-recorder <target-url> [options]
162
103
  ```
163
104
 
164
- ### CLI Options
165
-
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
170
-
171
- ### Examples
105
+ | Option | Default | Description |
106
+ | -------------- | -------------- | ----------------------------- |
107
+ | `<target-url>` | *(required)* | Backend URL to proxy |
108
+ | `--port, -p` | `8080` | Proxy listen port |
109
+ | `--dir, -d` | `./recordings` | Directory for recording files |
172
110
 
173
111
  ```bash
174
- # Basic usage
112
+ # Examples
175
113
  test-proxy-recorder http://localhost:8000
176
-
177
- # Custom port and recordings directory
178
114
  test-proxy-recorder http://localhost:8000 --port 8100 --dir ./mocks
179
-
180
- # Multiple targets (experimental)
181
- test-proxy-recorder http://localhost:8000 http://localhost:9000 --port 8100
182
115
  ```
183
116
 
184
117
  ## Playwright Integration
185
118
 
186
- ### Session Identification
187
-
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.
189
-
190
- **Cookie fallback**: For backward compatibility, the proxy also supports cookie-based session identification, but the custom header is preferred.
191
-
192
- ### Basic Test Structure
193
-
194
- Every test using the proxy should follow this pattern:
119
+ ### Basic pattern
195
120
 
196
121
  ```typescript
197
122
  import { test } from '@playwright/test';
198
123
  import { playwrightProxy } from 'test-proxy-recorder';
199
124
 
200
- test('test name', async ({ page }, testInfo) => {
201
- // Set mode BEFORE test actions
202
- // This automatically sets the recording ID header and cleanup handler
125
+ test('my test', async ({ page }, testInfo) => {
203
126
  await playwrightProxy.before(page, testInfo, 'replay');
204
-
205
- // Test code
206
- await page.goto('/page');
207
- // Test assertions...
127
+ // ... test code
208
128
  });
209
129
  ```
210
130
 
211
- ### Recording vs Replay
131
+ `playwrightProxy.before()` sets the proxy mode, attaches a session header (`x-test-rcrd-id`), and registers cleanup on page close. Recording filenames are derived from test names (`"create a user"` -> `create-a-user.mock.json`).
212
132
 
213
- ```typescript
214
- import { test } from '@playwright/test';
215
- import { playwrightProxy } from 'test-proxy-recorder';
216
-
217
- // Recording mode - captures API responses
218
- test('create user', async ({ page }, testInfo) => {
219
- await playwrightProxy.before(page, testInfo, 'record');
220
-
221
- await page.goto('/users/new');
222
- await page.fill('[name="username"]', 'testuser');
223
- await page.click('button[type="submit"]');
224
- });
225
-
226
- // Replay mode - uses recorded responses
227
- test('create user', async ({ page }, testInfo) => {
228
- await playwrightProxy.before(page, testInfo, 'replay');
229
-
230
- await page.goto('/users/new');
231
- await page.fill('[name="username"]', 'testuser');
232
- await page.click('button[type="submit"]');
233
- });
234
- ```
235
-
236
- ### Test Naming
237
-
238
- Recording files are auto-generated from test names:
239
-
240
- - Test: `"create a user"`
241
- - File: `create-a-user.mock.json`
242
-
243
- **Important**: Keep test names stable for replay to work correctly.
244
-
245
- ### Global Teardown (Recommended)
246
-
247
- Create `e2e/global-teardown.ts`:
133
+ ### Global teardown (recommended)
248
134
 
249
135
  ```typescript
136
+ // e2e/global-teardown.ts
250
137
  import { playwrightProxy } from 'test-proxy-recorder';
251
138
 
252
- async function globalTeardown() {
139
+ export default async function globalTeardown() {
253
140
  await playwrightProxy.teardown();
254
141
  }
255
-
256
- export default globalTeardown;
257
142
  ```
258
143
 
259
- Update `playwright.config.ts`:
260
-
261
144
  ```typescript
262
- import { defineConfig } from '@playwright/test';
263
-
145
+ // playwright.config.ts
264
146
  export default defineConfig({
265
- testDir: './e2e',
266
147
  globalTeardown: './e2e/global-teardown.ts',
267
- // ... rest of config
268
148
  });
269
149
  ```
270
150
 
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.
151
+ ### Client-side recording (3rd party APIs)
274
152
 
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:**
153
+ For browser-side requests that don't go through the proxy (e.g. AWS Cognito, analytics), use HAR recording:
281
154
 
282
155
  ```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"]');
156
+ await playwrightProxy.before(page, testInfo, 'replay', {
157
+ url: /cognito-.*amazonaws\.com|\.stream-io-api\.com/,
303
158
  });
304
159
  ```
305
160
 
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/**' }
313
-
314
- // Specific domain
315
- { url: /api\.external-service\.com/ }
316
- ```
161
+ Recordings are stored alongside server-side files:
317
162
 
318
- **Storage:**
319
- Client-side recordings are stored as HAR files alongside server-side recordings:
320
163
  ```
321
164
  e2e/recordings/
322
- ├── my-test.mock.json # Server-side recordings (proxy)
323
- └── my-test.har # Client-side recordings (browser)
165
+ my-test.mock.json # server-side (proxy)
166
+ my-test.har # client-side (HAR)
324
167
  ```
325
168
 
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
329
-
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.
331
-
332
169
  ## Next.js Integration
333
170
 
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.
335
-
336
- ### Option 1: Using Next.js Middleware (Recommended)
171
+ The proxy identifies sessions via a custom header. For SSR requests to carry this header, use one of:
337
172
 
338
- Create or update `middleware.ts` in your Next.js project root:
173
+ ### Middleware (recommended)
339
174
 
340
175
  ```typescript
176
+ // middleware.ts
341
177
  import { NextResponse } from 'next/server';
342
178
  import type { NextRequest } from 'next/server';
343
179
  import { setNextProxyHeaders } from 'test-proxy-recorder/nextjs';
344
180
 
345
181
  export function middleware(request: NextRequest) {
346
182
  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
-
183
+ setNextProxyHeaders(request, response); // no-op in production
352
184
  return response;
353
185
  }
354
186
  ```
355
187
 
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:
188
+ ### Manual header forwarding
363
189
 
364
190
  ```typescript
365
- // app/api/data/route.ts
366
191
  import { headers } from 'next/headers';
367
192
  import { createHeadersWithRecordingId } from 'test-proxy-recorder/nextjs';
368
193
 
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
- }
380
- ```
381
-
382
- ### Option 3: Using getRecordingId Helper
383
-
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
- }
194
+ const res = await fetch('http://localhost:8100/api/data', {
195
+ headers: createHeadersWithRecordingId(await headers(), {
196
+ 'Content-Type': 'application/json',
197
+ }),
198
+ });
402
199
  ```
403
200
 
404
201
  ## Control Endpoint
405
202
 
406
- The proxy exposes a control endpoint at `/__control` for programmatic mode switching and configuration retrieval.
203
+ The proxy exposes `/__control` for programmatic mode switching.
407
204
 
408
- ### GET - Retrieve Proxy Configuration
409
-
410
- Get the current proxy configuration including recordings directory, mode, and active session ID.
411
-
412
- **Via HTTP:**
413
205
  ```bash
206
+ # Get current state
414
207
  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
208
 
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
-
448
- # Switch to transparent mode
209
+ # Switch modes
449
210
  curl -X POST http://localhost:8100/__control \
450
211
  -H "Content-Type: application/json" \
451
- -d '{"mode": "transparent"}'
212
+ -d '{"mode": "record", "id": "my-test-1"}'
452
213
  ```
453
214
 
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
- });
465
- ```
466
-
467
- ### Control Request Interface
468
-
469
215
  ```typescript
470
216
  interface ControlRequest {
471
217
  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;
218
+ id?: string; // required for record/replay
219
+ timeout?: number; // auto-reset timeout in ms (default: 120000)
480
220
  }
481
221
  ```
482
222
 
483
- ## Typical Workflow
484
-
485
- ### Initial Recording
486
-
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'`
493
-
494
- ### Running with Replay
495
-
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
500
-
501
- ### Updating Recordings
502
-
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
507
-
508
- ## Recording Format
223
+ ## API Reference
509
224
 
510
- Recordings are stored in two formats depending on the recording type:
225
+ ### `playwrightProxy`
511
226
 
512
- **Server-side recordings** (via proxy): JSON files with `.mock.json` extension
513
- **Client-side recordings** (via HAR): HTTP Archive files with `.har` extension
227
+ ```typescript
228
+ const playwrightProxy: {
229
+ before(
230
+ page: Page,
231
+ testInfo: TestInfo,
232
+ mode: 'record' | 'replay' | 'transparent',
233
+ options?: number | { url?: string | RegExp; timeout?: number }
234
+ ): Promise<void>;
514
235
 
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
236
+ teardown(): Promise<void>;
237
+ };
521
238
  ```
522
239
 
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.
524
-
525
- ## Troubleshooting
526
-
527
- ### Proxy not responding
240
+ ### `setProxyMode`
528
241
 
529
- **Check if proxy is running**:
530
-
531
- ```bash
532
- curl http://localhost:8100/__control
242
+ ```typescript
243
+ function setProxyMode(
244
+ mode: 'record' | 'replay' | 'transparent',
245
+ id?: string,
246
+ timeout?: number
247
+ ): Promise<void>;
533
248
  ```
534
249
 
535
- **Check port availability**:
250
+ ### Next.js helpers (`test-proxy-recorder/nextjs`)
536
251
 
537
- ```bash
538
- lsof -i :8100
252
+ ```typescript
253
+ function setNextProxyHeaders(request: NextRequest, response: NextResponse): void;
254
+ function getRecordingId(headers: NextRequest | Headers): string | null;
255
+ function createHeadersWithRecordingId(
256
+ headers: NextRequest | Headers,
257
+ additional?: Record<string, string>
258
+ ): Record<string, string>;
539
259
  ```
540
260
 
541
- ### No recordings saved
542
-
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
261
+ ## Typical Workflow
547
262
 
548
- ### Test fails in replay mode
263
+ ```
264
+ 1. Record start proxy + app + backend, run tests with 'record' mode
265
+ 2. Commit git add e2e/recordings/
266
+ 3. Replay start proxy + app (no backend), run tests with 'replay' mode
267
+ 4. Update re-record when API changes, commit new recordings
268
+ ```
549
269
 
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
270
+ ## Next.js 16
554
271
 
555
- ### Recordings not matching requests
272
+ Next.js 16 uses `proxy.ts` as the middleware entry point (replaces `middleware.ts`). Place it at the project root alongside `next.config.ts`:
556
273
 
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
274
+ ```typescript
275
+ // proxy.ts (Next.js 16 middleware convention)
276
+ import { NextResponse } from 'next/server';
277
+ import type { NextRequest } from 'next/server';
278
+ import { setNextProxyHeaders } from 'test-proxy-recorder/nextjs';
561
279
 
280
+ export function middleware(request: NextRequest) {
281
+ const response = NextResponse.next();
282
+ setNextProxyHeaders(request, response);
283
+ return response;
284
+ }
562
285
 
563
- ## API Reference
286
+ export const config = {
287
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
288
+ };
289
+ ```
564
290
 
565
- ### ProxyServer Class
291
+ **package.json scripts** — start services from scripts, not from `playwright.config.ts`:
566
292
 
567
- ```typescript
568
- class ProxyServer {
569
- constructor(targets: string[], recordingsDir: string);
570
- async init(): Promise<void>;
571
- listen(port: number): http.Server;
293
+ ```json
294
+ {
295
+ "scripts": {
296
+ "mock": "node mock-backend/server.mjs",
297
+ "proxy": "test-proxy-recorder http://localhost:3002 -p 8100 -d ./e2e/recordings",
298
+ "start:all": "concurrently \"pnpm mock\" \"pnpm proxy\" \"pnpm build && next start --port 3000\""
299
+ }
572
300
  }
573
301
  ```
574
302
 
575
- ### Playwright Integration
303
+ ## Example App
576
304
 
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
- }
591
-
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>;
305
+ [`apps/example-nextjs16`](apps/example-nextjs16) is a full working example: a Next.js 16 todo app wired up with a mock backend, proxy, and Playwright e2e tests in record/replay mode.
602
306
 
603
- // Global teardown - switches proxy to transparent mode
604
- // Use in Playwright's globalTeardown configuration
605
- async teardown(): Promise<void>;
606
- };
307
+ ```text
308
+ apps/example-nextjs16/
309
+ app/ Next.js pages and components
310
+ mock-backend/ Standalone Node.js HTTP server (port 3002)
311
+ e2e/ Playwright tests + recordings
312
+ proxy.ts Next.js 16 middleware — forwards session headers to SSR fetches
313
+ ```
607
314
 
608
- // Direct mode control
609
- async function setProxyMode(
610
- mode: 'record' | 'replay' | 'transparent',
611
- id?: string,
612
- timeout?: number
613
- ): Promise<void>;
315
+ **Three-service architecture:**
614
316
 
615
- // Recording ID header constant
616
- const RECORDING_ID_HEADER: string; // 'x-test-rcrd-id'
317
+ ```text
318
+ Browser ──> Proxy (8100) ──> Mock Backend (3002)
319
+ Next.js SSR ──> Proxy (8100) ──> Mock Backend (3002)
617
320
  ```
618
321
 
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
322
+ Start everything and run the record/replay cycle:
624
323
 
625
- ### Next.js Integration
324
+ ```bash
325
+ # Start all services (mock backend + proxy + Next.js)
326
+ pnpm --filter example-nextjs16 start:all
626
327
 
627
- **IMPORTANT**: Use the `/nextjs` import path to avoid webpack bundling issues in Next.js:
328
+ # Record tests (run against live services, save to e2e/recordings/)
329
+ pnpm --filter example-nextjs16 test:e2e:record
628
330
 
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>;
331
+ # Replay tests (no backend needed — served from recordings)
332
+ pnpm --filter example-nextjs16 test:e2e
655
333
  ```
656
334
 
657
- ### Control Endpoint
335
+ ## Parallel Replay: Do Not Call `teardown()` Per-Test
658
336
 
659
- The control endpoint supports both GET and POST methods.
337
+ `playwrightProxy.teardown()` sets the **global** proxy mode to `transparent`. With `fullyParallel: true`, each Playwright worker runs its own `test.afterAll`. If a fast test completes and calls `teardown()` while a slower test (e.g., one with more interaction steps) is still running, the proxy switches to transparent mid-test. The remaining requests are forwarded to the real backend instead of being replayed, causing failures.
660
338
 
661
- **GET `/__control`** - Retrieve proxy configuration:
339
+ **Wrong:**
662
340
 
663
341
  ```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
- }
670
- ```
671
-
672
- **POST `/__control`** - Switch proxy mode:
673
-
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
- }
681
-
682
- // Response
683
- {
684
- success: boolean;
685
- mode: string;
686
- id: string | null;
687
- timeout: number;
688
- recordingsDir: string;
689
- }
342
+ // ❌ breaks parallel replay — teardown() affects all sessions globally
343
+ test.afterAll(async () => {
344
+ await playwrightProxy.teardown();
345
+ });
690
346
  ```
691
347
 
692
- **Note**: Switching to replay mode automatically resets session counters (clears served recordings tracker), allowing replay from the beginning.
348
+ **Correct:** omit `test.afterAll`. Session cleanup is automatic via `context.on('close')` → `cleanupSession()`. Use a [global teardown](https://playwright.dev/docs/test-global-setup-teardown) if you need to reset the proxy after a full test run.
693
349
 
694
350
  ## Requirements
695
351
 
696
352
  - Node.js >= 22.0.0
697
- - @playwright/test >= 1.0.0 (for Playwright integration)
353
+ - @playwright/test >= 1.0.0 (peer dependency)
698
354
 
699
355
  ## Contributing
700
356
 
701
- Contributions are welcome! Please feel free to submit a Pull Request.
357
+ Contributions welcome! Please submit a Pull Request.
702
358
 
703
359
  ## License
704
360