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 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
- ### Via HTTP
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
- ### Via JavaScript
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 as JSON files with `.mock.json` extension:
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
- // Automatically sets up page.on('close') handler for cleanup
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
- **Endpoint**: `POST http://localhost:8100/__control`
659
+ The control endpoint supports both GET and POST methods.
545
660
 
546
- **Request Body**:
661
+ **GET `/__control`** - Retrieve proxy configuration:
547
662
 
548
663
  ```typescript
664
+ // Response
549
665
  {
550
- mode: 'transparent' | 'record' | 'replay';
551
- id?: string; // Recording ID (required for record/replay)
552
- timeout?: number; // Auto-reset timeout in ms (default: 120000)
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
- **Note**: Switching to replay mode automatically resets session counters (clears served recordings tracker), allowing replay from the beginning.
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: 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 timeout - Optional timeout in milliseconds
109
+ * @param options - Optional configuration including timeout and client-side recording patterns
95
110
  */
96
- before(page: Page, testInfo: PlaywrightTestInfo, mode: Mode, timeout?: number): Promise<void>;
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: 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 timeout - Optional timeout in milliseconds
109
+ * @param options - Optional configuration including timeout and client-side recording patterns
95
110
  */
96
- before(page: Page, testInfo: PlaywrightTestInfo, mode: Mode, timeout?: number): Promise<void>;
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 };