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.
@@ -1,6 +1,12 @@
1
1
  'use strict';
2
2
 
3
- // src/constants.ts
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 timeout - Optional timeout in milliseconds
174
+ * @param options - Optional configuration including timeout and client-side recording patterns
98
175
  */
99
- async before(page, testInfo, mode, timeout) {
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
- page.on("close", async () => {
106
- try {
107
- await setProxyMode(Modes.replay, sessionId);
108
- console.log(
109
- `[Cleanup] Switched to transparent mode for session: ${sessionId}`
110
- );
111
- } catch (error) {
112
- console.error("[Cleanup] Error during page close cleanup:", error);
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-CVuiglPk.cjs';
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-CVuiglPk.js';
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
- // src/constants.ts
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 timeout - Optional timeout in milliseconds
168
+ * @param options - Optional configuration including timeout and client-side recording patterns
96
169
  */
97
- async before(page, testInfo, mode, timeout) {
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
- page.on("close", async () => {
104
- try {
105
- await setProxyMode(Modes.replay, sessionId);
106
- console.log(
107
- `[Cleanup] Switched to transparent mode for session: ${sessionId}`
108
- );
109
- } catch (error) {
110
- console.error("[Cleanup] Error during page close cleanup:", error);
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