test-proxy-recorder 0.1.5 → 0.1.7

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
@@ -9,328 +9,455 @@ HTTP proxy server for recording and replaying network requests in testing. Works
9
9
 
10
10
  ## Features
11
11
 
12
- - **Fast CI/CD Tests**: Record API responses once with real backend, replay them on CI/CD
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 NextJS.
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
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
16
17
 
17
- ## Installation
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
+ - [Control Endpoint](#control-endpoint)
25
+ - [Typical Workflow](#typical-workflow)
26
+ - [Recording Format](#recording-format)
27
+ - [Troubleshooting](#troubleshooting)
28
+ - [API Reference](#api-reference)
29
+
30
+ ## How It Works
31
+
32
+ The proxy server runs continuously and can switch between three modes per test:
33
+
34
+ ### 1. Transparent Mode (Default)
35
+
36
+ Passes requests through to the backend without recording or replaying.
37
+
38
+ ### 2. Record Mode
39
+
40
+ Captures all HTTP requests/responses and WebSocket messages to disk. Each test gets its own recording file based on the test name.
41
+
42
+ ### 3. Replay Mode
43
+
44
+ Replays previously recorded responses from disk instead of hitting the real API. Perfect for fast, deterministic tests.
45
+
46
+ ## Complete Setup Guide
47
+
48
+ ### Step 1: Install Package
49
+
50
+ ```bash
51
+ npm install --save-dev test-proxy-recorder
52
+ ```
53
+
54
+ ### Step 2: Add NPM Scripts
55
+
56
+ Add to `package.json`:
57
+
58
+ ```json
59
+ {
60
+ "scripts": {
61
+ "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --recordings-dir ./e2e/recordings"
62
+ }
63
+ }
64
+ ```
65
+
66
+ **RECOMMENDED**: Use `concurrently` to run proxy and app together:
67
+
68
+ ```bash
69
+ npm install --save-dev concurrently
70
+ ```
71
+
72
+ ```json
73
+ {
74
+ "scripts": {
75
+ "proxy": "test-proxy-recorder http://localhost:8000 --port 8100 --recordings-dir ./e2e/recordings",
76
+ "dev:proxy": "concurrently -n \"proxy,app\" -c \"blue,green\" \"npm run proxy\" \"INTERNAL_API_URL=http://localhost:8100 npm run dev\""
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Step 3: Configure Git for Recordings
82
+
83
+ **CRITICAL**: Recordings must be committed to git for CI/CD replay.
84
+
85
+ Create or update your `.gitattributes` file:
86
+
87
+ ```gitattributes
88
+ /e2e/recordings/** binary
89
+ ```
90
+
91
+ This marks recording files as binary, which causes long mock files to be collapsed/folded in Pull Request diffs for better readability.
92
+
93
+ **DO NOT** add `e2e/recordings` to `.gitignore`. Recordings need to be versioned in git for CI/CD to use them.
94
+
95
+ **Note**: The recordings directory will be created automatically when you first record a test - no need to create it manually.
96
+
97
+ ### Step 4: Create Playwright Global Teardown (Recommended)
98
+
99
+ Create `e2e/global-teardown.ts`:
100
+
101
+ ```typescript
102
+ import { setProxyMode } from 'test-proxy-recorder';
103
+
104
+ async function globalTeardown() {
105
+ await setProxyMode('transparent');
106
+ }
107
+
108
+ export default globalTeardown;
109
+ ```
110
+
111
+ Update `playwright.config.ts`:
112
+
113
+ ```typescript
114
+ import { defineConfig } from '@playwright/test';
115
+
116
+ export default defineConfig({
117
+ testDir: './e2e',
118
+ globalTeardown: './e2e/global-teardown.ts',
119
+ // ... rest of config
120
+ });
121
+ ```
122
+
123
+ ### Step 5: Create Example Test
124
+
125
+ Create `e2e/example.spec.ts`:
126
+
127
+ ```typescript
128
+ import { test, expect } from '@playwright/test';
129
+ import { playwrightProxy } from 'test-proxy-recorder';
130
+
131
+ test('example test with proxy', async ({ page }, testInfo) => {
132
+ // Set proxy mode: 'record' to capture, 'replay' to use recordings
133
+ await playwrightProxy.before(testInfo, 'replay');
134
+
135
+ await page.goto('/');
136
+ await expect(page.getByText('Welcome')).toBeVisible();
137
+
138
+ // Always cleanup after test
139
+ await playwrightProxy.after(testInfo);
140
+ });
141
+ ```
142
+
143
+ ### Step 6: Run Tests
144
+
145
+ **First run (record mode)**:
146
+
147
+ ```typescript
148
+ await playwrightProxy.before(testInfo, 'record');
149
+ ```
150
+
151
+ **Subsequent runs (replay mode)**:
152
+
153
+ ```typescript
154
+ await playwrightProxy.before(testInfo, 'replay');
155
+ ```
156
+
157
+ ## CLI Usage
158
+
159
+ ### Basic Command
160
+
161
+ ```bash
162
+ test-proxy-recorder <target-url> [options]
163
+ ```
164
+
165
+ ### CLI Options
166
+
167
+ - `<target-url>` - Backend API URL (positional argument, required)
168
+ - `--port, -p <number>` - Port to listen on (default: 8080)
169
+ - `--recordings-dir, -r <path>` - Directory to store recordings (default: ./recordings)
170
+ - `--help, -h` - Show help
171
+
172
+ ### Examples
18
173
 
19
174
  ```bash
20
- npm install test-proxy-recorder
21
- # or
22
- pnpm add test-proxy-recorder
23
- # or
24
- yarn add test-proxy-recorder
175
+ # Basic usage
176
+ test-proxy-recorder http://localhost:8000
177
+
178
+ # Custom port and recordings directory
179
+ test-proxy-recorder http://localhost:8000 --port 8100 --recordings-dir ./mocks
180
+
181
+ # Multiple targets (experimental)
182
+ test-proxy-recorder http://localhost:8000 http://localhost:9000 --port 8100
25
183
  ```
26
184
 
27
- ## Quick Start
185
+ ## Playwright Integration
28
186
 
29
- 1. Run proxy with your backend API as a target `test-proxy-recorder --port 8100 --target http://localhost:8000 --recordings ./recordings`, here your backend on port 8000 as target, proxy on port 8100.
30
- 2. Point your Frontend app to proxy port, 8100 as example
31
- 3. The proxy runs continuously in the background. Tests control the recording/replay mode using `playwrightProxy.before()` and `playwrightProxy.after()`:
187
+ ### Basic Test Structure
188
+
189
+ Every test using the proxy should follow this pattern:
32
190
 
33
191
  ```typescript
34
192
  import { test } from '@playwright/test';
35
193
  import { playwrightProxy } from 'test-proxy-recorder';
36
194
 
37
- test('Test UI with API responses', async ({ page }, testInfo) => {
38
- // Set proxy to recording mode to record mocks, sanitized test title will be used as file name
195
+ test('test name', async ({ page }, testInfo) => {
196
+ // 1. Set mode BEFORE test actions
197
+ await playwrightProxy.before(testInfo, 'replay');
198
+
199
+ // 2. Test code
200
+ await page.goto('/page');
201
+
202
+ // 3. Reset mode AFTER test completes
203
+ await playwrightProxy.after(testInfo);
204
+ });
205
+ ```
206
+
207
+ ### Recording vs Replay
208
+
209
+ ```typescript
210
+ // Recording mode - captures API responses
211
+ test('create user', async ({ page }, testInfo) => {
39
212
  await playwrightProxy.before(testInfo, 'record');
40
213
 
41
- // Make requests - they will be recorded
42
- await page.goto('/myPage');
43
- /// ... test content ...
214
+ await page.goto('/users/new');
215
+ await page.fill('[name="username"]', 'testuser');
216
+ await page.click('button[type="submit"]');
44
217
 
45
- // Save mock and return to transparent mode
46
218
  await playwrightProxy.after(testInfo);
47
219
  });
48
220
 
49
- test('replay recorded responses', async ({ page }, testInfo) => {
50
- // Set proxy to replay mode - uses recording from test above
221
+ // Replay mode - uses recorded responses
222
+ test('create user', async ({ page }, testInfo) => {
51
223
  await playwrightProxy.before(testInfo, 'replay');
52
224
 
53
- await page.goto('/myPage');
54
- /// ... test content ...
225
+ await page.goto('/users/new');
226
+ await page.fill('[name="username"]', 'testuser');
227
+ await page.click('button[type="submit"]');
55
228
 
56
229
  await playwrightProxy.after(testInfo);
57
230
  });
58
231
  ```
59
232
 
60
- ## How It Works
233
+ ### Test Naming
61
234
 
62
- The proxy server runs continuously and can switch between three modes:
235
+ Recording files are auto-generated from test names:
63
236
 
64
- ### 1. Transparent Mode (Default)
237
+ - Test: `"create a user"`
238
+ - File: `create-a-user.mock.json`
65
239
 
66
- Simply proxies requests to the backend without recording or replaying.
240
+ **Important**: Keep test names stable for replay to work correctly.
67
241
 
68
- ### 2. Record Mode
242
+ ### Global Teardown (Recommended)
69
243
 
70
- Captures all HTTP requests/responses and WebSocket messages to disk. Each test gets its own recording file based on the test name.
244
+ Create `e2e/global-teardown.ts`:
71
245
 
72
- ### 3. Replay Mode
246
+ ```typescript
247
+ import { setProxyMode } from 'test-proxy-recorder';
73
248
 
74
- Replays previously recorded responses from disk instead of hitting the real API. Perfect for fast, deterministic tests.
249
+ async function globalTeardown() {
250
+ await setProxyMode('transparent');
251
+ }
75
252
 
76
- ## Modes Control
253
+ export default globalTeardown;
254
+ ```
77
255
 
78
- ### Via Playwright
256
+ Update `playwright.config.ts`:
79
257
 
80
258
  ```typescript
81
- // Recording mode
82
- await playwrightProxy.before(testInfo, 'recording');
83
- // ... test code ...
84
- await playwrightProxy.after(testInfo);
259
+ import { defineConfig } from '@playwright/test';
85
260
 
86
- // Replay mode
87
- await playwrightProxy.before(testInfo, 'replay');
88
- // ... test code ...
89
- await playwrightProxy.after(testInfo);
261
+ export default defineConfig({
262
+ testDir: './e2e',
263
+ globalTeardown: './e2e/global-teardown.ts',
264
+ // ... rest of config
265
+ });
90
266
  ```
91
267
 
92
- ### Via HTTP Control Endpoint
268
+ ## Control Endpoint
93
269
 
94
- Using curl:
270
+ The proxy exposes a control endpoint at `/__control` for programmatic mode switching.
271
+
272
+ ### Via HTTP
95
273
 
96
274
  ```bash
97
275
  # Switch to record mode
98
276
  curl -X POST http://localhost:8100/__control \
99
277
  -H "Content-Type: application/json" \
100
- -d '{"mode": "record", "id": "my-testfile-1", "timeout": 30000}'
278
+ -d '{"mode": "record", "id": "my-test-1", "timeout": 30000}'
101
279
 
102
280
  # Switch to replay mode
103
281
  curl -X POST http://localhost:8100/__control \
104
282
  -H "Content-Type: application/json" \
105
- -d '{"mode": "replay", "id": "my-testfile-1"}'
283
+ -d '{"mode": "replay", "id": "my-test-1"}'
106
284
 
107
285
  # Switch to transparent mode
108
286
  curl -X POST http://localhost:8100/__control \
109
287
  -H "Content-Type: application/json" \
110
- -d '{"mode": "transparent", "id": "my-testfile-1"}'
288
+ -d '{"mode": "transparent"}'
111
289
  ```
112
290
 
113
- Using JavaScript fetch:
291
+ ### Via JavaScript
114
292
 
115
293
  ```javascript
116
- // Switch to record mode
117
294
  await fetch('http://localhost:8100/__control', {
118
295
  method: 'POST',
119
- headers: {
120
- 'Content-Type': 'application/json',
121
- },
296
+ headers: { 'Content-Type': 'application/json' },
122
297
  body: JSON.stringify({
123
298
  mode: 'record',
124
- id: 'my-testfile-1',
125
- timeout: 30000
126
- })
127
- });
128
-
129
- // Switch to replay mode
130
- await fetch('http://localhost:8100/__control', {
131
- method: 'POST',
132
- headers: {
133
- 'Content-Type': 'application/json',
134
- },
135
- body: JSON.stringify({
136
- mode: 'replay',
137
- id: 'my-testfile-1'
138
- })
139
- });
140
-
141
- // Switch to transparent mode
142
- await fetch('http://localhost:8100/__control', {
143
- method: 'POST',
144
- headers: {
145
- 'Content-Type': 'application/json',
146
- },
147
- body: JSON.stringify({
148
- mode: 'transparent',
299
+ id: 'my-test-1',
300
+ timeout: 30000 // Optional: auto-reset after 30s
149
301
  })
150
302
  });
151
303
  ```
152
304
 
153
- ## Usage
154
-
155
- ```bash
156
- # Start proxy server
157
- test-proxy-recorder --port 8100 --target http://localhost:8000 --recordings ./recordings
158
- ```
159
-
160
- ### CLI Options
161
-
162
- - `--port, -p`: Port to listen on (default: 8080)
163
- - `--target, -t`: Backend target URL (can add multiple targets)
164
- - `--recordings, -r`: Directory to store recordings (default: ./recordings)
165
-
166
- ## API
167
-
168
- ### ProxyServer
169
-
170
- ```typescript
171
- class ProxyServer {
172
- constructor(targets: string[], recordingsDir: string);
173
-
174
- async init(): Promise<void>;
175
- listen(port: number): http.Server;
176
- }
177
- ```
178
-
179
- ### Control Endpoint
180
-
181
- Send POST requests to `/__control` with JSON body:
305
+ ### Control Request Interface
182
306
 
183
307
  ```typescript
184
308
  interface ControlRequest {
185
309
  mode: 'transparent' | 'record' | 'replay';
186
- id?: string; // Will be used as file name for recordings, required for record/replay modes
187
- timeout?: number; // Auto-switch to transparent mode after timeout (ms), default 120 seconds
310
+ id?: string; // Recording ID, required for record/replay
311
+ timeout?: number; // Auto-reset timeout in ms (default: 120000)
188
312
  }
189
313
  ```
190
314
 
191
- ### Playwright Integration API
315
+ ## Typical Workflow
192
316
 
193
- ```typescript
194
- import { playwrightProxy, setProxyMode } from 'test-proxy-recorder';
317
+ ### Initial Recording
195
318
 
196
- // Main helper object for use with Playwright tests
197
- const playwrightProxy = {
198
- // Set proxy mode before test
199
- async before(testInfo: PlaywrightTestInfo, mode: 'record' | 'replay' | 'transparent'): Promise<void>;
319
+ 1. Start backend API: `npm run api`
320
+ 2. Start proxy and app: `npm run dev:proxy`
321
+ 3. Set test to `'record'` mode
322
+ 4. Run test: Recordings saved to `./e2e/recordings/` (directory created automatically)
323
+ 5. Commit `.mock.json` files to git
324
+ 6. Change mode to `'replay'`
200
325
 
201
- // Reset to transparent mode after test
202
- async after(testInfo: PlaywrightTestInfo): Promise<void>;
203
- };
204
- ```
326
+ ### Running with Replay
205
327
 
206
- ### Global Teardown and Hooks Setup (Recommended)
328
+ 1. Start proxy and app: `npm run dev:proxy` (no backend needed!)
329
+ 2. Set test to `'replay'` mode
330
+ 3. Run test: Uses recorded responses
331
+ 4. Tests run fast without backend
207
332
 
208
- For robust test setups, it's recommended to configure global teardown and afterEach hooks to ensure the proxy is properly reset even when tests fail. This prevents the proxy from staying in record/replay mode, which could affect subsequent test runs.
333
+ ### Updating Recordings
209
334
 
210
- #### 1. Create Global Teardown File
335
+ 1. Start backend API
336
+ 2. Set test to `'record'` mode
337
+ 3. Run test: Overwrites existing recording
338
+ 4. Commit updated `.mock.json` file
211
339
 
212
- Create `e2e/global-teardown.ts` to reset the proxy mode after all tests complete:
340
+ ## Recording Format
213
341
 
214
- ```typescript
215
- import { setProxyMode } from 'test-proxy-recorder';
342
+ Recordings are stored as JSON files with `.mock.json` extension:
216
343
 
217
- async function globalTeardown() {
218
- await setProxyMode('transparent');
219
- }
220
-
221
- export default globalTeardown;
344
+ ```text
345
+ e2e/recordings/
346
+ ├── create-a-user.mock.json
347
+ ├── fetch-users-list.mock.json
348
+ └── delete-user.mock.json
222
349
  ```
223
350
 
224
- #### 2. Create Global Hooks File
351
+ ## Troubleshooting
225
352
 
226
- Create `e2e/global-hooks.ts` to ensure proxy cleanup happens after each test, even on failure:
353
+ ### Proxy not responding
227
354
 
228
- ```typescript
229
- import { test } from '@playwright/test';
230
- import { playwrightProxy } from 'test-proxy-recorder';
355
+ **Check if proxy is running**:
231
356
 
232
- /**
233
- * Global afterEach hook to ensure proxy cleanup happens even when tests fail.
234
- * This will run after every test across all test files.
235
- */
236
- test.afterEach(async ({}, testInfo) => {
237
- try {
238
- await playwrightProxy.after(testInfo);
239
- } catch (error) {
240
- console.error('Error during proxy cleanup:', error);
241
- // Don't throw - we want cleanup to continue even if this fails
242
- }
243
- });
357
+ ```bash
358
+ curl http://localhost:8100/__control
244
359
  ```
245
360
 
246
- #### 3. Configure Playwright
361
+ **Check port availability**:
247
362
 
248
- Update your `playwright.config.ts` to include the global teardown:
363
+ ```bash
364
+ lsof -i :8100
365
+ ```
249
366
 
250
- ```typescript
251
- import { defineConfig } from '@playwright/test';
367
+ ### No recordings saved
252
368
 
253
- export default defineConfig({
254
- testDir: './e2e',
255
- globalTeardown: './e2e/global-teardown.ts',
256
- // ... rest of your config
257
- });
258
- ```
369
+ - Verify proxy mode is `'record'`
370
+ - Check app is using proxy URL (`http://localhost:8100`)
371
+ - Verify write permissions on recordings directory
372
+ - Check proxy server logs for errors
259
373
 
260
- #### 4. Import Global Hooks in Your Base Page or Test Setup
374
+ ### Test fails in replay mode
261
375
 
262
- Import the global hooks file in your base test file or base page to register the afterEach hook:
376
+ - Ensure recording exists for this test
377
+ - Check test name hasn't changed
378
+ - Verify recording file matches expected format
379
+ - Re-record if API responses changed
263
380
 
264
- ```typescript
265
- // In your e2e/basePage.ts or similar base test file
266
- import { test as base } from '@playwright/test';
381
+ ### Recordings not matching requests
267
382
 
268
- // Import global hooks to register afterEach for proxy cleanup
269
- import './global-hooks';
383
+ - Request URLs must match exactly
384
+ - Headers may affect matching (configurable)
385
+ - Query parameters must be in same order
386
+ - Re-record to capture current API behavior
270
387
 
271
- export const test = base.extend({
272
- // your fixtures
273
- });
274
- ```
275
388
 
276
- ## Recording Format
389
+ ## API Reference
277
390
 
278
- Recordings are stored as JSON files with `.mock.json` extension in the recordings directory:
391
+ ### ProxyServer Class
279
392
 
280
- ```text
281
- recordings/
282
- ├── test-session-1.mock.json
283
- ├── test-session-2.mock.json
284
- └── ...
393
+ ```typescript
394
+ class ProxyServer {
395
+ constructor(targets: string[], recordingsDir: string);
396
+ async init(): Promise<void>;
397
+ listen(port: number): http.Server;
398
+ }
285
399
  ```
286
400
 
287
- Each recording contains:
401
+ ### Playwright Integration
288
402
 
289
- - Request/response pairs with headers and bodies
290
- - WebSocket messages with timestamps
291
- - Unique keys for request matching during replay
403
+ ```typescript
404
+ import { playwrightProxy, setProxyMode } from 'test-proxy-recorder';
292
405
 
293
- ## Typical Workflow
406
+ // Main helper for Playwright tests
407
+ const playwrightProxy = {
408
+ // Set proxy mode before test
409
+ async before(
410
+ testInfo: TestInfo,
411
+ mode: 'record' | 'replay' | 'transparent',
412
+ timeout?: number
413
+ ): Promise<void>;
294
414
 
295
- 1. **Start the proxy server** (runs continuously):
415
+ // Reset to transparent mode after test
416
+ async after(testInfo: TestInfo): Promise<void>;
417
+ };
296
418
 
297
- ```bash
298
- test-proxy-recorder http://localhost:8000 --port 8100
299
- ```
419
+ // Direct mode control
420
+ async function setProxyMode(
421
+ mode: 'record' | 'replay' | 'transparent',
422
+ id?: string,
423
+ timeout?: number
424
+ ): Promise<void>;
425
+ ```
300
426
 
301
- 2. **Configure your app** to use the proxy (point your app to the proxy port, e.g., 8100)
427
+ ### Control Endpoint
428
+
429
+ **Endpoint**: `POST http://localhost:8100/__control`
302
430
 
303
- 3. **Record responses** (first run):
431
+ **Request Body**:
304
432
 
305
- ```typescript
306
- test('my test', async ({ page }, testInfo) => {
307
- await playwrightProxy.before(testInfo, 'record');
308
- // Test interacts with real API through proxy
309
- await page.goto('/my-page');
310
- await playwrightProxy.after(testInfo);
311
- });
312
- ```
433
+ ```typescript
434
+ {
435
+ mode: 'transparent' | 'record' | 'replay';
436
+ id?: string; // Recording ID (required for record/replay)
437
+ timeout?: number; // Auto-reset timeout in ms (default: 120000)
438
+ }
439
+ ```
313
440
 
314
- 4. **Replay responses** (subsequent runs):
441
+ **Response**:
315
442
 
316
- ```typescript
317
- test('my test', async ({ page }, testInfo) => {
318
- await playwrightProxy.before(testInfo, 'replay');
319
- // Test uses recorded responses - no real API calls
320
- await page.goto('/my-page');
321
- await playwrightProxy.after(testInfo);
322
- });
323
- ```
443
+ ```typescript
444
+ {
445
+ success: boolean;
446
+ mode: string;
447
+ id: string | null;
448
+ timeout: number;
449
+ }
450
+ ```
324
451
 
325
452
  ## Requirements
326
453
 
327
454
  - Node.js >= 22.0.0
328
- - @playwright/test >= 1.0.0
329
-
330
- ## License
331
-
332
- MIT
455
+ - @playwright/test >= 1.0.0 (for Playwright integration)
333
456
 
334
457
  ## Contributing
335
458
 
336
459
  Contributions are welcome! Please feel free to submit a Pull Request.
460
+
461
+ ## License
462
+
463
+ MIT