test-proxy-recorder 0.3.5 → 0.3.6

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.
@@ -0,0 +1,458 @@
1
+ ---
2
+ name: proxy-setup
3
+ description: >
4
+ Set up test-proxy-recorder for any Playwright project. Covers the proxy CLI
5
+ (test-proxy-recorder <target> --port --dir), package.json scripts for the
6
+ three-service architecture (UI app → proxy → backend API), playwright.config.ts
7
+ webServer block pointing to /__control, per-test fixtures using
8
+ playwrightProxy.before(page, testInfo, mode, { url }), HAR browser-side
9
+ recording via url pattern, .mock.json server-side recording, record/replay/
10
+ transparent modes, the record-once→commit→CI-replay lifecycle, and parallel
11
+ test execution with fullyParallel. Load this skill when installing
12
+ test-proxy-recorder, writing Playwright fixtures, or configuring record/replay.
13
+ type: core
14
+ library: test-proxy-recorder
15
+ library_version: "0.3.5"
16
+ sources:
17
+ - "asmyshlyaev177/test-proxy-recorder:README.md"
18
+ - "asmyshlyaev177/test-proxy-recorder:packages/test-proxy-recorder/src/playwright/index.ts"
19
+ - "asmyshlyaev177/test-proxy-recorder:packages/test-proxy-recorder/src/types.ts"
20
+ - "asmyshlyaev177/test-proxy-recorder:apps/example-nextjs16/package.json"
21
+ - "asmyshlyaev177/test-proxy-recorder:apps/example-extension/e2e/fixtures.ts"
22
+ - "asmyshlyaev177/test-proxy-recorder:apps/example-extension/playwright.config.ts"
23
+ ---
24
+
25
+ # test-proxy-recorder — Proxy Setup
26
+
27
+ `test-proxy-recorder` runs an HTTP proxy that records real API responses to
28
+ disk (`.mock.json` for server-side, `.har` for browser-side) and replays them
29
+ in Playwright tests without a live backend.
30
+
31
+ Two recording mechanisms work independently or together:
32
+
33
+ | Mechanism | File | Records |
34
+ |---|---|---|
35
+ | Proxy | `.mock.json` | Server-side (SSR) fetches from Node.js |
36
+ | HAR | `.har` | Browser-side `fetch` calls, Chrome extension traffic |
37
+
38
+ ## Setup
39
+
40
+ ### Browser-only / SPA / Chrome extension
41
+
42
+ No backend proxy needed for recording — only the proxy process for session
43
+ management via `/__control`.
44
+
45
+ ```typescript
46
+ // playwright.config.ts
47
+ import { defineConfig } from '@playwright/test';
48
+
49
+ export default defineConfig({
50
+ webServer: {
51
+ command: 'test-proxy-recorder https://api.example.com --port 8100 --dir ./e2e/recordings',
52
+ url: 'http://localhost:8100/__control',
53
+ reuseExistingServer: true,
54
+ timeout: 15_000,
55
+ },
56
+ });
57
+ ```
58
+
59
+ ```typescript
60
+ // e2e/fixtures.ts
61
+ import { test as base, type Page } from '@playwright/test';
62
+ import { playwrightProxy } from 'test-proxy-recorder';
63
+
64
+ const CLIENT_SIDE_URL = /api\.example\.com/;
65
+
66
+ // Change to 'record' to hit the real API and update recordings.
67
+ const MODE = 'replay' as const;
68
+
69
+ export const test = base.extend<{ page: Page }>({
70
+ page: async ({ context }, use, testInfo) => {
71
+ const page = await context.newPage();
72
+ await playwrightProxy.before(page, testInfo, MODE, { url: CLIENT_SIDE_URL });
73
+ await use(page);
74
+ },
75
+ });
76
+ export { expect } from '@playwright/test';
77
+ ```
78
+
79
+ ### Full-stack (SSR + browser)
80
+
81
+ The app's API base URL must be pointed at the proxy, not the real backend.
82
+
83
+ ```json
84
+ // package.json
85
+ {
86
+ "scripts": {
87
+ "proxy": "test-proxy-recorder http://localhost:3002 --port 8100 --dir ./e2e/recordings",
88
+ "start:all": "concurrently \"pnpm proxy\" \"INTERNAL_API_URL=http://localhost:8100 pnpm start\"",
89
+ "test:e2e": "pnpm build && concurrently --kill-others --success first --names services,tests \"pnpm start:all\" \"wait-on http://127.0.0.1:3000 http://127.0.0.1:8100/__control && playwright test --retries 1\"",
90
+ "test:e2e:record": "pnpm build && playwright test --workers 1 --ui"
91
+ }
92
+ }
93
+ ```
94
+
95
+ `INTERNAL_API_URL` stands in for whatever env var your app reads its API base
96
+ URL from — it must point at the proxy (see Common Mistakes). For Next.js apps
97
+ running a production build, also set `TEST_PROXY_RECORDER_ENABLED=true`
98
+ (see test-proxy-recorder/nextjs-ssr).
99
+
100
+ ```typescript
101
+ // e2e/my.test.ts
102
+ import { test, expect } from '@playwright/test';
103
+ import { playwrightProxy } from 'test-proxy-recorder';
104
+
105
+ // Change to 'record' to hit the real API and update recordings.
106
+ const MODE = 'replay' as const;
107
+
108
+ // External services the browser calls directly (auth, CDN, analytics, etc.).
109
+ // Server-side fetches through the proxy are recorded automatically via .mock.json.
110
+ const CLIENT_SIDE_URL = /cognito-.*\.amazonaws\.com|\.s3\..*\.amazonaws\.com/;
111
+
112
+ test.beforeEach(async ({ page }, testInfo) => {
113
+ await playwrightProxy.before(page, testInfo, MODE, {
114
+ url: CLIENT_SIDE_URL,
115
+ });
116
+ });
117
+
118
+ test('creates a todo', async ({ page }) => {
119
+ await page.goto('/');
120
+ await page.getByTestId('new-todo-input').fill('Buy groceries');
121
+ await page.getByTestId('add-btn').click();
122
+ await expect(page.getByTestId('todo-text').first()).toHaveText('Buy groceries');
123
+ });
124
+ ```
125
+
126
+ ## Core Patterns
127
+
128
+ ### Record/replay lifecycle
129
+
130
+ Recording is manual, done once per test with a single worker. Replay runs on
131
+ CI with multiple workers, headlessly.
132
+
133
+ ```bash
134
+ # 1. In fixtures.ts (or test file): set MODE = 'record'
135
+ # 2. Run once against the real backend, headed, one worker
136
+ npx playwright test --workers 1 --ui
137
+
138
+ # 3. Set MODE back to 'replay', commit recordings
139
+ git add e2e/recordings/
140
+ git commit -m "add e2e recordings"
141
+
142
+ # 4. Replay on CI — headless, parallel workers, no backend needed
143
+ npx playwright test
144
+ ```
145
+
146
+ Recording files must be committed — do not add `e2e/recordings/` to
147
+ `.gitignore`. Optionally collapse diffs with:
148
+
149
+ ```text
150
+ # .gitattributes
151
+ /e2e/recordings/** binary
152
+ ```
153
+
154
+ ### Auth setup
155
+
156
+ Auth always runs against the real auth provider — never recorded or replayed.
157
+ Use `setProxyMode('transparent')` so auth requests bypass the proxy entirely.
158
+ Skip the auth step in replay mode (the recorded session is already embedded in
159
+ the HAR / storage state file from the previous record run).
160
+
161
+ ```typescript
162
+ // e2e/auth.setup.ts
163
+ import { test as setup } from '@playwright/test';
164
+ import { setProxyMode } from 'test-proxy-recorder';
165
+
166
+ const AUTH_FILE = 'e2e/.auth/state.json';
167
+
168
+ const TEST_USER = {
169
+ email: 'testuser@example.com',
170
+ password: 'TestPassword123',
171
+ };
172
+
173
+ setup('authenticate', async ({ page }) => {
174
+ // Bypass the proxy — auth must always hit the real provider.
175
+ await setProxyMode('transparent');
176
+
177
+ await page.goto('/users/sign-in');
178
+ await page.getByTestId('email').fill(TEST_USER.email);
179
+ await page.getByTestId('password').fill(TEST_USER.password);
180
+ await page.getByTestId('signinButton').click();
181
+ await page.waitForURL('/', { timeout: 15_000 });
182
+
183
+ await page.context().storageState({ path: AUTH_FILE });
184
+ });
185
+ ```
186
+
187
+ Add the auth state file to `.gitignore` — it contains session tokens and must not be committed:
188
+
189
+ ```gitignore
190
+ # .gitignore
191
+ e2e/.auth/
192
+ ```
193
+
194
+ ```typescript
195
+ // playwright.config.ts
196
+ export default defineConfig({
197
+ projects: [
198
+ { name: 'setup', testMatch: /auth\.setup\.ts/ },
199
+ {
200
+ name: 'e2e',
201
+ testIgnore: /auth\.setup\.ts/,
202
+ dependencies: ['setup'],
203
+ use: { storageState: 'e2e/.auth/state.json' },
204
+ },
205
+ ],
206
+ });
207
+ ```
208
+
209
+ Include the auth provider domain in `CLIENT_SIDE_URL` when the browser makes
210
+ direct calls to it (e.g. Cognito token refresh, OAuth redirects):
211
+
212
+ ```typescript
213
+ // These browser-to-Cognito calls are recorded in the HAR, not via the proxy.
214
+ const CLIENT_SIDE_URL = /cognito-.*\.amazonaws\.com/;
215
+ ```
216
+
217
+ ### Global teardown
218
+
219
+ ```typescript
220
+ // e2e/global-teardown.ts
221
+ import { playwrightProxy } from 'test-proxy-recorder';
222
+
223
+ export default async function globalTeardown() {
224
+ await playwrightProxy.teardown().catch((err) => console.warn('teardown', err));
225
+ }
226
+ ```
227
+
228
+ ```typescript
229
+ // playwright.config.ts
230
+ export default defineConfig({
231
+ globalTeardown: './e2e/global-teardown.ts',
232
+ });
233
+ ```
234
+
235
+ ### playwrightProxy.before() signature
236
+
237
+ ```typescript
238
+ await playwrightProxy.before(
239
+ page, // Playwright Page
240
+ testInfo, // TestInfo from test function argument
241
+ mode, // 'record' | 'replay' | 'transparent'
242
+ {
243
+ url, // RegExp | string — browser-side requests to intercept via HAR
244
+ timeout, // ms — auto-reset timeout (default: 120000)
245
+ }
246
+ );
247
+ ```
248
+
249
+ `url` is optional. Omit it for proxy-only (SSR) recording with no browser-side
250
+ HAR interception.
251
+
252
+ ### Session file naming
253
+
254
+ Session IDs are derived from the test file path and title:
255
+
256
+ ```
257
+ jobs/Create.spec.ts + 'create a job' → jobs/Create__create-a-job
258
+ location.test.ts + 'homepage loads' → location__homepage-loads
259
+ ```
260
+
261
+ Files on disk:
262
+ ```
263
+ e2e/recordings/
264
+ jobs/Create__create-a-job.mock.json # server-side
265
+ jobs__Create__create-a-job.har # browser-side (/ replaced with __)
266
+ ```
267
+
268
+ ### Control endpoint
269
+
270
+ ```bash
271
+ # Check current proxy state
272
+ curl http://localhost:8100/__control
273
+
274
+ # Programmatically switch mode
275
+ curl -X POST http://localhost:8100/__control \
276
+ -H 'Content-Type: application/json' \
277
+ -d '{"mode": "record", "id": "my-test"}'
278
+ ```
279
+
280
+ Override the default port (8100) with `TEST_PROXY_RECORDER_PORT` env var.
281
+
282
+ ## Common Mistakes
283
+
284
+ ### CRITICAL App env var not redirected through proxy
285
+
286
+ Wrong:
287
+ ```json
288
+ {
289
+ "scripts": {
290
+ "dev:proxy": "concurrently \"pnpm proxy\" \"pnpm dev\""
291
+ }
292
+ }
293
+ ```
294
+
295
+ Correct:
296
+ ```json
297
+ {
298
+ "scripts": {
299
+ "dev:proxy": "concurrently \"pnpm proxy\" \"INTERNAL_API_URL=http://localhost:8100 pnpm dev\""
300
+ }
301
+ }
302
+ ```
303
+
304
+ The app's API base URL must point at the proxy, not the real backend. When
305
+ omitted, requests bypass the proxy entirely and nothing is recorded.
306
+
307
+ Source: README.md — Full-stack Quick Start
308
+
309
+ ---
310
+
311
+ ### CRITICAL Wrong CLIENT_SIDE_URL pattern for HAR recording
312
+
313
+ Wrong:
314
+ ```typescript
315
+ // Matches the proxy URL — but the proxy handles server-side recording
316
+ // automatically. This intercepts nothing useful for HAR.
317
+ await playwrightProxy.before(page, testInfo, MODE, {
318
+ url: /localhost:8100/,
319
+ });
320
+ ```
321
+
322
+ Correct:
323
+ ```typescript
324
+ // Match the actual external domains the browser calls directly:
325
+ // third-party auth, CDN, analytics, chat SDKs, etc.
326
+ const CLIENT_SIDE_URL = /cognito-.*\.amazonaws\.com|\.stream-io-api\.com/;
327
+ await playwrightProxy.before(page, testInfo, MODE, { url: CLIENT_SIDE_URL });
328
+
329
+ // Browser-only / SPA with no SSR — match the real API domain
330
+ await playwrightProxy.before(page, testInfo, MODE, { url: /api\.example\.com/ });
331
+ ```
332
+
333
+ `url` must match the external domains the browser calls directly — not the
334
+ proxy. Server-side fetches through the proxy are already recorded to
335
+ `.mock.json` automatically. `url` is only for browser-side HAR recording of
336
+ requests that never touch the proxy (third-party services, CDNs, auth providers).
337
+
338
+ Source: README.md — Playwright Integration; apps/example-extension/e2e/fixtures.ts
339
+
340
+ ---
341
+
342
+ ### HIGH teardown() called per-test breaks parallel replay
343
+
344
+ Wrong:
345
+ ```typescript
346
+ test.afterAll(async () => {
347
+ await playwrightProxy.teardown(); // resets global proxy mode for all workers
348
+ });
349
+ ```
350
+
351
+ Correct:
352
+ ```typescript
353
+ // Omit afterAll entirely.
354
+ // Session cleanup is automatic via context.on('close').
355
+ // Only call teardown() in globalTeardown (see Global Teardown pattern above).
356
+ ```
357
+
358
+ `teardown()` sets the **global** proxy mode to `transparent`. With
359
+ `fullyParallel: true`, a fast test's `afterAll` fires while other tests are
360
+ still replaying, switching the proxy mid-session and routing requests to the
361
+ real network.
362
+
363
+ Source: README.md — Parallel Replay section
364
+
365
+ ---
366
+
367
+ ### HIGH webServer url points to proxy root not /__control
368
+
369
+ Wrong:
370
+ ```typescript
371
+ webServer: {
372
+ command: 'test-proxy-recorder http://localhost:8000 --port 8100',
373
+ url: 'http://localhost:8100', // root proxies to backend — may 502
374
+ }
375
+ ```
376
+
377
+ Correct:
378
+ ```typescript
379
+ webServer: {
380
+ command: 'test-proxy-recorder http://localhost:8000 --port 8100 --dir ./e2e/recordings',
381
+ url: 'http://localhost:8100/__control',
382
+ }
383
+ ```
384
+
385
+ Playwright uses `url` to health-check that the server is ready. The proxy root
386
+ `/` forwards to the backend, which may be unavailable, causing Playwright to
387
+ report the server as not ready. `/__control` is always available.
388
+
389
+ Source: README.md; apps/example-extension/playwright.config.ts
390
+
391
+ ---
392
+
393
+ ### HIGH Recording files added to .gitignore
394
+
395
+ Wrong:
396
+ ```gitignore
397
+ # .gitignore
398
+ e2e/recordings/
399
+ ```
400
+
401
+ Correct:
402
+ ```gitignore
403
+ # .gitignore — do NOT list e2e/recordings/
404
+
405
+ # .gitattributes — collapse diffs without excluding files
406
+ /e2e/recordings/** binary
407
+ ```
408
+
409
+ CI has no recordings to replay from if the directory is gitignored. Tests will
410
+ fail or hit the real network.
411
+
412
+ Source: README.md — Switch to replay and commit
413
+
414
+ ---
415
+
416
+ ### MEDIUM Recording with Next.js dev server produces flaky recordings
417
+
418
+ Wrong:
419
+ ```bash
420
+ # Recording against the dev server (MODE = 'record' in fixtures)
421
+ next dev & npx playwright test --workers 1
422
+ ```
423
+
424
+ Correct:
425
+ ```bash
426
+ # Build first, then record against the production build
427
+ pnpm build && npx playwright test --workers 1 --ui
428
+ ```
429
+
430
+ The Next.js dev server is slow and can cause SSR fetches to timeout or execute
431
+ out of order, producing incomplete recordings that fail in replay.
432
+
433
+ Source: README.md — Full-stack Quick Start note; apps/example-nextjs16/package.json
434
+
435
+ ---
436
+
437
+ ### MEDIUM Recording with multiple workers corrupts session files
438
+
439
+ Wrong:
440
+ ```typescript
441
+ // fixtures.ts — MODE set to 'record', running with default workers
442
+ const MODE = 'record' as const;
443
+ // playwright test ← parallel workers write to same session files
444
+ ```
445
+
446
+ Correct:
447
+ ```typescript
448
+ // fixtures.ts
449
+ const MODE = 'record' as const;
450
+ // npx playwright test --workers 1 --ui ← single worker when recording
451
+ ```
452
+
453
+ Recording is a manual, single-worker operation. Replay is what uses multiple
454
+ workers (`fullyParallel: true`). Set `MODE = 'record'` in the fixture file, then set it back to `'replay'` before committing.
455
+
456
+ Source: apps/example-nextjs16/package.json; maintainer guidance
457
+
458
+ See also: test-proxy-recorder/nextjs-ssr — for Next.js SSR header propagation