test-proxy-recorder 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +345 -152
- package/dist/{index-De4mgziH.d.cts → index-CBjvm5rb.d.cts} +5 -1
- package/dist/{index-De4mgziH.d.ts → index-CBjvm5rb.d.ts} +5 -1
- package/dist/index.cjs +210 -35
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.mjs +210 -35
- package/dist/playwright/index.cjs +39 -3
- package/dist/playwright/index.d.cts +1 -1
- package/dist/playwright/index.d.ts +1 -1
- package/dist/playwright/index.mjs +39 -3
- package/dist/proxy.js +173 -34
- package/package.json +8 -3
package/dist/index.mjs
CHANGED
|
@@ -24,7 +24,7 @@ var Modes = {
|
|
|
24
24
|
};
|
|
25
25
|
var JSON_INDENT_SPACES = 2;
|
|
26
26
|
function getRecordingPath(recordingsDir, id) {
|
|
27
|
-
return path.join(recordingsDir, `${id}.json`);
|
|
27
|
+
return path.join(recordingsDir, `${id}.mock.json`);
|
|
28
28
|
}
|
|
29
29
|
async function loadRecordingSession(filePath) {
|
|
30
30
|
const fileContent = await fs.readFile(filePath, "utf8");
|
|
@@ -32,6 +32,8 @@ async function loadRecordingSession(filePath) {
|
|
|
32
32
|
}
|
|
33
33
|
async function saveRecordingSession(recordingsDir, session) {
|
|
34
34
|
const filePath = getRecordingPath(recordingsDir, session.id);
|
|
35
|
+
const dirPath = path.dirname(filePath);
|
|
36
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
35
37
|
await fs.writeFile(
|
|
36
38
|
filePath,
|
|
37
39
|
JSON.stringify(session, null, JSON_INDENT_SPACES)
|
|
@@ -116,6 +118,7 @@ var ProxyServer = class {
|
|
|
116
118
|
this.handleUpgrade(req, socket, head);
|
|
117
119
|
});
|
|
118
120
|
server.listen(port, () => {
|
|
121
|
+
process.env.TEST_PROXY_RECORDER_PORT = String(port);
|
|
119
122
|
this.logServerStartup(port);
|
|
120
123
|
});
|
|
121
124
|
return server;
|
|
@@ -124,14 +127,17 @@ var ProxyServer = class {
|
|
|
124
127
|
this.proxy.on("error", this.handleProxyError.bind(this));
|
|
125
128
|
this.proxy.on("proxyRes", this.handleProxyResponse.bind(this));
|
|
126
129
|
}
|
|
127
|
-
handleProxyError(err,
|
|
130
|
+
handleProxyError(err, req, res) {
|
|
128
131
|
console.error("Proxy error:", err);
|
|
129
132
|
if (!(res instanceof http.ServerResponse)) {
|
|
130
133
|
return;
|
|
131
134
|
}
|
|
132
135
|
if (!res.headersSent) {
|
|
136
|
+
const origin = req.headers.origin;
|
|
133
137
|
res.writeHead(HTTP_STATUS_BAD_GATEWAY, {
|
|
134
|
-
"Content-Type": "application/json"
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
"Access-Control-Allow-Origin": origin || "*",
|
|
140
|
+
"Access-Control-Allow-Credentials": "true"
|
|
135
141
|
});
|
|
136
142
|
}
|
|
137
143
|
res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
|
|
@@ -187,7 +193,7 @@ var ProxyServer = class {
|
|
|
187
193
|
async switchMode(mode, id) {
|
|
188
194
|
if (this.currentSession) {
|
|
189
195
|
console.log("Switching mode, saving current session first");
|
|
190
|
-
await this.saveCurrentSession();
|
|
196
|
+
await this.saveCurrentSession(true);
|
|
191
197
|
console.log("Session saved, continuing with mode switch");
|
|
192
198
|
}
|
|
193
199
|
switch (mode) {
|
|
@@ -242,13 +248,13 @@ var ProxyServer = class {
|
|
|
242
248
|
if (timeout && timeout > 0) {
|
|
243
249
|
this.modeTimeout = setTimeout(async () => {
|
|
244
250
|
console.log("Timeout reached, switching back to transparent mode");
|
|
245
|
-
await this.saveCurrentSession();
|
|
251
|
+
await this.saveCurrentSession(true);
|
|
246
252
|
this.switchToTransparentMode();
|
|
247
253
|
this.modeTimeout = null;
|
|
248
254
|
}, timeout);
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
|
-
async saveCurrentSession() {
|
|
257
|
+
async saveCurrentSession(filterIncomplete = false) {
|
|
252
258
|
if (!this.currentSession) {
|
|
253
259
|
console.log("No current session to save");
|
|
254
260
|
return;
|
|
@@ -257,13 +263,27 @@ var ProxyServer = class {
|
|
|
257
263
|
console.log("Session has no recordings, skipping save");
|
|
258
264
|
return;
|
|
259
265
|
}
|
|
266
|
+
if (filterIncomplete) {
|
|
267
|
+
const incompleteCount = this.currentSession.recordings.filter(
|
|
268
|
+
(r) => !r.response
|
|
269
|
+
).length;
|
|
270
|
+
if (incompleteCount > 0) {
|
|
271
|
+
console.log(
|
|
272
|
+
`Removing ${incompleteCount} incomplete recording(s) without responses`
|
|
273
|
+
);
|
|
274
|
+
this.currentSession.recordings = this.currentSession.recordings.filter(
|
|
275
|
+
(r) => r.response
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
260
279
|
console.log(
|
|
261
280
|
`Saving session with ${this.currentSession.recordings.length} HTTP and ${this.currentSession.websocketRecordings.length} WebSocket recordings`
|
|
262
281
|
);
|
|
263
282
|
await saveRecordingSession(this.recordingsDir, this.currentSession);
|
|
264
283
|
}
|
|
265
|
-
|
|
284
|
+
saveRequestRecordSync(req, body) {
|
|
266
285
|
if (!this.currentSession) {
|
|
286
|
+
console.log("saveRequestRecordSync: No current session");
|
|
267
287
|
return;
|
|
268
288
|
}
|
|
269
289
|
const key = getReqID(req);
|
|
@@ -281,6 +301,30 @@ var ProxyServer = class {
|
|
|
281
301
|
sequence: currentSequence
|
|
282
302
|
};
|
|
283
303
|
this.currentSession.recordings.push(record);
|
|
304
|
+
console.log(
|
|
305
|
+
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
306
|
+
`saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, seq: ${currentSequence}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
updateRequestBodySync(req, body) {
|
|
310
|
+
if (!this.currentSession) {
|
|
311
|
+
console.log("updateRequestBodySync: No current session");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const key = getReqID(req);
|
|
315
|
+
const record = this.currentSession.recordings.findLast(
|
|
316
|
+
(r) => r.key === key && !r.response
|
|
317
|
+
);
|
|
318
|
+
if (!record) {
|
|
319
|
+
console.error(
|
|
320
|
+
`updateRequestBodySync: Could not find request record for ${req.method} ${req.url}`
|
|
321
|
+
);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
record.request.body = body || null;
|
|
325
|
+
console.log(
|
|
326
|
+
`updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars)`
|
|
327
|
+
);
|
|
284
328
|
}
|
|
285
329
|
async recordResponse(req, proxyRes) {
|
|
286
330
|
if (!this.currentSession) {
|
|
@@ -309,49 +353,105 @@ var ProxyServer = class {
|
|
|
309
353
|
console.log(`Recorded: ${req.method} ${req.url}`);
|
|
310
354
|
});
|
|
311
355
|
}
|
|
356
|
+
async recordResponseData(req, proxyRes, body) {
|
|
357
|
+
if (!this.currentSession) {
|
|
358
|
+
console.log("recordResponseData: No current session");
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
const key = getReqID(req);
|
|
362
|
+
const record = this.currentSession.recordings.findLast(
|
|
363
|
+
(r) => r.key === key && !r.response
|
|
364
|
+
);
|
|
365
|
+
if (!record) {
|
|
366
|
+
const host = req.headers.host || "unknown";
|
|
367
|
+
const recordsWithKey = this.currentSession.recordings.filter(
|
|
368
|
+
(r) => r.key === key
|
|
369
|
+
);
|
|
370
|
+
console.error(
|
|
371
|
+
`Request record not found for response: ${key} at ${req.method} ${host}${req.url}`
|
|
372
|
+
);
|
|
373
|
+
console.error(
|
|
374
|
+
` Total recordings: ${this.currentSession.recordings.length}, with this key: ${recordsWithKey.length}`
|
|
375
|
+
);
|
|
376
|
+
console.error(
|
|
377
|
+
` Records with key:`,
|
|
378
|
+
recordsWithKey.map((r) => ({
|
|
379
|
+
seq: r.sequence,
|
|
380
|
+
hasResponse: !!r.response
|
|
381
|
+
}))
|
|
382
|
+
);
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
record.response = {
|
|
386
|
+
statusCode: proxyRes.statusCode,
|
|
387
|
+
headers: proxyRes.headers,
|
|
388
|
+
body: body || null
|
|
389
|
+
};
|
|
390
|
+
await this.saveCurrentSession();
|
|
391
|
+
console.log(
|
|
392
|
+
`recordResponseData: Recorded response for ${req.method} ${req.url}`
|
|
393
|
+
);
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
312
396
|
async handleReplayRequest(req, res) {
|
|
313
397
|
const key = getReqID(req);
|
|
314
398
|
const filePath = getRecordingPath(this.recordingsDir, this.replayId);
|
|
315
399
|
try {
|
|
316
400
|
const session = await loadRecordingSession(filePath);
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
(r) => r.key === key && r.
|
|
401
|
+
const host = req.headers.host || "unknown";
|
|
402
|
+
const recordsWithKey = session.recordings.filter(
|
|
403
|
+
(r) => r.key === key && r.response
|
|
320
404
|
);
|
|
321
|
-
if (
|
|
405
|
+
if (recordsWithKey.length === 0) {
|
|
322
406
|
throw new Error(
|
|
323
|
-
`No recording found for ${key}
|
|
407
|
+
`No recording found for ${key} at ${req.method} ${host}${req.url}`
|
|
324
408
|
);
|
|
325
409
|
}
|
|
410
|
+
const usageCount = this.replaySequenceMap.get(key) || 0;
|
|
411
|
+
const recordIndex = usageCount % recordsWithKey.length;
|
|
412
|
+
const record = recordsWithKey[recordIndex];
|
|
413
|
+
console.log(
|
|
414
|
+
`Replaying ${req.method} ${req.url} (usage: ${usageCount}, using recording ${recordIndex}/${recordsWithKey.length})`
|
|
415
|
+
);
|
|
416
|
+
this.replaySequenceMap.set(key, usageCount + 1);
|
|
326
417
|
if (!record.response) {
|
|
327
|
-
throw new Error(
|
|
418
|
+
throw new Error(
|
|
419
|
+
`No response recorded for this request: ${req.method} ${host}${req.url}`
|
|
420
|
+
);
|
|
328
421
|
}
|
|
329
|
-
this.replaySequenceMap.set(key, currentSequence + 1);
|
|
330
422
|
const { statusCode, headers, body } = record.response;
|
|
331
423
|
const origin = req.headers.origin;
|
|
332
424
|
const responseHeaders = {
|
|
333
425
|
...headers,
|
|
334
426
|
"access-control-allow-origin": origin || "*",
|
|
335
|
-
"access-control-allow-credentials": "true"
|
|
427
|
+
"access-control-allow-credentials": "true",
|
|
428
|
+
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
429
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
430
|
+
"access-control-expose-headers": "*"
|
|
336
431
|
};
|
|
337
432
|
res.writeHead(statusCode, responseHeaders);
|
|
338
433
|
res.end(body);
|
|
339
|
-
console.log(
|
|
340
|
-
`Replayed: ${req.method} ${req.url} (sequence: ${currentSequence})`
|
|
341
|
-
);
|
|
342
434
|
} catch (error) {
|
|
343
|
-
this.handleReplayError(res, error, key, filePath);
|
|
435
|
+
this.handleReplayError(req, res, error, key, filePath);
|
|
344
436
|
}
|
|
345
437
|
}
|
|
346
|
-
handleReplayError(res, err, key, filePath) {
|
|
438
|
+
handleReplayError(req, res, err, key, filePath) {
|
|
347
439
|
const isFileNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
348
440
|
console.error("Replay error:", err);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
441
|
+
const origin = req.headers.origin;
|
|
442
|
+
res.writeHead(HTTP_STATUS_NOT_FOUND, {
|
|
443
|
+
"Content-Type": "application/json",
|
|
444
|
+
"Access-Control-Allow-Origin": origin || "*",
|
|
445
|
+
"Access-Control-Allow-Credentials": "true"
|
|
354
446
|
});
|
|
447
|
+
res.end(
|
|
448
|
+
JSON.stringify({
|
|
449
|
+
error: isFileNotFound ? "Recording file not found" : "Recording not found",
|
|
450
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
451
|
+
key,
|
|
452
|
+
filePath
|
|
453
|
+
})
|
|
454
|
+
);
|
|
355
455
|
}
|
|
356
456
|
async handleRequest(req, res) {
|
|
357
457
|
if (req.method === "OPTIONS") {
|
|
@@ -381,6 +481,7 @@ var ProxyServer = class {
|
|
|
381
481
|
const target = this.getTarget();
|
|
382
482
|
console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
|
|
383
483
|
if (this.mode === Modes.record) {
|
|
484
|
+
this.saveRequestRecordSync(req, null);
|
|
384
485
|
await this.bufferAndProxyRequest(req, res, target);
|
|
385
486
|
} else {
|
|
386
487
|
this.proxy.web(req, res, { target });
|
|
@@ -391,11 +492,20 @@ var ProxyServer = class {
|
|
|
391
492
|
req.on("data", (chunk) => {
|
|
392
493
|
chunks.push(chunk);
|
|
393
494
|
});
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
495
|
+
try {
|
|
496
|
+
await new Promise((resolve, reject) => {
|
|
497
|
+
req.on("end", () => resolve());
|
|
498
|
+
req.on("error", (err) => reject(err));
|
|
499
|
+
setTimeout(
|
|
500
|
+
() => reject(new Error("Request buffering timeout")),
|
|
501
|
+
3e4
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.error("Error buffering request:", error);
|
|
506
|
+
}
|
|
397
507
|
const body = Buffer.concat(chunks).toString("utf8");
|
|
398
|
-
|
|
508
|
+
this.updateRequestBodySync(req, body);
|
|
399
509
|
const targetUrl = new URL(target);
|
|
400
510
|
const isHttps = targetUrl.protocol === "https:";
|
|
401
511
|
const requestModule = isHttps ? https : http;
|
|
@@ -410,9 +520,38 @@ var ProxyServer = class {
|
|
|
410
520
|
},
|
|
411
521
|
(proxyRes) => {
|
|
412
522
|
this.addCorsHeaders(proxyRes, req);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
523
|
+
const responseChunks = [];
|
|
524
|
+
proxyRes.on("data", (chunk) => {
|
|
525
|
+
responseChunks.push(chunk);
|
|
526
|
+
});
|
|
527
|
+
proxyRes.on("end", async () => {
|
|
528
|
+
const responseBody = Buffer.concat(responseChunks);
|
|
529
|
+
const recorded = await this.recordResponseData(
|
|
530
|
+
req,
|
|
531
|
+
proxyRes,
|
|
532
|
+
responseBody.toString("utf8")
|
|
533
|
+
);
|
|
534
|
+
const origin = req.headers.origin;
|
|
535
|
+
const responseHeaders = {
|
|
536
|
+
...proxyRes.headers,
|
|
537
|
+
"access-control-allow-origin": origin || "*",
|
|
538
|
+
"access-control-allow-credentials": "true",
|
|
539
|
+
"access-control-allow-headers": req.headers["access-control-request-headers"] || "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
540
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
|
|
541
|
+
"access-control-expose-headers": "*"
|
|
542
|
+
};
|
|
543
|
+
res.writeHead(proxyRes.statusCode || 200, responseHeaders);
|
|
544
|
+
res.end(responseBody);
|
|
545
|
+
if (recorded) {
|
|
546
|
+
console.log(`Recorded: ${req.method} ${req.url}`);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
proxyRes.on("error", (err) => {
|
|
550
|
+
console.error("Proxy response error:", err);
|
|
551
|
+
if (!res.headersSent) {
|
|
552
|
+
this.handleProxyError(err, req, res);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
416
555
|
}
|
|
417
556
|
);
|
|
418
557
|
proxyReq.on("error", (err) => {
|
|
@@ -594,15 +733,25 @@ var ProxyServer = class {
|
|
|
594
733
|
};
|
|
595
734
|
|
|
596
735
|
// src/playwright/index.ts
|
|
597
|
-
|
|
736
|
+
function getProxyPort() {
|
|
737
|
+
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
738
|
+
if (envPort) {
|
|
739
|
+
const parsed = Number.parseInt(envPort, 10);
|
|
740
|
+
if (!Number.isNaN(parsed)) {
|
|
741
|
+
return parsed;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return 8100;
|
|
745
|
+
}
|
|
598
746
|
async function setProxyMode(mode, sessionId, timeout) {
|
|
747
|
+
const proxyPort = getProxyPort();
|
|
599
748
|
try {
|
|
600
749
|
const body = {
|
|
601
750
|
mode,
|
|
602
751
|
id: sessionId,
|
|
603
752
|
...timeout && { timeout }
|
|
604
753
|
};
|
|
605
|
-
const response = await fetch(
|
|
754
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
606
755
|
method: "POST",
|
|
607
756
|
headers: { "Content-Type": "application/json" },
|
|
608
757
|
body: JSON.stringify(body)
|
|
@@ -619,8 +768,34 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
619
768
|
throw error;
|
|
620
769
|
}
|
|
621
770
|
}
|
|
771
|
+
function parseSpecFilePath(specPath) {
|
|
772
|
+
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
773
|
+
if (folderMatch) {
|
|
774
|
+
return { folder: folderMatch[1], fileName: folderMatch[2] };
|
|
775
|
+
}
|
|
776
|
+
const fileMatch = specPath.match(/^([^/]+)\.(spec|test)\.ts$/);
|
|
777
|
+
if (fileMatch) {
|
|
778
|
+
return { folder: null, fileName: fileMatch[1] };
|
|
779
|
+
}
|
|
780
|
+
return { folder: null, fileName: null };
|
|
781
|
+
}
|
|
782
|
+
function buildSessionPath(folder, fileName, testName) {
|
|
783
|
+
if (folder && fileName) {
|
|
784
|
+
return `${folder}/${fileName}__${testName}`;
|
|
785
|
+
}
|
|
786
|
+
if (fileName) {
|
|
787
|
+
return `${fileName}__${testName}`;
|
|
788
|
+
}
|
|
789
|
+
return testName;
|
|
790
|
+
}
|
|
622
791
|
function generateSessionId(testInfo) {
|
|
623
|
-
|
|
792
|
+
const { titlePath } = testInfo;
|
|
793
|
+
if (!titlePath || titlePath.length === 0) {
|
|
794
|
+
return testInfo.title.toLowerCase().replaceAll(/\s+/g, "-");
|
|
795
|
+
}
|
|
796
|
+
const { folder, fileName } = parseSpecFilePath(titlePath[0]);
|
|
797
|
+
const testName = titlePath.at(-1).toLowerCase().replaceAll(/\s+/g, "-");
|
|
798
|
+
return buildSessionPath(folder, fileName, testName);
|
|
624
799
|
}
|
|
625
800
|
async function startRecording(testInfo) {
|
|
626
801
|
const sessionId = generateSessionId(testInfo);
|
|
@@ -8,15 +8,25 @@ var Modes = {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
// src/playwright/index.ts
|
|
11
|
-
|
|
11
|
+
function getProxyPort() {
|
|
12
|
+
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
13
|
+
if (envPort) {
|
|
14
|
+
const parsed = Number.parseInt(envPort, 10);
|
|
15
|
+
if (!Number.isNaN(parsed)) {
|
|
16
|
+
return parsed;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return 8100;
|
|
20
|
+
}
|
|
12
21
|
async function setProxyMode(mode, sessionId, timeout) {
|
|
22
|
+
const proxyPort = getProxyPort();
|
|
13
23
|
try {
|
|
14
24
|
const body = {
|
|
15
25
|
mode,
|
|
16
26
|
id: sessionId,
|
|
17
27
|
...timeout && { timeout }
|
|
18
28
|
};
|
|
19
|
-
const response = await fetch(
|
|
29
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
20
30
|
method: "POST",
|
|
21
31
|
headers: { "Content-Type": "application/json" },
|
|
22
32
|
body: JSON.stringify(body)
|
|
@@ -33,8 +43,34 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
33
43
|
throw error;
|
|
34
44
|
}
|
|
35
45
|
}
|
|
46
|
+
function parseSpecFilePath(specPath) {
|
|
47
|
+
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
48
|
+
if (folderMatch) {
|
|
49
|
+
return { folder: folderMatch[1], fileName: folderMatch[2] };
|
|
50
|
+
}
|
|
51
|
+
const fileMatch = specPath.match(/^([^/]+)\.(spec|test)\.ts$/);
|
|
52
|
+
if (fileMatch) {
|
|
53
|
+
return { folder: null, fileName: fileMatch[1] };
|
|
54
|
+
}
|
|
55
|
+
return { folder: null, fileName: null };
|
|
56
|
+
}
|
|
57
|
+
function buildSessionPath(folder, fileName, testName) {
|
|
58
|
+
if (folder && fileName) {
|
|
59
|
+
return `${folder}/${fileName}__${testName}`;
|
|
60
|
+
}
|
|
61
|
+
if (fileName) {
|
|
62
|
+
return `${fileName}__${testName}`;
|
|
63
|
+
}
|
|
64
|
+
return testName;
|
|
65
|
+
}
|
|
36
66
|
function generateSessionId(testInfo) {
|
|
37
|
-
|
|
67
|
+
const { titlePath } = testInfo;
|
|
68
|
+
if (!titlePath || titlePath.length === 0) {
|
|
69
|
+
return testInfo.title.toLowerCase().replaceAll(/\s+/g, "-");
|
|
70
|
+
}
|
|
71
|
+
const { folder, fileName } = parseSpecFilePath(titlePath[0]);
|
|
72
|
+
const testName = titlePath.at(-1).toLowerCase().replaceAll(/\s+/g, "-");
|
|
73
|
+
return buildSessionPath(folder, fileName, testName);
|
|
38
74
|
}
|
|
39
75
|
async function startRecording(testInfo) {
|
|
40
76
|
const sessionId = generateSessionId(testInfo);
|
|
@@ -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 { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-CBjvm5rb.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 { P as PlaywrightTestInfo, g as generateSessionId, p as playwrightProxy, s as setProxyMode, b as startRecording, c as startReplay, d as stopProxy } from '../index-CBjvm5rb.js';
|
|
3
3
|
import 'node:http';
|
|
@@ -6,15 +6,25 @@ var Modes = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/playwright/index.ts
|
|
9
|
-
|
|
9
|
+
function getProxyPort() {
|
|
10
|
+
const envPort = process.env.TEST_PROXY_RECORDER_PORT;
|
|
11
|
+
if (envPort) {
|
|
12
|
+
const parsed = Number.parseInt(envPort, 10);
|
|
13
|
+
if (!Number.isNaN(parsed)) {
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return 8100;
|
|
18
|
+
}
|
|
10
19
|
async function setProxyMode(mode, sessionId, timeout) {
|
|
20
|
+
const proxyPort = getProxyPort();
|
|
11
21
|
try {
|
|
12
22
|
const body = {
|
|
13
23
|
mode,
|
|
14
24
|
id: sessionId,
|
|
15
25
|
...timeout && { timeout }
|
|
16
26
|
};
|
|
17
|
-
const response = await fetch(
|
|
27
|
+
const response = await fetch(`http://127.0.0.1:${proxyPort}/__control`, {
|
|
18
28
|
method: "POST",
|
|
19
29
|
headers: { "Content-Type": "application/json" },
|
|
20
30
|
body: JSON.stringify(body)
|
|
@@ -31,8 +41,34 @@ async function setProxyMode(mode, sessionId, timeout) {
|
|
|
31
41
|
throw error;
|
|
32
42
|
}
|
|
33
43
|
}
|
|
44
|
+
function parseSpecFilePath(specPath) {
|
|
45
|
+
const folderMatch = specPath.match(/^(.+?)\/([^/]+)\.(spec|test)\.ts$/);
|
|
46
|
+
if (folderMatch) {
|
|
47
|
+
return { folder: folderMatch[1], fileName: folderMatch[2] };
|
|
48
|
+
}
|
|
49
|
+
const fileMatch = specPath.match(/^([^/]+)\.(spec|test)\.ts$/);
|
|
50
|
+
if (fileMatch) {
|
|
51
|
+
return { folder: null, fileName: fileMatch[1] };
|
|
52
|
+
}
|
|
53
|
+
return { folder: null, fileName: null };
|
|
54
|
+
}
|
|
55
|
+
function buildSessionPath(folder, fileName, testName) {
|
|
56
|
+
if (folder && fileName) {
|
|
57
|
+
return `${folder}/${fileName}__${testName}`;
|
|
58
|
+
}
|
|
59
|
+
if (fileName) {
|
|
60
|
+
return `${fileName}__${testName}`;
|
|
61
|
+
}
|
|
62
|
+
return testName;
|
|
63
|
+
}
|
|
34
64
|
function generateSessionId(testInfo) {
|
|
35
|
-
|
|
65
|
+
const { titlePath } = testInfo;
|
|
66
|
+
if (!titlePath || titlePath.length === 0) {
|
|
67
|
+
return testInfo.title.toLowerCase().replaceAll(/\s+/g, "-");
|
|
68
|
+
}
|
|
69
|
+
const { folder, fileName } = parseSpecFilePath(titlePath[0]);
|
|
70
|
+
const testName = titlePath.at(-1).toLowerCase().replaceAll(/\s+/g, "-");
|
|
71
|
+
return buildSessionPath(folder, fileName, testName);
|
|
36
72
|
}
|
|
37
73
|
async function startRecording(testInfo) {
|
|
38
74
|
const sessionId = generateSessionId(testInfo);
|