@testrelic/playwright-analytics 2.3.2 → 2.3.4
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/api-fixture.cjs +2 -1
- package/dist/api-fixture.cjs.map +1 -1
- package/dist/api-fixture.js +2 -1
- package/dist/api-fixture.js.map +1 -1
- package/dist/cli.cjs +134 -42
- package/dist/fixture.cjs +2 -1
- package/dist/fixture.cjs.map +1 -1
- package/dist/fixture.js +2 -1
- package/dist/fixture.js.map +1 -1
- package/dist/index.cjs +158 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +158 -88
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -10,6 +10,45 @@ var __export = (target, all) => {
|
|
|
10
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// src/jsonl-stream.ts
|
|
14
|
+
async function readJsonlPage(filePath, page, pageSize, knownTotal) {
|
|
15
|
+
const skip = (page - 1) * pageSize;
|
|
16
|
+
const items = [];
|
|
17
|
+
let lineCount = 0;
|
|
18
|
+
const rl = (0, import_node_readline.createInterface)({
|
|
19
|
+
input: (0, import_node_fs2.createReadStream)(filePath, { encoding: "utf-8" }),
|
|
20
|
+
crlfDelay: Infinity
|
|
21
|
+
});
|
|
22
|
+
for await (const line of rl) {
|
|
23
|
+
if (line.length === 0) continue;
|
|
24
|
+
if (lineCount >= skip && items.length < pageSize) {
|
|
25
|
+
try {
|
|
26
|
+
items.push(JSON.parse(line));
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
lineCount++;
|
|
31
|
+
if (items.length >= pageSize && knownTotal !== void 0) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const total = knownTotal ?? lineCount;
|
|
36
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
37
|
+
return { items, total, page, pageSize, totalPages };
|
|
38
|
+
}
|
|
39
|
+
var import_node_fs2, import_node_path2, import_node_os, import_node_crypto2, import_node_readline, TESTRELIC_TMP_DIR;
|
|
40
|
+
var init_jsonl_stream = __esm({
|
|
41
|
+
"src/jsonl-stream.ts"() {
|
|
42
|
+
"use strict";
|
|
43
|
+
import_node_fs2 = require("fs");
|
|
44
|
+
import_node_path2 = require("path");
|
|
45
|
+
import_node_os = require("os");
|
|
46
|
+
import_node_crypto2 = require("crypto");
|
|
47
|
+
import_node_readline = require("readline");
|
|
48
|
+
TESTRELIC_TMP_DIR = (0, import_node_path2.join)((0, import_node_os.tmpdir)(), "testrelic-data");
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
13
52
|
// src/report-server-routes.ts
|
|
14
53
|
function sendJson(res, status, body) {
|
|
15
54
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
@@ -22,8 +61,8 @@ function setCorsHeaders(res) {
|
|
|
22
61
|
}
|
|
23
62
|
function readJsonFile(filePath) {
|
|
24
63
|
try {
|
|
25
|
-
if (!(0,
|
|
26
|
-
return JSON.parse((0,
|
|
64
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return null;
|
|
65
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(filePath, "utf-8"));
|
|
27
66
|
} catch {
|
|
28
67
|
return null;
|
|
29
68
|
}
|
|
@@ -31,11 +70,11 @@ function readJsonFile(filePath) {
|
|
|
31
70
|
function getDirSize(dirPath) {
|
|
32
71
|
let size = 0;
|
|
33
72
|
try {
|
|
34
|
-
const entries = (0,
|
|
73
|
+
const entries = (0, import_node_fs3.readdirSync)(dirPath, { withFileTypes: true });
|
|
35
74
|
for (const entry of entries) {
|
|
36
|
-
const fullPath = (0,
|
|
75
|
+
const fullPath = (0, import_node_path3.join)(dirPath, entry.name);
|
|
37
76
|
if (entry.isFile()) {
|
|
38
|
-
size += (0,
|
|
77
|
+
size += (0, import_node_fs3.statSync)(fullPath).size;
|
|
39
78
|
} else if (entry.isDirectory()) {
|
|
40
79
|
size += getDirSize(fullPath);
|
|
41
80
|
}
|
|
@@ -45,7 +84,7 @@ function getDirSize(dirPath) {
|
|
|
45
84
|
return size;
|
|
46
85
|
}
|
|
47
86
|
function handleHealth(_req, res, reportDir, startTime) {
|
|
48
|
-
const index = readJsonFile((0,
|
|
87
|
+
const index = readJsonFile((0, import_node_path3.join)(reportDir, "index.json"));
|
|
49
88
|
sendJson(res, 200, {
|
|
50
89
|
status: "ok",
|
|
51
90
|
reportMode: "streaming",
|
|
@@ -54,7 +93,7 @@ function handleHealth(_req, res, reportDir, startTime) {
|
|
|
54
93
|
});
|
|
55
94
|
}
|
|
56
95
|
function handleSummary(_req, res, reportDir) {
|
|
57
|
-
const summary = readJsonFile((0,
|
|
96
|
+
const summary = readJsonFile((0, import_node_path3.join)(reportDir, "summary.json"));
|
|
58
97
|
if (!summary) {
|
|
59
98
|
sendJson(res, 404, { error: "Summary not found" });
|
|
60
99
|
return;
|
|
@@ -62,7 +101,7 @@ function handleSummary(_req, res, reportDir) {
|
|
|
62
101
|
sendJson(res, 200, summary);
|
|
63
102
|
}
|
|
64
103
|
function handleTests(req, res, reportDir) {
|
|
65
|
-
const index = readJsonFile((0,
|
|
104
|
+
const index = readJsonFile((0, import_node_path3.join)(reportDir, "index.json"));
|
|
66
105
|
if (!index) {
|
|
67
106
|
sendJson(res, 404, { error: "Test index not found" });
|
|
68
107
|
return;
|
|
@@ -131,21 +170,68 @@ function handleTestDetail(_req, res, reportDir, testId) {
|
|
|
131
170
|
sendJson(res, 400, { error: "Invalid test ID format" });
|
|
132
171
|
return;
|
|
133
172
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
173
|
+
const metaPath = (0, import_node_path3.join)(reportDir, "tests", testId, "meta.json");
|
|
174
|
+
const legacyPath = (0, import_node_path3.join)(reportDir, "tests", `${testId}.json`);
|
|
175
|
+
const filePath = (0, import_node_fs3.existsSync)(metaPath) ? metaPath : (0, import_node_fs3.existsSync)(legacyPath) ? legacyPath : null;
|
|
176
|
+
if (!filePath) {
|
|
136
177
|
sendJson(res, 404, { error: `Test not found: ${testId}` });
|
|
137
178
|
return;
|
|
138
179
|
}
|
|
139
180
|
try {
|
|
140
|
-
const data = (0,
|
|
181
|
+
const data = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
|
|
141
182
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
142
183
|
res.end(data);
|
|
143
184
|
} catch (err) {
|
|
144
185
|
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
145
186
|
}
|
|
146
187
|
}
|
|
188
|
+
async function handleTestDataFile(req, res, reportDir, testId, dataType) {
|
|
189
|
+
if (!SAFE_ID_PATTERN.test(testId)) {
|
|
190
|
+
sendJson(res, 400, { error: "Invalid test ID format" });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const fileNames = {
|
|
194
|
+
"network": "network.jsonl",
|
|
195
|
+
"console": "console.jsonl",
|
|
196
|
+
"api-calls": "api-calls.jsonl"
|
|
197
|
+
};
|
|
198
|
+
const jsonlPath = (0, import_node_path3.join)(reportDir, "tests", testId, fileNames[dataType]);
|
|
199
|
+
if (!(0, import_node_fs3.existsSync)(jsonlPath)) {
|
|
200
|
+
sendJson(res, 200, { items: [], total: 0, page: 1, pageSize: 50, totalPages: 0 });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
205
|
+
const params = url.searchParams;
|
|
206
|
+
const page = Math.max(1, parseInt(params.get("page") ?? "1", 10) || 1);
|
|
207
|
+
const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, parseInt(params.get("pageSize") ?? "50", 10) || 50));
|
|
208
|
+
let knownTotal;
|
|
209
|
+
const metaPath = (0, import_node_path3.join)(reportDir, "tests", testId, "meta.json");
|
|
210
|
+
if ((0, import_node_fs3.existsSync)(metaPath)) {
|
|
211
|
+
try {
|
|
212
|
+
const meta = JSON.parse((0, import_node_fs3.readFileSync)(metaPath, "utf-8"));
|
|
213
|
+
switch (dataType) {
|
|
214
|
+
case "network":
|
|
215
|
+
knownTotal = meta.networkRequestsCount;
|
|
216
|
+
break;
|
|
217
|
+
case "console":
|
|
218
|
+
knownTotal = meta.consoleLogsCount;
|
|
219
|
+
break;
|
|
220
|
+
case "api-calls":
|
|
221
|
+
knownTotal = meta.apiCallsCount;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const result = await readJsonlPage(jsonlPath, page, pageSize, knownTotal);
|
|
228
|
+
sendJson(res, 200, result);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
147
233
|
function handleFiles(_req, res, reportDir) {
|
|
148
|
-
const index = readJsonFile((0,
|
|
234
|
+
const index = readJsonFile((0, import_node_path3.join)(reportDir, "index.json"));
|
|
149
235
|
if (!index) {
|
|
150
236
|
sendJson(res, 404, { error: "Test index not found" });
|
|
151
237
|
return;
|
|
@@ -181,19 +267,19 @@ function handleFiles(_req, res, reportDir) {
|
|
|
181
267
|
sendJson(res, 200, { files });
|
|
182
268
|
}
|
|
183
269
|
function handleArtifactsList(_req, res, artifactsDir) {
|
|
184
|
-
if (!(0,
|
|
270
|
+
if (!(0, import_node_fs3.existsSync)(artifactsDir)) {
|
|
185
271
|
sendJson(res, 200, { runs: [], totalSizeBytes: 0 });
|
|
186
272
|
return;
|
|
187
273
|
}
|
|
188
274
|
try {
|
|
189
275
|
const runs = [];
|
|
190
276
|
let totalSizeBytes = 0;
|
|
191
|
-
const entries = (0,
|
|
277
|
+
const entries = (0, import_node_fs3.readdirSync)(artifactsDir, { withFileTypes: true });
|
|
192
278
|
for (const entry of entries) {
|
|
193
279
|
if (!entry.isDirectory() || !TIMESTAMP_PATTERN.test(entry.name)) continue;
|
|
194
|
-
const runDir = (0,
|
|
280
|
+
const runDir = (0, import_node_path3.join)(artifactsDir, entry.name);
|
|
195
281
|
const size = getDirSize(runDir);
|
|
196
|
-
const testDirs = (0,
|
|
282
|
+
const testDirs = (0, import_node_fs3.readdirSync)(runDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
197
283
|
runs.push({ folderName: entry.name, totalSizeBytes: size, testCount: testDirs.length });
|
|
198
284
|
totalSizeBytes += size;
|
|
199
285
|
}
|
|
@@ -207,13 +293,13 @@ function handleArtifactsDeleteAll(_req, res, artifactsDir) {
|
|
|
207
293
|
try {
|
|
208
294
|
let deletedCount = 0;
|
|
209
295
|
let freedBytes = 0;
|
|
210
|
-
if ((0,
|
|
211
|
-
const entries = (0,
|
|
296
|
+
if ((0, import_node_fs3.existsSync)(artifactsDir)) {
|
|
297
|
+
const entries = (0, import_node_fs3.readdirSync)(artifactsDir, { withFileTypes: true });
|
|
212
298
|
for (const entry of entries) {
|
|
213
299
|
if (!entry.isDirectory() || !TIMESTAMP_PATTERN.test(entry.name)) continue;
|
|
214
|
-
const runDir = (0,
|
|
300
|
+
const runDir = (0, import_node_path3.join)(artifactsDir, entry.name);
|
|
215
301
|
const size = getDirSize(runDir);
|
|
216
|
-
(0,
|
|
302
|
+
(0, import_node_fs3.rmSync)(runDir, { recursive: true, force: true });
|
|
217
303
|
freedBytes += size;
|
|
218
304
|
deletedCount++;
|
|
219
305
|
}
|
|
@@ -228,9 +314,9 @@ function handleArtifactDelete(_req, res, artifactsDir, folderName) {
|
|
|
228
314
|
sendJson(res, 400, { error: "Invalid folder name" });
|
|
229
315
|
return;
|
|
230
316
|
}
|
|
231
|
-
const runDir = (0,
|
|
317
|
+
const runDir = (0, import_node_path3.join)(artifactsDir, folderName);
|
|
232
318
|
try {
|
|
233
|
-
const stat = (0,
|
|
319
|
+
const stat = (0, import_node_fs3.statSync)(runDir);
|
|
234
320
|
if (!stat.isDirectory()) {
|
|
235
321
|
sendJson(res, 404, { error: "Not found" });
|
|
236
322
|
return;
|
|
@@ -241,7 +327,7 @@ function handleArtifactDelete(_req, res, artifactsDir, folderName) {
|
|
|
241
327
|
}
|
|
242
328
|
try {
|
|
243
329
|
const freedBytes = getDirSize(runDir);
|
|
244
|
-
(0,
|
|
330
|
+
(0, import_node_fs3.rmSync)(runDir, { recursive: true, force: true });
|
|
245
331
|
sendJson(res, 200, { deleted: folderName, freedBytes });
|
|
246
332
|
} catch (err) {
|
|
247
333
|
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -252,13 +338,13 @@ function handleShutdown(_req, res, server) {
|
|
|
252
338
|
server.close();
|
|
253
339
|
}
|
|
254
340
|
function serveStaticFile(_req, res, filePath) {
|
|
255
|
-
if (!(0,
|
|
341
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) {
|
|
256
342
|
sendJson(res, 404, { error: "File not found" });
|
|
257
343
|
return;
|
|
258
344
|
}
|
|
259
345
|
try {
|
|
260
|
-
const data = (0,
|
|
261
|
-
const ext = (0,
|
|
346
|
+
const data = (0, import_node_fs3.readFileSync)(filePath);
|
|
347
|
+
const ext = (0, import_node_path3.extname)(filePath).toLowerCase();
|
|
262
348
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
263
349
|
res.writeHead(200, { "Content-Type": contentType });
|
|
264
350
|
res.end(data);
|
|
@@ -266,12 +352,13 @@ function serveStaticFile(_req, res, filePath) {
|
|
|
266
352
|
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
267
353
|
}
|
|
268
354
|
}
|
|
269
|
-
var
|
|
355
|
+
var import_node_fs3, import_node_path3, SAFE_ID_PATTERN, TIMESTAMP_PATTERN, MAX_PAGE_SIZE, MIME_TYPES;
|
|
270
356
|
var init_report_server_routes = __esm({
|
|
271
357
|
"src/report-server-routes.ts"() {
|
|
272
358
|
"use strict";
|
|
273
|
-
|
|
274
|
-
|
|
359
|
+
import_node_fs3 = require("fs");
|
|
360
|
+
import_node_path3 = require("path");
|
|
361
|
+
init_jsonl_stream();
|
|
275
362
|
SAFE_ID_PATTERN = /^[a-f0-9]{12}$/;
|
|
276
363
|
TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/;
|
|
277
364
|
MAX_PAGE_SIZE = 500;
|
|
@@ -303,7 +390,7 @@ function startReportServer(reportDir, options) {
|
|
|
303
390
|
const startTime = Date.now();
|
|
304
391
|
let inactivityTimer;
|
|
305
392
|
let currentAttempt = 0;
|
|
306
|
-
const artifactsDir = (0,
|
|
393
|
+
const artifactsDir = (0, import_node_fs4.existsSync)((0, import_node_path4.join)(reportDir, "artifacts")) ? (0, import_node_path4.join)(reportDir, "artifacts") : (0, import_node_fs4.existsSync)((0, import_node_path4.join)(reportDir, "..", "artifacts")) ? (0, import_node_path4.join)(reportDir, "..", "artifacts") : (0, import_node_path4.join)(reportDir, "artifacts");
|
|
307
394
|
function resetTimer() {
|
|
308
395
|
clearTimeout(inactivityTimer);
|
|
309
396
|
inactivityTimer = setTimeout(() => {
|
|
@@ -312,10 +399,10 @@ function startReportServer(reportDir, options) {
|
|
|
312
399
|
}
|
|
313
400
|
let htmlReportPath = options?.htmlPath ?? null;
|
|
314
401
|
if (!htmlReportPath) {
|
|
315
|
-
const parentDir = (0,
|
|
402
|
+
const parentDir = (0, import_node_path4.dirname)(reportDir);
|
|
316
403
|
try {
|
|
317
|
-
const htmlFile = (0,
|
|
318
|
-
if (htmlFile) htmlReportPath = (0,
|
|
404
|
+
const htmlFile = (0, import_node_fs4.readdirSync)(parentDir).find((f) => f.endsWith(".html"));
|
|
405
|
+
if (htmlFile) htmlReportPath = (0, import_node_path4.join)(parentDir, htmlFile);
|
|
319
406
|
} catch {
|
|
320
407
|
}
|
|
321
408
|
}
|
|
@@ -336,7 +423,7 @@ function startReportServer(reportDir, options) {
|
|
|
336
423
|
return;
|
|
337
424
|
}
|
|
338
425
|
if (req.method === "GET" && (pathname === "/" || pathname === "/index.html")) {
|
|
339
|
-
if (htmlReportPath && (0,
|
|
426
|
+
if (htmlReportPath && (0, import_node_fs4.existsSync)(htmlReportPath)) {
|
|
340
427
|
serveStaticFile(req, res, htmlReportPath);
|
|
341
428
|
return;
|
|
342
429
|
}
|
|
@@ -359,6 +446,11 @@ function startReportServer(reportDir, options) {
|
|
|
359
446
|
handleFiles(req, res, reportDir);
|
|
360
447
|
return;
|
|
361
448
|
}
|
|
449
|
+
const testDataMatch = pathname.match(/^\/api\/tests\/([a-f0-9]+)\/(network|console|api-calls)$/);
|
|
450
|
+
if (req.method === "GET" && testDataMatch) {
|
|
451
|
+
handleTestDataFile(req, res, reportDir, testDataMatch[1], testDataMatch[2]);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
362
454
|
const testMatch = pathname.match(/^\/api\/tests\/([a-f0-9]+)$/);
|
|
363
455
|
if (req.method === "GET" && testMatch) {
|
|
364
456
|
handleTestDetail(req, res, reportDir, testMatch[1]);
|
|
@@ -383,7 +475,7 @@ function startReportServer(reportDir, options) {
|
|
|
383
475
|
sendJson(res, 400, { error: "Invalid path" });
|
|
384
476
|
return;
|
|
385
477
|
}
|
|
386
|
-
serveStaticFile(req, res, (0,
|
|
478
|
+
serveStaticFile(req, res, (0, import_node_path4.join)(artifactsDir, relPath));
|
|
387
479
|
return;
|
|
388
480
|
}
|
|
389
481
|
if (req.method === "POST" && pathname === "/api/shutdown") {
|
|
@@ -422,13 +514,13 @@ function startReportServer(reportDir, options) {
|
|
|
422
514
|
tryListen(startPort);
|
|
423
515
|
});
|
|
424
516
|
}
|
|
425
|
-
var import_node_http,
|
|
517
|
+
var import_node_http, import_node_path4, import_node_fs4, DEFAULT_PORT, MAX_PORT_ATTEMPTS, INACTIVITY_TIMEOUT_MS;
|
|
426
518
|
var init_report_server = __esm({
|
|
427
519
|
"src/report-server.ts"() {
|
|
428
520
|
"use strict";
|
|
429
521
|
import_node_http = require("http");
|
|
430
|
-
|
|
431
|
-
|
|
522
|
+
import_node_path4 = require("path");
|
|
523
|
+
import_node_fs4 = require("fs");
|
|
432
524
|
init_report_server_routes();
|
|
433
525
|
DEFAULT_PORT = 9323;
|
|
434
526
|
MAX_PORT_ATTEMPTS = 10;
|
|
@@ -653,8 +745,8 @@ function recalculateSummaryFromList(summaries, timelineLength) {
|
|
|
653
745
|
}
|
|
654
746
|
|
|
655
747
|
// src/cli.ts
|
|
656
|
-
var
|
|
657
|
-
var
|
|
748
|
+
var import_node_path5 = require("path");
|
|
749
|
+
var import_node_fs5 = require("fs");
|
|
658
750
|
async function main() {
|
|
659
751
|
const args = process.argv.slice(2);
|
|
660
752
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
@@ -724,8 +816,8 @@ async function handleServe(serveArgs) {
|
|
|
724
816
|
process.stderr.write("Usage: testrelic serve <dir> [--port <port>]\n");
|
|
725
817
|
process.exit(1);
|
|
726
818
|
}
|
|
727
|
-
const reportDir = (0,
|
|
728
|
-
if (!(0,
|
|
819
|
+
const reportDir = (0, import_node_path5.resolve)(dir);
|
|
820
|
+
if (!(0, import_node_fs5.existsSync)(reportDir)) {
|
|
729
821
|
process.stderr.write(`Error: Directory not found: ${reportDir}
|
|
730
822
|
`);
|
|
731
823
|
process.exit(1);
|
package/dist/fixture.cjs
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
'use strict';var test=require('@playwright/test'),perf_hooks=require('perf_hooks');require('fs');var core=require('@testrelic/core');var M=["text/","application/json","application/xml","application/javascript","application/x-www-form-urlencoded","application/graphql"];function j(r){let t=r.toLowerCase();return M.some(e=>t.includes(e))}var T=class r{constructor(t,e){this.page=t;this.records=[];this.listeners=[];this.currentNetworkCounter=null;this.pendingRequests=new Map;this.capturedRequests=[];this.pendingBodyReads=[];this.requestIdCounter=0;this.consoleLogs=[];this.includeNetworkStats=e?.includeNetworkStats??true,this.origGoto=t.goto.bind(t),this.origGoBack=t.goBack.bind(t),this.origGoForward=t.goForward.bind(t),this.origReload=t.reload.bind(t),this.interceptMethods(),this.attachListeners();}async init(){await this.injectSPADetection();}async getCapturedRequests(){await Promise.allSettled(this.pendingBodyReads),this.pendingBodyReads=[];for(let[t,e]of this.pendingRequests)this.capturedRequests.push({url:e.url,method:e.method,resourceType:e.resourceType,statusCode:0,responseTimeMs:Date.now()-e.startTimeMs,startedAt:e.startedAt,requestHeaders:e.headers,requestBody:e.postData,responseBody:null,responseHeaders:null,contentType:null,responseSize:0,requestBodyTruncated:e.postDataTruncated,responseBodyTruncated:false,isBinary:false,error:"incomplete"}),this.pendingRequests.delete(t);return [...this.capturedRequests].sort((t,e)=>t.startedAt.localeCompare(e.startedAt))}static mapConsoleType(t){switch(t){case "log":return "log";case "warning":return "warn";case "error":return "error";case "info":return "info";case "debug":return "debug";default:return "log"}}async getData(){this.includeNetworkStats&&this.currentNetworkCounter&&this.records.length>0&&(this.records[this.records.length-1].networkStats={totalRequests:this.currentNetworkCounter.totalRequests,failedRequests:this.currentNetworkCounter.failedRequests,failedRequestUrls:[...this.currentNetworkCounter.failedRequestUrls],totalBytes:this.currentNetworkCounter.totalBytes,byType:{...this.currentNetworkCounter.byType}});let t=this.records.map(n=>({url:n.url,navigationType:n.navigationType,timestamp:n.timestamp,domContentLoadedAt:n.domContentLoadedAt,networkIdleAt:n.networkIdleAt,networkStats:n.networkStats})),e=this.includeNetworkStats?await this.getCapturedRequests():[];return {navigations:t,networkRequests:e,consoleLogs:[...this.consoleLogs]}}async flushLegacyAnnotations(t){this.includeNetworkStats&&this.currentNetworkCounter&&this.records.length>0&&(this.records[this.records.length-1].networkStats={totalRequests:this.currentNetworkCounter.totalRequests,failedRequests:this.currentNetworkCounter.failedRequests,failedRequestUrls:[...this.currentNetworkCounter.failedRequestUrls],totalBytes:this.currentNetworkCounter.totalBytes,byType:{...this.currentNetworkCounter.byType}});for(let e of this.records){let n={url:e.url,navigationType:e.navigationType,timestamp:e.timestamp,domContentLoadedAt:e.domContentLoadedAt,networkIdleAt:e.networkIdleAt,networkStats:e.networkStats};t.annotations.push({type:"lambdatest-navigation",description:JSON.stringify(n)});}if(this.includeNetworkStats){let e=await this.getCapturedRequests();e.length>0&&t.annotations.push({type:"__testrelic_network_requests",description:JSON.stringify(e)});}}dispose(){this.page.goto=this.origGoto,this.page.goBack=this.origGoBack,this.page.goForward=this.origGoForward,this.page.reload=this.origReload;for(let{event:t,handler:e}of this.listeners)this.page.off(t,e);this.listeners=[],this.records=[],this.pendingRequests.clear(),this.capturedRequests=[],this.pendingBodyReads=[];}getRecords(){return this.records}interceptMethods(){let t=this,e=this.page;e.goto=async function(n,s){return t.recordNavigation(n,"goto"),t.origGoto(n,s)},e.goBack=async function(n){let s=await t.origGoBack(n);return t.recordNavigation(e.url(),"back"),s},e.goForward=async function(n){let s=await t.origGoForward(n);return t.recordNavigation(e.url(),"forward"),s},e.reload=async function(n){return t.recordNavigation(e.url(),"refresh"),t.origReload(n)};}attachListeners(){let t=()=>{this.lastDomContentLoaded=new Date().toISOString(),this.records.length>0&&(this.records[this.records.length-1].domContentLoadedAt=this.lastDomContentLoaded);};this.page.on("domcontentloaded",t),this.listeners.push({event:"domcontentloaded",handler:t});let e=s=>{try{let c=s;if(typeof c.parentFrame=="function"&&c.parentFrame()!==null)return;let d=c.url(),u=this.records[this.records.length-1];if(u&&Date.now()-new Date(u.timestamp).getTime()<50&&u.url===d)return;this.recordNavigation(d,"navigation");}catch{}};this.page.on("framenavigated",e),this.listeners.push({event:"framenavigated",handler:e});let n=s=>{try{let c=s,d=c.type(),u=c.text();if(d==="debug"&&u.startsWith("__testrelic_nav:")){try{let i=JSON.parse(u.slice(16));i.type&&i.url&&this.recordNavigation(i.url,i.type);}catch{}return}{let i=null;try{let a=c.location();a&&a.url&&(i=`${a.url}:${a.lineNumber}:${a.columnNumber}`);}catch{}let p=r.mapConsoleType(d);this.consoleLogs.push({level:p,text:u,timestamp:new Date().toISOString(),location:i});}}catch{}};if(this.page.on("console",n),this.listeners.push({event:"console",handler:n}),this.includeNetworkStats){let s=u=>{this.currentNetworkCounter&&this.currentNetworkCounter.totalRequests++;try{let i=u,p=String(this.requestIdCounter++),a=i.postData()??null,g={url:i.url(),method:i.method(),resourceType:this.mapResourceType(i.resourceType()),headers:i.headers(),postData:a,postDataTruncated:!1,startedAt:new Date().toISOString(),startTimeMs:Date.now()};this.pendingRequests.set(p,g),u.__testrelic_id=p;}catch{}};this.page.on("request",s),this.listeners.push({event:"request",handler:s});let c=u=>{try{let i=u;if(this.currentNetworkCounter){let f=i.status();f>=400&&(this.currentNetworkCounter.failedRequests++,this.currentNetworkCounter.failedRequestUrls.push(f+" "+i.url()));let h=i.headers()["content-length"];h&&(this.currentNetworkCounter.totalBytes+=parseInt(h,10)||0);let R=i.request().resourceType(),w=this.mapResourceType(R);this.currentNetworkCounter.byType[w]++;}let p=i.request().__testrelic_id;if(!p)return;let a=this.pendingRequests.get(p);if(!a)return;this.pendingRequests.delete(p);let g=Date.now()-a.startTimeMs,l=i.headers(),o=l["content-type"]??null,k=parseInt(l["content-length"]??"0",10)||0,y=o?!j(o):!1,m=(async()=>{let f=null;if(!y)try{f=(await i.body()).toString("utf-8");}catch{}let h={url:a.url,method:a.method,resourceType:a.resourceType,statusCode:i.status(),responseTimeMs:g,startedAt:a.startedAt,requestHeaders:a.headers,requestBody:a.postData,responseBody:f,responseHeaders:l,contentType:o,responseSize:k,requestBodyTruncated:a.postDataTruncated,responseBodyTruncated:!1,isBinary:y,error:null};this.capturedRequests.push(h);})();this.pendingBodyReads.push(m);}catch{}};this.page.on("response",c),this.listeners.push({event:"response",handler:c});let d=u=>{if(this.currentNetworkCounter){this.currentNetworkCounter.failedRequests++;try{let i=u;this.currentNetworkCounter.failedRequestUrls.push("ERR "+i.url());}catch{}}try{let i=u,p=i.__testrelic_id;if(!p)return;let a=this.pendingRequests.get(p);if(!a)return;this.pendingRequests.delete(p);let g={url:a.url,method:a.method,resourceType:a.resourceType,statusCode:0,responseTimeMs:Date.now()-a.startTimeMs,startedAt:a.startedAt,requestHeaders:a.headers,requestBody:a.postData,responseBody:null,responseHeaders:null,contentType:null,responseSize:0,requestBodyTruncated:a.postDataTruncated,responseBodyTruncated:!1,isBinary:!1,error:i.failure()?.errorText??"Unknown error"};this.capturedRequests.push(g);}catch{}};this.page.on("requestfailed",d),this.listeners.push({event:"requestfailed",handler:d});}}async injectSPADetection(){try{await this.page.addInitScript(()=>{let t=history.pushState.bind(history),e=history.replaceState.bind(history);history.pushState=function(...n){t(...n),console.debug("__testrelic_nav:"+JSON.stringify({type:"spa_route",url:location.href}));},history.replaceState=function(...n){e(...n),console.debug("__testrelic_nav:"+JSON.stringify({type:"spa_replace",url:location.href}));},window.addEventListener("popstate",()=>{console.debug("__testrelic_nav:"+JSON.stringify({type:"popstate",url:location.href}));}),window.addEventListener("hashchange",()=>{console.debug("__testrelic_nav:"+JSON.stringify({type:"hash_change",url:location.href}));});});}catch{}}recordNavigation(t,e){this.includeNetworkStats&&this.currentNetworkCounter&&this.records.length>0&&(this.records[this.records.length-1].networkStats={totalRequests:this.currentNetworkCounter.totalRequests,failedRequests:this.currentNetworkCounter.failedRequests,failedRequestUrls:[...this.currentNetworkCounter.failedRequestUrls],totalBytes:this.currentNetworkCounter.totalBytes,byType:{...this.currentNetworkCounter.byType}}),this.records.push({url:t,navigationType:e,timestamp:new Date().toISOString()}),this.includeNetworkStats&&(this.currentNetworkCounter=this.createNetworkCounter());}createNetworkCounter(){return {totalRequests:0,failedRequests:0,failedRequestUrls:[],totalBytes:0,byType:{xhr:0,document:0,script:0,stylesheet:0,image:0,font:0,other:0}}}mapResourceType(t){switch(t){case "xhr":case "fetch":return "xhr";case "document":return "document";case "script":return "script";case "stylesheet":return "stylesheet";case "image":return "image";case "font":return "font";default:return "other"}}};var x=Symbol.for("__testrelic_call_id"),H="__testrelic_api_assertions",C=new WeakMap,A=class{constructor(){this.assertions=[];this.currentCallId=null;}recordAssertion(t){this.assertions.push(t);}getAssertions(){return this.assertions}setCurrentCallId(t){this.currentCallId=t;}getCurrentCallId(){return this.currentCallId}getData(){return [...this.assertions]}flushLegacyAnnotations(t){this.assertions.length!==0&&t.annotations.push({type:H,description:JSON.stringify(this.assertions)});}dispose(){this.assertions=[],this.currentCallId=null;}};var B="[REDACTED]";function S(r,t){if(r===null||t.length===0)return r;let e=new Set(t.map(s=>s.toLowerCase())),n={};for(let s of Object.keys(r))Object.hasOwn(r,s)&&(n[s]=e.has(s.toLowerCase())?B:r[s]);return n}function v(r,t){if(r===null||t.length===0)return r;let e;try{e=JSON.parse(r);}catch{return r}if(typeof e!="object"||e===null)return r;let n=new Set(t),s=b(e,n);return JSON.stringify(s)}function b(r,t){if(Array.isArray(r))return r.map(e=>b(e,t));if(typeof r=="object"&&r!==null){let e={};for(let n of Object.keys(r)){if(!Object.hasOwn(r,n))continue;let s=r[n];t.has(n)?e[n]=B:e[n]=b(s,t);}return e}return r}function P(r,t,e){if(e.length>0){for(let n of e)if(I(r,n))return false}if(t.length>0){for(let n of t)if(I(r,n))return true;return false}return true}function I(r,t){try{return t instanceof RegExp?t.test(r):F(t).test(r)}catch{return console.warn(`[testrelic] Invalid URL filter pattern: ${String(t)}`),false}}function F(r){let t="",e=0;for(;e<r.length;){let n=r[e];n==="*"&&r[e+1]==="*"?(t+=".*",e+=2,r[e]==="/"&&e++):n==="*"?(t+="[^/]*",e++):n==="?"?(t+="[^/]",e++):".+^${}()|[]\\".includes(n)?(t+="\\"+n,e++):(t+=n,e++);}return new RegExp(t)}var U=["get","post","put","patch","delete","head","fetch"],J=["text/","application/json","application/xml","application/javascript","application/x-www-form-urlencoded","application/graphql"],G="__testrelic_api_calls";function z(r){let t=r.toLowerCase();return J.some(e=>t.includes(e))}function Y(r){if(!r)return null;if(r.data!==void 0&&r.data!==null){let t=r.data;return typeof t=="string"?t:Buffer.isBuffer(t)?t.toString("base64"):JSON.stringify(t)}if(r.form!==void 0&&r.form!==null)return JSON.stringify(r.form);if(r.multipart!==void 0&&r.multipart!==null){let t=r.multipart,e={};for(let[n,s]of Object.entries(t))typeof s=="string"||typeof s=="number"||typeof s=="boolean"?e[n]=String(s):s&&typeof s=="object"&&"name"in s?e[n]=`[file: ${s.name}]`:e[n]="[binary]";return JSON.stringify(e)}return null}var q=class q{constructor(t,e,n){this.originals=new Map;this.capturedCalls=[];this.callCounter=0;this.disposed=false;this._lastCallId=null;this.primitiveCallIds=new Map;this.context=t,this.assertionTracker=e??null,this.apiConfig=n??q.DEFAULT_API_CONFIG;}get lastCallId(){return this._lastCallId}getCallIdForValue(t){return t!=null&&typeof t=="object"?C.get(t)??null:this.primitiveCallIds.get(t)??null}intercept(){for(let e of U){let n=this.context[e].bind(this.context);this.originals.set(e,n),this.context[e]=this.createWrapper(e,n);}let t=this.context.dispose.bind(this.context);this.originals.set("dispose",t),this.context.dispose=async e=>(this.disposed=true,t(e));}getData(){return [...this.capturedCalls]}flushLegacyAnnotations(t){this.capturedCalls.length!==0&&t.annotations.push({type:G,description:JSON.stringify(this.capturedCalls)});}dispose(){for(let[t,e]of this.originals)this.context[t]=e;this.originals.clear(),this.capturedCalls=[],this.callCounter=0,this._lastCallId=null,this.primitiveCallIds.clear();}get isDisposed(){return this.disposed}getCapturedCalls(){return this.capturedCalls}tagResponseMethods(t,e){let n=this,s=t.headers.bind(t);t.headers=function(){let o=s();return C.set(o,e),o};let c=t.headersArray.bind(t);t.headersArray=function(){let o=c();return C.set(o,e),o};let d=t.json.bind(t);t.json=async function(){let o=await d();return o!=null&&typeof o=="object"&&C.set(o,e),o};let u=t.status.bind(t);t.status=function(){let o=u();return n.primitiveCallIds.set(o,e),o};let i=t.statusText.bind(t);t.statusText=function(){let o=i();return n.primitiveCallIds.set(o,e),o};let p=t.ok.bind(t);t.ok=function(){let o=p();return n.primitiveCallIds.set(o,e),o};let a=t.text.bind(t);t.text=async function(){let o=await a();return n.primitiveCallIds.set(o,e),o};let g=t.body.bind(t);t.body=async function(){let o=await g();return C.set(o,e),o};}createWrapper(t,e){let n=this;return async function(c,d){if(!P(c,n.apiConfig.apiIncludeUrls,n.apiConfig.apiExcludeUrls))return e(c,d);let u=`api-call-${n.callCounter++}`,i=new Date().toISOString(),p=t==="fetch"?(d?.method??"GET").toUpperCase():t.toUpperCase(),a=Y(d),g=perf_hooks.performance.now();n._lastCallId=u,n.assertionTracker&&n.assertionTracker.setCurrentCallId(u);let l;try{l=await e(c,d);}catch(o){let k=perf_hooks.performance.now();try{let y=n.apiConfig.captureRequestBody?v(a,n.apiConfig.redactBodyFields):null,m={id:u,timestamp:i,method:p,url:c,requestHeaders:null,requestBody:y,responseStatusCode:null,responseStatusText:null,responseHeaders:null,responseBody:null,responseTimeMs:Math.round((k-g)*100)/100,isBinary:!1,error:o instanceof Error?o.message:String(o)};n.capturedCalls.push(m);}catch{}throw o}try{let o=perf_hooks.performance.now(),k=l.headers(),y=k["content-type"]??null,m=y?!z(y):!1,f=null;n.apiConfig.captureRequestHeaders&&d?.headers&&(f=d.headers);let h=null;n.apiConfig.captureResponseHeaders&&(h=k);let R=n.apiConfig.captureRequestBody?a:null,w=null;if(n.apiConfig.captureResponseBody)try{m?w=(await l.body()).toString("base64"):w=await l.text();}catch{}f=S(f,n.apiConfig.redactHeaders),h=S(h,n.apiConfig.redactHeaders),R=v(R,n.apiConfig.redactBodyFields),w=v(w,n.apiConfig.redactBodyFields);let E={id:u,timestamp:i,method:p,url:l.url(),requestHeaders:f,requestBody:R,responseStatusCode:l.status(),responseStatusText:l.statusText(),responseHeaders:h,responseBody:w,responseTimeMs:Math.round((o-g)*100)/100,isBinary:m,error:null};n.capturedCalls.push(E);}catch{}try{l[x]=u,n.tagResponseMethods(l,u);}catch{}return l}}};q.DEFAULT_API_CONFIG=Object.freeze({trackApiCalls:true,captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]});var N=q;var W="__testrelic_api_config",K="__testrelic_config_trackApiCalls";function V(r){let t=r.annotations.find(n=>n.type===W&&n.description!==void 0);if(t)try{return JSON.parse(t.description,X)}catch{}let e=r.annotations.find(n=>n.type===K&&n.description!==void 0);return e?{trackApiCalls:e.description!=="false",captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]}:{trackApiCalls:true,captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]}}function X(r,t){if(typeof t=="object"&&t!==null&&t.__regexp===true&&typeof t.source=="string"){let{source:e,flags:n}=t;return new RegExp(e,n)}return t}var Q={page:async({page:r},t,e)=>{let n=new T(r);try{await n.init();}catch{}await t(r);try{let{navigations:s,networkRequests:c,consoleLogs:d}=await n.getData(),u={testRelicData:!0,version:core.PAYLOAD_VERSION,navigations:s,networkRequests:c,apiCalls:[],apiAssertions:[],consoleLogs:d};await e.attach(core.ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(u)),contentType:core.ATTACHMENT_CONTENT_TYPE});}catch{}try{await n.flushLegacyAnnotations(e);}catch{}n.dispose();},request:async({request:r},t,e)=>{let n=V(e);if(!n.trackApiCalls){await t(r);return}let s=new A,c=new N(r,s,n);c.intercept(),await t(r);try{let d=c.getData(),u=n.captureAssertions?s.getData():[],i={testRelicData:!0,version:core.PAYLOAD_VERSION,navigations:[],networkRequests:[],apiCalls:d,apiAssertions:u,consoleLogs:[]};await e.attach(core.ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(i)),contentType:core.ATTACHMENT_CONTENT_TYPE});}catch{}try{c.flushLegacyAnnotations(e);}catch{}try{s.flushLegacyAnnotations(e);}catch{}c.dispose(),s.dispose();}},kt=test.test.extend(Q);Object.defineProperty(exports,"expect",{enumerable:true,get:function(){return test.expect}});exports.readApiConfig=V;exports.test=kt;exports.testRelicFixture=Q;//# sourceMappingURL=fixture.cjs.map
|
|
1
|
+
'use strict';var test=require('@playwright/test'),fs=require('fs'),path=require('path'),os=require('os'),crypto=require('crypto');require('readline');var perf_hooks=require('perf_hooks'),core=require('@testrelic/core');var I=path.join(os.tmpdir(),"testrelic-data"),k=class{constructor(t){this.count=0;this.closed=false;fs.mkdirSync(I,{recursive:true}),this.filePath=path.join(I,`${t}-${crypto.randomUUID().substring(0,8)}.jsonl`),this.fd=fs.openSync(this.filePath,"w");}append(t){if(this.closed)return;let e=JSON.stringify(t)+`
|
|
2
|
+
`;fs.writeSync(this.fd,e),this.count++;}getPath(){return this.filePath}getCount(){return this.count}close(){if(!this.closed){this.closed=true;try{fs.closeSync(this.fd);}catch{}}}cleanup(){try{fs.unlinkSync(this.filePath);}catch{}}};var z=["text/","application/json","application/xml","application/javascript","application/x-www-form-urlencoded","application/graphql"];function K(r){let t=r.toLowerCase();return z.some(e=>t.includes(e))}var A=class r{constructor(t,e){this.page=t;this.records=[];this.listeners=[];this.currentNetworkCounter=null;this.pendingRequests=new Map;this.networkWriter=null;this.consoleWriter=null;this.pendingBodyReads=[];this.requestIdCounter=0;this.networkRequestCount=0;this.consoleLogCount=0;this.includeNetworkStats=e?.includeNetworkStats??true,this.origGoto=t.goto.bind(t),this.origGoBack=t.goBack.bind(t),this.origGoForward=t.goForward.bind(t),this.origReload=t.reload.bind(t);try{this.includeNetworkStats&&(this.networkWriter=new k("network")),this.consoleWriter=new k("console");}catch{}this.interceptMethods(),this.attachListeners();}async init(){await this.injectSPADetection();}async finalizeCapturedRequests(){await Promise.allSettled(this.pendingBodyReads),this.pendingBodyReads=[];for(let[,t]of this.pendingRequests)this.networkWriter&&(this.networkWriter.append({url:t.url,method:t.method,resourceType:t.resourceType,statusCode:0,responseTimeMs:Date.now()-t.startTimeMs,startedAt:t.startedAt,requestHeaders:t.headers,requestBody:t.postData,responseBody:null,responseHeaders:null,contentType:null,responseSize:0,requestBodyTruncated:t.postDataTruncated,responseBodyTruncated:false,isBinary:false,error:"incomplete"}),this.networkRequestCount++);this.pendingRequests.clear(),this.networkWriter?.close(),this.consoleWriter?.close();}static mapConsoleType(t){switch(t){case "log":return "log";case "warning":return "warn";case "error":return "error";case "info":return "info";case "debug":return "debug";default:return "log"}}async getFileData(){this.includeNetworkStats&&this.currentNetworkCounter&&this.records.length>0&&(this.records[this.records.length-1].networkStats={totalRequests:this.currentNetworkCounter.totalRequests,failedRequests:this.currentNetworkCounter.failedRequests,failedRequestUrls:[...this.currentNetworkCounter.failedRequestUrls],totalBytes:this.currentNetworkCounter.totalBytes,byType:{...this.currentNetworkCounter.byType}});let t=this.records.map(e=>({url:e.url,navigationType:e.navigationType,timestamp:e.timestamp,domContentLoadedAt:e.domContentLoadedAt,networkIdleAt:e.networkIdleAt,networkStats:e.networkStats}));return this.includeNetworkStats?await this.finalizeCapturedRequests():this.consoleWriter?.close(),{navigations:t,networkRequestsFile:this.networkWriter?.getCount()?this.networkWriter.getPath():null,networkRequestsCount:this.networkWriter?.getCount()??0,consoleLogsFile:this.consoleWriter?.getCount()?this.consoleWriter.getPath():null,consoleLogsCount:this.consoleWriter?.getCount()??0}}async flushLegacyAnnotations(t){}dispose(){this.page.goto=this.origGoto,this.page.goBack=this.origGoBack,this.page.goForward=this.origGoForward,this.page.reload=this.origReload;for(let{event:t,handler:e}of this.listeners)this.page.off(t,e);this.listeners=[],this.records=[],this.pendingRequests.clear(),this.pendingBodyReads=[],this.networkWriter?.close(),this.consoleWriter?.close();}getRecords(){return this.records}interceptMethods(){let t=this,e=this.page;e.goto=async function(n,s){return t.recordNavigation(n,"goto"),t.origGoto(n,s)},e.goBack=async function(n){let s=await t.origGoBack(n);return t.recordNavigation(e.url(),"back"),s},e.goForward=async function(n){let s=await t.origGoForward(n);return t.recordNavigation(e.url(),"forward"),s},e.reload=async function(n){return t.recordNavigation(e.url(),"refresh"),t.origReload(n)};}attachListeners(){let t=()=>{this.lastDomContentLoaded=new Date().toISOString(),this.records.length>0&&(this.records[this.records.length-1].domContentLoadedAt=this.lastDomContentLoaded);};this.page.on("domcontentloaded",t),this.listeners.push({event:"domcontentloaded",handler:t});let e=s=>{try{let c=s;if(typeof c.parentFrame=="function"&&c.parentFrame()!==null)return;let d=c.url(),u=this.records[this.records.length-1];if(u&&Date.now()-new Date(u.timestamp).getTime()<50&&u.url===d)return;this.recordNavigation(d,"navigation");}catch{}};this.page.on("framenavigated",e),this.listeners.push({event:"framenavigated",handler:e});let n=s=>{try{let c=s,d=c.type(),u=c.text();if(d==="debug"&&u.startsWith("__testrelic_nav:")){try{let o=JSON.parse(u.slice(16));o.type&&o.url&&this.recordNavigation(o.url,o.type);}catch{}return}{let o=null;try{let a=c.location();a&&a.url&&(o=`${a.url}:${a.lineNumber}:${a.columnNumber}`);}catch{}let p=r.mapConsoleType(d);this.consoleWriter&&(this.consoleWriter.append({level:p,text:u,timestamp:new Date().toISOString(),location:o}),this.consoleLogCount++);}}catch{}};if(this.page.on("console",n),this.listeners.push({event:"console",handler:n}),this.includeNetworkStats){let s=u=>{this.currentNetworkCounter&&this.currentNetworkCounter.totalRequests++;try{let o=u,p=String(this.requestIdCounter++),a=o.postData()??null,g={url:o.url(),method:o.method(),resourceType:this.mapResourceType(o.resourceType()),headers:o.headers(),postData:a,postDataTruncated:!1,startedAt:new Date().toISOString(),startTimeMs:Date.now()};this.pendingRequests.set(p,g),u.__testrelic_id=p;}catch{}};this.page.on("request",s),this.listeners.push({event:"request",handler:s});let c=u=>{try{let o=u;if(this.currentNetworkCounter){let f=o.status();f>=400&&(this.currentNetworkCounter.failedRequests++,this.currentNetworkCounter.failedRequestUrls.push(f+" "+o.url()));let h=o.headers()["content-length"];h&&(this.currentNetworkCounter.totalBytes+=parseInt(h,10)||0);let R=o.request().resourceType(),w=this.mapResourceType(R);this.currentNetworkCounter.byType[w]++;}let p=o.request().__testrelic_id;if(!p)return;let a=this.pendingRequests.get(p);if(!a)return;this.pendingRequests.delete(p);let g=Date.now()-a.startTimeMs,l=o.headers(),i=l["content-type"]??null,m=parseInt(l["content-length"]??"0",10)||0,y=i?!K(i):!1,C=(async()=>{let f=null;if(!y)try{f=(await o.body()).toString("utf-8");}catch{}let h={url:a.url,method:a.method,resourceType:a.resourceType,statusCode:o.status(),responseTimeMs:g,startedAt:a.startedAt,requestHeaders:a.headers,requestBody:a.postData,responseBody:f,responseHeaders:l,contentType:i,responseSize:m,requestBodyTruncated:a.postDataTruncated,responseBodyTruncated:!1,isBinary:y,error:null};this.networkWriter&&(this.networkWriter.append(h),this.networkRequestCount++);})();this.pendingBodyReads.push(C);}catch{}};this.page.on("response",c),this.listeners.push({event:"response",handler:c});let d=u=>{if(this.currentNetworkCounter){this.currentNetworkCounter.failedRequests++;try{let o=u;this.currentNetworkCounter.failedRequestUrls.push("ERR "+o.url());}catch{}}try{let o=u,p=o.__testrelic_id;if(!p)return;let a=this.pendingRequests.get(p);if(!a)return;this.pendingRequests.delete(p);let g={url:a.url,method:a.method,resourceType:a.resourceType,statusCode:0,responseTimeMs:Date.now()-a.startTimeMs,startedAt:a.startedAt,requestHeaders:a.headers,requestBody:a.postData,responseBody:null,responseHeaders:null,contentType:null,responseSize:0,requestBodyTruncated:a.postDataTruncated,responseBodyTruncated:!1,isBinary:!1,error:o.failure()?.errorText??"Unknown error"};this.networkWriter&&(this.networkWriter.append(g),this.networkRequestCount++);}catch{}};this.page.on("requestfailed",d),this.listeners.push({event:"requestfailed",handler:d});}}async injectSPADetection(){try{await this.page.addInitScript(()=>{let t=history.pushState.bind(history),e=history.replaceState.bind(history);history.pushState=function(...n){t(...n),console.debug("__testrelic_nav:"+JSON.stringify({type:"spa_route",url:location.href}));},history.replaceState=function(...n){e(...n),console.debug("__testrelic_nav:"+JSON.stringify({type:"spa_replace",url:location.href}));},window.addEventListener("popstate",()=>{console.debug("__testrelic_nav:"+JSON.stringify({type:"popstate",url:location.href}));}),window.addEventListener("hashchange",()=>{console.debug("__testrelic_nav:"+JSON.stringify({type:"hash_change",url:location.href}));});});}catch{}}recordNavigation(t,e){this.includeNetworkStats&&this.currentNetworkCounter&&this.records.length>0&&(this.records[this.records.length-1].networkStats={totalRequests:this.currentNetworkCounter.totalRequests,failedRequests:this.currentNetworkCounter.failedRequests,failedRequestUrls:[...this.currentNetworkCounter.failedRequestUrls],totalBytes:this.currentNetworkCounter.totalBytes,byType:{...this.currentNetworkCounter.byType}}),this.records.push({url:t,navigationType:e,timestamp:new Date().toISOString()}),this.includeNetworkStats&&(this.currentNetworkCounter=this.createNetworkCounter());}createNetworkCounter(){return {totalRequests:0,failedRequests:0,failedRequestUrls:[],totalBytes:0,byType:{xhr:0,document:0,script:0,stylesheet:0,image:0,font:0,other:0}}}mapResourceType(t){switch(t){case "xhr":case "fetch":return "xhr";case "document":return "document";case "script":return "script";case "stylesheet":return "stylesheet";case "image":return "image";case "font":return "font";default:return "other"}}};var B=Symbol.for("__testrelic_call_id"),V="__testrelic_api_assertions",T=new WeakMap,v=class{constructor(){this.assertions=[];this.currentCallId=null;}recordAssertion(t){this.assertions.push(t);}getAssertions(){return this.assertions}setCurrentCallId(t){this.currentCallId=t;}getCurrentCallId(){return this.currentCallId}getData(){return [...this.assertions]}flushLegacyAnnotations(t){this.assertions.length!==0&&t.annotations.push({type:V,description:JSON.stringify(this.assertions)});}dispose(){this.assertions=[],this.currentCallId=null;}};var L="[REDACTED]";function S(r,t){if(r===null||t.length===0)return r;let e=new Set(t.map(s=>s.toLowerCase())),n={};for(let s of Object.keys(r))Object.hasOwn(r,s)&&(n[s]=e.has(s.toLowerCase())?L:r[s]);return n}function b(r,t){if(r===null||t.length===0)return r;let e;try{e=JSON.parse(r);}catch{return r}if(typeof e!="object"||e===null)return r;let n=new Set(t),s=_(e,n);return JSON.stringify(s)}function _(r,t){if(Array.isArray(r))return r.map(e=>_(e,t));if(typeof r=="object"&&r!==null){let e={};for(let n of Object.keys(r)){if(!Object.hasOwn(r,n))continue;let s=r[n];t.has(n)?e[n]=L:e[n]=_(s,t);}return e}return r}function O(r,t,e){if(e.length>0){for(let n of e)if(F(r,n))return false}if(t.length>0){for(let n of t)if(F(r,n))return true;return false}return true}function F(r,t){try{return t instanceof RegExp?t.test(r):X(t).test(r)}catch{return console.warn(`[testrelic] Invalid URL filter pattern: ${String(t)}`),false}}function X(r){let t="",e=0;for(;e<r.length;){let n=r[e];n==="*"&&r[e+1]==="*"?(t+=".*",e+=2,r[e]==="/"&&e++):n==="*"?(t+="[^/]*",e++):n==="?"?(t+="[^/]",e++):".+^${}()|[]\\".includes(n)?(t+="\\"+n,e++):(t+=n,e++);}return new RegExp(t)}var Q=["get","post","put","patch","delete","head","fetch"],Z=["text/","application/json","application/xml","application/javascript","application/x-www-form-urlencoded","application/graphql"];function tt(r){let t=r.toLowerCase();return Z.some(e=>t.includes(e))}function et(r){if(!r)return null;if(r.data!==void 0&&r.data!==null){let t=r.data;return typeof t=="string"?t:Buffer.isBuffer(t)?t.toString("base64"):JSON.stringify(t)}if(r.form!==void 0&&r.form!==null)return JSON.stringify(r.form);if(r.multipart!==void 0&&r.multipart!==null){let t=r.multipart,e={};for(let[n,s]of Object.entries(t))typeof s=="string"||typeof s=="number"||typeof s=="boolean"?e[n]=String(s):s&&typeof s=="object"&&"name"in s?e[n]=`[file: ${s.name}]`:e[n]="[binary]";return JSON.stringify(e)}return null}var q=class q{constructor(t,e,n){this.originals=new Map;this.apiCallWriter=null;this.callCounter=0;this.apiCallCount=0;this.disposed=false;this._lastCallId=null;this.primitiveCallIds=new Map;this.context=t,this.assertionTracker=e??null,this.apiConfig=n??q.DEFAULT_API_CONFIG;try{this.apiCallWriter=new k("api-calls");}catch{}}get lastCallId(){return this._lastCallId}getCallIdForValue(t){return t!=null&&typeof t=="object"?T.get(t)??null:this.primitiveCallIds.get(t)??null}intercept(){for(let e of Q){let n=this.context[e].bind(this.context);this.originals.set(e,n),this.context[e]=this.createWrapper(e,n);}let t=this.context.dispose.bind(this.context);this.originals.set("dispose",t),this.context.dispose=async e=>(this.disposed=true,t(e));}getFileData(){return this.apiCallWriter?.close(),{apiCallsFile:this.apiCallWriter?.getCount()?this.apiCallWriter.getPath():null,apiCallsCount:this.apiCallWriter?.getCount()??0}}flushLegacyAnnotations(t){}dispose(){for(let[t,e]of this.originals)this.context[t]=e;this.originals.clear(),this.apiCallWriter?.close(),this.callCounter=0,this.apiCallCount=0,this._lastCallId=null,this.primitiveCallIds.clear();}get isDisposed(){return this.disposed}getCapturedCallCount(){return this.apiCallCount}tagResponseMethods(t,e){let n=this,s=t.headers.bind(t);t.headers=function(){let i=s();return T.set(i,e),i};let c=t.headersArray.bind(t);t.headersArray=function(){let i=c();return T.set(i,e),i};let d=t.json.bind(t);t.json=async function(){let i=await d();return i!=null&&typeof i=="object"&&T.set(i,e),i};let u=t.status.bind(t);t.status=function(){let i=u();return n.primitiveCallIds.set(i,e),i};let o=t.statusText.bind(t);t.statusText=function(){let i=o();return n.primitiveCallIds.set(i,e),i};let p=t.ok.bind(t);t.ok=function(){let i=p();return n.primitiveCallIds.set(i,e),i};let a=t.text.bind(t);t.text=async function(){let i=await a();return n.primitiveCallIds.set(i,e),i};let g=t.body.bind(t);t.body=async function(){let i=await g();return T.set(i,e),i};}createWrapper(t,e){let n=this;return async function(c,d){if(!O(c,n.apiConfig.apiIncludeUrls,n.apiConfig.apiExcludeUrls))return e(c,d);let u=`api-call-${n.callCounter++}`,o=new Date().toISOString(),p=t==="fetch"?(d?.method??"GET").toUpperCase():t.toUpperCase(),a=et(d),g=perf_hooks.performance.now();n._lastCallId=u,n.assertionTracker&&n.assertionTracker.setCurrentCallId(u);let l;try{l=await e(c,d);}catch(i){let m=perf_hooks.performance.now();try{let y=n.apiConfig.captureRequestBody?b(a,n.apiConfig.redactBodyFields):null,C={id:u,timestamp:o,method:p,url:c,requestHeaders:null,requestBody:y,responseStatusCode:null,responseStatusText:null,responseHeaders:null,responseBody:null,responseTimeMs:Math.round((m-g)*100)/100,isBinary:!1,error:i instanceof Error?i.message:String(i)};n.apiCallWriter&&(n.apiCallWriter.append(C),n.apiCallCount++);}catch{}throw i}try{let i=perf_hooks.performance.now(),m=l.headers(),y=m["content-type"]??null,C=y?!tt(y):!1,f=null;n.apiConfig.captureRequestHeaders&&d?.headers&&(f=d.headers);let h=null;n.apiConfig.captureResponseHeaders&&(h=m);let R=n.apiConfig.captureRequestBody?a:null,w=null;if(n.apiConfig.captureResponseBody)try{C?w=(await l.body()).toString("base64"):w=await l.text();}catch{}f=S(f,n.apiConfig.redactHeaders),h=S(h,n.apiConfig.redactHeaders),R=b(R,n.apiConfig.redactBodyFields),w=b(w,n.apiConfig.redactBodyFields);let W={id:u,timestamp:o,method:p,url:l.url(),requestHeaders:f,requestBody:R,responseStatusCode:l.status(),responseStatusText:l.statusText(),responseHeaders:h,responseBody:w,responseTimeMs:Math.round((i-g)*100)/100,isBinary:C,error:null};n.apiCallWriter&&(n.apiCallWriter.append(W),n.apiCallCount++);}catch{}try{l[B]=u,n.tagResponseMethods(l,u);}catch{}return l}}};q.DEFAULT_API_CONFIG=Object.freeze({trackApiCalls:true,captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]});var N=q;var rt="__testrelic_api_config",st="__testrelic_config_trackApiCalls";function ot(r){let t=r.annotations.find(n=>n.type===rt&&n.description!==void 0);if(t)try{return JSON.parse(t.description,it)}catch{}let e=r.annotations.find(n=>n.type===st&&n.description!==void 0);return e?{trackApiCalls:e.description!=="false",captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]}:{trackApiCalls:true,captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]}}function it(r,t){if(typeof t=="object"&&t!==null&&t.__regexp===true&&typeof t.source=="string"){let{source:e,flags:n}=t;return new RegExp(e,n)}return t}var at={page:async({page:r},t,e)=>{let n=new A(r);try{await n.init();}catch{}await t(r);try{let{navigations:s,networkRequestsFile:c,networkRequestsCount:d,consoleLogsFile:u,consoleLogsCount:o}=await n.getFileData(),p={testRelicData:!0,version:core.PAYLOAD_VERSION,navigations:s,apiAssertions:[],networkRequestsFile:c,networkRequestsCount:d,consoleLogsFile:u,consoleLogsCount:o,apiCallsFile:null,apiCallsCount:0};await e.attach(core.ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(p)),contentType:core.ATTACHMENT_CONTENT_TYPE});}catch{}n.dispose();},request:async({request:r},t,e)=>{let n=ot(e);if(!n.trackApiCalls){await t(r);return}let s=new v,c=new N(r,s,n);c.intercept(),await t(r);try{let{apiCallsFile:d,apiCallsCount:u}=c.getFileData(),o=n.captureAssertions?s.getData():[],p={testRelicData:!0,version:core.PAYLOAD_VERSION,navigations:[],apiAssertions:o,networkRequestsFile:null,networkRequestsCount:0,consoleLogsFile:null,consoleLogsCount:0,apiCallsFile:d,apiCallsCount:u};await e.attach(core.ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(p)),contentType:core.ATTACHMENT_CONTENT_TYPE});}catch{}c.dispose(),s.dispose();}},Dt=test.test.extend(at);Object.defineProperty(exports,"expect",{enumerable:true,get:function(){return test.expect}});exports.readApiConfig=ot;exports.test=Dt;exports.testRelicFixture=at;//# sourceMappingURL=fixture.cjs.map
|
|
2
3
|
//# sourceMappingURL=fixture.cjs.map
|