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.
- package/dist/index-DfFpm8mB.d.cts +95 -0
- package/dist/index-DfFpm8mB.d.ts +95 -0
- package/dist/index.cjs +28 -29
- package/dist/index.d.cts +2 -47
- package/dist/index.d.ts +2 -47
- package/dist/index.mjs +27 -29
- package/dist/playwright/index.cjs +11 -4
- package/dist/playwright/index.d.cts +3 -50
- package/dist/playwright/index.d.ts +3 -50
- package/dist/playwright/index.mjs +11 -4
- package/dist/proxy.js +23 -25
- package/package.json +1 -1
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
557
|
+
await setProxyMode(Modes.record, sessionId);
|
|
559
558
|
}
|
|
560
559
|
async function startReplay(testInfo) {
|
|
561
560
|
const sessionId = generateSessionId(testInfo);
|
|
562
|
-
await setProxyMode(
|
|
561
|
+
await setProxyMode(Modes.replay, sessionId);
|
|
563
562
|
}
|
|
564
563
|
async function stopProxy(testInfo) {
|
|
565
564
|
const sessionId = generateSessionId(testInfo);
|
|
566
|
-
await setProxyMode(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
547
|
+
await setProxyMode(Modes.record, sessionId);
|
|
550
548
|
}
|
|
551
549
|
async function startReplay(testInfo) {
|
|
552
550
|
const sessionId = generateSessionId(testInfo);
|
|
553
|
-
await setProxyMode(
|
|
551
|
+
await setProxyMode(Modes.replay, sessionId);
|
|
554
552
|
}
|
|
555
553
|
async function stopProxy(testInfo) {
|
|
556
554
|
const sessionId = generateSessionId(testInfo);
|
|
557
|
-
await setProxyMode(
|
|
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(
|
|
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(
|
|
41
|
+
await setProxyMode(Modes.record, sessionId);
|
|
35
42
|
}
|
|
36
43
|
async function startReplay(testInfo) {
|
|
37
44
|
const sessionId = generateSessionId(testInfo);
|
|
38
|
-
await setProxyMode(
|
|
45
|
+
await setProxyMode(Modes.replay, sessionId);
|
|
39
46
|
}
|
|
40
47
|
async function stopProxy(testInfo) {
|
|
41
48
|
const sessionId = generateSessionId(testInfo);
|
|
42
|
-
await setProxyMode(
|
|
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(
|
|
68
|
+
await setProxyMode(Modes.transparent, sessionId);
|
|
62
69
|
}
|
|
63
70
|
};
|
|
64
71
|
|
|
@@ -1,50 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
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(
|
|
39
|
+
await setProxyMode(Modes.record, sessionId);
|
|
33
40
|
}
|
|
34
41
|
async function startReplay(testInfo) {
|
|
35
42
|
const sessionId = generateSessionId(testInfo);
|
|
36
|
-
await setProxyMode(
|
|
43
|
+
await setProxyMode(Modes.replay, sessionId);
|
|
37
44
|
}
|
|
38
45
|
async function stopProxy(testInfo) {
|
|
39
46
|
const sessionId = generateSessionId(testInfo);
|
|
40
|
-
await setProxyMode(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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",
|