test-proxy-recorder 0.3.0 → 0.3.2
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 +146 -21
- package/dist/{index-CVuiglPk.d.cts → index-BlBWqSE4.d.cts} +21 -4
- package/dist/{index-CVuiglPk.d.ts → index-BlBWqSE4.d.ts} +21 -4
- package/dist/index.cjs +439 -260
- package/dist/index.d.cts +15 -7
- package/dist/index.d.ts +15 -7
- package/dist/index.mjs +438 -259
- package/dist/playwright/index.cjs +151 -13
- package/dist/playwright/index.d.cts +1 -1
- package/dist/playwright/index.d.ts +1 -1
- package/dist/playwright/index.mjs +147 -14
- package/dist/proxy.js +296 -246
- package/package.json +1 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var path = require('path');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
8
|
+
|
|
9
|
+
// src/playwright/index.ts
|
|
4
10
|
var RECORDING_ID_HEADER = "x-test-rcrd-id";
|
|
5
11
|
|
|
6
12
|
// src/types.ts
|
|
@@ -46,6 +52,30 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
46
52
|
throw error;
|
|
47
53
|
}
|
|
48
54
|
}
|
|
55
|
+
async function cleanupSession(sessionId) {
|
|
56
|
+
const proxyPort = getProxyPort();
|
|
57
|
+
try {
|
|
58
|
+
const body = {
|
|
59
|
+
cleanup: true,
|
|
60
|
+
id: sessionId
|
|
61
|
+
};
|
|
62
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify(body)
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const text = await response.text();
|
|
69
|
+
console.error(`Failed to cleanup session ${sessionId}:`, text);
|
|
70
|
+
throw new Error(`Failed to cleanup session: ${text}`);
|
|
71
|
+
}
|
|
72
|
+
await response.json();
|
|
73
|
+
console.log(`Session cleaned up: ${sessionId}`);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(`Error cleaning up session:`, error);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
49
79
|
function parseSpecFilePath(specPath) {
|
|
50
80
|
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
51
81
|
if (folderMatch) {
|
|
@@ -87,6 +117,53 @@ async function stopProxy(testInfo) {
|
|
|
87
117
|
const sessionId = generateSessionId(testInfo);
|
|
88
118
|
await setProxyMode(Modes.transparent, sessionId);
|
|
89
119
|
}
|
|
120
|
+
var cachedRecordingsDir = null;
|
|
121
|
+
async function getRecordingsDir() {
|
|
122
|
+
if (cachedRecordingsDir) {
|
|
123
|
+
return cachedRecordingsDir;
|
|
124
|
+
}
|
|
125
|
+
const proxyPort = getProxyPort();
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`);
|
|
128
|
+
if (response.ok) {
|
|
129
|
+
const data = await response.json();
|
|
130
|
+
if (data.recordingsDir) {
|
|
131
|
+
cachedRecordingsDir = data.recordingsDir;
|
|
132
|
+
return cachedRecordingsDir;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.warn(
|
|
137
|
+
"Failed to get recordings directory from proxy, using default:",
|
|
138
|
+
error
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
cachedRecordingsDir = path__default.default.join(process.cwd(), "e2e", "recordings");
|
|
142
|
+
return cachedRecordingsDir;
|
|
143
|
+
}
|
|
144
|
+
async function setupClientSideRecording(page, sessionId, mode, url) {
|
|
145
|
+
const harFileName = sessionId.replaceAll("/", "__");
|
|
146
|
+
const recordingsDir = await getRecordingsDir();
|
|
147
|
+
const harPath = path__default.default.join(recordingsDir, `${harFileName}.har`);
|
|
148
|
+
console.log(
|
|
149
|
+
`[Client-Side Recording] Setting up HAR for session: ${sessionId}, mode: ${mode}, path: ${harPath}`
|
|
150
|
+
);
|
|
151
|
+
try {
|
|
152
|
+
await page.routeFromHAR(harPath, {
|
|
153
|
+
url,
|
|
154
|
+
update: mode === Modes.record,
|
|
155
|
+
updateContent: "embed"
|
|
156
|
+
});
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (mode === Modes.replay) {
|
|
159
|
+
console.error(
|
|
160
|
+
`[Client-Side Replay] Failed to load HAR file. Run tests in record mode first.`,
|
|
161
|
+
error
|
|
162
|
+
);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
90
167
|
var playwrightProxy = {
|
|
91
168
|
/**
|
|
92
169
|
* Setup before test - sets the proxy mode and configures page with custom header
|
|
@@ -94,24 +171,84 @@ var playwrightProxy = {
|
|
|
94
171
|
* @param page - Playwright page object
|
|
95
172
|
* @param testInfo - Playwright test info object
|
|
96
173
|
* @param mode - The proxy mode to use for this test
|
|
97
|
-
* @param
|
|
174
|
+
* @param options - Optional configuration including timeout and client-side recording patterns
|
|
98
175
|
*/
|
|
99
|
-
async before(page, testInfo, mode,
|
|
176
|
+
async before(page, testInfo, mode, options) {
|
|
177
|
+
const timeout = typeof options === "number" ? options : options?.timeout;
|
|
178
|
+
const clientSideOptions = typeof options === "object" && options !== null ? options : void 0;
|
|
100
179
|
const sessionId = generateSessionId(testInfo);
|
|
101
180
|
await page.setExtraHTTPHeaders({
|
|
102
181
|
[RECORDING_ID_HEADER]: sessionId
|
|
103
182
|
});
|
|
183
|
+
console.log(`[Setup] Setting proxy mode: ${mode}, session: ${sessionId}`);
|
|
104
184
|
await setProxyMode(mode, sessionId, timeout);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
185
|
+
console.log(`[Setup] Proxy mode set successfully`);
|
|
186
|
+
if (clientSideOptions?.url) {
|
|
187
|
+
console.log(`[Setup] Setting up client-side recording with pattern: ${clientSideOptions.url}`);
|
|
188
|
+
await setupClientSideRecording(
|
|
189
|
+
page,
|
|
190
|
+
sessionId,
|
|
191
|
+
mode,
|
|
192
|
+
clientSideOptions.url
|
|
193
|
+
);
|
|
194
|
+
console.log(`[Setup] Client-side recording setup complete`);
|
|
195
|
+
}
|
|
196
|
+
const proxyPort = process.env.TEST_PROXY_RECORDER_PORT || "8100";
|
|
197
|
+
const proxyUrl = `localhost:${proxyPort}`;
|
|
198
|
+
console.log(`[Setup] Registering proxy route handler for: ${proxyUrl}`);
|
|
199
|
+
await page.route(
|
|
200
|
+
(url) => {
|
|
201
|
+
const urlStr = url.toString();
|
|
202
|
+
const matches = urlStr.includes(proxyUrl);
|
|
203
|
+
if (matches) {
|
|
204
|
+
console.log(`[Route Matcher] Matched proxy request: ${urlStr}`);
|
|
205
|
+
}
|
|
206
|
+
return matches;
|
|
207
|
+
},
|
|
208
|
+
async (route) => {
|
|
209
|
+
try {
|
|
210
|
+
const url = route.request().url();
|
|
211
|
+
const method = route.request().method();
|
|
212
|
+
const headers = route.request().headers();
|
|
213
|
+
const hadHeader = !!headers[RECORDING_ID_HEADER];
|
|
214
|
+
headers[RECORDING_ID_HEADER] = sessionId;
|
|
215
|
+
console.log(
|
|
216
|
+
`[Route Intercept] ${method} ${url} (had header: ${hadHeader}, adding session: ${sessionId})`
|
|
217
|
+
);
|
|
218
|
+
await route.continue({ headers });
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(
|
|
221
|
+
`[Route Handler Error] Failed to add ${RECORDING_ID_HEADER} header:`,
|
|
222
|
+
error
|
|
223
|
+
);
|
|
224
|
+
await route.fallback();
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{ times: Infinity }
|
|
228
|
+
// Ensure the handler applies to all matching requests
|
|
229
|
+
);
|
|
230
|
+
console.log(`[Setup] Proxy route handler registered`);
|
|
231
|
+
const context = page.context();
|
|
232
|
+
const contextId = context._guid || "default";
|
|
233
|
+
const handlerKey = `cleanup_${contextId}`;
|
|
234
|
+
if (!globalThis[handlerKey]) {
|
|
235
|
+
globalThis[handlerKey] = true;
|
|
236
|
+
context.on("close", async () => {
|
|
237
|
+
try {
|
|
238
|
+
console.log(
|
|
239
|
+
`[Cleanup] Browser context closed, cleaning up session: ${sessionId}`
|
|
240
|
+
);
|
|
241
|
+
await cleanupSession(sessionId);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.warn(
|
|
244
|
+
`[Cleanup] Failed to cleanup session ${sessionId}:`,
|
|
245
|
+
error
|
|
246
|
+
);
|
|
247
|
+
} finally {
|
|
248
|
+
delete globalThis[handlerKey];
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
115
252
|
},
|
|
116
253
|
/**
|
|
117
254
|
* Global teardown - switches proxy to transparent mode
|
|
@@ -122,6 +259,7 @@ var playwrightProxy = {
|
|
|
122
259
|
}
|
|
123
260
|
};
|
|
124
261
|
|
|
262
|
+
exports.cleanupSession = cleanupSession;
|
|
125
263
|
exports.generateSessionId = generateSessionId;
|
|
126
264
|
exports.playwrightProxy = playwrightProxy;
|
|
127
265
|
exports.setProxyMode = setProxyMode;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
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-
|
|
2
|
+
export { f as ClientSideRecordingOptions, P as PlaywrightTestInfo, e as cleanupSession, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-BlBWqSE4.cjs';
|
|
3
3
|
import 'node:http';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
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-
|
|
2
|
+
export { f as ClientSideRecordingOptions, P as PlaywrightTestInfo, e as cleanupSession, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-BlBWqSE4.js';
|
|
3
3
|
import 'node:http';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
// src/playwright/index.ts
|
|
2
4
|
var RECORDING_ID_HEADER = "x-test-rcrd-id";
|
|
3
5
|
|
|
4
6
|
// src/types.ts
|
|
@@ -44,6 +46,30 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
44
46
|
throw error;
|
|
45
47
|
}
|
|
46
48
|
}
|
|
49
|
+
async function cleanupSession(sessionId) {
|
|
50
|
+
const proxyPort = getProxyPort();
|
|
51
|
+
try {
|
|
52
|
+
const body = {
|
|
53
|
+
cleanup: true,
|
|
54
|
+
id: sessionId
|
|
55
|
+
};
|
|
56
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: { "Content-Type": "application/json" },
|
|
59
|
+
body: JSON.stringify(body)
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const text = await response.text();
|
|
63
|
+
console.error(`Failed to cleanup session ${sessionId}:`, text);
|
|
64
|
+
throw new Error(`Failed to cleanup session: ${text}`);
|
|
65
|
+
}
|
|
66
|
+
await response.json();
|
|
67
|
+
console.log(`Session cleaned up: ${sessionId}`);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(`Error cleaning up session:`, error);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
47
73
|
function parseSpecFilePath(specPath) {
|
|
48
74
|
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
49
75
|
if (folderMatch) {
|
|
@@ -85,6 +111,53 @@ async function stopProxy(testInfo) {
|
|
|
85
111
|
const sessionId = generateSessionId(testInfo);
|
|
86
112
|
await setProxyMode(Modes.transparent, sessionId);
|
|
87
113
|
}
|
|
114
|
+
var cachedRecordingsDir = null;
|
|
115
|
+
async function getRecordingsDir() {
|
|
116
|
+
if (cachedRecordingsDir) {
|
|
117
|
+
return cachedRecordingsDir;
|
|
118
|
+
}
|
|
119
|
+
const proxyPort = getProxyPort();
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`);
|
|
122
|
+
if (response.ok) {
|
|
123
|
+
const data = await response.json();
|
|
124
|
+
if (data.recordingsDir) {
|
|
125
|
+
cachedRecordingsDir = data.recordingsDir;
|
|
126
|
+
return cachedRecordingsDir;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.warn(
|
|
131
|
+
"Failed to get recordings directory from proxy, using default:",
|
|
132
|
+
error
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
cachedRecordingsDir = path.join(process.cwd(), "e2e", "recordings");
|
|
136
|
+
return cachedRecordingsDir;
|
|
137
|
+
}
|
|
138
|
+
async function setupClientSideRecording(page, sessionId, mode, url) {
|
|
139
|
+
const harFileName = sessionId.replaceAll("/", "__");
|
|
140
|
+
const recordingsDir = await getRecordingsDir();
|
|
141
|
+
const harPath = path.join(recordingsDir, `${harFileName}.har`);
|
|
142
|
+
console.log(
|
|
143
|
+
`[Client-Side Recording] Setting up HAR for session: ${sessionId}, mode: ${mode}, path: ${harPath}`
|
|
144
|
+
);
|
|
145
|
+
try {
|
|
146
|
+
await page.routeFromHAR(harPath, {
|
|
147
|
+
url,
|
|
148
|
+
update: mode === Modes.record,
|
|
149
|
+
updateContent: "embed"
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (mode === Modes.replay) {
|
|
153
|
+
console.error(
|
|
154
|
+
`[Client-Side Replay] Failed to load HAR file. Run tests in record mode first.`,
|
|
155
|
+
error
|
|
156
|
+
);
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
88
161
|
var playwrightProxy = {
|
|
89
162
|
/**
|
|
90
163
|
* Setup before test - sets the proxy mode and configures page with custom header
|
|
@@ -92,24 +165,84 @@ var playwrightProxy = {
|
|
|
92
165
|
* @param page - Playwright page object
|
|
93
166
|
* @param testInfo - Playwright test info object
|
|
94
167
|
* @param mode - The proxy mode to use for this test
|
|
95
|
-
* @param
|
|
168
|
+
* @param options - Optional configuration including timeout and client-side recording patterns
|
|
96
169
|
*/
|
|
97
|
-
async before(page, testInfo, mode,
|
|
170
|
+
async before(page, testInfo, mode, options) {
|
|
171
|
+
const timeout = typeof options === "number" ? options : options?.timeout;
|
|
172
|
+
const clientSideOptions = typeof options === "object" && options !== null ? options : void 0;
|
|
98
173
|
const sessionId = generateSessionId(testInfo);
|
|
99
174
|
await page.setExtraHTTPHeaders({
|
|
100
175
|
[RECORDING_ID_HEADER]: sessionId
|
|
101
176
|
});
|
|
177
|
+
console.log(`[Setup] Setting proxy mode: ${mode}, session: ${sessionId}`);
|
|
102
178
|
await setProxyMode(mode, sessionId, timeout);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
179
|
+
console.log(`[Setup] Proxy mode set successfully`);
|
|
180
|
+
if (clientSideOptions?.url) {
|
|
181
|
+
console.log(`[Setup] Setting up client-side recording with pattern: ${clientSideOptions.url}`);
|
|
182
|
+
await setupClientSideRecording(
|
|
183
|
+
page,
|
|
184
|
+
sessionId,
|
|
185
|
+
mode,
|
|
186
|
+
clientSideOptions.url
|
|
187
|
+
);
|
|
188
|
+
console.log(`[Setup] Client-side recording setup complete`);
|
|
189
|
+
}
|
|
190
|
+
const proxyPort = process.env.TEST_PROXY_RECORDER_PORT || "8100";
|
|
191
|
+
const proxyUrl = `localhost:${proxyPort}`;
|
|
192
|
+
console.log(`[Setup] Registering proxy route handler for: ${proxyUrl}`);
|
|
193
|
+
await page.route(
|
|
194
|
+
(url) => {
|
|
195
|
+
const urlStr = url.toString();
|
|
196
|
+
const matches = urlStr.includes(proxyUrl);
|
|
197
|
+
if (matches) {
|
|
198
|
+
console.log(`[Route Matcher] Matched proxy request: ${urlStr}`);
|
|
199
|
+
}
|
|
200
|
+
return matches;
|
|
201
|
+
},
|
|
202
|
+
async (route) => {
|
|
203
|
+
try {
|
|
204
|
+
const url = route.request().url();
|
|
205
|
+
const method = route.request().method();
|
|
206
|
+
const headers = route.request().headers();
|
|
207
|
+
const hadHeader = !!headers[RECORDING_ID_HEADER];
|
|
208
|
+
headers[RECORDING_ID_HEADER] = sessionId;
|
|
209
|
+
console.log(
|
|
210
|
+
`[Route Intercept] ${method} ${url} (had header: ${hadHeader}, adding session: ${sessionId})`
|
|
211
|
+
);
|
|
212
|
+
await route.continue({ headers });
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error(
|
|
215
|
+
`[Route Handler Error] Failed to add ${RECORDING_ID_HEADER} header:`,
|
|
216
|
+
error
|
|
217
|
+
);
|
|
218
|
+
await route.fallback();
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{ times: Infinity }
|
|
222
|
+
// Ensure the handler applies to all matching requests
|
|
223
|
+
);
|
|
224
|
+
console.log(`[Setup] Proxy route handler registered`);
|
|
225
|
+
const context = page.context();
|
|
226
|
+
const contextId = context._guid || "default";
|
|
227
|
+
const handlerKey = `cleanup_${contextId}`;
|
|
228
|
+
if (!globalThis[handlerKey]) {
|
|
229
|
+
globalThis[handlerKey] = true;
|
|
230
|
+
context.on("close", async () => {
|
|
231
|
+
try {
|
|
232
|
+
console.log(
|
|
233
|
+
`[Cleanup] Browser context closed, cleaning up session: ${sessionId}`
|
|
234
|
+
);
|
|
235
|
+
await cleanupSession(sessionId);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.warn(
|
|
238
|
+
`[Cleanup] Failed to cleanup session ${sessionId}:`,
|
|
239
|
+
error
|
|
240
|
+
);
|
|
241
|
+
} finally {
|
|
242
|
+
delete globalThis[handlerKey];
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
113
246
|
},
|
|
114
247
|
/**
|
|
115
248
|
* Global teardown - switches proxy to transparent mode
|
|
@@ -120,6 +253,6 @@ var playwrightProxy = {
|
|
|
120
253
|
}
|
|
121
254
|
};
|
|
122
255
|
|
|
123
|
-
export { generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy };
|
|
256
|
+
export { cleanupSession, generateSessionId, playwrightProxy, setProxyMode, startRecording, startReplay, stopProxy };
|
|
124
257
|
//# sourceMappingURL=index.mjs.map
|
|
125
258
|
//# sourceMappingURL=index.mjs.map
|