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 +134 -33
- package/dist/{index-nmNRt1WE.d.cts → index-CVuiglPk.d.cts} +6 -10
- package/dist/{index-nmNRt1WE.d.ts → index-CVuiglPk.d.ts} +6 -10
- package/dist/index.cjs +291 -258
- package/dist/index.d.cts +25 -8
- package/dist/index.d.ts +25 -8
- package/dist/index.mjs +288 -259
- package/dist/nextjs/index.cjs +43 -0
- package/dist/nextjs/index.d.cts +89 -0
- package/dist/nextjs/index.d.ts +89 -0
- package/dist/nextjs/index.mjs +38 -0
- package/dist/playwright/index.cjs +20 -11
- package/dist/playwright/index.d.cts +1 -1
- package/dist/playwright/index.d.ts +1 -1
- package/dist/playwright/index.mjs +20 -11
- package/dist/proxy.js +239 -246
- package/package.json +6 -1
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 --
|
|
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 --
|
|
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
|
-
|
|
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
|
-
- `--
|
|
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 --
|
|
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
|
|
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
|
-
//
|
|
204
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|