test-proxy-recorder 0.3.1 → 0.3.3
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 +142 -17
- package/dist/{index-CVuiglPk.d.cts → index-BlBWqSE4.d.cts} +21 -4
- package/dist/{index-CVuiglPk.d.ts → index-BlBWqSE4.d.ts} +21 -4
- package/dist/index.cjs +269 -63
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.mjs +268 -62
- package/dist/playwright/index.cjs +129 -14
- package/dist/playwright/index.d.cts +1 -1
- package/dist/playwright/index.d.ts +1 -1
- package/dist/playwright/index.mjs +125 -15
- package/dist/proxy.js +145 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -268,6 +268,67 @@ export default defineConfig({
|
|
|
268
268
|
});
|
|
269
269
|
```
|
|
270
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
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
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
|
+
```
|
|
317
|
+
|
|
318
|
+
**Storage:**
|
|
319
|
+
Client-side recordings are stored as HAR files alongside server-side recordings:
|
|
320
|
+
```
|
|
321
|
+
e2e/recordings/
|
|
322
|
+
├── my-test.mock.json # Server-side recordings (proxy)
|
|
323
|
+
└── my-test.har # Client-side recordings (browser)
|
|
324
|
+
```
|
|
325
|
+
|
|
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
|
+
|
|
271
332
|
## Next.js Integration
|
|
272
333
|
|
|
273
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.
|
|
@@ -342,10 +403,37 @@ export async function GET() {
|
|
|
342
403
|
|
|
343
404
|
## Control Endpoint
|
|
344
405
|
|
|
345
|
-
The proxy exposes a control endpoint at `/__control` for programmatic mode switching.
|
|
406
|
+
The proxy exposes a control endpoint at `/__control` for programmatic mode switching and configuration retrieval.
|
|
407
|
+
|
|
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
|
+
```bash
|
|
414
|
+
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
|
+
```
|
|
346
433
|
|
|
347
|
-
###
|
|
434
|
+
### POST - Switch Proxy Mode
|
|
348
435
|
|
|
436
|
+
**Via HTTP:**
|
|
349
437
|
```bash
|
|
350
438
|
# Switch to record mode
|
|
351
439
|
curl -X POST http://localhost:8100/__control \
|
|
@@ -363,8 +451,7 @@ curl -X POST http://localhost:8100/__control \
|
|
|
363
451
|
-d '{"mode": "transparent"}'
|
|
364
452
|
```
|
|
365
453
|
|
|
366
|
-
|
|
367
|
-
|
|
454
|
+
**Via JavaScript:**
|
|
368
455
|
```javascript
|
|
369
456
|
await fetch('http://localhost:8100/__control', {
|
|
370
457
|
method: 'POST',
|
|
@@ -385,6 +472,12 @@ interface ControlRequest {
|
|
|
385
472
|
id?: string; // Recording ID, required for record/replay
|
|
386
473
|
timeout?: number; // Auto-reset timeout in ms (default: 120000)
|
|
387
474
|
}
|
|
475
|
+
|
|
476
|
+
interface ControlResponse {
|
|
477
|
+
recordingsDir: string;
|
|
478
|
+
mode: string;
|
|
479
|
+
id?: string;
|
|
480
|
+
}
|
|
388
481
|
```
|
|
389
482
|
|
|
390
483
|
## Typical Workflow
|
|
@@ -414,15 +507,21 @@ interface ControlRequest {
|
|
|
414
507
|
|
|
415
508
|
## Recording Format
|
|
416
509
|
|
|
417
|
-
Recordings are stored
|
|
510
|
+
Recordings are stored in two formats depending on the recording type:
|
|
511
|
+
|
|
512
|
+
**Server-side recordings** (via proxy): JSON files with `.mock.json` extension
|
|
513
|
+
**Client-side recordings** (via HAR): HTTP Archive files with `.har` extension
|
|
418
514
|
|
|
419
515
|
```text
|
|
420
516
|
e2e/recordings/
|
|
421
|
-
├── create-a-user.mock.json
|
|
517
|
+
├── create-a-user.mock.json # Server-side API calls
|
|
518
|
+
├── create-a-user.har # Client-side 3rd party requests
|
|
422
519
|
├── fetch-users-list.mock.json
|
|
423
520
|
└── delete-user.mock.json
|
|
424
521
|
```
|
|
425
522
|
|
|
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
|
+
|
|
426
525
|
## Troubleshooting
|
|
427
526
|
|
|
428
527
|
### Proxy not responding
|
|
@@ -479,18 +578,28 @@ class ProxyServer {
|
|
|
479
578
|
import { playwrightProxy, setProxyMode, RECORDING_ID_HEADER } from 'test-proxy-recorder';
|
|
480
579
|
import type { Page } from '@playwright/test';
|
|
481
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
|
+
|
|
482
592
|
// Main helper for Playwright tests
|
|
483
593
|
const playwrightProxy = {
|
|
484
594
|
// Set proxy mode before test and configure page with recording ID header
|
|
485
|
-
//
|
|
595
|
+
// Supports optional client-side recording for 3rd party APIs
|
|
486
596
|
async before(
|
|
487
597
|
page: Page,
|
|
488
598
|
testInfo: TestInfo,
|
|
489
599
|
mode: 'record' | 'replay' | 'transparent',
|
|
490
|
-
timeout?: number
|
|
600
|
+
options?: number | (ClientSideRecordingOptions & { timeout?: number })
|
|
491
601
|
): Promise<void>;
|
|
492
602
|
|
|
493
|
-
|
|
494
603
|
// Global teardown - switches proxy to transparent mode
|
|
495
604
|
// Use in Playwright's globalTeardown configuration
|
|
496
605
|
async teardown(): Promise<void>;
|
|
@@ -507,6 +616,12 @@ async function setProxyMode(
|
|
|
507
616
|
const RECORDING_ID_HEADER: string; // 'x-test-rcrd-id'
|
|
508
617
|
```
|
|
509
618
|
|
|
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
|
|
624
|
+
|
|
510
625
|
### Next.js Integration
|
|
511
626
|
|
|
512
627
|
**IMPORTANT**: Use the `/nextjs` import path to avoid webpack bundling issues in Next.js:
|
|
@@ -541,31 +656,41 @@ function createHeadersWithRecordingId(
|
|
|
541
656
|
|
|
542
657
|
### Control Endpoint
|
|
543
658
|
|
|
544
|
-
|
|
659
|
+
The control endpoint supports both GET and POST methods.
|
|
545
660
|
|
|
546
|
-
**
|
|
661
|
+
**GET `/__control`** - Retrieve proxy configuration:
|
|
547
662
|
|
|
548
663
|
```typescript
|
|
664
|
+
// Response
|
|
549
665
|
{
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
666
|
+
recordingsDir: string; // Path to recordings directory
|
|
667
|
+
mode: string; // Current mode: 'transparent' | 'record' | 'replay'
|
|
668
|
+
id?: string; // Active recording/replay session ID
|
|
553
669
|
}
|
|
554
670
|
```
|
|
555
671
|
|
|
556
|
-
**
|
|
557
|
-
|
|
558
|
-
**Response**:
|
|
672
|
+
**POST `/__control`** - Switch proxy mode:
|
|
559
673
|
|
|
560
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
|
|
561
683
|
{
|
|
562
684
|
success: boolean;
|
|
563
685
|
mode: string;
|
|
564
686
|
id: string | null;
|
|
565
687
|
timeout: number;
|
|
688
|
+
recordingsDir: string;
|
|
566
689
|
}
|
|
567
690
|
```
|
|
568
691
|
|
|
692
|
+
**Note**: Switching to replay mode automatically resets session counters (clears served recordings tracker), allowing replay from the beginning.
|
|
693
|
+
|
|
569
694
|
## Requirements
|
|
570
695
|
|
|
571
696
|
- Node.js >= 22.0.0
|
|
@@ -8,9 +8,10 @@ declare const Modes: {
|
|
|
8
8
|
};
|
|
9
9
|
type Mode = (typeof Modes)[keyof typeof Modes];
|
|
10
10
|
interface ControlRequest {
|
|
11
|
-
mode
|
|
11
|
+
mode?: Mode;
|
|
12
12
|
id?: string;
|
|
13
13
|
timeout?: number;
|
|
14
|
+
cleanup?: boolean;
|
|
14
15
|
}
|
|
15
16
|
interface RecordedRequest {
|
|
16
17
|
method: string;
|
|
@@ -56,6 +57,11 @@ type PlaywrightTestInfo = Pick<TestInfo, 'title' | 'titlePath'>;
|
|
|
56
57
|
* @param timeout - Optional timeout in milliseconds
|
|
57
58
|
*/
|
|
58
59
|
declare function setProxyMode(mode: Mode, sessionId?: string, timeout?: number): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Clean up a specific session - removes it from memory and resets counters
|
|
62
|
+
* @param sessionId - The session ID to clean up
|
|
63
|
+
*/
|
|
64
|
+
declare function cleanupSession(sessionId: string): Promise<void>;
|
|
59
65
|
/**
|
|
60
66
|
* Generate a session ID from test info
|
|
61
67
|
* Uses titlePath to create folder structure with test file name
|
|
@@ -84,6 +90,15 @@ declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
|
|
|
84
90
|
* Playwright test fixture helper for managing proxy mode
|
|
85
91
|
* Use this in test functions with page.on('close') for automatic cleanup
|
|
86
92
|
*/
|
|
93
|
+
interface ClientSideRecordingOptions {
|
|
94
|
+
/**
|
|
95
|
+
* URL pattern for client-side requests to record/replay
|
|
96
|
+
* Uses Playwright's native format (string or RegExp)
|
|
97
|
+
* Example: /cognito-.*amazonaws\.com|\.stream-io-api\.com/
|
|
98
|
+
* Example: 'https://api.example.com/**'
|
|
99
|
+
*/
|
|
100
|
+
url?: string | RegExp;
|
|
101
|
+
}
|
|
87
102
|
declare const playwrightProxy: {
|
|
88
103
|
/**
|
|
89
104
|
* Setup before test - sets the proxy mode and configures page with custom header
|
|
@@ -91,9 +106,11 @@ declare const playwrightProxy: {
|
|
|
91
106
|
* @param page - Playwright page object
|
|
92
107
|
* @param testInfo - Playwright test info object
|
|
93
108
|
* @param mode - The proxy mode to use for this test
|
|
94
|
-
* @param
|
|
109
|
+
* @param options - Optional configuration including timeout and client-side recording patterns
|
|
95
110
|
*/
|
|
96
|
-
before(page: Page, testInfo: PlaywrightTestInfo, mode: Mode,
|
|
111
|
+
before(page: Page, testInfo: PlaywrightTestInfo, mode: Mode, options?: number | (ClientSideRecordingOptions & {
|
|
112
|
+
timeout?: number;
|
|
113
|
+
})): Promise<void>;
|
|
97
114
|
/**
|
|
98
115
|
* Global teardown - switches proxy to transparent mode
|
|
99
116
|
* Use this in Playwright's globalTeardown to ensure clean state
|
|
@@ -101,4 +118,4 @@ declare const playwrightProxy: {
|
|
|
101
118
|
teardown(): Promise<void>;
|
|
102
119
|
};
|
|
103
120
|
|
|
104
|
-
export { type ControlRequest as C, type Mode as M, type PlaywrightTestInfo as P, type Recording as R, type WebSocketRecording as W, type RecordingSession as a, startRecording as b, startReplay as c, stopProxy as d, generateSessionId as g, playwrightProxy as p, setProxyMode as s };
|
|
121
|
+
export { type ControlRequest as C, type Mode as M, type PlaywrightTestInfo as P, type Recording as R, type WebSocketRecording as W, type RecordingSession as a, startRecording as b, startReplay as c, stopProxy as d, cleanupSession as e, type ClientSideRecordingOptions as f, generateSessionId as g, playwrightProxy as p, setProxyMode as s };
|
|
@@ -8,9 +8,10 @@ declare const Modes: {
|
|
|
8
8
|
};
|
|
9
9
|
type Mode = (typeof Modes)[keyof typeof Modes];
|
|
10
10
|
interface ControlRequest {
|
|
11
|
-
mode
|
|
11
|
+
mode?: Mode;
|
|
12
12
|
id?: string;
|
|
13
13
|
timeout?: number;
|
|
14
|
+
cleanup?: boolean;
|
|
14
15
|
}
|
|
15
16
|
interface RecordedRequest {
|
|
16
17
|
method: string;
|
|
@@ -56,6 +57,11 @@ type PlaywrightTestInfo = Pick<TestInfo, 'title' | 'titlePath'>;
|
|
|
56
57
|
* @param timeout - Optional timeout in milliseconds
|
|
57
58
|
*/
|
|
58
59
|
declare function setProxyMode(mode: Mode, sessionId?: string, timeout?: number): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Clean up a specific session - removes it from memory and resets counters
|
|
62
|
+
* @param sessionId - The session ID to clean up
|
|
63
|
+
*/
|
|
64
|
+
declare function cleanupSession(sessionId: string): Promise<void>;
|
|
59
65
|
/**
|
|
60
66
|
* Generate a session ID from test info
|
|
61
67
|
* Uses titlePath to create folder structure with test file name
|
|
@@ -84,6 +90,15 @@ declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
|
|
|
84
90
|
* Playwright test fixture helper for managing proxy mode
|
|
85
91
|
* Use this in test functions with page.on('close') for automatic cleanup
|
|
86
92
|
*/
|
|
93
|
+
interface ClientSideRecordingOptions {
|
|
94
|
+
/**
|
|
95
|
+
* URL pattern for client-side requests to record/replay
|
|
96
|
+
* Uses Playwright's native format (string or RegExp)
|
|
97
|
+
* Example: /cognito-.*amazonaws\.com|\.stream-io-api\.com/
|
|
98
|
+
* Example: 'https://api.example.com/**'
|
|
99
|
+
*/
|
|
100
|
+
url?: string | RegExp;
|
|
101
|
+
}
|
|
87
102
|
declare const playwrightProxy: {
|
|
88
103
|
/**
|
|
89
104
|
* Setup before test - sets the proxy mode and configures page with custom header
|
|
@@ -91,9 +106,11 @@ declare const playwrightProxy: {
|
|
|
91
106
|
* @param page - Playwright page object
|
|
92
107
|
* @param testInfo - Playwright test info object
|
|
93
108
|
* @param mode - The proxy mode to use for this test
|
|
94
|
-
* @param
|
|
109
|
+
* @param options - Optional configuration including timeout and client-side recording patterns
|
|
95
110
|
*/
|
|
96
|
-
before(page: Page, testInfo: PlaywrightTestInfo, mode: Mode,
|
|
111
|
+
before(page: Page, testInfo: PlaywrightTestInfo, mode: Mode, options?: number | (ClientSideRecordingOptions & {
|
|
112
|
+
timeout?: number;
|
|
113
|
+
})): Promise<void>;
|
|
97
114
|
/**
|
|
98
115
|
* Global teardown - switches proxy to transparent mode
|
|
99
116
|
* Use this in Playwright's globalTeardown to ensure clean state
|
|
@@ -101,4 +118,4 @@ declare const playwrightProxy: {
|
|
|
101
118
|
teardown(): Promise<void>;
|
|
102
119
|
};
|
|
103
120
|
|
|
104
|
-
export { type ControlRequest as C, type Mode as M, type PlaywrightTestInfo as P, type Recording as R, type WebSocketRecording as W, type RecordingSession as a, startRecording as b, startReplay as c, stopProxy as d, generateSessionId as g, playwrightProxy as p, setProxyMode as s };
|
|
121
|
+
export { type ControlRequest as C, type Mode as M, type PlaywrightTestInfo as P, type Recording as R, type WebSocketRecording as W, type RecordingSession as a, startRecording as b, startReplay as c, stopProxy as d, cleanupSession as e, type ClientSideRecordingOptions as f, generateSessionId as g, playwrightProxy as p, setProxyMode as s };
|