@piwitests/reporter 0.55.0 → 0.56.0
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/cli/index.js +1339 -1369
- package/dist/global-setup-module.js +32 -22
- package/dist/index.d.ts +9 -0
- package/dist/index.js +460 -331
- package/dist/internal/capture/capture-fixtures.d.ts +2 -1
- package/dist/internal/capture/capture-fixtures.js +1035 -980
- package/dist/internal/capture/locator-healing.js +70 -26
- package/dist/internal/capture/pick-on-failure.js +84 -54
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
45
45
|
// src/public/reporter.ts
|
|
46
46
|
var fs24 = __toESM(require("fs"));
|
|
47
47
|
var os9 = __toESM(require("os"));
|
|
48
|
-
var
|
|
48
|
+
var path24 = __toESM(require("path"));
|
|
49
49
|
|
|
50
50
|
// src/internal/config/desktop.ts
|
|
51
51
|
var fs = __toESM(require("fs"));
|
|
@@ -363,11 +363,15 @@ var HttpClient = class {
|
|
|
363
363
|
* @param logger Prefixed logger for verbose diagnostics.
|
|
364
364
|
* @param timeout Socket inactivity timeout in ms (default 30s), so a hung
|
|
365
365
|
* server fails fast rather than stalling the reporter.
|
|
366
|
+
* @param uploadTimeout Socket inactivity timeout of a multipart upload in ms
|
|
367
|
+
* (default 10 min): the dashboard answers one only once it
|
|
368
|
+
* has stored every report and trace in it.
|
|
366
369
|
*/
|
|
367
|
-
constructor(serverUrl, logger = new Logger(), timeout = 3e4) {
|
|
370
|
+
constructor(serverUrl, logger = new Logger(), timeout = 3e4, uploadTimeout = 10 * 60 * 1e3) {
|
|
368
371
|
this.serverUrl = serverUrl;
|
|
369
372
|
this.logger = logger;
|
|
370
373
|
this.timeout = timeout;
|
|
374
|
+
this.uploadTimeout = uploadTimeout;
|
|
371
375
|
/** Requests in flight, so `close` can abort them. */
|
|
372
376
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
373
377
|
/** Set by `close`: every later request fails with this reason. */
|
|
@@ -515,7 +519,7 @@ var HttpClient = class {
|
|
|
515
519
|
/** Send a multipart form-data POST request. Used for report and trace uploads. */
|
|
516
520
|
async postFormData(pathname, form, auth) {
|
|
517
521
|
const headers = form.getHeaders();
|
|
518
|
-
const res = await this.request("POST", pathname, { headers, form, auth });
|
|
522
|
+
const res = await this.request("POST", pathname, { headers, form, auth, timeout: this.uploadTimeout });
|
|
519
523
|
if (res.status < 200 || res.status >= 300) {
|
|
520
524
|
this.logger.debugError(`Response: ${res.text}`);
|
|
521
525
|
const detail = parseErrorMessage(res.text);
|
|
@@ -568,8 +572,9 @@ var HttpClient = class {
|
|
|
568
572
|
this.inFlight.add(req);
|
|
569
573
|
req.on("close", () => this.inFlight.delete(req));
|
|
570
574
|
req.on("error", reject);
|
|
571
|
-
|
|
572
|
-
|
|
575
|
+
const timeout = opts.timeout ?? this.timeout;
|
|
576
|
+
req.setTimeout(timeout, () => {
|
|
577
|
+
req.destroy(new Error(`Request to ${pathname} timed out after ${timeout}ms`));
|
|
573
578
|
});
|
|
574
579
|
if (opts.form) {
|
|
575
580
|
opts.form.on("error", (error) => req.destroy(error));
|
|
@@ -663,13 +668,18 @@ function browserProjectName(browser) {
|
|
|
663
668
|
}
|
|
664
669
|
return "";
|
|
665
670
|
}
|
|
671
|
+
function isFailingStatus(status) {
|
|
672
|
+
return status === "failed" || status === "timedOut" || status === "timedout" || status === "interrupted";
|
|
673
|
+
}
|
|
666
674
|
function computeDistinctRunCounts(testCases) {
|
|
667
675
|
const final = /* @__PURE__ */ new Map();
|
|
668
676
|
for (const tc of testCases) {
|
|
669
|
-
const key =
|
|
677
|
+
const key = tc.testId ?? [tc.title, tc.location, (tc.suitePath ?? []).join(""), browserProjectName(tc.browser)].join("\0");
|
|
678
|
+
const status = tc.status ?? "";
|
|
670
679
|
const retries = tc.retries ?? 0;
|
|
671
680
|
const prev = final.get(key);
|
|
672
|
-
|
|
681
|
+
const decides = !prev || retries > prev.retries || retries === prev.retries && (isFailingStatus(status) || !isFailingStatus(prev.status));
|
|
682
|
+
if (decides) final.set(key, { status, retries });
|
|
673
683
|
}
|
|
674
684
|
const counts = {
|
|
675
685
|
totalTests: final.size,
|
|
@@ -677,14 +687,11 @@ function computeDistinctRunCounts(testCases) {
|
|
|
677
687
|
failedTests: 0,
|
|
678
688
|
timedOutTests: 0,
|
|
679
689
|
skippedTests: 0,
|
|
680
|
-
didNotRunTests: 0
|
|
681
|
-
flakyTests: 0
|
|
690
|
+
didNotRunTests: 0
|
|
682
691
|
};
|
|
683
|
-
for (const { status
|
|
684
|
-
if (status === "passed")
|
|
685
|
-
|
|
686
|
-
if (retries > 0) counts.flakyTests++;
|
|
687
|
-
} else if (status === "failed") counts.failedTests++;
|
|
692
|
+
for (const { status } of final.values()) {
|
|
693
|
+
if (status === "passed") counts.passedTests++;
|
|
694
|
+
else if (status === "failed") counts.failedTests++;
|
|
688
695
|
else if (status === "timedOut" || status === "timedout") counts.timedOutTests++;
|
|
689
696
|
else if (status === "skipped") counts.skippedTests++;
|
|
690
697
|
else if (status === "didnotrun") counts.didNotRunTests++;
|
|
@@ -719,6 +726,7 @@ function serializeRun(payload, opts) {
|
|
|
719
726
|
filterDetails: payload.filterDetails ?? null
|
|
720
727
|
};
|
|
721
728
|
if (payload.resourceReport) body.resourceReport = payload.resourceReport;
|
|
729
|
+
if (payload.deliveryKey) body.deliveryKey = payload.deliveryKey;
|
|
722
730
|
if (opts.includeTestCases) {
|
|
723
731
|
body.testCases = payload.testCases.map((tc) => toWireTestCase(tc));
|
|
724
732
|
}
|
|
@@ -990,6 +998,9 @@ var StreamBuffer = class {
|
|
|
990
998
|
};
|
|
991
999
|
|
|
992
1000
|
// src/internal/streaming/crash-recovery.ts
|
|
1001
|
+
var crypto2 = __toESM(require("crypto"));
|
|
1002
|
+
var fs4 = __toESM(require("fs"));
|
|
1003
|
+
var os4 = __toESM(require("os"));
|
|
993
1004
|
var path4 = __toESM(require("path"));
|
|
994
1005
|
|
|
995
1006
|
// src/internal/support/errors.ts
|
|
@@ -999,45 +1010,79 @@ function errorMessage(error) {
|
|
|
999
1010
|
}
|
|
1000
1011
|
|
|
1001
1012
|
// src/internal/streaming/crash-recovery.ts
|
|
1002
|
-
|
|
1003
|
-
|
|
1013
|
+
function recoveryServer(serverUrl) {
|
|
1014
|
+
return serverUrl.replace(/\/+$/, "");
|
|
1015
|
+
}
|
|
1016
|
+
function recoveryFilePath(serverUrl, projectName) {
|
|
1017
|
+
const key = crypto2.createHash("sha256").update(`${recoveryServer(serverUrl)}
|
|
1018
|
+
${projectName}`).digest("hex").slice(0, 16);
|
|
1019
|
+
return path4.join(os4.tmpdir(), `piwi-dashboard-recovery-${key}.json`);
|
|
1020
|
+
}
|
|
1021
|
+
function refusesRun(error) {
|
|
1022
|
+
return error instanceof HttpError && [400, 409, 422].includes(error.status);
|
|
1023
|
+
}
|
|
1004
1024
|
var CrashRecovery = class {
|
|
1005
1025
|
/**
|
|
1006
|
-
* @param
|
|
1026
|
+
* @param serverUrl The dashboard the run is for: the file is kept for it and replayed to it only.
|
|
1027
|
+
* @param projectName With `serverUrl`, the key of the file.
|
|
1007
1028
|
* @param logger Prefixed logger.
|
|
1008
1029
|
*/
|
|
1009
|
-
constructor(projectName, logger = new Logger()) {
|
|
1030
|
+
constructor(serverUrl, projectName, logger = new Logger()) {
|
|
1010
1031
|
this.logger = logger;
|
|
1011
|
-
this.
|
|
1032
|
+
this.serverUrl = recoveryServer(serverUrl);
|
|
1033
|
+
this.filePath = recoveryFilePath(serverUrl, projectName);
|
|
1012
1034
|
}
|
|
1013
|
-
/**
|
|
1014
|
-
save(
|
|
1035
|
+
/** Write the payload for a later retry, owner-only, replacing the file in one step. */
|
|
1036
|
+
save(payload) {
|
|
1037
|
+
const file = { v: 1, serverUrl: this.serverUrl, payload };
|
|
1038
|
+
const temp = `${this.filePath}.${process.pid}.${crypto2.randomBytes(6).toString("hex")}.tmp`;
|
|
1015
1039
|
try {
|
|
1016
|
-
fs4.writeFileSync(
|
|
1040
|
+
fs4.writeFileSync(temp, JSON.stringify(file), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
1041
|
+
fs4.renameSync(temp, this.filePath);
|
|
1017
1042
|
this.logger.info("Saved recovery data for later upload");
|
|
1018
1043
|
} catch (error) {
|
|
1044
|
+
fs4.rmSync(temp, { force: true });
|
|
1019
1045
|
this.logger.error(`Failed to save recovery data: ${errorMessage(error)}`);
|
|
1020
1046
|
}
|
|
1021
1047
|
}
|
|
1022
|
-
/**
|
|
1048
|
+
/**
|
|
1049
|
+
* The saved payload, or null when there is none, or when the file is not a
|
|
1050
|
+
* regular file this user owns, or names another dashboard.
|
|
1051
|
+
*/
|
|
1023
1052
|
load() {
|
|
1053
|
+
let fd;
|
|
1024
1054
|
try {
|
|
1025
|
-
|
|
1055
|
+
fd = fs4.openSync(this.filePath, fs4.constants.O_RDONLY | (fs4.constants.O_NOFOLLOW ?? 0));
|
|
1026
1056
|
} catch {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
try {
|
|
1060
|
+
const stat = fs4.fstatSync(fd);
|
|
1061
|
+
if (!stat.isFile() || typeof process.getuid === "function" && stat.uid !== process.getuid()) return null;
|
|
1062
|
+
const file = JSON.parse(fs4.readFileSync(fd, "utf8"));
|
|
1063
|
+
if (file?.v !== 1 || file.serverUrl !== this.serverUrl) return null;
|
|
1064
|
+
return file.payload && typeof file.payload === "object" ? file.payload : null;
|
|
1065
|
+
} catch {
|
|
1066
|
+
return null;
|
|
1067
|
+
} finally {
|
|
1068
|
+
fs4.closeSync(fd);
|
|
1027
1069
|
}
|
|
1028
|
-
return null;
|
|
1029
1070
|
}
|
|
1030
1071
|
/** Delete the recovery file from disk */
|
|
1031
1072
|
clear() {
|
|
1032
1073
|
try {
|
|
1033
|
-
|
|
1074
|
+
fs4.rmSync(this.filePath, { force: true });
|
|
1034
1075
|
} catch {
|
|
1035
1076
|
}
|
|
1036
1077
|
}
|
|
1037
|
-
/**
|
|
1078
|
+
/**
|
|
1079
|
+
* Submit a previously saved payload to the dashboard it was saved for. The
|
|
1080
|
+
* file is deleted once the dashboard took the run, or refused it for good;
|
|
1081
|
+
* anything else keeps it for the next run.
|
|
1082
|
+
*/
|
|
1038
1083
|
async tryUpload(httpClient, auth) {
|
|
1039
1084
|
const data = this.load();
|
|
1040
|
-
if (!data) return;
|
|
1085
|
+
if (!data || recoveryServer(httpClient.baseUrl) !== this.serverUrl) return;
|
|
1041
1086
|
this.logger.info("Found saved test data from a previous run, uploading...");
|
|
1042
1087
|
delete data.instanceId;
|
|
1043
1088
|
const metadata = data.metadata && typeof data.metadata === "object" ? data.metadata : {};
|
|
@@ -1047,6 +1092,11 @@ var CrashRecovery = class {
|
|
|
1047
1092
|
this.logger.info("Successfully uploaded saved test data");
|
|
1048
1093
|
this.clear();
|
|
1049
1094
|
} catch (error) {
|
|
1095
|
+
if (refusesRun(error)) {
|
|
1096
|
+
this.logger.warn(`The dashboard refused the saved test data, which is deleted: ${errorMessage(error)}`);
|
|
1097
|
+
this.clear();
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1050
1100
|
this.logger.warn(`Could not upload saved test data: ${errorMessage(error)}`);
|
|
1051
1101
|
}
|
|
1052
1102
|
}
|
|
@@ -1056,7 +1106,7 @@ var CrashRecovery = class {
|
|
|
1056
1106
|
var fs6 = __toESM(require("fs"));
|
|
1057
1107
|
var os5 = __toESM(require("os"));
|
|
1058
1108
|
var path6 = __toESM(require("path"));
|
|
1059
|
-
var
|
|
1109
|
+
var crypto3 = __toESM(require("crypto"));
|
|
1060
1110
|
|
|
1061
1111
|
// src/internal/files/compression.ts
|
|
1062
1112
|
var fs5 = __toESM(require("fs"));
|
|
@@ -1355,7 +1405,7 @@ var FileHandler = class {
|
|
|
1355
1405
|
if (fs6.existsSync(tp)) lastPath = tp;
|
|
1356
1406
|
}
|
|
1357
1407
|
if (!lastPath) return null;
|
|
1358
|
-
const hash =
|
|
1408
|
+
const hash = crypto3.createHash("sha256");
|
|
1359
1409
|
await new Promise((resolve9, reject) => {
|
|
1360
1410
|
fs6.createReadStream(lastPath).on("data", (chunk) => hash.update(chunk)).on("end", resolve9).on("error", reject);
|
|
1361
1411
|
});
|
|
@@ -1387,6 +1437,31 @@ function formatMiB(bytes) {
|
|
|
1387
1437
|
|
|
1388
1438
|
// src/internal/collect/metadata-collector.ts
|
|
1389
1439
|
var import_node_child_process = require("child_process");
|
|
1440
|
+
|
|
1441
|
+
// src/internal/support/test-paths.ts
|
|
1442
|
+
var path7 = __toESM(require("path"));
|
|
1443
|
+
function configDirFromConfig(config) {
|
|
1444
|
+
if (config?.configFile) return path7.dirname(config.configFile);
|
|
1445
|
+
if (config?.rootDir) return config.rootDir;
|
|
1446
|
+
return process.cwd();
|
|
1447
|
+
}
|
|
1448
|
+
function relativeTestPath(file, base) {
|
|
1449
|
+
return path7.relative(base, file).split(path7.sep).join("/");
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// src/internal/collect/metadata-collector.ts
|
|
1453
|
+
function withoutCredentials(remoteUrl) {
|
|
1454
|
+
let url;
|
|
1455
|
+
try {
|
|
1456
|
+
url = new URL(remoteUrl);
|
|
1457
|
+
} catch {
|
|
1458
|
+
return remoteUrl;
|
|
1459
|
+
}
|
|
1460
|
+
if (!url.username && !url.password) return remoteUrl;
|
|
1461
|
+
url.username = "";
|
|
1462
|
+
url.password = "";
|
|
1463
|
+
return url.toString();
|
|
1464
|
+
}
|
|
1390
1465
|
var DETACHED_HEAD = "HEAD";
|
|
1391
1466
|
function resolveScmBranch(env, gitBranch) {
|
|
1392
1467
|
const pick = (...names) => {
|
|
@@ -1454,7 +1529,7 @@ var MetadataCollector = class {
|
|
|
1454
1529
|
/** Collect all available metadata from the environment, config, and suite */
|
|
1455
1530
|
collect(config, suite, options) {
|
|
1456
1531
|
const metadata = {
|
|
1457
|
-
workingDir:
|
|
1532
|
+
workingDir: configDirFromConfig(config),
|
|
1458
1533
|
[RUN_ORIGIN_METADATA_KEY]: resolveRunOrigin(process.env, (message) => this.logger.warn(message))
|
|
1459
1534
|
};
|
|
1460
1535
|
if (options.projectDescription) metadata.projectDescription = options.projectDescription;
|
|
@@ -1572,7 +1647,7 @@ var MetadataCollector = class {
|
|
|
1572
1647
|
scm.author = (0, import_node_child_process.execSync)('git log -1 --pretty=format:"%an"', execOpts).trim();
|
|
1573
1648
|
scm.commitMessage = (0, import_node_child_process.execSync)('git log -1 --pretty=format:"%s"', execOpts).trim();
|
|
1574
1649
|
try {
|
|
1575
|
-
scm.remoteUrl = (0, import_node_child_process.execSync)("git config --get remote.origin.url", execOpts).trim();
|
|
1650
|
+
scm.remoteUrl = withoutCredentials((0, import_node_child_process.execSync)("git config --get remote.origin.url", execOpts).trim());
|
|
1576
1651
|
} catch {
|
|
1577
1652
|
}
|
|
1578
1653
|
} catch (error) {
|
|
@@ -1825,11 +1900,11 @@ function createLimiter(maxConcurrent) {
|
|
|
1825
1900
|
}
|
|
1826
1901
|
|
|
1827
1902
|
// src/internal/support/setup-file.ts
|
|
1828
|
-
var
|
|
1903
|
+
var path8 = __toESM(require("path"));
|
|
1829
1904
|
var os6 = __toESM(require("os"));
|
|
1830
1905
|
var fs7 = __toESM(require("fs"));
|
|
1831
1906
|
function getSetupFilePath(projectName) {
|
|
1832
|
-
return
|
|
1907
|
+
return path8.join(os6.tmpdir(), `piwi-dashboard-setup-${hashForProject(projectName)}.json`);
|
|
1833
1908
|
}
|
|
1834
1909
|
function readSetupInfo(projectName) {
|
|
1835
1910
|
const setupFile = getSetupFilePath(projectName);
|
|
@@ -2678,6 +2753,10 @@ function tryParseLocatorChain(expr) {
|
|
|
2678
2753
|
return null;
|
|
2679
2754
|
}
|
|
2680
2755
|
}
|
|
2756
|
+
function quoteJsString(value) {
|
|
2757
|
+
const json = JSON.stringify(value).slice(1, -1);
|
|
2758
|
+
return `'${json.replace(/\\"/g, '"').replace(/'/g, "\\'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
|
|
2759
|
+
}
|
|
2681
2760
|
function parseLeafLocatorCall(expr) {
|
|
2682
2761
|
const chain = tryParseLocatorChain(expr);
|
|
2683
2762
|
if (!chain) return null;
|
|
@@ -2744,11 +2823,20 @@ function extractMessageHead(text) {
|
|
|
2744
2823
|
let head = text;
|
|
2745
2824
|
const callLogIdx = head.indexOf("\nCall log:");
|
|
2746
2825
|
if (callLogIdx !== -1) head = head.slice(0, callLogIdx);
|
|
2747
|
-
const stackIdx = head
|
|
2826
|
+
const stackIdx = stackStart(head);
|
|
2748
2827
|
if (stackIdx !== -1) head = head.slice(0, stackIdx);
|
|
2749
2828
|
const lines = head.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2750
2829
|
return lines.slice(0, 5).join("\n");
|
|
2751
2830
|
}
|
|
2831
|
+
function stackStart(text) {
|
|
2832
|
+
const runs = /\s+/g;
|
|
2833
|
+
for (let run = runs.exec(text); run; run = runs.exec(text)) {
|
|
2834
|
+
if (!text.startsWith("at ", runs.lastIndex)) continue;
|
|
2835
|
+
const lineBreak = run[0].indexOf("\n");
|
|
2836
|
+
if (lineBreak !== -1 && lineBreak < run[0].length - 1) return run.index + lineBreak;
|
|
2837
|
+
}
|
|
2838
|
+
return -1;
|
|
2839
|
+
}
|
|
2752
2840
|
function extractSelector(text) {
|
|
2753
2841
|
const match = SELECTOR_FN_RE.exec(text);
|
|
2754
2842
|
if (!match) return null;
|
|
@@ -2869,7 +2957,7 @@ function readChain(region) {
|
|
|
2869
2957
|
return link && CHAIN_LINK_METHODS.has(link[1]) ? null : scanned;
|
|
2870
2958
|
}
|
|
2871
2959
|
function extractStackFrames(text) {
|
|
2872
|
-
const frameRe =
|
|
2960
|
+
const frameRe = /^[ \t]+at (?:.*? \()?([^()\s][^()\n]*?):(\d+):(\d+)\)?[ \t]*$/gm;
|
|
2873
2961
|
const frames = [];
|
|
2874
2962
|
let m;
|
|
2875
2963
|
while ((m = frameRe.exec(text)) !== null) {
|
|
@@ -2992,21 +3080,25 @@ function readCallLog(text) {
|
|
|
2992
3080
|
return read;
|
|
2993
3081
|
}
|
|
2994
3082
|
function headerValue(text, label) {
|
|
2995
|
-
const re = new RegExp(
|
|
3083
|
+
const re = new RegExp(`^[ \\t]*${label}(?: string| pattern| substring| value)?:[ \\t]*(.*)$`, "m");
|
|
2996
3084
|
const m = re.exec(text);
|
|
2997
3085
|
if (!m) return null;
|
|
2998
3086
|
const value = m[1].trim();
|
|
2999
3087
|
return value.length > 0 ? value : null;
|
|
3000
3088
|
}
|
|
3089
|
+
function readLocatorHeader(text) {
|
|
3090
|
+
const header = /^[ \t]*Locator:[ \t]*(.+)$/m.exec(text);
|
|
3091
|
+
return header ? header[1].trim() : null;
|
|
3092
|
+
}
|
|
3001
3093
|
function readLocator(text, strict) {
|
|
3002
|
-
const header =
|
|
3003
|
-
if (header) return header
|
|
3094
|
+
const header = readLocatorHeader(text);
|
|
3095
|
+
if (header) return header;
|
|
3004
3096
|
if (strict) return strict[1].trim();
|
|
3005
3097
|
return extractLocatorChain(text);
|
|
3006
3098
|
}
|
|
3007
3099
|
function readTimeout(text, callLog) {
|
|
3008
3100
|
const patterns = [
|
|
3009
|
-
|
|
3101
|
+
/^[ \t]*Timeout:[ \t]*(\d+)ms/m,
|
|
3010
3102
|
/\bTimeout (\d+)ms exceeded/,
|
|
3011
3103
|
TEST_TIMEOUT_RE,
|
|
3012
3104
|
TIMED_OUT_EXPECT_RE,
|
|
@@ -3023,7 +3115,7 @@ function readUrl(text, callLog, received) {
|
|
|
3023
3115
|
const at = /\b(?:net::ERR_[A-Z0-9_]+|NS_ERROR_[A-Z0-9_]+) at (\S+)/.exec(text);
|
|
3024
3116
|
if (at) return at[1];
|
|
3025
3117
|
const head = extractMessageHead(text);
|
|
3026
|
-
const inHead = URL_RE.exec(head.replace(
|
|
3118
|
+
const inHead = URL_RE.exec(head.replace(/^[ \t]*Received.*$/gm, ""));
|
|
3027
3119
|
if (inHead) return inHead[0];
|
|
3028
3120
|
if (received) {
|
|
3029
3121
|
const inReceived = URL_RE.exec(received);
|
|
@@ -3039,8 +3131,9 @@ function readCustomMessage(messageHead) {
|
|
|
3039
3131
|
if (!first || MATCHER_LINE_RE.test(first)) return null;
|
|
3040
3132
|
return /^(?:expect[.(]|Timed out\b)/.test(lines[1]) ? first : null;
|
|
3041
3133
|
}
|
|
3134
|
+
var MAX_PARSED_ERROR_CHARS = 64 * 1024;
|
|
3042
3135
|
function parsePlaywrightError(raw, context) {
|
|
3043
|
-
const clean = stripAnsi(raw ?? "").replace(/\r\n?/g, "\n");
|
|
3136
|
+
const clean = stripAnsi((raw ?? "").slice(0, MAX_PARSED_ERROR_CHARS)).replace(/\r\n?/g, "\n");
|
|
3044
3137
|
const text = withoutStackFrames(clean);
|
|
3045
3138
|
const messageHead = extractMessageHead(clean);
|
|
3046
3139
|
const firstLine2 = messageHead.split("\n")[0] ?? "";
|
|
@@ -3077,7 +3170,7 @@ function parsePlaywrightError(raw, context) {
|
|
|
3077
3170
|
else if (testTimeout) kind = "test-timeout";
|
|
3078
3171
|
else if (CRASH_RE.test(text)) kind = "crash";
|
|
3079
3172
|
else if (isAssertion) {
|
|
3080
|
-
const retrying =
|
|
3173
|
+
const retrying = /^[ \t]*Timeout:[ \t]*\d+ms/m.test(text) || TIMED_OUT_EXPECT_RE.test(text) || callLog.matcher !== null;
|
|
3081
3174
|
kind = retrying ? "assertion-timeout" : "assertion";
|
|
3082
3175
|
} else if (isNavigationFailure) kind = "navigation";
|
|
3083
3176
|
else if (/\bTimeout \d+ms exceeded/.test(text) || errorName === "TimeoutError") kind = "action-timeout";
|
|
@@ -3128,9 +3221,25 @@ var LONG_HEX_RE = /\b[0-9a-f]{32,}\b/gi;
|
|
|
3128
3221
|
function maskTokenLike(text) {
|
|
3129
3222
|
return text.replace(DATA_URI_RE, "data:[masked]").replace(JWT_RE, "[masked-token]").replace(LONG_HEX_RE, "[masked-hex]");
|
|
3130
3223
|
}
|
|
3224
|
+
var CREDENTIAL_PARAM_RE = /token|code|secret|password|passwd|pwd|key|session|auth|sig|credential|assertion|nonce|otp|jwt/i;
|
|
3225
|
+
function maskUrlFragment(hash) {
|
|
3226
|
+
const body = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
3227
|
+
const query = body.indexOf("?");
|
|
3228
|
+
const route = query >= 0 ? body.slice(0, query) : body.includes("=") ? "" : body;
|
|
3229
|
+
const params = query >= 0 ? body.slice(query + 1) : body.includes("=") ? body : null;
|
|
3230
|
+
const masked = params === null ? null : params.split("&").map((param) => {
|
|
3231
|
+
const eq = param.indexOf("=");
|
|
3232
|
+
if (eq < 0) return maskTokenLike(param);
|
|
3233
|
+
const name = param.slice(0, eq);
|
|
3234
|
+
const value = param.slice(eq + 1);
|
|
3235
|
+
return `${name}=${value && CREDENTIAL_PARAM_RE.test(name) ? "[masked]" : maskTokenLike(value)}`;
|
|
3236
|
+
}).join("&");
|
|
3237
|
+
const out = maskTokenLike(route) + (masked === null ? "" : query >= 0 ? `?${masked}` : masked);
|
|
3238
|
+
return hash.startsWith("#") ? `#${out}` : out;
|
|
3239
|
+
}
|
|
3131
3240
|
|
|
3132
3241
|
// ../core/src/locator-break.ts
|
|
3133
|
-
var filePath = (
|
|
3242
|
+
var filePath = (path32) => path32.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3134
3243
|
function sameFilePath(a, b) {
|
|
3135
3244
|
const x = filePath(a);
|
|
3136
3245
|
const y = filePath(b);
|
|
@@ -3559,17 +3668,17 @@ function extractWaitEvents(steps, insideWait = false) {
|
|
|
3559
3668
|
|
|
3560
3669
|
// src/internal/support/reporter-version.ts
|
|
3561
3670
|
var fs9 = __toESM(require("fs"));
|
|
3562
|
-
var
|
|
3671
|
+
var path9 = __toESM(require("path"));
|
|
3563
3672
|
var cachedVersion = null;
|
|
3564
3673
|
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
3565
3674
|
function findOwnPackageJson(fromDir) {
|
|
3566
3675
|
for (const offset of CANDIDATE_OFFSETS) {
|
|
3567
3676
|
try {
|
|
3568
|
-
const pkgPath =
|
|
3677
|
+
const pkgPath = path9.resolve(fromDir, offset, "package.json");
|
|
3569
3678
|
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
3570
3679
|
const { name, version } = pkg;
|
|
3571
3680
|
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
3572
|
-
return { version, root:
|
|
3681
|
+
return { version, root: path9.dirname(pkgPath) };
|
|
3573
3682
|
} catch {
|
|
3574
3683
|
}
|
|
3575
3684
|
}
|
|
@@ -3583,7 +3692,7 @@ function getReporterVersion() {
|
|
|
3583
3692
|
|
|
3584
3693
|
// src/internal/support/source-snippet.ts
|
|
3585
3694
|
var fs10 = __toESM(require("fs"));
|
|
3586
|
-
var
|
|
3695
|
+
var path10 = __toESM(require("path"));
|
|
3587
3696
|
function readSourceSnippet(file, declLine, context, failingLine) {
|
|
3588
3697
|
try {
|
|
3589
3698
|
const content = fs10.readFileSync(file, "utf-8");
|
|
@@ -3605,6 +3714,10 @@ function readSourceSnippet(file, declLine, context, failingLine) {
|
|
|
3605
3714
|
return null;
|
|
3606
3715
|
}
|
|
3607
3716
|
}
|
|
3717
|
+
var MAX_SCANNED_ERROR_CHARS = 64 * 1024;
|
|
3718
|
+
function stackFrames(errorText) {
|
|
3719
|
+
return extractStackFrames(errorText.slice(0, MAX_SCANNED_ERROR_CHARS));
|
|
3720
|
+
}
|
|
3608
3721
|
function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
3609
3722
|
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
3610
3723
|
const context = opts.context ?? 8;
|
|
@@ -3618,23 +3731,20 @@ function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
|
3618
3731
|
picked.push({ absFile, line });
|
|
3619
3732
|
};
|
|
3620
3733
|
if (errorText) {
|
|
3621
|
-
const
|
|
3622
|
-
|
|
3623
|
-
while ((m = stackRe.exec(errorText)) !== null && picked.length < maxFrames) {
|
|
3734
|
+
for (const frame of stackFrames(errorText)) {
|
|
3735
|
+
if (picked.length >= maxFrames) break;
|
|
3624
3736
|
let abs;
|
|
3625
3737
|
try {
|
|
3626
|
-
abs =
|
|
3738
|
+
abs = path10.resolve(frame.file);
|
|
3627
3739
|
} catch {
|
|
3628
3740
|
continue;
|
|
3629
3741
|
}
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
if (
|
|
3633
|
-
const line = parseInt(m[2], 10);
|
|
3634
|
-
if (!isNaN(line)) add(abs, line);
|
|
3742
|
+
const rel = path10.relative(projectRoot, abs);
|
|
3743
|
+
if (rel.startsWith("..") || path10.isAbsolute(rel)) continue;
|
|
3744
|
+
if (!isNaN(frame.line)) add(abs, frame.line);
|
|
3635
3745
|
}
|
|
3636
3746
|
}
|
|
3637
|
-
const absTest =
|
|
3747
|
+
const absTest = path10.resolve(testFile2);
|
|
3638
3748
|
if (!picked.some((p) => p.absFile === absTest)) {
|
|
3639
3749
|
add(absTest, extractFailingLine(errorText, testFile2, declLine));
|
|
3640
3750
|
}
|
|
@@ -3642,23 +3752,17 @@ function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
|
3642
3752
|
for (const p of picked.slice(0, maxFrames)) {
|
|
3643
3753
|
const snippet = readSourceSnippet(p.absFile, p.line, context, p.line);
|
|
3644
3754
|
if (snippet) {
|
|
3645
|
-
frames.push({ file:
|
|
3755
|
+
frames.push({ file: path10.relative(projectRoot, p.absFile) || path10.basename(p.absFile), line: p.line, snippet });
|
|
3646
3756
|
}
|
|
3647
3757
|
}
|
|
3648
3758
|
return frames;
|
|
3649
3759
|
}
|
|
3650
3760
|
function extractFailingLine(errorText, testFile2, declarationLine) {
|
|
3651
3761
|
if (!errorText) return declarationLine;
|
|
3652
|
-
const expectedFile =
|
|
3653
|
-
const
|
|
3654
|
-
let m;
|
|
3655
|
-
while ((m = stackRe.exec(errorText)) !== null) {
|
|
3762
|
+
const expectedFile = path10.resolve(testFile2);
|
|
3763
|
+
for (const frame of stackFrames(errorText)) {
|
|
3656
3764
|
try {
|
|
3657
|
-
|
|
3658
|
-
if (frameFile === expectedFile) {
|
|
3659
|
-
const line = parseInt(m[2], 10);
|
|
3660
|
-
if (!isNaN(line)) return line;
|
|
3661
|
-
}
|
|
3765
|
+
if (path10.resolve(frame.file) === expectedFile && !isNaN(frame.line)) return frame.line;
|
|
3662
3766
|
} catch {
|
|
3663
3767
|
}
|
|
3664
3768
|
}
|
|
@@ -3776,18 +3880,18 @@ function isListMode(argv = process.argv) {
|
|
|
3776
3880
|
}
|
|
3777
3881
|
|
|
3778
3882
|
// src/public/global-setup.ts
|
|
3779
|
-
var
|
|
3883
|
+
var path14 = __toESM(require("path"));
|
|
3780
3884
|
var fs14 = __toESM(require("fs"));
|
|
3781
3885
|
|
|
3782
3886
|
// src/internal/support/aria-sampling.ts
|
|
3783
|
-
var
|
|
3887
|
+
var path11 = __toESM(require("path"));
|
|
3784
3888
|
var os7 = __toESM(require("os"));
|
|
3785
3889
|
var fs11 = __toESM(require("fs"));
|
|
3786
3890
|
function ariaSampleIdentity(filePath2, title) {
|
|
3787
3891
|
return `${filePath2}\0${title}`;
|
|
3788
3892
|
}
|
|
3789
3893
|
function getAriaSampleFilePath(projectName) {
|
|
3790
|
-
return
|
|
3894
|
+
return path11.join(os7.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
3791
3895
|
}
|
|
3792
3896
|
function writeAriaSampleFile(projectName, identities) {
|
|
3793
3897
|
try {
|
|
@@ -3817,15 +3921,14 @@ function loadAriaSampleSet(projectName) {
|
|
|
3817
3921
|
cachedSets.set(projectName, set);
|
|
3818
3922
|
return set;
|
|
3819
3923
|
}
|
|
3820
|
-
function relativeTestFile(file) {
|
|
3821
|
-
return path10.relative(process.cwd(), file).split(path10.sep).join("/");
|
|
3822
|
-
}
|
|
3823
3924
|
function isDueForAriaSample(testInfo) {
|
|
3824
3925
|
const projectName = process.env.PIWI_PROJECT_NAME;
|
|
3825
3926
|
if (!projectName) return false;
|
|
3826
3927
|
const set = loadAriaSampleSet(projectName);
|
|
3827
3928
|
if (!set || set.size === 0) return false;
|
|
3828
|
-
return set.has(
|
|
3929
|
+
return set.has(
|
|
3930
|
+
ariaSampleIdentity(relativeTestPath(testInfo.file, configDirFromConfig(testInfo.config)), testInfo.title)
|
|
3931
|
+
);
|
|
3829
3932
|
}
|
|
3830
3933
|
|
|
3831
3934
|
// src/internal/support/base-url-check.ts
|
|
@@ -3962,7 +4065,7 @@ async function checkBaseUrls(config, options = {}) {
|
|
|
3962
4065
|
|
|
3963
4066
|
// src/internal/manifest/collect.ts
|
|
3964
4067
|
var fs12 = __toESM(require("fs"));
|
|
3965
|
-
var
|
|
4068
|
+
var path12 = __toESM(require("path"));
|
|
3966
4069
|
|
|
3967
4070
|
// src/internal/transport/backend-request.ts
|
|
3968
4071
|
var http2 = __toESM(require("http"));
|
|
@@ -4028,7 +4131,7 @@ function hasInstrumentationHeader(headers) {
|
|
|
4028
4131
|
return INSTRUMENTATION_HEADERS.some((h) => get(h) != null && get(h) !== "");
|
|
4029
4132
|
}
|
|
4030
4133
|
function readCommittedManifest(configDir) {
|
|
4031
|
-
const file =
|
|
4134
|
+
const file = path12.join(configDir, COMMITTED_MANIFEST_FILE);
|
|
4032
4135
|
try {
|
|
4033
4136
|
if (!fs12.existsSync(file)) return null;
|
|
4034
4137
|
return parseManifestJson(fs12.readFileSync(file, "utf8"));
|
|
@@ -4036,11 +4139,6 @@ function readCommittedManifest(configDir) {
|
|
|
4036
4139
|
return null;
|
|
4037
4140
|
}
|
|
4038
4141
|
}
|
|
4039
|
-
function configDirFromConfig(config) {
|
|
4040
|
-
if (config?.configFile) return path11.dirname(config.configFile);
|
|
4041
|
-
if (config?.rootDir) return config.rootDir;
|
|
4042
|
-
return process.cwd();
|
|
4043
|
-
}
|
|
4044
4142
|
var INSTRUMENTATION_FETCH_TIMEOUT_MS = 5e3;
|
|
4045
4143
|
var MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
|
|
4046
4144
|
function backendManifestFetch(insecure) {
|
|
@@ -4062,8 +4160,8 @@ function backendManifestFetch(insecure) {
|
|
|
4062
4160
|
function instrumentationManifestUrls(baseUrl) {
|
|
4063
4161
|
const base = new URL(baseUrl);
|
|
4064
4162
|
const root = new URL(INSTRUMENTATION_MANIFEST_PATH, base).toString();
|
|
4065
|
-
const
|
|
4066
|
-
return
|
|
4163
|
+
const path32 = base.pathname.replace(/\/+$/, "");
|
|
4164
|
+
return path32 ? [new URL(`${path32}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
|
|
4067
4165
|
}
|
|
4068
4166
|
async function fetchInstrumentationManifest(baseUrl, fetchImpl = fetch) {
|
|
4069
4167
|
try {
|
|
@@ -4201,13 +4299,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
4201
4299
|
}
|
|
4202
4300
|
function normalizeCoveragePath(raw) {
|
|
4203
4301
|
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
4204
|
-
const
|
|
4205
|
-
if (!
|
|
4206
|
-
if (/(^|\/)node_modules\//.test(
|
|
4207
|
-
return
|
|
4302
|
+
const path32 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4303
|
+
if (!path32 || path32.startsWith("../") || path32.startsWith("/") || /^[A-Za-z]:\//.test(path32)) return null;
|
|
4304
|
+
if (/(^|\/)node_modules\//.test(path32)) return null;
|
|
4305
|
+
return path32;
|
|
4208
4306
|
}
|
|
4209
|
-
function fileEntry(
|
|
4210
|
-
const file = { path:
|
|
4307
|
+
function fileEntry(path32, runtime, blob, functions = [], lines = []) {
|
|
4308
|
+
const file = { path: path32, runtime };
|
|
4211
4309
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
4212
4310
|
if (functions.length > 0) file.functions = functions;
|
|
4213
4311
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -4226,11 +4324,11 @@ function sanitizeCoverage(raw) {
|
|
|
4226
4324
|
const files = [];
|
|
4227
4325
|
for (const item of raw.files) {
|
|
4228
4326
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
4229
|
-
const
|
|
4230
|
-
if (
|
|
4327
|
+
const path32 = normalizeCoveragePath(item.path);
|
|
4328
|
+
if (path32 === null) continue;
|
|
4231
4329
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
4232
4330
|
files.push(
|
|
4233
|
-
fileEntry(
|
|
4331
|
+
fileEntry(path32, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
4234
4332
|
);
|
|
4235
4333
|
}
|
|
4236
4334
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -4294,9 +4392,9 @@ function reduceCoverage(capture, granularity) {
|
|
|
4294
4392
|
}
|
|
4295
4393
|
|
|
4296
4394
|
// src/internal/collect/source-checksums.ts
|
|
4297
|
-
var
|
|
4395
|
+
var crypto4 = __toESM(require("crypto"));
|
|
4298
4396
|
var fs13 = __toESM(require("fs"));
|
|
4299
|
-
var
|
|
4397
|
+
var path13 = __toESM(require("path"));
|
|
4300
4398
|
var CHECKSUM = /^(sha1|sha256):[0-9a-f]{40,64}$/;
|
|
4301
4399
|
function checksumsOf(raw) {
|
|
4302
4400
|
if (!Array.isArray(raw)) return [];
|
|
@@ -4312,7 +4410,7 @@ function contentChecksums(content) {
|
|
|
4312
4410
|
const out = /* @__PURE__ */ new Set();
|
|
4313
4411
|
for (const form of lineEndingForms(content)) {
|
|
4314
4412
|
for (const algorithm of ["sha1", "sha256"]) {
|
|
4315
|
-
out.add(`${algorithm}:${
|
|
4413
|
+
out.add(`${algorithm}:${crypto4.createHash(algorithm).update(form).digest("hex")}`);
|
|
4316
4414
|
}
|
|
4317
4415
|
}
|
|
4318
4416
|
return out;
|
|
@@ -4361,9 +4459,9 @@ var SourceChecksums = class {
|
|
|
4361
4459
|
}
|
|
4362
4460
|
async read(relativePath2) {
|
|
4363
4461
|
try {
|
|
4364
|
-
const root =
|
|
4365
|
-
const file =
|
|
4366
|
-
if (!file.startsWith(root +
|
|
4462
|
+
const root = path13.resolve(this.repoRoot());
|
|
4463
|
+
const file = path13.resolve(root, relativePath2);
|
|
4464
|
+
if (!file.startsWith(root + path13.sep)) return null;
|
|
4367
4465
|
return contentChecksums(await fs13.promises.readFile(file));
|
|
4368
4466
|
} catch {
|
|
4369
4467
|
return null;
|
|
@@ -4771,14 +4869,14 @@ var ServerCoverageCollector = class {
|
|
|
4771
4869
|
// src/public/global-setup.ts
|
|
4772
4870
|
function createGlobalSetup(options, userSetup) {
|
|
4773
4871
|
return async function globalSetupFn(config) {
|
|
4774
|
-
const piwiReporterPath =
|
|
4872
|
+
const piwiReporterPath = path14.resolve(__dirname, "./index.js");
|
|
4775
4873
|
let inlineReporterOptions = {};
|
|
4776
4874
|
if (Array.isArray(config?.reporter)) {
|
|
4777
4875
|
for (const r of config.reporter) {
|
|
4778
4876
|
if (!Array.isArray(r) || typeof r[0] !== "string") continue;
|
|
4779
4877
|
const isPiwi = r[0].toLowerCase().includes("piwi") || (() => {
|
|
4780
4878
|
try {
|
|
4781
|
-
return
|
|
4879
|
+
return path14.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
4782
4880
|
} catch {
|
|
4783
4881
|
return false;
|
|
4784
4882
|
}
|
|
@@ -4814,7 +4912,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
4814
4912
|
if (!Array.isArray(r) || typeof r[0] !== "string") return false;
|
|
4815
4913
|
if (r[0].toLowerCase().includes("piwi")) return true;
|
|
4816
4914
|
try {
|
|
4817
|
-
return
|
|
4915
|
+
return path14.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
4818
4916
|
} catch {
|
|
4819
4917
|
return false;
|
|
4820
4918
|
}
|
|
@@ -4898,12 +4996,11 @@ function createGlobalSetup(options, userSetup) {
|
|
|
4898
4996
|
}
|
|
4899
4997
|
|
|
4900
4998
|
// src/public/config-wrapper.ts
|
|
4901
|
-
var
|
|
4999
|
+
var path15 = __toESM(require("path"));
|
|
4902
5000
|
var fs17 = __toESM(require("fs"));
|
|
4903
5001
|
|
|
4904
5002
|
// src/internal/probe/mode.ts
|
|
4905
5003
|
var fs15 = __toESM(require("fs"));
|
|
4906
|
-
var path14 = __toESM(require("path"));
|
|
4907
5004
|
|
|
4908
5005
|
// src/internal/probe/plan.ts
|
|
4909
5006
|
function probeRunMetadata(base = {}) {
|
|
@@ -4915,8 +5012,8 @@ function parseRouteKey(routeKey2) {
|
|
|
4915
5012
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
4916
5013
|
}
|
|
4917
5014
|
function patternToRegex(pattern) {
|
|
4918
|
-
const
|
|
4919
|
-
const escaped =
|
|
5015
|
+
const path32 = pattern.split("?")[0] ?? pattern;
|
|
5016
|
+
const escaped = path32.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
4920
5017
|
return new RegExp(`^${escaped}$`);
|
|
4921
5018
|
}
|
|
4922
5019
|
function suitePathEqual(a, b) {
|
|
@@ -4939,13 +5036,13 @@ function matchProbeItem(plan, test) {
|
|
|
4939
5036
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
4940
5037
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
4941
5038
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
4942
|
-
let
|
|
5039
|
+
let path32;
|
|
4943
5040
|
try {
|
|
4944
|
-
|
|
5041
|
+
path32 = new URL(url).pathname;
|
|
4945
5042
|
} catch {
|
|
4946
|
-
|
|
5043
|
+
path32 = url.split("?")[0] ?? url;
|
|
4947
5044
|
}
|
|
4948
|
-
return patternToRegex(pattern).test(
|
|
5045
|
+
return patternToRegex(pattern).test(path32);
|
|
4949
5046
|
}
|
|
4950
5047
|
function createProbeState() {
|
|
4951
5048
|
return { navigated: false, matches: 0 };
|
|
@@ -4967,10 +5064,10 @@ function isProbeMode() {
|
|
|
4967
5064
|
function loadPlan() {
|
|
4968
5065
|
if (planCache !== void 0) return planCache;
|
|
4969
5066
|
planCache = null;
|
|
4970
|
-
const
|
|
4971
|
-
if (
|
|
5067
|
+
const path32 = process.env[PIWI_PROBE_ENV.plan];
|
|
5068
|
+
if (path32) {
|
|
4972
5069
|
try {
|
|
4973
|
-
planCache = JSON.parse(fs15.readFileSync(
|
|
5070
|
+
planCache = JSON.parse(fs15.readFileSync(path32, "utf8"));
|
|
4974
5071
|
} catch {
|
|
4975
5072
|
planCache = null;
|
|
4976
5073
|
}
|
|
@@ -4980,7 +5077,7 @@ function loadPlan() {
|
|
|
4980
5077
|
function probeItemForTest(test) {
|
|
4981
5078
|
const plan = loadPlan();
|
|
4982
5079
|
if (!plan) return null;
|
|
4983
|
-
const filePath2 = test.file ?
|
|
5080
|
+
const filePath2 = test.file ? relativeTestPath(test.file, test.base ?? process.cwd()) : null;
|
|
4984
5081
|
const suitePath = test.titlePath ? test.titlePath.slice(1, -1) : void 0;
|
|
4985
5082
|
return matchProbeItem(plan, { title: test.title, filePath: filePath2, suitePath });
|
|
4986
5083
|
}
|
|
@@ -4994,10 +5091,10 @@ function outcomeFromStatus(status, applied) {
|
|
|
4994
5091
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
4995
5092
|
}
|
|
4996
5093
|
function recordProbeOutcome(line) {
|
|
4997
|
-
const
|
|
4998
|
-
if (!
|
|
5094
|
+
const path32 = process.env[PIWI_PROBE_ENV.results];
|
|
5095
|
+
if (!path32) return;
|
|
4999
5096
|
try {
|
|
5000
|
-
fs15.appendFileSync(
|
|
5097
|
+
fs15.appendFileSync(path32, `${JSON.stringify(line)}
|
|
5001
5098
|
`);
|
|
5002
5099
|
} catch {
|
|
5003
5100
|
}
|
|
@@ -5005,7 +5102,6 @@ function recordProbeOutcome(line) {
|
|
|
5005
5102
|
|
|
5006
5103
|
// src/internal/flake/mode.ts
|
|
5007
5104
|
var fs16 = __toESM(require("fs"));
|
|
5008
|
-
var path15 = __toESM(require("path"));
|
|
5009
5105
|
|
|
5010
5106
|
// ../core/src/error-signature.ts
|
|
5011
5107
|
var UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
|
|
@@ -5525,12 +5621,12 @@ function flakeRunMetadata(stamp, base = {}) {
|
|
|
5525
5621
|
[RUN_ORIGIN_METADATA_KEY]: ref === void 0 ? { kind: "flake-lab" } : { kind: "flake-lab", ref }
|
|
5526
5622
|
};
|
|
5527
5623
|
}
|
|
5528
|
-
function
|
|
5529
|
-
return file ?
|
|
5624
|
+
function relativeTestFile(file, base = process.cwd()) {
|
|
5625
|
+
return file ? relativeTestPath(file, base) : null;
|
|
5530
5626
|
}
|
|
5531
5627
|
function flakeRoleForTest(plan, test) {
|
|
5532
5628
|
const running = {
|
|
5533
|
-
file:
|
|
5629
|
+
file: relativeTestFile(test.file, test.base),
|
|
5534
5630
|
title: test.title,
|
|
5535
5631
|
suite: test.titlePath ? test.titlePath.slice(1, -1) : void 0
|
|
5536
5632
|
};
|
|
@@ -5646,7 +5742,7 @@ function flakeResultLine(plan, attempt) {
|
|
|
5646
5742
|
experimentId: plan.experimentId,
|
|
5647
5743
|
armId: plan.arm.id,
|
|
5648
5744
|
role: attempt.role,
|
|
5649
|
-
file:
|
|
5745
|
+
file: relativeTestFile(attempt.file, attempt.base),
|
|
5650
5746
|
title: attempt.title,
|
|
5651
5747
|
project: attempt.project,
|
|
5652
5748
|
browserName: attempt.browserName,
|
|
@@ -5737,9 +5833,9 @@ function injectReporter(reporter, piwiOptions) {
|
|
|
5737
5833
|
}
|
|
5738
5834
|
function resolveSetupModule() {
|
|
5739
5835
|
const candidates = [
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5836
|
+
path15.join(__dirname, "global-setup-module.js"),
|
|
5837
|
+
path15.join(__dirname, "..", "global-setup-module.js"),
|
|
5838
|
+
path15.join(__dirname, "..", "global-setup-module.ts")
|
|
5743
5839
|
];
|
|
5744
5840
|
return candidates.find((candidate) => fs17.existsSync(candidate)) ?? candidates[0];
|
|
5745
5841
|
}
|
|
@@ -5949,9 +6045,6 @@ function collectTestMetadata(annotations) {
|
|
|
5949
6045
|
return parseTestMetadata(annotations);
|
|
5950
6046
|
}
|
|
5951
6047
|
|
|
5952
|
-
// src/internal/collect/error-text.ts
|
|
5953
|
-
var path17 = __toESM(require("path"));
|
|
5954
|
-
|
|
5955
6048
|
// ../core/src/error-text.ts
|
|
5956
6049
|
function joinErrorMessages(errors) {
|
|
5957
6050
|
if (!errors || errors.length === 0) return null;
|
|
@@ -5968,24 +6061,26 @@ function joinErrorMessages(errors) {
|
|
|
5968
6061
|
return parts.join("\n---\n") || null;
|
|
5969
6062
|
}
|
|
5970
6063
|
function appendErrorLocation(text, location) {
|
|
5971
|
-
if (!location?.file || /\n\
|
|
6064
|
+
if (!location?.file || /\n[ \t]+at\s/.test(text)) return text;
|
|
5972
6065
|
return `${text}
|
|
5973
6066
|
at ${location.file}:${location.line}:${location.column}`;
|
|
5974
6067
|
}
|
|
5975
6068
|
|
|
5976
6069
|
// src/internal/collect/error-text.ts
|
|
5977
|
-
function buildErrorText(result) {
|
|
6070
|
+
function buildErrorText(result, base = process.cwd()) {
|
|
5978
6071
|
const text = joinErrorMessages(result.errors);
|
|
5979
6072
|
if (!text) return null;
|
|
5980
6073
|
const loc = result.error?.location;
|
|
5981
6074
|
if (!loc?.file) return text;
|
|
5982
|
-
|
|
5983
|
-
return appendErrorLocation(text, { file: rel, line: loc.line, column: loc.column });
|
|
6075
|
+
return appendErrorLocation(text, { file: relativeTestPath(loc.file, base), line: loc.line, column: loc.column });
|
|
5984
6076
|
}
|
|
5985
6077
|
|
|
6078
|
+
// src/internal/submit/run-submitter.ts
|
|
6079
|
+
var import_node_crypto2 = require("crypto");
|
|
6080
|
+
|
|
5986
6081
|
// src/internal/support/ci-output.ts
|
|
5987
6082
|
var fs18 = __toESM(require("fs"));
|
|
5988
|
-
var
|
|
6083
|
+
var path16 = __toESM(require("path"));
|
|
5989
6084
|
var SUMMARY_MAX_FAILURES = 20;
|
|
5990
6085
|
function emitRunOutputs(output, logger, outputFile, env = process.env) {
|
|
5991
6086
|
logger.info(`View run: ${output.runUrl}`);
|
|
@@ -5995,7 +6090,7 @@ function emitRunOutputs(output, logger, outputFile, env = process.env) {
|
|
|
5995
6090
|
}
|
|
5996
6091
|
function writeOutputFile(file, output, logger) {
|
|
5997
6092
|
try {
|
|
5998
|
-
const dir =
|
|
6093
|
+
const dir = path16.dirname(file);
|
|
5999
6094
|
if (dir && dir !== ".") fs18.mkdirSync(dir, { recursive: true });
|
|
6000
6095
|
fs18.writeFileSync(
|
|
6001
6096
|
file,
|
|
@@ -6107,8 +6202,8 @@ function ciBuildUrlFromMetadata(metadata) {
|
|
|
6107
6202
|
|
|
6108
6203
|
// src/internal/submit/run-submitter.ts
|
|
6109
6204
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
6110
|
-
function markSubmitFallback(run,
|
|
6111
|
-
run.metadata.ingestHealth = { submitFallback: { path:
|
|
6205
|
+
function markSubmitFallback(run, path32, reason) {
|
|
6206
|
+
run.metadata.ingestHealth = { submitFallback: { path: path32, reason } };
|
|
6112
6207
|
}
|
|
6113
6208
|
var RunSubmitter = class {
|
|
6114
6209
|
/**
|
|
@@ -6126,6 +6221,8 @@ var RunSubmitter = class {
|
|
|
6126
6221
|
this.streamManager = streamManager;
|
|
6127
6222
|
this.logger = logger;
|
|
6128
6223
|
this.failureLinks = failureLinks;
|
|
6224
|
+
/** The key every batch delivery of this run carries, so the dashboard stores the run once. */
|
|
6225
|
+
this.deliveryKey = (0, import_node_crypto2.randomUUID)();
|
|
6129
6226
|
}
|
|
6130
6227
|
/**
|
|
6131
6228
|
* Run the fallback ladder for a completed test run, within the
|
|
@@ -6181,8 +6278,9 @@ var RunSubmitter = class {
|
|
|
6181
6278
|
auth = sm?.auth ?? await this.httpClient.resolveAuth(run.options);
|
|
6182
6279
|
} catch (error) {
|
|
6183
6280
|
this.logger.error(`Authentication failed: ${errorMessage(error)}`);
|
|
6281
|
+
this.logRecoverySaved();
|
|
6184
6282
|
this.saveRecovery(this.buildRunPayload(run, overallStatus, duration));
|
|
6185
|
-
|
|
6283
|
+
return null;
|
|
6186
6284
|
}
|
|
6187
6285
|
if (!sm) await this.recovery.tryUpload(this.httpClient, auth);
|
|
6188
6286
|
let outcome = { done: false, output: null };
|
|
@@ -6256,7 +6354,8 @@ var RunSubmitter = class {
|
|
|
6256
6354
|
shardTotal: run.shardInfo?.total,
|
|
6257
6355
|
isFullRun: run.isFullRun,
|
|
6258
6356
|
filterDetails: run.filterDetails,
|
|
6259
|
-
resourceReport: run.resourceReport ?? null
|
|
6357
|
+
resourceReport: run.resourceReport ?? null,
|
|
6358
|
+
deliveryKey: this.deliveryKey
|
|
6260
6359
|
};
|
|
6261
6360
|
}
|
|
6262
6361
|
async tryFinishStreaming(run, overallStatus, duration, auth) {
|
|
@@ -6322,11 +6421,7 @@ var RunSubmitter = class {
|
|
|
6322
6421
|
this.dropLocalCopies();
|
|
6323
6422
|
return { done: true, output: this.buildOutput(response?.runId, response?.projectId, run, overallStatus) };
|
|
6324
6423
|
} catch (error) {
|
|
6325
|
-
if (error instanceof HttpError && error.status === 401 && !auth)
|
|
6326
|
-
this.logAuthRequired(run.options.serverUrl);
|
|
6327
|
-
this.saveRecovery(payload);
|
|
6328
|
-
throw error;
|
|
6329
|
-
}
|
|
6424
|
+
if (error instanceof HttpError && error.status === 401 && !auth) return this.stopForMissingAuth(run, payload);
|
|
6330
6425
|
this.logger.warn(`Failed to upload with files: ${errorMessage(error)}`);
|
|
6331
6426
|
this.logger.info("Falling back to JSON upload...");
|
|
6332
6427
|
return { done: false, output: null };
|
|
@@ -6339,22 +6434,20 @@ var RunSubmitter = class {
|
|
|
6339
6434
|
this.dropLocalCopies();
|
|
6340
6435
|
return { done: true, output: this.buildOutput(response?.runId, response?.projectId, run, overallStatus) };
|
|
6341
6436
|
} catch (error) {
|
|
6342
|
-
if (error instanceof HttpError && error.status === 401 && !auth)
|
|
6343
|
-
this.logAuthRequired(run.options.serverUrl);
|
|
6344
|
-
this.saveRecovery(payload);
|
|
6345
|
-
throw error;
|
|
6346
|
-
}
|
|
6437
|
+
if (error instanceof HttpError && error.status === 401 && !auth) return this.stopForMissingAuth(run, payload);
|
|
6347
6438
|
this.logger.error(`All upload methods failed: ${errorMessage(error)}`);
|
|
6348
|
-
this.
|
|
6349
|
-
`
|
|
6439
|
+
this.logRecoverySaved(
|
|
6440
|
+
`If this keeps happening, check that serverUrl (${run.options.serverUrl ?? "not set"}) is correct and reachable.`
|
|
6350
6441
|
);
|
|
6351
6442
|
this.saveRecovery(payload);
|
|
6352
6443
|
return { done: true, output: null };
|
|
6353
6444
|
}
|
|
6354
6445
|
}
|
|
6355
|
-
/**
|
|
6446
|
+
/**
|
|
6447
|
+
* The run reached the server: drop its buffered live events. A recovery file
|
|
6448
|
+
* on disk is an earlier run's, deleted only once its own replay succeeds.
|
|
6449
|
+
*/
|
|
6356
6450
|
dropLocalCopies() {
|
|
6357
|
-
this.recovery.clear();
|
|
6358
6451
|
this.streamManager?.discardBuffered();
|
|
6359
6452
|
}
|
|
6360
6453
|
/**
|
|
@@ -6364,22 +6457,35 @@ var RunSubmitter = class {
|
|
|
6364
6457
|
saveRecovery(payload) {
|
|
6365
6458
|
this.recovery.save(serializeRun(payload, { includeTestCases: true }));
|
|
6366
6459
|
}
|
|
6367
|
-
/**
|
|
6368
|
-
|
|
6460
|
+
/**
|
|
6461
|
+
* The dashboard asked for a credential the reporter has none of: no other
|
|
6462
|
+
* rung can succeed, so the ladder stops with the run saved for the next one.
|
|
6463
|
+
* The test run's own result stays what Playwright made it.
|
|
6464
|
+
*/
|
|
6465
|
+
stopForMissingAuth(run, payload) {
|
|
6369
6466
|
this.logger.error(
|
|
6370
|
-
`Authentication is required by ${serverUrl ?? "the dashboard"} but no credentials were configured. Create an API key (Settings \u2192 Users on the dashboard) and set the reporter's \`apiKey\` option or the PIWI_API_KEY environment variable.`
|
|
6467
|
+
`Authentication is required by ${run.options.serverUrl ?? "the dashboard"} but no credentials were configured. Create an API key (Settings \u2192 Users on the dashboard) and set the reporter's \`apiKey\` option or the PIWI_API_KEY environment variable.`
|
|
6468
|
+
);
|
|
6469
|
+
this.logRecoverySaved();
|
|
6470
|
+
this.saveRecovery(payload);
|
|
6471
|
+
return { done: true, output: null };
|
|
6472
|
+
}
|
|
6473
|
+
/** Say where the run went when the dashboard did not take it, with an optional hint. */
|
|
6474
|
+
logRecoverySaved(hint) {
|
|
6475
|
+
this.logger.info(
|
|
6476
|
+
`Saved a local recovery copy \u2014 it will be uploaded automatically on your next test run.${hint ? ` ${hint}` : ""}`
|
|
6371
6477
|
);
|
|
6372
6478
|
}
|
|
6373
6479
|
};
|
|
6374
6480
|
|
|
6375
6481
|
// src/internal/capture/resource-ledger.ts
|
|
6376
6482
|
var fs20 = __toESM(require("fs"));
|
|
6377
|
-
var
|
|
6483
|
+
var path19 = __toESM(require("path"));
|
|
6378
6484
|
var import_node_module2 = require("module");
|
|
6379
6485
|
|
|
6380
6486
|
// src/internal/capture/quiet-capture.ts
|
|
6381
6487
|
var import_node_module = require("module");
|
|
6382
|
-
var
|
|
6488
|
+
var path17 = __toESM(require("path"));
|
|
6383
6489
|
var BOXED_PREFIX_EXPORTS = [
|
|
6384
6490
|
[
|
|
6385
6491
|
"playwright-core/lib/coreBundle",
|
|
@@ -6416,7 +6522,7 @@ function resolveBoxing() {
|
|
|
6416
6522
|
if (typeof set !== "function") continue;
|
|
6417
6523
|
return {
|
|
6418
6524
|
set,
|
|
6419
|
-
prefixes: [
|
|
6525
|
+
prefixes: [path17.dirname(playwrightPackageJson), path17.join(root, "dist") + path17.sep]
|
|
6420
6526
|
};
|
|
6421
6527
|
}
|
|
6422
6528
|
} catch {
|
|
@@ -6449,7 +6555,7 @@ var import_node_perf_hooks = require("perf_hooks");
|
|
|
6449
6555
|
|
|
6450
6556
|
// src/internal/support/system-readers.ts
|
|
6451
6557
|
var fs19 = __toESM(require("fs"));
|
|
6452
|
-
var
|
|
6558
|
+
var path18 = __toESM(require("path"));
|
|
6453
6559
|
var CLK_TCK = 100;
|
|
6454
6560
|
function readText(file) {
|
|
6455
6561
|
try {
|
|
@@ -6587,7 +6693,7 @@ var ProcFs = class {
|
|
|
6587
6693
|
}
|
|
6588
6694
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
6589
6695
|
available() {
|
|
6590
|
-
return fs19.existsSync(
|
|
6696
|
+
return fs19.existsSync(path18.join(this.root, "stat"));
|
|
6591
6697
|
}
|
|
6592
6698
|
/** Every process, keyed by pid. */
|
|
6593
6699
|
table() {
|
|
@@ -6600,32 +6706,32 @@ var ProcFs = class {
|
|
|
6600
6706
|
}
|
|
6601
6707
|
for (const name of names) {
|
|
6602
6708
|
if (!/^\d+$/.test(name)) continue;
|
|
6603
|
-
const text = readText(
|
|
6709
|
+
const text = readText(path18.join(this.root, name, "stat"));
|
|
6604
6710
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
6605
6711
|
if (stat) out.set(stat.pid, stat);
|
|
6606
6712
|
}
|
|
6607
6713
|
return out;
|
|
6608
6714
|
}
|
|
6609
6715
|
stat(pid) {
|
|
6610
|
-
const text = readText(
|
|
6716
|
+
const text = readText(path18.join(this.root, String(pid), "stat"));
|
|
6611
6717
|
return text ? parseProcStat(pid, text) : null;
|
|
6612
6718
|
}
|
|
6613
6719
|
runWaitMs(pid) {
|
|
6614
|
-
const text = readText(
|
|
6720
|
+
const text = readText(path18.join(this.root, String(pid), "schedstat"));
|
|
6615
6721
|
return text ? parseSchedstat(text) : null;
|
|
6616
6722
|
}
|
|
6617
6723
|
cmdline(pid) {
|
|
6618
|
-
return (readText(
|
|
6724
|
+
return (readText(path18.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
6619
6725
|
}
|
|
6620
6726
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
6621
6727
|
statusKb(pid, key) {
|
|
6622
|
-
const text = readText(
|
|
6728
|
+
const text = readText(path18.join(this.root, String(pid), "status"));
|
|
6623
6729
|
return text ? parseKbField(text, key) : null;
|
|
6624
6730
|
}
|
|
6625
6731
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
6626
6732
|
async pssKb(pid) {
|
|
6627
6733
|
try {
|
|
6628
|
-
return parseKbField(await fs19.promises.readFile(
|
|
6734
|
+
return parseKbField(await fs19.promises.readFile(path18.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
6629
6735
|
} catch {
|
|
6630
6736
|
return null;
|
|
6631
6737
|
}
|
|
@@ -6633,29 +6739,29 @@ var ProcFs = class {
|
|
|
6633
6739
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
6634
6740
|
resetPeakRss(pid) {
|
|
6635
6741
|
try {
|
|
6636
|
-
fs19.writeFileSync(
|
|
6742
|
+
fs19.writeFileSync(path18.join(this.root, String(pid), "clear_refs"), "5");
|
|
6637
6743
|
return true;
|
|
6638
6744
|
} catch {
|
|
6639
6745
|
return false;
|
|
6640
6746
|
}
|
|
6641
6747
|
}
|
|
6642
6748
|
cpuTicks() {
|
|
6643
|
-
const text = readText(
|
|
6749
|
+
const text = readText(path18.join(this.root, "stat"));
|
|
6644
6750
|
return text ? parseCpuTicks(text) : null;
|
|
6645
6751
|
}
|
|
6646
6752
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
6647
6753
|
pressureUs(resource) {
|
|
6648
|
-
const text = readText(
|
|
6754
|
+
const text = readText(path18.join(this.root, "pressure", resource));
|
|
6649
6755
|
return text ? parsePsiTotal(text) : null;
|
|
6650
6756
|
}
|
|
6651
6757
|
memAvailableKb() {
|
|
6652
|
-
const text = readText(
|
|
6758
|
+
const text = readText(path18.join(this.root, "meminfo"));
|
|
6653
6759
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
6654
6760
|
}
|
|
6655
6761
|
/** Open file descriptors of a process. */
|
|
6656
6762
|
fdCount(pid) {
|
|
6657
6763
|
try {
|
|
6658
|
-
return fs19.readdirSync(
|
|
6764
|
+
return fs19.readdirSync(path18.join(this.root, String(pid), "fd")).length;
|
|
6659
6765
|
} catch {
|
|
6660
6766
|
return null;
|
|
6661
6767
|
}
|
|
@@ -6676,7 +6782,7 @@ var CgroupFs = class {
|
|
|
6676
6782
|
}
|
|
6677
6783
|
/** The directory of a controller (v1) or of the unified hierarchy (v2), when it exists. */
|
|
6678
6784
|
dirs() {
|
|
6679
|
-
const text = readText(
|
|
6785
|
+
const text = readText(path18.join(this.procRoot, String(this.pid), "cgroup"));
|
|
6680
6786
|
if (!text) return null;
|
|
6681
6787
|
const v1 = /* @__PURE__ */ new Map();
|
|
6682
6788
|
let v2 = null;
|
|
@@ -6687,21 +6793,21 @@ var CgroupFs = class {
|
|
|
6687
6793
|
else for (const controller of match[1].split(",")) v1.set(controller, match[2]);
|
|
6688
6794
|
}
|
|
6689
6795
|
const pick = (mount, sub) => {
|
|
6690
|
-
const nested =
|
|
6796
|
+
const nested = path18.join(mount, sub);
|
|
6691
6797
|
if (fs19.existsSync(nested)) return nested;
|
|
6692
6798
|
return fs19.existsSync(mount) ? mount : null;
|
|
6693
6799
|
};
|
|
6694
6800
|
if (v1.has("memory") || v1.has("cpu")) {
|
|
6695
6801
|
const memoryPath = v1.get("memory");
|
|
6696
6802
|
const cpuPath = v1.get("cpu");
|
|
6697
|
-
const cpuMount = ["cpu,cpuacct", "cpu"].map((name) =>
|
|
6803
|
+
const cpuMount = ["cpu,cpuacct", "cpu"].map((name) => path18.join(this.cgroupRoot, name)).find((dir) => fs19.existsSync(dir));
|
|
6698
6804
|
return {
|
|
6699
6805
|
version: 1,
|
|
6700
|
-
memory: memoryPath !== void 0 ? pick(
|
|
6806
|
+
memory: memoryPath !== void 0 ? pick(path18.join(this.cgroupRoot, "memory"), memoryPath) : null,
|
|
6701
6807
|
cpu: cpuPath !== void 0 && cpuMount ? pick(cpuMount, cpuPath) : null
|
|
6702
6808
|
};
|
|
6703
6809
|
}
|
|
6704
|
-
if (v2 !== null && fs19.existsSync(
|
|
6810
|
+
if (v2 !== null && fs19.existsSync(path18.join(this.cgroupRoot, "cgroup.controllers"))) {
|
|
6705
6811
|
const dir = pick(this.cgroupRoot, v2);
|
|
6706
6812
|
return { version: 2, memory: dir, cpu: dir };
|
|
6707
6813
|
}
|
|
@@ -6710,7 +6816,7 @@ var CgroupFs = class {
|
|
|
6710
6816
|
read() {
|
|
6711
6817
|
const dirs = this.dirs();
|
|
6712
6818
|
if (!dirs) return null;
|
|
6713
|
-
const file = (dir, name) => dir ? readText(
|
|
6819
|
+
const file = (dir, name) => dir ? readText(path18.join(dir, name)) : null;
|
|
6714
6820
|
if (dirs.version === 2) {
|
|
6715
6821
|
const max = file(dirs.memory, "memory.max");
|
|
6716
6822
|
const cpuMax = file(dirs.cpu, "cpu.max")?.trim().split(/\s+/);
|
|
@@ -6896,10 +7002,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
6896
7002
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
6897
7003
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
6898
7004
|
function relativePath(file) {
|
|
6899
|
-
return
|
|
7005
|
+
return path19.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path19.sep).join("/");
|
|
6900
7006
|
}
|
|
6901
7007
|
function isLibraryFile(file) {
|
|
6902
|
-
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${
|
|
7008
|
+
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path19.sep}node_modules${path19.sep}`) || file.includes("/node_modules/") || file === __filename;
|
|
6903
7009
|
}
|
|
6904
7010
|
function userSite(stack) {
|
|
6905
7011
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -6947,7 +7053,7 @@ function playwrightTestInfoSource() {
|
|
|
6947
7053
|
try {
|
|
6948
7054
|
const packageJson = resolvePlaywrightPackageJson();
|
|
6949
7055
|
if (!packageJson) return () => null;
|
|
6950
|
-
const globals = (0, import_node_module2.createRequire)(packageJson)(
|
|
7056
|
+
const globals = (0, import_node_module2.createRequire)(packageJson)(path19.join(path19.dirname(packageJson), "lib", "globals.js"));
|
|
6951
7057
|
const read = globals.currentTestInfo;
|
|
6952
7058
|
if (typeof read !== "function") return () => null;
|
|
6953
7059
|
return () => {
|
|
@@ -7436,7 +7542,7 @@ async function recordResourceCensus(testInfo) {
|
|
|
7436
7542
|
}
|
|
7437
7543
|
|
|
7438
7544
|
// src/internal/collect/resource-verdicts.ts
|
|
7439
|
-
var
|
|
7545
|
+
var path20 = __toESM(require("path"));
|
|
7440
7546
|
var OPEN_STEP_TITLES = {
|
|
7441
7547
|
"Create context": "context",
|
|
7442
7548
|
"Launch persistent context": "context",
|
|
@@ -7468,7 +7574,7 @@ function userFixturesOf(steps) {
|
|
|
7468
7574
|
const walk = (list) => {
|
|
7469
7575
|
for (const step of list) {
|
|
7470
7576
|
const file = step.location?.file;
|
|
7471
|
-
if (step.category === "fixture" && file && !file.includes(`${
|
|
7577
|
+
if (step.category === "fixture" && file && !file.includes(`${path20.sep}node_modules${path20.sep}`)) {
|
|
7472
7578
|
const name = step.title.replace(/^(?:Fixture\s+|fixture:\s*)/, "").replace(/^"(.*)"$/, "$1");
|
|
7473
7579
|
if (!name.startsWith("piwi")) out.push(`${name} (${relativeFile(file)}:${step.location.line})`);
|
|
7474
7580
|
}
|
|
@@ -7501,7 +7607,7 @@ function tallyLifecycleSteps(file, steps) {
|
|
|
7501
7607
|
return tally;
|
|
7502
7608
|
}
|
|
7503
7609
|
function relativeFile(file) {
|
|
7504
|
-
return
|
|
7610
|
+
return path20.relative(process.cwd(), file).split(path20.sep).join("/");
|
|
7505
7611
|
}
|
|
7506
7612
|
function buildResourceReport(input) {
|
|
7507
7613
|
const findings = [];
|
|
@@ -7861,7 +7967,7 @@ function formatResourceSummary(report, leakCheck, maxLines = 10) {
|
|
|
7861
7967
|
// src/internal/collect/process-sampler.ts
|
|
7862
7968
|
var fs21 = __toESM(require("fs"));
|
|
7863
7969
|
var os8 = __toESM(require("os"));
|
|
7864
|
-
var
|
|
7970
|
+
var path21 = __toESM(require("path"));
|
|
7865
7971
|
var import_node_child_process2 = require("child_process");
|
|
7866
7972
|
var WALK_FILE_CAP = 1e4;
|
|
7867
7973
|
var SERIES_CAP = 3600;
|
|
@@ -7926,13 +8032,13 @@ function defaultWindowsQuery() {
|
|
|
7926
8032
|
});
|
|
7927
8033
|
}
|
|
7928
8034
|
function freeBytes(dir) {
|
|
7929
|
-
let current =
|
|
8035
|
+
let current = path21.resolve(dir);
|
|
7930
8036
|
for (; ; ) {
|
|
7931
8037
|
try {
|
|
7932
8038
|
const stats = fs21.statfsSync(current);
|
|
7933
8039
|
return stats.bavail * stats.bsize;
|
|
7934
8040
|
} catch {
|
|
7935
|
-
const parent =
|
|
8041
|
+
const parent = path21.dirname(current);
|
|
7936
8042
|
if (parent === current) return null;
|
|
7937
8043
|
current = parent;
|
|
7938
8044
|
}
|
|
@@ -7948,7 +8054,7 @@ async function dirBytes(dir, budget) {
|
|
|
7948
8054
|
}
|
|
7949
8055
|
for (const entry of entries) {
|
|
7950
8056
|
if (budget.files <= 0) return bytes;
|
|
7951
|
-
const full =
|
|
8057
|
+
const full = path21.join(dir, entry.name);
|
|
7952
8058
|
if (entry.isDirectory()) bytes += await dirBytes(full, budget);
|
|
7953
8059
|
else if (entry.isFile()) {
|
|
7954
8060
|
budget.files--;
|
|
@@ -7993,7 +8099,7 @@ var RunSampler = class {
|
|
|
7993
8099
|
this.platform = options.platform ?? process.platform;
|
|
7994
8100
|
this.proc = new ProcFs(options.procRoot);
|
|
7995
8101
|
this.cgroup = new CgroupFs(options.procRoot, options.cgroupRoot);
|
|
7996
|
-
this.outputDirs = [...new Set((options.outputDirs ?? []).map((dir) =>
|
|
8102
|
+
this.outputDirs = [...new Set((options.outputDirs ?? []).map((dir) => path21.resolve(dir)))];
|
|
7997
8103
|
this.tmpDir = options.tmpDir ?? os8.tmpdir();
|
|
7998
8104
|
this.intervalMs = options.intervalMs ?? 1e3;
|
|
7999
8105
|
this.heavyEvery = Math.max(1, options.heavyEvery ?? 5);
|
|
@@ -8195,7 +8301,7 @@ var RunSampler = class {
|
|
|
8195
8301
|
} catch {
|
|
8196
8302
|
return;
|
|
8197
8303
|
}
|
|
8198
|
-
this.noteTree(list, (proc) => roleOf(
|
|
8304
|
+
this.noteTree(list, (proc) => roleOf(path21.basename(proc.command.split(" ")[0] ?? ""), proc.command), at);
|
|
8199
8305
|
}
|
|
8200
8306
|
async sampleWindowsTree(at) {
|
|
8201
8307
|
let list;
|
|
@@ -8251,7 +8357,7 @@ var RunSampler = class {
|
|
|
8251
8357
|
let bytes = 0;
|
|
8252
8358
|
for (const dir of this.outputDirs) bytes += await dirBytes(dir, budget);
|
|
8253
8359
|
for (const name of await this.playwrightTempEntries((mtimeMs) => mtimeMs >= this.startedAt - 1e3)) {
|
|
8254
|
-
bytes += await dirBytes(
|
|
8360
|
+
bytes += await dirBytes(path21.join(this.tmpDir, name), budget);
|
|
8255
8361
|
}
|
|
8256
8362
|
if (budget.files <= 0) this.peakInUseIsLowerBound = true;
|
|
8257
8363
|
this.peakInUse = Math.max(this.peakInUse ?? 0, bytes);
|
|
@@ -8268,7 +8374,7 @@ var RunSampler = class {
|
|
|
8268
8374
|
for (const name of names) {
|
|
8269
8375
|
if (!PLAYWRIGHT_TEMP.test(name)) continue;
|
|
8270
8376
|
try {
|
|
8271
|
-
if (keep((await fs21.promises.stat(
|
|
8377
|
+
if (keep((await fs21.promises.stat(path21.join(this.tmpDir, name))).mtimeMs)) out.push(name);
|
|
8272
8378
|
} catch {
|
|
8273
8379
|
}
|
|
8274
8380
|
}
|
|
@@ -8279,7 +8385,7 @@ var RunSampler = class {
|
|
|
8279
8385
|
const budget = { files: WALK_FILE_CAP };
|
|
8280
8386
|
let bytes = 0;
|
|
8281
8387
|
for (const name of await this.playwrightTempEntries((mtimeMs) => mtimeMs < this.startedAt - 1e3)) {
|
|
8282
|
-
bytes += await dirBytes(
|
|
8388
|
+
bytes += await dirBytes(path21.join(this.tmpDir, name), budget);
|
|
8283
8389
|
}
|
|
8284
8390
|
return bytes;
|
|
8285
8391
|
} catch {
|
|
@@ -8672,7 +8778,7 @@ function resourceTimeline(samples, censuses) {
|
|
|
8672
8778
|
let startedAt = hasSamples ? samples.startedAt : Infinity;
|
|
8673
8779
|
for (const { events } of workers) startedAt = Math.min(startedAt, events[0][0]);
|
|
8674
8780
|
const shift = hasSamples ? samples.startedAt - startedAt : 0;
|
|
8675
|
-
const
|
|
8781
|
+
const relative11 = (points, by) => points.map(([at, value]) => [Math.round(at + by), value]);
|
|
8676
8782
|
const trees = hasSamples ? workerTreesByIndex(samples, censuses) : [];
|
|
8677
8783
|
const perWorker = Math.max(
|
|
8678
8784
|
60,
|
|
@@ -8680,17 +8786,17 @@ function resourceTimeline(samples, censuses) {
|
|
|
8680
8786
|
);
|
|
8681
8787
|
return {
|
|
8682
8788
|
startedAt,
|
|
8683
|
-
cpuPct: hasSamples ? downsamplePoints(
|
|
8684
|
-
memoryBytes: hasSamples ? downsamplePoints(
|
|
8789
|
+
cpuPct: hasSamples ? downsamplePoints(relative11(samples.cpuPct, shift), TIMELINE_SERIES_CAP, "mean") : [],
|
|
8790
|
+
memoryBytes: hasSamples ? downsamplePoints(relative11(samples.memoryBytes, shift), TIMELINE_SERIES_CAP, "max") : [],
|
|
8685
8791
|
pages: workers.map(({ worker, events }) => {
|
|
8686
|
-
const points =
|
|
8792
|
+
const points = relative11(events, -startedAt);
|
|
8687
8793
|
const last = points.pop();
|
|
8688
8794
|
return { worker, points: [...downsamplePoints(points, TIMELINE_WORKER_POINTS_CAP - 1, "max"), last] };
|
|
8689
8795
|
}),
|
|
8690
8796
|
workers: trees.map((tree) => ({
|
|
8691
8797
|
worker: tree.worker,
|
|
8692
|
-
cpuCores: downsamplePoints(
|
|
8693
|
-
memoryBytes: downsamplePoints(
|
|
8798
|
+
cpuCores: downsamplePoints(relative11(tree.cpuCores, shift), perWorker, "mean"),
|
|
8799
|
+
memoryBytes: downsamplePoints(relative11(tree.memoryBytes, shift), perWorker, "max")
|
|
8694
8800
|
}))
|
|
8695
8801
|
};
|
|
8696
8802
|
}
|
|
@@ -9252,7 +9358,7 @@ function browserFunctionsInWorker() {
|
|
|
9252
9358
|
function browserFunctionCoverage(files) {
|
|
9253
9359
|
return sanitizeCoverage({
|
|
9254
9360
|
v: 1,
|
|
9255
|
-
files: [...files].map(([
|
|
9361
|
+
files: [...files].map(([path32, functions]) => ({ path: path32, runtime: "browser", functions }))
|
|
9256
9362
|
});
|
|
9257
9363
|
}
|
|
9258
9364
|
function newKnownFunctions(known, sent) {
|
|
@@ -9265,19 +9371,19 @@ function newKnownFunctions(known, sent) {
|
|
|
9265
9371
|
budget = MAX_COVERAGE_RANGES;
|
|
9266
9372
|
};
|
|
9267
9373
|
for (const [raw, ranges] of known) {
|
|
9268
|
-
const
|
|
9269
|
-
if (
|
|
9270
|
-
let named = sent.get(
|
|
9374
|
+
const path32 = normalizeCoveragePath(raw);
|
|
9375
|
+
if (path32 === null) continue;
|
|
9376
|
+
let named = sent.get(path32);
|
|
9271
9377
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
9272
9378
|
if (fresh.length === 0) continue;
|
|
9273
|
-
if (!named) sent.set(
|
|
9379
|
+
if (!named) sent.set(path32, named = /* @__PURE__ */ new Set());
|
|
9274
9380
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
9275
9381
|
while (fresh.length > 0) {
|
|
9276
9382
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
9277
9383
|
const part = fresh.slice(0, budget);
|
|
9278
9384
|
fresh = fresh.slice(part.length);
|
|
9279
9385
|
budget -= part.length;
|
|
9280
|
-
files.push({ path:
|
|
9386
|
+
files.push({ path: path32, allFunctions: part });
|
|
9281
9387
|
}
|
|
9282
9388
|
}
|
|
9283
9389
|
close();
|
|
@@ -9288,9 +9394,9 @@ function serverCoverageInWorker() {
|
|
|
9288
9394
|
}
|
|
9289
9395
|
|
|
9290
9396
|
// src/internal/capture/code-reach.ts
|
|
9291
|
-
var
|
|
9397
|
+
var crypto5 = __toESM(require("crypto"));
|
|
9292
9398
|
var fs22 = __toESM(require("fs"));
|
|
9293
|
-
var
|
|
9399
|
+
var path22 = __toESM(require("path"));
|
|
9294
9400
|
var import_node_child_process3 = require("child_process");
|
|
9295
9401
|
|
|
9296
9402
|
// ../core/src/source-map.ts
|
|
@@ -9434,14 +9540,6 @@ function decodeSourceMap(raw) {
|
|
|
9434
9540
|
return originalAt(line, column)?.source ?? null;
|
|
9435
9541
|
},
|
|
9436
9542
|
originalAt,
|
|
9437
|
-
mapsBetween(source, start, end) {
|
|
9438
|
-
let found = false;
|
|
9439
|
-
eachSegmentBetween(segments, start, end, (i) => {
|
|
9440
|
-
found = segments.sources[i] === source;
|
|
9441
|
-
return found;
|
|
9442
|
-
});
|
|
9443
|
-
return found;
|
|
9444
|
-
},
|
|
9445
9543
|
firstOriginalLineBetween(source, start, end) {
|
|
9446
9544
|
let first = null;
|
|
9447
9545
|
eachSegmentBetween(segments, start, end, (i) => {
|
|
@@ -9643,12 +9741,12 @@ function repositoryRoot(from) {
|
|
|
9643
9741
|
}).trim();
|
|
9644
9742
|
} catch {
|
|
9645
9743
|
}
|
|
9646
|
-
cachedRepoRoot = { from, root:
|
|
9744
|
+
cachedRepoRoot = { from, root: path22.resolve(root) };
|
|
9647
9745
|
return cachedRepoRoot.root;
|
|
9648
9746
|
}
|
|
9649
9747
|
function codeReachRoots(configDir, configured) {
|
|
9650
9748
|
const repoRoot = repositoryRoot(configDir);
|
|
9651
|
-
const resolve9 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) =>
|
|
9749
|
+
const resolve9 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path22.resolve(configDir, r));
|
|
9652
9750
|
const global = Array.isArray(configured) ? resolve9(configured) : resolve9(configured?.["*"]);
|
|
9653
9751
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
9654
9752
|
if (configured && !Array.isArray(configured)) {
|
|
@@ -9685,10 +9783,10 @@ function isFile(file) {
|
|
|
9685
9783
|
function resolveSourcePath(source, roots) {
|
|
9686
9784
|
const normalized = normalizeSourcePath(source);
|
|
9687
9785
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
9688
|
-
if (
|
|
9689
|
-
const
|
|
9786
|
+
if (path22.isAbsolute(normalized) && isFile(normalized)) return path22.resolve(normalized);
|
|
9787
|
+
const relative11 = normalized.replace(/^\/+/, "");
|
|
9690
9788
|
for (const root of roots) {
|
|
9691
|
-
const candidate =
|
|
9789
|
+
const candidate = path22.resolve(root, relative11);
|
|
9692
9790
|
if (isFile(candidate)) return candidate;
|
|
9693
9791
|
}
|
|
9694
9792
|
return null;
|
|
@@ -9720,10 +9818,10 @@ function fileForUrl(url, roots, bases) {
|
|
|
9720
9818
|
return file;
|
|
9721
9819
|
}
|
|
9722
9820
|
function relativeToRepo(file, repoRoot) {
|
|
9723
|
-
return
|
|
9821
|
+
return path22.relative(repoRoot, file).split(path22.sep).join("/");
|
|
9724
9822
|
}
|
|
9725
9823
|
function scriptVersion(entry) {
|
|
9726
|
-
return entry.hash ??
|
|
9824
|
+
return entry.hash ?? crypto5.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
9727
9825
|
}
|
|
9728
9826
|
async function loadMap(entry, fetchMap) {
|
|
9729
9827
|
const ref = entry.source ? sourceMappingUrl(entry.source) : null;
|
|
@@ -9777,10 +9875,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
9777
9875
|
const files = /* @__PURE__ */ new Map();
|
|
9778
9876
|
const known = /* @__PURE__ */ new Map();
|
|
9779
9877
|
const addTo = (into, file, functions) => {
|
|
9780
|
-
const
|
|
9781
|
-
const listed = into.get(
|
|
9878
|
+
const path32 = relativeToRepo(file, roots.repoRoot);
|
|
9879
|
+
const listed = into.get(path32);
|
|
9782
9880
|
if (listed) listed.push(...functions);
|
|
9783
|
-
else into.set(
|
|
9881
|
+
else into.set(path32, [...functions]);
|
|
9784
9882
|
};
|
|
9785
9883
|
const add = (file, functions) => addTo(files, file, functions);
|
|
9786
9884
|
const bases = viteBases(entries);
|
|
@@ -10044,11 +10142,11 @@ async function stopCodeReach(page) {
|
|
|
10044
10142
|
|
|
10045
10143
|
// src/internal/collect/blob-ids.ts
|
|
10046
10144
|
var import_node_child_process4 = require("child_process");
|
|
10047
|
-
var
|
|
10145
|
+
var crypto6 = __toESM(require("crypto"));
|
|
10048
10146
|
var fs23 = __toESM(require("fs"));
|
|
10049
|
-
var
|
|
10147
|
+
var path23 = __toESM(require("path"));
|
|
10050
10148
|
function gitBlobId(content) {
|
|
10051
|
-
return
|
|
10149
|
+
return crypto6.createHash("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
|
|
10052
10150
|
}
|
|
10053
10151
|
function namesLines(file) {
|
|
10054
10152
|
return Boolean(file.functions?.length || file.lines?.length || file.executable?.length || file.allFunctions?.length);
|
|
@@ -10113,14 +10211,14 @@ var BlobIds = class {
|
|
|
10113
10211
|
async read(relativePaths) {
|
|
10114
10212
|
let root;
|
|
10115
10213
|
try {
|
|
10116
|
-
root =
|
|
10214
|
+
root = path23.resolve(this.repoRoot());
|
|
10117
10215
|
} catch {
|
|
10118
10216
|
return relativePaths.map(() => null);
|
|
10119
10217
|
}
|
|
10120
10218
|
const files = await Promise.all(
|
|
10121
10219
|
relativePaths.map(async (relativePath2) => {
|
|
10122
|
-
const file =
|
|
10123
|
-
if (!file.startsWith(root +
|
|
10220
|
+
const file = path23.resolve(root, relativePath2);
|
|
10221
|
+
if (!file.startsWith(root + path23.sep)) return null;
|
|
10124
10222
|
const stat = await fs23.promises.stat(file).catch(() => null);
|
|
10125
10223
|
return stat?.isFile() ? file : null;
|
|
10126
10224
|
})
|
|
@@ -10129,7 +10227,7 @@ var BlobIds = class {
|
|
|
10129
10227
|
const fromGit = /* @__PURE__ */ new Map();
|
|
10130
10228
|
const ids = forGit.length > 0 ? await this.hashObjects(
|
|
10131
10229
|
root,
|
|
10132
|
-
forGit.map((file) =>
|
|
10230
|
+
forGit.map((file) => path23.relative(root, file))
|
|
10133
10231
|
) : null;
|
|
10134
10232
|
if (ids) forGit.forEach((file, i) => fromGit.set(file, ids[i]));
|
|
10135
10233
|
return Promise.all(
|
|
@@ -10425,11 +10523,11 @@ var CoverageCollection = class {
|
|
|
10425
10523
|
};
|
|
10426
10524
|
|
|
10427
10525
|
// src/public/reporter.ts
|
|
10428
|
-
function testLocation(test) {
|
|
10429
|
-
return `${testFile(test)}:${test.location.line}:${test.location.column}`;
|
|
10526
|
+
function testLocation(test, base) {
|
|
10527
|
+
return `${testFile(test, base)}:${test.location.line}:${test.location.column}`;
|
|
10430
10528
|
}
|
|
10431
|
-
function testFile(test) {
|
|
10432
|
-
return
|
|
10529
|
+
function testFile(test, base) {
|
|
10530
|
+
return relativeTestPath(test.location.file, base);
|
|
10433
10531
|
}
|
|
10434
10532
|
var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
10435
10533
|
constructor(rawOptions = {}) {
|
|
@@ -10449,6 +10547,8 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10449
10547
|
this.reportedTestIds = /* @__PURE__ */ new Set();
|
|
10450
10548
|
/** Per-test attempt history, keyed by `test.id`; snapshotted onto every attempt's payload. */
|
|
10451
10549
|
this.attemptsByTest = /* @__PURE__ */ new Map();
|
|
10550
|
+
/** The folder test files are named relative to: the Playwright config's, set in `onBegin`. */
|
|
10551
|
+
this.testPathBase = process.cwd();
|
|
10452
10552
|
/** Derived in `onBegin`, once the shard is known. */
|
|
10453
10553
|
this.instanceId = "";
|
|
10454
10554
|
this.shardInfo = null;
|
|
@@ -10492,7 +10592,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10492
10592
|
this.httpClient = new HttpClient(this.options.serverUrl ?? "http://localhost:3000", logger);
|
|
10493
10593
|
this.fileHandler = new FileHandler(logger);
|
|
10494
10594
|
this.uploader = new Uploader(this.httpClient, this.fileHandler, logger);
|
|
10495
|
-
this.recovery = new CrashRecovery(this.options.projectName, logger);
|
|
10595
|
+
this.recovery = new CrashRecovery(this.httpClient.baseUrl, this.options.projectName, logger);
|
|
10496
10596
|
this.metadataCollector = new MetadataCollector(logger);
|
|
10497
10597
|
this.failureLinks = new FailureLinks(this.httpClient.baseUrl, logger);
|
|
10498
10598
|
const streamBuffer = new StreamBuffer(this.options.projectName);
|
|
@@ -10523,6 +10623,15 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10523
10623
|
static {
|
|
10524
10624
|
this.createGlobalSetup = createGlobalSetup;
|
|
10525
10625
|
}
|
|
10626
|
+
/**
|
|
10627
|
+
* Playwright reporter hook: whether this reporter writes the run's progress to
|
|
10628
|
+
* the terminal. It does not (it prints only its own `[Piwi Dashboard]` lines),
|
|
10629
|
+
* so Playwright keeps its own terminal reporter when Piwi is the only one
|
|
10630
|
+
* configured.
|
|
10631
|
+
*/
|
|
10632
|
+
printsToStdio() {
|
|
10633
|
+
return false;
|
|
10634
|
+
}
|
|
10526
10635
|
/** Playwright reporter hook: called once at the start of the test run */
|
|
10527
10636
|
onBegin(config, suite) {
|
|
10528
10637
|
if (this.listMode) {
|
|
@@ -10537,10 +10646,11 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10537
10646
|
this.logger.info(`Using the desktop app running at ${this.options.serverUrl} (no serverUrl configured).`);
|
|
10538
10647
|
}
|
|
10539
10648
|
this.startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
10649
|
+
this.testPathBase = configDirFromConfig(config);
|
|
10540
10650
|
this.playwrightVersion = config.version;
|
|
10541
10651
|
this.maxFailures = config.maxFailures ?? 0;
|
|
10542
10652
|
if (this.options.captureResources !== false) {
|
|
10543
|
-
this.resourcesFile =
|
|
10653
|
+
this.resourcesFile = path24.join(os9.tmpdir(), `piwi-resources-${process.pid}-${Date.now()}.jsonl`);
|
|
10544
10654
|
process.env[PIWI_RESOURCES_RESULTS_ENV] = this.resourcesFile;
|
|
10545
10655
|
try {
|
|
10546
10656
|
this.sampler = new RunSampler({ outputDirs: (config.projects ?? []).map((project) => project.outputDir) });
|
|
@@ -10642,7 +10752,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10642
10752
|
const beginEvent = {
|
|
10643
10753
|
type: "begin",
|
|
10644
10754
|
title: test.title,
|
|
10645
|
-
location: testLocation(test),
|
|
10755
|
+
location: testLocation(test, this.testPathBase),
|
|
10646
10756
|
workerIndex: workerIndexOf(result),
|
|
10647
10757
|
shardIndex: this.shardInfo?.current ?? null,
|
|
10648
10758
|
browser: this.metadataCollector.getBrowserConfig(test) || void 0,
|
|
@@ -10738,8 +10848,9 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10738
10848
|
this.attemptsByTest.set(test.id, attempts);
|
|
10739
10849
|
const testCase = {
|
|
10740
10850
|
type: "complete",
|
|
10851
|
+
testId: test.id,
|
|
10741
10852
|
title: test.title,
|
|
10742
|
-
location: testLocation(test),
|
|
10853
|
+
location: testLocation(test, this.testPathBase),
|
|
10743
10854
|
status,
|
|
10744
10855
|
duration: result.duration,
|
|
10745
10856
|
// Effective per-test timeout (reflects project config + describe-level
|
|
@@ -10747,7 +10858,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10747
10858
|
timeout: test.timeout ?? null,
|
|
10748
10859
|
// A `test.fail()` test that passed is now `failed` with no recorded error;
|
|
10749
10860
|
// Playwright reports the same line, so synthesize it.
|
|
10750
|
-
error: expectedFailureError(result.status, annotations) ?? buildErrorText(result),
|
|
10861
|
+
error: expectedFailureError(result.status, annotations) ?? buildErrorText(result, this.testPathBase),
|
|
10751
10862
|
retries: result.retry,
|
|
10752
10863
|
attempts: attempts.map((a) => ({ ...a })),
|
|
10753
10864
|
workerIndex: workerIndexOf(result),
|
|
@@ -10831,7 +10942,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10831
10942
|
if (isFailure && result.retry >= (test.retries ?? 0)) {
|
|
10832
10943
|
this.failureLinks.add({
|
|
10833
10944
|
title: test.title,
|
|
10834
|
-
file: testFile(test),
|
|
10945
|
+
file: testFile(test, this.testPathBase),
|
|
10835
10946
|
retry: result.retry,
|
|
10836
10947
|
browser: typeof testCase.browser?.projectName === "string" ? testCase.browser.projectName : null,
|
|
10837
10948
|
headline: failureHeadline(testCase.error, testCase.performanceMetrics?.steps)
|
|
@@ -10881,7 +10992,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10881
10992
|
return;
|
|
10882
10993
|
}
|
|
10883
10994
|
if (!result.steps?.length) return;
|
|
10884
|
-
const tally = tallyLifecycleSteps(testFile(test), result.steps);
|
|
10995
|
+
const tally = tallyLifecycleSteps(testFile(test, this.testPathBase), result.steps);
|
|
10885
10996
|
if (tally.opened.length > 0 || tally.closed.context > 0 || tally.closed.browser > 0)
|
|
10886
10997
|
this.lifecycleTallies.push(tally);
|
|
10887
10998
|
}
|
|
@@ -10944,8 +11055,9 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10944
11055
|
const locks = collectTestLocks(test);
|
|
10945
11056
|
const testCase = {
|
|
10946
11057
|
type: "complete",
|
|
11058
|
+
testId: test.id,
|
|
10947
11059
|
title: test.title,
|
|
10948
|
-
location: testLocation(test),
|
|
11060
|
+
location: testLocation(test, this.testPathBase),
|
|
10949
11061
|
status: "didnotrun",
|
|
10950
11062
|
duration: 0,
|
|
10951
11063
|
timeout: test.timeout ?? null,
|
|
@@ -11022,6 +11134,8 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11022
11134
|
const branch = resolveScmBranch(process.env) ?? null;
|
|
11023
11135
|
await this.manifests.upload(this.httpClient, submitted.auth, this.options.projectName, branch);
|
|
11024
11136
|
}
|
|
11137
|
+
} catch (error) {
|
|
11138
|
+
this.logger.error(`Could not report the run to the dashboard: ${errorMessage(error)}`);
|
|
11025
11139
|
} finally {
|
|
11026
11140
|
this.coverage?.close();
|
|
11027
11141
|
this.fileHandler.cleanupBodyAttachments();
|
|
@@ -11103,7 +11217,7 @@ async function resolveSelection(options = {}) {
|
|
|
11103
11217
|
}
|
|
11104
11218
|
|
|
11105
11219
|
// src/internal/capture/capture-fixtures.ts
|
|
11106
|
-
var
|
|
11220
|
+
var path28 = __toESM(require("path"));
|
|
11107
11221
|
var import_node_zlib2 = require("zlib");
|
|
11108
11222
|
|
|
11109
11223
|
// ../picker-dom/src/probe.ts
|
|
@@ -11534,26 +11648,26 @@ function installPickerOverlay(arg) {
|
|
|
11534
11648
|
}
|
|
11535
11649
|
};
|
|
11536
11650
|
mount(highlight, label, banner);
|
|
11537
|
-
const
|
|
11651
|
+
const quoteJs = (s) => `'${JSON.stringify(s).slice(1, -1).replace(/\\"/g, '"').replace(/'/g, "\\'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}'`;
|
|
11538
11652
|
const describe = (el) => {
|
|
11539
11653
|
const tag = (el.tagName || "?").toLowerCase();
|
|
11540
11654
|
const testId = el.getAttribute && el.getAttribute(testIdAttr);
|
|
11541
|
-
if (testId) return `getByTestId(
|
|
11655
|
+
if (testId) return `getByTestId(${quoteJs(testId)})`;
|
|
11542
11656
|
if (el.labels && el.labels.length > 0) {
|
|
11543
11657
|
const labelText = (el.labels[0].textContent || "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
11544
|
-
if (labelText) return `getByLabel(
|
|
11658
|
+
if (labelText) return `getByLabel(${quoteJs(labelText)})`;
|
|
11545
11659
|
}
|
|
11546
11660
|
const ariaLabel = el.getAttribute && el.getAttribute("aria-label");
|
|
11547
|
-
if (ariaLabel) return `getByLabel(
|
|
11661
|
+
if (ariaLabel) return `getByLabel(${quoteJs(ariaLabel)})`;
|
|
11548
11662
|
const placeholder = el.getAttribute && el.getAttribute("placeholder");
|
|
11549
|
-
if (placeholder) return `getByPlaceholder(
|
|
11663
|
+
if (placeholder) return `getByPlaceholder(${quoteJs(placeholder)})`;
|
|
11550
11664
|
const alt = el.getAttribute && el.getAttribute("alt");
|
|
11551
|
-
if (alt) return `getByAltText(
|
|
11665
|
+
if (alt) return `getByAltText(${quoteJs(alt)})`;
|
|
11552
11666
|
const titleAttr = el.getAttribute && el.getAttribute("title");
|
|
11553
|
-
if (titleAttr) return `getByTitle(
|
|
11554
|
-
if (el.id) return `locator(
|
|
11667
|
+
if (titleAttr) return `getByTitle(${quoteJs(titleAttr)})`;
|
|
11668
|
+
if (el.id) return `locator(${quoteJs(`#${el.id}`)})`;
|
|
11555
11669
|
const cls = (el.getAttribute && el.getAttribute("class") || "").split(/\s+/).find((c) => c.length > 1);
|
|
11556
|
-
return cls ? `locator(
|
|
11670
|
+
return cls ? `locator(${quoteJs(`.${cls}`)})` : tag;
|
|
11557
11671
|
};
|
|
11558
11672
|
const buildChain = (raw) => {
|
|
11559
11673
|
const chain2 = [];
|
|
@@ -12339,7 +12453,6 @@ function headingLevel(attrs, role) {
|
|
|
12339
12453
|
return null;
|
|
12340
12454
|
}
|
|
12341
12455
|
var attr = (a, key) => a.attributes[key] || null;
|
|
12342
|
-
var esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
12343
12456
|
var escCssAttrValue = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
12344
12457
|
var isCssSafeId = (id) => /^[A-Za-z][A-Za-z0-9_-]*$/.test(id);
|
|
12345
12458
|
function classifyCssStability(className) {
|
|
@@ -12394,7 +12507,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12394
12507
|
const testId = attr(attrs, testIdAttribute);
|
|
12395
12508
|
if (testId && isUnique(counts?.testId)) {
|
|
12396
12509
|
add({
|
|
12397
|
-
locator: `getByTestId(
|
|
12510
|
+
locator: `getByTestId(${quoteJsString(testId)})`,
|
|
12398
12511
|
method: "getByTestId",
|
|
12399
12512
|
args: { testId },
|
|
12400
12513
|
score: 100
|
|
@@ -12405,7 +12518,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12405
12518
|
const withLevel = (base) => level != null ? { ...base, level } : base;
|
|
12406
12519
|
if (role && accessibleName) {
|
|
12407
12520
|
add({
|
|
12408
|
-
locator: `getByRole(
|
|
12521
|
+
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(accessibleName)}${levelPart} })`,
|
|
12409
12522
|
method: "getByRole",
|
|
12410
12523
|
args: withLevel({ role, name: accessibleName }),
|
|
12411
12524
|
score: 90 - ambiguityPenalty(counts?.roleName)
|
|
@@ -12414,7 +12527,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12414
12527
|
const ariaLabel = attr(attrs, "aria-label");
|
|
12415
12528
|
if (role && ariaLabel && ariaLabel !== accessibleName) {
|
|
12416
12529
|
add({
|
|
12417
|
-
locator: `getByRole(
|
|
12530
|
+
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(ariaLabel)}${levelPart} })`,
|
|
12418
12531
|
method: "getByRole",
|
|
12419
12532
|
args: withLevel({ role, name: ariaLabel }),
|
|
12420
12533
|
score: 85 - ambiguityPenalty(counts?.roleName)
|
|
@@ -12425,7 +12538,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12425
12538
|
const labelBacked = attrs.hasLabel === void 0 ? true : attrs.hasLabel === true || label === accessibleName;
|
|
12426
12539
|
if (labelBacked) {
|
|
12427
12540
|
add({
|
|
12428
|
-
locator: `getByLabel(
|
|
12541
|
+
locator: `getByLabel(${quoteJsString(accessibleName)})`,
|
|
12429
12542
|
method: "getByLabel",
|
|
12430
12543
|
args: { label: accessibleName },
|
|
12431
12544
|
score: 85
|
|
@@ -12435,7 +12548,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12435
12548
|
const placeholder = attr(attrs, "placeholder");
|
|
12436
12549
|
if (placeholder) {
|
|
12437
12550
|
add({
|
|
12438
|
-
locator: `getByPlaceholder(
|
|
12551
|
+
locator: `getByPlaceholder(${quoteJsString(placeholder)})`,
|
|
12439
12552
|
method: "getByPlaceholder",
|
|
12440
12553
|
args: { placeholder },
|
|
12441
12554
|
score: 80 - ambiguityPenalty(counts?.placeholder)
|
|
@@ -12443,7 +12556,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12443
12556
|
}
|
|
12444
12557
|
if (text && text.length < 80) {
|
|
12445
12558
|
add({
|
|
12446
|
-
locator: `getByText(
|
|
12559
|
+
locator: `getByText(${quoteJsString(text)})`,
|
|
12447
12560
|
method: "getByText",
|
|
12448
12561
|
args: { text },
|
|
12449
12562
|
score: 75 - ambiguityPenalty(counts?.text)
|
|
@@ -12453,7 +12566,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12453
12566
|
if (id && selectable("id") && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
12454
12567
|
const selector = isCssSafeId(id) ? `#${id}` : `[id="${escCssAttrValue(id)}"]`;
|
|
12455
12568
|
add({
|
|
12456
|
-
locator: `locator(
|
|
12569
|
+
locator: `locator(${quoteJsString(selector)})`,
|
|
12457
12570
|
method: "locator",
|
|
12458
12571
|
args: { selector },
|
|
12459
12572
|
score: 65
|
|
@@ -12463,7 +12576,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12463
12576
|
if (name && selectable("name") && isUnique(counts?.name)) {
|
|
12464
12577
|
const selector = `[name="${escCssAttrValue(name)}"]`;
|
|
12465
12578
|
add({
|
|
12466
|
-
locator: `locator(
|
|
12579
|
+
locator: `locator(${quoteJsString(selector)})`,
|
|
12467
12580
|
method: "locator",
|
|
12468
12581
|
args: { selector },
|
|
12469
12582
|
score: 60
|
|
@@ -12472,7 +12585,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12472
12585
|
const alt = attr(attrs, "alt");
|
|
12473
12586
|
if (alt) {
|
|
12474
12587
|
add({
|
|
12475
|
-
locator: `getByAltText(
|
|
12588
|
+
locator: `getByAltText(${quoteJsString(alt)})`,
|
|
12476
12589
|
method: "getByAltText",
|
|
12477
12590
|
args: { text: alt },
|
|
12478
12591
|
score: 60 - ambiguityPenalty(counts?.alt)
|
|
@@ -12481,7 +12594,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12481
12594
|
const title = attr(attrs, "title");
|
|
12482
12595
|
if (title) {
|
|
12483
12596
|
add({
|
|
12484
|
-
locator: `getByTitle(
|
|
12597
|
+
locator: `getByTitle(${quoteJsString(title)})`,
|
|
12485
12598
|
method: "getByTitle",
|
|
12486
12599
|
args: { title },
|
|
12487
12600
|
score: 50 - ambiguityPenalty(counts?.title)
|
|
@@ -12499,7 +12612,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12499
12612
|
if (!testIdAnchorDone && anc.testId && anc.testIdCount === 1) {
|
|
12500
12613
|
testIdAnchorDone = true;
|
|
12501
12614
|
add({
|
|
12502
|
-
locator: `getByTestId(
|
|
12615
|
+
locator: `getByTestId(${quoteJsString(anc.testId)}).${leaf.expr}`,
|
|
12503
12616
|
method: leaf.method,
|
|
12504
12617
|
args: { ...leaf.args, anchorTestId: anc.testId },
|
|
12505
12618
|
score: scores.testId
|
|
@@ -12509,7 +12622,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12509
12622
|
idAnchorDone = true;
|
|
12510
12623
|
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
12511
12624
|
add({
|
|
12512
|
-
locator: `locator(
|
|
12625
|
+
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
12513
12626
|
method: leaf.method,
|
|
12514
12627
|
args: { ...leaf.args, anchorSelector },
|
|
12515
12628
|
score: scores.id
|
|
@@ -12520,7 +12633,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12520
12633
|
const { name: name2, value } = anc.dataAttr;
|
|
12521
12634
|
const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
|
|
12522
12635
|
add({
|
|
12523
|
-
locator: `locator(
|
|
12636
|
+
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
12524
12637
|
method: leaf.method,
|
|
12525
12638
|
args: { ...leaf.args, anchorSelector },
|
|
12526
12639
|
score: TEST_DATA_ATTRS.has(name2) ? scores.testData : scores.data
|
|
@@ -12530,7 +12643,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12530
12643
|
if (!roleAnchorDone && ancestorRole && ancestorRole !== leaf.role && anc.roleCount === 1) {
|
|
12531
12644
|
roleAnchorDone = true;
|
|
12532
12645
|
add({
|
|
12533
|
-
locator: `getByRole(
|
|
12646
|
+
locator: `getByRole(${quoteJsString(ancestorRole)}).${leaf.expr}`,
|
|
12534
12647
|
method: leaf.method,
|
|
12535
12648
|
args: { ...leaf.args, anchorRole: ancestorRole },
|
|
12536
12649
|
score: scores.role
|
|
@@ -12539,7 +12652,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12539
12652
|
if (!filterAnchorDone && ancestorRole && anc.filterText && anc.filterRoleCount === 1) {
|
|
12540
12653
|
filterAnchorDone = true;
|
|
12541
12654
|
add({
|
|
12542
|
-
locator: `getByRole(
|
|
12655
|
+
locator: `getByRole(${quoteJsString(ancestorRole)}).filter({ hasText: ${quoteJsString(anc.filterText)} }).${leaf.expr}`,
|
|
12543
12656
|
method: leaf.method,
|
|
12544
12657
|
args: { ...leaf.args, anchorRole: ancestorRole, anchorHasText: anc.filterText },
|
|
12545
12658
|
score: scores.filter
|
|
@@ -12548,7 +12661,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12548
12661
|
}
|
|
12549
12662
|
};
|
|
12550
12663
|
if (role && !hasOwnTestId) {
|
|
12551
|
-
const rolePart = level != null ?
|
|
12664
|
+
const rolePart = level != null ? `${quoteJsString(role)}, { level: ${level} }` : quoteJsString(role);
|
|
12552
12665
|
const leafArgs = withLevel({ role });
|
|
12553
12666
|
addAnchoredChains(
|
|
12554
12667
|
{ expr: `getByRole(${rolePart})`, method: "getByRole", args: leafArgs, role },
|
|
@@ -12566,7 +12679,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12566
12679
|
}
|
|
12567
12680
|
} else if (!role && text && text.length < 80 && !hasOwnTestId) {
|
|
12568
12681
|
addAnchoredChains(
|
|
12569
|
-
{ expr: `getByText(
|
|
12682
|
+
{ expr: `getByText(${quoteJsString(text)})`, method: "getByText", args: { text }, role: null },
|
|
12570
12683
|
(anc) => anc.scopedTextCount,
|
|
12571
12684
|
{ testId: 68, testData: 66, id: 60, data: 56, role: 51, filter: 49 }
|
|
12572
12685
|
);
|
|
@@ -12577,7 +12690,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12577
12690
|
const classes = clsStr.split(/\s+/).filter((c) => c.length > 1 && /^[A-Za-z_-][A-Za-z0-9_-]*$/.test(c) && isUnique(countsClasses?.[c])).map((cls) => ({ cls, score: classifyCssStability(cls) })).sort((a, b) => b.score - a.score).slice(0, 3);
|
|
12578
12691
|
for (const { cls, score } of classes) {
|
|
12579
12692
|
add({
|
|
12580
|
-
locator: `locator(
|
|
12693
|
+
locator: `locator(${quoteJsString(`.${cls}`)})`,
|
|
12581
12694
|
method: "locator",
|
|
12582
12695
|
args: { selector: `.${cls}` },
|
|
12583
12696
|
score
|
|
@@ -12605,26 +12718,25 @@ function approximateAccessibleName(attrs) {
|
|
|
12605
12718
|
}
|
|
12606
12719
|
|
|
12607
12720
|
// ../picker-dom/src/anchor-alternatives.ts
|
|
12608
|
-
var escStr = (s) => s.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
12609
12721
|
var cssIdSelector = (id) => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(id) ? `#${id}` : `[id="${id.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"]`;
|
|
12610
12722
|
var ANCHOR_KIND_SCORES = { testid: 80, id: 74, labeledRole: 68, role: 62 };
|
|
12611
12723
|
function anchorSegment(a) {
|
|
12612
12724
|
if (a.testId && a.testIdCount === 1) {
|
|
12613
|
-
return { code: `getByTestId(
|
|
12725
|
+
return { code: `getByTestId(${quoteJsString(a.testId)})`, kind: "testid", flatArgs: { anchorTestId: a.testId } };
|
|
12614
12726
|
}
|
|
12615
12727
|
if (a.id && !isAutoGenerated(a.id) && a.idCount === 1) {
|
|
12616
12728
|
const anchorSelector = cssIdSelector(a.id);
|
|
12617
|
-
return { code: `locator(
|
|
12729
|
+
return { code: `locator(${quoteJsString(anchorSelector)})`, kind: "id", flatArgs: { anchorSelector } };
|
|
12618
12730
|
}
|
|
12619
12731
|
if (a.role && a.ariaLabel && a.labeledRoleCount === 1) {
|
|
12620
12732
|
return {
|
|
12621
|
-
code: `getByRole(
|
|
12733
|
+
code: `getByRole(${quoteJsString(a.role)}, { name: ${quoteJsString(a.ariaLabel)} })`,
|
|
12622
12734
|
kind: "labeledRole",
|
|
12623
12735
|
flatArgs: { anchorRole: a.role, anchorName: a.ariaLabel }
|
|
12624
12736
|
};
|
|
12625
12737
|
}
|
|
12626
12738
|
if (a.role && a.roleCount === 1) {
|
|
12627
|
-
return { code: `getByRole(
|
|
12739
|
+
return { code: `getByRole(${quoteJsString(a.role)})`, kind: "role", flatArgs: { anchorRole: a.role } };
|
|
12628
12740
|
}
|
|
12629
12741
|
return null;
|
|
12630
12742
|
}
|
|
@@ -12632,7 +12744,7 @@ function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
|
|
|
12632
12744
|
if (!leaf.role || anchors.length === 0) return [];
|
|
12633
12745
|
const out = [];
|
|
12634
12746
|
const seen = /* @__PURE__ */ new Set();
|
|
12635
|
-
const rolePart = leaf.level != null ?
|
|
12747
|
+
const rolePart = leaf.level != null ? `${quoteJsString(leaf.role)}, { level: ${leaf.level} }` : quoteJsString(leaf.role);
|
|
12636
12748
|
const leafArgs = leaf.level != null ? { role: leaf.role, level: leaf.level } : { role: leaf.role };
|
|
12637
12749
|
const add = (l) => {
|
|
12638
12750
|
if (!seen.has(l.locator)) {
|
|
@@ -12687,21 +12799,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
12687
12799
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
12688
12800
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
12689
12801
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
12690
|
-
function isElementPath(
|
|
12691
|
-
return typeof
|
|
12802
|
+
function isElementPath(path32) {
|
|
12803
|
+
return typeof path32 === "string" && path32.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path32);
|
|
12692
12804
|
}
|
|
12693
12805
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
12694
12806
|
function elementRef(v) {
|
|
12695
12807
|
if (!v || typeof v !== "object") return null;
|
|
12696
|
-
const { path:
|
|
12697
|
-
if (!isElementPath(
|
|
12698
|
-
return { path:
|
|
12808
|
+
const { path: path32, label } = v;
|
|
12809
|
+
if (!isElementPath(path32)) return null;
|
|
12810
|
+
return { path: path32, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
12699
12811
|
}
|
|
12700
12812
|
function textPoint(v) {
|
|
12701
12813
|
if (!v || typeof v !== "object") return null;
|
|
12702
|
-
const { path:
|
|
12814
|
+
const { path: path32, offset } = v;
|
|
12703
12815
|
const at = offsetOf(offset);
|
|
12704
|
-
return isElementPath(
|
|
12816
|
+
return isElementPath(path32) && at !== null ? { path: path32, offset: at } : null;
|
|
12705
12817
|
}
|
|
12706
12818
|
function sanitizePageInteraction(raw) {
|
|
12707
12819
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -12732,7 +12844,7 @@ function sanitizePageInteraction(raw) {
|
|
|
12732
12844
|
}
|
|
12733
12845
|
|
|
12734
12846
|
// src/internal/capture/locator-healing.ts
|
|
12735
|
-
var
|
|
12847
|
+
var path25 = __toESM(require("path"));
|
|
12736
12848
|
|
|
12737
12849
|
// ../core/src/locator-fingerprint.ts
|
|
12738
12850
|
var ELEMENT_MATCH_SCORES = { role: 60, text: 55, label: 50 };
|
|
@@ -12758,7 +12870,6 @@ var TEXT_CONTENT_ROLES = /* @__PURE__ */ new Set([
|
|
|
12758
12870
|
"switch"
|
|
12759
12871
|
]);
|
|
12760
12872
|
var FORM_FIELD_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox", "searchbox", "spinbutton", "slider"]);
|
|
12761
|
-
var escapeQuote = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
12762
12873
|
function parseAriaCandidates(ariaSnapshot) {
|
|
12763
12874
|
if (!ariaSnapshot) return [];
|
|
12764
12875
|
const out = [];
|
|
@@ -12786,6 +12897,23 @@ function textSimilarity(a, b) {
|
|
|
12786
12897
|
for (const t of sa) if (sb.has(t)) common++;
|
|
12787
12898
|
return 2 * common / (sa.size + sb.size);
|
|
12788
12899
|
}
|
|
12900
|
+
var FUNCTION_WORDS = new Set(
|
|
12901
|
+
"the and for with from your you our this that les des une pour avec par sur aux dans vos votre nos notre".split(" ")
|
|
12902
|
+
);
|
|
12903
|
+
function significantWords(text) {
|
|
12904
|
+
const all = (text ?? "").replace(new RegExp("(\\p{Ll})(\\p{Lu})", "gu"), "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
12905
|
+
const meaningful = all.filter((w) => w.length >= 3 && !FUNCTION_WORDS.has(w));
|
|
12906
|
+
return new Set(meaningful.length > 0 ? meaningful : all);
|
|
12907
|
+
}
|
|
12908
|
+
function significantWordSimilarity(a, b) {
|
|
12909
|
+
const sa = significantWords(a);
|
|
12910
|
+
const sb = significantWords(b);
|
|
12911
|
+
if (sa.size === 0 && sb.size === 0) return 1;
|
|
12912
|
+
if (sa.size === 0 || sb.size === 0) return 0;
|
|
12913
|
+
let common = 0;
|
|
12914
|
+
for (const w of sa) if (sb.has(w)) common++;
|
|
12915
|
+
return 2 * common / (sa.size + sb.size);
|
|
12916
|
+
}
|
|
12789
12917
|
function fingerprintPresent(fp, candidates) {
|
|
12790
12918
|
if (!fp.name) return false;
|
|
12791
12919
|
return candidates.some(
|
|
@@ -12807,7 +12935,7 @@ function matchRenamedElement(fp, candidates) {
|
|
|
12807
12935
|
let best = null;
|
|
12808
12936
|
let bestScore = -1;
|
|
12809
12937
|
for (const c of pool) {
|
|
12810
|
-
const s =
|
|
12938
|
+
const s = significantWordSimilarity(c.name, fp.name);
|
|
12811
12939
|
if (s > bestScore) {
|
|
12812
12940
|
bestScore = s;
|
|
12813
12941
|
best = c;
|
|
@@ -12827,12 +12955,12 @@ function freshLocatorsFromCandidate(c) {
|
|
|
12827
12955
|
if (!name) return out;
|
|
12828
12956
|
out.push(
|
|
12829
12957
|
c.level != null ? {
|
|
12830
|
-
locator: `getByRole(
|
|
12958
|
+
locator: `getByRole(${quoteJsString(c.role)}, { name: ${quoteJsString(name)}, level: ${c.level} })`,
|
|
12831
12959
|
method: "getByRole",
|
|
12832
12960
|
args: { role: c.role, name, level: c.level },
|
|
12833
12961
|
score: ELEMENT_MATCH_SCORES.role
|
|
12834
12962
|
} : {
|
|
12835
|
-
locator: `getByRole(
|
|
12963
|
+
locator: `getByRole(${quoteJsString(c.role)}, { name: ${quoteJsString(name)} })`,
|
|
12836
12964
|
method: "getByRole",
|
|
12837
12965
|
args: { role: c.role, name },
|
|
12838
12966
|
score: ELEMENT_MATCH_SCORES.role
|
|
@@ -12840,14 +12968,14 @@ function freshLocatorsFromCandidate(c) {
|
|
|
12840
12968
|
);
|
|
12841
12969
|
if (TEXT_CONTENT_ROLES.has(c.role)) {
|
|
12842
12970
|
out.push({
|
|
12843
|
-
locator: `getByText(
|
|
12971
|
+
locator: `getByText(${quoteJsString(name)})`,
|
|
12844
12972
|
method: "getByText",
|
|
12845
12973
|
args: { text: name },
|
|
12846
12974
|
score: ELEMENT_MATCH_SCORES.text
|
|
12847
12975
|
});
|
|
12848
12976
|
} else if (FORM_FIELD_ROLES.has(c.role)) {
|
|
12849
12977
|
out.push({
|
|
12850
|
-
locator: `getByLabel(
|
|
12978
|
+
locator: `getByLabel(${quoteJsString(name)})`,
|
|
12851
12979
|
method: "getByLabel",
|
|
12852
12980
|
args: { label: name },
|
|
12853
12981
|
score: ELEMENT_MATCH_SCORES.label
|
|
@@ -12992,7 +13120,6 @@ var NAME_BASED_METHODS = /* @__PURE__ */ new Set([
|
|
|
12992
13120
|
"getByTitle",
|
|
12993
13121
|
"getByAltText"
|
|
12994
13122
|
]);
|
|
12995
|
-
var escAttr = (s) => s.replaceAll("\\", "\\\\").replaceAll("'", "\\'");
|
|
12996
13123
|
var SUGG_TEXT_ROLES = /* @__PURE__ */ new Set([
|
|
12997
13124
|
"button",
|
|
12998
13125
|
"link",
|
|
@@ -13025,9 +13152,9 @@ function failedNameAndRole(failed) {
|
|
|
13025
13152
|
function renderFailing(failed) {
|
|
13026
13153
|
const { role, name } = failedNameAndRole(failed);
|
|
13027
13154
|
if (failed.method === "getByRole") {
|
|
13028
|
-
return name ? `getByRole(
|
|
13155
|
+
return name ? `getByRole(${quoteJsString(role ?? "")}, { name: ${quoteJsString(name)} })` : `getByRole(${quoteJsString(role ?? "")})`;
|
|
13029
13156
|
}
|
|
13030
|
-
return `${failed.method}(
|
|
13157
|
+
return `${failed.method}(${quoteJsString(name ?? "")})`;
|
|
13031
13158
|
}
|
|
13032
13159
|
function freshSuggestions(candidate, failedMethod) {
|
|
13033
13160
|
const out = [];
|
|
@@ -13037,9 +13164,9 @@ function freshSuggestions(candidate, failedMethod) {
|
|
|
13037
13164
|
if (!out.includes(s)) out.push(s);
|
|
13038
13165
|
};
|
|
13039
13166
|
const levelPart = candidate.level != null ? `, level: ${candidate.level}` : "";
|
|
13040
|
-
const roleLoc = `getByRole(
|
|
13041
|
-
const textLoc = `getByText(
|
|
13042
|
-
const labelLoc = `getByLabel(
|
|
13167
|
+
const roleLoc = `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(name)}${levelPart} })`;
|
|
13168
|
+
const textLoc = `getByText(${quoteJsString(name)})`;
|
|
13169
|
+
const labelLoc = `getByLabel(${quoteJsString(name)})`;
|
|
13043
13170
|
if (failedMethod === "getByText" && SUGG_TEXT_ROLES.has(role)) push(textLoc);
|
|
13044
13171
|
if (failedMethod === "getByLabel" && SUGG_FIELD_ROLES.has(role)) push(labelLoc);
|
|
13045
13172
|
push(roleLoc);
|
|
@@ -13102,10 +13229,10 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
|
|
|
13102
13229
|
}
|
|
13103
13230
|
let rel = file;
|
|
13104
13231
|
try {
|
|
13105
|
-
rel =
|
|
13232
|
+
rel = path25.relative(process.cwd(), file);
|
|
13106
13233
|
} catch {
|
|
13107
13234
|
}
|
|
13108
|
-
rel = rel.split(
|
|
13235
|
+
rel = rel.split(path25.sep).join("/");
|
|
13109
13236
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
13110
13237
|
return `${rel}:${m[3]}:${m[4]}`;
|
|
13111
13238
|
}
|
|
@@ -13335,8 +13462,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
|
|
|
13335
13462
|
}
|
|
13336
13463
|
|
|
13337
13464
|
// src/internal/capture/pick-on-failure.ts
|
|
13338
|
-
var
|
|
13339
|
-
var ANSI_RE2 = /\x1b\[[0-9;]*m/g;
|
|
13465
|
+
var path26 = __toESM(require("path"));
|
|
13340
13466
|
function parseLeafLocatorExpression(rawExpr) {
|
|
13341
13467
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
13342
13468
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -13345,14 +13471,13 @@ function deriveFailedLocator(testInfo) {
|
|
|
13345
13471
|
const info = testInfo;
|
|
13346
13472
|
const errors = info.errors && info.errors.length > 0 ? info.errors : info.error ? [info.error] : [];
|
|
13347
13473
|
for (const err of errors) {
|
|
13348
|
-
const
|
|
13349
|
-
${err.stack ?? ""}
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
const parsed2 = parseLeafLocatorExpression(line[1].trim());
|
|
13474
|
+
const header = readLocatorHeader(stripAnsi(`${err.message ?? ""}
|
|
13475
|
+
${err.stack ?? ""}`));
|
|
13476
|
+
if (!header) continue;
|
|
13477
|
+
const parsed2 = parseLeafLocatorExpression(header);
|
|
13353
13478
|
if (!parsed2) continue;
|
|
13354
13479
|
const loc = err.location;
|
|
13355
|
-
const location = loc ? `${
|
|
13480
|
+
const location = loc ? `${path26.relative(process.cwd(), loc.file).split(path26.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
13356
13481
|
return { method: parsed2.method, args: parsed2.args, location };
|
|
13357
13482
|
}
|
|
13358
13483
|
return null;
|
|
@@ -13505,7 +13630,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
13505
13630
|
}
|
|
13506
13631
|
|
|
13507
13632
|
// src/internal/capture/pause-at.ts
|
|
13508
|
-
var
|
|
13633
|
+
var path27 = __toESM(require("path"));
|
|
13509
13634
|
|
|
13510
13635
|
// ../core/src/editor-send.ts
|
|
13511
13636
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -13549,9 +13674,9 @@ var PauseSet = class {
|
|
|
13549
13674
|
var EMPTY = new PauseSet(/* @__PURE__ */ new Set());
|
|
13550
13675
|
function callSiteFile(file, cwd) {
|
|
13551
13676
|
const slashed = file.replace(/\\/g, "/");
|
|
13552
|
-
const absolute =
|
|
13553
|
-
let rel = absolute ?
|
|
13554
|
-
rel = rel.split(
|
|
13677
|
+
const absolute = path27.isAbsolute(file) || path27.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
|
|
13678
|
+
let rel = absolute ? path27.relative(cwd, file) : path27.normalize(slashed);
|
|
13679
|
+
rel = rel.split(path27.sep).join("/").replace(/\\/g, "/");
|
|
13555
13680
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
13556
13681
|
return rel;
|
|
13557
13682
|
}
|
|
@@ -14035,7 +14160,7 @@ function configuredCodeReachRoots() {
|
|
|
14035
14160
|
}
|
|
14036
14161
|
} catch {
|
|
14037
14162
|
}
|
|
14038
|
-
return raw.split(
|
|
14163
|
+
return raw.split(path28.delimiter).filter(Boolean);
|
|
14039
14164
|
}
|
|
14040
14165
|
async function stopSinkCodeReach(sink, closing) {
|
|
14041
14166
|
const stopping = [];
|
|
@@ -14051,9 +14176,7 @@ async function stopSinkCodeReach(sink, closing) {
|
|
|
14051
14176
|
}
|
|
14052
14177
|
if (stopping.length > 0) {
|
|
14053
14178
|
const info = sink.testInfo;
|
|
14054
|
-
const
|
|
14055
|
-
const configDir = configFile ? path30.dirname(configFile) : info?.config.rootDir ?? process.cwd();
|
|
14056
|
-
const roots = codeReachRoots(configDir, configuredCodeReachRoots());
|
|
14179
|
+
const roots = codeReachRoots(configDirFromConfig(info?.config), configuredCodeReachRoots());
|
|
14057
14180
|
for (const [page, session] of stopping) {
|
|
14058
14181
|
session.stopping = stopPageCodeReach(sink, page, session, roots);
|
|
14059
14182
|
underway.push(session.stopping);
|
|
@@ -14195,8 +14318,8 @@ function buildPageState(raw, cookies, interaction) {
|
|
|
14195
14318
|
length: typeof e.length === "number" ? e.length : 0
|
|
14196
14319
|
}));
|
|
14197
14320
|
return {
|
|
14198
|
-
url: raw.url,
|
|
14199
|
-
hash: raw.hash
|
|
14321
|
+
url: raw.url.split("#")[0],
|
|
14322
|
+
hash: raw.hash ? maskUrlFragment(raw.hash) : null,
|
|
14200
14323
|
historyState: historyState || null,
|
|
14201
14324
|
localStorage: capStorage(raw.localStorage),
|
|
14202
14325
|
sessionStorage: capStorage(raw.sessionStorage),
|
|
@@ -15213,6 +15336,7 @@ var piwiFixtures = {
|
|
|
15213
15336
|
const plan = loadFlakePlan();
|
|
15214
15337
|
const role = flakeRoleForTest(plan, {
|
|
15215
15338
|
file: testInfo.file,
|
|
15339
|
+
base: configDirFromConfig(testInfo.config),
|
|
15216
15340
|
title: testInfo.title,
|
|
15217
15341
|
titlePath: testInfo.titlePath,
|
|
15218
15342
|
project: testInfo.project.name
|
|
@@ -15226,7 +15350,8 @@ var piwiFixtures = {
|
|
|
15226
15350
|
sink.probeItem = probeItemForTest({
|
|
15227
15351
|
title: testInfo.title,
|
|
15228
15352
|
file: testInfo.file,
|
|
15229
|
-
titlePath: testInfo.titlePath
|
|
15353
|
+
titlePath: testInfo.titlePath,
|
|
15354
|
+
base: configDirFromConfig(testInfo.config)
|
|
15230
15355
|
});
|
|
15231
15356
|
activeResourceLedger()?.testStarted(testInfo);
|
|
15232
15357
|
currentSink = sink;
|
|
@@ -15270,6 +15395,7 @@ async function recordFlakeAttempt(flake, testInfo) {
|
|
|
15270
15395
|
flakeResultLine(flake.plan, {
|
|
15271
15396
|
role: flake.role,
|
|
15272
15397
|
file: testInfo.file,
|
|
15398
|
+
base: configDirFromConfig(testInfo.config),
|
|
15273
15399
|
title: testInfo.title,
|
|
15274
15400
|
project: testInfo.project.name || null,
|
|
15275
15401
|
browserName: flake.browserName,
|
|
@@ -15291,12 +15417,12 @@ function extendPiwiFixtures(test) {
|
|
|
15291
15417
|
|
|
15292
15418
|
// src/internal/ai/ai-fixtures.ts
|
|
15293
15419
|
var fs26 = __toESM(require("fs"));
|
|
15294
|
-
var
|
|
15420
|
+
var path31 = __toESM(require("path"));
|
|
15295
15421
|
var import_test = require("@playwright/test");
|
|
15296
15422
|
|
|
15297
15423
|
// src/internal/ai/artifact.ts
|
|
15298
15424
|
var fs25 = __toESM(require("fs"));
|
|
15299
|
-
var
|
|
15425
|
+
var path29 = __toESM(require("path"));
|
|
15300
15426
|
var ARTIFACT_VERSION = 1;
|
|
15301
15427
|
var LOCATOR_METHOD_SET2 = new Set(LOCATOR_METHODS);
|
|
15302
15428
|
var ACTION_METHOD_SET2 = new Set(ACTION_METHODS);
|
|
@@ -15400,7 +15526,7 @@ function writeEntry(file, entry) {
|
|
|
15400
15526
|
const canonical = serializeEntry(entry);
|
|
15401
15527
|
return withEntryLock(file, () => {
|
|
15402
15528
|
if (fs25.existsSync(file) && fs25.readFileSync(file, "utf8") === canonical) return { written: false };
|
|
15403
|
-
fs25.mkdirSync(
|
|
15529
|
+
fs25.mkdirSync(path29.dirname(file), { recursive: true });
|
|
15404
15530
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
15405
15531
|
fs25.writeFileSync(tmp, canonical);
|
|
15406
15532
|
fs25.renameSync(tmp, file);
|
|
@@ -15409,7 +15535,7 @@ function writeEntry(file, entry) {
|
|
|
15409
15535
|
}
|
|
15410
15536
|
function withEntryLock(file, fn) {
|
|
15411
15537
|
const lock = `${file}.lock`;
|
|
15412
|
-
fs25.mkdirSync(
|
|
15538
|
+
fs25.mkdirSync(path29.dirname(file), { recursive: true });
|
|
15413
15539
|
const deadline = Date.now() + 5e3;
|
|
15414
15540
|
for (; ; ) {
|
|
15415
15541
|
try {
|
|
@@ -15437,28 +15563,28 @@ function withEntryLock(file, fn) {
|
|
|
15437
15563
|
}
|
|
15438
15564
|
|
|
15439
15565
|
// src/internal/ai/keys.ts
|
|
15440
|
-
var
|
|
15441
|
-
var
|
|
15566
|
+
var crypto7 = __toESM(require("crypto"));
|
|
15567
|
+
var path30 = __toESM(require("path"));
|
|
15442
15568
|
var DEFAULT_AI_DIR = "__piwi__";
|
|
15443
15569
|
function normalizeTemplate(template) {
|
|
15444
15570
|
return template.trim().replace(/\s+/g, " ").toLowerCase();
|
|
15445
15571
|
}
|
|
15446
15572
|
function hashTemplate(normalized, ordinal = 0) {
|
|
15447
15573
|
const input = ordinal > 0 ? `${normalized}#${ordinal}` : normalized;
|
|
15448
|
-
return
|
|
15574
|
+
return crypto7.createHash("sha256").update(input).digest("hex").slice(0, 8);
|
|
15449
15575
|
}
|
|
15450
15576
|
function slug(text, maxLength = 40) {
|
|
15451
15577
|
const base = text.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
15452
15578
|
return (base || "x").slice(0, maxLength).replace(/-+$/g, "") || "x";
|
|
15453
15579
|
}
|
|
15454
15580
|
function entryDir(specFile, dir = DEFAULT_AI_DIR) {
|
|
15455
|
-
return
|
|
15581
|
+
return path30.join(path30.dirname(specFile), dir, path30.basename(specFile));
|
|
15456
15582
|
}
|
|
15457
15583
|
function entryPath(params) {
|
|
15458
15584
|
const basis = `${normalizeTemplate(params.testTitle)}::${normalizeTemplate(params.template)}`;
|
|
15459
15585
|
const hash = hashTemplate(basis, params.ordinal ?? 0);
|
|
15460
15586
|
const name = `${slug(params.testTitle)}.${slug(params.template)}.${hash}.json`;
|
|
15461
|
-
return
|
|
15587
|
+
return path30.join(entryDir(params.specFile, params.dir), name);
|
|
15462
15588
|
}
|
|
15463
15589
|
function parseLocation2(location) {
|
|
15464
15590
|
const match = /:(\d+):(\d+)$/.exec(location);
|
|
@@ -15865,6 +15991,8 @@ async function groundElement(page, element, params) {
|
|
|
15865
15991
|
// src/internal/ai/resolution.ts
|
|
15866
15992
|
var MAX_SNAPSHOT_CHARS = 24e3;
|
|
15867
15993
|
var DEFAULT_MAX_STEPS = 20;
|
|
15994
|
+
var MAX_OBSERVED_RESPONSES = 50;
|
|
15995
|
+
var MAX_OBSERVED_URL_CHARS = 4e3;
|
|
15868
15996
|
var ServerStepResolver = class {
|
|
15869
15997
|
constructor(serverUrl, apiKey) {
|
|
15870
15998
|
this.serverUrl = serverUrl;
|
|
@@ -16016,14 +16144,15 @@ async function resolveRun(template, ctx) {
|
|
|
16016
16144
|
ctx.responseWaitTimeout
|
|
16017
16145
|
);
|
|
16018
16146
|
history.push({ action: decision.action, element: decision.element, value: decision.value });
|
|
16019
|
-
|
|
16147
|
+
const observedResponses = [...new Set(observed.map((url) => maskValues(url, ctx.params)))].filter((url) => url.length <= MAX_OBSERVED_URL_CHARS).slice(0, MAX_OBSERVED_RESPONSES);
|
|
16148
|
+
if (observedResponses.length > 0) {
|
|
16020
16149
|
const waitDecision = await ctx.resolver.resolveStep({
|
|
16021
16150
|
kind: "wait",
|
|
16022
16151
|
template,
|
|
16023
16152
|
paramNames,
|
|
16024
16153
|
ariaSnapshot: "",
|
|
16025
16154
|
history,
|
|
16026
|
-
observedResponses
|
|
16155
|
+
observedResponses
|
|
16027
16156
|
});
|
|
16028
16157
|
if (waitDecision.waitForResponse) step.waitForResponse = waitDecision.waitForResponse;
|
|
16029
16158
|
}
|
|
@@ -16058,7 +16187,7 @@ function testIdentity(testInfo) {
|
|
|
16058
16187
|
return titles.length > 0 ? titles.join(" \u203A ") : testInfo.title;
|
|
16059
16188
|
}
|
|
16060
16189
|
function relativeToCwd(file) {
|
|
16061
|
-
return
|
|
16190
|
+
return path31.relative(process.cwd(), file).split(path31.sep).join("/");
|
|
16062
16191
|
}
|
|
16063
16192
|
function recordIntents(intents, entry) {
|
|
16064
16193
|
const add = (locator, kind) => {
|