test-proxy-recorder 0.2.0 → 0.3.1

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
@@ -21,6 +21,7 @@ HTTP proxy server for recording and replaying network requests in testing. Works
21
21
  - [Complete Setup Guide](#complete-setup-guide)
22
22
  - [CLI Usage](#cli-usage)
23
23
  - [Playwright Integration](#playwright-integration)
24
+ - [Next.js Integration](#nextjs-integration)
24
25
  - [Control Endpoint](#control-endpoint)
25
26
  - [Typical Workflow](#typical-workflow)
26
27
  - [Recording Format](#recording-format)
@@ -58,7 +59,7 @@ Add to `package.json`:
58
59
  ```json
59
60
  {
60
61
  "scripts": {
61
- "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --recordings-dir ./e2e/recordings"
62
+ "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --dir ./e2e/recordings"
62
63
  }
63
64
  }
64
65
  ```
@@ -72,7 +73,7 @@ npm install --save-dev concurrently
72
73
  ```json
73
74
  {
74
75
  "scripts": {
75
- "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --recordings-dir ./e2e/recordings",
76
+ "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --dir ./e2e/recordings",
76
77
  "dev:proxy": "concurrently -n \"proxy,app\" -c \"blue,green\" \"npm run proxy\" \"INTERNAL_API_URL=http://localhost:8100 npm run dev\""
77
78
  }
78
79
  }
@@ -128,14 +129,10 @@ Create `e2e/example.spec.ts`:
128
129
  import { test, expect } from '@playwright/test';
129
130
  import { playwrightProxy } from 'test-proxy-recorder';
130
131
 
131
- // Setup afterEach hook to reset proxy after each test
132
- test.afterEach(async ({ page: _page }, testInfo) => {
133
- await playwrightProxy.after(testInfo);
134
- });
135
-
136
132
  test('example test with proxy', async ({ page }, testInfo) => {
137
133
  // Set proxy mode: 'record' to capture, 'replay' to use recordings
138
- await playwrightProxy.before(testInfo, 'replay');
134
+ // This automatically sets up page.on('close') for cleanup
135
+ await playwrightProxy.before(page, testInfo, 'replay');
139
136
 
140
137
  await page.goto('/');
141
138
  await expect(page.getByText('Welcome')).toBeVisible();
@@ -147,13 +144,13 @@ test('example test with proxy', async ({ page }, testInfo) => {
147
144
  **First run (record mode)**:
148
145
 
149
146
  ```typescript
150
- await playwrightProxy.before(testInfo, 'record');
147
+ await playwrightProxy.before(page, testInfo, 'record');
151
148
  ```
152
149
 
153
150
  **Subsequent runs (replay mode)**:
154
151
 
155
152
  ```typescript
156
- await playwrightProxy.before(testInfo, 'replay');
153
+ await playwrightProxy.before(page, testInfo, 'replay');
157
154
  ```
158
155
 
159
156
  ## CLI Usage
@@ -168,7 +165,7 @@ test-proxy-recorder <target-url> [options]
168
165
 
169
166
  - `<target-url>` - Backend API URL (positional argument, required)
170
167
  - `--port, -p <number>` - Port to listen on (default: 8080)
171
- - `--recordings-dir, -r <path>` - Directory to store recordings (default: ./recordings)
168
+ - `--dir, -d <path>` - Directory to store recordings (default: ./recordings)
172
169
  - `--help, -h` - Show help
173
170
 
174
171
  ### Examples
@@ -178,7 +175,7 @@ test-proxy-recorder <target-url> [options]
178
175
  test-proxy-recorder http://localhost:8000
179
176
 
180
177
  # Custom port and recordings directory
181
- test-proxy-recorder http://localhost:8000 --port 8100 --recordings-dir ./mocks
178
+ test-proxy-recorder http://localhost:8000 --port 8100 --dir ./mocks
182
179
 
183
180
  # Multiple targets (experimental)
184
181
  test-proxy-recorder http://localhost:8000 http://localhost:9000 --port 8100
@@ -186,24 +183,26 @@ test-proxy-recorder http://localhost:8000 http://localhost:9000 --port 8100
186
183
 
187
184
  ## Playwright Integration
188
185
 
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
+
189
192
  ### Basic Test Structure
190
193
 
191
- Every test file using the proxy should follow this pattern:
194
+ Every test using the proxy should follow this pattern:
192
195
 
193
196
  ```typescript
194
197
  import { test } from '@playwright/test';
195
198
  import { playwrightProxy } from 'test-proxy-recorder';
196
199
 
197
- // Setup afterEach hook once per test file
198
- test.afterEach(async ({ page: _page }, testInfo) => {
199
- await playwrightProxy.after(testInfo);
200
- });
201
-
202
200
  test('test name', async ({ page }, testInfo) => {
203
- // 1. Set mode BEFORE test actions
204
- await playwrightProxy.before(testInfo, 'replay');
201
+ // Set mode BEFORE test actions
202
+ // This automatically sets the recording ID header and cleanup handler
203
+ await playwrightProxy.before(page, testInfo, 'replay');
205
204
 
206
- // 2. Test code
205
+ // Test code
207
206
  await page.goto('/page');
208
207
  // Test assertions...
209
208
  });
@@ -215,14 +214,9 @@ test('test name', async ({ page }, testInfo) => {
215
214
  import { test } from '@playwright/test';
216
215
  import { playwrightProxy } from 'test-proxy-recorder';
217
216
 
218
- // Setup afterEach hook to automatically cleanup after each test
219
- test.afterEach(async ({ page: _page }, testInfo) => {
220
- await playwrightProxy.after(testInfo);
221
- });
222
-
223
217
  // Recording mode - captures API responses
224
218
  test('create user', async ({ page }, testInfo) => {
225
- await playwrightProxy.before(testInfo, 'record');
219
+ await playwrightProxy.before(page, testInfo, 'record');
226
220
 
227
221
  await page.goto('/users/new');
228
222
  await page.fill('[name="username"]', 'testuser');
@@ -231,7 +225,7 @@ test('create user', async ({ page }, testInfo) => {
231
225
 
232
226
  // Replay mode - uses recorded responses
233
227
  test('create user', async ({ page }, testInfo) => {
234
- await playwrightProxy.before(testInfo, 'replay');
228
+ await playwrightProxy.before(page, testInfo, 'replay');
235
229
 
236
230
  await page.goto('/users/new');
237
231
  await page.fill('[name="username"]', 'testuser');
@@ -274,6 +268,78 @@ export default defineConfig({
274
268
  });
275
269
  ```
276
270
 
271
+ ## Next.js Integration
272
+
273
+ 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.
274
+
275
+ ### Option 1: Using Next.js Middleware (Recommended)
276
+
277
+ Create or update `middleware.ts` in your Next.js project root:
278
+
279
+ ```typescript
280
+ import { NextResponse } from 'next/server';
281
+ import type { NextRequest } from 'next/server';
282
+ import { setNextProxyHeaders } from 'test-proxy-recorder/nextjs';
283
+
284
+ export function middleware(request: NextRequest) {
285
+ const response = NextResponse.next();
286
+
287
+ // Forward the recording ID header during tests
288
+ // Only runs in non-production or when TEST_PROXY_RECORDER_ENABLED=true
289
+ setNextProxyHeaders(request, response);
290
+
291
+ return response;
292
+ }
293
+ ```
294
+
295
+ **Environment Variables:**
296
+ - Automatically skipped when `NODE_ENV=production`
297
+ - Can be explicitly enabled in production with `TEST_PROXY_RECORDER_ENABLED=true`
298
+
299
+ ### Option 2: Manual Header Forwarding in API Routes
300
+
301
+ For API routes or server components, manually include the header in fetch requests:
302
+
303
+ ```typescript
304
+ // app/api/data/route.ts
305
+ import { headers } from 'next/headers';
306
+ import { createHeadersWithRecordingId } from 'test-proxy-recorder/nextjs';
307
+
308
+ export async function GET() {
309
+ const requestHeaders = await headers();
310
+
311
+ const response = await fetch('http://localhost:8100/api/data', {
312
+ headers: createHeadersWithRecordingId(requestHeaders, {
313
+ 'Content-Type': 'application/json',
314
+ })
315
+ });
316
+
317
+ return Response.json(await response.json());
318
+ }
319
+ ```
320
+
321
+ ### Option 3: Using getRecordingId Helper
322
+
323
+ For more control, extract the recording ID and use it manually:
324
+
325
+ ```typescript
326
+ import { headers } from 'next/headers';
327
+ import { getRecordingId, RECORDING_ID_HEADER } from 'test-proxy-recorder/nextjs';
328
+
329
+ export async function GET() {
330
+ const recordingId = getRecordingId(await headers());
331
+
332
+ const response = await fetch('http://localhost:8100/api/data', {
333
+ headers: {
334
+ 'Content-Type': 'application/json',
335
+ ...(recordingId && { [RECORDING_ID_HEADER]: recordingId })
336
+ }
337
+ });
338
+
339
+ return Response.json(await response.json());
340
+ }
341
+ ```
342
+
277
343
  ## Control Endpoint
278
344
 
279
345
  The proxy exposes a control endpoint at `/__control` for programmatic mode switching.
@@ -410,20 +476,20 @@ class ProxyServer {
410
476
  ### Playwright Integration
411
477
 
412
478
  ```typescript
413
- import { playwrightProxy, setProxyMode } from 'test-proxy-recorder';
479
+ import { playwrightProxy, setProxyMode, RECORDING_ID_HEADER } from 'test-proxy-recorder';
480
+ import type { Page } from '@playwright/test';
414
481
 
415
482
  // Main helper for Playwright tests
416
483
  const playwrightProxy = {
417
- // Set proxy mode before test
484
+ // Set proxy mode before test and configure page with recording ID header
485
+ // Automatically sets up page.on('close') handler for cleanup
418
486
  async before(
487
+ page: Page,
419
488
  testInfo: TestInfo,
420
489
  mode: 'record' | 'replay' | 'transparent',
421
490
  timeout?: number
422
491
  ): Promise<void>;
423
492
 
424
- // Reset replay session and return to transparent mode after test
425
- // Resets sequence counters to ensure next replay starts fresh
426
- async after(testInfo: TestInfo): Promise<void>;
427
493
 
428
494
  // Global teardown - switches proxy to transparent mode
429
495
  // Use in Playwright's globalTeardown configuration
@@ -436,6 +502,41 @@ async function setProxyMode(
436
502
  id?: string,
437
503
  timeout?: number
438
504
  ): Promise<void>;
505
+
506
+ // Recording ID header constant
507
+ const RECORDING_ID_HEADER: string; // 'x-test-rcrd-id'
508
+ ```
509
+
510
+ ### Next.js Integration
511
+
512
+ **IMPORTANT**: Use the `/nextjs` import path to avoid webpack bundling issues in Next.js:
513
+
514
+ ```typescript
515
+ import {
516
+ setNextProxyHeaders,
517
+ getRecordingId,
518
+ createHeadersWithRecordingId,
519
+ RECORDING_ID_HEADER
520
+ } from 'test-proxy-recorder/nextjs';
521
+ import type { NextRequest, NextResponse } from 'next/server';
522
+
523
+ // Forward recording ID header in Next.js middleware
524
+ // Automatically skipped in production unless TEST_PROXY_RECORDER_ENABLED=true
525
+ function setNextProxyHeaders(
526
+ request: NextRequest,
527
+ response: NextResponse
528
+ ): void;
529
+
530
+ // Get recording ID from request headers
531
+ function getRecordingId(
532
+ requestHeaders: NextRequest | Headers
533
+ ): string | null;
534
+
535
+ // Create headers object with recording ID for fetch requests
536
+ function createHeadersWithRecordingId(
537
+ requestHeaders: NextRequest | Headers,
538
+ additionalHeaders?: Record<string, string>
539
+ ): Record<string, string>;
439
540
  ```
440
541
 
441
542
  ### Control Endpoint
@@ -1,4 +1,4 @@
1
- import { TestInfo } from '@playwright/test';
1
+ import { TestInfo, Page } from '@playwright/test';
2
2
  import http from 'node:http';
3
3
 
4
4
  declare const Modes: {
@@ -82,22 +82,18 @@ declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
82
82
  declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
83
83
  /**
84
84
  * Playwright test fixture helper for managing proxy mode
85
- * Use this in beforeEach/afterEach hooks
85
+ * Use this in test functions with page.on('close') for automatic cleanup
86
86
  */
87
87
  declare const playwrightProxy: {
88
88
  /**
89
- * Setup before test - sets the proxy mode
89
+ * Setup before test - sets the proxy mode and configures page with custom header
90
+ * Automatically sets up page.on('close') handler for cleanup
91
+ * @param page - Playwright page object
90
92
  * @param testInfo - Playwright test info object
91
93
  * @param mode - The proxy mode to use for this test
92
94
  * @param timeout - Optional timeout in milliseconds
93
95
  */
94
- before(testInfo: PlaywrightTestInfo, mode: Mode, timeout?: number): Promise<void>;
95
- /**
96
- * Cleanup after test - resets replay session by re-entering replay mode
97
- * switchToReplayMode automatically clears sequence counters
98
- * @param testInfo - Playwright test info object
99
- */
100
- after(testInfo: PlaywrightTestInfo): Promise<void>;
96
+ before(page: Page, testInfo: PlaywrightTestInfo, mode: Mode, timeout?: number): Promise<void>;
101
97
  /**
102
98
  * Global teardown - switches proxy to transparent mode
103
99
  * Use this in Playwright's globalTeardown to ensure clean state
@@ -1,4 +1,4 @@
1
- import { TestInfo } from '@playwright/test';
1
+ import { TestInfo, Page } from '@playwright/test';
2
2
  import http from 'node:http';
3
3
 
4
4
  declare const Modes: {
@@ -82,22 +82,18 @@ declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
82
82
  declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
83
83
  /**
84
84
  * Playwright test fixture helper for managing proxy mode
85
- * Use this in beforeEach/afterEach hooks
85
+ * Use this in test functions with page.on('close') for automatic cleanup
86
86
  */
87
87
  declare const playwrightProxy: {
88
88
  /**
89
- * Setup before test - sets the proxy mode
89
+ * Setup before test - sets the proxy mode and configures page with custom header
90
+ * Automatically sets up page.on('close') handler for cleanup
91
+ * @param page - Playwright page object
90
92
  * @param testInfo - Playwright test info object
91
93
  * @param mode - The proxy mode to use for this test
92
94
  * @param timeout - Optional timeout in milliseconds
93
95
  */
94
- before(testInfo: PlaywrightTestInfo, mode: Mode, timeout?: number): Promise<void>;
95
- /**
96
- * Cleanup after test - resets replay session by re-entering replay mode
97
- * switchToReplayMode automatically clears sequence counters
98
- * @param testInfo - Playwright test info object
99
- */
100
- after(testInfo: PlaywrightTestInfo): Promise<void>;
96
+ before(page: Page, testInfo: PlaywrightTestInfo, mode: Mode, timeout?: number): Promise<void>;
101
97
  /**
102
98
  * Global teardown - switches proxy to transparent mode
103
99
  * Use this in Playwright's globalTeardown to ensure clean state