test-proxy-recorder 0.1.2 → 0.1.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.
@@ -0,0 +1,95 @@
1
+ import { TestInfo } from '@playwright/test';
2
+ import http from 'node:http';
3
+
4
+ declare const Modes: {
5
+ readonly transparent: "transparent";
6
+ readonly record: "record";
7
+ readonly replay: "replay";
8
+ };
9
+ type Mode = (typeof Modes)[keyof typeof Modes];
10
+ interface ControlRequest {
11
+ mode: Mode;
12
+ id?: string;
13
+ timeout?: number;
14
+ }
15
+ interface RecordedRequest {
16
+ method: string;
17
+ url: string;
18
+ headers: http.IncomingHttpHeaders;
19
+ body: string | null;
20
+ }
21
+ interface RecordedResponse {
22
+ statusCode: number;
23
+ headers: http.IncomingHttpHeaders;
24
+ body: string | null;
25
+ }
26
+ interface Recording {
27
+ request: RecordedRequest;
28
+ response?: RecordedResponse;
29
+ timestamp: string;
30
+ key: string;
31
+ }
32
+ interface WebSocketMessage {
33
+ direction: 'client-to-server' | 'server-to-client';
34
+ data: string;
35
+ timestamp: string;
36
+ }
37
+ interface WebSocketRecording {
38
+ url: string;
39
+ messages: WebSocketMessage[];
40
+ timestamp: string;
41
+ key: string;
42
+ }
43
+ interface RecordingSession {
44
+ id: string;
45
+ recordings: Recording[];
46
+ websocketRecordings: WebSocketRecording[];
47
+ }
48
+
49
+ type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
50
+ /**
51
+ * Set the proxy mode for a given session
52
+ * @param mode - The proxy mode to set (recording, replay, transparent)
53
+ * @param sessionId - Unique identifier for the session
54
+ * @param timeout - Optional timeout in milliseconds
55
+ */
56
+ declare function setProxyMode(mode: Mode, sessionId: string, timeout?: number): Promise<void>;
57
+ /**
58
+ * Generate a session ID from test info
59
+ * @param testInfo - Playwright test info object
60
+ */
61
+ declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
62
+ /**
63
+ * Start recording for a test
64
+ * @param testInfo - Playwright test info object
65
+ */
66
+ declare function startRecording(testInfo: PlaywrightTestInfo): Promise<void>;
67
+ /**
68
+ * Start replay for a test
69
+ * @param testInfo - Playwright test info object
70
+ */
71
+ declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
72
+ /**
73
+ * Stop recording/replay and return to transparent mode
74
+ * @param testInfo - Playwright test info object
75
+ */
76
+ declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
77
+ /**
78
+ * Playwright test fixture helper for managing proxy mode
79
+ * Use this in beforeEach/afterEach hooks
80
+ */
81
+ declare const playwrightProxy: {
82
+ /**
83
+ * Setup before test - sets the proxy mode
84
+ * @param testInfo - Playwright test info object
85
+ * @param mode - The proxy mode to use for this test
86
+ */
87
+ before(testInfo: PlaywrightTestInfo, mode: Mode): Promise<void>;
88
+ /**
89
+ * Cleanup after test - returns to transparent mode
90
+ * @param testInfo - Playwright test info object
91
+ */
92
+ after(testInfo: PlaywrightTestInfo): Promise<void>;
93
+ };
94
+
95
+ 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 };
@@ -0,0 +1,95 @@
1
+ import { TestInfo } from '@playwright/test';
2
+ import http from 'node:http';
3
+
4
+ declare const Modes: {
5
+ readonly transparent: "transparent";
6
+ readonly record: "record";
7
+ readonly replay: "replay";
8
+ };
9
+ type Mode = (typeof Modes)[keyof typeof Modes];
10
+ interface ControlRequest {
11
+ mode: Mode;
12
+ id?: string;
13
+ timeout?: number;
14
+ }
15
+ interface RecordedRequest {
16
+ method: string;
17
+ url: string;
18
+ headers: http.IncomingHttpHeaders;
19
+ body: string | null;
20
+ }
21
+ interface RecordedResponse {
22
+ statusCode: number;
23
+ headers: http.IncomingHttpHeaders;
24
+ body: string | null;
25
+ }
26
+ interface Recording {
27
+ request: RecordedRequest;
28
+ response?: RecordedResponse;
29
+ timestamp: string;
30
+ key: string;
31
+ }
32
+ interface WebSocketMessage {
33
+ direction: 'client-to-server' | 'server-to-client';
34
+ data: string;
35
+ timestamp: string;
36
+ }
37
+ interface WebSocketRecording {
38
+ url: string;
39
+ messages: WebSocketMessage[];
40
+ timestamp: string;
41
+ key: string;
42
+ }
43
+ interface RecordingSession {
44
+ id: string;
45
+ recordings: Recording[];
46
+ websocketRecordings: WebSocketRecording[];
47
+ }
48
+
49
+ type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
50
+ /**
51
+ * Set the proxy mode for a given session
52
+ * @param mode - The proxy mode to set (recording, replay, transparent)
53
+ * @param sessionId - Unique identifier for the session
54
+ * @param timeout - Optional timeout in milliseconds
55
+ */
56
+ declare function setProxyMode(mode: Mode, sessionId: string, timeout?: number): Promise<void>;
57
+ /**
58
+ * Generate a session ID from test info
59
+ * @param testInfo - Playwright test info object
60
+ */
61
+ declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
62
+ /**
63
+ * Start recording for a test
64
+ * @param testInfo - Playwright test info object
65
+ */
66
+ declare function startRecording(testInfo: PlaywrightTestInfo): Promise<void>;
67
+ /**
68
+ * Start replay for a test
69
+ * @param testInfo - Playwright test info object
70
+ */
71
+ declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
72
+ /**
73
+ * Stop recording/replay and return to transparent mode
74
+ * @param testInfo - Playwright test info object
75
+ */
76
+ declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
77
+ /**
78
+ * Playwright test fixture helper for managing proxy mode
79
+ * Use this in beforeEach/afterEach hooks
80
+ */
81
+ declare const playwrightProxy: {
82
+ /**
83
+ * Setup before test - sets the proxy mode
84
+ * @param testInfo - Playwright test info object
85
+ * @param mode - The proxy mode to use for this test
86
+ */
87
+ before(testInfo: PlaywrightTestInfo, mode: Mode): Promise<void>;
88
+ /**
89
+ * Cleanup after test - returns to transparent mode
90
+ * @param testInfo - Playwright test info object
91
+ */
92
+ after(testInfo: PlaywrightTestInfo): Promise<void>;
93
+ };
94
+
95
+ 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 };
package/dist/index.cjs CHANGED
@@ -5,6 +5,7 @@ var http = require('http');
5
5
  var httpProxy = require('http-proxy');
6
6
  var ws = require('ws');
7
7
  var path = require('path');
8
+ var filenamify = require('filenamify');
8
9
 
9
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
11
 
@@ -12,6 +13,7 @@ var fs__default = /*#__PURE__*/_interopDefault(fs);
12
13
  var http__default = /*#__PURE__*/_interopDefault(http);
13
14
  var httpProxy__default = /*#__PURE__*/_interopDefault(httpProxy);
14
15
  var path__default = /*#__PURE__*/_interopDefault(path);
16
+ var filenamify__default = /*#__PURE__*/_interopDefault(filenamify);
15
17
 
16
18
  // src/ProxyServer.ts
17
19
 
@@ -47,6 +49,24 @@ async function saveRecordingSession(recordingsDir, session) {
47
49
  `Saved ${session.recordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
48
50
  );
49
51
  }
52
+ var QUERY_HASH_LENGTH = 8;
53
+ function getReqID(req) {
54
+ const urlParts = req.url.split("?");
55
+ const pathname = urlParts[0];
56
+ const query = urlParts[1] || "";
57
+ const pathPart = pathname === "/" ? "root" : pathname.slice(1);
58
+ const normalizedPath = filenamify__default.default(pathPart, { replacement: "_" });
59
+ const queryHash = generateQueryHash(query);
60
+ const filename = `${req.method}_${normalizedPath}${queryHash}.json`;
61
+ return filenamify__default.default(filename, { replacement: "_" });
62
+ }
63
+ function generateQueryHash(query) {
64
+ if (!query) {
65
+ return "";
66
+ }
67
+ const hash = Buffer.from(query).toString("base64").replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, Math.max(0, QUERY_HASH_LENGTH));
68
+ return `_${hash}`;
69
+ }
50
70
 
51
71
  // src/utils/httpHelpers.ts
52
72
  var CONTENT_TYPE_JSON = "application/json";
@@ -62,28 +82,6 @@ function sendJsonResponse(res, statusCode, data) {
62
82
  res.end(JSON.stringify(data));
63
83
  }
64
84
 
65
- // src/utils/requestKeyGenerator.ts
66
- var QUERY_HASH_LENGTH = 8;
67
- function generateRequestKey(req) {
68
- const urlParts = req.url.split("?");
69
- const pathname = urlParts[0];
70
- const query = urlParts[1] || "";
71
- const normalizedPath = normalizePathname(pathname);
72
- const queryHash = generateQueryHash(query);
73
- return `${req.method}_${normalizedPath}${queryHash}.json`;
74
- }
75
- function normalizePathname(pathname) {
76
- const normalized = pathname.replaceAll("/", "_").replace(/^_/, "");
77
- return normalized || "root";
78
- }
79
- function generateQueryHash(query) {
80
- if (!query) {
81
- return "";
82
- }
83
- const hash = Buffer.from(query).toString("base64").replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, Math.max(0, QUERY_HASH_LENGTH));
84
- return `_${hash}`;
85
- }
86
-
87
85
  // src/ProxyServer.ts
88
86
  var ProxyServer = class {
89
87
  targets;
@@ -209,6 +207,7 @@ var ProxyServer = class {
209
207
  this.recordingId = null;
210
208
  this.replayId = null;
211
209
  this.currentSession = null;
210
+ clearTimeout(this.modeTimeout || 0);
212
211
  console.log("Switched to transparent mode");
213
212
  }
214
213
  switchToRecordMode(id) {
@@ -259,7 +258,7 @@ var ProxyServer = class {
259
258
  if (!this.currentSession) {
260
259
  return;
261
260
  }
262
- const key = generateRequestKey(req);
261
+ const key = getReqID(req);
263
262
  const record = {
264
263
  request: {
265
264
  method: req.method,
@@ -276,7 +275,7 @@ var ProxyServer = class {
276
275
  if (!this.currentSession) {
277
276
  return;
278
277
  }
279
- const key = generateRequestKey(req);
278
+ const key = getReqID(req);
280
279
  const record = this.currentSession.recordings.find((r) => r.key === key);
281
280
  if (!record) {
282
281
  console.error("Request record not found for response:", key);
@@ -298,7 +297,7 @@ var ProxyServer = class {
298
297
  });
299
298
  }
300
299
  async handleReplayRequest(req, res) {
301
- const key = generateRequestKey(req);
300
+ const key = getReqID(req);
302
301
  const filePath = getRecordingPath(this.recordingsDir, this.replayId);
303
302
  try {
304
303
  const session = await loadRecordingSession(filePath);
@@ -555,15 +554,15 @@ function generateSessionId(testInfo) {
555
554
  }
556
555
  async function startRecording(testInfo) {
557
556
  const sessionId = generateSessionId(testInfo);
558
- await setProxyMode("recording", sessionId);
557
+ await setProxyMode(Modes.record, sessionId);
559
558
  }
560
559
  async function startReplay(testInfo) {
561
560
  const sessionId = generateSessionId(testInfo);
562
- await setProxyMode("replay", sessionId);
561
+ await setProxyMode(Modes.replay, sessionId);
563
562
  }
564
563
  async function stopProxy(testInfo) {
565
564
  const sessionId = generateSessionId(testInfo);
566
- await setProxyMode("transparent", sessionId);
565
+ await setProxyMode(Modes.transparent, sessionId);
567
566
  }
568
567
  var playwrightProxy = {
569
568
  /**
@@ -582,7 +581,7 @@ var playwrightProxy = {
582
581
  */
583
582
  async after(testInfo) {
584
583
  const sessionId = generateSessionId(testInfo);
585
- await setProxyMode("transparent", sessionId);
584
+ await setProxyMode(Modes.transparent, sessionId);
586
585
  }
587
586
  };
588
587
 
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import http from 'node:http';
2
- export { PlaywrightTestInfo, ProxyMode, generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy } from './playwright/index.cjs';
2
+ export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-DfFpm8mB.cjs';
3
3
  import '@playwright/test';
4
4
 
5
5
  declare class ProxyServer {
@@ -40,49 +40,4 @@ declare class ProxyServer {
40
40
  private logServerStartup;
41
41
  }
42
42
 
43
- declare const Modes: {
44
- readonly transparent: "transparent";
45
- readonly record: "record";
46
- readonly replay: "replay";
47
- };
48
- type Mode = (typeof Modes)[keyof typeof Modes];
49
- interface ControlRequest {
50
- mode: Mode;
51
- id?: string;
52
- timeout?: number;
53
- }
54
- interface RecordedRequest {
55
- method: string;
56
- url: string;
57
- headers: http.IncomingHttpHeaders;
58
- body: string | null;
59
- }
60
- interface RecordedResponse {
61
- statusCode: number;
62
- headers: http.IncomingHttpHeaders;
63
- body: string | null;
64
- }
65
- interface Recording {
66
- request: RecordedRequest;
67
- response?: RecordedResponse;
68
- timestamp: string;
69
- key: string;
70
- }
71
- interface WebSocketMessage {
72
- direction: 'client-to-server' | 'server-to-client';
73
- data: string;
74
- timestamp: string;
75
- }
76
- interface WebSocketRecording {
77
- url: string;
78
- messages: WebSocketMessage[];
79
- timestamp: string;
80
- key: string;
81
- }
82
- interface RecordingSession {
83
- id: string;
84
- recordings: Recording[];
85
- websocketRecordings: WebSocketRecording[];
86
- }
87
-
88
- export { type ControlRequest, type Mode, ProxyServer, type Recording, type RecordingSession, type WebSocketRecording };
43
+ export { ProxyServer };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import http from 'node:http';
2
- export { PlaywrightTestInfo, ProxyMode, generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy } from './playwright/index.js';
2
+ export { C as ControlRequest, M as Mode, P as PlaywrightTestInfo, R as Recording, a as RecordingSession, W as WebSocketRecording, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from './index-DfFpm8mB.js';
3
3
  import '@playwright/test';
4
4
 
5
5
  declare class ProxyServer {
@@ -40,49 +40,4 @@ declare class ProxyServer {
40
40
  private logServerStartup;
41
41
  }
42
42
 
43
- declare const Modes: {
44
- readonly transparent: "transparent";
45
- readonly record: "record";
46
- readonly replay: "replay";
47
- };
48
- type Mode = (typeof Modes)[keyof typeof Modes];
49
- interface ControlRequest {
50
- mode: Mode;
51
- id?: string;
52
- timeout?: number;
53
- }
54
- interface RecordedRequest {
55
- method: string;
56
- url: string;
57
- headers: http.IncomingHttpHeaders;
58
- body: string | null;
59
- }
60
- interface RecordedResponse {
61
- statusCode: number;
62
- headers: http.IncomingHttpHeaders;
63
- body: string | null;
64
- }
65
- interface Recording {
66
- request: RecordedRequest;
67
- response?: RecordedResponse;
68
- timestamp: string;
69
- key: string;
70
- }
71
- interface WebSocketMessage {
72
- direction: 'client-to-server' | 'server-to-client';
73
- data: string;
74
- timestamp: string;
75
- }
76
- interface WebSocketRecording {
77
- url: string;
78
- messages: WebSocketMessage[];
79
- timestamp: string;
80
- key: string;
81
- }
82
- interface RecordingSession {
83
- id: string;
84
- recordings: Recording[];
85
- websocketRecordings: WebSocketRecording[];
86
- }
87
-
88
- export { type ControlRequest, type Mode, ProxyServer, type Recording, type RecordingSession, type WebSocketRecording };
43
+ export { ProxyServer };
package/dist/index.mjs CHANGED
@@ -3,6 +3,7 @@ import http from 'http';
3
3
  import httpProxy from 'http-proxy';
4
4
  import { WebSocket, WebSocketServer } from 'ws';
5
5
  import path from 'path';
6
+ import filenamify from 'filenamify';
6
7
 
7
8
  // src/ProxyServer.ts
8
9
 
@@ -38,6 +39,24 @@ async function saveRecordingSession(recordingsDir, session) {
38
39
  `Saved ${session.recordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
39
40
  );
40
41
  }
42
+ var QUERY_HASH_LENGTH = 8;
43
+ function getReqID(req) {
44
+ const urlParts = req.url.split("?");
45
+ const pathname = urlParts[0];
46
+ const query = urlParts[1] || "";
47
+ const pathPart = pathname === "/" ? "root" : pathname.slice(1);
48
+ const normalizedPath = filenamify(pathPart, { replacement: "_" });
49
+ const queryHash = generateQueryHash(query);
50
+ const filename = `${req.method}_${normalizedPath}${queryHash}.json`;
51
+ return filenamify(filename, { replacement: "_" });
52
+ }
53
+ function generateQueryHash(query) {
54
+ if (!query) {
55
+ return "";
56
+ }
57
+ const hash = Buffer.from(query).toString("base64").replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, Math.max(0, QUERY_HASH_LENGTH));
58
+ return `_${hash}`;
59
+ }
41
60
 
42
61
  // src/utils/httpHelpers.ts
43
62
  var CONTENT_TYPE_JSON = "application/json";
@@ -53,28 +72,6 @@ function sendJsonResponse(res, statusCode, data) {
53
72
  res.end(JSON.stringify(data));
54
73
  }
55
74
 
56
- // src/utils/requestKeyGenerator.ts
57
- var QUERY_HASH_LENGTH = 8;
58
- function generateRequestKey(req) {
59
- const urlParts = req.url.split("?");
60
- const pathname = urlParts[0];
61
- const query = urlParts[1] || "";
62
- const normalizedPath = normalizePathname(pathname);
63
- const queryHash = generateQueryHash(query);
64
- return `${req.method}_${normalizedPath}${queryHash}.json`;
65
- }
66
- function normalizePathname(pathname) {
67
- const normalized = pathname.replaceAll("/", "_").replace(/^_/, "");
68
- return normalized || "root";
69
- }
70
- function generateQueryHash(query) {
71
- if (!query) {
72
- return "";
73
- }
74
- const hash = Buffer.from(query).toString("base64").replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, Math.max(0, QUERY_HASH_LENGTH));
75
- return `_${hash}`;
76
- }
77
-
78
75
  // src/ProxyServer.ts
79
76
  var ProxyServer = class {
80
77
  targets;
@@ -200,6 +197,7 @@ var ProxyServer = class {
200
197
  this.recordingId = null;
201
198
  this.replayId = null;
202
199
  this.currentSession = null;
200
+ clearTimeout(this.modeTimeout || 0);
203
201
  console.log("Switched to transparent mode");
204
202
  }
205
203
  switchToRecordMode(id) {
@@ -250,7 +248,7 @@ var ProxyServer = class {
250
248
  if (!this.currentSession) {
251
249
  return;
252
250
  }
253
- const key = generateRequestKey(req);
251
+ const key = getReqID(req);
254
252
  const record = {
255
253
  request: {
256
254
  method: req.method,
@@ -267,7 +265,7 @@ var ProxyServer = class {
267
265
  if (!this.currentSession) {
268
266
  return;
269
267
  }
270
- const key = generateRequestKey(req);
268
+ const key = getReqID(req);
271
269
  const record = this.currentSession.recordings.find((r) => r.key === key);
272
270
  if (!record) {
273
271
  console.error("Request record not found for response:", key);
@@ -289,7 +287,7 @@ var ProxyServer = class {
289
287
  });
290
288
  }
291
289
  async handleReplayRequest(req, res) {
292
- const key = generateRequestKey(req);
290
+ const key = getReqID(req);
293
291
  const filePath = getRecordingPath(this.recordingsDir, this.replayId);
294
292
  try {
295
293
  const session = await loadRecordingSession(filePath);
@@ -546,15 +544,15 @@ function generateSessionId(testInfo) {
546
544
  }
547
545
  async function startRecording(testInfo) {
548
546
  const sessionId = generateSessionId(testInfo);
549
- await setProxyMode("recording", sessionId);
547
+ await setProxyMode(Modes.record, sessionId);
550
548
  }
551
549
  async function startReplay(testInfo) {
552
550
  const sessionId = generateSessionId(testInfo);
553
- await setProxyMode("replay", sessionId);
551
+ await setProxyMode(Modes.replay, sessionId);
554
552
  }
555
553
  async function stopProxy(testInfo) {
556
554
  const sessionId = generateSessionId(testInfo);
557
- await setProxyMode("transparent", sessionId);
555
+ await setProxyMode(Modes.transparent, sessionId);
558
556
  }
559
557
  var playwrightProxy = {
560
558
  /**
@@ -573,7 +571,7 @@ var playwrightProxy = {
573
571
  */
574
572
  async after(testInfo) {
575
573
  const sessionId = generateSessionId(testInfo);
576
- await setProxyMode("transparent", sessionId);
574
+ await setProxyMode(Modes.transparent, sessionId);
577
575
  }
578
576
  };
579
577
 
@@ -1,5 +1,12 @@
1
1
  'use strict';
2
2
 
3
+ // src/types.ts
4
+ var Modes = {
5
+ transparent: "transparent",
6
+ record: "record",
7
+ replay: "replay"
8
+ };
9
+
3
10
  // src/playwright/index.ts
4
11
  var INTERNAL_API_URL = process.env.INTERNAL_API_URL || "http://localhost:8100";
5
12
  async function setProxyMode(mode, sessionId, timeout) {
@@ -31,15 +38,15 @@ function generateSessionId(testInfo) {
31
38
  }
32
39
  async function startRecording(testInfo) {
33
40
  const sessionId = generateSessionId(testInfo);
34
- await setProxyMode("recording", sessionId);
41
+ await setProxyMode(Modes.record, sessionId);
35
42
  }
36
43
  async function startReplay(testInfo) {
37
44
  const sessionId = generateSessionId(testInfo);
38
- await setProxyMode("replay", sessionId);
45
+ await setProxyMode(Modes.replay, sessionId);
39
46
  }
40
47
  async function stopProxy(testInfo) {
41
48
  const sessionId = generateSessionId(testInfo);
42
- await setProxyMode("transparent", sessionId);
49
+ await setProxyMode(Modes.transparent, sessionId);
43
50
  }
44
51
  var playwrightProxy = {
45
52
  /**
@@ -58,7 +65,7 @@ var playwrightProxy = {
58
65
  */
59
66
  async after(testInfo) {
60
67
  const sessionId = generateSessionId(testInfo);
61
- await setProxyMode("transparent", sessionId);
68
+ await setProxyMode(Modes.transparent, sessionId);
62
69
  }
63
70
  };
64
71
 
@@ -1,50 +1,3 @@
1
- import { TestInfo } from '@playwright/test';
2
-
3
- type ProxyMode = 'recording' | 'replay' | 'transparent';
4
- type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
5
- /**
6
- * Set the proxy mode for a given session
7
- * @param mode - The proxy mode to set (recording, replay, transparent)
8
- * @param sessionId - Unique identifier for the session
9
- * @param timeout - Optional timeout in milliseconds
10
- */
11
- declare function setProxyMode(mode: ProxyMode, sessionId: string, timeout?: number): Promise<void>;
12
- /**
13
- * Generate a session ID from test info
14
- * @param testInfo - Playwright test info object
15
- */
16
- declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
17
- /**
18
- * Start recording for a test
19
- * @param testInfo - Playwright test info object
20
- */
21
- declare function startRecording(testInfo: PlaywrightTestInfo): Promise<void>;
22
- /**
23
- * Start replay for a test
24
- * @param testInfo - Playwright test info object
25
- */
26
- declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
27
- /**
28
- * Stop recording/replay and return to transparent mode
29
- * @param testInfo - Playwright test info object
30
- */
31
- declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
32
- /**
33
- * Playwright test fixture helper for managing proxy mode
34
- * Use this in beforeEach/afterEach hooks
35
- */
36
- declare const playwrightProxy: {
37
- /**
38
- * Setup before test - sets the proxy mode
39
- * @param testInfo - Playwright test info object
40
- * @param mode - The proxy mode to use for this test
41
- */
42
- before(testInfo: PlaywrightTestInfo, mode: ProxyMode): Promise<void>;
43
- /**
44
- * Cleanup after test - returns to transparent mode
45
- * @param testInfo - Playwright test info object
46
- */
47
- after(testInfo: PlaywrightTestInfo): Promise<void>;
48
- };
49
-
50
- export { type PlaywrightTestInfo, type ProxyMode, generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy };
1
+ import '@playwright/test';
2
+ export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-DfFpm8mB.cjs';
3
+ import 'node:http';
@@ -1,50 +1,3 @@
1
- import { TestInfo } from '@playwright/test';
2
-
3
- type ProxyMode = 'recording' | 'replay' | 'transparent';
4
- type PlaywrightTestInfo = Pick<TestInfo, 'title'>;
5
- /**
6
- * Set the proxy mode for a given session
7
- * @param mode - The proxy mode to set (recording, replay, transparent)
8
- * @param sessionId - Unique identifier for the session
9
- * @param timeout - Optional timeout in milliseconds
10
- */
11
- declare function setProxyMode(mode: ProxyMode, sessionId: string, timeout?: number): Promise<void>;
12
- /**
13
- * Generate a session ID from test info
14
- * @param testInfo - Playwright test info object
15
- */
16
- declare function generateSessionId(testInfo: PlaywrightTestInfo): string;
17
- /**
18
- * Start recording for a test
19
- * @param testInfo - Playwright test info object
20
- */
21
- declare function startRecording(testInfo: PlaywrightTestInfo): Promise<void>;
22
- /**
23
- * Start replay for a test
24
- * @param testInfo - Playwright test info object
25
- */
26
- declare function startReplay(testInfo: PlaywrightTestInfo): Promise<void>;
27
- /**
28
- * Stop recording/replay and return to transparent mode
29
- * @param testInfo - Playwright test info object
30
- */
31
- declare function stopProxy(testInfo: PlaywrightTestInfo): Promise<void>;
32
- /**
33
- * Playwright test fixture helper for managing proxy mode
34
- * Use this in beforeEach/afterEach hooks
35
- */
36
- declare const playwrightProxy: {
37
- /**
38
- * Setup before test - sets the proxy mode
39
- * @param testInfo - Playwright test info object
40
- * @param mode - The proxy mode to use for this test
41
- */
42
- before(testInfo: PlaywrightTestInfo, mode: ProxyMode): Promise<void>;
43
- /**
44
- * Cleanup after test - returns to transparent mode
45
- * @param testInfo - Playwright test info object
46
- */
47
- after(testInfo: PlaywrightTestInfo): Promise<void>;
48
- };
49
-
50
- export { type PlaywrightTestInfo, type ProxyMode, generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy };
1
+ import '@playwright/test';
2
+ export { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-DfFpm8mB.js';
3
+ import 'node:http';
@@ -1,3 +1,10 @@
1
+ // src/types.ts
2
+ var Modes = {
3
+ transparent: "transparent",
4
+ record: "record",
5
+ replay: "replay"
6
+ };
7
+
1
8
  // src/playwright/index.ts
2
9
  var INTERNAL_API_URL = process.env.INTERNAL_API_URL || "http://localhost:8100";
3
10
  async function setProxyMode(mode, sessionId, timeout) {
@@ -29,15 +36,15 @@ function generateSessionId(testInfo) {
29
36
  }
30
37
  async function startRecording(testInfo) {
31
38
  const sessionId = generateSessionId(testInfo);
32
- await setProxyMode("recording", sessionId);
39
+ await setProxyMode(Modes.record, sessionId);
33
40
  }
34
41
  async function startReplay(testInfo) {
35
42
  const sessionId = generateSessionId(testInfo);
36
- await setProxyMode("replay", sessionId);
43
+ await setProxyMode(Modes.replay, sessionId);
37
44
  }
38
45
  async function stopProxy(testInfo) {
39
46
  const sessionId = generateSessionId(testInfo);
40
- await setProxyMode("transparent", sessionId);
47
+ await setProxyMode(Modes.transparent, sessionId);
41
48
  }
42
49
  var playwrightProxy = {
43
50
  /**
@@ -56,7 +63,7 @@ var playwrightProxy = {
56
63
  */
57
64
  async after(testInfo) {
58
65
  const sessionId = generateSessionId(testInfo);
59
- await setProxyMode("transparent", sessionId);
66
+ await setProxyMode(Modes.transparent, sessionId);
60
67
  }
61
68
  };
62
69
 
package/dist/proxy.js CHANGED
@@ -4,6 +4,7 @@ import fs from 'fs/promises';
4
4
  import http from 'http';
5
5
  import httpProxy from 'http-proxy';
6
6
  import { WebSocket, WebSocketServer } from 'ws';
7
+ import filenamify from 'filenamify';
7
8
 
8
9
  // src/cli.ts
9
10
  var DEFAULT_PORT = 8e3;
@@ -72,6 +73,24 @@ async function saveRecordingSession(recordingsDir2, session) {
72
73
  `Saved ${session.recordings.length} HTTP recordings and ${session.websocketRecordings?.length || 0} WebSocket recordings to ${filePath}`
73
74
  );
74
75
  }
76
+ var QUERY_HASH_LENGTH = 8;
77
+ function getReqID(req) {
78
+ const urlParts = req.url.split("?");
79
+ const pathname = urlParts[0];
80
+ const query = urlParts[1] || "";
81
+ const pathPart = pathname === "/" ? "root" : pathname.slice(1);
82
+ const normalizedPath = filenamify(pathPart, { replacement: "_" });
83
+ const queryHash = generateQueryHash(query);
84
+ const filename = `${req.method}_${normalizedPath}${queryHash}.json`;
85
+ return filenamify(filename, { replacement: "_" });
86
+ }
87
+ function generateQueryHash(query) {
88
+ if (!query) {
89
+ return "";
90
+ }
91
+ const hash = Buffer.from(query).toString("base64").replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, Math.max(0, QUERY_HASH_LENGTH));
92
+ return `_${hash}`;
93
+ }
75
94
 
76
95
  // src/utils/httpHelpers.ts
77
96
  var CONTENT_TYPE_JSON = "application/json";
@@ -87,28 +106,6 @@ function sendJsonResponse(res, statusCode, data) {
87
106
  res.end(JSON.stringify(data));
88
107
  }
89
108
 
90
- // src/utils/requestKeyGenerator.ts
91
- var QUERY_HASH_LENGTH = 8;
92
- function generateRequestKey(req) {
93
- const urlParts = req.url.split("?");
94
- const pathname = urlParts[0];
95
- const query = urlParts[1] || "";
96
- const normalizedPath = normalizePathname(pathname);
97
- const queryHash = generateQueryHash(query);
98
- return `${req.method}_${normalizedPath}${queryHash}.json`;
99
- }
100
- function normalizePathname(pathname) {
101
- const normalized = pathname.replaceAll("/", "_").replace(/^_/, "");
102
- return normalized || "root";
103
- }
104
- function generateQueryHash(query) {
105
- if (!query) {
106
- return "";
107
- }
108
- const hash = Buffer.from(query).toString("base64").replaceAll(/[^a-zA-Z0-9]/g, "").slice(0, Math.max(0, QUERY_HASH_LENGTH));
109
- return `_${hash}`;
110
- }
111
-
112
109
  // src/ProxyServer.ts
113
110
  var ProxyServer = class {
114
111
  targets;
@@ -234,6 +231,7 @@ var ProxyServer = class {
234
231
  this.recordingId = null;
235
232
  this.replayId = null;
236
233
  this.currentSession = null;
234
+ clearTimeout(this.modeTimeout || 0);
237
235
  console.log("Switched to transparent mode");
238
236
  }
239
237
  switchToRecordMode(id) {
@@ -284,7 +282,7 @@ var ProxyServer = class {
284
282
  if (!this.currentSession) {
285
283
  return;
286
284
  }
287
- const key = generateRequestKey(req);
285
+ const key = getReqID(req);
288
286
  const record = {
289
287
  request: {
290
288
  method: req.method,
@@ -301,7 +299,7 @@ var ProxyServer = class {
301
299
  if (!this.currentSession) {
302
300
  return;
303
301
  }
304
- const key = generateRequestKey(req);
302
+ const key = getReqID(req);
305
303
  const record = this.currentSession.recordings.find((r) => r.key === key);
306
304
  if (!record) {
307
305
  console.error("Request record not found for response:", key);
@@ -323,7 +321,7 @@ var ProxyServer = class {
323
321
  });
324
322
  }
325
323
  async handleReplayRequest(req, res) {
326
- const key = generateRequestKey(req);
324
+ const key = getReqID(req);
327
325
  const filePath = getRecordingPath(this.recordingsDir, this.replayId);
328
326
  try {
329
327
  const session = await loadRecordingSession(filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "test-proxy-recorder",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "HTTP proxy server for recording and replaying network requests in testing. Works seamlessly with Playwright testing framework.",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",