@piwitests/reporter 0.59.0 → 0.60.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 +1456 -1305
- package/dist/global-setup-module.js +137 -54
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1234 -480
- package/dist/internal/capture/attachments.d.ts +1 -0
- package/dist/internal/capture/attachments.js +2 -1
- package/dist/internal/capture/capture-fixtures.js +939 -320
- package/dist/internal/capture/pick-on-failure.d.ts +6 -3
- package/dist/internal/capture/pick-on-failure.js +49 -30
- package/package.json +5 -5
- package/templates/skills/run-the-right-tests/SKILL.md +19 -8
- package/templates/skills/setup-piwi/SKILL.md +4 -1
package/dist/index.js
CHANGED
|
@@ -43,9 +43,12 @@ __export(index_exports, {
|
|
|
43
43
|
module.exports = __toCommonJS(index_exports);
|
|
44
44
|
|
|
45
45
|
// src/public/reporter.ts
|
|
46
|
-
var
|
|
46
|
+
var fs25 = __toESM(require("fs"));
|
|
47
47
|
var os9 = __toESM(require("os"));
|
|
48
|
-
var
|
|
48
|
+
var path25 = __toESM(require("path"));
|
|
49
|
+
|
|
50
|
+
// src/internal/config/env.ts
|
|
51
|
+
var path2 = __toESM(require("path"));
|
|
49
52
|
|
|
50
53
|
// src/internal/config/desktop.ts
|
|
51
54
|
var fs = __toESM(require("fs"));
|
|
@@ -54,6 +57,14 @@ var path = __toESM(require("path"));
|
|
|
54
57
|
function defaultDesktopConfigPath() {
|
|
55
58
|
return path.join(os.homedir(), ".piwi", "desktop.json");
|
|
56
59
|
}
|
|
60
|
+
function processAlive(pid) {
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, 0);
|
|
63
|
+
return true;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return error.code === "EPERM";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
57
68
|
function readDesktopConfig(filePath2) {
|
|
58
69
|
try {
|
|
59
70
|
const parsed2 = JSON.parse(fs.readFileSync(filePath2, "utf8"));
|
|
@@ -61,12 +72,75 @@ function readDesktopConfig(filePath2) {
|
|
|
61
72
|
const token = parsed2?.token;
|
|
62
73
|
if (typeof url !== "string" || !url) return null;
|
|
63
74
|
if (typeof token !== "string" || !token) return null;
|
|
75
|
+
if (Number.isInteger(parsed2?.pid) && parsed2.pid > 0 && !processAlive(parsed2.pid)) return null;
|
|
64
76
|
return { url, token };
|
|
65
77
|
} catch {
|
|
66
78
|
return null;
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
|
|
82
|
+
// src/internal/config/dotenv.ts
|
|
83
|
+
var fs2 = __toESM(require("fs"));
|
|
84
|
+
|
|
85
|
+
// ../core/src/dotenv.ts
|
|
86
|
+
function parseDotEnv(text) {
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
89
|
+
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(raw);
|
|
90
|
+
if (!m) continue;
|
|
91
|
+
let value = m[2];
|
|
92
|
+
const quote = value[0];
|
|
93
|
+
if ((quote === '"' || quote === "'") && value.length > 1 && value.endsWith(quote)) {
|
|
94
|
+
value = value.slice(1, -1);
|
|
95
|
+
if (quote === '"') value = value.replace(/\\n/g, "\n").replace(/\\"/g, '"');
|
|
96
|
+
} else {
|
|
97
|
+
value = value.replace(/\s+#.*$/, "");
|
|
98
|
+
}
|
|
99
|
+
out[m[1]] = value;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/internal/config/dotenv.ts
|
|
105
|
+
function readDotEnv(filePath2) {
|
|
106
|
+
try {
|
|
107
|
+
return parseDotEnv(fs2.readFileSync(filePath2, "utf-8"));
|
|
108
|
+
} catch {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/internal/support/ci.ts
|
|
114
|
+
function detectCi(env) {
|
|
115
|
+
return Boolean(
|
|
116
|
+
env.JENKINS_URL || env.GITHUB_ACTIONS || env.GITLAB_CI || env.CIRCLECI || env.TRAVIS || env.TF_BUILD || env.BITBUCKET_BUILD_NUMBER || env.TEAMCITY_VERSION || env.CODEBUILD_BUILD_ID || env.bamboo_buildKey || env.CI && env.CI.toLowerCase() !== "false" && env.CI !== "0"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function detectCiIds(env) {
|
|
120
|
+
if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) {
|
|
121
|
+
const run = env.GITHUB_RUN_ATTEMPT ? `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` : env.GITHUB_RUN_ID;
|
|
122
|
+
return { run, job: env.GITHUB_JOB || null };
|
|
123
|
+
}
|
|
124
|
+
if (env.GITLAB_CI && env.CI_PIPELINE_ID) return { run: env.CI_PIPELINE_ID, job: env.CI_JOB_ID || null };
|
|
125
|
+
if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return { run: env.CIRCLE_WORKFLOW_ID, job: env.CIRCLE_BUILD_NUM || null };
|
|
126
|
+
if (env.TRAVIS && env.TRAVIS_BUILD_ID) return { run: env.TRAVIS_BUILD_ID, job: env.TRAVIS_JOB_ID || null };
|
|
127
|
+
if (env.TF_BUILD && env.BUILD_BUILDID) return { run: env.BUILD_BUILDID, job: env.SYSTEM_JOBID || null };
|
|
128
|
+
if (env.JENKINS_URL && env.BUILD_ID) return { run: env.BUILD_ID, job: null };
|
|
129
|
+
if (env.BUILDKITE_BUILD_ID) return { run: env.BUILDKITE_BUILD_ID, job: env.BUILDKITE_JOB_ID || null };
|
|
130
|
+
if (env.TEAMCITY_BUILD_ID) return { run: env.TEAMCITY_BUILD_ID, job: null };
|
|
131
|
+
if (env.BITBUCKET_BUILD_NUMBER) return { run: env.BITBUCKET_BUILD_NUMBER, job: env.BITBUCKET_STEP_UUID || null };
|
|
132
|
+
if (env.SEMAPHORE_WORKFLOW_ID) return { run: env.SEMAPHORE_WORKFLOW_ID, job: env.SEMAPHORE_JOB_ID || null };
|
|
133
|
+
if (env.APPVEYOR_BUILD_ID) return { run: env.APPVEYOR_BUILD_ID, job: env.APPVEYOR_JOB_ID || null };
|
|
134
|
+
if (env.DRONE_BUILD_NUMBER) return { run: env.DRONE_BUILD_NUMBER, job: null };
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function resolveRunLabel(configured, sharded) {
|
|
138
|
+
if (configured) return configured;
|
|
139
|
+
const ci = detectCiIds(process.env);
|
|
140
|
+
if (!ci) return null;
|
|
141
|
+
return !sharded && ci.job ? `${ci.run}|${ci.job}` : ci.run;
|
|
142
|
+
}
|
|
143
|
+
|
|
70
144
|
// src/internal/config/env.ts
|
|
71
145
|
var DEFAULTS = {
|
|
72
146
|
projectName: "default-project",
|
|
@@ -103,6 +177,7 @@ var DEFAULTS = {
|
|
|
103
177
|
};
|
|
104
178
|
var PIWI_ENV_KEYS = {
|
|
105
179
|
serverUrl: "PIWI_DASHBOARD_URL",
|
|
180
|
+
desktopApp: "PIWI_DESKTOP_APP",
|
|
106
181
|
projectName: "PIWI_PROJECT_NAME",
|
|
107
182
|
verbose: "PIWI_VERBOSE",
|
|
108
183
|
apiKey: "PIWI_API_KEY",
|
|
@@ -215,11 +290,27 @@ var ENV_FALLBACK_SPECS = [
|
|
|
215
290
|
{ option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
|
|
216
291
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
217
292
|
];
|
|
293
|
+
var DOTENV_OPTIONS = ["serverUrl", "apiKey", "projectName"];
|
|
294
|
+
var DESKTOP_APP_MODES = ["prefer", "fallback", "off"];
|
|
295
|
+
function desktopAppMode(option, envValue) {
|
|
296
|
+
const fromEnv = envValue?.trim().toLowerCase();
|
|
297
|
+
if (DESKTOP_APP_MODES.includes(fromEnv)) return fromEnv;
|
|
298
|
+
if (DESKTOP_APP_MODES.includes(option)) return option;
|
|
299
|
+
return "fallback";
|
|
300
|
+
}
|
|
218
301
|
var desktopDiscovered = false;
|
|
302
|
+
var desktopReplacedServerUrl = null;
|
|
303
|
+
var dotEnvUse = null;
|
|
219
304
|
function usedDesktopDiscovery() {
|
|
220
305
|
return desktopDiscovered;
|
|
221
306
|
}
|
|
222
|
-
function
|
|
307
|
+
function desktopReplacedServer() {
|
|
308
|
+
return desktopReplacedServerUrl;
|
|
309
|
+
}
|
|
310
|
+
function usedDotEnv() {
|
|
311
|
+
return dotEnvUse;
|
|
312
|
+
}
|
|
313
|
+
function resolveOptions(raw, configDir) {
|
|
223
314
|
const env = process.env;
|
|
224
315
|
const mergedRaw = { ...raw };
|
|
225
316
|
for (const spec of ENV_FALLBACK_SPECS) {
|
|
@@ -231,14 +322,35 @@ function resolveOptions(raw) {
|
|
|
231
322
|
mergedRaw[spec.option] = spec.kind === "number" ? Number(value) : value;
|
|
232
323
|
}
|
|
233
324
|
}
|
|
325
|
+
dotEnvUse = null;
|
|
326
|
+
if (configDir) {
|
|
327
|
+
const file = path2.join(configDir, ".env");
|
|
328
|
+
const dotEnv = readDotEnv(file);
|
|
329
|
+
const keys = [];
|
|
330
|
+
for (const option of DOTENV_OPTIONS) {
|
|
331
|
+
const value = dotEnv[PIWI_ENV_KEYS[option]];
|
|
332
|
+
if (mergedRaw[option] !== void 0 || !value) continue;
|
|
333
|
+
mergedRaw[option] = value;
|
|
334
|
+
keys.push(PIWI_ENV_KEYS[option]);
|
|
335
|
+
}
|
|
336
|
+
if (keys.length > 0) dotEnvUse = { file, keys };
|
|
337
|
+
}
|
|
234
338
|
desktopDiscovered = false;
|
|
339
|
+
desktopReplacedServerUrl = null;
|
|
340
|
+
const mode = desktopAppMode(mergedRaw.desktopApp, env[PIWI_ENV_KEYS.desktopApp]);
|
|
341
|
+
mergedRaw.desktopApp = mode;
|
|
235
342
|
const serverUrlUnset = mergedRaw.serverUrl === void 0;
|
|
236
343
|
const apiKeyUnset = mergedRaw.apiKey === void 0 || mergedRaw.apiKey === null;
|
|
237
|
-
|
|
344
|
+
const nothingConfigured = serverUrlUnset && apiKeyUnset;
|
|
345
|
+
const preferLocally = mode === "prefer" && !detectCi(env);
|
|
346
|
+
if (mode !== "off" && (nothingConfigured || preferLocally)) {
|
|
238
347
|
const desktop = readDesktopConfig(env[PIWI_DESKTOP_CONFIG_ENV] || defaultDesktopConfigPath());
|
|
239
348
|
if (desktop) {
|
|
349
|
+
if (!serverUrlUnset && mergedRaw.serverUrl !== desktop.url) desktopReplacedServerUrl = mergedRaw.serverUrl;
|
|
240
350
|
mergedRaw.serverUrl = desktop.url;
|
|
241
351
|
mergedRaw.apiKey = desktop.token;
|
|
352
|
+
mergedRaw.username = null;
|
|
353
|
+
mergedRaw.password = null;
|
|
242
354
|
desktopDiscovered = true;
|
|
243
355
|
}
|
|
244
356
|
}
|
|
@@ -263,6 +375,8 @@ function resolveOptions(raw) {
|
|
|
263
375
|
function applyOptionsToEnv(options) {
|
|
264
376
|
const env = process.env;
|
|
265
377
|
if (options.serverUrl !== void 0) env[PIWI_ENV_KEYS.serverUrl] = options.serverUrl;
|
|
378
|
+
if (options.desktopApp !== void 0 && !env[PIWI_ENV_KEYS.desktopApp])
|
|
379
|
+
env[PIWI_ENV_KEYS.desktopApp] = options.desktopApp;
|
|
266
380
|
if (options.projectName !== void 0) env[PIWI_ENV_KEYS.projectName] = options.projectName;
|
|
267
381
|
if (options.verbose !== void 0) env[PIWI_ENV_KEYS.verbose] = String(options.verbose);
|
|
268
382
|
if (options.apiKey) env[PIWI_ENV_KEYS.apiKey] = options.apiKey;
|
|
@@ -545,7 +659,7 @@ var HttpClient = class {
|
|
|
545
659
|
}
|
|
546
660
|
/** Unified request core: transport, headers, auth, response accumulation, timeout. */
|
|
547
661
|
request(method, pathname, opts) {
|
|
548
|
-
return new Promise((
|
|
662
|
+
return new Promise((resolve11, reject) => {
|
|
549
663
|
if (this.closedReason !== null) {
|
|
550
664
|
reject(new Error(this.closedReason));
|
|
551
665
|
return;
|
|
@@ -568,7 +682,7 @@ var HttpClient = class {
|
|
|
568
682
|
data += chunk;
|
|
569
683
|
});
|
|
570
684
|
res.on("end", () => {
|
|
571
|
-
|
|
685
|
+
resolve11({ status: res.statusCode ?? 0, text: data, headers: res.headers });
|
|
572
686
|
});
|
|
573
687
|
res.on("error", reject);
|
|
574
688
|
res.on("close", () => {
|
|
@@ -607,8 +721,8 @@ var HttpClient = class {
|
|
|
607
721
|
};
|
|
608
722
|
|
|
609
723
|
// src/internal/submit/uploader.ts
|
|
610
|
-
var
|
|
611
|
-
var
|
|
724
|
+
var fs3 = __toESM(require("fs"));
|
|
725
|
+
var path3 = __toESM(require("path"));
|
|
612
726
|
var import_form_data2 = __toESM(require("form-data"));
|
|
613
727
|
|
|
614
728
|
// src/internal/submit/serializer.ts
|
|
@@ -858,8 +972,8 @@ var Uploader = class {
|
|
|
858
972
|
if (traceInfo) {
|
|
859
973
|
form.append("trace_hash", traceInfo.hash);
|
|
860
974
|
if (includeTraceFile2) {
|
|
861
|
-
form.append("trace",
|
|
862
|
-
filename:
|
|
975
|
+
form.append("trace", fs3.createReadStream(traceInfo.tracePath), {
|
|
976
|
+
filename: path3.basename(traceInfo.tracePath)
|
|
863
977
|
});
|
|
864
978
|
}
|
|
865
979
|
}
|
|
@@ -871,7 +985,7 @@ var Uploader = class {
|
|
|
871
985
|
)
|
|
872
986
|
);
|
|
873
987
|
for (const a of attachments) {
|
|
874
|
-
form.append("attach_file",
|
|
988
|
+
form.append("attach_file", fs3.createReadStream(a.path), { filename: a.originalName });
|
|
875
989
|
}
|
|
876
990
|
}
|
|
877
991
|
return form;
|
|
@@ -924,7 +1038,7 @@ var Uploader = class {
|
|
|
924
1038
|
)
|
|
925
1039
|
);
|
|
926
1040
|
for (const a of attachments) {
|
|
927
|
-
form.append(`attach_file_${i}`,
|
|
1041
|
+
form.append(`attach_file_${i}`, fs3.createReadStream(a.path), { filename: a.originalName });
|
|
928
1042
|
attachmentCount++;
|
|
929
1043
|
}
|
|
930
1044
|
}
|
|
@@ -933,8 +1047,8 @@ var Uploader = class {
|
|
|
933
1047
|
let traceCount = 0;
|
|
934
1048
|
for (const [i, tc] of testCases.entries()) {
|
|
935
1049
|
for (const tp of this.fileHandler.findTraceFiles(tc)) {
|
|
936
|
-
if (
|
|
937
|
-
form.append(`trace_${i}`,
|
|
1050
|
+
if (fs3.existsSync(tp)) {
|
|
1051
|
+
form.append(`trace_${i}`, fs3.createReadStream(tp), { filename: path3.basename(tp) });
|
|
938
1052
|
traceCount++;
|
|
939
1053
|
}
|
|
940
1054
|
}
|
|
@@ -944,9 +1058,9 @@ var Uploader = class {
|
|
|
944
1058
|
};
|
|
945
1059
|
|
|
946
1060
|
// src/internal/streaming/stream-buffer.ts
|
|
947
|
-
var
|
|
1061
|
+
var path4 = __toESM(require("path"));
|
|
948
1062
|
var os3 = __toESM(require("os"));
|
|
949
|
-
var
|
|
1063
|
+
var fs4 = __toESM(require("fs"));
|
|
950
1064
|
|
|
951
1065
|
// src/internal/support/instance-id.ts
|
|
952
1066
|
var crypto = __toESM(require("crypto"));
|
|
@@ -967,14 +1081,14 @@ var StreamBuffer = class {
|
|
|
967
1081
|
}
|
|
968
1082
|
/** Scope the buffer to a run. Until then the buffer is empty and appends are dropped. */
|
|
969
1083
|
bindRun(runId) {
|
|
970
|
-
this.filePath =
|
|
1084
|
+
this.filePath = path4.join(os3.tmpdir(), `${this.prefix}-${runId}.jsonl`);
|
|
971
1085
|
}
|
|
972
1086
|
/** Append one or more events to the on-disk buffer */
|
|
973
1087
|
append(events) {
|
|
974
1088
|
if (!this.filePath || events.length === 0) return;
|
|
975
1089
|
try {
|
|
976
1090
|
const lines = events.map((e) => JSON.stringify(e) + "\n").join("");
|
|
977
|
-
|
|
1091
|
+
fs4.appendFileSync(this.filePath, lines, "utf8");
|
|
978
1092
|
} catch {
|
|
979
1093
|
}
|
|
980
1094
|
}
|
|
@@ -982,8 +1096,8 @@ var StreamBuffer = class {
|
|
|
982
1096
|
load() {
|
|
983
1097
|
if (!this.filePath) return [];
|
|
984
1098
|
try {
|
|
985
|
-
if (
|
|
986
|
-
const content =
|
|
1099
|
+
if (fs4.existsSync(this.filePath)) {
|
|
1100
|
+
const content = fs4.readFileSync(this.filePath, "utf8");
|
|
987
1101
|
return content.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
988
1102
|
}
|
|
989
1103
|
} catch {
|
|
@@ -994,7 +1108,7 @@ var StreamBuffer = class {
|
|
|
994
1108
|
clear() {
|
|
995
1109
|
if (!this.filePath) return;
|
|
996
1110
|
try {
|
|
997
|
-
if (
|
|
1111
|
+
if (fs4.existsSync(this.filePath)) fs4.unlinkSync(this.filePath);
|
|
998
1112
|
} catch {
|
|
999
1113
|
}
|
|
1000
1114
|
}
|
|
@@ -1003,14 +1117,14 @@ var StreamBuffer = class {
|
|
|
1003
1117
|
const dir = os3.tmpdir();
|
|
1004
1118
|
let names;
|
|
1005
1119
|
try {
|
|
1006
|
-
names =
|
|
1120
|
+
names = fs4.readdirSync(dir).filter((name) => name.startsWith(this.prefix));
|
|
1007
1121
|
} catch {
|
|
1008
1122
|
return;
|
|
1009
1123
|
}
|
|
1010
1124
|
for (const name of names) {
|
|
1011
1125
|
try {
|
|
1012
|
-
const file =
|
|
1013
|
-
if (Date.now() -
|
|
1126
|
+
const file = path4.join(dir, name);
|
|
1127
|
+
if (Date.now() - fs4.statSync(file).mtimeMs > maxAgeMs) fs4.unlinkSync(file);
|
|
1014
1128
|
} catch {
|
|
1015
1129
|
}
|
|
1016
1130
|
}
|
|
@@ -1019,9 +1133,9 @@ var StreamBuffer = class {
|
|
|
1019
1133
|
|
|
1020
1134
|
// src/internal/streaming/crash-recovery.ts
|
|
1021
1135
|
var crypto2 = __toESM(require("crypto"));
|
|
1022
|
-
var
|
|
1136
|
+
var fs5 = __toESM(require("fs"));
|
|
1023
1137
|
var os4 = __toESM(require("os"));
|
|
1024
|
-
var
|
|
1138
|
+
var path5 = __toESM(require("path"));
|
|
1025
1139
|
|
|
1026
1140
|
// src/internal/support/errors.ts
|
|
1027
1141
|
function errorMessage(error) {
|
|
@@ -1036,7 +1150,7 @@ function recoveryServer(serverUrl) {
|
|
|
1036
1150
|
function recoveryFilePath(serverUrl, projectName) {
|
|
1037
1151
|
const key = crypto2.createHash("sha256").update(`${recoveryServer(serverUrl)}
|
|
1038
1152
|
${projectName}`).digest("hex").slice(0, 16);
|
|
1039
|
-
return
|
|
1153
|
+
return path5.join(os4.tmpdir(), `piwi-dashboard-recovery-${key}.json`);
|
|
1040
1154
|
}
|
|
1041
1155
|
function refusesRun(error) {
|
|
1042
1156
|
return error instanceof HttpError && [400, 409, 422].includes(error.status);
|
|
@@ -1057,11 +1171,11 @@ var CrashRecovery = class {
|
|
|
1057
1171
|
const file = { v: 1, serverUrl: this.serverUrl, payload };
|
|
1058
1172
|
const temp = `${this.filePath}.${process.pid}.${crypto2.randomBytes(6).toString("hex")}.tmp`;
|
|
1059
1173
|
try {
|
|
1060
|
-
|
|
1061
|
-
|
|
1174
|
+
fs5.writeFileSync(temp, JSON.stringify(file), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
1175
|
+
fs5.renameSync(temp, this.filePath);
|
|
1062
1176
|
this.logger.info("Saved recovery data for later upload");
|
|
1063
1177
|
} catch (error) {
|
|
1064
|
-
|
|
1178
|
+
fs5.rmSync(temp, { force: true });
|
|
1065
1179
|
this.logger.error(`Failed to save recovery data: ${errorMessage(error)}`);
|
|
1066
1180
|
}
|
|
1067
1181
|
}
|
|
@@ -1072,26 +1186,26 @@ var CrashRecovery = class {
|
|
|
1072
1186
|
load() {
|
|
1073
1187
|
let fd;
|
|
1074
1188
|
try {
|
|
1075
|
-
fd =
|
|
1189
|
+
fd = fs5.openSync(this.filePath, fs5.constants.O_RDONLY | (fs5.constants.O_NOFOLLOW ?? 0));
|
|
1076
1190
|
} catch {
|
|
1077
1191
|
return null;
|
|
1078
1192
|
}
|
|
1079
1193
|
try {
|
|
1080
|
-
const stat =
|
|
1194
|
+
const stat = fs5.fstatSync(fd);
|
|
1081
1195
|
if (!stat.isFile() || typeof process.getuid === "function" && stat.uid !== process.getuid()) return null;
|
|
1082
|
-
const file = JSON.parse(
|
|
1196
|
+
const file = JSON.parse(fs5.readFileSync(fd, "utf8"));
|
|
1083
1197
|
if (file?.v !== 1 || file.serverUrl !== this.serverUrl) return null;
|
|
1084
1198
|
return file.payload && typeof file.payload === "object" ? file.payload : null;
|
|
1085
1199
|
} catch {
|
|
1086
1200
|
return null;
|
|
1087
1201
|
} finally {
|
|
1088
|
-
|
|
1202
|
+
fs5.closeSync(fd);
|
|
1089
1203
|
}
|
|
1090
1204
|
}
|
|
1091
1205
|
/** Delete the recovery file from disk */
|
|
1092
1206
|
clear() {
|
|
1093
1207
|
try {
|
|
1094
|
-
|
|
1208
|
+
fs5.rmSync(this.filePath, { force: true });
|
|
1095
1209
|
} catch {
|
|
1096
1210
|
}
|
|
1097
1211
|
}
|
|
@@ -1123,25 +1237,25 @@ var CrashRecovery = class {
|
|
|
1123
1237
|
};
|
|
1124
1238
|
|
|
1125
1239
|
// src/internal/files/file-handler.ts
|
|
1126
|
-
var
|
|
1240
|
+
var fs7 = __toESM(require("fs"));
|
|
1127
1241
|
var os5 = __toESM(require("os"));
|
|
1128
|
-
var
|
|
1242
|
+
var path7 = __toESM(require("path"));
|
|
1129
1243
|
var crypto3 = __toESM(require("crypto"));
|
|
1130
1244
|
|
|
1131
1245
|
// src/internal/files/compression.ts
|
|
1132
|
-
var
|
|
1133
|
-
var
|
|
1246
|
+
var fs6 = __toESM(require("fs"));
|
|
1247
|
+
var path6 = __toESM(require("path"));
|
|
1134
1248
|
var zlib = __toESM(require("zlib"));
|
|
1135
1249
|
var import_node_util = require("util");
|
|
1136
1250
|
var gzipAsync = (0, import_node_util.promisify)(zlib.gzip);
|
|
1137
1251
|
async function compressDirectory(sourceDir) {
|
|
1138
1252
|
const files = [];
|
|
1139
1253
|
function collect(dir, baseDir = "") {
|
|
1140
|
-
for (const entry of
|
|
1141
|
-
const full =
|
|
1142
|
-
const rel =
|
|
1254
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
1255
|
+
const full = path6.join(dir, entry.name);
|
|
1256
|
+
const rel = path6.join(baseDir, entry.name);
|
|
1143
1257
|
if (entry.isDirectory()) collect(full, rel);
|
|
1144
|
-
else if (entry.isFile()) files.push({ path: rel, content:
|
|
1258
|
+
else if (entry.isFile()) files.push({ path: rel, content: fs6.readFileSync(full) });
|
|
1145
1259
|
}
|
|
1146
1260
|
}
|
|
1147
1261
|
collect(sourceDir);
|
|
@@ -1205,7 +1319,8 @@ var ATTACHMENT_NAMES = {
|
|
|
1205
1319
|
userPick: "piwi-user-pick",
|
|
1206
1320
|
aiUsage: "piwi-ai-usage",
|
|
1207
1321
|
aiMeta: "piwi-ai-meta",
|
|
1208
|
-
resources: "piwi-resources"
|
|
1322
|
+
resources: "piwi-resources",
|
|
1323
|
+
pauses: "piwi-pauses"
|
|
1209
1324
|
};
|
|
1210
1325
|
var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
|
|
1211
1326
|
var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
|
|
@@ -1239,19 +1354,19 @@ var FileHandler = class {
|
|
|
1239
1354
|
}
|
|
1240
1355
|
/** Locate a Playwright HTML report directory containing `index.html`. Optionally override the search path. */
|
|
1241
1356
|
findHTMLReportDirectory(customDir) {
|
|
1242
|
-
const possibleDirs = customDir ? [customDir,
|
|
1357
|
+
const possibleDirs = customDir ? [customDir, path7.join(process.cwd(), customDir)] : ["playwright-report", "./playwright-report", path7.join(process.cwd(), "playwright-report")];
|
|
1243
1358
|
for (const reportDir of possibleDirs) {
|
|
1244
|
-
if (
|
|
1245
|
-
if (
|
|
1359
|
+
if (fs7.existsSync(reportDir) && fs7.statSync(reportDir).isDirectory()) {
|
|
1360
|
+
if (fs7.existsSync(path7.join(reportDir, "index.html"))) return reportDir;
|
|
1246
1361
|
}
|
|
1247
1362
|
}
|
|
1248
1363
|
return null;
|
|
1249
1364
|
}
|
|
1250
1365
|
/** Check if `dir` exists and is a directory; also checks `path.join(process.cwd(), dir)` */
|
|
1251
1366
|
findReportDirectory(dir) {
|
|
1252
|
-
const candidates = [dir,
|
|
1367
|
+
const candidates = [dir, path7.join(process.cwd(), dir)];
|
|
1253
1368
|
for (const candidate of candidates) {
|
|
1254
|
-
if (
|
|
1369
|
+
if (fs7.existsSync(candidate) && fs7.statSync(candidate).isDirectory()) return candidate;
|
|
1255
1370
|
}
|
|
1256
1371
|
return null;
|
|
1257
1372
|
}
|
|
@@ -1269,7 +1384,7 @@ var FileHandler = class {
|
|
|
1269
1384
|
const set = /* @__PURE__ */ new Set();
|
|
1270
1385
|
if (testCase.attachments) {
|
|
1271
1386
|
for (const a of testCase.attachments) {
|
|
1272
|
-
if (a.name === "trace" && a.path) set.add(
|
|
1387
|
+
if (a.name === "trace" && a.path) set.add(path7.resolve(a.path));
|
|
1273
1388
|
}
|
|
1274
1389
|
}
|
|
1275
1390
|
return Array.from(set);
|
|
@@ -1297,24 +1412,24 @@ var FileHandler = class {
|
|
|
1297
1412
|
cleanupBodyAttachments() {
|
|
1298
1413
|
if (!this.bodyDir) return;
|
|
1299
1414
|
try {
|
|
1300
|
-
|
|
1415
|
+
fs7.rmSync(this.bodyDir, { recursive: true, force: true });
|
|
1301
1416
|
} catch (error) {
|
|
1302
1417
|
this.logger.debug(`Could not remove temp attachments at ${this.bodyDir}: ${errorMessage(error)}`);
|
|
1303
1418
|
}
|
|
1304
1419
|
this.bodyDir = null;
|
|
1305
1420
|
}
|
|
1306
1421
|
resolvePathAttachment(a, testCase) {
|
|
1307
|
-
if (!a.path || !
|
|
1308
|
-
const size =
|
|
1422
|
+
if (!a.path || !fs7.existsSync(a.path)) return null;
|
|
1423
|
+
const size = fs7.statSync(a.path).size;
|
|
1309
1424
|
if (size > this.maxAttachmentBytes) {
|
|
1310
1425
|
this.warnOversized(a, testCase, size);
|
|
1311
1426
|
return null;
|
|
1312
1427
|
}
|
|
1313
1428
|
return {
|
|
1314
1429
|
name: a.name || "attachment",
|
|
1315
|
-
path:
|
|
1430
|
+
path: path7.resolve(a.path),
|
|
1316
1431
|
contentType: a.contentType || "application/octet-stream",
|
|
1317
|
-
originalName:
|
|
1432
|
+
originalName: path7.basename(a.path)
|
|
1318
1433
|
};
|
|
1319
1434
|
}
|
|
1320
1435
|
resolveBodyAttachment(a, testCase) {
|
|
@@ -1329,8 +1444,8 @@ var FileHandler = class {
|
|
|
1329
1444
|
let filePath2 = this.bodyFiles.get(a);
|
|
1330
1445
|
if (!filePath2) {
|
|
1331
1446
|
try {
|
|
1332
|
-
filePath2 =
|
|
1333
|
-
|
|
1447
|
+
filePath2 = path7.join(this.bodyAttachmentDir(), `${++this.bodyFileCount}-${originalName}`);
|
|
1448
|
+
fs7.writeFileSync(filePath2, body);
|
|
1334
1449
|
this.bodyFiles.set(a, filePath2);
|
|
1335
1450
|
} catch (error) {
|
|
1336
1451
|
this.logger.warn(`Could not stage attachment "${a.name}" for upload: ${errorMessage(error)}`);
|
|
@@ -1340,7 +1455,7 @@ var FileHandler = class {
|
|
|
1340
1455
|
return { name: a.name || "attachment", path: filePath2, contentType, originalName };
|
|
1341
1456
|
}
|
|
1342
1457
|
bodyAttachmentDir() {
|
|
1343
|
-
if (!this.bodyDir) this.bodyDir =
|
|
1458
|
+
if (!this.bodyDir) this.bodyDir = fs7.mkdtempSync(path7.join(os5.tmpdir(), "piwi-dashboard-attachments-"));
|
|
1344
1459
|
return this.bodyDir;
|
|
1345
1460
|
}
|
|
1346
1461
|
warnOversized(a, testCase, size) {
|
|
@@ -1363,10 +1478,10 @@ var FileHandler = class {
|
|
|
1363
1478
|
/** Parse Piwi-internal attachment bodies (`piwi-network`, `piwi-web-vitals`, `piwi-console`, `piwi-aria-snapshot`, `piwi-aria-snapshot-json`) into structured fields on the test case */
|
|
1364
1479
|
parsePerformanceAttachments(testCase, attachments) {
|
|
1365
1480
|
const find = (name) => attachments.find((a) => a.name === name);
|
|
1366
|
-
const
|
|
1367
|
-
if (
|
|
1481
|
+
const net2 = find(ATTACHMENT_NAMES.network);
|
|
1482
|
+
if (net2?.body) {
|
|
1368
1483
|
try {
|
|
1369
|
-
testCase.networkRequests = JSON.parse(
|
|
1484
|
+
testCase.networkRequests = JSON.parse(net2.body.toString());
|
|
1370
1485
|
} catch {
|
|
1371
1486
|
}
|
|
1372
1487
|
}
|
|
@@ -1422,17 +1537,17 @@ var FileHandler = class {
|
|
|
1422
1537
|
const tracePaths = this.findTraceFiles(testCase);
|
|
1423
1538
|
let lastPath = null;
|
|
1424
1539
|
for (const tp of tracePaths) {
|
|
1425
|
-
if (
|
|
1540
|
+
if (fs7.existsSync(tp)) lastPath = tp;
|
|
1426
1541
|
}
|
|
1427
1542
|
if (!lastPath) return null;
|
|
1428
1543
|
const hash = crypto3.createHash("sha256");
|
|
1429
|
-
await new Promise((
|
|
1430
|
-
|
|
1544
|
+
await new Promise((resolve11, reject) => {
|
|
1545
|
+
fs7.createReadStream(lastPath).on("data", (chunk) => hash.update(chunk)).on("end", resolve11).on("error", reject);
|
|
1431
1546
|
});
|
|
1432
1547
|
return {
|
|
1433
1548
|
tracePath: lastPath,
|
|
1434
1549
|
hash: hash.digest("hex"),
|
|
1435
|
-
size:
|
|
1550
|
+
size: fs7.statSync(lastPath).size
|
|
1436
1551
|
};
|
|
1437
1552
|
}
|
|
1438
1553
|
/** Ask the server which trace hashes it already has, returning the set of hashes that are missing */
|
|
@@ -1459,14 +1574,14 @@ function formatMiB(bytes) {
|
|
|
1459
1574
|
var import_node_child_process = require("child_process");
|
|
1460
1575
|
|
|
1461
1576
|
// src/internal/support/test-paths.ts
|
|
1462
|
-
var
|
|
1577
|
+
var path8 = __toESM(require("path"));
|
|
1463
1578
|
function configDirFromConfig(config) {
|
|
1464
|
-
if (config?.configFile) return
|
|
1579
|
+
if (config?.configFile) return path8.dirname(config.configFile);
|
|
1465
1580
|
if (config?.rootDir) return config.rootDir;
|
|
1466
1581
|
return process.cwd();
|
|
1467
1582
|
}
|
|
1468
1583
|
function relativeTestPath(file, base) {
|
|
1469
|
-
return
|
|
1584
|
+
return path8.relative(base, file).split(path8.sep).join("/");
|
|
1470
1585
|
}
|
|
1471
1586
|
|
|
1472
1587
|
// src/internal/collect/metadata-collector.ts
|
|
@@ -1526,11 +1641,6 @@ function resolveScmBaseBranch(env) {
|
|
|
1526
1641
|
const trimmed = target?.trim();
|
|
1527
1642
|
return trimmed ? normalizeRef(trimmed) : void 0;
|
|
1528
1643
|
}
|
|
1529
|
-
function detectCi(env) {
|
|
1530
|
-
return Boolean(
|
|
1531
|
-
env.JENKINS_URL || env.GITHUB_ACTIONS || env.GITLAB_CI || env.CIRCLECI || env.TRAVIS || env.TF_BUILD || env.BITBUCKET_BUILD_NUMBER || env.CI && env.CI !== "false" && env.CI !== "0"
|
|
1532
|
-
);
|
|
1533
|
-
}
|
|
1534
1644
|
function resolveRunOrigin(env, warn) {
|
|
1535
1645
|
const raw = env[PIWI_ORIGIN_ENV.kind]?.trim();
|
|
1536
1646
|
let kind = detectCi(env) ? "ci" : "local";
|
|
@@ -1769,7 +1879,7 @@ var MetadataCollector = class {
|
|
|
1769
1879
|
};
|
|
1770
1880
|
|
|
1771
1881
|
// src/internal/streaming/stream-manager.ts
|
|
1772
|
-
var
|
|
1882
|
+
var fs9 = __toESM(require("fs"));
|
|
1773
1883
|
|
|
1774
1884
|
// src/internal/streaming/bounded-queue.ts
|
|
1775
1885
|
var DROP_TIER = {
|
|
@@ -1908,10 +2018,10 @@ function createLimiter(maxConcurrent) {
|
|
|
1908
2018
|
if (run) run();
|
|
1909
2019
|
};
|
|
1910
2020
|
return function limit(fn) {
|
|
1911
|
-
return new Promise((
|
|
2021
|
+
return new Promise((resolve11, reject) => {
|
|
1912
2022
|
const run = () => {
|
|
1913
2023
|
active++;
|
|
1914
|
-
Promise.resolve().then(fn).then(
|
|
2024
|
+
Promise.resolve().then(fn).then(resolve11, reject).finally(next);
|
|
1915
2025
|
};
|
|
1916
2026
|
if (active < limitValue) run();
|
|
1917
2027
|
else queue.push(run);
|
|
@@ -1920,18 +2030,18 @@ function createLimiter(maxConcurrent) {
|
|
|
1920
2030
|
}
|
|
1921
2031
|
|
|
1922
2032
|
// src/internal/support/setup-file.ts
|
|
1923
|
-
var
|
|
2033
|
+
var path9 = __toESM(require("path"));
|
|
1924
2034
|
var os6 = __toESM(require("os"));
|
|
1925
|
-
var
|
|
2035
|
+
var fs8 = __toESM(require("fs"));
|
|
1926
2036
|
function getSetupFilePath(projectName) {
|
|
1927
|
-
return
|
|
2037
|
+
return path9.join(os6.tmpdir(), `piwi-dashboard-setup-${hashForProject(projectName)}.json`);
|
|
1928
2038
|
}
|
|
1929
2039
|
function readSetupInfo(projectName) {
|
|
1930
2040
|
const setupFile = getSetupFilePath(projectName);
|
|
1931
2041
|
try {
|
|
1932
|
-
if (
|
|
1933
|
-
const info = JSON.parse(
|
|
1934
|
-
|
|
2042
|
+
if (fs8.existsSync(setupFile)) {
|
|
2043
|
+
const info = JSON.parse(fs8.readFileSync(setupFile, "utf8"));
|
|
2044
|
+
fs8.unlinkSync(setupFile);
|
|
1935
2045
|
if (info.projectName === projectName) return info;
|
|
1936
2046
|
}
|
|
1937
2047
|
} catch {
|
|
@@ -2150,7 +2260,7 @@ var StreamManager = class {
|
|
|
2150
2260
|
} catch (error) {
|
|
2151
2261
|
if (error instanceof HttpError && (error.status === 401 || error.status === 403)) {
|
|
2152
2262
|
this.logger.warn(
|
|
2153
|
-
`Live streaming could not start: the dashboard at ${this.options.serverUrl} requires authentication. Set the reporter's \`apiKey\` option
|
|
2263
|
+
`Live streaming could not start: the dashboard at ${this.options.serverUrl} requires authentication. Set the reporter's \`apiKey\` option, or PIWI_API_KEY in the environment or in the .env file next to the Playwright config \u2014 create a key under Settings \u2192 Users on the dashboard. Falling back to batch upload at the end of the run.`
|
|
2154
2264
|
);
|
|
2155
2265
|
} else {
|
|
2156
2266
|
this.logger.warn(
|
|
@@ -2387,7 +2497,7 @@ var StreamManager = class {
|
|
|
2387
2497
|
this.logger.debugError(
|
|
2388
2498
|
`${this.pendingEvents.length} events pending, retrying (attempt ${attempt + 1}/${MAX_ATTEMPTS})...`
|
|
2389
2499
|
);
|
|
2390
|
-
await new Promise((
|
|
2500
|
+
await new Promise((resolve11) => setTimeout(resolve11, Math.min(1e3 * Math.pow(2, attempt), 1e4)));
|
|
2391
2501
|
}
|
|
2392
2502
|
if (!this.pendingEvents.isEmpty) {
|
|
2393
2503
|
const remaining = this.pendingEvents.takeAll();
|
|
@@ -2431,7 +2541,7 @@ var StreamManager = class {
|
|
|
2431
2541
|
}
|
|
2432
2542
|
/** Schedule a live upload of trace and attachment files for a test case. Skips cases with no files. Concurrency is limited to 2 simultaneous uploads. */
|
|
2433
2543
|
scheduleLiveUpload(tc) {
|
|
2434
|
-
const hasTrace = !!this.options.uploadTraces && this.fileHandler.findTraceFiles(tc).some((p) =>
|
|
2544
|
+
const hasTrace = !!this.options.uploadTraces && this.fileHandler.findTraceFiles(tc).some((p) => fs9.existsSync(p));
|
|
2435
2545
|
const hasAttachments = this.fileHandler.findAllAttachments(tc).length > 0;
|
|
2436
2546
|
if (!hasTrace && !hasAttachments) return;
|
|
2437
2547
|
const promise = (async () => {
|
|
@@ -2444,7 +2554,7 @@ var StreamManager = class {
|
|
|
2444
2554
|
if (flush) await flush;
|
|
2445
2555
|
const delays = [0, 1e3, 3e3];
|
|
2446
2556
|
for (let attempt = 0; attempt < delays.length; attempt++) {
|
|
2447
|
-
if (delays[attempt]) await new Promise((
|
|
2557
|
+
if (delays[attempt]) await new Promise((resolve11) => setTimeout(resolve11, delays[attempt]));
|
|
2448
2558
|
try {
|
|
2449
2559
|
await this.limitLiveUpload(
|
|
2450
2560
|
() => this.uploader.uploadCaseFiles(
|
|
@@ -3259,7 +3369,7 @@ function maskUrlFragment(hash) {
|
|
|
3259
3369
|
}
|
|
3260
3370
|
|
|
3261
3371
|
// ../core/src/locator-break.ts
|
|
3262
|
-
var filePath = (
|
|
3372
|
+
var filePath = (path34) => path34.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3263
3373
|
function sameFilePath(a, b) {
|
|
3264
3374
|
const x = filePath(a);
|
|
3265
3375
|
const y = filePath(b);
|
|
@@ -3554,23 +3664,48 @@ function flattenSteps(steps, depth = 0) {
|
|
|
3554
3664
|
}
|
|
3555
3665
|
return result;
|
|
3556
3666
|
}
|
|
3557
|
-
function
|
|
3667
|
+
function epochMs(value) {
|
|
3668
|
+
if (value instanceof Date) return value.getTime();
|
|
3669
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3670
|
+
}
|
|
3671
|
+
function pausedOverlap(start, duration, pauses) {
|
|
3672
|
+
const from = epochMs(start);
|
|
3673
|
+
const length = typeof duration === "number" && duration > 0 ? duration : 0;
|
|
3674
|
+
if (from === null || length === 0 || pauses.length === 0) return 0;
|
|
3675
|
+
const to = from + length;
|
|
3676
|
+
const spans = pauses.filter((p) => p.duration > 0).map((p) => [Math.max(from, p.startedAt), Math.min(to, p.startedAt + p.duration)]).filter(([a, b]) => b > a).sort((x, y) => x[0] - y[0]);
|
|
3677
|
+
let ms = 0;
|
|
3678
|
+
let reached = from;
|
|
3679
|
+
for (const [a, b] of spans) {
|
|
3680
|
+
const begin = Math.max(a, reached);
|
|
3681
|
+
if (b > begin) ms += b - begin;
|
|
3682
|
+
reached = Math.max(reached, b);
|
|
3683
|
+
}
|
|
3684
|
+
return ms;
|
|
3685
|
+
}
|
|
3686
|
+
function unpausedDuration(step, pauses) {
|
|
3687
|
+
const duration = typeof step.duration === "number" && step.duration > 0 ? step.duration : 0;
|
|
3688
|
+
return duration - pausedOverlap(step.startTime, duration, pauses);
|
|
3689
|
+
}
|
|
3690
|
+
function collectStepMetrics(steps, errors, pauses = []) {
|
|
3558
3691
|
const flatSteps = flattenSteps(steps);
|
|
3559
3692
|
if (errors) markRecoveredSteps(flatSteps, errors);
|
|
3560
|
-
const
|
|
3693
|
+
const own = (s) => pauses.length > 0 && s.duration > 0 ? unpausedDuration(s, pauses) : s.duration;
|
|
3694
|
+
const totalStepDuration = steps.reduce((sum, s) => sum + (own(s) || 0), 0);
|
|
3561
3695
|
let slowestStep = null;
|
|
3562
3696
|
for (const s of flatSteps) {
|
|
3563
|
-
|
|
3697
|
+
const duration = own(s);
|
|
3698
|
+
if (!slowestStep || duration > slowestStep.duration) slowestStep = { title: s.title, duration };
|
|
3564
3699
|
}
|
|
3565
3700
|
const navSteps = flatSteps.filter((s) => s.category === "navigation");
|
|
3566
3701
|
const waitSteps = flatSteps.filter((s) => s.category === "wait");
|
|
3567
|
-
const waitTotalDuration = waitSteps.reduce((sum, s) => sum + (s
|
|
3702
|
+
const waitTotalDuration = waitSteps.reduce((sum, s) => sum + (own(s) || 0), 0);
|
|
3568
3703
|
return {
|
|
3569
3704
|
steps: flatSteps,
|
|
3570
3705
|
totalStepDuration,
|
|
3571
3706
|
slowestStep,
|
|
3572
3707
|
navigationCount: navSteps.length,
|
|
3573
|
-
navigationTotalDuration: navSteps.reduce((sum, s) => sum + (s
|
|
3708
|
+
navigationTotalDuration: navSteps.reduce((sum, s) => sum + (own(s) || 0), 0),
|
|
3574
3709
|
waitTotalDuration,
|
|
3575
3710
|
waitCount: waitSteps.length
|
|
3576
3711
|
};
|
|
@@ -3686,19 +3821,43 @@ function extractWaitEvents(steps, insideWait = false) {
|
|
|
3686
3821
|
return events;
|
|
3687
3822
|
}
|
|
3688
3823
|
|
|
3824
|
+
// src/internal/collect/pause-events.ts
|
|
3825
|
+
function readPauses(body) {
|
|
3826
|
+
if (!body) return [];
|
|
3827
|
+
try {
|
|
3828
|
+
const value = JSON.parse(body.toString("utf8"));
|
|
3829
|
+
if (!Array.isArray(value)) return [];
|
|
3830
|
+
return value.filter(
|
|
3831
|
+
(p) => !!p && typeof p === "object" && typeof p.location === "string" && typeof p.startedAt === "number" && typeof p.duration === "number"
|
|
3832
|
+
);
|
|
3833
|
+
} catch {
|
|
3834
|
+
return [];
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
function pauseStepEvents(pauses) {
|
|
3838
|
+
return pauses.map((pause) => ({
|
|
3839
|
+
title: typeof pause.title === "string" ? pause.title : "",
|
|
3840
|
+
category: "pause",
|
|
3841
|
+
startedAt: pause.startedAt,
|
|
3842
|
+
duration: Math.max(0, pause.duration),
|
|
3843
|
+
status: typeof pause.reason === "string" ? pause.reason : "breakpoint",
|
|
3844
|
+
location: pause.location
|
|
3845
|
+
}));
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3689
3848
|
// src/internal/support/reporter-version.ts
|
|
3690
|
-
var
|
|
3691
|
-
var
|
|
3849
|
+
var fs10 = __toESM(require("fs"));
|
|
3850
|
+
var path10 = __toESM(require("path"));
|
|
3692
3851
|
var cachedVersion = null;
|
|
3693
3852
|
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
3694
3853
|
function findOwnPackageJson(fromDir) {
|
|
3695
3854
|
for (const offset of CANDIDATE_OFFSETS) {
|
|
3696
3855
|
try {
|
|
3697
|
-
const pkgPath =
|
|
3698
|
-
const pkg = JSON.parse(
|
|
3856
|
+
const pkgPath = path10.resolve(fromDir, offset, "package.json");
|
|
3857
|
+
const pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
|
|
3699
3858
|
const { name, version } = pkg;
|
|
3700
3859
|
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
3701
|
-
return { version, root:
|
|
3860
|
+
return { version, root: path10.dirname(pkgPath) };
|
|
3702
3861
|
} catch {
|
|
3703
3862
|
}
|
|
3704
3863
|
}
|
|
@@ -3711,11 +3870,11 @@ function getReporterVersion() {
|
|
|
3711
3870
|
}
|
|
3712
3871
|
|
|
3713
3872
|
// src/internal/support/source-snippet.ts
|
|
3714
|
-
var
|
|
3715
|
-
var
|
|
3873
|
+
var fs11 = __toESM(require("fs"));
|
|
3874
|
+
var path11 = __toESM(require("path"));
|
|
3716
3875
|
function readSourceSnippet(file, declLine, context, failingLine) {
|
|
3717
3876
|
try {
|
|
3718
|
-
const content =
|
|
3877
|
+
const content = fs11.readFileSync(file, "utf-8");
|
|
3719
3878
|
const lines = content.split("\n");
|
|
3720
3879
|
const anchor = failingLine ?? declLine;
|
|
3721
3880
|
const start = Math.max(0, anchor - context - 1);
|
|
@@ -3755,16 +3914,16 @@ function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
|
3755
3914
|
if (picked.length >= maxFrames) break;
|
|
3756
3915
|
let abs;
|
|
3757
3916
|
try {
|
|
3758
|
-
abs =
|
|
3917
|
+
abs = path11.resolve(frame.file);
|
|
3759
3918
|
} catch {
|
|
3760
3919
|
continue;
|
|
3761
3920
|
}
|
|
3762
|
-
const rel =
|
|
3763
|
-
if (rel.startsWith("..") ||
|
|
3921
|
+
const rel = path11.relative(projectRoot, abs);
|
|
3922
|
+
if (rel.startsWith("..") || path11.isAbsolute(rel)) continue;
|
|
3764
3923
|
if (!isNaN(frame.line)) add(abs, frame.line);
|
|
3765
3924
|
}
|
|
3766
3925
|
}
|
|
3767
|
-
const absTest =
|
|
3926
|
+
const absTest = path11.resolve(testFile2);
|
|
3768
3927
|
if (!picked.some((p) => p.absFile === absTest)) {
|
|
3769
3928
|
add(absTest, extractFailingLine(errorText, testFile2, declLine));
|
|
3770
3929
|
}
|
|
@@ -3772,49 +3931,23 @@ function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
|
3772
3931
|
for (const p of picked.slice(0, maxFrames)) {
|
|
3773
3932
|
const snippet = readSourceSnippet(p.absFile, p.line, context, p.line);
|
|
3774
3933
|
if (snippet) {
|
|
3775
|
-
frames.push({ file:
|
|
3934
|
+
frames.push({ file: path11.relative(projectRoot, p.absFile) || path11.basename(p.absFile), line: p.line, snippet });
|
|
3776
3935
|
}
|
|
3777
3936
|
}
|
|
3778
3937
|
return frames;
|
|
3779
3938
|
}
|
|
3780
3939
|
function extractFailingLine(errorText, testFile2, declarationLine) {
|
|
3781
3940
|
if (!errorText) return declarationLine;
|
|
3782
|
-
const expectedFile =
|
|
3941
|
+
const expectedFile = path11.resolve(testFile2);
|
|
3783
3942
|
for (const frame of stackFrames(errorText)) {
|
|
3784
3943
|
try {
|
|
3785
|
-
if (
|
|
3944
|
+
if (path11.resolve(frame.file) === expectedFile && !isNaN(frame.line)) return frame.line;
|
|
3786
3945
|
} catch {
|
|
3787
3946
|
}
|
|
3788
3947
|
}
|
|
3789
3948
|
return declarationLine;
|
|
3790
3949
|
}
|
|
3791
3950
|
|
|
3792
|
-
// src/internal/support/ci.ts
|
|
3793
|
-
function detectCiIds(env) {
|
|
3794
|
-
if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) {
|
|
3795
|
-
const run = env.GITHUB_RUN_ATTEMPT ? `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` : env.GITHUB_RUN_ID;
|
|
3796
|
-
return { run, job: env.GITHUB_JOB || null };
|
|
3797
|
-
}
|
|
3798
|
-
if (env.GITLAB_CI && env.CI_PIPELINE_ID) return { run: env.CI_PIPELINE_ID, job: env.CI_JOB_ID || null };
|
|
3799
|
-
if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return { run: env.CIRCLE_WORKFLOW_ID, job: env.CIRCLE_BUILD_NUM || null };
|
|
3800
|
-
if (env.TRAVIS && env.TRAVIS_BUILD_ID) return { run: env.TRAVIS_BUILD_ID, job: env.TRAVIS_JOB_ID || null };
|
|
3801
|
-
if (env.TF_BUILD && env.BUILD_BUILDID) return { run: env.BUILD_BUILDID, job: env.SYSTEM_JOBID || null };
|
|
3802
|
-
if (env.JENKINS_URL && env.BUILD_ID) return { run: env.BUILD_ID, job: null };
|
|
3803
|
-
if (env.BUILDKITE_BUILD_ID) return { run: env.BUILDKITE_BUILD_ID, job: env.BUILDKITE_JOB_ID || null };
|
|
3804
|
-
if (env.TEAMCITY_BUILD_ID) return { run: env.TEAMCITY_BUILD_ID, job: null };
|
|
3805
|
-
if (env.BITBUCKET_BUILD_NUMBER) return { run: env.BITBUCKET_BUILD_NUMBER, job: env.BITBUCKET_STEP_UUID || null };
|
|
3806
|
-
if (env.SEMAPHORE_WORKFLOW_ID) return { run: env.SEMAPHORE_WORKFLOW_ID, job: env.SEMAPHORE_JOB_ID || null };
|
|
3807
|
-
if (env.APPVEYOR_BUILD_ID) return { run: env.APPVEYOR_BUILD_ID, job: env.APPVEYOR_JOB_ID || null };
|
|
3808
|
-
if (env.DRONE_BUILD_NUMBER) return { run: env.DRONE_BUILD_NUMBER, job: null };
|
|
3809
|
-
return null;
|
|
3810
|
-
}
|
|
3811
|
-
function resolveRunLabel(configured, sharded) {
|
|
3812
|
-
if (configured) return configured;
|
|
3813
|
-
const ci = detectCiIds(process.env);
|
|
3814
|
-
if (!ci) return null;
|
|
3815
|
-
return !sharded && ci.job ? `${ci.run}|${ci.job}` : ci.run;
|
|
3816
|
-
}
|
|
3817
|
-
|
|
3818
3951
|
// src/internal/support/worker-index.ts
|
|
3819
3952
|
function workerIndexOf(result) {
|
|
3820
3953
|
if (!result) return null;
|
|
@@ -3900,28 +4033,28 @@ function isListMode(argv = process.argv) {
|
|
|
3900
4033
|
}
|
|
3901
4034
|
|
|
3902
4035
|
// src/public/global-setup.ts
|
|
3903
|
-
var
|
|
3904
|
-
var
|
|
4036
|
+
var path15 = __toESM(require("path"));
|
|
4037
|
+
var fs15 = __toESM(require("fs"));
|
|
3905
4038
|
|
|
3906
4039
|
// src/internal/support/aria-sampling.ts
|
|
3907
|
-
var
|
|
4040
|
+
var path12 = __toESM(require("path"));
|
|
3908
4041
|
var os7 = __toESM(require("os"));
|
|
3909
|
-
var
|
|
4042
|
+
var fs12 = __toESM(require("fs"));
|
|
3910
4043
|
function ariaSampleIdentity(filePath2, title) {
|
|
3911
4044
|
return `${filePath2}\0${title}`;
|
|
3912
4045
|
}
|
|
3913
4046
|
function getAriaSampleFilePath(projectName) {
|
|
3914
|
-
return
|
|
4047
|
+
return path12.join(os7.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
3915
4048
|
}
|
|
3916
4049
|
function writeAriaSampleFile(projectName, identities) {
|
|
3917
4050
|
try {
|
|
3918
|
-
|
|
4051
|
+
fs12.writeFileSync(getAriaSampleFilePath(projectName), JSON.stringify({ projectName, identities }));
|
|
3919
4052
|
} catch {
|
|
3920
4053
|
}
|
|
3921
4054
|
}
|
|
3922
4055
|
function clearAriaSampleFile(projectName) {
|
|
3923
4056
|
try {
|
|
3924
|
-
|
|
4057
|
+
fs12.rmSync(getAriaSampleFilePath(projectName), { force: true });
|
|
3925
4058
|
} catch {
|
|
3926
4059
|
}
|
|
3927
4060
|
}
|
|
@@ -3930,7 +4063,7 @@ function loadAriaSampleSet(projectName) {
|
|
|
3930
4063
|
if (cachedSets.has(projectName)) return cachedSets.get(projectName);
|
|
3931
4064
|
let set = null;
|
|
3932
4065
|
try {
|
|
3933
|
-
const raw =
|
|
4066
|
+
const raw = fs12.readFileSync(getAriaSampleFilePath(projectName), "utf8");
|
|
3934
4067
|
const parsed2 = JSON.parse(raw);
|
|
3935
4068
|
if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
|
|
3936
4069
|
set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
|
|
@@ -4060,7 +4193,7 @@ async function checkBaseUrl(target, options = {}) {
|
|
|
4060
4193
|
} else {
|
|
4061
4194
|
reason = outcome.error;
|
|
4062
4195
|
}
|
|
4063
|
-
if (attempt < attempts) await new Promise((
|
|
4196
|
+
if (attempt < attempts) await new Promise((resolve11) => setTimeout(resolve11, delayMs));
|
|
4064
4197
|
}
|
|
4065
4198
|
return `${reason} (${attempts} ${attempts === 1 ? "attempt" : "attempts"})`;
|
|
4066
4199
|
}
|
|
@@ -4084,14 +4217,14 @@ async function checkBaseUrls(config, options = {}) {
|
|
|
4084
4217
|
}
|
|
4085
4218
|
|
|
4086
4219
|
// src/internal/manifest/collect.ts
|
|
4087
|
-
var
|
|
4088
|
-
var
|
|
4220
|
+
var fs13 = __toESM(require("fs"));
|
|
4221
|
+
var path13 = __toESM(require("path"));
|
|
4089
4222
|
|
|
4090
4223
|
// src/internal/transport/backend-request.ts
|
|
4091
4224
|
var http2 = __toESM(require("http"));
|
|
4092
4225
|
var https2 = __toESM(require("https"));
|
|
4093
4226
|
function backendRequest(url, init) {
|
|
4094
|
-
return new Promise((
|
|
4227
|
+
return new Promise((resolve11, reject) => {
|
|
4095
4228
|
const target = new URL(url);
|
|
4096
4229
|
const headers = { ...init.headers };
|
|
4097
4230
|
if (init.body !== void 0) headers["Content-Length"] = Buffer.byteLength(init.body);
|
|
@@ -4106,7 +4239,7 @@ function backendRequest(url, init) {
|
|
|
4106
4239
|
});
|
|
4107
4240
|
res.on(
|
|
4108
4241
|
"end",
|
|
4109
|
-
() =>
|
|
4242
|
+
() => resolve11({ status: res.statusCode ?? 0, headers: res.headers, text: Buffer.concat(chunks).toString("utf8") })
|
|
4110
4243
|
);
|
|
4111
4244
|
res.on("error", reject);
|
|
4112
4245
|
res.on("close", () => {
|
|
@@ -4151,10 +4284,10 @@ function hasInstrumentationHeader(headers) {
|
|
|
4151
4284
|
return INSTRUMENTATION_HEADERS.some((h) => get(h) != null && get(h) !== "");
|
|
4152
4285
|
}
|
|
4153
4286
|
function readCommittedManifest(configDir) {
|
|
4154
|
-
const file =
|
|
4287
|
+
const file = path13.join(configDir, COMMITTED_MANIFEST_FILE);
|
|
4155
4288
|
try {
|
|
4156
|
-
if (!
|
|
4157
|
-
return parseManifestJson(
|
|
4289
|
+
if (!fs13.existsSync(file)) return null;
|
|
4290
|
+
return parseManifestJson(fs13.readFileSync(file, "utf8"));
|
|
4158
4291
|
} catch {
|
|
4159
4292
|
return null;
|
|
4160
4293
|
}
|
|
@@ -4180,8 +4313,8 @@ function backendManifestFetch(insecure) {
|
|
|
4180
4313
|
function instrumentationManifestUrls(baseUrl) {
|
|
4181
4314
|
const base = new URL(baseUrl);
|
|
4182
4315
|
const root = new URL(INSTRUMENTATION_MANIFEST_PATH, base).toString();
|
|
4183
|
-
const
|
|
4184
|
-
return
|
|
4316
|
+
const path34 = base.pathname.replace(/\/+$/, "");
|
|
4317
|
+
return path34 ? [new URL(`${path34}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
|
|
4185
4318
|
}
|
|
4186
4319
|
async function fetchInstrumentationManifest(baseUrl, fetchImpl = fetch) {
|
|
4187
4320
|
try {
|
|
@@ -4319,13 +4452,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
4319
4452
|
}
|
|
4320
4453
|
function normalizeCoveragePath(raw) {
|
|
4321
4454
|
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
4322
|
-
const
|
|
4323
|
-
if (!
|
|
4324
|
-
if (/(^|\/)node_modules\//.test(
|
|
4325
|
-
return
|
|
4455
|
+
const path34 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4456
|
+
if (!path34 || path34.startsWith("../") || path34.startsWith("/") || /^[A-Za-z]:\//.test(path34)) return null;
|
|
4457
|
+
if (/(^|\/)node_modules\//.test(path34)) return null;
|
|
4458
|
+
return path34;
|
|
4326
4459
|
}
|
|
4327
|
-
function fileEntry(
|
|
4328
|
-
const file = { path:
|
|
4460
|
+
function fileEntry(path34, runtime, blob, functions = [], lines = []) {
|
|
4461
|
+
const file = { path: path34, runtime };
|
|
4329
4462
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
4330
4463
|
if (functions.length > 0) file.functions = functions;
|
|
4331
4464
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -4344,11 +4477,11 @@ function sanitizeCoverage(raw) {
|
|
|
4344
4477
|
const files = [];
|
|
4345
4478
|
for (const item of raw.files) {
|
|
4346
4479
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
4347
|
-
const
|
|
4348
|
-
if (
|
|
4480
|
+
const path34 = normalizeCoveragePath(item.path);
|
|
4481
|
+
if (path34 === null) continue;
|
|
4349
4482
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
4350
4483
|
files.push(
|
|
4351
|
-
fileEntry(
|
|
4484
|
+
fileEntry(path34, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
4352
4485
|
);
|
|
4353
4486
|
}
|
|
4354
4487
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -4413,8 +4546,8 @@ function reduceCoverage(capture, granularity) {
|
|
|
4413
4546
|
|
|
4414
4547
|
// src/internal/collect/source-checksums.ts
|
|
4415
4548
|
var crypto4 = __toESM(require("crypto"));
|
|
4416
|
-
var
|
|
4417
|
-
var
|
|
4549
|
+
var fs14 = __toESM(require("fs"));
|
|
4550
|
+
var path14 = __toESM(require("path"));
|
|
4418
4551
|
var CHECKSUM = /^(sha1|sha256):[0-9a-f]{40,64}$/;
|
|
4419
4552
|
function checksumsOf(raw) {
|
|
4420
4553
|
if (!Array.isArray(raw)) return [];
|
|
@@ -4479,10 +4612,10 @@ var SourceChecksums = class {
|
|
|
4479
4612
|
}
|
|
4480
4613
|
async read(relativePath2) {
|
|
4481
4614
|
try {
|
|
4482
|
-
const root =
|
|
4483
|
-
const file =
|
|
4484
|
-
if (!file.startsWith(root +
|
|
4485
|
-
return contentChecksums(await
|
|
4615
|
+
const root = path14.resolve(this.repoRoot());
|
|
4616
|
+
const file = path14.resolve(root, relativePath2);
|
|
4617
|
+
if (!file.startsWith(root + path14.sep)) return null;
|
|
4618
|
+
return contentChecksums(await fs14.promises.readFile(file));
|
|
4486
4619
|
} catch {
|
|
4487
4620
|
return null;
|
|
4488
4621
|
}
|
|
@@ -4627,13 +4760,13 @@ var ServerCoverageCollector = class {
|
|
|
4627
4760
|
if (parts.length === 0 || this.closer.signal.aborted) {
|
|
4628
4761
|
return Promise.resolve({ files: [], incomplete: truncated || parts.length > 0 });
|
|
4629
4762
|
}
|
|
4630
|
-
return new Promise((
|
|
4763
|
+
return new Promise((resolve11) => {
|
|
4631
4764
|
const entry = {
|
|
4632
4765
|
pending: parts.length,
|
|
4633
4766
|
captures: [],
|
|
4634
4767
|
checksums: /* @__PURE__ */ new Map(),
|
|
4635
4768
|
incomplete: truncated,
|
|
4636
|
-
resolve:
|
|
4769
|
+
resolve: resolve11
|
|
4637
4770
|
};
|
|
4638
4771
|
for (const [origin, ids] of parts) this.enqueue(origin, { entry, ids });
|
|
4639
4772
|
});
|
|
@@ -4959,14 +5092,14 @@ var ServerCoverageCollector = class {
|
|
|
4959
5092
|
// src/public/global-setup.ts
|
|
4960
5093
|
function createGlobalSetup(options, userSetup) {
|
|
4961
5094
|
return async function globalSetupFn(config) {
|
|
4962
|
-
const piwiReporterPath =
|
|
5095
|
+
const piwiReporterPath = path15.resolve(__dirname, "./index.js");
|
|
4963
5096
|
let inlineReporterOptions = {};
|
|
4964
5097
|
if (Array.isArray(config?.reporter)) {
|
|
4965
5098
|
for (const r of config.reporter) {
|
|
4966
5099
|
if (!Array.isArray(r) || typeof r[0] !== "string") continue;
|
|
4967
5100
|
const isPiwi = r[0].toLowerCase().includes("piwi") || (() => {
|
|
4968
5101
|
try {
|
|
4969
|
-
return
|
|
5102
|
+
return path15.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
4970
5103
|
} catch {
|
|
4971
5104
|
return false;
|
|
4972
5105
|
}
|
|
@@ -4977,7 +5110,10 @@ function createGlobalSetup(options, userSetup) {
|
|
|
4977
5110
|
}
|
|
4978
5111
|
}
|
|
4979
5112
|
}
|
|
4980
|
-
const opts = resolveOptions(
|
|
5113
|
+
const opts = resolveOptions(
|
|
5114
|
+
{ ...inlineReporterOptions, ...options },
|
|
5115
|
+
configDirFromConfig(config)
|
|
5116
|
+
);
|
|
4981
5117
|
const logger = new Logger(opts.verbose ?? false);
|
|
4982
5118
|
if (isUiMode()) {
|
|
4983
5119
|
logger.debug("UI mode detected \u2014 skipping run registration.");
|
|
@@ -5002,7 +5138,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
5002
5138
|
if (!Array.isArray(r) || typeof r[0] !== "string") return false;
|
|
5003
5139
|
if (r[0].toLowerCase().includes("piwi")) return true;
|
|
5004
5140
|
try {
|
|
5005
|
-
return
|
|
5141
|
+
return path15.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
5006
5142
|
} catch {
|
|
5007
5143
|
return false;
|
|
5008
5144
|
}
|
|
@@ -5032,7 +5168,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
5032
5168
|
auth
|
|
5033
5169
|
);
|
|
5034
5170
|
if (response?.runId && response?.setupToken) {
|
|
5035
|
-
|
|
5171
|
+
fs15.writeFileSync(
|
|
5036
5172
|
getSetupFilePath(opts.projectName),
|
|
5037
5173
|
JSON.stringify({
|
|
5038
5174
|
runId: response.runId,
|
|
@@ -5086,11 +5222,11 @@ function createGlobalSetup(options, userSetup) {
|
|
|
5086
5222
|
}
|
|
5087
5223
|
|
|
5088
5224
|
// src/public/config-wrapper.ts
|
|
5089
|
-
var
|
|
5090
|
-
var
|
|
5225
|
+
var path16 = __toESM(require("path"));
|
|
5226
|
+
var fs18 = __toESM(require("fs"));
|
|
5091
5227
|
|
|
5092
5228
|
// src/internal/probe/mode.ts
|
|
5093
|
-
var
|
|
5229
|
+
var fs16 = __toESM(require("fs"));
|
|
5094
5230
|
|
|
5095
5231
|
// src/internal/probe/plan.ts
|
|
5096
5232
|
function probeRunMetadata(base = {}) {
|
|
@@ -5102,8 +5238,8 @@ function parseRouteKey(routeKey2) {
|
|
|
5102
5238
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
5103
5239
|
}
|
|
5104
5240
|
function patternToRegex(pattern) {
|
|
5105
|
-
const
|
|
5106
|
-
const escaped =
|
|
5241
|
+
const path34 = pattern.split("?")[0] ?? pattern;
|
|
5242
|
+
const escaped = path34.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
5107
5243
|
return new RegExp(`^${escaped}$`);
|
|
5108
5244
|
}
|
|
5109
5245
|
function suitePathEqual(a, b) {
|
|
@@ -5126,13 +5262,13 @@ function matchProbeItem(plan, test) {
|
|
|
5126
5262
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
5127
5263
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
5128
5264
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
5129
|
-
let
|
|
5265
|
+
let path34;
|
|
5130
5266
|
try {
|
|
5131
|
-
|
|
5267
|
+
path34 = new URL(url).pathname;
|
|
5132
5268
|
} catch {
|
|
5133
|
-
|
|
5269
|
+
path34 = url.split("?")[0] ?? url;
|
|
5134
5270
|
}
|
|
5135
|
-
return patternToRegex(pattern).test(
|
|
5271
|
+
return patternToRegex(pattern).test(path34);
|
|
5136
5272
|
}
|
|
5137
5273
|
function createProbeState() {
|
|
5138
5274
|
return { navigated: false, matches: 0 };
|
|
@@ -5154,10 +5290,10 @@ function isProbeMode() {
|
|
|
5154
5290
|
function loadPlan() {
|
|
5155
5291
|
if (planCache !== void 0) return planCache;
|
|
5156
5292
|
planCache = null;
|
|
5157
|
-
const
|
|
5158
|
-
if (
|
|
5293
|
+
const path34 = process.env[PIWI_PROBE_ENV.plan];
|
|
5294
|
+
if (path34) {
|
|
5159
5295
|
try {
|
|
5160
|
-
planCache = JSON.parse(
|
|
5296
|
+
planCache = JSON.parse(fs16.readFileSync(path34, "utf8"));
|
|
5161
5297
|
} catch {
|
|
5162
5298
|
planCache = null;
|
|
5163
5299
|
}
|
|
@@ -5181,17 +5317,17 @@ function outcomeFromStatus(status, applied) {
|
|
|
5181
5317
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
5182
5318
|
}
|
|
5183
5319
|
function recordProbeOutcome(line) {
|
|
5184
|
-
const
|
|
5185
|
-
if (!
|
|
5320
|
+
const path34 = process.env[PIWI_PROBE_ENV.results];
|
|
5321
|
+
if (!path34) return;
|
|
5186
5322
|
try {
|
|
5187
|
-
|
|
5323
|
+
fs16.appendFileSync(path34, `${JSON.stringify(line)}
|
|
5188
5324
|
`);
|
|
5189
5325
|
} catch {
|
|
5190
5326
|
}
|
|
5191
5327
|
}
|
|
5192
5328
|
|
|
5193
5329
|
// src/internal/flake/mode.ts
|
|
5194
|
-
var
|
|
5330
|
+
var fs17 = __toESM(require("fs"));
|
|
5195
5331
|
|
|
5196
5332
|
// ../core/src/error-signature.ts
|
|
5197
5333
|
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;
|
|
@@ -5567,7 +5703,7 @@ function originOf2(url) {
|
|
|
5567
5703
|
return null;
|
|
5568
5704
|
}
|
|
5569
5705
|
}
|
|
5570
|
-
var sleep = (ms) => new Promise((
|
|
5706
|
+
var sleep = (ms) => new Promise((resolve11) => setTimeout(resolve11, Math.max(0, ms)));
|
|
5571
5707
|
async function performRouteAction(route, action, startedAt) {
|
|
5572
5708
|
switch (action.kind) {
|
|
5573
5709
|
case "abort":
|
|
@@ -5691,7 +5827,7 @@ function loadFlakePlan() {
|
|
|
5691
5827
|
if (planCache2 && planCache2.file === file) return planCache2.plan;
|
|
5692
5828
|
let text;
|
|
5693
5829
|
try {
|
|
5694
|
-
text =
|
|
5830
|
+
text = fs17.readFileSync(file, "utf8");
|
|
5695
5831
|
} catch (error) {
|
|
5696
5832
|
throw new Error(`[Piwi Dashboard] Cannot read the flake plan ${file}: ${errorMessage(error)}`);
|
|
5697
5833
|
}
|
|
@@ -5852,7 +5988,7 @@ function recordFlakeResult(line) {
|
|
|
5852
5988
|
const file = process.env[PIWI_FLAKE_ENV.results]?.trim();
|
|
5853
5989
|
if (!file) return;
|
|
5854
5990
|
try {
|
|
5855
|
-
|
|
5991
|
+
fs17.appendFileSync(file, `${JSON.stringify(line)}
|
|
5856
5992
|
`);
|
|
5857
5993
|
} catch {
|
|
5858
5994
|
}
|
|
@@ -5923,11 +6059,11 @@ function injectReporter(reporter, piwiOptions) {
|
|
|
5923
6059
|
}
|
|
5924
6060
|
function resolveSetupModule() {
|
|
5925
6061
|
const candidates = [
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
6062
|
+
path16.join(__dirname, "global-setup-module.js"),
|
|
6063
|
+
path16.join(__dirname, "..", "global-setup-module.js"),
|
|
6064
|
+
path16.join(__dirname, "..", "global-setup-module.ts")
|
|
5929
6065
|
];
|
|
5930
|
-
return candidates.find((candidate) =>
|
|
6066
|
+
return candidates.find((candidate) => fs18.existsSync(candidate)) ?? candidates[0];
|
|
5931
6067
|
}
|
|
5932
6068
|
function wrapConfig(config, piwiOptions) {
|
|
5933
6069
|
if (piwiOptions) applyOptionsToEnv(piwiOptions);
|
|
@@ -6323,8 +6459,8 @@ function buildErrorText(result, base = process.cwd()) {
|
|
|
6323
6459
|
var import_node_crypto2 = require("crypto");
|
|
6324
6460
|
|
|
6325
6461
|
// src/internal/support/ci-output.ts
|
|
6326
|
-
var
|
|
6327
|
-
var
|
|
6462
|
+
var fs19 = __toESM(require("fs"));
|
|
6463
|
+
var path17 = __toESM(require("path"));
|
|
6328
6464
|
var SUMMARY_MAX_FAILURES = 20;
|
|
6329
6465
|
function emitRunOutputs(output, logger, outputFile, env = process.env) {
|
|
6330
6466
|
logger.info(`View run: ${output.runUrl}`);
|
|
@@ -6334,9 +6470,9 @@ function emitRunOutputs(output, logger, outputFile, env = process.env) {
|
|
|
6334
6470
|
}
|
|
6335
6471
|
function writeOutputFile(file, output, logger) {
|
|
6336
6472
|
try {
|
|
6337
|
-
const dir =
|
|
6338
|
-
if (dir && dir !== ".")
|
|
6339
|
-
|
|
6473
|
+
const dir = path17.dirname(file);
|
|
6474
|
+
if (dir && dir !== ".") fs19.mkdirSync(dir, { recursive: true });
|
|
6475
|
+
fs19.writeFileSync(
|
|
6340
6476
|
file,
|
|
6341
6477
|
JSON.stringify(
|
|
6342
6478
|
{
|
|
@@ -6416,7 +6552,7 @@ function emitGitLabDotenv(output, env, logger) {
|
|
|
6416
6552
|
if (output.projectId != null) lines.push(`PIWI_PROJECT_ID=${output.projectId}`);
|
|
6417
6553
|
if (output.ciBuildUrl) lines.push(`PIWI_CI_BUILD_URL=${output.ciBuildUrl}`);
|
|
6418
6554
|
try {
|
|
6419
|
-
|
|
6555
|
+
fs19.writeFileSync(file, lines.join("\n") + "\n");
|
|
6420
6556
|
logger.info(`Wrote GitLab dotenv report to ${file} (declare it as artifacts:reports:dotenv)`);
|
|
6421
6557
|
} catch (error) {
|
|
6422
6558
|
logger.warn(`Failed to write GitLab dotenv file '${file}': ${errorMessage(error)}`);
|
|
@@ -6433,7 +6569,7 @@ function emitResourceSummary(lines, logger, env = process.env) {
|
|
|
6433
6569
|
}
|
|
6434
6570
|
function appendFileLines(file, lines, logger, label) {
|
|
6435
6571
|
try {
|
|
6436
|
-
|
|
6572
|
+
fs19.appendFileSync(file, lines.join("\n") + "\n");
|
|
6437
6573
|
} catch (error) {
|
|
6438
6574
|
logger.warn(`Failed to write GitHub Actions ${label}: ${errorMessage(error)}`);
|
|
6439
6575
|
}
|
|
@@ -6446,8 +6582,8 @@ function ciBuildUrlFromMetadata(metadata) {
|
|
|
6446
6582
|
|
|
6447
6583
|
// src/internal/submit/run-submitter.ts
|
|
6448
6584
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
6449
|
-
function markSubmitFallback(run,
|
|
6450
|
-
run.metadata.ingestHealth = { submitFallback: { path:
|
|
6585
|
+
function markSubmitFallback(run, path34, reason) {
|
|
6586
|
+
run.metadata.ingestHealth = { submitFallback: { path: path34, reason } };
|
|
6451
6587
|
}
|
|
6452
6588
|
var RunSubmitter = class {
|
|
6453
6589
|
/**
|
|
@@ -6708,7 +6844,7 @@ var RunSubmitter = class {
|
|
|
6708
6844
|
*/
|
|
6709
6845
|
stopForMissingAuth(run, payload) {
|
|
6710
6846
|
this.logger.error(
|
|
6711
|
-
`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
|
|
6847
|
+
`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 PIWI_API_KEY in the environment or in the .env file next to the Playwright config.`
|
|
6712
6848
|
);
|
|
6713
6849
|
this.logRecoverySaved();
|
|
6714
6850
|
this.saveRecovery(payload);
|
|
@@ -6723,13 +6859,13 @@ var RunSubmitter = class {
|
|
|
6723
6859
|
};
|
|
6724
6860
|
|
|
6725
6861
|
// src/internal/capture/resource-ledger.ts
|
|
6726
|
-
var
|
|
6727
|
-
var
|
|
6862
|
+
var fs21 = __toESM(require("fs"));
|
|
6863
|
+
var path20 = __toESM(require("path"));
|
|
6728
6864
|
var import_node_module2 = require("module");
|
|
6729
6865
|
|
|
6730
6866
|
// src/internal/capture/quiet-capture.ts
|
|
6731
6867
|
var import_node_module = require("module");
|
|
6732
|
-
var
|
|
6868
|
+
var path18 = __toESM(require("path"));
|
|
6733
6869
|
var BOXED_PREFIX_EXPORTS = [
|
|
6734
6870
|
[
|
|
6735
6871
|
"playwright-core/lib/coreBundle",
|
|
@@ -6766,7 +6902,7 @@ function resolveBoxing() {
|
|
|
6766
6902
|
if (typeof set !== "function") continue;
|
|
6767
6903
|
return {
|
|
6768
6904
|
set,
|
|
6769
|
-
prefixes: [
|
|
6905
|
+
prefixes: [path18.dirname(playwrightPackageJson), path18.join(root, "dist") + path18.sep]
|
|
6770
6906
|
};
|
|
6771
6907
|
}
|
|
6772
6908
|
} catch {
|
|
@@ -6798,12 +6934,12 @@ function internalCall(owner, run) {
|
|
|
6798
6934
|
var import_node_perf_hooks = require("perf_hooks");
|
|
6799
6935
|
|
|
6800
6936
|
// src/internal/support/system-readers.ts
|
|
6801
|
-
var
|
|
6802
|
-
var
|
|
6937
|
+
var fs20 = __toESM(require("fs"));
|
|
6938
|
+
var path19 = __toESM(require("path"));
|
|
6803
6939
|
var CLK_TCK = 100;
|
|
6804
6940
|
function readText(file) {
|
|
6805
6941
|
try {
|
|
6806
|
-
return
|
|
6942
|
+
return fs20.readFileSync(file, "utf8");
|
|
6807
6943
|
} catch {
|
|
6808
6944
|
return null;
|
|
6809
6945
|
}
|
|
@@ -6937,45 +7073,45 @@ var ProcFs = class {
|
|
|
6937
7073
|
}
|
|
6938
7074
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
6939
7075
|
available() {
|
|
6940
|
-
return
|
|
7076
|
+
return fs20.existsSync(path19.join(this.root, "stat"));
|
|
6941
7077
|
}
|
|
6942
7078
|
/** Every process, keyed by pid. */
|
|
6943
7079
|
table() {
|
|
6944
7080
|
const out = /* @__PURE__ */ new Map();
|
|
6945
7081
|
let names;
|
|
6946
7082
|
try {
|
|
6947
|
-
names =
|
|
7083
|
+
names = fs20.readdirSync(this.root);
|
|
6948
7084
|
} catch {
|
|
6949
7085
|
return out;
|
|
6950
7086
|
}
|
|
6951
7087
|
for (const name of names) {
|
|
6952
7088
|
if (!/^\d+$/.test(name)) continue;
|
|
6953
|
-
const text = readText(
|
|
7089
|
+
const text = readText(path19.join(this.root, name, "stat"));
|
|
6954
7090
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
6955
7091
|
if (stat) out.set(stat.pid, stat);
|
|
6956
7092
|
}
|
|
6957
7093
|
return out;
|
|
6958
7094
|
}
|
|
6959
7095
|
stat(pid) {
|
|
6960
|
-
const text = readText(
|
|
7096
|
+
const text = readText(path19.join(this.root, String(pid), "stat"));
|
|
6961
7097
|
return text ? parseProcStat(pid, text) : null;
|
|
6962
7098
|
}
|
|
6963
7099
|
runWaitMs(pid) {
|
|
6964
|
-
const text = readText(
|
|
7100
|
+
const text = readText(path19.join(this.root, String(pid), "schedstat"));
|
|
6965
7101
|
return text ? parseSchedstat(text) : null;
|
|
6966
7102
|
}
|
|
6967
7103
|
cmdline(pid) {
|
|
6968
|
-
return (readText(
|
|
7104
|
+
return (readText(path19.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
6969
7105
|
}
|
|
6970
7106
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
6971
7107
|
statusKb(pid, key) {
|
|
6972
|
-
const text = readText(
|
|
7108
|
+
const text = readText(path19.join(this.root, String(pid), "status"));
|
|
6973
7109
|
return text ? parseKbField(text, key) : null;
|
|
6974
7110
|
}
|
|
6975
7111
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
6976
7112
|
async pssKb(pid) {
|
|
6977
7113
|
try {
|
|
6978
|
-
return parseKbField(await
|
|
7114
|
+
return parseKbField(await fs20.promises.readFile(path19.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
6979
7115
|
} catch {
|
|
6980
7116
|
return null;
|
|
6981
7117
|
}
|
|
@@ -6983,29 +7119,29 @@ var ProcFs = class {
|
|
|
6983
7119
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
6984
7120
|
resetPeakRss(pid) {
|
|
6985
7121
|
try {
|
|
6986
|
-
|
|
7122
|
+
fs20.writeFileSync(path19.join(this.root, String(pid), "clear_refs"), "5");
|
|
6987
7123
|
return true;
|
|
6988
7124
|
} catch {
|
|
6989
7125
|
return false;
|
|
6990
7126
|
}
|
|
6991
7127
|
}
|
|
6992
7128
|
cpuTicks() {
|
|
6993
|
-
const text = readText(
|
|
7129
|
+
const text = readText(path19.join(this.root, "stat"));
|
|
6994
7130
|
return text ? parseCpuTicks(text) : null;
|
|
6995
7131
|
}
|
|
6996
7132
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
6997
7133
|
pressureUs(resource) {
|
|
6998
|
-
const text = readText(
|
|
7134
|
+
const text = readText(path19.join(this.root, "pressure", resource));
|
|
6999
7135
|
return text ? parsePsiTotal(text) : null;
|
|
7000
7136
|
}
|
|
7001
7137
|
memAvailableKb() {
|
|
7002
|
-
const text = readText(
|
|
7138
|
+
const text = readText(path19.join(this.root, "meminfo"));
|
|
7003
7139
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
7004
7140
|
}
|
|
7005
7141
|
/** Open file descriptors of a process. */
|
|
7006
7142
|
fdCount(pid) {
|
|
7007
7143
|
try {
|
|
7008
|
-
return
|
|
7144
|
+
return fs20.readdirSync(path19.join(this.root, String(pid), "fd")).length;
|
|
7009
7145
|
} catch {
|
|
7010
7146
|
return null;
|
|
7011
7147
|
}
|
|
@@ -7026,7 +7162,7 @@ var CgroupFs = class {
|
|
|
7026
7162
|
}
|
|
7027
7163
|
/** The directory of a controller (v1) or of the unified hierarchy (v2), when it exists. */
|
|
7028
7164
|
dirs() {
|
|
7029
|
-
const text = readText(
|
|
7165
|
+
const text = readText(path19.join(this.procRoot, String(this.pid), "cgroup"));
|
|
7030
7166
|
if (!text) return null;
|
|
7031
7167
|
const v1 = /* @__PURE__ */ new Map();
|
|
7032
7168
|
let v2 = null;
|
|
@@ -7037,21 +7173,21 @@ var CgroupFs = class {
|
|
|
7037
7173
|
else for (const controller of match[1].split(",")) v1.set(controller, match[2]);
|
|
7038
7174
|
}
|
|
7039
7175
|
const pick = (mount, sub) => {
|
|
7040
|
-
const nested =
|
|
7041
|
-
if (
|
|
7042
|
-
return
|
|
7176
|
+
const nested = path19.join(mount, sub);
|
|
7177
|
+
if (fs20.existsSync(nested)) return nested;
|
|
7178
|
+
return fs20.existsSync(mount) ? mount : null;
|
|
7043
7179
|
};
|
|
7044
7180
|
if (v1.has("memory") || v1.has("cpu")) {
|
|
7045
7181
|
const memoryPath = v1.get("memory");
|
|
7046
7182
|
const cpuPath = v1.get("cpu");
|
|
7047
|
-
const cpuMount = ["cpu,cpuacct", "cpu"].map((name) =>
|
|
7183
|
+
const cpuMount = ["cpu,cpuacct", "cpu"].map((name) => path19.join(this.cgroupRoot, name)).find((dir) => fs20.existsSync(dir));
|
|
7048
7184
|
return {
|
|
7049
7185
|
version: 1,
|
|
7050
|
-
memory: memoryPath !== void 0 ? pick(
|
|
7186
|
+
memory: memoryPath !== void 0 ? pick(path19.join(this.cgroupRoot, "memory"), memoryPath) : null,
|
|
7051
7187
|
cpu: cpuPath !== void 0 && cpuMount ? pick(cpuMount, cpuPath) : null
|
|
7052
7188
|
};
|
|
7053
7189
|
}
|
|
7054
|
-
if (v2 !== null &&
|
|
7190
|
+
if (v2 !== null && fs20.existsSync(path19.join(this.cgroupRoot, "cgroup.controllers"))) {
|
|
7055
7191
|
const dir = pick(this.cgroupRoot, v2);
|
|
7056
7192
|
return { version: 2, memory: dir, cpu: dir };
|
|
7057
7193
|
}
|
|
@@ -7060,7 +7196,7 @@ var CgroupFs = class {
|
|
|
7060
7196
|
read() {
|
|
7061
7197
|
const dirs = this.dirs();
|
|
7062
7198
|
if (!dirs) return null;
|
|
7063
|
-
const file = (dir, name) => dir ? readText(
|
|
7199
|
+
const file = (dir, name) => dir ? readText(path19.join(dir, name)) : null;
|
|
7064
7200
|
if (dirs.version === 2) {
|
|
7065
7201
|
const max = file(dirs.memory, "memory.max");
|
|
7066
7202
|
const cpuMax = file(dirs.cpu, "cpu.max")?.trim().split(/\s+/);
|
|
@@ -7246,10 +7382,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
7246
7382
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
7247
7383
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
7248
7384
|
function relativePath(file) {
|
|
7249
|
-
return
|
|
7385
|
+
return path20.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path20.sep).join("/");
|
|
7250
7386
|
}
|
|
7251
7387
|
function isLibraryFile(file) {
|
|
7252
|
-
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${
|
|
7388
|
+
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path20.sep}node_modules${path20.sep}`) || file.includes("/node_modules/") || file === __filename;
|
|
7253
7389
|
}
|
|
7254
7390
|
function userSite(stack) {
|
|
7255
7391
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -7297,7 +7433,7 @@ function playwrightTestInfoSource() {
|
|
|
7297
7433
|
try {
|
|
7298
7434
|
const packageJson = resolvePlaywrightPackageJson();
|
|
7299
7435
|
if (!packageJson) return () => null;
|
|
7300
|
-
const globals = (0, import_node_module2.createRequire)(packageJson)(
|
|
7436
|
+
const globals = (0, import_node_module2.createRequire)(packageJson)(path20.join(path20.dirname(packageJson), "lib", "globals.js"));
|
|
7301
7437
|
const read = globals.currentTestInfo;
|
|
7302
7438
|
if (typeof read !== "function") return () => null;
|
|
7303
7439
|
return () => {
|
|
@@ -7326,15 +7462,15 @@ function countHandles() {
|
|
|
7326
7462
|
async function settledHandles(start) {
|
|
7327
7463
|
const end = countHandles();
|
|
7328
7464
|
if (!Object.entries(end).some(([type, n]) => n > (start[type] ?? 0))) return end;
|
|
7329
|
-
await new Promise((
|
|
7465
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7330
7466
|
return countHandles();
|
|
7331
7467
|
}
|
|
7332
7468
|
function settleWithin(promise, ms) {
|
|
7333
7469
|
let timer;
|
|
7334
7470
|
return Promise.race([
|
|
7335
7471
|
promise.catch(() => void 0),
|
|
7336
|
-
new Promise((
|
|
7337
|
-
timer = setTimeout(
|
|
7472
|
+
new Promise((resolve11) => {
|
|
7473
|
+
timer = setTimeout(resolve11, ms);
|
|
7338
7474
|
})
|
|
7339
7475
|
]).finally(() => clearTimeout(timer));
|
|
7340
7476
|
}
|
|
@@ -7463,7 +7599,7 @@ var ResourceLedger = class {
|
|
|
7463
7599
|
const census = this.census(null);
|
|
7464
7600
|
if (this.resultsFile) {
|
|
7465
7601
|
try {
|
|
7466
|
-
|
|
7602
|
+
fs21.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
|
|
7467
7603
|
`);
|
|
7468
7604
|
} catch {
|
|
7469
7605
|
}
|
|
@@ -7786,7 +7922,7 @@ async function recordResourceCensus(testInfo) {
|
|
|
7786
7922
|
}
|
|
7787
7923
|
|
|
7788
7924
|
// src/internal/collect/resource-verdicts.ts
|
|
7789
|
-
var
|
|
7925
|
+
var path21 = __toESM(require("path"));
|
|
7790
7926
|
var OPEN_STEP_TITLES = {
|
|
7791
7927
|
"Create context": "context",
|
|
7792
7928
|
"Launch persistent context": "context",
|
|
@@ -7818,7 +7954,7 @@ function userFixturesOf(steps) {
|
|
|
7818
7954
|
const walk = (list) => {
|
|
7819
7955
|
for (const step of list) {
|
|
7820
7956
|
const file = step.location?.file;
|
|
7821
|
-
if (step.category === "fixture" && file && !file.includes(`${
|
|
7957
|
+
if (step.category === "fixture" && file && !file.includes(`${path21.sep}node_modules${path21.sep}`)) {
|
|
7822
7958
|
const name = step.title.replace(/^(?:Fixture\s+|fixture:\s*)/, "").replace(/^"(.*)"$/, "$1");
|
|
7823
7959
|
if (!name.startsWith("piwi")) out.push(`${name} (${relativeFile(file)}:${step.location.line})`);
|
|
7824
7960
|
}
|
|
@@ -7851,7 +7987,7 @@ function tallyLifecycleSteps(file, steps) {
|
|
|
7851
7987
|
return tally;
|
|
7852
7988
|
}
|
|
7853
7989
|
function relativeFile(file) {
|
|
7854
|
-
return
|
|
7990
|
+
return path21.relative(process.cwd(), file).split(path21.sep).join("/");
|
|
7855
7991
|
}
|
|
7856
7992
|
function buildResourceReport(input) {
|
|
7857
7993
|
const findings = [];
|
|
@@ -8209,9 +8345,9 @@ function formatResourceSummary(report, leakCheck, maxLines = 10) {
|
|
|
8209
8345
|
}
|
|
8210
8346
|
|
|
8211
8347
|
// src/internal/collect/process-sampler.ts
|
|
8212
|
-
var
|
|
8348
|
+
var fs22 = __toESM(require("fs"));
|
|
8213
8349
|
var os8 = __toESM(require("os"));
|
|
8214
|
-
var
|
|
8350
|
+
var path22 = __toESM(require("path"));
|
|
8215
8351
|
var import_node_child_process2 = require("child_process");
|
|
8216
8352
|
var WALK_FILE_CAP = 1e4;
|
|
8217
8353
|
var SERIES_CAP = 3600;
|
|
@@ -8251,17 +8387,17 @@ function nodeCpuTicks() {
|
|
|
8251
8387
|
return { busy, idle, iowait: 0, steal: 0, total: busy + idle };
|
|
8252
8388
|
}
|
|
8253
8389
|
function defaultPs() {
|
|
8254
|
-
return new Promise((
|
|
8390
|
+
return new Promise((resolve11, reject) => {
|
|
8255
8391
|
(0, import_node_child_process2.execFile)(
|
|
8256
8392
|
"ps",
|
|
8257
8393
|
["-A", "-o", "pid=,ppid=,rss=,time=,command="],
|
|
8258
8394
|
{ timeout: 2e3, maxBuffer: 8 * 1024 * 1024 },
|
|
8259
|
-
(error, stdout) => error ? reject(error) :
|
|
8395
|
+
(error, stdout) => error ? reject(error) : resolve11(stdout)
|
|
8260
8396
|
);
|
|
8261
8397
|
});
|
|
8262
8398
|
}
|
|
8263
8399
|
function defaultWindowsQuery() {
|
|
8264
|
-
return new Promise((
|
|
8400
|
+
return new Promise((resolve11, reject) => {
|
|
8265
8401
|
(0, import_node_child_process2.execFile)(
|
|
8266
8402
|
"powershell.exe",
|
|
8267
8403
|
[
|
|
@@ -8271,18 +8407,18 @@ function defaultWindowsQuery() {
|
|
|
8271
8407
|
"[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $PID -and $_.ParentProcessId -ne $PID } | Select-Object ProcessId,ParentProcessId,WorkingSetSize,UserModeTime,KernelModeTime,Name,CommandLine | ConvertTo-Json -Compress"
|
|
8272
8408
|
],
|
|
8273
8409
|
{ timeout: 1e4, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
|
|
8274
|
-
(error, stdout) => error ? reject(error) :
|
|
8410
|
+
(error, stdout) => error ? reject(error) : resolve11(stdout)
|
|
8275
8411
|
);
|
|
8276
8412
|
});
|
|
8277
8413
|
}
|
|
8278
8414
|
function freeBytes(dir) {
|
|
8279
|
-
let current =
|
|
8415
|
+
let current = path22.resolve(dir);
|
|
8280
8416
|
for (; ; ) {
|
|
8281
8417
|
try {
|
|
8282
|
-
const stats =
|
|
8418
|
+
const stats = fs22.statfsSync(current);
|
|
8283
8419
|
return stats.bavail * stats.bsize;
|
|
8284
8420
|
} catch {
|
|
8285
|
-
const parent =
|
|
8421
|
+
const parent = path22.dirname(current);
|
|
8286
8422
|
if (parent === current) return null;
|
|
8287
8423
|
current = parent;
|
|
8288
8424
|
}
|
|
@@ -8292,18 +8428,18 @@ async function dirBytes(dir, budget) {
|
|
|
8292
8428
|
let bytes = 0;
|
|
8293
8429
|
let entries;
|
|
8294
8430
|
try {
|
|
8295
|
-
entries = await
|
|
8431
|
+
entries = await fs22.promises.readdir(dir, { withFileTypes: true });
|
|
8296
8432
|
} catch {
|
|
8297
8433
|
return 0;
|
|
8298
8434
|
}
|
|
8299
8435
|
for (const entry of entries) {
|
|
8300
8436
|
if (budget.files <= 0) return bytes;
|
|
8301
|
-
const full =
|
|
8437
|
+
const full = path22.join(dir, entry.name);
|
|
8302
8438
|
if (entry.isDirectory()) bytes += await dirBytes(full, budget);
|
|
8303
8439
|
else if (entry.isFile()) {
|
|
8304
8440
|
budget.files--;
|
|
8305
8441
|
try {
|
|
8306
|
-
bytes += (await
|
|
8442
|
+
bytes += (await fs22.promises.lstat(full)).size;
|
|
8307
8443
|
} catch {
|
|
8308
8444
|
}
|
|
8309
8445
|
}
|
|
@@ -8343,7 +8479,7 @@ var RunSampler = class {
|
|
|
8343
8479
|
this.platform = options.platform ?? process.platform;
|
|
8344
8480
|
this.proc = new ProcFs(options.procRoot);
|
|
8345
8481
|
this.cgroup = new CgroupFs(options.procRoot, options.cgroupRoot);
|
|
8346
|
-
this.outputDirs = [...new Set((options.outputDirs ?? []).map((dir) =>
|
|
8482
|
+
this.outputDirs = [...new Set((options.outputDirs ?? []).map((dir) => path22.resolve(dir)))];
|
|
8347
8483
|
this.tmpDir = options.tmpDir ?? os8.tmpdir();
|
|
8348
8484
|
this.intervalMs = options.intervalMs ?? 1e3;
|
|
8349
8485
|
this.heavyEvery = Math.max(1, options.heavyEvery ?? 5);
|
|
@@ -8545,7 +8681,7 @@ var RunSampler = class {
|
|
|
8545
8681
|
} catch {
|
|
8546
8682
|
return;
|
|
8547
8683
|
}
|
|
8548
|
-
this.noteTree(list, (proc) => roleOf(
|
|
8684
|
+
this.noteTree(list, (proc) => roleOf(path22.basename(proc.command.split(" ")[0] ?? ""), proc.command), at);
|
|
8549
8685
|
}
|
|
8550
8686
|
async sampleWindowsTree(at) {
|
|
8551
8687
|
let list;
|
|
@@ -8601,7 +8737,7 @@ var RunSampler = class {
|
|
|
8601
8737
|
let bytes = 0;
|
|
8602
8738
|
for (const dir of this.outputDirs) bytes += await dirBytes(dir, budget);
|
|
8603
8739
|
for (const name of await this.playwrightTempEntries((mtimeMs) => mtimeMs >= this.startedAt - 1e3)) {
|
|
8604
|
-
bytes += await dirBytes(
|
|
8740
|
+
bytes += await dirBytes(path22.join(this.tmpDir, name), budget);
|
|
8605
8741
|
}
|
|
8606
8742
|
if (budget.files <= 0) this.peakInUseIsLowerBound = true;
|
|
8607
8743
|
this.peakInUse = Math.max(this.peakInUse ?? 0, bytes);
|
|
@@ -8610,7 +8746,7 @@ var RunSampler = class {
|
|
|
8610
8746
|
async playwrightTempEntries(keep) {
|
|
8611
8747
|
let names;
|
|
8612
8748
|
try {
|
|
8613
|
-
names = await
|
|
8749
|
+
names = await fs22.promises.readdir(this.tmpDir);
|
|
8614
8750
|
} catch {
|
|
8615
8751
|
return [];
|
|
8616
8752
|
}
|
|
@@ -8618,7 +8754,7 @@ var RunSampler = class {
|
|
|
8618
8754
|
for (const name of names) {
|
|
8619
8755
|
if (!PLAYWRIGHT_TEMP.test(name)) continue;
|
|
8620
8756
|
try {
|
|
8621
|
-
if (keep((await
|
|
8757
|
+
if (keep((await fs22.promises.stat(path22.join(this.tmpDir, name))).mtimeMs)) out.push(name);
|
|
8622
8758
|
} catch {
|
|
8623
8759
|
}
|
|
8624
8760
|
}
|
|
@@ -8629,7 +8765,7 @@ var RunSampler = class {
|
|
|
8629
8765
|
const budget = { files: WALK_FILE_CAP };
|
|
8630
8766
|
let bytes = 0;
|
|
8631
8767
|
for (const name of await this.playwrightTempEntries((mtimeMs) => mtimeMs < this.startedAt - 1e3)) {
|
|
8632
|
-
bytes += await dirBytes(
|
|
8768
|
+
bytes += await dirBytes(path22.join(this.tmpDir, name), budget);
|
|
8633
8769
|
}
|
|
8634
8770
|
return bytes;
|
|
8635
8771
|
} catch {
|
|
@@ -9603,7 +9739,7 @@ function browserFunctionsInWorker() {
|
|
|
9603
9739
|
function browserFunctionCoverage(files) {
|
|
9604
9740
|
return sanitizeCoverage({
|
|
9605
9741
|
v: 1,
|
|
9606
|
-
files: [...files].map(([
|
|
9742
|
+
files: [...files].map(([path34, functions]) => ({ path: path34, runtime: "browser", functions }))
|
|
9607
9743
|
});
|
|
9608
9744
|
}
|
|
9609
9745
|
function newKnownFunctions(known, sent) {
|
|
@@ -9616,19 +9752,19 @@ function newKnownFunctions(known, sent) {
|
|
|
9616
9752
|
budget = MAX_COVERAGE_RANGES;
|
|
9617
9753
|
};
|
|
9618
9754
|
for (const [raw, ranges] of known) {
|
|
9619
|
-
const
|
|
9620
|
-
if (
|
|
9621
|
-
let named = sent.get(
|
|
9755
|
+
const path34 = normalizeCoveragePath(raw);
|
|
9756
|
+
if (path34 === null) continue;
|
|
9757
|
+
let named = sent.get(path34);
|
|
9622
9758
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
9623
9759
|
if (fresh.length === 0) continue;
|
|
9624
|
-
if (!named) sent.set(
|
|
9760
|
+
if (!named) sent.set(path34, named = /* @__PURE__ */ new Set());
|
|
9625
9761
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
9626
9762
|
while (fresh.length > 0) {
|
|
9627
9763
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
9628
9764
|
const part = fresh.slice(0, budget);
|
|
9629
9765
|
fresh = fresh.slice(part.length);
|
|
9630
9766
|
budget -= part.length;
|
|
9631
|
-
files.push({ path:
|
|
9767
|
+
files.push({ path: path34, allFunctions: part });
|
|
9632
9768
|
}
|
|
9633
9769
|
}
|
|
9634
9770
|
close();
|
|
@@ -9640,8 +9776,8 @@ function serverCoverageInWorker() {
|
|
|
9640
9776
|
|
|
9641
9777
|
// src/internal/capture/code-reach.ts
|
|
9642
9778
|
var crypto5 = __toESM(require("crypto"));
|
|
9643
|
-
var
|
|
9644
|
-
var
|
|
9779
|
+
var fs23 = __toESM(require("fs"));
|
|
9780
|
+
var path23 = __toESM(require("path"));
|
|
9645
9781
|
var import_node_child_process3 = require("child_process");
|
|
9646
9782
|
|
|
9647
9783
|
// ../core/src/source-map.ts
|
|
@@ -9986,18 +10122,18 @@ function repositoryRoot(from) {
|
|
|
9986
10122
|
}).trim();
|
|
9987
10123
|
} catch {
|
|
9988
10124
|
}
|
|
9989
|
-
cachedRepoRoot = { from, root:
|
|
10125
|
+
cachedRepoRoot = { from, root: path23.resolve(root) };
|
|
9990
10126
|
return cachedRepoRoot.root;
|
|
9991
10127
|
}
|
|
9992
10128
|
function codeReachRoots(configDir, configured) {
|
|
9993
10129
|
const repoRoot = repositoryRoot(configDir);
|
|
9994
|
-
const
|
|
9995
|
-
const global = Array.isArray(configured) ?
|
|
10130
|
+
const resolve11 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path23.resolve(configDir, r));
|
|
10131
|
+
const global = Array.isArray(configured) ? resolve11(configured) : resolve11(configured?.["*"]);
|
|
9996
10132
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
9997
10133
|
if (configured && !Array.isArray(configured)) {
|
|
9998
10134
|
for (const [key, dirs] of Object.entries(configured)) {
|
|
9999
10135
|
const origin = key === "*" ? null : originOf3(key);
|
|
10000
|
-
const resolved =
|
|
10136
|
+
const resolved = resolve11(dirs);
|
|
10001
10137
|
if (origin && resolved.length) byOrigin.set(origin, resolved);
|
|
10002
10138
|
}
|
|
10003
10139
|
}
|
|
@@ -10020,7 +10156,7 @@ function scriptRoots(roots, scriptUrl, pageUrl) {
|
|
|
10020
10156
|
}
|
|
10021
10157
|
function isFile(file) {
|
|
10022
10158
|
try {
|
|
10023
|
-
return
|
|
10159
|
+
return fs23.statSync(file).isFile();
|
|
10024
10160
|
} catch {
|
|
10025
10161
|
return false;
|
|
10026
10162
|
}
|
|
@@ -10028,10 +10164,10 @@ function isFile(file) {
|
|
|
10028
10164
|
function resolveSourcePath(source, roots) {
|
|
10029
10165
|
const normalized = normalizeSourcePath(source);
|
|
10030
10166
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
10031
|
-
if (
|
|
10167
|
+
if (path23.isAbsolute(normalized) && isFile(normalized)) return path23.resolve(normalized);
|
|
10032
10168
|
const relative11 = normalized.replace(/^\/+/, "");
|
|
10033
10169
|
for (const root of roots) {
|
|
10034
|
-
const candidate =
|
|
10170
|
+
const candidate = path23.resolve(root, relative11);
|
|
10035
10171
|
if (isFile(candidate)) return candidate;
|
|
10036
10172
|
}
|
|
10037
10173
|
return null;
|
|
@@ -10063,7 +10199,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
10063
10199
|
return file;
|
|
10064
10200
|
}
|
|
10065
10201
|
function relativeToRepo(file, repoRoot) {
|
|
10066
|
-
return
|
|
10202
|
+
return path23.relative(repoRoot, file).split(path23.sep).join("/");
|
|
10067
10203
|
}
|
|
10068
10204
|
function scriptVersion(entry) {
|
|
10069
10205
|
return entry.hash ?? crypto5.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -10120,10 +10256,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
10120
10256
|
const files = /* @__PURE__ */ new Map();
|
|
10121
10257
|
const known = /* @__PURE__ */ new Map();
|
|
10122
10258
|
const addTo = (into, file, functions) => {
|
|
10123
|
-
const
|
|
10124
|
-
const listed = into.get(
|
|
10259
|
+
const path34 = relativeToRepo(file, roots.repoRoot);
|
|
10260
|
+
const listed = into.get(path34);
|
|
10125
10261
|
if (listed) listed.push(...functions);
|
|
10126
|
-
else into.set(
|
|
10262
|
+
else into.set(path34, [...functions]);
|
|
10127
10263
|
};
|
|
10128
10264
|
const add = (file, functions) => addTo(files, file, functions);
|
|
10129
10265
|
const bases = viteBases(entries);
|
|
@@ -10275,8 +10411,8 @@ function onScriptParsed(page, capture, event) {
|
|
|
10275
10411
|
var READ_WAIT_MS = 5e3;
|
|
10276
10412
|
function readCoverage(page, cdp) {
|
|
10277
10413
|
let timer;
|
|
10278
|
-
const late = new Promise((
|
|
10279
|
-
timer = setTimeout(() =>
|
|
10414
|
+
const late = new Promise((resolve11) => {
|
|
10415
|
+
timer = setTimeout(() => resolve11([]), READ_WAIT_MS);
|
|
10280
10416
|
});
|
|
10281
10417
|
const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
|
|
10282
10418
|
(answer) => answer.result,
|
|
@@ -10388,8 +10524,8 @@ async function stopCodeReach(page) {
|
|
|
10388
10524
|
// src/internal/collect/blob-ids.ts
|
|
10389
10525
|
var import_node_child_process4 = require("child_process");
|
|
10390
10526
|
var crypto6 = __toESM(require("crypto"));
|
|
10391
|
-
var
|
|
10392
|
-
var
|
|
10527
|
+
var fs24 = __toESM(require("fs"));
|
|
10528
|
+
var path24 = __toESM(require("path"));
|
|
10393
10529
|
function gitBlobId(content) {
|
|
10394
10530
|
return crypto6.createHash("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
|
|
10395
10531
|
}
|
|
@@ -10398,14 +10534,14 @@ function namesLines(file) {
|
|
|
10398
10534
|
}
|
|
10399
10535
|
var GIT_BATCH = 500;
|
|
10400
10536
|
function gitHashObjects(root, paths) {
|
|
10401
|
-
return new Promise((
|
|
10537
|
+
return new Promise((resolve11) => {
|
|
10402
10538
|
const child = (0, import_node_child_process4.execFile)(
|
|
10403
10539
|
"git",
|
|
10404
10540
|
["hash-object", "--stdin-paths"],
|
|
10405
10541
|
{ cwd: root, encoding: "utf8", timeout: 3e4, windowsHide: true },
|
|
10406
10542
|
(error, stdout) => {
|
|
10407
10543
|
const ids = error ? [] : stdout.split("\n").filter(Boolean);
|
|
10408
|
-
|
|
10544
|
+
resolve11(ids.length === paths.length ? ids : null);
|
|
10409
10545
|
}
|
|
10410
10546
|
);
|
|
10411
10547
|
child.stdin?.on("error", () => {
|
|
@@ -10456,15 +10592,15 @@ var BlobIds = class {
|
|
|
10456
10592
|
async read(relativePaths) {
|
|
10457
10593
|
let root;
|
|
10458
10594
|
try {
|
|
10459
|
-
root =
|
|
10595
|
+
root = path24.resolve(this.repoRoot());
|
|
10460
10596
|
} catch {
|
|
10461
10597
|
return relativePaths.map(() => null);
|
|
10462
10598
|
}
|
|
10463
10599
|
const files = await Promise.all(
|
|
10464
10600
|
relativePaths.map(async (relativePath2) => {
|
|
10465
|
-
const file =
|
|
10466
|
-
if (!file.startsWith(root +
|
|
10467
|
-
const stat = await
|
|
10601
|
+
const file = path24.resolve(root, relativePath2);
|
|
10602
|
+
if (!file.startsWith(root + path24.sep)) return null;
|
|
10603
|
+
const stat = await fs24.promises.stat(file).catch(() => null);
|
|
10468
10604
|
return stat?.isFile() ? file : null;
|
|
10469
10605
|
})
|
|
10470
10606
|
);
|
|
@@ -10472,7 +10608,7 @@ var BlobIds = class {
|
|
|
10472
10608
|
const fromGit = /* @__PURE__ */ new Map();
|
|
10473
10609
|
const ids = forGit.length > 0 ? await this.hashObjects(
|
|
10474
10610
|
root,
|
|
10475
|
-
forGit.map((file) =>
|
|
10611
|
+
forGit.map((file) => path24.relative(root, file))
|
|
10476
10612
|
) : null;
|
|
10477
10613
|
if (ids) forGit.forEach((file, i) => fromGit.set(file, ids[i]));
|
|
10478
10614
|
return Promise.all(
|
|
@@ -10481,7 +10617,7 @@ var BlobIds = class {
|
|
|
10481
10617
|
const id = fromGit.get(file);
|
|
10482
10618
|
if (id) return id;
|
|
10483
10619
|
try {
|
|
10484
|
-
return gitBlobId(await
|
|
10620
|
+
return gitBlobId(await fs24.promises.readFile(file));
|
|
10485
10621
|
} catch {
|
|
10486
10622
|
return null;
|
|
10487
10623
|
}
|
|
@@ -10678,7 +10814,7 @@ var CoverageCollection = class {
|
|
|
10678
10814
|
}
|
|
10679
10815
|
let timer;
|
|
10680
10816
|
let finish;
|
|
10681
|
-
const done = new Promise((
|
|
10817
|
+
const done = new Promise((resolve11) => {
|
|
10682
10818
|
let settled = false;
|
|
10683
10819
|
finish = (server) => {
|
|
10684
10820
|
if (settled) return;
|
|
@@ -10687,7 +10823,7 @@ var CoverageCollection = class {
|
|
|
10687
10823
|
this.pending.delete(entry);
|
|
10688
10824
|
void Promise.resolve(browser).then((files) => {
|
|
10689
10825
|
this.assign(testCase, composeTestCoverage(server, files, this.options.settings));
|
|
10690
|
-
|
|
10826
|
+
resolve11();
|
|
10691
10827
|
});
|
|
10692
10828
|
};
|
|
10693
10829
|
});
|
|
@@ -10719,8 +10855,8 @@ var CoverageCollection = class {
|
|
|
10719
10855
|
let timer;
|
|
10720
10856
|
await Promise.race([
|
|
10721
10857
|
Promise.all(waiting),
|
|
10722
|
-
new Promise((
|
|
10723
|
-
timer = setTimeout(
|
|
10858
|
+
new Promise((resolve11) => {
|
|
10859
|
+
timer = setTimeout(resolve11, boundMs);
|
|
10724
10860
|
})
|
|
10725
10861
|
]);
|
|
10726
10862
|
clearTimeout(timer);
|
|
@@ -10838,10 +10974,13 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10838
10974
|
this.streamManager = null;
|
|
10839
10975
|
/** Track suite-level setup steps (beforeAll/afterAll) not tied to any test */
|
|
10840
10976
|
this.setupSteps = [];
|
|
10841
|
-
|
|
10977
|
+
const configDir = typeof rawOptions.configDir === "string" ? rawOptions.configDir : void 0;
|
|
10978
|
+
this.options = resolveOptions(rawOptions, configDir);
|
|
10842
10979
|
this.enabled = this.options.enabled !== false && !!this.options.serverUrl;
|
|
10843
10980
|
this.listMode = isListMode();
|
|
10844
10981
|
this.viaDesktopApp = usedDesktopDiscovery();
|
|
10982
|
+
this.desktopReplaced = desktopReplacedServer();
|
|
10983
|
+
this.viaDotEnv = usedDotEnv();
|
|
10845
10984
|
this.coverageSettings = coverageSettings(this.options);
|
|
10846
10985
|
const logger = new Logger(this.options.verbose ?? false);
|
|
10847
10986
|
this.logger = logger;
|
|
@@ -10899,14 +11038,17 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10899
11038
|
return;
|
|
10900
11039
|
}
|
|
10901
11040
|
if (this.viaDesktopApp) {
|
|
10902
|
-
this.logger.info(
|
|
11041
|
+
this.logger.info(
|
|
11042
|
+
this.desktopReplaced ? `Using the desktop app running at ${this.options.serverUrl} instead of ${this.desktopReplaced} (desktopApp: 'prefer', not on CI).` : `Using the desktop app running at ${this.options.serverUrl} (no serverUrl configured).`
|
|
11043
|
+
);
|
|
10903
11044
|
}
|
|
11045
|
+
if (this.viaDotEnv) this.logger.debug(`Read ${this.viaDotEnv.keys.join(", ")} from ${this.viaDotEnv.file}.`);
|
|
10904
11046
|
this.startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
10905
11047
|
this.testPathBase = configDirFromConfig(config);
|
|
10906
11048
|
this.playwrightVersion = config.version;
|
|
10907
11049
|
this.maxFailures = config.maxFailures ?? 0;
|
|
10908
11050
|
if (this.options.captureResources !== false) {
|
|
10909
|
-
this.resourcesFile =
|
|
11051
|
+
this.resourcesFile = path25.join(os9.tmpdir(), `piwi-resources-${process.pid}-${Date.now()}.jsonl`);
|
|
10910
11052
|
process.env[PIWI_RESOURCES_RESULTS_ENV] = this.resourcesFile;
|
|
10911
11053
|
try {
|
|
10912
11054
|
this.sampler = new RunSampler({ outputDirs: (config.projects ?? []).map((project) => project.outputDir) });
|
|
@@ -11184,10 +11326,11 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11184
11326
|
if (frames.length) testCase.testSourceFrames = frames;
|
|
11185
11327
|
}
|
|
11186
11328
|
if (this.options.collectPerformanceMetrics && result.steps?.length > 0) {
|
|
11187
|
-
|
|
11329
|
+
const pauses = readPauses(result.attachments?.find((a) => a.name === ATTACHMENT_NAMES.pauses)?.body);
|
|
11330
|
+
testCase.performanceMetrics = collectStepMetrics(result.steps, result.errors, pauses);
|
|
11188
11331
|
const stepEvents = extractTestStepEvents(result.steps, result.startTime);
|
|
11189
11332
|
const waitEvents = extractWaitEvents(result.steps);
|
|
11190
|
-
const allEvents = [...stepEvents, ...waitEvents];
|
|
11333
|
+
const allEvents = [...stepEvents, ...pauseStepEvents(pauses), ...waitEvents];
|
|
11191
11334
|
if (allEvents.length > 0) testCase.stepEvents = allEvents;
|
|
11192
11335
|
}
|
|
11193
11336
|
if (this.options.collectPerformanceMetrics && result.attachments) {
|
|
@@ -11275,7 +11418,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11275
11418
|
if (!file.path || INTERNAL_ATTACHMENT_NAMES.has(file.name)) continue;
|
|
11276
11419
|
try {
|
|
11277
11420
|
const kind = artifactKind(file.name, file.contentType);
|
|
11278
|
-
const bytes =
|
|
11421
|
+
const bytes = fs25.statSync(file.path).size;
|
|
11279
11422
|
artifacts[kind] += bytes;
|
|
11280
11423
|
this.artifactBytes[kind] += bytes;
|
|
11281
11424
|
} catch {
|
|
@@ -11308,8 +11451,8 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11308
11451
|
const censuses = [...this.resourceCensuses];
|
|
11309
11452
|
if (this.resourcesFile) {
|
|
11310
11453
|
try {
|
|
11311
|
-
censuses.push(...parseResourceResults(
|
|
11312
|
-
|
|
11454
|
+
censuses.push(...parseResourceResults(fs25.readFileSync(this.resourcesFile, "utf8")));
|
|
11455
|
+
fs25.rmSync(this.resourcesFile, { force: true });
|
|
11313
11456
|
} catch {
|
|
11314
11457
|
}
|
|
11315
11458
|
}
|
|
@@ -11534,7 +11677,7 @@ async function resolveSelection(options = {}) {
|
|
|
11534
11677
|
}
|
|
11535
11678
|
|
|
11536
11679
|
// src/internal/capture/capture-fixtures.ts
|
|
11537
|
-
var
|
|
11680
|
+
var path30 = __toESM(require("path"));
|
|
11538
11681
|
var import_node_zlib2 = require("zlib");
|
|
11539
11682
|
|
|
11540
11683
|
// ../picker-dom/src/probe.ts
|
|
@@ -11993,8 +12136,8 @@ function installPickerOverlay(arg) {
|
|
|
11993
12136
|
return root && root !== doc && root.host ? root.host : null;
|
|
11994
12137
|
};
|
|
11995
12138
|
const targetOf = (e) => {
|
|
11996
|
-
const
|
|
11997
|
-
const first =
|
|
12139
|
+
const path34 = typeof e.composedPath === "function" ? e.composedPath() : null;
|
|
12140
|
+
const first = path34 && path34.length ? path34[0] : null;
|
|
11998
12141
|
return first && first.nodeType === 1 ? first : e.target;
|
|
11999
12142
|
};
|
|
12000
12143
|
const buildChain = (raw) => {
|
|
@@ -12698,8 +12841,11 @@ function showPauseBar(arg) {
|
|
|
12698
12841
|
};
|
|
12699
12842
|
const buttons = [
|
|
12700
12843
|
["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
|
|
12701
|
-
["Step", "step", "Pause again
|
|
12844
|
+
["Step", "step", "Pause again before the next call", false],
|
|
12702
12845
|
["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
|
|
12846
|
+
...arg.record ? [
|
|
12847
|
+
["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
|
|
12848
|
+
] : [],
|
|
12703
12849
|
["Finish", "finish", "Run on and pause no more in this test", false]
|
|
12704
12850
|
];
|
|
12705
12851
|
for (const [label, choice, hint, primary] of buttons) {
|
|
@@ -13175,21 +13321,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
13175
13321
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
13176
13322
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
13177
13323
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
13178
|
-
function isElementPath(
|
|
13179
|
-
return typeof
|
|
13324
|
+
function isElementPath(path34) {
|
|
13325
|
+
return typeof path34 === "string" && path34.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path34);
|
|
13180
13326
|
}
|
|
13181
13327
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
13182
13328
|
function elementRef(v) {
|
|
13183
13329
|
if (!v || typeof v !== "object") return null;
|
|
13184
|
-
const { path:
|
|
13185
|
-
if (!isElementPath(
|
|
13186
|
-
return { path:
|
|
13330
|
+
const { path: path34, label } = v;
|
|
13331
|
+
if (!isElementPath(path34)) return null;
|
|
13332
|
+
return { path: path34, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
13187
13333
|
}
|
|
13188
13334
|
function textPoint(v) {
|
|
13189
13335
|
if (!v || typeof v !== "object") return null;
|
|
13190
|
-
const { path:
|
|
13336
|
+
const { path: path34, offset } = v;
|
|
13191
13337
|
const at = offsetOf(offset);
|
|
13192
|
-
return isElementPath(
|
|
13338
|
+
return isElementPath(path34) && at !== null ? { path: path34, offset: at } : null;
|
|
13193
13339
|
}
|
|
13194
13340
|
function sanitizePageInteraction(raw) {
|
|
13195
13341
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -13220,7 +13366,7 @@ function sanitizePageInteraction(raw) {
|
|
|
13220
13366
|
}
|
|
13221
13367
|
|
|
13222
13368
|
// src/internal/capture/locator-healing.ts
|
|
13223
|
-
var
|
|
13369
|
+
var path26 = __toESM(require("path"));
|
|
13224
13370
|
|
|
13225
13371
|
// ../core/src/locator-fingerprint.ts
|
|
13226
13372
|
var ELEMENT_MATCH_SCORES = { role: 60, text: 55, label: 50 };
|
|
@@ -13605,10 +13751,10 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
|
|
|
13605
13751
|
}
|
|
13606
13752
|
let rel = file;
|
|
13607
13753
|
try {
|
|
13608
|
-
rel =
|
|
13754
|
+
rel = path26.relative(process.cwd(), file);
|
|
13609
13755
|
} catch {
|
|
13610
13756
|
}
|
|
13611
|
-
rel = rel.split(
|
|
13757
|
+
rel = rel.split(path26.sep).join("/");
|
|
13612
13758
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
13613
13759
|
return `${rel}:${m[3]}:${m[4]}`;
|
|
13614
13760
|
}
|
|
@@ -13838,7 +13984,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
|
|
|
13838
13984
|
}
|
|
13839
13985
|
|
|
13840
13986
|
// src/internal/capture/pick-on-failure.ts
|
|
13841
|
-
var
|
|
13987
|
+
var path27 = __toESM(require("path"));
|
|
13842
13988
|
function parseLeafLocatorExpression(rawExpr) {
|
|
13843
13989
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
13844
13990
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -13853,37 +13999,60 @@ ${err.stack ?? ""}`));
|
|
|
13853
13999
|
const parsed2 = parseLeafLocatorExpression(header);
|
|
13854
14000
|
if (!parsed2) continue;
|
|
13855
14001
|
const loc = err.location;
|
|
13856
|
-
const location = loc ? `${
|
|
14002
|
+
const location = loc ? `${path27.relative(process.cwd(), loc.file).split(path27.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
13857
14003
|
return { method: parsed2.method, args: parsed2.args, location };
|
|
13858
14004
|
}
|
|
13859
14005
|
return null;
|
|
13860
14006
|
}
|
|
13861
|
-
|
|
14007
|
+
var PICK_ENDED = "__piwiPickEnded";
|
|
14008
|
+
async function cleanupPicker(page, ended = false) {
|
|
13862
14009
|
try {
|
|
13863
|
-
await page.evaluate(
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
14010
|
+
await page.evaluate(
|
|
14011
|
+
([endedKey, unanswered]) => {
|
|
14012
|
+
const g = globalThis;
|
|
14013
|
+
if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
|
|
14014
|
+
if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
|
|
14015
|
+
delete g.__piwiPickCleanup;
|
|
14016
|
+
delete g.__piwiAnchorCleanup;
|
|
14017
|
+
delete g.__piwiPickState;
|
|
14018
|
+
delete g.__piwiPickChoice;
|
|
14019
|
+
delete g.__piwiPickedElement;
|
|
14020
|
+
delete g.__piwiAnchorState;
|
|
14021
|
+
delete g.__piwiPickAnchors;
|
|
14022
|
+
delete g.__piwiPickChainCount;
|
|
14023
|
+
if (unanswered) g[endedKey] = true;
|
|
14024
|
+
else delete g[endedKey];
|
|
14025
|
+
},
|
|
14026
|
+
[PICK_ENDED, ended]
|
|
14027
|
+
);
|
|
13876
14028
|
} catch {
|
|
13877
14029
|
}
|
|
13878
14030
|
}
|
|
13879
|
-
|
|
14031
|
+
function unlessEnded(run, ended) {
|
|
14032
|
+
if (!ended) return run;
|
|
14033
|
+
return Promise.race([
|
|
14034
|
+
run,
|
|
14035
|
+
new Promise((resolve11) => {
|
|
14036
|
+
if (ended.aborted) resolve11(void 0);
|
|
14037
|
+
else ended.addEventListener("abort", () => resolve11(void 0), { once: true });
|
|
14038
|
+
})
|
|
14039
|
+
]);
|
|
14040
|
+
}
|
|
14041
|
+
async function pickLocator(page, probe, failing, ended) {
|
|
14042
|
+
const answered = async (name) => {
|
|
14043
|
+
const wait = page.waitForFunction(
|
|
14044
|
+
([key, endedKey]) => globalThis[key] !== void 0 || globalThis[endedKey] === true,
|
|
14045
|
+
[name, PICK_ENDED],
|
|
14046
|
+
{ timeout: 0, polling: 250 }
|
|
14047
|
+
);
|
|
14048
|
+
await unlessEnded(wait, ended);
|
|
14049
|
+
return !ended?.aborted;
|
|
14050
|
+
};
|
|
13880
14051
|
try {
|
|
13881
14052
|
const overlayArg = { transport: "global", failing };
|
|
14053
|
+
await page.evaluate((endedKey) => delete globalThis[endedKey], PICK_ENDED);
|
|
13882
14054
|
await page.evaluate(installPickerOverlay, overlayArg);
|
|
13883
|
-
await
|
|
13884
|
-
timeout: 0,
|
|
13885
|
-
polling: 250
|
|
13886
|
-
});
|
|
14055
|
+
if (!await answered("__piwiPickState")) return null;
|
|
13887
14056
|
const state = await page.evaluate(() => globalThis.__piwiPickState);
|
|
13888
14057
|
if (state !== "picked") return null;
|
|
13889
14058
|
const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
|
|
@@ -13904,10 +14073,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13904
14073
|
leafLevel: level,
|
|
13905
14074
|
leafTestId: attrs.attributes["data-testid"] ?? null
|
|
13906
14075
|
});
|
|
13907
|
-
await
|
|
13908
|
-
timeout: 0,
|
|
13909
|
-
polling: 250
|
|
13910
|
-
});
|
|
14076
|
+
if (!await answered("__piwiAnchorState")) return null;
|
|
13911
14077
|
const anchorResult = await page.evaluate(() => {
|
|
13912
14078
|
const g = globalThis;
|
|
13913
14079
|
return { state: g.__piwiAnchorState, anchors: g.__piwiPickAnchors ?? [], chainCount: g.__piwiPickChainCount };
|
|
@@ -13929,10 +14095,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13929
14095
|
failing,
|
|
13930
14096
|
choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
|
|
13931
14097
|
});
|
|
13932
|
-
await
|
|
13933
|
-
timeout: 0,
|
|
13934
|
-
polling: 250
|
|
13935
|
-
});
|
|
14098
|
+
if (!await answered("__piwiPickChoice")) return null;
|
|
13936
14099
|
const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
|
|
13937
14100
|
if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
|
|
13938
14101
|
const picked = { ...ranked[choice], pickedByUser: true };
|
|
@@ -13951,7 +14114,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13951
14114
|
...anchors.length > 0 ? { anchors } : {}
|
|
13952
14115
|
};
|
|
13953
14116
|
} finally {
|
|
13954
|
-
await cleanupPicker(page);
|
|
14117
|
+
await cleanupPicker(page, ended?.aborted);
|
|
13955
14118
|
}
|
|
13956
14119
|
}
|
|
13957
14120
|
async function runLocatorPicker(page, testInfo, failed, probe) {
|
|
@@ -14006,7 +14169,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
14006
14169
|
}
|
|
14007
14170
|
|
|
14008
14171
|
// src/internal/capture/pause-at.ts
|
|
14009
|
-
var
|
|
14172
|
+
var path28 = __toESM(require("path"));
|
|
14010
14173
|
|
|
14011
14174
|
// ../core/src/editor-send.ts
|
|
14012
14175
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -14047,18 +14210,32 @@ var PauseSet = class {
|
|
|
14047
14210
|
return col > 0 && this.lines.has(callerLocation.slice(0, col));
|
|
14048
14211
|
}
|
|
14049
14212
|
};
|
|
14050
|
-
var
|
|
14213
|
+
var NO_BREAKPOINTS = new PauseSet(/* @__PURE__ */ new Set());
|
|
14051
14214
|
function callSiteFile(file, cwd) {
|
|
14052
14215
|
const slashed = file.replace(/\\/g, "/");
|
|
14053
|
-
const absolute =
|
|
14054
|
-
let rel = absolute ?
|
|
14055
|
-
rel = rel.split(
|
|
14216
|
+
const absolute = path28.isAbsolute(file) || path28.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
|
|
14217
|
+
let rel = absolute ? path28.relative(cwd, file) : path28.normalize(slashed);
|
|
14218
|
+
rel = rel.split(path28.sep).join("/").replace(/\\/g, "/");
|
|
14056
14219
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
14057
14220
|
return rel;
|
|
14058
14221
|
}
|
|
14222
|
+
function stopCallSite(location, cwd = process.cwd()) {
|
|
14223
|
+
if (!location.line) return null;
|
|
14224
|
+
return `${callSiteFile(location.file, cwd)}:${location.line}:${location.column ?? 0}`;
|
|
14225
|
+
}
|
|
14226
|
+
function callSiteLine(callSite) {
|
|
14227
|
+
const col = callSite.lastIndexOf(":");
|
|
14228
|
+
return col > 0 ? callSite.slice(0, col) : callSite;
|
|
14229
|
+
}
|
|
14230
|
+
function inFlightCallSite(inFlight) {
|
|
14231
|
+
const [first] = inFlight;
|
|
14232
|
+
if (first === void 0) return null;
|
|
14233
|
+
const line = callSiteLine(first);
|
|
14234
|
+
return inFlight.every((site) => callSiteLine(site) === line) ? first : null;
|
|
14235
|
+
}
|
|
14059
14236
|
var ignoredLogged = /* @__PURE__ */ new Set();
|
|
14060
14237
|
function parsePauseAt(value, cwd = process.cwd()) {
|
|
14061
|
-
if (!value?.trim()) return
|
|
14238
|
+
if (!value?.trim()) return NO_BREAKPOINTS;
|
|
14062
14239
|
const lines = /* @__PURE__ */ new Set();
|
|
14063
14240
|
for (const raw of value.split(";")) {
|
|
14064
14241
|
const entry = raw.trim();
|
|
@@ -14074,7 +14251,7 @@ function parsePauseAt(value, cwd = process.cwd()) {
|
|
|
14074
14251
|
}
|
|
14075
14252
|
lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
|
|
14076
14253
|
}
|
|
14077
|
-
return lines.size ? new PauseSet(lines) :
|
|
14254
|
+
return lines.size ? new PauseSet(lines) : NO_BREAKPOINTS;
|
|
14078
14255
|
}
|
|
14079
14256
|
var parsed = null;
|
|
14080
14257
|
function currentPauseSet() {
|
|
@@ -14084,27 +14261,121 @@ function currentPauseSet() {
|
|
|
14084
14261
|
}
|
|
14085
14262
|
function pauseGateFromTestInfo(testInfo) {
|
|
14086
14263
|
const use = testInfo.project?.use ?? {};
|
|
14087
|
-
|
|
14264
|
+
const debug = testInfo._configInternal?.configCLIOverrides?.debug;
|
|
14265
|
+
return { ci: process.env.CI, headless: use.headless, pwdebug: process.env.PWDEBUG, debug };
|
|
14088
14266
|
}
|
|
14089
14267
|
function pauseSkipReason(gate) {
|
|
14090
14268
|
if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
|
|
14091
14269
|
if (gate.headless !== false)
|
|
14092
14270
|
return "the browser is headless \u2014 re-run with --headed (or set use: { headless: false })";
|
|
14271
|
+
if (gate.pwdebug && !["0", "false", "console"].includes(gate.pwdebug))
|
|
14272
|
+
return "Playwright's Inspector runs the test (PWDEBUG) \u2014 run without --debug";
|
|
14273
|
+
if (typeof gate.debug === "string" && gate.debug)
|
|
14274
|
+
return `Playwright's debugger runs the test (--debug=${gate.debug}) \u2014 run without --debug`;
|
|
14093
14275
|
return null;
|
|
14094
14276
|
}
|
|
14095
|
-
function
|
|
14096
|
-
|
|
14277
|
+
function atStop(state, callSite) {
|
|
14278
|
+
if (callSite && !state.finished) {
|
|
14279
|
+
if (state.step) {
|
|
14280
|
+
state.step = false;
|
|
14281
|
+
return { show: "step" };
|
|
14282
|
+
}
|
|
14283
|
+
if (state.set.matches(callSite)) return { show: "breakpoint" };
|
|
14284
|
+
}
|
|
14285
|
+
return { go: goOn(state) };
|
|
14286
|
+
}
|
|
14287
|
+
function goOn(state, answer) {
|
|
14288
|
+
if (answer === "step") state.step = true;
|
|
14289
|
+
else if (answer === "finish") state.finished = true;
|
|
14290
|
+
return !state.finished && (state.step || state.set.size > 0) ? "next" : "resume";
|
|
14291
|
+
}
|
|
14292
|
+
function contextDebugger(context) {
|
|
14293
|
+
const dbg = context.debugger;
|
|
14294
|
+
return typeof dbg?.requestPause === "function" && typeof dbg.on === "function" ? dbg : null;
|
|
14295
|
+
}
|
|
14296
|
+
var WATCHED = /* @__PURE__ */ new WeakSet();
|
|
14297
|
+
function watchStops(context, onStop) {
|
|
14298
|
+
const dbg = contextDebugger(context);
|
|
14299
|
+
if (!dbg) return false;
|
|
14300
|
+
if (WATCHED.has(context)) return true;
|
|
14301
|
+
WATCHED.add(context);
|
|
14302
|
+
inspectorSwitch.watching = true;
|
|
14303
|
+
syncInspectorSwitch();
|
|
14304
|
+
dbg.on("pausedstatechanged", () => onStop(context));
|
|
14305
|
+
return true;
|
|
14306
|
+
}
|
|
14307
|
+
var NO_INSPECTOR_ENV = "PW_CODEGEN_NO_INSPECTOR";
|
|
14308
|
+
var inspectorSwitch = {
|
|
14309
|
+
/** Set by Piwi, so Piwi's to clear. */
|
|
14310
|
+
set: false,
|
|
14311
|
+
/** Piwi answers the stops of a context in this worker. */
|
|
14312
|
+
watching: false,
|
|
14313
|
+
/** Every `page.pause()` of the worker's Playwright reaches it through Piwi (`markPausesWrapped`). */
|
|
14314
|
+
wrapped: false,
|
|
14315
|
+
/** The `page.pause()` calls left to Playwright that are running (`withInspector`). */
|
|
14316
|
+
open: 0
|
|
14317
|
+
};
|
|
14318
|
+
function syncInspectorSwitch() {
|
|
14319
|
+
const closed = inspectorSwitch.watching && inspectorSwitch.wrapped && inspectorSwitch.open === 0;
|
|
14320
|
+
if (closed && process.env[NO_INSPECTOR_ENV] === void 0) {
|
|
14321
|
+
process.env[NO_INSPECTOR_ENV] = "1";
|
|
14322
|
+
inspectorSwitch.set = true;
|
|
14323
|
+
} else if (!closed && inspectorSwitch.set) {
|
|
14324
|
+
if (process.env[NO_INSPECTOR_ENV] === "1") delete process.env[NO_INSPECTOR_ENV];
|
|
14325
|
+
inspectorSwitch.set = false;
|
|
14326
|
+
}
|
|
14327
|
+
}
|
|
14328
|
+
function markPausesWrapped() {
|
|
14329
|
+
inspectorSwitch.wrapped = true;
|
|
14330
|
+
syncInspectorSwitch();
|
|
14331
|
+
}
|
|
14332
|
+
async function withInspector(run) {
|
|
14333
|
+
inspectorSwitch.open++;
|
|
14334
|
+
syncInspectorSwitch();
|
|
14335
|
+
try {
|
|
14336
|
+
return await run();
|
|
14337
|
+
} finally {
|
|
14338
|
+
inspectorSwitch.open--;
|
|
14339
|
+
syncInspectorSwitch();
|
|
14340
|
+
}
|
|
14341
|
+
}
|
|
14342
|
+
async function steerDebugger(context, how) {
|
|
14343
|
+
const dbg = contextDebugger(context);
|
|
14344
|
+
if (!dbg) return;
|
|
14345
|
+
try {
|
|
14346
|
+
await internalCall(
|
|
14347
|
+
context,
|
|
14348
|
+
() => how === "pause" ? dbg.requestPause() : how === "next" ? dbg.next() : dbg.resume()
|
|
14349
|
+
);
|
|
14350
|
+
} catch {
|
|
14351
|
+
}
|
|
14352
|
+
}
|
|
14353
|
+
async function withTestTimeoutHeld(testInfo, run) {
|
|
14354
|
+
const set = testInfo._setIgnoreTimeouts;
|
|
14355
|
+
if (typeof set !== "function") return run();
|
|
14356
|
+
set.call(testInfo, true);
|
|
14357
|
+
try {
|
|
14358
|
+
return await run();
|
|
14359
|
+
} finally {
|
|
14360
|
+
set.call(testInfo, false);
|
|
14361
|
+
}
|
|
14362
|
+
}
|
|
14363
|
+
function testTimeSlot(testInfo) {
|
|
14364
|
+
try {
|
|
14365
|
+
const slot = testInfo._timeoutManager?.defaultSlot?.();
|
|
14366
|
+
return slot && typeof slot.elapsed === "number" ? slot : null;
|
|
14367
|
+
} catch {
|
|
14368
|
+
return null;
|
|
14369
|
+
}
|
|
14370
|
+
}
|
|
14371
|
+
function restoreTestTime(testInfo, heldAt) {
|
|
14372
|
+
const slot = testTimeSlot(testInfo);
|
|
14373
|
+
if (slot && Number.isFinite(heldAt) && heldAt >= 0 && heldAt < slot.elapsed) slot.elapsed = heldAt;
|
|
14097
14374
|
}
|
|
14098
14375
|
function pausePlace(callerLocation) {
|
|
14099
|
-
const
|
|
14100
|
-
const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
|
|
14376
|
+
const fileLine = callSiteLine(callerLocation);
|
|
14101
14377
|
return fileLine.slice(fileLine.lastIndexOf("/") + 1);
|
|
14102
14378
|
}
|
|
14103
|
-
function assertionLabel(expression, isNot) {
|
|
14104
|
-
const [first = "", ...rest] = expression.split(".").filter(Boolean);
|
|
14105
|
-
const name = first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
14106
|
-
return `${isNot ? "not." : ""}${name || "expect"}`;
|
|
14107
|
-
}
|
|
14108
14379
|
function pickPlace(callerLocation) {
|
|
14109
14380
|
const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
|
|
14110
14381
|
if (!m) return null;
|
|
@@ -14149,59 +14420,312 @@ function logPauseSkipOnce(reason) {
|
|
|
14149
14420
|
skipLogged = true;
|
|
14150
14421
|
console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
|
|
14151
14422
|
}
|
|
14423
|
+
var NAVIGATED = /Execution context was destroyed|Cannot find context with specified id/;
|
|
14424
|
+
var MAX_NAVIGATED = 10;
|
|
14152
14425
|
var HIGHLIGHT_MS = 2e3;
|
|
14153
|
-
async function pauseHere(page,
|
|
14154
|
-
const before = testInfo.timeout;
|
|
14155
|
-
const started = Date.now();
|
|
14426
|
+
async function pauseHere(page, point, probe, onPick, onRecord, ended) {
|
|
14156
14427
|
let shown = null;
|
|
14157
14428
|
try {
|
|
14158
|
-
testInfo.setTimeout(0);
|
|
14159
14429
|
const highlight = point.target?.highlight;
|
|
14160
14430
|
if (typeof highlight === "function") {
|
|
14161
14431
|
let timer;
|
|
14162
14432
|
shown = await Promise.race([
|
|
14163
14433
|
Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
|
|
14164
|
-
new Promise((
|
|
14165
|
-
timer = setTimeout(() =>
|
|
14434
|
+
new Promise((resolve11) => {
|
|
14435
|
+
timer = setTimeout(() => resolve11(null), HIGHLIGHT_MS);
|
|
14166
14436
|
})
|
|
14167
14437
|
]);
|
|
14168
14438
|
clearTimeout(timer);
|
|
14169
14439
|
}
|
|
14170
14440
|
const arg = {
|
|
14171
14441
|
place: pausePlace(point.location),
|
|
14172
|
-
attempt:
|
|
14173
|
-
action: point.
|
|
14174
|
-
locator:
|
|
14442
|
+
attempt: point.attempt,
|
|
14443
|
+
action: point.call,
|
|
14444
|
+
locator: null,
|
|
14445
|
+
record: !!onRecord
|
|
14175
14446
|
};
|
|
14176
14447
|
console.log(
|
|
14177
|
-
`[piwi] Paused at ${point.location} before ${point.
|
|
14448
|
+
`[piwi] Paused at ${callSiteLine(point.location)} before ${point.call}: Resume, Step, Pick a locator${onRecord ? ", Record here" : ""} or Finish in the browser (Esc resumes).`
|
|
14178
14449
|
);
|
|
14450
|
+
let navigated = 0;
|
|
14179
14451
|
for (; ; ) {
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14452
|
+
try {
|
|
14453
|
+
if (ended?.aborted) return "resume";
|
|
14454
|
+
await page.evaluate(showPauseBar, arg);
|
|
14455
|
+
await unlessEnded(page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 }), ended);
|
|
14456
|
+
if (ended?.aborted) return "resume";
|
|
14457
|
+
const choice = await page.evaluate(takePauseState);
|
|
14458
|
+
navigated = 0;
|
|
14459
|
+
if (choice === "resume" || choice === "step" || choice === "finish") return choice;
|
|
14460
|
+
if (choice === "record" && onRecord) {
|
|
14461
|
+
await unlessEnded(
|
|
14462
|
+
onRecord(ended).catch(() => void 0),
|
|
14463
|
+
ended
|
|
14464
|
+
);
|
|
14465
|
+
continue;
|
|
14466
|
+
}
|
|
14467
|
+
if (choice !== "pick") continue;
|
|
14468
|
+
const picked = await pickLocator(page, probe, null, ended).catch(() => null);
|
|
14469
|
+
if (picked && !ended?.aborted) await onPick(picked);
|
|
14470
|
+
} catch (error) {
|
|
14471
|
+
if (page.isClosed() || !NAVIGATED.test(String(error)) || ++navigated > MAX_NAVIGATED) throw error;
|
|
14472
|
+
await page.waitForLoadState("domcontentloaded").catch(() => void 0);
|
|
14473
|
+
}
|
|
14187
14474
|
}
|
|
14188
14475
|
} catch {
|
|
14189
14476
|
return "resume";
|
|
14190
14477
|
} finally {
|
|
14191
14478
|
try {
|
|
14479
|
+
if (ended?.aborted) await page.evaluate(takePauseState);
|
|
14192
14480
|
const dispose = shown?.dispose;
|
|
14193
14481
|
const hide = page.hideHighlight;
|
|
14194
14482
|
if (typeof dispose === "function") await dispose.call(shown);
|
|
14195
14483
|
else if (typeof hide === "function") await hide.call(page);
|
|
14196
14484
|
} catch {
|
|
14197
14485
|
}
|
|
14198
|
-
try {
|
|
14199
|
-
testInfo.setTimeout(rearmedTimeout(before, Date.now() - started));
|
|
14200
|
-
} catch {
|
|
14201
|
-
}
|
|
14202
14486
|
}
|
|
14203
14487
|
}
|
|
14204
14488
|
|
|
14489
|
+
// src/internal/capture/record-handoff.ts
|
|
14490
|
+
var fs26 = __toESM(require("fs"));
|
|
14491
|
+
var net = __toESM(require("net"));
|
|
14492
|
+
var path29 = __toESM(require("path"));
|
|
14493
|
+
|
|
14494
|
+
// ../core/src/ide-recorder.ts
|
|
14495
|
+
var IDE_RECORDER_BINDING = "__piwiRecorder";
|
|
14496
|
+
var IDE_DISPATCH_GLOBAL = "__piwiIdeDispatch";
|
|
14497
|
+
var RECORD_CHANNEL_ENV = "PIWI_RECORD_CHANNEL";
|
|
14498
|
+
var MAX_HANDOFF_LINE = 64e6;
|
|
14499
|
+
function parseRecordChannel(value) {
|
|
14500
|
+
const text = value?.trim() ?? "";
|
|
14501
|
+
const hash = text.lastIndexOf("#");
|
|
14502
|
+
if (hash <= 0) return null;
|
|
14503
|
+
const token = text.slice(hash + 1);
|
|
14504
|
+
const path34 = text.slice(0, hash);
|
|
14505
|
+
return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path34.includes("\0") ? { path: path34, token } : null;
|
|
14506
|
+
}
|
|
14507
|
+
function isObject2(value) {
|
|
14508
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14509
|
+
}
|
|
14510
|
+
function parseHandoffToTest(line) {
|
|
14511
|
+
let value;
|
|
14512
|
+
try {
|
|
14513
|
+
value = JSON.parse(line);
|
|
14514
|
+
} catch {
|
|
14515
|
+
return null;
|
|
14516
|
+
}
|
|
14517
|
+
if (!isObject2(value)) return null;
|
|
14518
|
+
switch (value.type) {
|
|
14519
|
+
case "refused":
|
|
14520
|
+
return typeof value.message === "string" ? { type: "refused", message: value.message } : null;
|
|
14521
|
+
case "load":
|
|
14522
|
+
return typeof value.bundle === "string" && value.bundle ? { type: "load", bundle: value.bundle } : null;
|
|
14523
|
+
case "answer":
|
|
14524
|
+
return typeof value.call === "number" ? { type: "answer", call: value.call, value: value.value } : null;
|
|
14525
|
+
case "dispatch":
|
|
14526
|
+
return { type: "dispatch", message: value.message };
|
|
14527
|
+
case "stop":
|
|
14528
|
+
return { type: "stop" };
|
|
14529
|
+
default:
|
|
14530
|
+
return null;
|
|
14531
|
+
}
|
|
14532
|
+
}
|
|
14533
|
+
function lineReader(onLine, onOverflow) {
|
|
14534
|
+
let buffered = "";
|
|
14535
|
+
return (chunk) => {
|
|
14536
|
+
buffered += chunk;
|
|
14537
|
+
for (let end = buffered.indexOf("\n"); end >= 0; end = buffered.indexOf("\n")) {
|
|
14538
|
+
const line = buffered.slice(0, end);
|
|
14539
|
+
buffered = buffered.slice(end + 1);
|
|
14540
|
+
if (line.trim()) onLine(line);
|
|
14541
|
+
}
|
|
14542
|
+
if (buffered.length > MAX_HANDOFF_LINE) {
|
|
14543
|
+
buffered = "";
|
|
14544
|
+
onOverflow();
|
|
14545
|
+
}
|
|
14546
|
+
};
|
|
14547
|
+
}
|
|
14548
|
+
|
|
14549
|
+
// src/internal/capture/record-handoff.ts
|
|
14550
|
+
var START_WAIT_MS = 6e4;
|
|
14551
|
+
var NEW_TAB_NOTICE = "A new tab opened: what you do there is not recorded yet. Record in the first tab.";
|
|
14552
|
+
var relays = /* @__PURE__ */ new WeakMap();
|
|
14553
|
+
function idleAnswer(payload) {
|
|
14554
|
+
const request3 = payload ?? {};
|
|
14555
|
+
if (request3.kind === "local-get") return {};
|
|
14556
|
+
if (request3.kind !== "message") return null;
|
|
14557
|
+
if (request3.message?.type === "piwi-ping") return { ok: true };
|
|
14558
|
+
if (request3.message?.type === "piwi-session-storage") {
|
|
14559
|
+
return request3.message.op === "get" ? { ok: true, items: {} } : { ok: true };
|
|
14560
|
+
}
|
|
14561
|
+
return { ok: false, error: "No recording runs in this page." };
|
|
14562
|
+
}
|
|
14563
|
+
function canRecordFromHere(channel = process.env[RECORD_CHANNEL_ENV]) {
|
|
14564
|
+
return parseRecordChannel(channel) !== null;
|
|
14565
|
+
}
|
|
14566
|
+
function handoffPlace(location, cwd = process.cwd()) {
|
|
14567
|
+
const m = /^(.+):(\d+):\d+$/.exec(location);
|
|
14568
|
+
if (!m) return null;
|
|
14569
|
+
return { file: path29.resolve(cwd, m[1]), line: Number(m[2]) };
|
|
14570
|
+
}
|
|
14571
|
+
async function recordFromHere(options) {
|
|
14572
|
+
const channel = parseRecordChannel(options.channel ?? process.env[RECORD_CHANNEL_ENV]);
|
|
14573
|
+
const place = handoffPlace(options.location);
|
|
14574
|
+
if (!channel || !place) return "this run names no editor to record into.";
|
|
14575
|
+
const { page, testInfo, ended } = options;
|
|
14576
|
+
if (ended?.aborted) return "the test ended.";
|
|
14577
|
+
const use = testInfo.project?.use ?? {};
|
|
14578
|
+
const context = page.context();
|
|
14579
|
+
return new Promise((resolve11) => {
|
|
14580
|
+
const socket = net.connect(channel.path);
|
|
14581
|
+
socket.setEncoding("utf8");
|
|
14582
|
+
const answers = /* @__PURE__ */ new Map();
|
|
14583
|
+
let calls = 0;
|
|
14584
|
+
let attached = false;
|
|
14585
|
+
let recording = false;
|
|
14586
|
+
let settled = false;
|
|
14587
|
+
let initScript;
|
|
14588
|
+
const send = (message) => {
|
|
14589
|
+
if (!socket.destroyed) socket.write(`${JSON.stringify(message)}
|
|
14590
|
+
`);
|
|
14591
|
+
};
|
|
14592
|
+
const onPage = () => send({ type: "notice", message: NEW_TAB_NOTICE });
|
|
14593
|
+
const onGone = () => {
|
|
14594
|
+
if (attached) send({ type: "closed" });
|
|
14595
|
+
finish("the page closed.");
|
|
14596
|
+
};
|
|
14597
|
+
const onEnded = () => {
|
|
14598
|
+
try {
|
|
14599
|
+
if (recording) {
|
|
14600
|
+
void internalCall(
|
|
14601
|
+
page,
|
|
14602
|
+
() => page.evaluate(
|
|
14603
|
+
([name, value]) => {
|
|
14604
|
+
const dispatch = globalThis[name];
|
|
14605
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
14606
|
+
},
|
|
14607
|
+
[IDE_DISPATCH_GLOBAL, { type: "piwi-recording-stopped" }]
|
|
14608
|
+
)
|
|
14609
|
+
).catch(() => void 0);
|
|
14610
|
+
}
|
|
14611
|
+
if (attached) send({ type: "closed" });
|
|
14612
|
+
finish("the test ended.");
|
|
14613
|
+
} catch {
|
|
14614
|
+
}
|
|
14615
|
+
};
|
|
14616
|
+
page.once("close", onGone);
|
|
14617
|
+
page.once("crash", onGone);
|
|
14618
|
+
ended?.addEventListener("abort", onEnded, { once: true });
|
|
14619
|
+
const startWait = setTimeout(() => finish("the editor did not start the recording."), START_WAIT_MS);
|
|
14620
|
+
const release = () => {
|
|
14621
|
+
const relay = relays.get(context);
|
|
14622
|
+
if (relay && relay.recording === relayed) relay.recording = null;
|
|
14623
|
+
void Promise.resolve(initScript?.dispose?.()).catch(() => void 0);
|
|
14624
|
+
initScript = void 0;
|
|
14625
|
+
};
|
|
14626
|
+
const finish = (said) => {
|
|
14627
|
+
if (settled) return;
|
|
14628
|
+
settled = true;
|
|
14629
|
+
clearTimeout(startWait);
|
|
14630
|
+
release();
|
|
14631
|
+
for (const [call, answer] of answers) {
|
|
14632
|
+
answers.delete(call);
|
|
14633
|
+
answer(null);
|
|
14634
|
+
}
|
|
14635
|
+
context.off("page", onPage);
|
|
14636
|
+
page.off("close", onGone);
|
|
14637
|
+
page.off("crash", onGone);
|
|
14638
|
+
ended?.removeEventListener("abort", onEnded);
|
|
14639
|
+
socket.end();
|
|
14640
|
+
resolve11(recording ? null : said);
|
|
14641
|
+
};
|
|
14642
|
+
const relayed = (source, payload) => new Promise((answer) => {
|
|
14643
|
+
const call = ++calls;
|
|
14644
|
+
answers.set(call, answer);
|
|
14645
|
+
const outside = source.page !== page || source.frame !== page.mainFrame();
|
|
14646
|
+
send({ type: "binding", call, outside, payload });
|
|
14647
|
+
});
|
|
14648
|
+
const load = async (bundle) => {
|
|
14649
|
+
if (!/^https?:/i.test(page.url())) {
|
|
14650
|
+
const why = `the recorder records http(s) pages only, and this page is ${page.url() || "blank"}.`;
|
|
14651
|
+
send({ type: "notice", message: `Nothing can be recorded here: ${why}` });
|
|
14652
|
+
finish(why);
|
|
14653
|
+
return;
|
|
14654
|
+
}
|
|
14655
|
+
let relay = relays.get(context);
|
|
14656
|
+
if (!relay) {
|
|
14657
|
+
const created = { recording: null, lingering: false };
|
|
14658
|
+
await context.exposeBinding(
|
|
14659
|
+
IDE_RECORDER_BINDING,
|
|
14660
|
+
(source, payload) => created.recording ? created.recording(source, payload) : idleAnswer(payload)
|
|
14661
|
+
);
|
|
14662
|
+
relays.set(context, created);
|
|
14663
|
+
relay = created;
|
|
14664
|
+
}
|
|
14665
|
+
if (settled) return;
|
|
14666
|
+
relay.recording = relayed;
|
|
14667
|
+
if (!relay.lingering) {
|
|
14668
|
+
initScript = await context.addInitScript({ path: bundle });
|
|
14669
|
+
if (typeof initScript?.dispose !== "function") relay.lingering = true;
|
|
14670
|
+
}
|
|
14671
|
+
if (settled) return release();
|
|
14672
|
+
await page.evaluate(fs26.readFileSync(bundle, "utf-8"));
|
|
14673
|
+
if (settled) return release();
|
|
14674
|
+
recording = true;
|
|
14675
|
+
clearTimeout(startWait);
|
|
14676
|
+
context.on("page", onPage);
|
|
14677
|
+
send({ type: "loaded" });
|
|
14678
|
+
};
|
|
14679
|
+
socket.on("connect", () => {
|
|
14680
|
+
attached = true;
|
|
14681
|
+
send({
|
|
14682
|
+
type: "attach",
|
|
14683
|
+
token: channel.token,
|
|
14684
|
+
file: place.file,
|
|
14685
|
+
line: place.line,
|
|
14686
|
+
reason: options.reason,
|
|
14687
|
+
testIdAttribute: typeof use.testIdAttribute === "string" ? use.testIdAttribute : null,
|
|
14688
|
+
baseURL: typeof use.baseURL === "string" ? use.baseURL : null
|
|
14689
|
+
});
|
|
14690
|
+
});
|
|
14691
|
+
socket.on(
|
|
14692
|
+
"data",
|
|
14693
|
+
lineReader(
|
|
14694
|
+
(line) => {
|
|
14695
|
+
const message = parseHandoffToTest(line);
|
|
14696
|
+
if (!message || settled) return;
|
|
14697
|
+
switch (message.type) {
|
|
14698
|
+
case "refused":
|
|
14699
|
+
return finish(message.message);
|
|
14700
|
+
case "load":
|
|
14701
|
+
return void load(message.bundle).catch(
|
|
14702
|
+
(error) => finish(`the recorder could not be loaded: ${error instanceof Error ? error.message : String(error)}`)
|
|
14703
|
+
);
|
|
14704
|
+
case "answer": {
|
|
14705
|
+
const answer = answers.get(message.call);
|
|
14706
|
+
answers.delete(message.call);
|
|
14707
|
+
return answer?.(message.value);
|
|
14708
|
+
}
|
|
14709
|
+
case "dispatch":
|
|
14710
|
+
return void page.evaluate(
|
|
14711
|
+
([name, value]) => {
|
|
14712
|
+
const dispatch = globalThis[name];
|
|
14713
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
14714
|
+
},
|
|
14715
|
+
[IDE_DISPATCH_GLOBAL, message.message]
|
|
14716
|
+
).catch(() => void 0);
|
|
14717
|
+
case "stop":
|
|
14718
|
+
return finish(null);
|
|
14719
|
+
}
|
|
14720
|
+
},
|
|
14721
|
+
() => finish("the editor sent a message too long to read.")
|
|
14722
|
+
)
|
|
14723
|
+
);
|
|
14724
|
+
socket.on("error", (error) => finish(`the editor could not be reached: ${error.message}`));
|
|
14725
|
+
socket.on("close", () => finish("the editor closed the recording."));
|
|
14726
|
+
});
|
|
14727
|
+
}
|
|
14728
|
+
|
|
14205
14729
|
// src/internal/capture/page-interaction.ts
|
|
14206
14730
|
function readPageInteraction() {
|
|
14207
14731
|
const doc = globalThis.document;
|
|
@@ -14497,6 +15021,13 @@ function createSink() {
|
|
|
14497
15021
|
pickOffered: false,
|
|
14498
15022
|
userPick: null,
|
|
14499
15023
|
pause: null,
|
|
15024
|
+
locatorCalls: /* @__PURE__ */ new Map(),
|
|
15025
|
+
stops: [],
|
|
15026
|
+
shownStops: /* @__PURE__ */ new Map(),
|
|
15027
|
+
released: [],
|
|
15028
|
+
releasedRequests: /* @__PURE__ */ new Set(),
|
|
15029
|
+
onReleasedRequestsEnded: null,
|
|
15030
|
+
releasedQuietSince: 0,
|
|
14500
15031
|
locatorPages: new LocatorPageLog(),
|
|
14501
15032
|
codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
|
|
14502
15033
|
serverCoverage: /* @__PURE__ */ new Map(),
|
|
@@ -14522,7 +15053,7 @@ function maybeStartCodeReach(page, alreadyOpen = false) {
|
|
|
14522
15053
|
async function startCodeReachOnOpenPages() {
|
|
14523
15054
|
if (!browserCoverageInWorker() || OPEN_PAGES.size === 0) return;
|
|
14524
15055
|
const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
|
|
14525
|
-
await Promise.race([started, new Promise((
|
|
15056
|
+
await Promise.race([started, new Promise((resolve11) => setTimeout(resolve11, CODE_REACH_START_WAIT_MS).unref())]);
|
|
14526
15057
|
}
|
|
14527
15058
|
function configuredCodeReachRoots() {
|
|
14528
15059
|
const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
|
|
@@ -14536,7 +15067,7 @@ function configuredCodeReachRoots() {
|
|
|
14536
15067
|
}
|
|
14537
15068
|
} catch {
|
|
14538
15069
|
}
|
|
14539
|
-
return raw.split(
|
|
15070
|
+
return raw.split(path30.delimiter).filter(Boolean);
|
|
14540
15071
|
}
|
|
14541
15072
|
async function stopSinkCodeReach(sink, closing) {
|
|
14542
15073
|
const stopping = [];
|
|
@@ -14585,8 +15116,8 @@ async function drainPendingProbes(capMs) {
|
|
|
14585
15116
|
let cap;
|
|
14586
15117
|
await Promise.race([
|
|
14587
15118
|
Promise.allSettled(PENDING_PROBES),
|
|
14588
|
-
new Promise((
|
|
14589
|
-
cap = setTimeout(
|
|
15119
|
+
new Promise((resolve11) => {
|
|
15120
|
+
cap = setTimeout(resolve11, capMs);
|
|
14590
15121
|
})
|
|
14591
15122
|
]);
|
|
14592
15123
|
clearTimeout(cap);
|
|
@@ -14626,10 +15157,10 @@ async function readWebVitals(page) {
|
|
|
14626
15157
|
const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
14627
15158
|
paint[key] = Math.round(entry.startTime);
|
|
14628
15159
|
}
|
|
14629
|
-
const readBuffered = (type, extra) => new Promise((
|
|
15160
|
+
const readBuffered = (type, extra) => new Promise((resolve11) => {
|
|
14630
15161
|
try {
|
|
14631
15162
|
const PO = globalThis.PerformanceObserver;
|
|
14632
|
-
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return
|
|
15163
|
+
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve11(null);
|
|
14633
15164
|
const out = [];
|
|
14634
15165
|
const po = new PO((list) => out.push(...list.getEntries()));
|
|
14635
15166
|
po.observe({ type, buffered: true, ...extra });
|
|
@@ -14639,10 +15170,10 @@ async function readWebVitals(page) {
|
|
|
14639
15170
|
po.disconnect();
|
|
14640
15171
|
} catch {
|
|
14641
15172
|
}
|
|
14642
|
-
|
|
15173
|
+
resolve11(out);
|
|
14643
15174
|
}, 0);
|
|
14644
15175
|
} catch {
|
|
14645
|
-
|
|
15176
|
+
resolve11(null);
|
|
14646
15177
|
}
|
|
14647
15178
|
});
|
|
14648
15179
|
const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
|
|
@@ -14757,12 +15288,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
|
|
|
14757
15288
|
var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
|
|
14758
15289
|
var MAX_PAGE_INVENTORY_WINDOWS = 64;
|
|
14759
15290
|
function withTimeout(promise, ms) {
|
|
14760
|
-
return new Promise((
|
|
15291
|
+
return new Promise((resolve11, reject) => {
|
|
14761
15292
|
const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
|
|
14762
15293
|
promise.then(
|
|
14763
15294
|
(value) => {
|
|
14764
15295
|
clearTimeout(timer);
|
|
14765
|
-
|
|
15296
|
+
resolve11(value);
|
|
14766
15297
|
},
|
|
14767
15298
|
(error) => {
|
|
14768
15299
|
clearTimeout(timer);
|
|
@@ -14877,6 +15408,7 @@ async function maybeOpenPicker(sink, closing) {
|
|
|
14877
15408
|
var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
14878
15409
|
var OPEN_PAGES = /* @__PURE__ */ new Set();
|
|
14879
15410
|
var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
|
|
15411
|
+
var OPEN_CONTEXTS = /* @__PURE__ */ new Set();
|
|
14880
15412
|
var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
14881
15413
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
14882
15414
|
var ACTION_METHOD_SET = new Set(ACTION_METHODS);
|
|
@@ -14997,8 +15529,20 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
|
|
|
14997
15529
|
} catch {
|
|
14998
15530
|
}
|
|
14999
15531
|
}
|
|
15000
|
-
function
|
|
15001
|
-
|
|
15532
|
+
function knowLocatorCall(sink, page, target, location, origin) {
|
|
15533
|
+
if (!sink?.pause || !location) return null;
|
|
15534
|
+
let settle = () => {
|
|
15535
|
+
};
|
|
15536
|
+
const settled = new Promise((resolve11) => settle = resolve11);
|
|
15537
|
+
const call = { page, target, location, origin, picked: false, settled, settle };
|
|
15538
|
+
sink.locatorCalls.set(callSiteLine(location), call);
|
|
15539
|
+
return call;
|
|
15540
|
+
}
|
|
15541
|
+
function forgetLocatorCall(sink, call) {
|
|
15542
|
+
if (!sink || !call) return;
|
|
15543
|
+
call.settle();
|
|
15544
|
+
const key = callSiteLine(call.location);
|
|
15545
|
+
if (sink.locatorCalls.get(key) === call) sink.locatorCalls.delete(key);
|
|
15002
15546
|
}
|
|
15003
15547
|
function locatorSource(target, method, args) {
|
|
15004
15548
|
try {
|
|
@@ -15008,41 +15552,219 @@ function locatorSource(target, method, args) {
|
|
|
15008
15552
|
}
|
|
15009
15553
|
return renderFailing({ method, args });
|
|
15010
15554
|
}
|
|
15011
|
-
|
|
15012
|
-
|
|
15555
|
+
var PAGE_PAUSES = /* @__PURE__ */ new WeakMap();
|
|
15556
|
+
var PLAYWRIGHT_PAUSES = /* @__PURE__ */ new WeakMap();
|
|
15557
|
+
async function armBreakpoints(contexts) {
|
|
15558
|
+
const state = currentSink?.pause;
|
|
15559
|
+
if (!state || state.finished || state.set.size === 0) return;
|
|
15560
|
+
await Promise.all(
|
|
15561
|
+
[...contexts].map(
|
|
15562
|
+
(context) => context && watchStops(context, onDebuggerStop) ? steerDebugger(context, "pause") : void 0
|
|
15563
|
+
)
|
|
15564
|
+
);
|
|
15565
|
+
}
|
|
15566
|
+
function onDebuggerStop(context) {
|
|
15567
|
+
const details = contextDebugger(context)?.pausedDetails();
|
|
15568
|
+
if (!details || PLAYWRIGHT_PAUSES.has(context)) return;
|
|
15569
|
+
const sink = currentSink;
|
|
15570
|
+
const state = sink?.pause ?? null;
|
|
15571
|
+
void (async () => {
|
|
15572
|
+
let how = "resume";
|
|
15573
|
+
try {
|
|
15574
|
+
if (!sink || !state) return;
|
|
15575
|
+
const callSite = stopSite(sink, details.location);
|
|
15576
|
+
const pagePause = PAGE_PAUSES.get(context);
|
|
15577
|
+
const decided = pagePause ? { show: "page-pause" } : atStop(state, callSite);
|
|
15578
|
+
if ("go" in decided) {
|
|
15579
|
+
how = decided.go;
|
|
15580
|
+
return;
|
|
15581
|
+
}
|
|
15582
|
+
how = goOn(state, await showStop(sink, context, details.title, callSite ?? pagePause, decided.show));
|
|
15583
|
+
} catch {
|
|
15584
|
+
if (state) how = goOn(state);
|
|
15585
|
+
} finally {
|
|
15586
|
+
await steerDebugger(context, how);
|
|
15587
|
+
}
|
|
15588
|
+
})();
|
|
15589
|
+
}
|
|
15590
|
+
function stopSite(sink, location) {
|
|
15591
|
+
if (path30.dirname(path30.resolve(location.file)) !== __dirname) return stopCallSite(location);
|
|
15592
|
+
return inFlightCallSite([...sink.locatorCalls.values()].map((call) => call.location));
|
|
15593
|
+
}
|
|
15594
|
+
function stopPage(sink, context, call) {
|
|
15595
|
+
for (const page of [call?.page, sink.lastActivePage]) {
|
|
15596
|
+
if (page && !isPageClosed(page) && pageContext(page) === context) return page;
|
|
15597
|
+
}
|
|
15598
|
+
const open = context.pages().filter((page) => !isPageClosed(page));
|
|
15599
|
+
return open[open.length - 1] ?? null;
|
|
15600
|
+
}
|
|
15601
|
+
async function showStop(sink, context, title, location, reason) {
|
|
15013
15602
|
const testInfo = sink.testInfo;
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15603
|
+
const call = sink.locatorCalls.get(callSiteLine(location));
|
|
15604
|
+
const page = stopPage(sink, context, call);
|
|
15605
|
+
if (!testInfo || !page) return void 0;
|
|
15606
|
+
const onPick = async (picked) => {
|
|
15607
|
+
if (call) {
|
|
15608
|
+
call.picked = true;
|
|
15609
|
+
const { method, args } = call.origin;
|
|
15610
|
+
const rendered = locatorSource(call.target, method, args);
|
|
15611
|
+
applyPickToSnapshots(sink.capturedLocators, {
|
|
15612
|
+
failing: { method, args, rendered, location: call.location },
|
|
15021
15613
|
...picked
|
|
15022
|
-
};
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15614
|
+
});
|
|
15615
|
+
sink.probedLocations.add(call.location);
|
|
15616
|
+
}
|
|
15617
|
+
console.log(`[piwi] Locator picked at ${callSiteLine(location)}: ${picked.picked.locator}`);
|
|
15618
|
+
await postPickToEditor(process.env[PIWI_LOCAL_DEBUG_ENV.editorSend], pickPayload(picked.picked.locator, location));
|
|
15619
|
+
};
|
|
15620
|
+
const record = { title, location, reason, startedAt: Date.now(), duration: 0 };
|
|
15621
|
+
sink.stops.push(record);
|
|
15622
|
+
const end = new AbortController();
|
|
15623
|
+
const shown = withTestTimeoutHeld(
|
|
15624
|
+
testInfo,
|
|
15625
|
+
() => internalCall(
|
|
15033
15626
|
page,
|
|
15034
15627
|
() => pauseHere(
|
|
15035
15628
|
page,
|
|
15036
|
-
|
|
15037
|
-
|
|
15629
|
+
{
|
|
15630
|
+
location,
|
|
15631
|
+
call: title,
|
|
15632
|
+
attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
|
|
15633
|
+
target: call?.page === page ? call.target : null
|
|
15634
|
+
},
|
|
15038
15635
|
{ fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
|
|
15039
|
-
onPick
|
|
15636
|
+
onPick,
|
|
15637
|
+
recorderAt(page, testInfo, location, reason === "page-pause" ? "pause" : "breakpoint"),
|
|
15638
|
+
end.signal
|
|
15040
15639
|
)
|
|
15041
|
-
)
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15640
|
+
)
|
|
15641
|
+
).finally(() => {
|
|
15642
|
+
if (!end.signal.aborted) record.duration = Date.now() - record.startedAt;
|
|
15643
|
+
else if (end.signal.reason === "ended") releaseCall(sink, call?.settled ?? Promise.resolve());
|
|
15644
|
+
sink.shownStops.delete(end);
|
|
15645
|
+
});
|
|
15646
|
+
const heldAt = testTimeSlot(testInfo)?.elapsed ?? null;
|
|
15647
|
+
sink.shownStops.set(end, { page, record, done: shown.catch(() => void 0), heldAt });
|
|
15648
|
+
const answer = await shown;
|
|
15649
|
+
if (!end.signal.aborted) return answer;
|
|
15650
|
+
return end.signal.reason === "closed" ? "resume" : "finish";
|
|
15651
|
+
}
|
|
15652
|
+
var END_STOP_MS = 2e3;
|
|
15653
|
+
var RELEASED_CALL_MS = 2e3;
|
|
15654
|
+
var RELEASED_QUIET_MS = 200;
|
|
15655
|
+
async function endShownStops(sink, why, closing) {
|
|
15656
|
+
const ending = [];
|
|
15657
|
+
let heldAt = Infinity;
|
|
15658
|
+
for (const [end, { page, record, done, heldAt: stopHeldAt }] of sink.shownStops) {
|
|
15659
|
+
if (closing && closing.page !== page && (!closing.context || pageContext(page) !== closing.context)) continue;
|
|
15660
|
+
if (!end.signal.aborted) {
|
|
15661
|
+
record.duration = Date.now() - record.startedAt;
|
|
15662
|
+
end.abort(why);
|
|
15663
|
+
if (why === "ended" && stopHeldAt !== null) heldAt = Math.min(heldAt, stopHeldAt);
|
|
15664
|
+
}
|
|
15665
|
+
ending.push(done);
|
|
15666
|
+
}
|
|
15667
|
+
if (sink.testInfo && heldAt !== Infinity) restoreTestTime(sink.testInfo, heldAt);
|
|
15668
|
+
await settledWithin(ending, END_STOP_MS);
|
|
15669
|
+
}
|
|
15670
|
+
function releaseCall(sink, settled) {
|
|
15671
|
+
sink.releasedQuietSince = Date.now();
|
|
15672
|
+
sink.released.push(settled.then(() => void (sink.releasedQuietSince = Date.now())));
|
|
15673
|
+
}
|
|
15674
|
+
function releasedRequestsEnded(sink) {
|
|
15675
|
+
if (sink.releasedRequests.size === 0) return Promise.resolve();
|
|
15676
|
+
return new Promise((resolve11) => sink.onReleasedRequestsEnded = resolve11);
|
|
15677
|
+
}
|
|
15678
|
+
async function awaitReleasedCalls(sink) {
|
|
15679
|
+
if (sink.released.length === 0) return;
|
|
15680
|
+
const deadline = Date.now() + RELEASED_CALL_MS;
|
|
15681
|
+
await settledWithin(sink.released, RELEASED_CALL_MS);
|
|
15682
|
+
for (let now = Date.now(); now < deadline; now = Date.now()) {
|
|
15683
|
+
if (sink.releasedRequests.size > 0) {
|
|
15684
|
+
await settledWithin([releasedRequestsEnded(sink)], deadline - now);
|
|
15685
|
+
continue;
|
|
15686
|
+
}
|
|
15687
|
+
const quiet = now - sink.releasedQuietSince;
|
|
15688
|
+
if (quiet >= RELEASED_QUIET_MS) return;
|
|
15689
|
+
await new Promise((resolve11) => setTimeout(resolve11, Math.min(RELEASED_QUIET_MS - quiet, deadline - now)));
|
|
15690
|
+
}
|
|
15691
|
+
}
|
|
15692
|
+
async function settledWithin(pending, ms) {
|
|
15693
|
+
if (pending.length === 0) return;
|
|
15694
|
+
let timer;
|
|
15695
|
+
await Promise.race([Promise.allSettled(pending), new Promise((resolve11) => timer = setTimeout(resolve11, ms))]);
|
|
15696
|
+
clearTimeout(timer);
|
|
15697
|
+
}
|
|
15698
|
+
function endStopsWithTestFunction(testInfo, sink) {
|
|
15699
|
+
const callbacks = testInfo._onDidFinishTestFunctionCallbacks;
|
|
15700
|
+
if (!(callbacks instanceof Set)) return;
|
|
15701
|
+
const theirs = [...callbacks];
|
|
15702
|
+
callbacks.clear();
|
|
15703
|
+
callbacks.add(() => endShownStops(sink, "ended"));
|
|
15704
|
+
for (const callback of theirs) callbacks.add(callback);
|
|
15705
|
+
}
|
|
15706
|
+
function recorderAt(page, testInfo, location, reason) {
|
|
15707
|
+
if (!canRecordFromHere()) return void 0;
|
|
15708
|
+
return async (ended) => {
|
|
15709
|
+
console.log(`[piwi] Recording from ${callSiteLine(location)}: the editor writes the steps.`);
|
|
15710
|
+
const why = await recordFromHere({ page, testInfo, location, reason, ended });
|
|
15711
|
+
if (ended?.aborted) console.log("[piwi] The recording ended with the test.");
|
|
15712
|
+
else
|
|
15713
|
+
console.log(
|
|
15714
|
+
why ? `[piwi] Nothing was recorded: ${why}` : "[piwi] The recording ended: the test is paused again."
|
|
15715
|
+
);
|
|
15716
|
+
};
|
|
15717
|
+
}
|
|
15718
|
+
async function pauseInPlace(page, playwrightPause, location) {
|
|
15719
|
+
const sink = currentSink;
|
|
15720
|
+
const testInfo = sink?.testInfo;
|
|
15721
|
+
const context = pageContext(page);
|
|
15722
|
+
if (!sink || !testInfo || !location || !context || !contextDebugger(context)) {
|
|
15723
|
+
return leaveToPlaywright(context, playwrightPause);
|
|
15724
|
+
}
|
|
15725
|
+
if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo)))) {
|
|
15726
|
+
return leaveToPlaywright(context, playwrightPause);
|
|
15727
|
+
}
|
|
15728
|
+
sink.pause ??= { set: NO_BREAKPOINTS, step: false, finished: false };
|
|
15729
|
+
watchStops(context, onDebuggerStop);
|
|
15730
|
+
await steerDebugger(context, "pause");
|
|
15731
|
+
PAGE_PAUSES.set(context, location);
|
|
15732
|
+
try {
|
|
15733
|
+
await playwrightPause({ __testHookKeepTestTimeout: true });
|
|
15734
|
+
} finally {
|
|
15735
|
+
PAGE_PAUSES.delete(context);
|
|
15736
|
+
}
|
|
15737
|
+
}
|
|
15738
|
+
async function leaveToPlaywright(context, playwrightPause) {
|
|
15739
|
+
if (context) PLAYWRIGHT_PAUSES.set(context, (PLAYWRIGHT_PAUSES.get(context) ?? 0) + 1);
|
|
15740
|
+
try {
|
|
15741
|
+
await withInspector(() => playwrightPause());
|
|
15742
|
+
} finally {
|
|
15743
|
+
if (context) {
|
|
15744
|
+
const left = (PLAYWRIGHT_PAUSES.get(context) ?? 1) - 1;
|
|
15745
|
+
if (left > 0) PLAYWRIGHT_PAUSES.set(context, left);
|
|
15746
|
+
else PLAYWRIGHT_PAUSES.delete(context);
|
|
15747
|
+
}
|
|
15748
|
+
}
|
|
15749
|
+
}
|
|
15750
|
+
var PLAYWRIGHT_PAUSE = /* @__PURE__ */ Symbol.for("@piwitests/reporter:playwright-pause");
|
|
15751
|
+
function playwrightPauseOf(page) {
|
|
15752
|
+
const current = page.pause;
|
|
15753
|
+
if (typeof current !== "function") return null;
|
|
15754
|
+
const own = current[PLAYWRIGHT_PAUSE] ?? current;
|
|
15755
|
+
const proto = Object.getPrototypeOf(page);
|
|
15756
|
+
const theirs = proto?.pause;
|
|
15757
|
+
if (proto && typeof theirs === "function") {
|
|
15758
|
+
if (!theirs[PLAYWRIGHT_PAUSE]) {
|
|
15759
|
+
const wrapped = function(options) {
|
|
15760
|
+
return leaveToPlaywright(pageContext(this), () => theirs.call(this, options));
|
|
15761
|
+
};
|
|
15762
|
+
wrapped[PLAYWRIGHT_PAUSE] = theirs;
|
|
15763
|
+
proto.pause = wrapped;
|
|
15764
|
+
}
|
|
15765
|
+
markPausesWrapped();
|
|
15045
15766
|
}
|
|
15767
|
+
return (options) => own.call(page, options);
|
|
15046
15768
|
}
|
|
15047
15769
|
function wrapLocator(page, locator, originMethod, originArgs) {
|
|
15048
15770
|
return new Proxy(locator, {
|
|
@@ -15065,36 +15787,35 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
15065
15787
|
const isNot = Boolean(callArgs[1]?.isNot);
|
|
15066
15788
|
const callerLocation = sink ? captureCallerLocation() : null;
|
|
15067
15789
|
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
15068
|
-
|
|
15069
|
-
|
|
15790
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, { method: originMethod, args: originArgs });
|
|
15791
|
+
try {
|
|
15792
|
+
if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
|
|
15793
|
+
return await callMethod(target, prop, callArgs);
|
|
15794
|
+
}
|
|
15795
|
+
sink.lastActivePage = page;
|
|
15796
|
+
const used = {
|
|
15070
15797
|
method: originMethod,
|
|
15071
|
-
args: originArgs
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
|
|
15090
|
-
|
|
15091
|
-
if (matches === true && !alreadyProbed) {
|
|
15092
|
-
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
15093
|
-
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
15094
|
-
} else if (matches === false) {
|
|
15095
|
-
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
15798
|
+
args: originArgs,
|
|
15799
|
+
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
15800
|
+
};
|
|
15801
|
+
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
15802
|
+
let seq = -1;
|
|
15803
|
+
if (!alreadyProbed) {
|
|
15804
|
+
seq = sink.capturedLocators.length;
|
|
15805
|
+
sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
|
|
15806
|
+
}
|
|
15807
|
+
const result = await callMethod(target, prop, callArgs);
|
|
15808
|
+
const matches = result?.matches;
|
|
15809
|
+
if (matches === true && !alreadyProbed && !call?.picked) {
|
|
15810
|
+
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
15811
|
+
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
15812
|
+
} else if (matches === false) {
|
|
15813
|
+
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
15814
|
+
}
|
|
15815
|
+
return result;
|
|
15816
|
+
} finally {
|
|
15817
|
+
forgetLocatorCall(sink, call);
|
|
15096
15818
|
}
|
|
15097
|
-
return result;
|
|
15098
15819
|
};
|
|
15099
15820
|
}
|
|
15100
15821
|
if (!ACTION_METHOD_SET.has(prop)) return original;
|
|
@@ -15109,9 +15830,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
15109
15830
|
args: originArgs,
|
|
15110
15831
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
15111
15832
|
};
|
|
15112
|
-
|
|
15113
|
-
await pauseBefore(sink, page, target, callerLocation, String(prop), used);
|
|
15114
|
-
}
|
|
15833
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, used);
|
|
15115
15834
|
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
15116
15835
|
let seq = -1;
|
|
15117
15836
|
if (!alreadyProbed) {
|
|
@@ -15129,8 +15848,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
15129
15848
|
} catch (error) {
|
|
15130
15849
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
15131
15850
|
throw error;
|
|
15851
|
+
} finally {
|
|
15852
|
+
forgetLocatorCall(sink, call);
|
|
15132
15853
|
}
|
|
15133
|
-
if (!alreadyProbed) {
|
|
15854
|
+
if (!alreadyProbed && !call?.picked) {
|
|
15134
15855
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
15135
15856
|
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
15136
15857
|
}
|
|
@@ -15183,12 +15904,17 @@ function instrumentPage(page) {
|
|
|
15183
15904
|
}
|
|
15184
15905
|
const ctx = pageContext(page);
|
|
15185
15906
|
if (ctx) instrumentContext(ctx);
|
|
15907
|
+
const playwrightPause = playwrightPauseOf(page);
|
|
15908
|
+
if (playwrightPause) {
|
|
15909
|
+
page.pause = () => pauseInPlace(page, playwrightPause, captureCallerLocation());
|
|
15910
|
+
}
|
|
15186
15911
|
if (typeof page.close === "function") {
|
|
15187
15912
|
const originalClose = page.close.bind(page);
|
|
15188
15913
|
page.close = async (...args) => {
|
|
15189
15914
|
await drainPendingProbes(1e3);
|
|
15190
15915
|
const sink = currentSink;
|
|
15191
15916
|
if (sink) {
|
|
15917
|
+
await endShownStops(sink, "closed", { page });
|
|
15192
15918
|
await stashPageState(sink, { page });
|
|
15193
15919
|
await maybeOpenPicker(sink, { page });
|
|
15194
15920
|
await stopSinkCodeReach(sink, { page });
|
|
@@ -15277,8 +16003,18 @@ function instrumentPage(page) {
|
|
|
15277
16003
|
} catch {
|
|
15278
16004
|
}
|
|
15279
16005
|
});
|
|
16006
|
+
page.on("request", (request3) => {
|
|
16007
|
+
const sink = currentSink;
|
|
16008
|
+
if (!sink || sink.released.length === 0) return;
|
|
16009
|
+
sink.releasedRequests.add(request3);
|
|
16010
|
+
sink.releasedQuietSince = Date.now();
|
|
16011
|
+
});
|
|
15280
16012
|
const onRequestEnd = (request3, failed) => {
|
|
15281
16013
|
const sink = currentSink;
|
|
16014
|
+
if (sink?.releasedRequests.delete(request3)) {
|
|
16015
|
+
sink.releasedQuietSince = Date.now();
|
|
16016
|
+
if (sink.releasedRequests.size === 0) sink.onReleasedRequestsEnded?.();
|
|
16017
|
+
}
|
|
15282
16018
|
if (!sink || RECORDED_OPEN.has(request3)) return;
|
|
15283
16019
|
sink.lastActivePage = page;
|
|
15284
16020
|
const response = sink.openResponses.get(request3) ?? null;
|
|
@@ -15384,6 +16120,10 @@ async function awaitFlakeConditions() {
|
|
|
15384
16120
|
function instrumentContext(context) {
|
|
15385
16121
|
if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
|
|
15386
16122
|
INSTRUMENTED_CONTEXTS.add(context);
|
|
16123
|
+
if (typeof context.once === "function") {
|
|
16124
|
+
OPEN_CONTEXTS.add(context);
|
|
16125
|
+
context.once("close", () => OPEN_CONTEXTS.delete(context));
|
|
16126
|
+
}
|
|
15387
16127
|
observeOwnApiRequests(context);
|
|
15388
16128
|
if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
|
|
15389
16129
|
void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
|
|
@@ -15403,6 +16143,7 @@ function instrumentContext(context) {
|
|
|
15403
16143
|
await drainPendingProbes(1e3);
|
|
15404
16144
|
const sink = currentSink;
|
|
15405
16145
|
if (sink) {
|
|
16146
|
+
await endShownStops(sink, "closed", { context });
|
|
15406
16147
|
await stashPageState(sink, { context });
|
|
15407
16148
|
await maybeOpenPicker(sink, { context });
|
|
15408
16149
|
await stopSinkCodeReach(sink, { context });
|
|
@@ -15419,6 +16160,7 @@ function patchBrowser(browser) {
|
|
|
15419
16160
|
browser.newPage = async (...args) => {
|
|
15420
16161
|
const page = await originalNewPage(...args);
|
|
15421
16162
|
instrumentPage(page);
|
|
16163
|
+
await armBreakpoints([pageContext(page)]);
|
|
15422
16164
|
await awaitFlakeConditions();
|
|
15423
16165
|
await maybeStartCodeReach(page);
|
|
15424
16166
|
return page;
|
|
@@ -15427,6 +16169,7 @@ function patchBrowser(browser) {
|
|
|
15427
16169
|
browser.newContext = async (...args) => {
|
|
15428
16170
|
const context = await originalNewContext(...args);
|
|
15429
16171
|
instrumentContext(context);
|
|
16172
|
+
await armBreakpoints([context]);
|
|
15430
16173
|
return context;
|
|
15431
16174
|
};
|
|
15432
16175
|
if (typeof browser.close === "function") {
|
|
@@ -15478,14 +16221,20 @@ async function flushSink(sink, testInfo) {
|
|
|
15478
16221
|
let drainDeadline;
|
|
15479
16222
|
await Promise.race([
|
|
15480
16223
|
Promise.allSettled(sink.capturePromises),
|
|
15481
|
-
new Promise((
|
|
15482
|
-
drainDeadline = setTimeout(
|
|
16224
|
+
new Promise((resolve11) => {
|
|
16225
|
+
drainDeadline = setTimeout(resolve11, 2e3);
|
|
15483
16226
|
})
|
|
15484
16227
|
]);
|
|
15485
16228
|
clearTimeout(drainDeadline);
|
|
15486
16229
|
const page = sink.lastActivePage;
|
|
15487
16230
|
const pageReadable = page !== null && !isPageClosed(page);
|
|
15488
16231
|
if (pageReadable) await maybeOpenPicker(sink);
|
|
16232
|
+
if (sink.stops.length > 0) {
|
|
16233
|
+
await testInfo.attach(ATTACHMENT_NAMES.pauses, {
|
|
16234
|
+
contentType: "application/json",
|
|
16235
|
+
body: Buffer.from(JSON.stringify(sink.stops))
|
|
16236
|
+
});
|
|
16237
|
+
}
|
|
15489
16238
|
if (sink.capturedLocators.length > 0) {
|
|
15490
16239
|
await testInfo.attach(ATTACHMENT_NAMES.locators, {
|
|
15491
16240
|
contentType: "application/json",
|
|
@@ -15686,6 +16435,7 @@ var piwiFixtures = {
|
|
|
15686
16435
|
// never double-wraps a page the browser patch already instrumented.
|
|
15687
16436
|
page: async ({ page }, use) => {
|
|
15688
16437
|
instrumentPage(page);
|
|
16438
|
+
await armBreakpoints([pageContext(page)]);
|
|
15689
16439
|
await awaitFlakeConditions();
|
|
15690
16440
|
await maybeStartCodeReach(page);
|
|
15691
16441
|
await use(page);
|
|
@@ -15706,8 +16456,9 @@ var piwiFixtures = {
|
|
|
15706
16456
|
if (pauseSet.size) {
|
|
15707
16457
|
const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
|
|
15708
16458
|
if (reason) logPauseSkipOnce(reason);
|
|
15709
|
-
else sink.pause = { set: pauseSet,
|
|
16459
|
+
else sink.pause = { set: pauseSet, step: false, finished: false };
|
|
15710
16460
|
}
|
|
16461
|
+
if (sink.pause || canRecordFromHere()) endStopsWithTestFunction(testInfo, sink);
|
|
15711
16462
|
if (isFlakeMode()) {
|
|
15712
16463
|
const plan = loadFlakePlan();
|
|
15713
16464
|
const role = flakeRoleForTest(plan, {
|
|
@@ -15732,9 +16483,12 @@ var piwiFixtures = {
|
|
|
15732
16483
|
activeResourceLedger()?.testStarted(testInfo);
|
|
15733
16484
|
currentSink = sink;
|
|
15734
16485
|
try {
|
|
16486
|
+
await armBreakpoints(OPEN_CONTEXTS);
|
|
15735
16487
|
await startCodeReachOnOpenPages();
|
|
15736
16488
|
await use();
|
|
15737
16489
|
} finally {
|
|
16490
|
+
await endShownStops(sink, "ended");
|
|
16491
|
+
await awaitReleasedCalls(sink);
|
|
15738
16492
|
currentSink = null;
|
|
15739
16493
|
if (sink.probeItem) {
|
|
15740
16494
|
const applied = sink.probeInterception?.applied() ?? false;
|
|
@@ -15792,13 +16546,13 @@ function extendPiwiFixtures(test) {
|
|
|
15792
16546
|
}
|
|
15793
16547
|
|
|
15794
16548
|
// src/internal/ai/ai-fixtures.ts
|
|
15795
|
-
var
|
|
15796
|
-
var
|
|
16549
|
+
var fs28 = __toESM(require("fs"));
|
|
16550
|
+
var path33 = __toESM(require("path"));
|
|
15797
16551
|
var import_test = require("@playwright/test");
|
|
15798
16552
|
|
|
15799
16553
|
// src/internal/ai/artifact.ts
|
|
15800
|
-
var
|
|
15801
|
-
var
|
|
16554
|
+
var fs27 = __toESM(require("fs"));
|
|
16555
|
+
var path31 = __toESM(require("path"));
|
|
15802
16556
|
var ARTIFACT_VERSION = 1;
|
|
15803
16557
|
var LOCATOR_METHOD_SET2 = new Set(LOCATOR_METHODS);
|
|
15804
16558
|
var ACTION_METHOD_SET2 = new Set(ACTION_METHODS);
|
|
@@ -15891,7 +16645,7 @@ function parseEntry(text) {
|
|
|
15891
16645
|
function readEntry(file) {
|
|
15892
16646
|
let text;
|
|
15893
16647
|
try {
|
|
15894
|
-
text =
|
|
16648
|
+
text = fs27.readFileSync(file, "utf8");
|
|
15895
16649
|
} catch (error) {
|
|
15896
16650
|
if (error.code === "ENOENT") return null;
|
|
15897
16651
|
throw error;
|
|
@@ -15901,28 +16655,28 @@ function readEntry(file) {
|
|
|
15901
16655
|
function writeEntry(file, entry) {
|
|
15902
16656
|
const canonical = serializeEntry(entry);
|
|
15903
16657
|
return withEntryLock(file, () => {
|
|
15904
|
-
if (
|
|
15905
|
-
|
|
16658
|
+
if (fs27.existsSync(file) && fs27.readFileSync(file, "utf8") === canonical) return { written: false };
|
|
16659
|
+
fs27.mkdirSync(path31.dirname(file), { recursive: true });
|
|
15906
16660
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
15907
|
-
|
|
15908
|
-
|
|
16661
|
+
fs27.writeFileSync(tmp, canonical);
|
|
16662
|
+
fs27.renameSync(tmp, file);
|
|
15909
16663
|
return { written: true };
|
|
15910
16664
|
});
|
|
15911
16665
|
}
|
|
15912
16666
|
function withEntryLock(file, fn) {
|
|
15913
16667
|
const lock = `${file}.lock`;
|
|
15914
|
-
|
|
16668
|
+
fs27.mkdirSync(path31.dirname(file), { recursive: true });
|
|
15915
16669
|
const deadline = Date.now() + 5e3;
|
|
15916
16670
|
for (; ; ) {
|
|
15917
16671
|
try {
|
|
15918
|
-
const fd =
|
|
15919
|
-
|
|
16672
|
+
const fd = fs27.openSync(lock, "wx");
|
|
16673
|
+
fs27.closeSync(fd);
|
|
15920
16674
|
break;
|
|
15921
16675
|
} catch (error) {
|
|
15922
16676
|
if (error.code !== "EEXIST") throw error;
|
|
15923
16677
|
if (Date.now() > deadline) {
|
|
15924
16678
|
try {
|
|
15925
|
-
|
|
16679
|
+
fs27.unlinkSync(lock);
|
|
15926
16680
|
} catch {
|
|
15927
16681
|
}
|
|
15928
16682
|
}
|
|
@@ -15932,7 +16686,7 @@ function withEntryLock(file, fn) {
|
|
|
15932
16686
|
return fn();
|
|
15933
16687
|
} finally {
|
|
15934
16688
|
try {
|
|
15935
|
-
|
|
16689
|
+
fs27.unlinkSync(lock);
|
|
15936
16690
|
} catch {
|
|
15937
16691
|
}
|
|
15938
16692
|
}
|
|
@@ -15940,7 +16694,7 @@ function withEntryLock(file, fn) {
|
|
|
15940
16694
|
|
|
15941
16695
|
// src/internal/ai/keys.ts
|
|
15942
16696
|
var crypto7 = __toESM(require("crypto"));
|
|
15943
|
-
var
|
|
16697
|
+
var path32 = __toESM(require("path"));
|
|
15944
16698
|
var DEFAULT_AI_DIR = "__piwi__";
|
|
15945
16699
|
function normalizeTemplate(template) {
|
|
15946
16700
|
return template.trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -15954,13 +16708,13 @@ function slug(text, maxLength = 40) {
|
|
|
15954
16708
|
return (base || "x").slice(0, maxLength).replace(/-+$/g, "") || "x";
|
|
15955
16709
|
}
|
|
15956
16710
|
function entryDir(specFile, dir = DEFAULT_AI_DIR) {
|
|
15957
|
-
return
|
|
16711
|
+
return path32.join(path32.dirname(specFile), dir, path32.basename(specFile));
|
|
15958
16712
|
}
|
|
15959
16713
|
function entryPath(params) {
|
|
15960
16714
|
const basis = `${normalizeTemplate(params.testTitle)}::${normalizeTemplate(params.template)}`;
|
|
15961
16715
|
const hash = hashTemplate(basis, params.ordinal ?? 0);
|
|
15962
16716
|
const name = `${slug(params.testTitle)}.${slug(params.template)}.${hash}.json`;
|
|
15963
|
-
return
|
|
16717
|
+
return path32.join(entryDir(params.specFile, params.dir), name);
|
|
15964
16718
|
}
|
|
15965
16719
|
function parseLocation2(location) {
|
|
15966
16720
|
const match = /:(\d+):(\d+)$/.exec(location);
|
|
@@ -16563,7 +17317,7 @@ function testIdentity(testInfo) {
|
|
|
16563
17317
|
return titles.length > 0 ? titles.join(" \u203A ") : testInfo.title;
|
|
16564
17318
|
}
|
|
16565
17319
|
function relativeToCwd(file) {
|
|
16566
|
-
return
|
|
17320
|
+
return path33.relative(process.cwd(), file).split(path33.sep).join("/");
|
|
16567
17321
|
}
|
|
16568
17322
|
function recordIntents(intents, entry) {
|
|
16569
17323
|
const add = (locator, kind) => {
|
|
@@ -16590,7 +17344,7 @@ function createAiApi(page, testInfo, config, used, intents) {
|
|
|
16590
17344
|
const readSource = () => {
|
|
16591
17345
|
if (source === null) {
|
|
16592
17346
|
try {
|
|
16593
|
-
source =
|
|
17347
|
+
source = fs28.readFileSync(testInfo.file, "utf8");
|
|
16594
17348
|
} catch {
|
|
16595
17349
|
source = "";
|
|
16596
17350
|
}
|