@piwitests/reporter 0.59.0 → 0.61.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 +1520 -1307
- package/dist/global-setup-module.js +203 -63
- package/dist/index.d.ts +36 -2
- package/dist/index.js +1973 -584
- package/dist/internal/capture/attachments.d.ts +3 -0
- package/dist/internal/capture/attachments.js +4 -1
- package/dist/internal/capture/capture-fixtures.js +1363 -373
- 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",
|
|
@@ -128,6 +203,7 @@ var PIWI_ENV_KEYS = {
|
|
|
128
203
|
codeReachRoots: "PIWI_CODE_REACH_ROOTS",
|
|
129
204
|
coverageBrowser: "PIWI_COVERAGE_BROWSER",
|
|
130
205
|
coverageServer: "PIWI_COVERAGE_SERVER",
|
|
206
|
+
coverageCss: "PIWI_COVERAGE_CSS",
|
|
131
207
|
externalIdPattern: "PIWI_EXTERNAL_ID_PATTERN",
|
|
132
208
|
externalIdOnDuplicate: "PIWI_EXTERNAL_ID_ON_DUPLICATE",
|
|
133
209
|
captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
|
|
@@ -215,11 +291,27 @@ var ENV_FALLBACK_SPECS = [
|
|
|
215
291
|
{ option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
|
|
216
292
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
217
293
|
];
|
|
294
|
+
var DOTENV_OPTIONS = ["serverUrl", "apiKey", "projectName"];
|
|
295
|
+
var DESKTOP_APP_MODES = ["prefer", "fallback", "off"];
|
|
296
|
+
function desktopAppMode(option, envValue) {
|
|
297
|
+
const fromEnv = envValue?.trim().toLowerCase();
|
|
298
|
+
if (DESKTOP_APP_MODES.includes(fromEnv)) return fromEnv;
|
|
299
|
+
if (DESKTOP_APP_MODES.includes(option)) return option;
|
|
300
|
+
return "fallback";
|
|
301
|
+
}
|
|
218
302
|
var desktopDiscovered = false;
|
|
303
|
+
var desktopReplacedServerUrl = null;
|
|
304
|
+
var dotEnvUse = null;
|
|
219
305
|
function usedDesktopDiscovery() {
|
|
220
306
|
return desktopDiscovered;
|
|
221
307
|
}
|
|
222
|
-
function
|
|
308
|
+
function desktopReplacedServer() {
|
|
309
|
+
return desktopReplacedServerUrl;
|
|
310
|
+
}
|
|
311
|
+
function usedDotEnv() {
|
|
312
|
+
return dotEnvUse;
|
|
313
|
+
}
|
|
314
|
+
function resolveOptions(raw, configDir) {
|
|
223
315
|
const env = process.env;
|
|
224
316
|
const mergedRaw = { ...raw };
|
|
225
317
|
for (const spec of ENV_FALLBACK_SPECS) {
|
|
@@ -231,24 +323,49 @@ function resolveOptions(raw) {
|
|
|
231
323
|
mergedRaw[spec.option] = spec.kind === "number" ? Number(value) : value;
|
|
232
324
|
}
|
|
233
325
|
}
|
|
326
|
+
dotEnvUse = null;
|
|
327
|
+
if (configDir) {
|
|
328
|
+
const file = path2.join(configDir, ".env");
|
|
329
|
+
const dotEnv = readDotEnv(file);
|
|
330
|
+
const keys = [];
|
|
331
|
+
for (const option of DOTENV_OPTIONS) {
|
|
332
|
+
const value = dotEnv[PIWI_ENV_KEYS[option]];
|
|
333
|
+
if (mergedRaw[option] !== void 0 || !value) continue;
|
|
334
|
+
mergedRaw[option] = value;
|
|
335
|
+
keys.push(PIWI_ENV_KEYS[option]);
|
|
336
|
+
}
|
|
337
|
+
if (keys.length > 0) dotEnvUse = { file, keys };
|
|
338
|
+
}
|
|
234
339
|
desktopDiscovered = false;
|
|
340
|
+
desktopReplacedServerUrl = null;
|
|
341
|
+
const mode = desktopAppMode(mergedRaw.desktopApp, env[PIWI_ENV_KEYS.desktopApp]);
|
|
342
|
+
mergedRaw.desktopApp = mode;
|
|
235
343
|
const serverUrlUnset = mergedRaw.serverUrl === void 0;
|
|
236
344
|
const apiKeyUnset = mergedRaw.apiKey === void 0 || mergedRaw.apiKey === null;
|
|
237
|
-
|
|
345
|
+
const nothingConfigured = serverUrlUnset && apiKeyUnset;
|
|
346
|
+
const preferLocally = mode === "prefer" && !detectCi(env);
|
|
347
|
+
if (mode !== "off" && (nothingConfigured || preferLocally)) {
|
|
238
348
|
const desktop = readDesktopConfig(env[PIWI_DESKTOP_CONFIG_ENV] || defaultDesktopConfigPath());
|
|
239
349
|
if (desktop) {
|
|
350
|
+
if (!serverUrlUnset && mergedRaw.serverUrl !== desktop.url) desktopReplacedServerUrl = mergedRaw.serverUrl;
|
|
240
351
|
mergedRaw.serverUrl = desktop.url;
|
|
241
352
|
mergedRaw.apiKey = desktop.token;
|
|
353
|
+
mergedRaw.username = null;
|
|
354
|
+
mergedRaw.password = null;
|
|
242
355
|
desktopDiscovered = true;
|
|
243
356
|
}
|
|
244
357
|
}
|
|
245
358
|
const coverage = { ...mergedRaw.coverage };
|
|
246
359
|
const coverageBrowser = env[PIWI_ENV_KEYS.coverageBrowser];
|
|
247
360
|
const coverageServer = env[PIWI_ENV_KEYS.coverageServer];
|
|
361
|
+
const coverageCss = env[PIWI_ENV_KEYS.coverageCss];
|
|
248
362
|
if (coverage.browser === void 0 && coverageBrowser)
|
|
249
363
|
coverage.browser = coverageBrowser;
|
|
250
364
|
if (coverage.server === void 0 && coverageServer) coverage.server = coverageServer;
|
|
251
|
-
if (coverage.
|
|
365
|
+
if (coverage.css === void 0 && coverageCss) coverage.css = coverageCss;
|
|
366
|
+
if (coverage.browser !== void 0 || coverage.server !== void 0 || coverage.css !== void 0) {
|
|
367
|
+
mergedRaw.coverage = coverage;
|
|
368
|
+
}
|
|
252
369
|
const externalId = { ...mergedRaw.externalId };
|
|
253
370
|
const externalIdPattern = env[PIWI_ENV_KEYS.externalIdPattern];
|
|
254
371
|
const externalIdOnDuplicate = env[PIWI_ENV_KEYS.externalIdOnDuplicate];
|
|
@@ -263,6 +380,8 @@ function resolveOptions(raw) {
|
|
|
263
380
|
function applyOptionsToEnv(options) {
|
|
264
381
|
const env = process.env;
|
|
265
382
|
if (options.serverUrl !== void 0) env[PIWI_ENV_KEYS.serverUrl] = options.serverUrl;
|
|
383
|
+
if (options.desktopApp !== void 0 && !env[PIWI_ENV_KEYS.desktopApp])
|
|
384
|
+
env[PIWI_ENV_KEYS.desktopApp] = options.desktopApp;
|
|
266
385
|
if (options.projectName !== void 0) env[PIWI_ENV_KEYS.projectName] = options.projectName;
|
|
267
386
|
if (options.verbose !== void 0) env[PIWI_ENV_KEYS.verbose] = String(options.verbose);
|
|
268
387
|
if (options.apiKey) env[PIWI_ENV_KEYS.apiKey] = options.apiKey;
|
|
@@ -292,9 +411,11 @@ function applyOptionsToEnv(options) {
|
|
|
292
411
|
if (options.collectPerformanceMetrics === false) {
|
|
293
412
|
env[PIWI_ENV_KEYS.coverageBrowser] = "off";
|
|
294
413
|
env[PIWI_ENV_KEYS.coverageServer] = "off";
|
|
414
|
+
env[PIWI_ENV_KEYS.coverageCss] = "off";
|
|
295
415
|
} else {
|
|
296
416
|
if (options.coverage?.browser !== void 0) env[PIWI_ENV_KEYS.coverageBrowser] = options.coverage.browser;
|
|
297
417
|
if (options.coverage?.server !== void 0) env[PIWI_ENV_KEYS.coverageServer] = options.coverage.server;
|
|
418
|
+
if (options.coverage?.css !== void 0) env[PIWI_ENV_KEYS.coverageCss] = options.coverage.css;
|
|
298
419
|
}
|
|
299
420
|
if (options.captureServerTraces === false || options.collectPerformanceMetrics === false)
|
|
300
421
|
env[PIWI_ENV_KEYS.captureServerTraces] = "false";
|
|
@@ -545,7 +666,7 @@ var HttpClient = class {
|
|
|
545
666
|
}
|
|
546
667
|
/** Unified request core: transport, headers, auth, response accumulation, timeout. */
|
|
547
668
|
request(method, pathname, opts) {
|
|
548
|
-
return new Promise((
|
|
669
|
+
return new Promise((resolve11, reject) => {
|
|
549
670
|
if (this.closedReason !== null) {
|
|
550
671
|
reject(new Error(this.closedReason));
|
|
551
672
|
return;
|
|
@@ -568,7 +689,7 @@ var HttpClient = class {
|
|
|
568
689
|
data += chunk;
|
|
569
690
|
});
|
|
570
691
|
res.on("end", () => {
|
|
571
|
-
|
|
692
|
+
resolve11({ status: res.statusCode ?? 0, text: data, headers: res.headers });
|
|
572
693
|
});
|
|
573
694
|
res.on("error", reject);
|
|
574
695
|
res.on("close", () => {
|
|
@@ -607,8 +728,8 @@ var HttpClient = class {
|
|
|
607
728
|
};
|
|
608
729
|
|
|
609
730
|
// src/internal/submit/uploader.ts
|
|
610
|
-
var
|
|
611
|
-
var
|
|
731
|
+
var fs3 = __toESM(require("fs"));
|
|
732
|
+
var path3 = __toESM(require("path"));
|
|
612
733
|
var import_form_data2 = __toESM(require("form-data"));
|
|
613
734
|
|
|
614
735
|
// src/internal/submit/serializer.ts
|
|
@@ -858,8 +979,8 @@ var Uploader = class {
|
|
|
858
979
|
if (traceInfo) {
|
|
859
980
|
form.append("trace_hash", traceInfo.hash);
|
|
860
981
|
if (includeTraceFile2) {
|
|
861
|
-
form.append("trace",
|
|
862
|
-
filename:
|
|
982
|
+
form.append("trace", fs3.createReadStream(traceInfo.tracePath), {
|
|
983
|
+
filename: path3.basename(traceInfo.tracePath)
|
|
863
984
|
});
|
|
864
985
|
}
|
|
865
986
|
}
|
|
@@ -871,7 +992,7 @@ var Uploader = class {
|
|
|
871
992
|
)
|
|
872
993
|
);
|
|
873
994
|
for (const a of attachments) {
|
|
874
|
-
form.append("attach_file",
|
|
995
|
+
form.append("attach_file", fs3.createReadStream(a.path), { filename: a.originalName });
|
|
875
996
|
}
|
|
876
997
|
}
|
|
877
998
|
return form;
|
|
@@ -924,7 +1045,7 @@ var Uploader = class {
|
|
|
924
1045
|
)
|
|
925
1046
|
);
|
|
926
1047
|
for (const a of attachments) {
|
|
927
|
-
form.append(`attach_file_${i}`,
|
|
1048
|
+
form.append(`attach_file_${i}`, fs3.createReadStream(a.path), { filename: a.originalName });
|
|
928
1049
|
attachmentCount++;
|
|
929
1050
|
}
|
|
930
1051
|
}
|
|
@@ -933,8 +1054,8 @@ var Uploader = class {
|
|
|
933
1054
|
let traceCount = 0;
|
|
934
1055
|
for (const [i, tc] of testCases.entries()) {
|
|
935
1056
|
for (const tp of this.fileHandler.findTraceFiles(tc)) {
|
|
936
|
-
if (
|
|
937
|
-
form.append(`trace_${i}`,
|
|
1057
|
+
if (fs3.existsSync(tp)) {
|
|
1058
|
+
form.append(`trace_${i}`, fs3.createReadStream(tp), { filename: path3.basename(tp) });
|
|
938
1059
|
traceCount++;
|
|
939
1060
|
}
|
|
940
1061
|
}
|
|
@@ -944,9 +1065,9 @@ var Uploader = class {
|
|
|
944
1065
|
};
|
|
945
1066
|
|
|
946
1067
|
// src/internal/streaming/stream-buffer.ts
|
|
947
|
-
var
|
|
1068
|
+
var path4 = __toESM(require("path"));
|
|
948
1069
|
var os3 = __toESM(require("os"));
|
|
949
|
-
var
|
|
1070
|
+
var fs4 = __toESM(require("fs"));
|
|
950
1071
|
|
|
951
1072
|
// src/internal/support/instance-id.ts
|
|
952
1073
|
var crypto = __toESM(require("crypto"));
|
|
@@ -967,14 +1088,14 @@ var StreamBuffer = class {
|
|
|
967
1088
|
}
|
|
968
1089
|
/** Scope the buffer to a run. Until then the buffer is empty and appends are dropped. */
|
|
969
1090
|
bindRun(runId) {
|
|
970
|
-
this.filePath =
|
|
1091
|
+
this.filePath = path4.join(os3.tmpdir(), `${this.prefix}-${runId}.jsonl`);
|
|
971
1092
|
}
|
|
972
1093
|
/** Append one or more events to the on-disk buffer */
|
|
973
1094
|
append(events) {
|
|
974
1095
|
if (!this.filePath || events.length === 0) return;
|
|
975
1096
|
try {
|
|
976
1097
|
const lines = events.map((e) => JSON.stringify(e) + "\n").join("");
|
|
977
|
-
|
|
1098
|
+
fs4.appendFileSync(this.filePath, lines, "utf8");
|
|
978
1099
|
} catch {
|
|
979
1100
|
}
|
|
980
1101
|
}
|
|
@@ -982,8 +1103,8 @@ var StreamBuffer = class {
|
|
|
982
1103
|
load() {
|
|
983
1104
|
if (!this.filePath) return [];
|
|
984
1105
|
try {
|
|
985
|
-
if (
|
|
986
|
-
const content =
|
|
1106
|
+
if (fs4.existsSync(this.filePath)) {
|
|
1107
|
+
const content = fs4.readFileSync(this.filePath, "utf8");
|
|
987
1108
|
return content.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
988
1109
|
}
|
|
989
1110
|
} catch {
|
|
@@ -994,7 +1115,7 @@ var StreamBuffer = class {
|
|
|
994
1115
|
clear() {
|
|
995
1116
|
if (!this.filePath) return;
|
|
996
1117
|
try {
|
|
997
|
-
if (
|
|
1118
|
+
if (fs4.existsSync(this.filePath)) fs4.unlinkSync(this.filePath);
|
|
998
1119
|
} catch {
|
|
999
1120
|
}
|
|
1000
1121
|
}
|
|
@@ -1003,14 +1124,14 @@ var StreamBuffer = class {
|
|
|
1003
1124
|
const dir = os3.tmpdir();
|
|
1004
1125
|
let names;
|
|
1005
1126
|
try {
|
|
1006
|
-
names =
|
|
1127
|
+
names = fs4.readdirSync(dir).filter((name) => name.startsWith(this.prefix));
|
|
1007
1128
|
} catch {
|
|
1008
1129
|
return;
|
|
1009
1130
|
}
|
|
1010
1131
|
for (const name of names) {
|
|
1011
1132
|
try {
|
|
1012
|
-
const file =
|
|
1013
|
-
if (Date.now() -
|
|
1133
|
+
const file = path4.join(dir, name);
|
|
1134
|
+
if (Date.now() - fs4.statSync(file).mtimeMs > maxAgeMs) fs4.unlinkSync(file);
|
|
1014
1135
|
} catch {
|
|
1015
1136
|
}
|
|
1016
1137
|
}
|
|
@@ -1019,9 +1140,9 @@ var StreamBuffer = class {
|
|
|
1019
1140
|
|
|
1020
1141
|
// src/internal/streaming/crash-recovery.ts
|
|
1021
1142
|
var crypto2 = __toESM(require("crypto"));
|
|
1022
|
-
var
|
|
1143
|
+
var fs5 = __toESM(require("fs"));
|
|
1023
1144
|
var os4 = __toESM(require("os"));
|
|
1024
|
-
var
|
|
1145
|
+
var path5 = __toESM(require("path"));
|
|
1025
1146
|
|
|
1026
1147
|
// src/internal/support/errors.ts
|
|
1027
1148
|
function errorMessage(error) {
|
|
@@ -1036,7 +1157,7 @@ function recoveryServer(serverUrl) {
|
|
|
1036
1157
|
function recoveryFilePath(serverUrl, projectName) {
|
|
1037
1158
|
const key = crypto2.createHash("sha256").update(`${recoveryServer(serverUrl)}
|
|
1038
1159
|
${projectName}`).digest("hex").slice(0, 16);
|
|
1039
|
-
return
|
|
1160
|
+
return path5.join(os4.tmpdir(), `piwi-dashboard-recovery-${key}.json`);
|
|
1040
1161
|
}
|
|
1041
1162
|
function refusesRun(error) {
|
|
1042
1163
|
return error instanceof HttpError && [400, 409, 422].includes(error.status);
|
|
@@ -1057,11 +1178,11 @@ var CrashRecovery = class {
|
|
|
1057
1178
|
const file = { v: 1, serverUrl: this.serverUrl, payload };
|
|
1058
1179
|
const temp = `${this.filePath}.${process.pid}.${crypto2.randomBytes(6).toString("hex")}.tmp`;
|
|
1059
1180
|
try {
|
|
1060
|
-
|
|
1061
|
-
|
|
1181
|
+
fs5.writeFileSync(temp, JSON.stringify(file), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
1182
|
+
fs5.renameSync(temp, this.filePath);
|
|
1062
1183
|
this.logger.info("Saved recovery data for later upload");
|
|
1063
1184
|
} catch (error) {
|
|
1064
|
-
|
|
1185
|
+
fs5.rmSync(temp, { force: true });
|
|
1065
1186
|
this.logger.error(`Failed to save recovery data: ${errorMessage(error)}`);
|
|
1066
1187
|
}
|
|
1067
1188
|
}
|
|
@@ -1072,26 +1193,26 @@ var CrashRecovery = class {
|
|
|
1072
1193
|
load() {
|
|
1073
1194
|
let fd;
|
|
1074
1195
|
try {
|
|
1075
|
-
fd =
|
|
1196
|
+
fd = fs5.openSync(this.filePath, fs5.constants.O_RDONLY | (fs5.constants.O_NOFOLLOW ?? 0));
|
|
1076
1197
|
} catch {
|
|
1077
1198
|
return null;
|
|
1078
1199
|
}
|
|
1079
1200
|
try {
|
|
1080
|
-
const stat =
|
|
1201
|
+
const stat = fs5.fstatSync(fd);
|
|
1081
1202
|
if (!stat.isFile() || typeof process.getuid === "function" && stat.uid !== process.getuid()) return null;
|
|
1082
|
-
const file = JSON.parse(
|
|
1203
|
+
const file = JSON.parse(fs5.readFileSync(fd, "utf8"));
|
|
1083
1204
|
if (file?.v !== 1 || file.serverUrl !== this.serverUrl) return null;
|
|
1084
1205
|
return file.payload && typeof file.payload === "object" ? file.payload : null;
|
|
1085
1206
|
} catch {
|
|
1086
1207
|
return null;
|
|
1087
1208
|
} finally {
|
|
1088
|
-
|
|
1209
|
+
fs5.closeSync(fd);
|
|
1089
1210
|
}
|
|
1090
1211
|
}
|
|
1091
1212
|
/** Delete the recovery file from disk */
|
|
1092
1213
|
clear() {
|
|
1093
1214
|
try {
|
|
1094
|
-
|
|
1215
|
+
fs5.rmSync(this.filePath, { force: true });
|
|
1095
1216
|
} catch {
|
|
1096
1217
|
}
|
|
1097
1218
|
}
|
|
@@ -1123,25 +1244,25 @@ var CrashRecovery = class {
|
|
|
1123
1244
|
};
|
|
1124
1245
|
|
|
1125
1246
|
// src/internal/files/file-handler.ts
|
|
1126
|
-
var
|
|
1247
|
+
var fs7 = __toESM(require("fs"));
|
|
1127
1248
|
var os5 = __toESM(require("os"));
|
|
1128
|
-
var
|
|
1249
|
+
var path7 = __toESM(require("path"));
|
|
1129
1250
|
var crypto3 = __toESM(require("crypto"));
|
|
1130
1251
|
|
|
1131
1252
|
// src/internal/files/compression.ts
|
|
1132
|
-
var
|
|
1133
|
-
var
|
|
1253
|
+
var fs6 = __toESM(require("fs"));
|
|
1254
|
+
var path6 = __toESM(require("path"));
|
|
1134
1255
|
var zlib = __toESM(require("zlib"));
|
|
1135
1256
|
var import_node_util = require("util");
|
|
1136
1257
|
var gzipAsync = (0, import_node_util.promisify)(zlib.gzip);
|
|
1137
1258
|
async function compressDirectory(sourceDir) {
|
|
1138
1259
|
const files = [];
|
|
1139
1260
|
function collect(dir, baseDir = "") {
|
|
1140
|
-
for (const entry of
|
|
1141
|
-
const full =
|
|
1142
|
-
const rel =
|
|
1261
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
1262
|
+
const full = path6.join(dir, entry.name);
|
|
1263
|
+
const rel = path6.join(baseDir, entry.name);
|
|
1143
1264
|
if (entry.isDirectory()) collect(full, rel);
|
|
1144
|
-
else if (entry.isFile()) files.push({ path: rel, content:
|
|
1265
|
+
else if (entry.isFile()) files.push({ path: rel, content: fs6.readFileSync(full) });
|
|
1145
1266
|
}
|
|
1146
1267
|
}
|
|
1147
1268
|
collect(sourceDir);
|
|
@@ -1198,6 +1319,8 @@ var ATTACHMENT_NAMES = {
|
|
|
1198
1319
|
codeReach: "piwi-code-reach",
|
|
1199
1320
|
browserCoverage: "piwi-browser-coverage",
|
|
1200
1321
|
browserFunctions: "piwi-browser-functions",
|
|
1322
|
+
cssCoverage: "piwi-css-coverage",
|
|
1323
|
+
cssRules: "piwi-css-rules",
|
|
1201
1324
|
serverCoverageRequests: "piwi-server-coverage-requests",
|
|
1202
1325
|
webVitals: "piwi-web-vitals",
|
|
1203
1326
|
locatorSuggestion: "piwi-locator-suggestion",
|
|
@@ -1205,7 +1328,8 @@ var ATTACHMENT_NAMES = {
|
|
|
1205
1328
|
userPick: "piwi-user-pick",
|
|
1206
1329
|
aiUsage: "piwi-ai-usage",
|
|
1207
1330
|
aiMeta: "piwi-ai-meta",
|
|
1208
|
-
resources: "piwi-resources"
|
|
1331
|
+
resources: "piwi-resources",
|
|
1332
|
+
pauses: "piwi-pauses"
|
|
1209
1333
|
};
|
|
1210
1334
|
var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
|
|
1211
1335
|
var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
|
|
@@ -1239,19 +1363,19 @@ var FileHandler = class {
|
|
|
1239
1363
|
}
|
|
1240
1364
|
/** Locate a Playwright HTML report directory containing `index.html`. Optionally override the search path. */
|
|
1241
1365
|
findHTMLReportDirectory(customDir) {
|
|
1242
|
-
const possibleDirs = customDir ? [customDir,
|
|
1366
|
+
const possibleDirs = customDir ? [customDir, path7.join(process.cwd(), customDir)] : ["playwright-report", "./playwright-report", path7.join(process.cwd(), "playwright-report")];
|
|
1243
1367
|
for (const reportDir of possibleDirs) {
|
|
1244
|
-
if (
|
|
1245
|
-
if (
|
|
1368
|
+
if (fs7.existsSync(reportDir) && fs7.statSync(reportDir).isDirectory()) {
|
|
1369
|
+
if (fs7.existsSync(path7.join(reportDir, "index.html"))) return reportDir;
|
|
1246
1370
|
}
|
|
1247
1371
|
}
|
|
1248
1372
|
return null;
|
|
1249
1373
|
}
|
|
1250
1374
|
/** Check if `dir` exists and is a directory; also checks `path.join(process.cwd(), dir)` */
|
|
1251
1375
|
findReportDirectory(dir) {
|
|
1252
|
-
const candidates = [dir,
|
|
1376
|
+
const candidates = [dir, path7.join(process.cwd(), dir)];
|
|
1253
1377
|
for (const candidate of candidates) {
|
|
1254
|
-
if (
|
|
1378
|
+
if (fs7.existsSync(candidate) && fs7.statSync(candidate).isDirectory()) return candidate;
|
|
1255
1379
|
}
|
|
1256
1380
|
return null;
|
|
1257
1381
|
}
|
|
@@ -1269,7 +1393,7 @@ var FileHandler = class {
|
|
|
1269
1393
|
const set = /* @__PURE__ */ new Set();
|
|
1270
1394
|
if (testCase.attachments) {
|
|
1271
1395
|
for (const a of testCase.attachments) {
|
|
1272
|
-
if (a.name === "trace" && a.path) set.add(
|
|
1396
|
+
if (a.name === "trace" && a.path) set.add(path7.resolve(a.path));
|
|
1273
1397
|
}
|
|
1274
1398
|
}
|
|
1275
1399
|
return Array.from(set);
|
|
@@ -1297,24 +1421,24 @@ var FileHandler = class {
|
|
|
1297
1421
|
cleanupBodyAttachments() {
|
|
1298
1422
|
if (!this.bodyDir) return;
|
|
1299
1423
|
try {
|
|
1300
|
-
|
|
1424
|
+
fs7.rmSync(this.bodyDir, { recursive: true, force: true });
|
|
1301
1425
|
} catch (error) {
|
|
1302
1426
|
this.logger.debug(`Could not remove temp attachments at ${this.bodyDir}: ${errorMessage(error)}`);
|
|
1303
1427
|
}
|
|
1304
1428
|
this.bodyDir = null;
|
|
1305
1429
|
}
|
|
1306
1430
|
resolvePathAttachment(a, testCase) {
|
|
1307
|
-
if (!a.path || !
|
|
1308
|
-
const size =
|
|
1431
|
+
if (!a.path || !fs7.existsSync(a.path)) return null;
|
|
1432
|
+
const size = fs7.statSync(a.path).size;
|
|
1309
1433
|
if (size > this.maxAttachmentBytes) {
|
|
1310
1434
|
this.warnOversized(a, testCase, size);
|
|
1311
1435
|
return null;
|
|
1312
1436
|
}
|
|
1313
1437
|
return {
|
|
1314
1438
|
name: a.name || "attachment",
|
|
1315
|
-
path:
|
|
1439
|
+
path: path7.resolve(a.path),
|
|
1316
1440
|
contentType: a.contentType || "application/octet-stream",
|
|
1317
|
-
originalName:
|
|
1441
|
+
originalName: path7.basename(a.path)
|
|
1318
1442
|
};
|
|
1319
1443
|
}
|
|
1320
1444
|
resolveBodyAttachment(a, testCase) {
|
|
@@ -1329,8 +1453,8 @@ var FileHandler = class {
|
|
|
1329
1453
|
let filePath2 = this.bodyFiles.get(a);
|
|
1330
1454
|
if (!filePath2) {
|
|
1331
1455
|
try {
|
|
1332
|
-
filePath2 =
|
|
1333
|
-
|
|
1456
|
+
filePath2 = path7.join(this.bodyAttachmentDir(), `${++this.bodyFileCount}-${originalName}`);
|
|
1457
|
+
fs7.writeFileSync(filePath2, body);
|
|
1334
1458
|
this.bodyFiles.set(a, filePath2);
|
|
1335
1459
|
} catch (error) {
|
|
1336
1460
|
this.logger.warn(`Could not stage attachment "${a.name}" for upload: ${errorMessage(error)}`);
|
|
@@ -1340,7 +1464,7 @@ var FileHandler = class {
|
|
|
1340
1464
|
return { name: a.name || "attachment", path: filePath2, contentType, originalName };
|
|
1341
1465
|
}
|
|
1342
1466
|
bodyAttachmentDir() {
|
|
1343
|
-
if (!this.bodyDir) this.bodyDir =
|
|
1467
|
+
if (!this.bodyDir) this.bodyDir = fs7.mkdtempSync(path7.join(os5.tmpdir(), "piwi-dashboard-attachments-"));
|
|
1344
1468
|
return this.bodyDir;
|
|
1345
1469
|
}
|
|
1346
1470
|
warnOversized(a, testCase, size) {
|
|
@@ -1363,10 +1487,10 @@ var FileHandler = class {
|
|
|
1363
1487
|
/** 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
1488
|
parsePerformanceAttachments(testCase, attachments) {
|
|
1365
1489
|
const find = (name) => attachments.find((a) => a.name === name);
|
|
1366
|
-
const
|
|
1367
|
-
if (
|
|
1490
|
+
const net3 = find(ATTACHMENT_NAMES.network);
|
|
1491
|
+
if (net3?.body) {
|
|
1368
1492
|
try {
|
|
1369
|
-
testCase.networkRequests = JSON.parse(
|
|
1493
|
+
testCase.networkRequests = JSON.parse(net3.body.toString());
|
|
1370
1494
|
} catch {
|
|
1371
1495
|
}
|
|
1372
1496
|
}
|
|
@@ -1422,17 +1546,17 @@ var FileHandler = class {
|
|
|
1422
1546
|
const tracePaths = this.findTraceFiles(testCase);
|
|
1423
1547
|
let lastPath = null;
|
|
1424
1548
|
for (const tp of tracePaths) {
|
|
1425
|
-
if (
|
|
1549
|
+
if (fs7.existsSync(tp)) lastPath = tp;
|
|
1426
1550
|
}
|
|
1427
1551
|
if (!lastPath) return null;
|
|
1428
1552
|
const hash = crypto3.createHash("sha256");
|
|
1429
|
-
await new Promise((
|
|
1430
|
-
|
|
1553
|
+
await new Promise((resolve11, reject) => {
|
|
1554
|
+
fs7.createReadStream(lastPath).on("data", (chunk) => hash.update(chunk)).on("end", resolve11).on("error", reject);
|
|
1431
1555
|
});
|
|
1432
1556
|
return {
|
|
1433
1557
|
tracePath: lastPath,
|
|
1434
1558
|
hash: hash.digest("hex"),
|
|
1435
|
-
size:
|
|
1559
|
+
size: fs7.statSync(lastPath).size
|
|
1436
1560
|
};
|
|
1437
1561
|
}
|
|
1438
1562
|
/** Ask the server which trace hashes it already has, returning the set of hashes that are missing */
|
|
@@ -1459,14 +1583,14 @@ function formatMiB(bytes) {
|
|
|
1459
1583
|
var import_node_child_process = require("child_process");
|
|
1460
1584
|
|
|
1461
1585
|
// src/internal/support/test-paths.ts
|
|
1462
|
-
var
|
|
1586
|
+
var path8 = __toESM(require("path"));
|
|
1463
1587
|
function configDirFromConfig(config) {
|
|
1464
|
-
if (config?.configFile) return
|
|
1588
|
+
if (config?.configFile) return path8.dirname(config.configFile);
|
|
1465
1589
|
if (config?.rootDir) return config.rootDir;
|
|
1466
1590
|
return process.cwd();
|
|
1467
1591
|
}
|
|
1468
1592
|
function relativeTestPath(file, base) {
|
|
1469
|
-
return
|
|
1593
|
+
return path8.relative(base, file).split(path8.sep).join("/");
|
|
1470
1594
|
}
|
|
1471
1595
|
|
|
1472
1596
|
// src/internal/collect/metadata-collector.ts
|
|
@@ -1526,11 +1650,6 @@ function resolveScmBaseBranch(env) {
|
|
|
1526
1650
|
const trimmed = target?.trim();
|
|
1527
1651
|
return trimmed ? normalizeRef(trimmed) : void 0;
|
|
1528
1652
|
}
|
|
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
1653
|
function resolveRunOrigin(env, warn) {
|
|
1535
1654
|
const raw = env[PIWI_ORIGIN_ENV.kind]?.trim();
|
|
1536
1655
|
let kind = detectCi(env) ? "ci" : "local";
|
|
@@ -1769,7 +1888,7 @@ var MetadataCollector = class {
|
|
|
1769
1888
|
};
|
|
1770
1889
|
|
|
1771
1890
|
// src/internal/streaming/stream-manager.ts
|
|
1772
|
-
var
|
|
1891
|
+
var fs9 = __toESM(require("fs"));
|
|
1773
1892
|
|
|
1774
1893
|
// src/internal/streaming/bounded-queue.ts
|
|
1775
1894
|
var DROP_TIER = {
|
|
@@ -1908,10 +2027,10 @@ function createLimiter(maxConcurrent) {
|
|
|
1908
2027
|
if (run) run();
|
|
1909
2028
|
};
|
|
1910
2029
|
return function limit(fn) {
|
|
1911
|
-
return new Promise((
|
|
2030
|
+
return new Promise((resolve11, reject) => {
|
|
1912
2031
|
const run = () => {
|
|
1913
2032
|
active++;
|
|
1914
|
-
Promise.resolve().then(fn).then(
|
|
2033
|
+
Promise.resolve().then(fn).then(resolve11, reject).finally(next);
|
|
1915
2034
|
};
|
|
1916
2035
|
if (active < limitValue) run();
|
|
1917
2036
|
else queue.push(run);
|
|
@@ -1920,18 +2039,18 @@ function createLimiter(maxConcurrent) {
|
|
|
1920
2039
|
}
|
|
1921
2040
|
|
|
1922
2041
|
// src/internal/support/setup-file.ts
|
|
1923
|
-
var
|
|
2042
|
+
var path9 = __toESM(require("path"));
|
|
1924
2043
|
var os6 = __toESM(require("os"));
|
|
1925
|
-
var
|
|
2044
|
+
var fs8 = __toESM(require("fs"));
|
|
1926
2045
|
function getSetupFilePath(projectName) {
|
|
1927
|
-
return
|
|
2046
|
+
return path9.join(os6.tmpdir(), `piwi-dashboard-setup-${hashForProject(projectName)}.json`);
|
|
1928
2047
|
}
|
|
1929
2048
|
function readSetupInfo(projectName) {
|
|
1930
2049
|
const setupFile = getSetupFilePath(projectName);
|
|
1931
2050
|
try {
|
|
1932
|
-
if (
|
|
1933
|
-
const info = JSON.parse(
|
|
1934
|
-
|
|
2051
|
+
if (fs8.existsSync(setupFile)) {
|
|
2052
|
+
const info = JSON.parse(fs8.readFileSync(setupFile, "utf8"));
|
|
2053
|
+
fs8.unlinkSync(setupFile);
|
|
1935
2054
|
if (info.projectName === projectName) return info;
|
|
1936
2055
|
}
|
|
1937
2056
|
} catch {
|
|
@@ -2009,6 +2128,8 @@ var StreamManager = class {
|
|
|
2009
2128
|
this._token = null;
|
|
2010
2129
|
this._auth = null;
|
|
2011
2130
|
this._startPromise = null;
|
|
2131
|
+
/** Called when the dashboard says someone opened the run. */
|
|
2132
|
+
this.onWatched = null;
|
|
2012
2133
|
const maxBytes = this.options.maxStreamBufferBytes ?? 0;
|
|
2013
2134
|
this.pendingEvents = new BoundedEventQueue(maxBytes);
|
|
2014
2135
|
this.pendingBeginEvents = new BoundedEventQueue(maxBytes);
|
|
@@ -2033,6 +2154,10 @@ var StreamManager = class {
|
|
|
2033
2154
|
get startPromise() {
|
|
2034
2155
|
return this._startPromise;
|
|
2035
2156
|
}
|
|
2157
|
+
/** Whether anyone has the run open live, as the dashboard last said; `null` until it says. */
|
|
2158
|
+
get isWatched() {
|
|
2159
|
+
return this.watched;
|
|
2160
|
+
}
|
|
2036
2161
|
/**
|
|
2037
2162
|
* Whether a test-result (`complete`) event never reached the server through
|
|
2038
2163
|
* the live stream: shed under buffer pressure, too large for any request the
|
|
@@ -2150,7 +2275,7 @@ var StreamManager = class {
|
|
|
2150
2275
|
} catch (error) {
|
|
2151
2276
|
if (error instanceof HttpError && (error.status === 401 || error.status === 403)) {
|
|
2152
2277
|
this.logger.warn(
|
|
2153
|
-
`Live streaming could not start: the dashboard at ${this.options.serverUrl} requires authentication. Set the reporter's \`apiKey\` option
|
|
2278
|
+
`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
2279
|
);
|
|
2155
2280
|
} else {
|
|
2156
2281
|
this.logger.warn(
|
|
@@ -2283,8 +2408,11 @@ var StreamManager = class {
|
|
|
2283
2408
|
if (this.pendingEvents.length > 0) this.flush();
|
|
2284
2409
|
}, delay);
|
|
2285
2410
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2411
|
+
/**
|
|
2412
|
+
* Take the dashboard's answer on whether anyone watches the run, from any
|
|
2413
|
+
* request of the stream. An answer without the flag (an older dashboard)
|
|
2414
|
+
* keeps step events flowing.
|
|
2415
|
+
*/
|
|
2288
2416
|
noteWatched(response) {
|
|
2289
2417
|
const watched = response?.watched;
|
|
2290
2418
|
const next = typeof watched === "boolean" ? watched : null;
|
|
@@ -2293,7 +2421,9 @@ var StreamManager = class {
|
|
|
2293
2421
|
next ? "The run is open on the dashboard: streaming live steps." : "Nobody is watching the run: live steps are not sent."
|
|
2294
2422
|
);
|
|
2295
2423
|
}
|
|
2424
|
+
const opened = next === true && this.watched !== true;
|
|
2296
2425
|
this.watched = next;
|
|
2426
|
+
if (opened) this.onWatched?.();
|
|
2297
2427
|
}
|
|
2298
2428
|
// Idle time after which a heartbeat goes out.
|
|
2299
2429
|
heartbeatDelay() {
|
|
@@ -2387,7 +2517,7 @@ var StreamManager = class {
|
|
|
2387
2517
|
this.logger.debugError(
|
|
2388
2518
|
`${this.pendingEvents.length} events pending, retrying (attempt ${attempt + 1}/${MAX_ATTEMPTS})...`
|
|
2389
2519
|
);
|
|
2390
|
-
await new Promise((
|
|
2520
|
+
await new Promise((resolve11) => setTimeout(resolve11, Math.min(1e3 * Math.pow(2, attempt), 1e4)));
|
|
2391
2521
|
}
|
|
2392
2522
|
if (!this.pendingEvents.isEmpty) {
|
|
2393
2523
|
const remaining = this.pendingEvents.takeAll();
|
|
@@ -2431,7 +2561,7 @@ var StreamManager = class {
|
|
|
2431
2561
|
}
|
|
2432
2562
|
/** 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
2563
|
scheduleLiveUpload(tc) {
|
|
2434
|
-
const hasTrace = !!this.options.uploadTraces && this.fileHandler.findTraceFiles(tc).some((p) =>
|
|
2564
|
+
const hasTrace = !!this.options.uploadTraces && this.fileHandler.findTraceFiles(tc).some((p) => fs9.existsSync(p));
|
|
2435
2565
|
const hasAttachments = this.fileHandler.findAllAttachments(tc).length > 0;
|
|
2436
2566
|
if (!hasTrace && !hasAttachments) return;
|
|
2437
2567
|
const promise = (async () => {
|
|
@@ -2444,7 +2574,7 @@ var StreamManager = class {
|
|
|
2444
2574
|
if (flush) await flush;
|
|
2445
2575
|
const delays = [0, 1e3, 3e3];
|
|
2446
2576
|
for (let attempt = 0; attempt < delays.length; attempt++) {
|
|
2447
|
-
if (delays[attempt]) await new Promise((
|
|
2577
|
+
if (delays[attempt]) await new Promise((resolve11) => setTimeout(resolve11, delays[attempt]));
|
|
2448
2578
|
try {
|
|
2449
2579
|
await this.limitLiveUpload(
|
|
2450
2580
|
() => this.uploader.uploadCaseFiles(
|
|
@@ -3259,7 +3389,7 @@ function maskUrlFragment(hash) {
|
|
|
3259
3389
|
}
|
|
3260
3390
|
|
|
3261
3391
|
// ../core/src/locator-break.ts
|
|
3262
|
-
var filePath = (
|
|
3392
|
+
var filePath = (path34) => path34.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3263
3393
|
function sameFilePath(a, b) {
|
|
3264
3394
|
const x = filePath(a);
|
|
3265
3395
|
const y = filePath(b);
|
|
@@ -3554,23 +3684,48 @@ function flattenSteps(steps, depth = 0) {
|
|
|
3554
3684
|
}
|
|
3555
3685
|
return result;
|
|
3556
3686
|
}
|
|
3557
|
-
function
|
|
3687
|
+
function epochMs(value) {
|
|
3688
|
+
if (value instanceof Date) return value.getTime();
|
|
3689
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3690
|
+
}
|
|
3691
|
+
function pausedOverlap(start, duration, pauses) {
|
|
3692
|
+
const from = epochMs(start);
|
|
3693
|
+
const length = typeof duration === "number" && duration > 0 ? duration : 0;
|
|
3694
|
+
if (from === null || length === 0 || pauses.length === 0) return 0;
|
|
3695
|
+
const to = from + length;
|
|
3696
|
+
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]);
|
|
3697
|
+
let ms = 0;
|
|
3698
|
+
let reached = from;
|
|
3699
|
+
for (const [a, b] of spans) {
|
|
3700
|
+
const begin = Math.max(a, reached);
|
|
3701
|
+
if (b > begin) ms += b - begin;
|
|
3702
|
+
reached = Math.max(reached, b);
|
|
3703
|
+
}
|
|
3704
|
+
return ms;
|
|
3705
|
+
}
|
|
3706
|
+
function unpausedDuration(step, pauses) {
|
|
3707
|
+
const duration = typeof step.duration === "number" && step.duration > 0 ? step.duration : 0;
|
|
3708
|
+
return duration - pausedOverlap(step.startTime, duration, pauses);
|
|
3709
|
+
}
|
|
3710
|
+
function collectStepMetrics(steps, errors, pauses = []) {
|
|
3558
3711
|
const flatSteps = flattenSteps(steps);
|
|
3559
3712
|
if (errors) markRecoveredSteps(flatSteps, errors);
|
|
3560
|
-
const
|
|
3713
|
+
const own = (s) => pauses.length > 0 && s.duration > 0 ? unpausedDuration(s, pauses) : s.duration;
|
|
3714
|
+
const totalStepDuration = steps.reduce((sum, s) => sum + (own(s) || 0), 0);
|
|
3561
3715
|
let slowestStep = null;
|
|
3562
3716
|
for (const s of flatSteps) {
|
|
3563
|
-
|
|
3717
|
+
const duration = own(s);
|
|
3718
|
+
if (!slowestStep || duration > slowestStep.duration) slowestStep = { title: s.title, duration };
|
|
3564
3719
|
}
|
|
3565
3720
|
const navSteps = flatSteps.filter((s) => s.category === "navigation");
|
|
3566
3721
|
const waitSteps = flatSteps.filter((s) => s.category === "wait");
|
|
3567
|
-
const waitTotalDuration = waitSteps.reduce((sum, s) => sum + (s
|
|
3722
|
+
const waitTotalDuration = waitSteps.reduce((sum, s) => sum + (own(s) || 0), 0);
|
|
3568
3723
|
return {
|
|
3569
3724
|
steps: flatSteps,
|
|
3570
3725
|
totalStepDuration,
|
|
3571
3726
|
slowestStep,
|
|
3572
3727
|
navigationCount: navSteps.length,
|
|
3573
|
-
navigationTotalDuration: navSteps.reduce((sum, s) => sum + (s
|
|
3728
|
+
navigationTotalDuration: navSteps.reduce((sum, s) => sum + (own(s) || 0), 0),
|
|
3574
3729
|
waitTotalDuration,
|
|
3575
3730
|
waitCount: waitSteps.length
|
|
3576
3731
|
};
|
|
@@ -3686,19 +3841,43 @@ function extractWaitEvents(steps, insideWait = false) {
|
|
|
3686
3841
|
return events;
|
|
3687
3842
|
}
|
|
3688
3843
|
|
|
3844
|
+
// src/internal/collect/pause-events.ts
|
|
3845
|
+
function readPauses(body) {
|
|
3846
|
+
if (!body) return [];
|
|
3847
|
+
try {
|
|
3848
|
+
const value = JSON.parse(body.toString("utf8"));
|
|
3849
|
+
if (!Array.isArray(value)) return [];
|
|
3850
|
+
return value.filter(
|
|
3851
|
+
(p) => !!p && typeof p === "object" && typeof p.location === "string" && typeof p.startedAt === "number" && typeof p.duration === "number"
|
|
3852
|
+
);
|
|
3853
|
+
} catch {
|
|
3854
|
+
return [];
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
function pauseStepEvents(pauses) {
|
|
3858
|
+
return pauses.map((pause) => ({
|
|
3859
|
+
title: typeof pause.title === "string" ? pause.title : "",
|
|
3860
|
+
category: "pause",
|
|
3861
|
+
startedAt: pause.startedAt,
|
|
3862
|
+
duration: Math.max(0, pause.duration),
|
|
3863
|
+
status: typeof pause.reason === "string" ? pause.reason : "breakpoint",
|
|
3864
|
+
location: pause.location
|
|
3865
|
+
}));
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3689
3868
|
// src/internal/support/reporter-version.ts
|
|
3690
|
-
var
|
|
3691
|
-
var
|
|
3869
|
+
var fs10 = __toESM(require("fs"));
|
|
3870
|
+
var path10 = __toESM(require("path"));
|
|
3692
3871
|
var cachedVersion = null;
|
|
3693
3872
|
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
3694
3873
|
function findOwnPackageJson(fromDir) {
|
|
3695
3874
|
for (const offset of CANDIDATE_OFFSETS) {
|
|
3696
3875
|
try {
|
|
3697
|
-
const pkgPath =
|
|
3698
|
-
const pkg = JSON.parse(
|
|
3876
|
+
const pkgPath = path10.resolve(fromDir, offset, "package.json");
|
|
3877
|
+
const pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
|
|
3699
3878
|
const { name, version } = pkg;
|
|
3700
3879
|
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
3701
|
-
return { version, root:
|
|
3880
|
+
return { version, root: path10.dirname(pkgPath) };
|
|
3702
3881
|
} catch {
|
|
3703
3882
|
}
|
|
3704
3883
|
}
|
|
@@ -3711,11 +3890,11 @@ function getReporterVersion() {
|
|
|
3711
3890
|
}
|
|
3712
3891
|
|
|
3713
3892
|
// src/internal/support/source-snippet.ts
|
|
3714
|
-
var
|
|
3715
|
-
var
|
|
3893
|
+
var fs11 = __toESM(require("fs"));
|
|
3894
|
+
var path11 = __toESM(require("path"));
|
|
3716
3895
|
function readSourceSnippet(file, declLine, context, failingLine) {
|
|
3717
3896
|
try {
|
|
3718
|
-
const content =
|
|
3897
|
+
const content = fs11.readFileSync(file, "utf-8");
|
|
3719
3898
|
const lines = content.split("\n");
|
|
3720
3899
|
const anchor = failingLine ?? declLine;
|
|
3721
3900
|
const start = Math.max(0, anchor - context - 1);
|
|
@@ -3744,7 +3923,7 @@ function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
|
3744
3923
|
const maxFrames = opts.maxFrames ?? 4;
|
|
3745
3924
|
const picked = [];
|
|
3746
3925
|
const seen = /* @__PURE__ */ new Set();
|
|
3747
|
-
const
|
|
3926
|
+
const add2 = (absFile, line) => {
|
|
3748
3927
|
const key = `${absFile}:${line}`;
|
|
3749
3928
|
if (seen.has(key)) return;
|
|
3750
3929
|
seen.add(key);
|
|
@@ -3755,66 +3934,40 @@ function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
|
3755
3934
|
if (picked.length >= maxFrames) break;
|
|
3756
3935
|
let abs;
|
|
3757
3936
|
try {
|
|
3758
|
-
abs =
|
|
3937
|
+
abs = path11.resolve(frame.file);
|
|
3759
3938
|
} catch {
|
|
3760
3939
|
continue;
|
|
3761
3940
|
}
|
|
3762
|
-
const rel =
|
|
3763
|
-
if (rel.startsWith("..") ||
|
|
3764
|
-
if (!isNaN(frame.line))
|
|
3941
|
+
const rel = path11.relative(projectRoot, abs);
|
|
3942
|
+
if (rel.startsWith("..") || path11.isAbsolute(rel)) continue;
|
|
3943
|
+
if (!isNaN(frame.line)) add2(abs, frame.line);
|
|
3765
3944
|
}
|
|
3766
3945
|
}
|
|
3767
|
-
const absTest =
|
|
3946
|
+
const absTest = path11.resolve(testFile2);
|
|
3768
3947
|
if (!picked.some((p) => p.absFile === absTest)) {
|
|
3769
|
-
|
|
3948
|
+
add2(absTest, extractFailingLine(errorText, testFile2, declLine));
|
|
3770
3949
|
}
|
|
3771
3950
|
const frames = [];
|
|
3772
3951
|
for (const p of picked.slice(0, maxFrames)) {
|
|
3773
3952
|
const snippet = readSourceSnippet(p.absFile, p.line, context, p.line);
|
|
3774
3953
|
if (snippet) {
|
|
3775
|
-
frames.push({ file:
|
|
3954
|
+
frames.push({ file: path11.relative(projectRoot, p.absFile) || path11.basename(p.absFile), line: p.line, snippet });
|
|
3776
3955
|
}
|
|
3777
3956
|
}
|
|
3778
3957
|
return frames;
|
|
3779
3958
|
}
|
|
3780
3959
|
function extractFailingLine(errorText, testFile2, declarationLine) {
|
|
3781
3960
|
if (!errorText) return declarationLine;
|
|
3782
|
-
const expectedFile =
|
|
3961
|
+
const expectedFile = path11.resolve(testFile2);
|
|
3783
3962
|
for (const frame of stackFrames(errorText)) {
|
|
3784
3963
|
try {
|
|
3785
|
-
if (
|
|
3964
|
+
if (path11.resolve(frame.file) === expectedFile && !isNaN(frame.line)) return frame.line;
|
|
3786
3965
|
} catch {
|
|
3787
3966
|
}
|
|
3788
3967
|
}
|
|
3789
3968
|
return declarationLine;
|
|
3790
3969
|
}
|
|
3791
3970
|
|
|
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
3971
|
// src/internal/support/worker-index.ts
|
|
3819
3972
|
function workerIndexOf(result) {
|
|
3820
3973
|
if (!result) return null;
|
|
@@ -3900,28 +4053,28 @@ function isListMode(argv = process.argv) {
|
|
|
3900
4053
|
}
|
|
3901
4054
|
|
|
3902
4055
|
// src/public/global-setup.ts
|
|
3903
|
-
var
|
|
3904
|
-
var
|
|
4056
|
+
var path15 = __toESM(require("path"));
|
|
4057
|
+
var fs15 = __toESM(require("fs"));
|
|
3905
4058
|
|
|
3906
4059
|
// src/internal/support/aria-sampling.ts
|
|
3907
|
-
var
|
|
4060
|
+
var path12 = __toESM(require("path"));
|
|
3908
4061
|
var os7 = __toESM(require("os"));
|
|
3909
|
-
var
|
|
4062
|
+
var fs12 = __toESM(require("fs"));
|
|
3910
4063
|
function ariaSampleIdentity(filePath2, title) {
|
|
3911
4064
|
return `${filePath2}\0${title}`;
|
|
3912
4065
|
}
|
|
3913
4066
|
function getAriaSampleFilePath(projectName) {
|
|
3914
|
-
return
|
|
4067
|
+
return path12.join(os7.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
3915
4068
|
}
|
|
3916
4069
|
function writeAriaSampleFile(projectName, identities) {
|
|
3917
4070
|
try {
|
|
3918
|
-
|
|
4071
|
+
fs12.writeFileSync(getAriaSampleFilePath(projectName), JSON.stringify({ projectName, identities }));
|
|
3919
4072
|
} catch {
|
|
3920
4073
|
}
|
|
3921
4074
|
}
|
|
3922
4075
|
function clearAriaSampleFile(projectName) {
|
|
3923
4076
|
try {
|
|
3924
|
-
|
|
4077
|
+
fs12.rmSync(getAriaSampleFilePath(projectName), { force: true });
|
|
3925
4078
|
} catch {
|
|
3926
4079
|
}
|
|
3927
4080
|
}
|
|
@@ -3930,7 +4083,7 @@ function loadAriaSampleSet(projectName) {
|
|
|
3930
4083
|
if (cachedSets.has(projectName)) return cachedSets.get(projectName);
|
|
3931
4084
|
let set = null;
|
|
3932
4085
|
try {
|
|
3933
|
-
const raw =
|
|
4086
|
+
const raw = fs12.readFileSync(getAriaSampleFilePath(projectName), "utf8");
|
|
3934
4087
|
const parsed2 = JSON.parse(raw);
|
|
3935
4088
|
if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
|
|
3936
4089
|
set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
|
|
@@ -3952,6 +4105,7 @@ function isDueForAriaSample(testInfo) {
|
|
|
3952
4105
|
}
|
|
3953
4106
|
|
|
3954
4107
|
// src/internal/support/base-url-check.ts
|
|
4108
|
+
var net = __toESM(require("net"));
|
|
3955
4109
|
var BASE_URL_CHECK_ATTEMPTS = 3;
|
|
3956
4110
|
var BASE_URL_CHECK_TIMEOUT_MS = 1e4;
|
|
3957
4111
|
var BASE_URL_CHECK_DELAY_MS = 2e3;
|
|
@@ -3973,9 +4127,9 @@ function cliProjectNames(argv = process.argv) {
|
|
|
3973
4127
|
}
|
|
3974
4128
|
return names;
|
|
3975
4129
|
}
|
|
3976
|
-
function wildcard(pattern) {
|
|
4130
|
+
function wildcard(pattern, flags = "i") {
|
|
3977
4131
|
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
3978
|
-
return new RegExp(`^${escaped.join(".*")}$`,
|
|
4132
|
+
return new RegExp(`^${escaped.join(".*")}$`, flags);
|
|
3979
4133
|
}
|
|
3980
4134
|
function selectedProjects(projects, argv = process.argv, filtered) {
|
|
3981
4135
|
let chosen;
|
|
@@ -3985,7 +4139,7 @@ function selectedProjects(projects, argv = process.argv, filtered) {
|
|
|
3985
4139
|
} else {
|
|
3986
4140
|
const names = cliProjectNames(argv);
|
|
3987
4141
|
if (!names || names.length === 0) return projects;
|
|
3988
|
-
const patterns = names.map(wildcard);
|
|
4142
|
+
const patterns = names.map((name) => wildcard(name));
|
|
3989
4143
|
chosen = new Set(projects.filter((p) => patterns.some((re) => re.test(p.name ?? ""))));
|
|
3990
4144
|
}
|
|
3991
4145
|
if (!argv.includes("--no-deps")) {
|
|
@@ -4007,23 +4161,58 @@ function asProxy(value) {
|
|
|
4007
4161
|
const proxy = value;
|
|
4008
4162
|
return proxy && typeof proxy === "object" && typeof proxy.server === "string" ? proxy : void 0;
|
|
4009
4163
|
}
|
|
4010
|
-
function
|
|
4164
|
+
function exposeNetworkCovers(rule, url) {
|
|
4165
|
+
if (!rule) return false;
|
|
4166
|
+
let parsed2;
|
|
4167
|
+
try {
|
|
4168
|
+
parsed2 = new URL(url);
|
|
4169
|
+
} catch {
|
|
4170
|
+
return false;
|
|
4171
|
+
}
|
|
4172
|
+
const host = parsed2.hostname;
|
|
4173
|
+
const port = parsed2.port ? Number(parsed2.port) : parsed2.protocol === "https:" ? 443 : 80;
|
|
4174
|
+
return rule.split(",").some((token) => {
|
|
4175
|
+
const match = token.match(/^(.*?)(?::(\d+))?$/);
|
|
4176
|
+
if (!match) return false;
|
|
4177
|
+
if (match[2] !== void 0 && Number(match[2]) !== port) return false;
|
|
4178
|
+
const tokenHost = match[1];
|
|
4179
|
+
if (tokenHost === "<loopback>") {
|
|
4180
|
+
return host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "[::1]";
|
|
4181
|
+
}
|
|
4182
|
+
if (tokenHost === "*") return true;
|
|
4183
|
+
if (net.isIP(tokenHost)) return host === tokenHost;
|
|
4184
|
+
if (net.isIP(host)) return false;
|
|
4185
|
+
return wildcard(tokenHost.startsWith(".") ? `*${tokenHost}` : tokenHost, "").test(host);
|
|
4186
|
+
});
|
|
4187
|
+
}
|
|
4188
|
+
function reachedRemotely(use, url, env) {
|
|
4189
|
+
if (use?.connectOptions === void 0) {
|
|
4190
|
+
return !!env.PW_TEST_CONNECT_WS_ENDPOINT && !exposeNetworkCovers(env.PW_TEST_CONNECT_EXPOSE_NETWORK, url);
|
|
4191
|
+
}
|
|
4192
|
+
const connect2 = use.connectOptions;
|
|
4193
|
+
if (!connect2 || typeof connect2 !== "object" || typeof connect2.wsEndpoint !== "string") return false;
|
|
4194
|
+
return !exposeNetworkCovers(typeof connect2.exposeNetwork === "string" ? connect2.exposeNetwork : void 0, url);
|
|
4195
|
+
}
|
|
4196
|
+
function baseUrlTargets(config, argv = process.argv, env = process.env) {
|
|
4011
4197
|
const projects = config.projects?.length ? config.projects : [{ name: "", use: config.use }];
|
|
4012
4198
|
const filtered = config.projects?.length ? config.filteredProjects : void 0;
|
|
4013
4199
|
const targets = /* @__PURE__ */ new Map();
|
|
4014
4200
|
for (const project of selectedProjects(projects, argv, filtered)) {
|
|
4015
4201
|
const url = project.use?.baseURL;
|
|
4016
4202
|
if (typeof url !== "string" || !/^https?:\/\//i.test(url)) continue;
|
|
4203
|
+
const remoteBrowser = reachedRemotely(project.use, url, env);
|
|
4017
4204
|
const target = targets.get(url);
|
|
4018
4205
|
if (target) {
|
|
4019
4206
|
if (project.name) target.projects.push(project.name);
|
|
4207
|
+
target.remoteBrowser ||= remoteBrowser;
|
|
4020
4208
|
continue;
|
|
4021
4209
|
}
|
|
4022
4210
|
targets.set(url, {
|
|
4023
4211
|
url,
|
|
4024
4212
|
projects: project.name ? [project.name] : [],
|
|
4025
4213
|
ignoreHTTPSErrors: project.use?.ignoreHTTPSErrors === true,
|
|
4026
|
-
proxy: asProxy(project.use?.proxy)
|
|
4214
|
+
proxy: asProxy(project.use?.proxy),
|
|
4215
|
+
remoteBrowser
|
|
4027
4216
|
});
|
|
4028
4217
|
}
|
|
4029
4218
|
return [...targets.values()];
|
|
@@ -4060,18 +4249,25 @@ async function checkBaseUrl(target, options = {}) {
|
|
|
4060
4249
|
} else {
|
|
4061
4250
|
reason = outcome.error;
|
|
4062
4251
|
}
|
|
4063
|
-
if (attempt < attempts) await new Promise((
|
|
4252
|
+
if (attempt < attempts) await new Promise((resolve11) => setTimeout(resolve11, delayMs));
|
|
4064
4253
|
}
|
|
4065
4254
|
return `${reason} (${attempts} ${attempts === 1 ? "attempt" : "attempts"})`;
|
|
4066
4255
|
}
|
|
4256
|
+
function remoteBrowserNote(target) {
|
|
4257
|
+
if (!target.remoteBrowser) return null;
|
|
4258
|
+
const { host } = new URL(target.url);
|
|
4259
|
+
return `The check ran from this machine's network, but a project's remote browser reaches ${host} from its own, since its exposeNetwork rule does not cover that address.`;
|
|
4260
|
+
}
|
|
4067
4261
|
async function checkBaseUrls(config, options = {}) {
|
|
4068
|
-
const targets = baseUrlTargets(config, options.argv);
|
|
4262
|
+
const targets = baseUrlTargets(config, options.argv, options.env);
|
|
4069
4263
|
const reasons = await Promise.all(targets.map((target) => checkBaseUrl(target, options)));
|
|
4070
4264
|
const down = targets.flatMap((target, i) => reasons[i] ? [{ target, reason: reasons[i] }] : []);
|
|
4071
4265
|
if (down.length === 0) return targets;
|
|
4072
4266
|
const lines = down.map(({ target, reason }) => {
|
|
4073
4267
|
const used = target.projects.length > 0 ? ` (${target.projects.join(", ")})` : "";
|
|
4074
|
-
|
|
4268
|
+
const note = remoteBrowserNote(target);
|
|
4269
|
+
return ` ${target.url}${used}: ${reason}${note ? `
|
|
4270
|
+
${note}` : ""}`;
|
|
4075
4271
|
});
|
|
4076
4272
|
const message = [
|
|
4077
4273
|
`[Piwi Dashboard] The app under test did not answer, so no test ran:`,
|
|
@@ -4084,14 +4280,14 @@ async function checkBaseUrls(config, options = {}) {
|
|
|
4084
4280
|
}
|
|
4085
4281
|
|
|
4086
4282
|
// src/internal/manifest/collect.ts
|
|
4087
|
-
var
|
|
4088
|
-
var
|
|
4283
|
+
var fs13 = __toESM(require("fs"));
|
|
4284
|
+
var path13 = __toESM(require("path"));
|
|
4089
4285
|
|
|
4090
4286
|
// src/internal/transport/backend-request.ts
|
|
4091
4287
|
var http2 = __toESM(require("http"));
|
|
4092
4288
|
var https2 = __toESM(require("https"));
|
|
4093
4289
|
function backendRequest(url, init) {
|
|
4094
|
-
return new Promise((
|
|
4290
|
+
return new Promise((resolve11, reject) => {
|
|
4095
4291
|
const target = new URL(url);
|
|
4096
4292
|
const headers = { ...init.headers };
|
|
4097
4293
|
if (init.body !== void 0) headers["Content-Length"] = Buffer.byteLength(init.body);
|
|
@@ -4106,7 +4302,7 @@ function backendRequest(url, init) {
|
|
|
4106
4302
|
});
|
|
4107
4303
|
res.on(
|
|
4108
4304
|
"end",
|
|
4109
|
-
() =>
|
|
4305
|
+
() => resolve11({ status: res.statusCode ?? 0, headers: res.headers, text: Buffer.concat(chunks).toString("utf8") })
|
|
4110
4306
|
);
|
|
4111
4307
|
res.on("error", reject);
|
|
4112
4308
|
res.on("close", () => {
|
|
@@ -4151,10 +4347,10 @@ function hasInstrumentationHeader(headers) {
|
|
|
4151
4347
|
return INSTRUMENTATION_HEADERS.some((h) => get(h) != null && get(h) !== "");
|
|
4152
4348
|
}
|
|
4153
4349
|
function readCommittedManifest(configDir) {
|
|
4154
|
-
const file =
|
|
4350
|
+
const file = path13.join(configDir, COMMITTED_MANIFEST_FILE);
|
|
4155
4351
|
try {
|
|
4156
|
-
if (!
|
|
4157
|
-
return parseManifestJson(
|
|
4352
|
+
if (!fs13.existsSync(file)) return null;
|
|
4353
|
+
return parseManifestJson(fs13.readFileSync(file, "utf8"));
|
|
4158
4354
|
} catch {
|
|
4159
4355
|
return null;
|
|
4160
4356
|
}
|
|
@@ -4180,8 +4376,8 @@ function backendManifestFetch(insecure) {
|
|
|
4180
4376
|
function instrumentationManifestUrls(baseUrl) {
|
|
4181
4377
|
const base = new URL(baseUrl);
|
|
4182
4378
|
const root = new URL(INSTRUMENTATION_MANIFEST_PATH, base).toString();
|
|
4183
|
-
const
|
|
4184
|
-
return
|
|
4379
|
+
const path34 = base.pathname.replace(/\/+$/, "");
|
|
4380
|
+
return path34 ? [new URL(`${path34}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
|
|
4185
4381
|
}
|
|
4186
4382
|
async function fetchInstrumentationManifest(baseUrl, fetchImpl = fetch) {
|
|
4187
4383
|
try {
|
|
@@ -4319,13 +4515,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
4319
4515
|
}
|
|
4320
4516
|
function normalizeCoveragePath(raw) {
|
|
4321
4517
|
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
|
|
4518
|
+
const path34 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4519
|
+
if (!path34 || path34.startsWith("../") || path34.startsWith("/") || /^[A-Za-z]:\//.test(path34)) return null;
|
|
4520
|
+
if (/(^|\/)node_modules\//.test(path34)) return null;
|
|
4521
|
+
return path34;
|
|
4326
4522
|
}
|
|
4327
|
-
function fileEntry(
|
|
4328
|
-
const file = { path:
|
|
4523
|
+
function fileEntry(path34, runtime, blob, functions = [], lines = []) {
|
|
4524
|
+
const file = { path: path34, runtime };
|
|
4329
4525
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
4330
4526
|
if (functions.length > 0) file.functions = functions;
|
|
4331
4527
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -4344,11 +4540,11 @@ function sanitizeCoverage(raw) {
|
|
|
4344
4540
|
const files = [];
|
|
4345
4541
|
for (const item of raw.files) {
|
|
4346
4542
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
4347
|
-
const
|
|
4348
|
-
if (
|
|
4543
|
+
const path34 = normalizeCoveragePath(item.path);
|
|
4544
|
+
if (path34 === null) continue;
|
|
4349
4545
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
4350
4546
|
files.push(
|
|
4351
|
-
fileEntry(
|
|
4547
|
+
fileEntry(path34, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
4352
4548
|
);
|
|
4353
4549
|
}
|
|
4354
4550
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -4413,8 +4609,8 @@ function reduceCoverage(capture, granularity) {
|
|
|
4413
4609
|
|
|
4414
4610
|
// src/internal/collect/source-checksums.ts
|
|
4415
4611
|
var crypto4 = __toESM(require("crypto"));
|
|
4416
|
-
var
|
|
4417
|
-
var
|
|
4612
|
+
var fs14 = __toESM(require("fs"));
|
|
4613
|
+
var path14 = __toESM(require("path"));
|
|
4418
4614
|
var CHECKSUM = /^(sha1|sha256):[0-9a-f]{40,64}$/;
|
|
4419
4615
|
function checksumsOf(raw) {
|
|
4420
4616
|
if (!Array.isArray(raw)) return [];
|
|
@@ -4479,10 +4675,10 @@ var SourceChecksums = class {
|
|
|
4479
4675
|
}
|
|
4480
4676
|
async read(relativePath2) {
|
|
4481
4677
|
try {
|
|
4482
|
-
const root =
|
|
4483
|
-
const file =
|
|
4484
|
-
if (!file.startsWith(root +
|
|
4485
|
-
return contentChecksums(await
|
|
4678
|
+
const root = path14.resolve(this.repoRoot());
|
|
4679
|
+
const file = path14.resolve(root, relativePath2);
|
|
4680
|
+
if (!file.startsWith(root + path14.sep)) return null;
|
|
4681
|
+
return contentChecksums(await fs14.promises.readFile(file));
|
|
4486
4682
|
} catch {
|
|
4487
4683
|
return null;
|
|
4488
4684
|
}
|
|
@@ -4576,7 +4772,7 @@ function instanceOf(answer) {
|
|
|
4576
4772
|
return { id: instance.id, startedAt };
|
|
4577
4773
|
}
|
|
4578
4774
|
function processKeyOf(id) {
|
|
4579
|
-
const dot = id.
|
|
4775
|
+
const dot = id.indexOf(".");
|
|
4580
4776
|
return dot > 0 ? id.slice(0, dot) : null;
|
|
4581
4777
|
}
|
|
4582
4778
|
function withTestCoverage(files, tests) {
|
|
@@ -4627,13 +4823,13 @@ var ServerCoverageCollector = class {
|
|
|
4627
4823
|
if (parts.length === 0 || this.closer.signal.aborted) {
|
|
4628
4824
|
return Promise.resolve({ files: [], incomplete: truncated || parts.length > 0 });
|
|
4629
4825
|
}
|
|
4630
|
-
return new Promise((
|
|
4826
|
+
return new Promise((resolve11) => {
|
|
4631
4827
|
const entry = {
|
|
4632
4828
|
pending: parts.length,
|
|
4633
4829
|
captures: [],
|
|
4634
4830
|
checksums: /* @__PURE__ */ new Map(),
|
|
4635
4831
|
incomplete: truncated,
|
|
4636
|
-
resolve:
|
|
4832
|
+
resolve: resolve11
|
|
4637
4833
|
};
|
|
4638
4834
|
for (const [origin, ids] of parts) this.enqueue(origin, { entry, ids });
|
|
4639
4835
|
});
|
|
@@ -4959,14 +5155,14 @@ var ServerCoverageCollector = class {
|
|
|
4959
5155
|
// src/public/global-setup.ts
|
|
4960
5156
|
function createGlobalSetup(options, userSetup) {
|
|
4961
5157
|
return async function globalSetupFn(config) {
|
|
4962
|
-
const piwiReporterPath =
|
|
5158
|
+
const piwiReporterPath = path15.resolve(__dirname, "./index.js");
|
|
4963
5159
|
let inlineReporterOptions = {};
|
|
4964
5160
|
if (Array.isArray(config?.reporter)) {
|
|
4965
5161
|
for (const r of config.reporter) {
|
|
4966
5162
|
if (!Array.isArray(r) || typeof r[0] !== "string") continue;
|
|
4967
5163
|
const isPiwi = r[0].toLowerCase().includes("piwi") || (() => {
|
|
4968
5164
|
try {
|
|
4969
|
-
return
|
|
5165
|
+
return path15.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
4970
5166
|
} catch {
|
|
4971
5167
|
return false;
|
|
4972
5168
|
}
|
|
@@ -4977,7 +5173,10 @@ function createGlobalSetup(options, userSetup) {
|
|
|
4977
5173
|
}
|
|
4978
5174
|
}
|
|
4979
5175
|
}
|
|
4980
|
-
const opts = resolveOptions(
|
|
5176
|
+
const opts = resolveOptions(
|
|
5177
|
+
{ ...inlineReporterOptions, ...options },
|
|
5178
|
+
configDirFromConfig(config)
|
|
5179
|
+
);
|
|
4981
5180
|
const logger = new Logger(opts.verbose ?? false);
|
|
4982
5181
|
if (isUiMode()) {
|
|
4983
5182
|
logger.debug("UI mode detected \u2014 skipping run registration.");
|
|
@@ -4991,7 +5190,16 @@ function createGlobalSetup(options, userSetup) {
|
|
|
4991
5190
|
}
|
|
4992
5191
|
if (opts.checkBaseUrl === true) {
|
|
4993
5192
|
const checked = await checkBaseUrls(config ?? {});
|
|
4994
|
-
for (const target of checked)
|
|
5193
|
+
for (const target of checked) {
|
|
5194
|
+
const note = remoteBrowserNote(target);
|
|
5195
|
+
if (note) {
|
|
5196
|
+
logger.info(
|
|
5197
|
+
`Base URL answered: ${target.url}. ${note} A navigation failure from that browser can mean its machine cannot reach the app, not that the app is down.`
|
|
5198
|
+
);
|
|
5199
|
+
} else {
|
|
5200
|
+
logger.debug(`Base URL answered: ${target.url}`);
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
4995
5203
|
}
|
|
4996
5204
|
if (opts.enabled === false || !opts.serverUrl) {
|
|
4997
5205
|
logger.info("Not enabled \u2014 set PIWI_DASHBOARD_URL or serverUrl to enable.");
|
|
@@ -5002,7 +5210,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
5002
5210
|
if (!Array.isArray(r) || typeof r[0] !== "string") return false;
|
|
5003
5211
|
if (r[0].toLowerCase().includes("piwi")) return true;
|
|
5004
5212
|
try {
|
|
5005
|
-
return
|
|
5213
|
+
return path15.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
5006
5214
|
} catch {
|
|
5007
5215
|
return false;
|
|
5008
5216
|
}
|
|
@@ -5032,7 +5240,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
5032
5240
|
auth
|
|
5033
5241
|
);
|
|
5034
5242
|
if (response?.runId && response?.setupToken) {
|
|
5035
|
-
|
|
5243
|
+
fs15.writeFileSync(
|
|
5036
5244
|
getSetupFilePath(opts.projectName),
|
|
5037
5245
|
JSON.stringify({
|
|
5038
5246
|
runId: response.runId,
|
|
@@ -5086,11 +5294,11 @@ function createGlobalSetup(options, userSetup) {
|
|
|
5086
5294
|
}
|
|
5087
5295
|
|
|
5088
5296
|
// src/public/config-wrapper.ts
|
|
5089
|
-
var
|
|
5090
|
-
var
|
|
5297
|
+
var path16 = __toESM(require("path"));
|
|
5298
|
+
var fs18 = __toESM(require("fs"));
|
|
5091
5299
|
|
|
5092
5300
|
// src/internal/probe/mode.ts
|
|
5093
|
-
var
|
|
5301
|
+
var fs16 = __toESM(require("fs"));
|
|
5094
5302
|
|
|
5095
5303
|
// src/internal/probe/plan.ts
|
|
5096
5304
|
function probeRunMetadata(base = {}) {
|
|
@@ -5102,8 +5310,8 @@ function parseRouteKey(routeKey2) {
|
|
|
5102
5310
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
5103
5311
|
}
|
|
5104
5312
|
function patternToRegex(pattern) {
|
|
5105
|
-
const
|
|
5106
|
-
const escaped =
|
|
5313
|
+
const path34 = pattern.split("?")[0] ?? pattern;
|
|
5314
|
+
const escaped = path34.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
5107
5315
|
return new RegExp(`^${escaped}$`);
|
|
5108
5316
|
}
|
|
5109
5317
|
function suitePathEqual(a, b) {
|
|
@@ -5126,13 +5334,13 @@ function matchProbeItem(plan, test) {
|
|
|
5126
5334
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
5127
5335
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
5128
5336
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
5129
|
-
let
|
|
5337
|
+
let path34;
|
|
5130
5338
|
try {
|
|
5131
|
-
|
|
5339
|
+
path34 = new URL(url).pathname;
|
|
5132
5340
|
} catch {
|
|
5133
|
-
|
|
5341
|
+
path34 = url.split("?")[0] ?? url;
|
|
5134
5342
|
}
|
|
5135
|
-
return patternToRegex(pattern).test(
|
|
5343
|
+
return patternToRegex(pattern).test(path34);
|
|
5136
5344
|
}
|
|
5137
5345
|
function createProbeState() {
|
|
5138
5346
|
return { navigated: false, matches: 0 };
|
|
@@ -5154,10 +5362,10 @@ function isProbeMode() {
|
|
|
5154
5362
|
function loadPlan() {
|
|
5155
5363
|
if (planCache !== void 0) return planCache;
|
|
5156
5364
|
planCache = null;
|
|
5157
|
-
const
|
|
5158
|
-
if (
|
|
5365
|
+
const path34 = process.env[PIWI_PROBE_ENV.plan];
|
|
5366
|
+
if (path34) {
|
|
5159
5367
|
try {
|
|
5160
|
-
planCache = JSON.parse(
|
|
5368
|
+
planCache = JSON.parse(fs16.readFileSync(path34, "utf8"));
|
|
5161
5369
|
} catch {
|
|
5162
5370
|
planCache = null;
|
|
5163
5371
|
}
|
|
@@ -5181,17 +5389,17 @@ function outcomeFromStatus(status, applied) {
|
|
|
5181
5389
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
5182
5390
|
}
|
|
5183
5391
|
function recordProbeOutcome(line) {
|
|
5184
|
-
const
|
|
5185
|
-
if (!
|
|
5392
|
+
const path34 = process.env[PIWI_PROBE_ENV.results];
|
|
5393
|
+
if (!path34) return;
|
|
5186
5394
|
try {
|
|
5187
|
-
|
|
5395
|
+
fs16.appendFileSync(path34, `${JSON.stringify(line)}
|
|
5188
5396
|
`);
|
|
5189
5397
|
} catch {
|
|
5190
5398
|
}
|
|
5191
5399
|
}
|
|
5192
5400
|
|
|
5193
5401
|
// src/internal/flake/mode.ts
|
|
5194
|
-
var
|
|
5402
|
+
var fs17 = __toESM(require("fs"));
|
|
5195
5403
|
|
|
5196
5404
|
// ../core/src/error-signature.ts
|
|
5197
5405
|
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 +5775,7 @@ function originOf2(url) {
|
|
|
5567
5775
|
return null;
|
|
5568
5776
|
}
|
|
5569
5777
|
}
|
|
5570
|
-
var sleep = (ms) => new Promise((
|
|
5778
|
+
var sleep = (ms) => new Promise((resolve11) => setTimeout(resolve11, Math.max(0, ms)));
|
|
5571
5779
|
async function performRouteAction(route, action, startedAt) {
|
|
5572
5780
|
switch (action.kind) {
|
|
5573
5781
|
case "abort":
|
|
@@ -5691,7 +5899,7 @@ function loadFlakePlan() {
|
|
|
5691
5899
|
if (planCache2 && planCache2.file === file) return planCache2.plan;
|
|
5692
5900
|
let text;
|
|
5693
5901
|
try {
|
|
5694
|
-
text =
|
|
5902
|
+
text = fs17.readFileSync(file, "utf8");
|
|
5695
5903
|
} catch (error) {
|
|
5696
5904
|
throw new Error(`[Piwi Dashboard] Cannot read the flake plan ${file}: ${errorMessage(error)}`);
|
|
5697
5905
|
}
|
|
@@ -5852,7 +6060,7 @@ function recordFlakeResult(line) {
|
|
|
5852
6060
|
const file = process.env[PIWI_FLAKE_ENV.results]?.trim();
|
|
5853
6061
|
if (!file) return;
|
|
5854
6062
|
try {
|
|
5855
|
-
|
|
6063
|
+
fs17.appendFileSync(file, `${JSON.stringify(line)}
|
|
5856
6064
|
`);
|
|
5857
6065
|
} catch {
|
|
5858
6066
|
}
|
|
@@ -5923,11 +6131,11 @@ function injectReporter(reporter, piwiOptions) {
|
|
|
5923
6131
|
}
|
|
5924
6132
|
function resolveSetupModule() {
|
|
5925
6133
|
const candidates = [
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
6134
|
+
path16.join(__dirname, "global-setup-module.js"),
|
|
6135
|
+
path16.join(__dirname, "..", "global-setup-module.js"),
|
|
6136
|
+
path16.join(__dirname, "..", "global-setup-module.ts")
|
|
5929
6137
|
];
|
|
5930
|
-
return candidates.find((candidate) =>
|
|
6138
|
+
return candidates.find((candidate) => fs18.existsSync(candidate)) ?? candidates[0];
|
|
5931
6139
|
}
|
|
5932
6140
|
function wrapConfig(config, piwiOptions) {
|
|
5933
6141
|
if (piwiOptions) applyOptionsToEnv(piwiOptions);
|
|
@@ -6323,8 +6531,8 @@ function buildErrorText(result, base = process.cwd()) {
|
|
|
6323
6531
|
var import_node_crypto2 = require("crypto");
|
|
6324
6532
|
|
|
6325
6533
|
// src/internal/support/ci-output.ts
|
|
6326
|
-
var
|
|
6327
|
-
var
|
|
6534
|
+
var fs19 = __toESM(require("fs"));
|
|
6535
|
+
var path17 = __toESM(require("path"));
|
|
6328
6536
|
var SUMMARY_MAX_FAILURES = 20;
|
|
6329
6537
|
function emitRunOutputs(output, logger, outputFile, env = process.env) {
|
|
6330
6538
|
logger.info(`View run: ${output.runUrl}`);
|
|
@@ -6334,9 +6542,9 @@ function emitRunOutputs(output, logger, outputFile, env = process.env) {
|
|
|
6334
6542
|
}
|
|
6335
6543
|
function writeOutputFile(file, output, logger) {
|
|
6336
6544
|
try {
|
|
6337
|
-
const dir =
|
|
6338
|
-
if (dir && dir !== ".")
|
|
6339
|
-
|
|
6545
|
+
const dir = path17.dirname(file);
|
|
6546
|
+
if (dir && dir !== ".") fs19.mkdirSync(dir, { recursive: true });
|
|
6547
|
+
fs19.writeFileSync(
|
|
6340
6548
|
file,
|
|
6341
6549
|
JSON.stringify(
|
|
6342
6550
|
{
|
|
@@ -6416,7 +6624,7 @@ function emitGitLabDotenv(output, env, logger) {
|
|
|
6416
6624
|
if (output.projectId != null) lines.push(`PIWI_PROJECT_ID=${output.projectId}`);
|
|
6417
6625
|
if (output.ciBuildUrl) lines.push(`PIWI_CI_BUILD_URL=${output.ciBuildUrl}`);
|
|
6418
6626
|
try {
|
|
6419
|
-
|
|
6627
|
+
fs19.writeFileSync(file, lines.join("\n") + "\n");
|
|
6420
6628
|
logger.info(`Wrote GitLab dotenv report to ${file} (declare it as artifacts:reports:dotenv)`);
|
|
6421
6629
|
} catch (error) {
|
|
6422
6630
|
logger.warn(`Failed to write GitLab dotenv file '${file}': ${errorMessage(error)}`);
|
|
@@ -6433,7 +6641,7 @@ function emitResourceSummary(lines, logger, env = process.env) {
|
|
|
6433
6641
|
}
|
|
6434
6642
|
function appendFileLines(file, lines, logger, label) {
|
|
6435
6643
|
try {
|
|
6436
|
-
|
|
6644
|
+
fs19.appendFileSync(file, lines.join("\n") + "\n");
|
|
6437
6645
|
} catch (error) {
|
|
6438
6646
|
logger.warn(`Failed to write GitHub Actions ${label}: ${errorMessage(error)}`);
|
|
6439
6647
|
}
|
|
@@ -6446,8 +6654,8 @@ function ciBuildUrlFromMetadata(metadata) {
|
|
|
6446
6654
|
|
|
6447
6655
|
// src/internal/submit/run-submitter.ts
|
|
6448
6656
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
6449
|
-
function markSubmitFallback(run,
|
|
6450
|
-
run.metadata.ingestHealth = { submitFallback: { path:
|
|
6657
|
+
function markSubmitFallback(run, path34, reason) {
|
|
6658
|
+
run.metadata.ingestHealth = { submitFallback: { path: path34, reason } };
|
|
6451
6659
|
}
|
|
6452
6660
|
var RunSubmitter = class {
|
|
6453
6661
|
/**
|
|
@@ -6708,7 +6916,7 @@ var RunSubmitter = class {
|
|
|
6708
6916
|
*/
|
|
6709
6917
|
stopForMissingAuth(run, payload) {
|
|
6710
6918
|
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
|
|
6919
|
+
`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
6920
|
);
|
|
6713
6921
|
this.logRecoverySaved();
|
|
6714
6922
|
this.saveRecovery(payload);
|
|
@@ -6723,13 +6931,13 @@ var RunSubmitter = class {
|
|
|
6723
6931
|
};
|
|
6724
6932
|
|
|
6725
6933
|
// src/internal/capture/resource-ledger.ts
|
|
6726
|
-
var
|
|
6727
|
-
var
|
|
6934
|
+
var fs21 = __toESM(require("fs"));
|
|
6935
|
+
var path20 = __toESM(require("path"));
|
|
6728
6936
|
var import_node_module2 = require("module");
|
|
6729
6937
|
|
|
6730
6938
|
// src/internal/capture/quiet-capture.ts
|
|
6731
6939
|
var import_node_module = require("module");
|
|
6732
|
-
var
|
|
6940
|
+
var path18 = __toESM(require("path"));
|
|
6733
6941
|
var BOXED_PREFIX_EXPORTS = [
|
|
6734
6942
|
[
|
|
6735
6943
|
"playwright-core/lib/coreBundle",
|
|
@@ -6766,7 +6974,7 @@ function resolveBoxing() {
|
|
|
6766
6974
|
if (typeof set !== "function") continue;
|
|
6767
6975
|
return {
|
|
6768
6976
|
set,
|
|
6769
|
-
prefixes: [
|
|
6977
|
+
prefixes: [path18.dirname(playwrightPackageJson), path18.join(root, "dist") + path18.sep]
|
|
6770
6978
|
};
|
|
6771
6979
|
}
|
|
6772
6980
|
} catch {
|
|
@@ -6798,12 +7006,12 @@ function internalCall(owner, run) {
|
|
|
6798
7006
|
var import_node_perf_hooks = require("perf_hooks");
|
|
6799
7007
|
|
|
6800
7008
|
// src/internal/support/system-readers.ts
|
|
6801
|
-
var
|
|
6802
|
-
var
|
|
7009
|
+
var fs20 = __toESM(require("fs"));
|
|
7010
|
+
var path19 = __toESM(require("path"));
|
|
6803
7011
|
var CLK_TCK = 100;
|
|
6804
7012
|
function readText(file) {
|
|
6805
7013
|
try {
|
|
6806
|
-
return
|
|
7014
|
+
return fs20.readFileSync(file, "utf8");
|
|
6807
7015
|
} catch {
|
|
6808
7016
|
return null;
|
|
6809
7017
|
}
|
|
@@ -6937,45 +7145,45 @@ var ProcFs = class {
|
|
|
6937
7145
|
}
|
|
6938
7146
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
6939
7147
|
available() {
|
|
6940
|
-
return
|
|
7148
|
+
return fs20.existsSync(path19.join(this.root, "stat"));
|
|
6941
7149
|
}
|
|
6942
7150
|
/** Every process, keyed by pid. */
|
|
6943
7151
|
table() {
|
|
6944
7152
|
const out = /* @__PURE__ */ new Map();
|
|
6945
7153
|
let names;
|
|
6946
7154
|
try {
|
|
6947
|
-
names =
|
|
7155
|
+
names = fs20.readdirSync(this.root);
|
|
6948
7156
|
} catch {
|
|
6949
7157
|
return out;
|
|
6950
7158
|
}
|
|
6951
7159
|
for (const name of names) {
|
|
6952
7160
|
if (!/^\d+$/.test(name)) continue;
|
|
6953
|
-
const text = readText(
|
|
7161
|
+
const text = readText(path19.join(this.root, name, "stat"));
|
|
6954
7162
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
6955
7163
|
if (stat) out.set(stat.pid, stat);
|
|
6956
7164
|
}
|
|
6957
7165
|
return out;
|
|
6958
7166
|
}
|
|
6959
7167
|
stat(pid) {
|
|
6960
|
-
const text = readText(
|
|
7168
|
+
const text = readText(path19.join(this.root, String(pid), "stat"));
|
|
6961
7169
|
return text ? parseProcStat(pid, text) : null;
|
|
6962
7170
|
}
|
|
6963
7171
|
runWaitMs(pid) {
|
|
6964
|
-
const text = readText(
|
|
7172
|
+
const text = readText(path19.join(this.root, String(pid), "schedstat"));
|
|
6965
7173
|
return text ? parseSchedstat(text) : null;
|
|
6966
7174
|
}
|
|
6967
7175
|
cmdline(pid) {
|
|
6968
|
-
return (readText(
|
|
7176
|
+
return (readText(path19.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
6969
7177
|
}
|
|
6970
7178
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
6971
7179
|
statusKb(pid, key) {
|
|
6972
|
-
const text = readText(
|
|
7180
|
+
const text = readText(path19.join(this.root, String(pid), "status"));
|
|
6973
7181
|
return text ? parseKbField(text, key) : null;
|
|
6974
7182
|
}
|
|
6975
7183
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
6976
7184
|
async pssKb(pid) {
|
|
6977
7185
|
try {
|
|
6978
|
-
return parseKbField(await
|
|
7186
|
+
return parseKbField(await fs20.promises.readFile(path19.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
6979
7187
|
} catch {
|
|
6980
7188
|
return null;
|
|
6981
7189
|
}
|
|
@@ -6983,29 +7191,29 @@ var ProcFs = class {
|
|
|
6983
7191
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
6984
7192
|
resetPeakRss(pid) {
|
|
6985
7193
|
try {
|
|
6986
|
-
|
|
7194
|
+
fs20.writeFileSync(path19.join(this.root, String(pid), "clear_refs"), "5");
|
|
6987
7195
|
return true;
|
|
6988
7196
|
} catch {
|
|
6989
7197
|
return false;
|
|
6990
7198
|
}
|
|
6991
7199
|
}
|
|
6992
7200
|
cpuTicks() {
|
|
6993
|
-
const text = readText(
|
|
7201
|
+
const text = readText(path19.join(this.root, "stat"));
|
|
6994
7202
|
return text ? parseCpuTicks(text) : null;
|
|
6995
7203
|
}
|
|
6996
7204
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
6997
7205
|
pressureUs(resource) {
|
|
6998
|
-
const text = readText(
|
|
7206
|
+
const text = readText(path19.join(this.root, "pressure", resource));
|
|
6999
7207
|
return text ? parsePsiTotal(text) : null;
|
|
7000
7208
|
}
|
|
7001
7209
|
memAvailableKb() {
|
|
7002
|
-
const text = readText(
|
|
7210
|
+
const text = readText(path19.join(this.root, "meminfo"));
|
|
7003
7211
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
7004
7212
|
}
|
|
7005
7213
|
/** Open file descriptors of a process. */
|
|
7006
7214
|
fdCount(pid) {
|
|
7007
7215
|
try {
|
|
7008
|
-
return
|
|
7216
|
+
return fs20.readdirSync(path19.join(this.root, String(pid), "fd")).length;
|
|
7009
7217
|
} catch {
|
|
7010
7218
|
return null;
|
|
7011
7219
|
}
|
|
@@ -7026,7 +7234,7 @@ var CgroupFs = class {
|
|
|
7026
7234
|
}
|
|
7027
7235
|
/** The directory of a controller (v1) or of the unified hierarchy (v2), when it exists. */
|
|
7028
7236
|
dirs() {
|
|
7029
|
-
const text = readText(
|
|
7237
|
+
const text = readText(path19.join(this.procRoot, String(this.pid), "cgroup"));
|
|
7030
7238
|
if (!text) return null;
|
|
7031
7239
|
const v1 = /* @__PURE__ */ new Map();
|
|
7032
7240
|
let v2 = null;
|
|
@@ -7037,21 +7245,21 @@ var CgroupFs = class {
|
|
|
7037
7245
|
else for (const controller of match[1].split(",")) v1.set(controller, match[2]);
|
|
7038
7246
|
}
|
|
7039
7247
|
const pick = (mount, sub) => {
|
|
7040
|
-
const nested =
|
|
7041
|
-
if (
|
|
7042
|
-
return
|
|
7248
|
+
const nested = path19.join(mount, sub);
|
|
7249
|
+
if (fs20.existsSync(nested)) return nested;
|
|
7250
|
+
return fs20.existsSync(mount) ? mount : null;
|
|
7043
7251
|
};
|
|
7044
7252
|
if (v1.has("memory") || v1.has("cpu")) {
|
|
7045
7253
|
const memoryPath = v1.get("memory");
|
|
7046
7254
|
const cpuPath = v1.get("cpu");
|
|
7047
|
-
const cpuMount = ["cpu,cpuacct", "cpu"].map((name) =>
|
|
7255
|
+
const cpuMount = ["cpu,cpuacct", "cpu"].map((name) => path19.join(this.cgroupRoot, name)).find((dir) => fs20.existsSync(dir));
|
|
7048
7256
|
return {
|
|
7049
7257
|
version: 1,
|
|
7050
|
-
memory: memoryPath !== void 0 ? pick(
|
|
7258
|
+
memory: memoryPath !== void 0 ? pick(path19.join(this.cgroupRoot, "memory"), memoryPath) : null,
|
|
7051
7259
|
cpu: cpuPath !== void 0 && cpuMount ? pick(cpuMount, cpuPath) : null
|
|
7052
7260
|
};
|
|
7053
7261
|
}
|
|
7054
|
-
if (v2 !== null &&
|
|
7262
|
+
if (v2 !== null && fs20.existsSync(path19.join(this.cgroupRoot, "cgroup.controllers"))) {
|
|
7055
7263
|
const dir = pick(this.cgroupRoot, v2);
|
|
7056
7264
|
return { version: 2, memory: dir, cpu: dir };
|
|
7057
7265
|
}
|
|
@@ -7060,7 +7268,7 @@ var CgroupFs = class {
|
|
|
7060
7268
|
read() {
|
|
7061
7269
|
const dirs = this.dirs();
|
|
7062
7270
|
if (!dirs) return null;
|
|
7063
|
-
const file = (dir, name) => dir ? readText(
|
|
7271
|
+
const file = (dir, name) => dir ? readText(path19.join(dir, name)) : null;
|
|
7064
7272
|
if (dirs.version === 2) {
|
|
7065
7273
|
const max = file(dirs.memory, "memory.max");
|
|
7066
7274
|
const cpuMax = file(dirs.cpu, "cpu.max")?.trim().split(/\s+/);
|
|
@@ -7246,10 +7454,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
7246
7454
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
7247
7455
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
7248
7456
|
function relativePath(file) {
|
|
7249
|
-
return
|
|
7457
|
+
return path20.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path20.sep).join("/");
|
|
7250
7458
|
}
|
|
7251
7459
|
function isLibraryFile(file) {
|
|
7252
|
-
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${
|
|
7460
|
+
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path20.sep}node_modules${path20.sep}`) || file.includes("/node_modules/") || file === __filename;
|
|
7253
7461
|
}
|
|
7254
7462
|
function userSite(stack) {
|
|
7255
7463
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -7297,7 +7505,7 @@ function playwrightTestInfoSource() {
|
|
|
7297
7505
|
try {
|
|
7298
7506
|
const packageJson = resolvePlaywrightPackageJson();
|
|
7299
7507
|
if (!packageJson) return () => null;
|
|
7300
|
-
const globals = (0, import_node_module2.createRequire)(packageJson)(
|
|
7508
|
+
const globals = (0, import_node_module2.createRequire)(packageJson)(path20.join(path20.dirname(packageJson), "lib", "globals.js"));
|
|
7301
7509
|
const read = globals.currentTestInfo;
|
|
7302
7510
|
if (typeof read !== "function") return () => null;
|
|
7303
7511
|
return () => {
|
|
@@ -7326,15 +7534,15 @@ function countHandles() {
|
|
|
7326
7534
|
async function settledHandles(start) {
|
|
7327
7535
|
const end = countHandles();
|
|
7328
7536
|
if (!Object.entries(end).some(([type, n]) => n > (start[type] ?? 0))) return end;
|
|
7329
|
-
await new Promise((
|
|
7537
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7330
7538
|
return countHandles();
|
|
7331
7539
|
}
|
|
7332
7540
|
function settleWithin(promise, ms) {
|
|
7333
7541
|
let timer;
|
|
7334
7542
|
return Promise.race([
|
|
7335
7543
|
promise.catch(() => void 0),
|
|
7336
|
-
new Promise((
|
|
7337
|
-
timer = setTimeout(
|
|
7544
|
+
new Promise((resolve11) => {
|
|
7545
|
+
timer = setTimeout(resolve11, ms);
|
|
7338
7546
|
})
|
|
7339
7547
|
]).finally(() => clearTimeout(timer));
|
|
7340
7548
|
}
|
|
@@ -7463,7 +7671,7 @@ var ResourceLedger = class {
|
|
|
7463
7671
|
const census = this.census(null);
|
|
7464
7672
|
if (this.resultsFile) {
|
|
7465
7673
|
try {
|
|
7466
|
-
|
|
7674
|
+
fs21.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
|
|
7467
7675
|
`);
|
|
7468
7676
|
} catch {
|
|
7469
7677
|
}
|
|
@@ -7786,7 +7994,7 @@ async function recordResourceCensus(testInfo) {
|
|
|
7786
7994
|
}
|
|
7787
7995
|
|
|
7788
7996
|
// src/internal/collect/resource-verdicts.ts
|
|
7789
|
-
var
|
|
7997
|
+
var path21 = __toESM(require("path"));
|
|
7790
7998
|
var OPEN_STEP_TITLES = {
|
|
7791
7999
|
"Create context": "context",
|
|
7792
8000
|
"Launch persistent context": "context",
|
|
@@ -7818,7 +8026,7 @@ function userFixturesOf(steps) {
|
|
|
7818
8026
|
const walk = (list) => {
|
|
7819
8027
|
for (const step of list) {
|
|
7820
8028
|
const file = step.location?.file;
|
|
7821
|
-
if (step.category === "fixture" && file && !file.includes(`${
|
|
8029
|
+
if (step.category === "fixture" && file && !file.includes(`${path21.sep}node_modules${path21.sep}`)) {
|
|
7822
8030
|
const name = step.title.replace(/^(?:Fixture\s+|fixture:\s*)/, "").replace(/^"(.*)"$/, "$1");
|
|
7823
8031
|
if (!name.startsWith("piwi")) out.push(`${name} (${relativeFile(file)}:${step.location.line})`);
|
|
7824
8032
|
}
|
|
@@ -7851,7 +8059,7 @@ function tallyLifecycleSteps(file, steps) {
|
|
|
7851
8059
|
return tally;
|
|
7852
8060
|
}
|
|
7853
8061
|
function relativeFile(file) {
|
|
7854
|
-
return
|
|
8062
|
+
return path21.relative(process.cwd(), file).split(path21.sep).join("/");
|
|
7855
8063
|
}
|
|
7856
8064
|
function buildResourceReport(input) {
|
|
7857
8065
|
const findings = [];
|
|
@@ -8209,9 +8417,9 @@ function formatResourceSummary(report, leakCheck, maxLines = 10) {
|
|
|
8209
8417
|
}
|
|
8210
8418
|
|
|
8211
8419
|
// src/internal/collect/process-sampler.ts
|
|
8212
|
-
var
|
|
8420
|
+
var fs22 = __toESM(require("fs"));
|
|
8213
8421
|
var os8 = __toESM(require("os"));
|
|
8214
|
-
var
|
|
8422
|
+
var path22 = __toESM(require("path"));
|
|
8215
8423
|
var import_node_child_process2 = require("child_process");
|
|
8216
8424
|
var WALK_FILE_CAP = 1e4;
|
|
8217
8425
|
var SERIES_CAP = 3600;
|
|
@@ -8251,17 +8459,17 @@ function nodeCpuTicks() {
|
|
|
8251
8459
|
return { busy, idle, iowait: 0, steal: 0, total: busy + idle };
|
|
8252
8460
|
}
|
|
8253
8461
|
function defaultPs() {
|
|
8254
|
-
return new Promise((
|
|
8462
|
+
return new Promise((resolve11, reject) => {
|
|
8255
8463
|
(0, import_node_child_process2.execFile)(
|
|
8256
8464
|
"ps",
|
|
8257
8465
|
["-A", "-o", "pid=,ppid=,rss=,time=,command="],
|
|
8258
8466
|
{ timeout: 2e3, maxBuffer: 8 * 1024 * 1024 },
|
|
8259
|
-
(error, stdout) => error ? reject(error) :
|
|
8467
|
+
(error, stdout) => error ? reject(error) : resolve11(stdout)
|
|
8260
8468
|
);
|
|
8261
8469
|
});
|
|
8262
8470
|
}
|
|
8263
8471
|
function defaultWindowsQuery() {
|
|
8264
|
-
return new Promise((
|
|
8472
|
+
return new Promise((resolve11, reject) => {
|
|
8265
8473
|
(0, import_node_child_process2.execFile)(
|
|
8266
8474
|
"powershell.exe",
|
|
8267
8475
|
[
|
|
@@ -8271,18 +8479,18 @@ function defaultWindowsQuery() {
|
|
|
8271
8479
|
"[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
8480
|
],
|
|
8273
8481
|
{ timeout: 1e4, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
|
|
8274
|
-
(error, stdout) => error ? reject(error) :
|
|
8482
|
+
(error, stdout) => error ? reject(error) : resolve11(stdout)
|
|
8275
8483
|
);
|
|
8276
8484
|
});
|
|
8277
8485
|
}
|
|
8278
8486
|
function freeBytes(dir) {
|
|
8279
|
-
let current =
|
|
8487
|
+
let current = path22.resolve(dir);
|
|
8280
8488
|
for (; ; ) {
|
|
8281
8489
|
try {
|
|
8282
|
-
const stats =
|
|
8490
|
+
const stats = fs22.statfsSync(current);
|
|
8283
8491
|
return stats.bavail * stats.bsize;
|
|
8284
8492
|
} catch {
|
|
8285
|
-
const parent =
|
|
8493
|
+
const parent = path22.dirname(current);
|
|
8286
8494
|
if (parent === current) return null;
|
|
8287
8495
|
current = parent;
|
|
8288
8496
|
}
|
|
@@ -8292,24 +8500,30 @@ async function dirBytes(dir, budget) {
|
|
|
8292
8500
|
let bytes = 0;
|
|
8293
8501
|
let entries;
|
|
8294
8502
|
try {
|
|
8295
|
-
entries = await
|
|
8503
|
+
entries = await fs22.promises.readdir(dir, { withFileTypes: true });
|
|
8296
8504
|
} catch {
|
|
8297
8505
|
return 0;
|
|
8298
8506
|
}
|
|
8299
8507
|
for (const entry of entries) {
|
|
8300
8508
|
if (budget.files <= 0) return bytes;
|
|
8301
|
-
const full =
|
|
8509
|
+
const full = path22.join(dir, entry.name);
|
|
8302
8510
|
if (entry.isDirectory()) bytes += await dirBytes(full, budget);
|
|
8303
8511
|
else if (entry.isFile()) {
|
|
8304
8512
|
budget.files--;
|
|
8305
8513
|
try {
|
|
8306
|
-
bytes += (await
|
|
8514
|
+
bytes += (await fs22.promises.lstat(full)).size;
|
|
8307
8515
|
} catch {
|
|
8308
8516
|
}
|
|
8309
8517
|
}
|
|
8310
8518
|
}
|
|
8311
8519
|
return bytes;
|
|
8312
8520
|
}
|
|
8521
|
+
function memoryOf(container) {
|
|
8522
|
+
const totalMemory = os8.totalmem();
|
|
8523
|
+
const constrained = typeof process.constrainedMemory === "function" ? process.constrainedMemory() : void 0;
|
|
8524
|
+
const limit = container?.memoryLimitBytes ?? (constrained && constrained > 0 ? constrained : null);
|
|
8525
|
+
return { memoryBytes: totalMemory, memoryLimitBytes: limit !== null && limit < totalMemory ? limit : null };
|
|
8526
|
+
}
|
|
8313
8527
|
var RunSampler = class {
|
|
8314
8528
|
constructor(options = {}) {
|
|
8315
8529
|
this.timer = null;
|
|
@@ -8343,7 +8557,7 @@ var RunSampler = class {
|
|
|
8343
8557
|
this.platform = options.platform ?? process.platform;
|
|
8344
8558
|
this.proc = new ProcFs(options.procRoot);
|
|
8345
8559
|
this.cgroup = new CgroupFs(options.procRoot, options.cgroupRoot);
|
|
8346
|
-
this.outputDirs = [...new Set((options.outputDirs ?? []).map((dir) =>
|
|
8560
|
+
this.outputDirs = [...new Set((options.outputDirs ?? []).map((dir) => path22.resolve(dir)))];
|
|
8347
8561
|
this.tmpDir = options.tmpDir ?? os8.tmpdir();
|
|
8348
8562
|
this.intervalMs = options.intervalMs ?? 1e3;
|
|
8349
8563
|
this.heavyEvery = Math.max(1, options.heavyEvery ?? 5);
|
|
@@ -8378,6 +8592,18 @@ var RunSampler = class {
|
|
|
8378
8592
|
}
|
|
8379
8593
|
return this.profile(await this.leftoverBytes());
|
|
8380
8594
|
}
|
|
8595
|
+
/**
|
|
8596
|
+
* What a live run's memory is drawn against: the machine's memory, or its
|
|
8597
|
+
* container's limit when lower, read once, and how the run's memory is
|
|
8598
|
+
* measured so far.
|
|
8599
|
+
*/
|
|
8600
|
+
liveMemory() {
|
|
8601
|
+
if (this.capacityBytes === void 0) {
|
|
8602
|
+
const machine = memoryOf(this.linux ? this.cgroup.read() : null);
|
|
8603
|
+
this.capacityBytes = machine.memoryLimitBytes ?? machine.memoryBytes;
|
|
8604
|
+
}
|
|
8605
|
+
return { capacityBytes: this.capacityBytes || null, kind: this.memoryKind };
|
|
8606
|
+
}
|
|
8381
8607
|
/** The series read so far, each point at the time it was read. */
|
|
8382
8608
|
timedSeries() {
|
|
8383
8609
|
return {
|
|
@@ -8545,7 +8771,7 @@ var RunSampler = class {
|
|
|
8545
8771
|
} catch {
|
|
8546
8772
|
return;
|
|
8547
8773
|
}
|
|
8548
|
-
this.noteTree(list, (proc) => roleOf(
|
|
8774
|
+
this.noteTree(list, (proc) => roleOf(path22.basename(proc.command.split(" ")[0] ?? ""), proc.command), at);
|
|
8549
8775
|
}
|
|
8550
8776
|
async sampleWindowsTree(at) {
|
|
8551
8777
|
let list;
|
|
@@ -8601,7 +8827,7 @@ var RunSampler = class {
|
|
|
8601
8827
|
let bytes = 0;
|
|
8602
8828
|
for (const dir of this.outputDirs) bytes += await dirBytes(dir, budget);
|
|
8603
8829
|
for (const name of await this.playwrightTempEntries((mtimeMs) => mtimeMs >= this.startedAt - 1e3)) {
|
|
8604
|
-
bytes += await dirBytes(
|
|
8830
|
+
bytes += await dirBytes(path22.join(this.tmpDir, name), budget);
|
|
8605
8831
|
}
|
|
8606
8832
|
if (budget.files <= 0) this.peakInUseIsLowerBound = true;
|
|
8607
8833
|
this.peakInUse = Math.max(this.peakInUse ?? 0, bytes);
|
|
@@ -8610,7 +8836,7 @@ var RunSampler = class {
|
|
|
8610
8836
|
async playwrightTempEntries(keep) {
|
|
8611
8837
|
let names;
|
|
8612
8838
|
try {
|
|
8613
|
-
names = await
|
|
8839
|
+
names = await fs22.promises.readdir(this.tmpDir);
|
|
8614
8840
|
} catch {
|
|
8615
8841
|
return [];
|
|
8616
8842
|
}
|
|
@@ -8618,7 +8844,7 @@ var RunSampler = class {
|
|
|
8618
8844
|
for (const name of names) {
|
|
8619
8845
|
if (!PLAYWRIGHT_TEMP.test(name)) continue;
|
|
8620
8846
|
try {
|
|
8621
|
-
if (keep((await
|
|
8847
|
+
if (keep((await fs22.promises.stat(path22.join(this.tmpDir, name))).mtimeMs)) out.push(name);
|
|
8622
8848
|
} catch {
|
|
8623
8849
|
}
|
|
8624
8850
|
}
|
|
@@ -8629,7 +8855,7 @@ var RunSampler = class {
|
|
|
8629
8855
|
const budget = { files: WALK_FILE_CAP };
|
|
8630
8856
|
let bytes = 0;
|
|
8631
8857
|
for (const name of await this.playwrightTempEntries((mtimeMs) => mtimeMs < this.startedAt - 1e3)) {
|
|
8632
|
-
bytes += await dirBytes(
|
|
8858
|
+
bytes += await dirBytes(path22.join(this.tmpDir, name), budget);
|
|
8633
8859
|
}
|
|
8634
8860
|
return bytes;
|
|
8635
8861
|
} catch {
|
|
@@ -8674,9 +8900,6 @@ var RunSampler = class {
|
|
|
8674
8900
|
if (cpuPressure === null) notMeasured.push("CPU pressure");
|
|
8675
8901
|
const container = this.linux ? this.cgroup.read() : null;
|
|
8676
8902
|
const startContainer = this.containerStart;
|
|
8677
|
-
const totalMemory = os8.totalmem();
|
|
8678
|
-
const constrained = typeof process.constrainedMemory === "function" ? process.constrainedMemory() : void 0;
|
|
8679
|
-
const limit = container?.memoryLimitBytes ?? (constrained && constrained > 0 ? constrained : null);
|
|
8680
8903
|
const raisedPeak = container?.memoryPeakBytes != null && startContainer?.memoryPeakBytes != null && container.memoryPeakBytes > startContainer.memoryPeakBytes ? container.memoryPeakBytes : null;
|
|
8681
8904
|
const delta = (to, from) => to != null && from != null ? Math.max(0, to - from) : null;
|
|
8682
8905
|
return {
|
|
@@ -8684,8 +8907,7 @@ var RunSampler = class {
|
|
|
8684
8907
|
wallMs,
|
|
8685
8908
|
machine: {
|
|
8686
8909
|
cores: typeof os8.availableParallelism === "function" ? os8.availableParallelism() : os8.cpus().length,
|
|
8687
|
-
|
|
8688
|
-
memoryLimitBytes: limit !== null && limit < totalMemory ? limit : null,
|
|
8910
|
+
...memoryOf(container),
|
|
8689
8911
|
cpuQuotaCores: container?.cpuQuotaCores ?? null
|
|
8690
8912
|
},
|
|
8691
8913
|
cpu: {
|
|
@@ -8957,7 +9179,7 @@ function openPagesByWorker(censuses) {
|
|
|
8957
9179
|
}
|
|
8958
9180
|
return out;
|
|
8959
9181
|
}
|
|
8960
|
-
function pagesOverTime(censuses) {
|
|
9182
|
+
function pagesOverTime(censuses, live = false) {
|
|
8961
9183
|
const byWorker = /* @__PURE__ */ new Map();
|
|
8962
9184
|
for (const census of censuses) {
|
|
8963
9185
|
const list = byWorker.get(census.worker) ?? [];
|
|
@@ -8990,7 +9212,7 @@ function pagesOverTime(censuses) {
|
|
|
8990
9212
|
if (last && last[0] === at) last[1] = open;
|
|
8991
9213
|
else events.push([at, open]);
|
|
8992
9214
|
}
|
|
8993
|
-
if (open > 0 && lastAt >= events[events.length - 1][0]) events.push([lastAt, 0]);
|
|
9215
|
+
if (!live && open > 0 && lastAt >= events[events.length - 1][0]) events.push([lastAt, 0]);
|
|
8994
9216
|
out.push({ worker, events });
|
|
8995
9217
|
}
|
|
8996
9218
|
return out;
|
|
@@ -9059,6 +9281,144 @@ function resourceReportWire(input) {
|
|
|
9059
9281
|
};
|
|
9060
9282
|
}
|
|
9061
9283
|
|
|
9284
|
+
// src/internal/streaming/live-resources.ts
|
|
9285
|
+
var LIVE_INTERVAL_MS = 5e3;
|
|
9286
|
+
function since(points, cursor, cap, merge) {
|
|
9287
|
+
const fresh = cursor === void 0 ? points : points.filter(([at]) => at > cursor);
|
|
9288
|
+
return downsamplePoints(fresh, cap, merge);
|
|
9289
|
+
}
|
|
9290
|
+
var LiveResources = class {
|
|
9291
|
+
constructor(stream, httpClient, options, logger = new Logger()) {
|
|
9292
|
+
this.stream = stream;
|
|
9293
|
+
this.httpClient = httpClient;
|
|
9294
|
+
this.options = options;
|
|
9295
|
+
this.logger = logger;
|
|
9296
|
+
this.timer = null;
|
|
9297
|
+
this.inFlight = null;
|
|
9298
|
+
this.stopped = false;
|
|
9299
|
+
/** Whether the next post must carry every point: the first one, and after the dashboard lost them. */
|
|
9300
|
+
this.reset = true;
|
|
9301
|
+
/** The time of the last point the dashboard took, per series. */
|
|
9302
|
+
this.sent = /* @__PURE__ */ new Map();
|
|
9303
|
+
/** How many censuses the open pages last posted came from. */
|
|
9304
|
+
this.censusCount = 0;
|
|
9305
|
+
}
|
|
9306
|
+
/** Post on a timer that never keeps the process alive. */
|
|
9307
|
+
start() {
|
|
9308
|
+
if (this.timer || this.stopped) return;
|
|
9309
|
+
this.timer = setInterval(() => this.tick(), this.options.intervalMs ?? LIVE_INTERVAL_MS);
|
|
9310
|
+
this.timer.unref?.();
|
|
9311
|
+
}
|
|
9312
|
+
/** Post nothing more, and wait for a post in flight. */
|
|
9313
|
+
async stop() {
|
|
9314
|
+
this.stopped = true;
|
|
9315
|
+
if (this.timer) clearInterval(this.timer);
|
|
9316
|
+
this.timer = null;
|
|
9317
|
+
await this.inFlight;
|
|
9318
|
+
}
|
|
9319
|
+
/** Post now, when the run is watched and no post is in flight: the dashboard says someone just opened it. */
|
|
9320
|
+
tick() {
|
|
9321
|
+
if (this.stopped || this.inFlight) return null;
|
|
9322
|
+
const { enabled, runId, token } = this.stream;
|
|
9323
|
+
if (!enabled || runId === null || !token || this.stream.isWatched !== true) return null;
|
|
9324
|
+
let draft;
|
|
9325
|
+
try {
|
|
9326
|
+
draft = this.draft();
|
|
9327
|
+
} catch (error) {
|
|
9328
|
+
this.logger.debug(`Live resources not read: ${errorMessage(error)}`);
|
|
9329
|
+
return null;
|
|
9330
|
+
}
|
|
9331
|
+
if (!draft) return null;
|
|
9332
|
+
this.inFlight = this.send(runId, token, draft).finally(() => {
|
|
9333
|
+
this.inFlight = null;
|
|
9334
|
+
});
|
|
9335
|
+
return this.inFlight;
|
|
9336
|
+
}
|
|
9337
|
+
/** What is new since the last post the dashboard took; null when nothing is. */
|
|
9338
|
+
draft() {
|
|
9339
|
+
const samples = this.options.sampler?.timedSeries() ?? null;
|
|
9340
|
+
const startedAt = samples?.startedAt || this.options.runStartedAt;
|
|
9341
|
+
const censuses = this.options.censuses();
|
|
9342
|
+
const cursors = this.reset ? /* @__PURE__ */ new Map() : this.sent;
|
|
9343
|
+
const sent = new Map(cursors);
|
|
9344
|
+
let fresh = false;
|
|
9345
|
+
const take = (key, points, cap, merge) => {
|
|
9346
|
+
const out = since(points, cursors.get(key), cap, merge);
|
|
9347
|
+
const last = points[points.length - 1];
|
|
9348
|
+
if (out.length > 0 && last) {
|
|
9349
|
+
sent.set(key, last[0]);
|
|
9350
|
+
fresh = true;
|
|
9351
|
+
}
|
|
9352
|
+
return out;
|
|
9353
|
+
};
|
|
9354
|
+
const timeline = { startedAt, cpuPct: [], memoryBytes: [], pages: [], workers: [] };
|
|
9355
|
+
if (samples) {
|
|
9356
|
+
timeline.cpuPct = take("cpu", samples.cpuPct, TIMELINE_SERIES_CAP, "mean");
|
|
9357
|
+
timeline.memoryBytes = take("memory", samples.memoryBytes, TIMELINE_SERIES_CAP, "max");
|
|
9358
|
+
for (const tree of workerTreesByIndex(samples, censuses)) {
|
|
9359
|
+
const cpuCores = take(`cores:${tree.worker}`, tree.cpuCores, TIMELINE_WORKER_POINTS_CAP, "mean");
|
|
9360
|
+
const memoryBytes = take(`bytes:${tree.worker}`, tree.memoryBytes, TIMELINE_WORKER_POINTS_CAP, "max");
|
|
9361
|
+
if (cpuCores.length > 0 || memoryBytes.length > 0) {
|
|
9362
|
+
timeline.workers.push({ worker: tree.worker, cpuCores, memoryBytes });
|
|
9363
|
+
}
|
|
9364
|
+
}
|
|
9365
|
+
}
|
|
9366
|
+
if (this.reset || censuses.length !== this.censusCount) {
|
|
9367
|
+
for (const { worker, events } of pagesOverTime(censuses, true)) {
|
|
9368
|
+
const points = events.map(([at, open]) => [at - startedAt, open]);
|
|
9369
|
+
const last = points.pop();
|
|
9370
|
+
timeline.pages.push({
|
|
9371
|
+
worker,
|
|
9372
|
+
points: [...downsamplePoints(points, TIMELINE_WORKER_POINTS_CAP - 1, "max"), last]
|
|
9373
|
+
});
|
|
9374
|
+
}
|
|
9375
|
+
fresh ||= timeline.pages.length > 0;
|
|
9376
|
+
}
|
|
9377
|
+
if (!fresh) return null;
|
|
9378
|
+
const memory = this.options.sampler?.liveMemory() ?? { capacityBytes: null, kind: null };
|
|
9379
|
+
return {
|
|
9380
|
+
post: {
|
|
9381
|
+
shardIndex: this.options.shardIndex,
|
|
9382
|
+
reset: this.reset,
|
|
9383
|
+
timeline,
|
|
9384
|
+
memoryCapacityBytes: memory.capacityBytes,
|
|
9385
|
+
memoryKind: memory.kind
|
|
9386
|
+
},
|
|
9387
|
+
sent,
|
|
9388
|
+
censusCount: censuses.length
|
|
9389
|
+
};
|
|
9390
|
+
}
|
|
9391
|
+
async send(runId, token, draft) {
|
|
9392
|
+
try {
|
|
9393
|
+
const answer = await this.httpClient.postJSON(
|
|
9394
|
+
`/api/test-runs/${runId}/live-resources`,
|
|
9395
|
+
{ streamToken: token, ...draft.post },
|
|
9396
|
+
this.stream.auth
|
|
9397
|
+
);
|
|
9398
|
+
if (answer?.declined) {
|
|
9399
|
+
this.logger.debug("The project declined resources: live resources are not sent.");
|
|
9400
|
+
this.stopped = true;
|
|
9401
|
+
return;
|
|
9402
|
+
}
|
|
9403
|
+
this.stream.noteWatched(answer);
|
|
9404
|
+
if (answer?.resync) {
|
|
9405
|
+
this.reset = true;
|
|
9406
|
+
return;
|
|
9407
|
+
}
|
|
9408
|
+
this.sent = draft.sent;
|
|
9409
|
+
this.censusCount = draft.censusCount;
|
|
9410
|
+
this.reset = false;
|
|
9411
|
+
} catch (error) {
|
|
9412
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
9413
|
+
this.logger.debug("The dashboard takes no live resources.");
|
|
9414
|
+
this.stopped = true;
|
|
9415
|
+
return;
|
|
9416
|
+
}
|
|
9417
|
+
this.logger.debug(`Live resources not sent: ${errorMessage(error)}`);
|
|
9418
|
+
}
|
|
9419
|
+
}
|
|
9420
|
+
};
|
|
9421
|
+
|
|
9062
9422
|
// ../core/src/describe-failure.ts
|
|
9063
9423
|
var HEADLINE_MAX_CHARS = 120;
|
|
9064
9424
|
var VALUE_MAX_CHARS = 40;
|
|
@@ -9588,10 +9948,14 @@ function isServerGranularity(value) {
|
|
|
9588
9948
|
return typeof value === "string" && SERVER_VALUES.has(value);
|
|
9589
9949
|
}
|
|
9590
9950
|
function coverageSettings(options) {
|
|
9591
|
-
if (options.collectPerformanceMetrics === false) return { browser: "off", server: "off" };
|
|
9951
|
+
if (options.collectPerformanceMetrics === false) return { browser: "off", server: "off", css: "off" };
|
|
9592
9952
|
const browser = options.coverage?.browser === "functions" ? "functions" : options.coverage?.browser === "files" || options.captureCodeReach === true ? "files" : "off";
|
|
9593
9953
|
const server = isServerGranularity(options.coverage?.server) ? options.coverage.server : "off";
|
|
9594
|
-
|
|
9954
|
+
const css = options.coverage?.css === "rules" ? "rules" : "off";
|
|
9955
|
+
return { browser, server, css };
|
|
9956
|
+
}
|
|
9957
|
+
function cssCoverageInWorker() {
|
|
9958
|
+
return process.env[PIWI_ENV_KEYS.coverageCss] === "rules";
|
|
9595
9959
|
}
|
|
9596
9960
|
function browserCoverageInWorker() {
|
|
9597
9961
|
const browser = process.env[PIWI_ENV_KEYS.coverageBrowser];
|
|
@@ -9603,7 +9967,7 @@ function browserFunctionsInWorker() {
|
|
|
9603
9967
|
function browserFunctionCoverage(files) {
|
|
9604
9968
|
return sanitizeCoverage({
|
|
9605
9969
|
v: 1,
|
|
9606
|
-
files: [...files].map(([
|
|
9970
|
+
files: [...files].map(([path34, functions]) => ({ path: path34, runtime: "browser", functions }))
|
|
9607
9971
|
});
|
|
9608
9972
|
}
|
|
9609
9973
|
function newKnownFunctions(known, sent) {
|
|
@@ -9616,19 +9980,19 @@ function newKnownFunctions(known, sent) {
|
|
|
9616
9980
|
budget = MAX_COVERAGE_RANGES;
|
|
9617
9981
|
};
|
|
9618
9982
|
for (const [raw, ranges] of known) {
|
|
9619
|
-
const
|
|
9620
|
-
if (
|
|
9621
|
-
let named = sent.get(
|
|
9983
|
+
const path34 = normalizeCoveragePath(raw);
|
|
9984
|
+
if (path34 === null) continue;
|
|
9985
|
+
let named = sent.get(path34);
|
|
9622
9986
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
9623
9987
|
if (fresh.length === 0) continue;
|
|
9624
|
-
if (!named) sent.set(
|
|
9988
|
+
if (!named) sent.set(path34, named = /* @__PURE__ */ new Set());
|
|
9625
9989
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
9626
9990
|
while (fresh.length > 0) {
|
|
9627
9991
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
9628
9992
|
const part = fresh.slice(0, budget);
|
|
9629
9993
|
fresh = fresh.slice(part.length);
|
|
9630
9994
|
budget -= part.length;
|
|
9631
|
-
files.push({ path:
|
|
9995
|
+
files.push({ path: path34, allFunctions: part });
|
|
9632
9996
|
}
|
|
9633
9997
|
}
|
|
9634
9998
|
close();
|
|
@@ -9640,8 +10004,8 @@ function serverCoverageInWorker() {
|
|
|
9640
10004
|
|
|
9641
10005
|
// src/internal/capture/code-reach.ts
|
|
9642
10006
|
var crypto5 = __toESM(require("crypto"));
|
|
9643
|
-
var
|
|
9644
|
-
var
|
|
10007
|
+
var fs23 = __toESM(require("fs"));
|
|
10008
|
+
var path23 = __toESM(require("path"));
|
|
9645
10009
|
var import_node_child_process3 = require("child_process");
|
|
9646
10010
|
|
|
9647
10011
|
// ../core/src/source-map.ts
|
|
@@ -9711,10 +10075,10 @@ function joinRoot(root, source) {
|
|
|
9711
10075
|
function decodeLines(raw) {
|
|
9712
10076
|
const sources = [];
|
|
9713
10077
|
const decoded = [];
|
|
9714
|
-
const
|
|
10078
|
+
const add2 = (map, lineOffset, columnOffset) => {
|
|
9715
10079
|
if (map.sections) {
|
|
9716
10080
|
for (const section of map.sections) {
|
|
9717
|
-
if (!
|
|
10081
|
+
if (!add2(section.map, section.offset.line, section.offset.column)) return false;
|
|
9718
10082
|
}
|
|
9719
10083
|
return true;
|
|
9720
10084
|
}
|
|
@@ -9735,7 +10099,7 @@ function decodeLines(raw) {
|
|
|
9735
10099
|
});
|
|
9736
10100
|
return true;
|
|
9737
10101
|
};
|
|
9738
|
-
return
|
|
10102
|
+
return add2(raw, 0, 0) ? { sources, lines: decoded } : null;
|
|
9739
10103
|
}
|
|
9740
10104
|
function compactSegments(lines) {
|
|
9741
10105
|
let total = 0;
|
|
@@ -9911,7 +10275,7 @@ function functionLines(entry, map, text, all) {
|
|
|
9911
10275
|
const read = textOf(entry, text);
|
|
9912
10276
|
const ranges = executedFunctionRanges(entry, read?.length, all);
|
|
9913
10277
|
if (!ranges.length || !read) return out;
|
|
9914
|
-
const
|
|
10278
|
+
const add2 = (source, range) => {
|
|
9915
10279
|
const list = out.get(source);
|
|
9916
10280
|
if (list) list.push(range);
|
|
9917
10281
|
else out.set(source, [range]);
|
|
@@ -9921,7 +10285,7 @@ function functionLines(entry, map, text, all) {
|
|
|
9921
10285
|
const start = offsetToPosition(starts, range.start);
|
|
9922
10286
|
if (!map) {
|
|
9923
10287
|
const last = offsetToPosition(starts, Math.max(range.start, range.end - 1));
|
|
9924
|
-
|
|
10288
|
+
add2(-1, [start.line + 1, last.line + 1]);
|
|
9925
10289
|
continue;
|
|
9926
10290
|
}
|
|
9927
10291
|
const at = map.originalAt(start.line, start.column);
|
|
@@ -9931,7 +10295,7 @@ function functionLines(entry, map, text, all) {
|
|
|
9931
10295
|
if (first === null) continue;
|
|
9932
10296
|
const header = first === at.line || first === at.line + 1 ? at.line : first;
|
|
9933
10297
|
const lines = map.originalLinesBetween(at.source, start, end, header);
|
|
9934
|
-
if (lines)
|
|
10298
|
+
if (lines) add2(at.source, [header + 1, lines[1] + 1]);
|
|
9935
10299
|
}
|
|
9936
10300
|
return out;
|
|
9937
10301
|
}
|
|
@@ -9986,18 +10350,18 @@ function repositoryRoot(from) {
|
|
|
9986
10350
|
}).trim();
|
|
9987
10351
|
} catch {
|
|
9988
10352
|
}
|
|
9989
|
-
cachedRepoRoot = { from, root:
|
|
10353
|
+
cachedRepoRoot = { from, root: path23.resolve(root) };
|
|
9990
10354
|
return cachedRepoRoot.root;
|
|
9991
10355
|
}
|
|
9992
10356
|
function codeReachRoots(configDir, configured) {
|
|
9993
10357
|
const repoRoot = repositoryRoot(configDir);
|
|
9994
|
-
const
|
|
9995
|
-
const global = Array.isArray(configured) ?
|
|
10358
|
+
const resolve11 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path23.resolve(configDir, r));
|
|
10359
|
+
const global = Array.isArray(configured) ? resolve11(configured) : resolve11(configured?.["*"]);
|
|
9996
10360
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
9997
10361
|
if (configured && !Array.isArray(configured)) {
|
|
9998
10362
|
for (const [key, dirs] of Object.entries(configured)) {
|
|
9999
10363
|
const origin = key === "*" ? null : originOf3(key);
|
|
10000
|
-
const resolved =
|
|
10364
|
+
const resolved = resolve11(dirs);
|
|
10001
10365
|
if (origin && resolved.length) byOrigin.set(origin, resolved);
|
|
10002
10366
|
}
|
|
10003
10367
|
}
|
|
@@ -10020,7 +10384,7 @@ function scriptRoots(roots, scriptUrl, pageUrl) {
|
|
|
10020
10384
|
}
|
|
10021
10385
|
function isFile(file) {
|
|
10022
10386
|
try {
|
|
10023
|
-
return
|
|
10387
|
+
return fs23.statSync(file).isFile();
|
|
10024
10388
|
} catch {
|
|
10025
10389
|
return false;
|
|
10026
10390
|
}
|
|
@@ -10028,10 +10392,10 @@ function isFile(file) {
|
|
|
10028
10392
|
function resolveSourcePath(source, roots) {
|
|
10029
10393
|
const normalized = normalizeSourcePath(source);
|
|
10030
10394
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
10031
|
-
if (
|
|
10395
|
+
if (path23.isAbsolute(normalized) && isFile(normalized)) return path23.resolve(normalized);
|
|
10032
10396
|
const relative11 = normalized.replace(/^\/+/, "");
|
|
10033
10397
|
for (const root of roots) {
|
|
10034
|
-
const candidate =
|
|
10398
|
+
const candidate = path23.resolve(root, relative11);
|
|
10035
10399
|
if (isFile(candidate)) return candidate;
|
|
10036
10400
|
}
|
|
10037
10401
|
return null;
|
|
@@ -10063,7 +10427,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
10063
10427
|
return file;
|
|
10064
10428
|
}
|
|
10065
10429
|
function relativeToRepo(file, repoRoot) {
|
|
10066
|
-
return
|
|
10430
|
+
return path23.relative(repoRoot, file).split(path23.sep).join("/");
|
|
10067
10431
|
}
|
|
10068
10432
|
function scriptVersion(entry) {
|
|
10069
10433
|
return entry.hash ?? crypto5.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -10120,12 +10484,12 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
10120
10484
|
const files = /* @__PURE__ */ new Map();
|
|
10121
10485
|
const known = /* @__PURE__ */ new Map();
|
|
10122
10486
|
const addTo = (into, file, functions) => {
|
|
10123
|
-
const
|
|
10124
|
-
const listed = into.get(
|
|
10487
|
+
const path34 = relativeToRepo(file, roots.repoRoot);
|
|
10488
|
+
const listed = into.get(path34);
|
|
10125
10489
|
if (listed) listed.push(...functions);
|
|
10126
|
-
else into.set(
|
|
10490
|
+
else into.set(path34, [...functions]);
|
|
10127
10491
|
};
|
|
10128
|
-
const
|
|
10492
|
+
const add2 = (file, functions) => addTo(files, file, functions);
|
|
10129
10493
|
const bases = viteBases(entries);
|
|
10130
10494
|
const scripts = entries.filter((entry) => entry.url && entry.functions?.length && !REPLAYED_SCRIPT.test(entry.url));
|
|
10131
10495
|
const cached = scripts.map((entry) => scriptCache.get(entry.url, scriptVersion(entry)));
|
|
@@ -10152,14 +10516,14 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
10152
10516
|
const text = textOf2(covered, resolution);
|
|
10153
10517
|
if (resolution.lookup) {
|
|
10154
10518
|
const reached = reachedFunctionLines(covered, resolution.lookup, text);
|
|
10155
|
-
if (reached.size > 0)
|
|
10519
|
+
if (reached.size > 0) add2(resolution.file, [...reached.values()].flat());
|
|
10156
10520
|
} else if (scriptRan(covered, text)) {
|
|
10157
|
-
|
|
10521
|
+
add2(resolution.file, reachedFunctionLines(covered, null, text).get(-1) ?? []);
|
|
10158
10522
|
}
|
|
10159
10523
|
} else if (resolution.kind === "map") {
|
|
10160
10524
|
for (const [index, functions] of reachedFunctionLines(covered, resolution.lookup, textOf2(covered, resolution))) {
|
|
10161
10525
|
const file = mapFile(resolution, index);
|
|
10162
|
-
if (file)
|
|
10526
|
+
if (file) add2(file, functions);
|
|
10163
10527
|
}
|
|
10164
10528
|
}
|
|
10165
10529
|
if (!withKnown || resolution.kind === "none") continue;
|
|
@@ -10238,10 +10602,13 @@ async function markCurrentDocument(page, cdp) {
|
|
|
10238
10602
|
}
|
|
10239
10603
|
function onPaused(page, capture, event) {
|
|
10240
10604
|
const leaving = event.reason === "EventListener" && event.data?.eventName === "listener:beforeunload" && (event.callFrames?.[0]?.functionName ?? MARK_FUNCTION) === MARK_FUNCTION;
|
|
10241
|
-
const read = leaving ?
|
|
10605
|
+
const read = leaving ? Promise.all([
|
|
10606
|
+
capture.js ? readCoverage(page, capture.cdp) : Promise.resolve([]),
|
|
10607
|
+
capture.css ? readCssUsage(page, capture.cdp, capture.css) : Promise.resolve()
|
|
10608
|
+
]) : null;
|
|
10242
10609
|
if (read) {
|
|
10243
10610
|
const merging = read.then(
|
|
10244
|
-
(coverage) => mergeCoverageRead(capture.ran, capture.scripts, coverage, capture.keepIdle)
|
|
10611
|
+
([coverage]) => mergeCoverageRead(capture.ran, capture.scripts, coverage, capture.keepIdle)
|
|
10245
10612
|
);
|
|
10246
10613
|
capture.reading.add(merging);
|
|
10247
10614
|
void merging.finally(() => capture.reading.delete(merging));
|
|
@@ -10255,7 +10622,7 @@ function versionKey(url, hash) {
|
|
|
10255
10622
|
return `${hash} ${url}`;
|
|
10256
10623
|
}
|
|
10257
10624
|
function onScriptParsed(page, capture, event) {
|
|
10258
|
-
if (!event.url || REPLAYED_SCRIPT.test(event.url)) return;
|
|
10625
|
+
if (!capture.js || !event.url || REPLAYED_SCRIPT.test(event.url)) return;
|
|
10259
10626
|
capture.scripts.set(event.scriptId, { url: event.url, hash: event.hash, pageUrl: capture.pageUrl });
|
|
10260
10627
|
if (NEVER_REACHED.test(event.url)) return;
|
|
10261
10628
|
const key = versionKey(event.url, event.hash);
|
|
@@ -10275,8 +10642,8 @@ function onScriptParsed(page, capture, event) {
|
|
|
10275
10642
|
var READ_WAIT_MS = 5e3;
|
|
10276
10643
|
function readCoverage(page, cdp) {
|
|
10277
10644
|
let timer;
|
|
10278
|
-
const late = new Promise((
|
|
10279
|
-
timer = setTimeout(() =>
|
|
10645
|
+
const late = new Promise((resolve11) => {
|
|
10646
|
+
timer = setTimeout(() => resolve11([]), READ_WAIT_MS);
|
|
10280
10647
|
});
|
|
10281
10648
|
const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
|
|
10282
10649
|
(answer) => answer.result,
|
|
@@ -10284,8 +10651,55 @@ function readCoverage(page, cdp) {
|
|
|
10284
10651
|
);
|
|
10285
10652
|
return Promise.race([taken, late]).finally(() => clearTimeout(timer));
|
|
10286
10653
|
}
|
|
10287
|
-
function
|
|
10288
|
-
|
|
10654
|
+
function onStyleSheetAdded(page, cdp, css, pageUrl, header) {
|
|
10655
|
+
if (header.isConstructed) return;
|
|
10656
|
+
const sheet = {
|
|
10657
|
+
sourceURL: header.sourceURL,
|
|
10658
|
+
...header.sourceMapURL ? { sourceMapURL: header.sourceMapURL } : {},
|
|
10659
|
+
isInline: header.isInline,
|
|
10660
|
+
startLine: header.startLine,
|
|
10661
|
+
pageUrl,
|
|
10662
|
+
used: /* @__PURE__ */ new Map()
|
|
10663
|
+
};
|
|
10664
|
+
css.sheets.set(header.styleSheetId, sheet);
|
|
10665
|
+
const reading = (async () => {
|
|
10666
|
+
try {
|
|
10667
|
+
if (header.ownerNode !== void 0 && !/\.css(?:[?#]|$)/i.test(header.sourceURL)) {
|
|
10668
|
+
const { node } = await internalCall(
|
|
10669
|
+
page,
|
|
10670
|
+
() => cdp.send("DOM.describeNode", { backendNodeId: header.ownerNode })
|
|
10671
|
+
);
|
|
10672
|
+
const attributes = node.attributes ?? [];
|
|
10673
|
+
const at = attributes.indexOf("data-vite-dev-id");
|
|
10674
|
+
if (at >= 0 && typeof attributes[at + 1] === "string") sheet.devId = attributes[at + 1];
|
|
10675
|
+
}
|
|
10676
|
+
if (/\/node_modules\//.test(sheet.devId ?? header.sourceURL)) return;
|
|
10677
|
+
const { text } = await internalCall(
|
|
10678
|
+
page,
|
|
10679
|
+
() => cdp.send("CSS.getStyleSheetText", { styleSheetId: header.styleSheetId })
|
|
10680
|
+
);
|
|
10681
|
+
sheet.text = text;
|
|
10682
|
+
} catch {
|
|
10683
|
+
}
|
|
10684
|
+
})();
|
|
10685
|
+
css.reading.add(reading);
|
|
10686
|
+
void reading.finally(() => css.reading.delete(reading));
|
|
10687
|
+
}
|
|
10688
|
+
async function readCssUsage(page, cdp, css) {
|
|
10689
|
+
try {
|
|
10690
|
+
const { coverage } = await internalCall(page, () => cdp.send("CSS.takeCoverageDelta"));
|
|
10691
|
+
for (const rule of coverage) {
|
|
10692
|
+
if (!rule.used) continue;
|
|
10693
|
+
css.sheets.get(rule.styleSheetId)?.used.set(`${rule.startOffset}:${rule.endOffset}`, {
|
|
10694
|
+
startOffset: rule.startOffset,
|
|
10695
|
+
endOffset: rule.endOffset
|
|
10696
|
+
});
|
|
10697
|
+
}
|
|
10698
|
+
} catch {
|
|
10699
|
+
}
|
|
10700
|
+
}
|
|
10701
|
+
function mergeCoverageRead(ran, scripts, read, keepIdle = true) {
|
|
10702
|
+
for (const covered of read) {
|
|
10289
10703
|
const script = scripts.get(covered.scriptId);
|
|
10290
10704
|
if (!script) continue;
|
|
10291
10705
|
const key = versionKey(script.url, script.hash);
|
|
@@ -10318,11 +10732,15 @@ async function detach(page, cdp) {
|
|
|
10318
10732
|
}
|
|
10319
10733
|
async function startCodeReach(page) {
|
|
10320
10734
|
if (!isChromiumPage(page) || captures.has(page)) return false;
|
|
10735
|
+
const withCss = cssCoverageInWorker();
|
|
10736
|
+
const js = browserCoverageInWorker() || !withCss;
|
|
10321
10737
|
let cdp = null;
|
|
10322
10738
|
try {
|
|
10323
10739
|
cdp = await internalCall(page, () => page.context().newCDPSession(page));
|
|
10324
10740
|
const capture = {
|
|
10325
10741
|
cdp,
|
|
10742
|
+
js,
|
|
10743
|
+
css: withCss ? { sheets: /* @__PURE__ */ new Map(), reading: /* @__PURE__ */ new Set() } : null,
|
|
10326
10744
|
scripts: /* @__PURE__ */ new Map(),
|
|
10327
10745
|
sources: /* @__PURE__ */ new Map(),
|
|
10328
10746
|
ran: /* @__PURE__ */ new Map(),
|
|
@@ -10332,6 +10750,9 @@ async function startCodeReach(page) {
|
|
|
10332
10750
|
};
|
|
10333
10751
|
const marks = marksOf(page);
|
|
10334
10752
|
cdp.on("Debugger.scriptParsed", (event) => onScriptParsed(page, capture, event));
|
|
10753
|
+
const css = capture.css;
|
|
10754
|
+
if (css)
|
|
10755
|
+
cdp.on("CSS.styleSheetAdded", (event) => onStyleSheetAdded(page, cdp, css, capture.pageUrl, event.header));
|
|
10335
10756
|
cdp.on("Debugger.paused", (event) => onPaused(page, capture, event));
|
|
10336
10757
|
cdp.on("Page.frameNavigated", (event) => {
|
|
10337
10758
|
if (event.frame.parentId) return;
|
|
@@ -10342,8 +10763,17 @@ async function startCodeReach(page) {
|
|
|
10342
10763
|
page,
|
|
10343
10764
|
() => Promise.all([
|
|
10344
10765
|
capture.cdp.send("Debugger.enable"),
|
|
10345
|
-
|
|
10346
|
-
|
|
10766
|
+
...js ? [
|
|
10767
|
+
capture.cdp.send("Profiler.enable"),
|
|
10768
|
+
capture.cdp.send("Profiler.startPreciseCoverage", { callCount: false, detailed: false })
|
|
10769
|
+
] : [],
|
|
10770
|
+
// The style sheets' rule usage records nothing without the DOM and CSS domains. A failure there leaves the
|
|
10771
|
+
// scripts' coverage running.
|
|
10772
|
+
...css ? [
|
|
10773
|
+
capture.cdp.send("DOM.enable").then(() => capture.cdp.send("CSS.enable")).then(() => capture.cdp.send("CSS.startRuleUsageTracking")).catch(() => {
|
|
10774
|
+
capture.css = null;
|
|
10775
|
+
})
|
|
10776
|
+
] : [],
|
|
10347
10777
|
capture.cdp.send("Page.enable"),
|
|
10348
10778
|
capture.cdp.send("Page.addScriptToEvaluateOnNewDocument", { source: MARK_SOURCE, worldName: MARK_WORLD }),
|
|
10349
10779
|
capture.cdp.send("DOMDebugger.setEventListenerBreakpoint", { eventName: "beforeunload" })
|
|
@@ -10357,18 +10787,26 @@ async function startCodeReach(page) {
|
|
|
10357
10787
|
return false;
|
|
10358
10788
|
}
|
|
10359
10789
|
}
|
|
10360
|
-
async function
|
|
10790
|
+
async function stopPageCoverage(page) {
|
|
10361
10791
|
const capture = captures.get(page);
|
|
10362
|
-
if (!capture) return null;
|
|
10792
|
+
if (!capture) return { scripts: null, sheets: [] };
|
|
10363
10793
|
captures.delete(page);
|
|
10364
10794
|
const { cdp } = capture;
|
|
10365
|
-
|
|
10795
|
+
const sheets = async () => {
|
|
10796
|
+
if (!capture.css) return [];
|
|
10797
|
+
await readCssUsage(page, cdp, capture.css);
|
|
10798
|
+
await Promise.all(capture.css.reading);
|
|
10799
|
+
await internalCall(page, () => cdp.send("CSS.stopRuleUsageTracking")).catch(() => {
|
|
10800
|
+
});
|
|
10801
|
+
return [...capture.css.sheets.values()].filter((sheet) => sheet.used.size > 0 || sheet.text !== void 0);
|
|
10802
|
+
};
|
|
10803
|
+
const scripts = async () => {
|
|
10804
|
+
if (!capture.js) return null;
|
|
10366
10805
|
const last = await internalCall(page, async () => {
|
|
10367
10806
|
const taken = await cdp.send("Profiler.takePreciseCoverage");
|
|
10368
10807
|
await cdp.send("Profiler.stopPreciseCoverage");
|
|
10369
10808
|
return taken.result;
|
|
10370
10809
|
});
|
|
10371
|
-
await Promise.all(capture.reading);
|
|
10372
10810
|
mergeCoverageRead(capture.ran, capture.scripts, last, capture.keepIdle);
|
|
10373
10811
|
const entries = [];
|
|
10374
10812
|
for (const [key, { script, functions }] of capture.ran) {
|
|
@@ -10378,8 +10816,11 @@ async function stopCodeReach(page) {
|
|
|
10378
10816
|
entries.push(entry);
|
|
10379
10817
|
}
|
|
10380
10818
|
return entries;
|
|
10381
|
-
}
|
|
10382
|
-
|
|
10819
|
+
};
|
|
10820
|
+
try {
|
|
10821
|
+
await Promise.all(capture.reading);
|
|
10822
|
+
const [read, styles] = await Promise.all([scripts().catch(() => null), sheets().catch(() => [])]);
|
|
10823
|
+
return { scripts: read, sheets: styles };
|
|
10383
10824
|
} finally {
|
|
10384
10825
|
await detach(page, cdp);
|
|
10385
10826
|
}
|
|
@@ -10388,8 +10829,8 @@ async function stopCodeReach(page) {
|
|
|
10388
10829
|
// src/internal/collect/blob-ids.ts
|
|
10389
10830
|
var import_node_child_process4 = require("child_process");
|
|
10390
10831
|
var crypto6 = __toESM(require("crypto"));
|
|
10391
|
-
var
|
|
10392
|
-
var
|
|
10832
|
+
var fs24 = __toESM(require("fs"));
|
|
10833
|
+
var path24 = __toESM(require("path"));
|
|
10393
10834
|
function gitBlobId(content) {
|
|
10394
10835
|
return crypto6.createHash("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
|
|
10395
10836
|
}
|
|
@@ -10398,14 +10839,14 @@ function namesLines(file) {
|
|
|
10398
10839
|
}
|
|
10399
10840
|
var GIT_BATCH = 500;
|
|
10400
10841
|
function gitHashObjects(root, paths) {
|
|
10401
|
-
return new Promise((
|
|
10842
|
+
return new Promise((resolve11) => {
|
|
10402
10843
|
const child = (0, import_node_child_process4.execFile)(
|
|
10403
10844
|
"git",
|
|
10404
10845
|
["hash-object", "--stdin-paths"],
|
|
10405
10846
|
{ cwd: root, encoding: "utf8", timeout: 3e4, windowsHide: true },
|
|
10406
10847
|
(error, stdout) => {
|
|
10407
10848
|
const ids = error ? [] : stdout.split("\n").filter(Boolean);
|
|
10408
|
-
|
|
10849
|
+
resolve11(ids.length === paths.length ? ids : null);
|
|
10409
10850
|
}
|
|
10410
10851
|
);
|
|
10411
10852
|
child.stdin?.on("error", () => {
|
|
@@ -10456,15 +10897,15 @@ var BlobIds = class {
|
|
|
10456
10897
|
async read(relativePaths) {
|
|
10457
10898
|
let root;
|
|
10458
10899
|
try {
|
|
10459
|
-
root =
|
|
10900
|
+
root = path24.resolve(this.repoRoot());
|
|
10460
10901
|
} catch {
|
|
10461
10902
|
return relativePaths.map(() => null);
|
|
10462
10903
|
}
|
|
10463
10904
|
const files = await Promise.all(
|
|
10464
10905
|
relativePaths.map(async (relativePath2) => {
|
|
10465
|
-
const file =
|
|
10466
|
-
if (!file.startsWith(root +
|
|
10467
|
-
const stat = await
|
|
10906
|
+
const file = path24.resolve(root, relativePath2);
|
|
10907
|
+
if (!file.startsWith(root + path24.sep)) return null;
|
|
10908
|
+
const stat = await fs24.promises.stat(file).catch(() => null);
|
|
10468
10909
|
return stat?.isFile() ? file : null;
|
|
10469
10910
|
})
|
|
10470
10911
|
);
|
|
@@ -10472,7 +10913,7 @@ var BlobIds = class {
|
|
|
10472
10913
|
const fromGit = /* @__PURE__ */ new Map();
|
|
10473
10914
|
const ids = forGit.length > 0 ? await this.hashObjects(
|
|
10474
10915
|
root,
|
|
10475
|
-
forGit.map((file) =>
|
|
10916
|
+
forGit.map((file) => path24.relative(root, file))
|
|
10476
10917
|
) : null;
|
|
10477
10918
|
if (ids) forGit.forEach((file, i) => fromGit.set(file, ids[i]));
|
|
10478
10919
|
return Promise.all(
|
|
@@ -10481,7 +10922,7 @@ var BlobIds = class {
|
|
|
10481
10922
|
const id = fromGit.get(file);
|
|
10482
10923
|
if (id) return id;
|
|
10483
10924
|
try {
|
|
10484
|
-
return gitBlobId(await
|
|
10925
|
+
return gitBlobId(await fs24.promises.readFile(file));
|
|
10485
10926
|
} catch {
|
|
10486
10927
|
return null;
|
|
10487
10928
|
}
|
|
@@ -10496,7 +10937,7 @@ var SETTLE_WAIT_MS = 15e3;
|
|
|
10496
10937
|
var RUN_COVERAGE_FILES_PER_REQUEST = 5e3;
|
|
10497
10938
|
var RUN_COVERAGE_BYTES_PER_REQUEST = 8 * 1024 * 1024;
|
|
10498
10939
|
var INCOMPLETE = { files: [], incomplete: true };
|
|
10499
|
-
function composeTestCoverage(server, browserFiles, settings) {
|
|
10940
|
+
function composeTestCoverage(server, browserFiles, settings, cssFiles) {
|
|
10500
10941
|
const serverCapture = settings.server !== "off" && server ? reduceCoverage(
|
|
10501
10942
|
{
|
|
10502
10943
|
v: 1,
|
|
@@ -10514,7 +10955,19 @@ function composeTestCoverage(server, browserFiles, settings) {
|
|
|
10514
10955
|
},
|
|
10515
10956
|
settings.browser
|
|
10516
10957
|
) : null;
|
|
10517
|
-
const
|
|
10958
|
+
const wholeFiles = new Set(
|
|
10959
|
+
settings.browser === "functions" ? [] : (browserFiles ?? []).map((file) => typeof file === "string" ? file : file.path)
|
|
10960
|
+
);
|
|
10961
|
+
const cssCapture = settings.css === "rules" && cssFiles?.length ? reduceCoverage(
|
|
10962
|
+
{
|
|
10963
|
+
v: 1,
|
|
10964
|
+
files: cssFiles.map(
|
|
10965
|
+
(file) => wholeFiles.has(file.path) ? { path: file.path, runtime: "browser" } : { ...file, runtime: "browser" }
|
|
10966
|
+
)
|
|
10967
|
+
},
|
|
10968
|
+
"functions"
|
|
10969
|
+
) : null;
|
|
10970
|
+
const merged = mergeCoverage(serverCapture, mergeCoverage(browserCapture, cssCapture));
|
|
10518
10971
|
return merged ? sanitizeCoverage(merged) : null;
|
|
10519
10972
|
}
|
|
10520
10973
|
function addRanges(into, filePath2, ranges) {
|
|
@@ -10574,7 +11027,7 @@ var CoverageCollection = class {
|
|
|
10574
11027
|
this.browserKnown = /* @__PURE__ */ new Map();
|
|
10575
11028
|
this.logger = options.logger ?? new Logger();
|
|
10576
11029
|
this.waitMs = options.waitMs ?? TEST_COVERAGE_WAIT_MS;
|
|
10577
|
-
this.browserBlobs = options.settings.browser === "functions" ? new BlobIds(() => repositoryRoot(options.configDir)) : null;
|
|
11030
|
+
this.browserBlobs = options.settings.browser === "functions" || options.settings.css === "rules" ? new BlobIds(() => repositoryRoot(options.configDir)) : null;
|
|
10578
11031
|
const granularity = options.settings.server;
|
|
10579
11032
|
this.collector = granularity === "off" ? null : new ServerCoverageCollector({
|
|
10580
11033
|
granularity,
|
|
@@ -10591,28 +11044,40 @@ var CoverageCollection = class {
|
|
|
10591
11044
|
/**
|
|
10592
11045
|
* The browser side of a test's coverage: its code reach paths, or, at
|
|
10593
11046
|
* `functions` granularity, the files of its `piwi-browser-coverage` attachment
|
|
10594
|
-
* with their blob ids, once read.
|
|
11047
|
+
* with their blob ids, once read; with `coverage.css: 'rules'`, the style
|
|
11048
|
+
* sheet files of its `piwi-css-coverage` attachment too. Never rejects.
|
|
10595
11049
|
*/
|
|
10596
11050
|
browserCoverage(testCase, attachments) {
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
const
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
11051
|
+
const { browser, css } = this.options.settings;
|
|
11052
|
+
if (browser === "off" && css !== "rules") return {};
|
|
11053
|
+
const paths = browser === "off" ? void 0 : testCase.codeReach;
|
|
11054
|
+
for (const known of attachments ?? []) {
|
|
11055
|
+
if (browser === "functions" && known.name === ATTACHMENT_NAMES.browserFunctions) this.addKnownFunctions(known);
|
|
11056
|
+
if (css === "rules" && known.name === ATTACHMENT_NAMES.cssRules) this.addKnownFunctions(known);
|
|
11057
|
+
}
|
|
11058
|
+
const scripts = browser === "functions" ? this.rangedFiles(attachments, ATTACHMENT_NAMES.browserCoverage) : [];
|
|
11059
|
+
const sheets = css === "rules" ? this.rangedFiles(attachments, ATTACHMENT_NAMES.cssCoverage) : [];
|
|
11060
|
+
const blobs = this.browserBlobs;
|
|
11061
|
+
if (!blobs || scripts.length === 0 && sheets.length === 0) return { files: paths };
|
|
11062
|
+
const withBlobs = (files) => blobs.withBlobs(files).catch(() => files);
|
|
11063
|
+
return Promise.all([
|
|
11064
|
+
scripts.length > 0 ? withBlobs(scripts) : Promise.resolve(null),
|
|
11065
|
+
sheets.length > 0 ? withBlobs(sheets) : Promise.resolve([])
|
|
11066
|
+
]).then(([files, rules]) => ({ files: files ?? paths, css: rules }));
|
|
11067
|
+
}
|
|
11068
|
+
/** The browser files of a test's attachment holding line ranges, added to the run's. */
|
|
11069
|
+
rangedFiles(attachments, name) {
|
|
11070
|
+
const attachment = attachments?.find((a) => a.name === name);
|
|
11071
|
+
if (!attachment?.body) return [];
|
|
10606
11072
|
let files = [];
|
|
10607
11073
|
try {
|
|
10608
11074
|
files = sanitizeCoverage(JSON.parse(attachment.body.toString("utf8")))?.files ?? [];
|
|
10609
11075
|
} catch {
|
|
10610
|
-
return
|
|
11076
|
+
return [];
|
|
10611
11077
|
}
|
|
10612
11078
|
const browser = files.filter((file) => file.runtime === "browser");
|
|
10613
|
-
if (browser.length === 0) return paths;
|
|
10614
11079
|
for (const file of browser) addRanges(this.browserRan, file.path, file.functions ?? []);
|
|
10615
|
-
return
|
|
11080
|
+
return browser;
|
|
10616
11081
|
}
|
|
10617
11082
|
/** Add the functions of a `piwi-browser-functions` attachment, one of a test's, to the run's. */
|
|
10618
11083
|
addKnownFunctions(attachment) {
|
|
@@ -10667,27 +11132,27 @@ var CoverageCollection = class {
|
|
|
10667
11132
|
const requests = attachment?.body ? parseServerCoverageRequests(attachment.body) : null;
|
|
10668
11133
|
if (!this.collector || !requests) {
|
|
10669
11134
|
if (!(browser instanceof Promise)) {
|
|
10670
|
-
this.assign(testCase, composeTestCoverage(null, browser, this.options.settings));
|
|
11135
|
+
this.assign(testCase, composeTestCoverage(null, browser.files, this.options.settings, browser.css));
|
|
10671
11136
|
return null;
|
|
10672
11137
|
}
|
|
10673
11138
|
const ready = browser.then(
|
|
10674
|
-
(
|
|
11139
|
+
(side) => this.assign(testCase, composeTestCoverage(null, side.files, this.options.settings, side.css))
|
|
10675
11140
|
);
|
|
10676
11141
|
this.browserPending.add(ready);
|
|
10677
11142
|
return ready.finally(() => this.browserPending.delete(ready));
|
|
10678
11143
|
}
|
|
10679
11144
|
let timer;
|
|
10680
11145
|
let finish;
|
|
10681
|
-
const done = new Promise((
|
|
11146
|
+
const done = new Promise((resolve11) => {
|
|
10682
11147
|
let settled = false;
|
|
10683
11148
|
finish = (server) => {
|
|
10684
11149
|
if (settled) return;
|
|
10685
11150
|
settled = true;
|
|
10686
11151
|
clearTimeout(timer);
|
|
10687
11152
|
this.pending.delete(entry);
|
|
10688
|
-
void Promise.resolve(browser).then((
|
|
10689
|
-
this.assign(testCase, composeTestCoverage(server, files, this.options.settings));
|
|
10690
|
-
|
|
11153
|
+
void Promise.resolve(browser).then((side) => {
|
|
11154
|
+
this.assign(testCase, composeTestCoverage(server, side.files, this.options.settings, side.css));
|
|
11155
|
+
resolve11();
|
|
10691
11156
|
});
|
|
10692
11157
|
};
|
|
10693
11158
|
});
|
|
@@ -10719,8 +11184,8 @@ var CoverageCollection = class {
|
|
|
10719
11184
|
let timer;
|
|
10720
11185
|
await Promise.race([
|
|
10721
11186
|
Promise.all(waiting),
|
|
10722
|
-
new Promise((
|
|
10723
|
-
timer = setTimeout(
|
|
11187
|
+
new Promise((resolve11) => {
|
|
11188
|
+
timer = setTimeout(resolve11, boundMs);
|
|
10724
11189
|
})
|
|
10725
11190
|
]);
|
|
10726
11191
|
clearTimeout(timer);
|
|
@@ -10823,6 +11288,8 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10823
11288
|
this.resourcesFile = null;
|
|
10824
11289
|
/** Samples the run's processes and the machine from `onBegin` to `onEnd`. */
|
|
10825
11290
|
this.sampler = null;
|
|
11291
|
+
/** Posts what the sampler and the censuses add while someone has the run open on the dashboard. */
|
|
11292
|
+
this.liveResources = null;
|
|
10826
11293
|
/** Bytes of the files the tests attached, by kind. */
|
|
10827
11294
|
this.artifactBytes = emptyArtifacts();
|
|
10828
11295
|
/** The run's resource report for the dashboard, built in `onEnd`. */
|
|
@@ -10838,10 +11305,13 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10838
11305
|
this.streamManager = null;
|
|
10839
11306
|
/** Track suite-level setup steps (beforeAll/afterAll) not tied to any test */
|
|
10840
11307
|
this.setupSteps = [];
|
|
10841
|
-
|
|
11308
|
+
const configDir = typeof rawOptions.configDir === "string" ? rawOptions.configDir : void 0;
|
|
11309
|
+
this.options = resolveOptions(rawOptions, configDir);
|
|
10842
11310
|
this.enabled = this.options.enabled !== false && !!this.options.serverUrl;
|
|
10843
11311
|
this.listMode = isListMode();
|
|
10844
11312
|
this.viaDesktopApp = usedDesktopDiscovery();
|
|
11313
|
+
this.desktopReplaced = desktopReplacedServer();
|
|
11314
|
+
this.viaDotEnv = usedDotEnv();
|
|
10845
11315
|
this.coverageSettings = coverageSettings(this.options);
|
|
10846
11316
|
const logger = new Logger(this.options.verbose ?? false);
|
|
10847
11317
|
this.logger = logger;
|
|
@@ -10899,14 +11369,17 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10899
11369
|
return;
|
|
10900
11370
|
}
|
|
10901
11371
|
if (this.viaDesktopApp) {
|
|
10902
|
-
this.logger.info(
|
|
11372
|
+
this.logger.info(
|
|
11373
|
+
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).`
|
|
11374
|
+
);
|
|
10903
11375
|
}
|
|
11376
|
+
if (this.viaDotEnv) this.logger.debug(`Read ${this.viaDotEnv.keys.join(", ")} from ${this.viaDotEnv.file}.`);
|
|
10904
11377
|
this.startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
10905
11378
|
this.testPathBase = configDirFromConfig(config);
|
|
10906
11379
|
this.playwrightVersion = config.version;
|
|
10907
11380
|
this.maxFailures = config.maxFailures ?? 0;
|
|
10908
11381
|
if (this.options.captureResources !== false) {
|
|
10909
|
-
this.resourcesFile =
|
|
11382
|
+
this.resourcesFile = path25.join(os9.tmpdir(), `piwi-resources-${process.pid}-${Date.now()}.jsonl`);
|
|
10910
11383
|
process.env[PIWI_RESOURCES_RESULTS_ENV] = this.resourcesFile;
|
|
10911
11384
|
try {
|
|
10912
11385
|
this.sampler = new RunSampler({ outputDirs: (config.projects ?? []).map((project) => project.outputDir) });
|
|
@@ -10975,8 +11448,9 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10975
11448
|
);
|
|
10976
11449
|
}
|
|
10977
11450
|
if (coverage.server !== "off") this.lastTestWatch = new LastTestWatch(this.plannedTests.length);
|
|
10978
|
-
if (coverage.browser !== "off" || coverage.server !== "off") {
|
|
11451
|
+
if (coverage.browser !== "off" || coverage.server !== "off" || coverage.css === "rules") {
|
|
10979
11452
|
if (coverage.browser !== "off") process.env[PIWI_ENV_KEYS.coverageBrowser] = coverage.browser;
|
|
11453
|
+
if (coverage.css === "rules") process.env[PIWI_ENV_KEYS.coverageCss] = coverage.css;
|
|
10980
11454
|
if (coverage.server !== "off") {
|
|
10981
11455
|
process.env[PIWI_ENV_KEYS.coverageServer] = coverage.server;
|
|
10982
11456
|
}
|
|
@@ -11001,6 +11475,22 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11001
11475
|
// the dashboard shows the real total from the first render.
|
|
11002
11476
|
this.plannedTests.length
|
|
11003
11477
|
);
|
|
11478
|
+
if (this.streamManager && this.options.captureResources !== false) {
|
|
11479
|
+
const live = new LiveResources(
|
|
11480
|
+
this.streamManager,
|
|
11481
|
+
this.httpClient,
|
|
11482
|
+
{
|
|
11483
|
+
sampler: this.sampler,
|
|
11484
|
+
censuses: () => this.resourceCensuses,
|
|
11485
|
+
shardIndex: this.shardInfo?.current ?? null,
|
|
11486
|
+
runStartedAt: Date.now()
|
|
11487
|
+
},
|
|
11488
|
+
this.logger
|
|
11489
|
+
);
|
|
11490
|
+
this.streamManager.onWatched = () => void live.tick();
|
|
11491
|
+
live.start();
|
|
11492
|
+
this.liveResources = live;
|
|
11493
|
+
}
|
|
11004
11494
|
}
|
|
11005
11495
|
/**
|
|
11006
11496
|
* Settle each planned test's external ID before any test runs, name the IDs two tests share, and record them on
|
|
@@ -11184,10 +11674,11 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11184
11674
|
if (frames.length) testCase.testSourceFrames = frames;
|
|
11185
11675
|
}
|
|
11186
11676
|
if (this.options.collectPerformanceMetrics && result.steps?.length > 0) {
|
|
11187
|
-
|
|
11677
|
+
const pauses = readPauses(result.attachments?.find((a) => a.name === ATTACHMENT_NAMES.pauses)?.body);
|
|
11678
|
+
testCase.performanceMetrics = collectStepMetrics(result.steps, result.errors, pauses);
|
|
11188
11679
|
const stepEvents = extractTestStepEvents(result.steps, result.startTime);
|
|
11189
11680
|
const waitEvents = extractWaitEvents(result.steps);
|
|
11190
|
-
const allEvents = [...stepEvents, ...waitEvents];
|
|
11681
|
+
const allEvents = [...stepEvents, ...pauseStepEvents(pauses), ...waitEvents];
|
|
11191
11682
|
if (allEvents.length > 0) testCase.stepEvents = allEvents;
|
|
11192
11683
|
}
|
|
11193
11684
|
if (this.options.collectPerformanceMetrics && result.attachments) {
|
|
@@ -11275,7 +11766,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11275
11766
|
if (!file.path || INTERNAL_ATTACHMENT_NAMES.has(file.name)) continue;
|
|
11276
11767
|
try {
|
|
11277
11768
|
const kind = artifactKind(file.name, file.contentType);
|
|
11278
|
-
const bytes =
|
|
11769
|
+
const bytes = fs25.statSync(file.path).size;
|
|
11279
11770
|
artifacts[kind] += bytes;
|
|
11280
11771
|
this.artifactBytes[kind] += bytes;
|
|
11281
11772
|
} catch {
|
|
@@ -11302,14 +11793,16 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11302
11793
|
*/
|
|
11303
11794
|
async reportResources() {
|
|
11304
11795
|
if (this.options.captureResources === false) return;
|
|
11796
|
+
await this.liveResources?.stop();
|
|
11797
|
+
this.liveResources = null;
|
|
11305
11798
|
const profile = this.sampler ? await this.sampler.stop() : null;
|
|
11306
11799
|
const samples = this.sampler?.timedSeries() ?? null;
|
|
11307
11800
|
this.sampler = null;
|
|
11308
11801
|
const censuses = [...this.resourceCensuses];
|
|
11309
11802
|
if (this.resourcesFile) {
|
|
11310
11803
|
try {
|
|
11311
|
-
censuses.push(...parseResourceResults(
|
|
11312
|
-
|
|
11804
|
+
censuses.push(...parseResourceResults(fs25.readFileSync(this.resourcesFile, "utf8")));
|
|
11805
|
+
fs25.rmSync(this.resourcesFile, { force: true });
|
|
11313
11806
|
} catch {
|
|
11314
11807
|
}
|
|
11315
11808
|
}
|
|
@@ -11534,7 +12027,7 @@ async function resolveSelection(options = {}) {
|
|
|
11534
12027
|
}
|
|
11535
12028
|
|
|
11536
12029
|
// src/internal/capture/capture-fixtures.ts
|
|
11537
|
-
var
|
|
12030
|
+
var path30 = __toESM(require("path"));
|
|
11538
12031
|
var import_node_zlib2 = require("zlib");
|
|
11539
12032
|
|
|
11540
12033
|
// ../picker-dom/src/probe.ts
|
|
@@ -11993,8 +12486,8 @@ function installPickerOverlay(arg) {
|
|
|
11993
12486
|
return root && root !== doc && root.host ? root.host : null;
|
|
11994
12487
|
};
|
|
11995
12488
|
const targetOf = (e) => {
|
|
11996
|
-
const
|
|
11997
|
-
const first =
|
|
12489
|
+
const path34 = typeof e.composedPath === "function" ? e.composedPath() : null;
|
|
12490
|
+
const first = path34 && path34.length ? path34[0] : null;
|
|
11998
12491
|
return first && first.nodeType === 1 ? first : e.target;
|
|
11999
12492
|
};
|
|
12000
12493
|
const buildChain = (raw) => {
|
|
@@ -12698,8 +13191,11 @@ function showPauseBar(arg) {
|
|
|
12698
13191
|
};
|
|
12699
13192
|
const buttons = [
|
|
12700
13193
|
["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
|
|
12701
|
-
["Step", "step", "Pause again
|
|
13194
|
+
["Step", "step", "Pause again before the next call", false],
|
|
12702
13195
|
["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
|
|
13196
|
+
...arg.record ? [
|
|
13197
|
+
["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
|
|
13198
|
+
] : [],
|
|
12703
13199
|
["Finish", "finish", "Run on and pause no more in this test", false]
|
|
12704
13200
|
];
|
|
12705
13201
|
for (const [label, choice, hint, primary] of buttons) {
|
|
@@ -12865,7 +13361,7 @@ var TEST_DATA_ATTRS = /* @__PURE__ */ new Set(["data-test", "data-test-id", "dat
|
|
|
12865
13361
|
function generateAlternatives(attrs, options = {}) {
|
|
12866
13362
|
const alts = [];
|
|
12867
13363
|
const seen = /* @__PURE__ */ new Set();
|
|
12868
|
-
const
|
|
13364
|
+
const add2 = (loc) => {
|
|
12869
13365
|
if (!seen.has(loc.locator)) {
|
|
12870
13366
|
seen.add(loc.locator);
|
|
12871
13367
|
alts.push(loc);
|
|
@@ -12882,7 +13378,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12882
13378
|
const selectable = (name2) => name2.toLowerCase() !== testIdAttribute.toLowerCase();
|
|
12883
13379
|
const testId = attr(attrs, testIdAttribute);
|
|
12884
13380
|
if (testId && isUnique(counts?.testId)) {
|
|
12885
|
-
|
|
13381
|
+
add2({
|
|
12886
13382
|
locator: `getByTestId(${quoteJsString(testId)})`,
|
|
12887
13383
|
method: "getByTestId",
|
|
12888
13384
|
args: { testId },
|
|
@@ -12893,7 +13389,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12893
13389
|
const levelPart = level != null ? `, level: ${level}` : "";
|
|
12894
13390
|
const withLevel = (base) => level != null ? { ...base, level } : base;
|
|
12895
13391
|
if (role && accessibleName) {
|
|
12896
|
-
|
|
13392
|
+
add2({
|
|
12897
13393
|
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(accessibleName)}${levelPart} })`,
|
|
12898
13394
|
method: "getByRole",
|
|
12899
13395
|
args: withLevel({ role, name: accessibleName }),
|
|
@@ -12902,7 +13398,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12902
13398
|
}
|
|
12903
13399
|
const ariaLabel = attr(attrs, "aria-label");
|
|
12904
13400
|
if (role && ariaLabel && ariaLabel !== accessibleName) {
|
|
12905
|
-
|
|
13401
|
+
add2({
|
|
12906
13402
|
locator: `getByRole(${quoteJsString(role)}, { name: ${quoteJsString(ariaLabel)}${levelPart} })`,
|
|
12907
13403
|
method: "getByRole",
|
|
12908
13404
|
args: withLevel({ role, name: ariaLabel }),
|
|
@@ -12913,7 +13409,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12913
13409
|
const label = attr(attrs, "aria-label");
|
|
12914
13410
|
const labelBacked = attrs.hasLabel === void 0 ? true : attrs.hasLabel === true || label === accessibleName;
|
|
12915
13411
|
if (labelBacked) {
|
|
12916
|
-
|
|
13412
|
+
add2({
|
|
12917
13413
|
locator: `getByLabel(${quoteJsString(accessibleName)})`,
|
|
12918
13414
|
method: "getByLabel",
|
|
12919
13415
|
args: { label: accessibleName },
|
|
@@ -12923,7 +13419,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12923
13419
|
}
|
|
12924
13420
|
const placeholder = attr(attrs, "placeholder");
|
|
12925
13421
|
if (placeholder) {
|
|
12926
|
-
|
|
13422
|
+
add2({
|
|
12927
13423
|
locator: `getByPlaceholder(${quoteJsString(placeholder)})`,
|
|
12928
13424
|
method: "getByPlaceholder",
|
|
12929
13425
|
args: { placeholder },
|
|
@@ -12931,7 +13427,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12931
13427
|
});
|
|
12932
13428
|
}
|
|
12933
13429
|
if (text && text.length < 80) {
|
|
12934
|
-
|
|
13430
|
+
add2({
|
|
12935
13431
|
locator: `getByText(${quoteJsString(text)})`,
|
|
12936
13432
|
method: "getByText",
|
|
12937
13433
|
args: { text },
|
|
@@ -12941,7 +13437,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12941
13437
|
const id = attr(attrs, "id");
|
|
12942
13438
|
if (id && selectable("id") && !isAutoGenerated(id) && isUnique(counts?.id)) {
|
|
12943
13439
|
const selector = isCssSafeId(id) ? `#${id}` : `[id="${escCssAttrValue(id)}"]`;
|
|
12944
|
-
|
|
13440
|
+
add2({
|
|
12945
13441
|
locator: `locator(${quoteJsString(selector)})`,
|
|
12946
13442
|
method: "locator",
|
|
12947
13443
|
args: { selector },
|
|
@@ -12951,7 +13447,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12951
13447
|
const name = attr(attrs, "name");
|
|
12952
13448
|
if (name && selectable("name") && isUnique(counts?.name)) {
|
|
12953
13449
|
const selector = `[name="${escCssAttrValue(name)}"]`;
|
|
12954
|
-
|
|
13450
|
+
add2({
|
|
12955
13451
|
locator: `locator(${quoteJsString(selector)})`,
|
|
12956
13452
|
method: "locator",
|
|
12957
13453
|
args: { selector },
|
|
@@ -12960,7 +13456,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12960
13456
|
}
|
|
12961
13457
|
const alt = attr(attrs, "alt");
|
|
12962
13458
|
if (alt) {
|
|
12963
|
-
|
|
13459
|
+
add2({
|
|
12964
13460
|
locator: `getByAltText(${quoteJsString(alt)})`,
|
|
12965
13461
|
method: "getByAltText",
|
|
12966
13462
|
args: { text: alt },
|
|
@@ -12969,7 +13465,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12969
13465
|
}
|
|
12970
13466
|
const title = attr(attrs, "title");
|
|
12971
13467
|
if (title) {
|
|
12972
|
-
|
|
13468
|
+
add2({
|
|
12973
13469
|
locator: `getByTitle(${quoteJsString(title)})`,
|
|
12974
13470
|
method: "getByTitle",
|
|
12975
13471
|
args: { title },
|
|
@@ -12987,7 +13483,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12987
13483
|
if (scopedCount(anc) !== 1) continue;
|
|
12988
13484
|
if (!testIdAnchorDone && anc.testId && anc.testIdCount === 1) {
|
|
12989
13485
|
testIdAnchorDone = true;
|
|
12990
|
-
|
|
13486
|
+
add2({
|
|
12991
13487
|
locator: `getByTestId(${quoteJsString(anc.testId)}).${leaf.expr}`,
|
|
12992
13488
|
method: leaf.method,
|
|
12993
13489
|
args: { ...leaf.args, anchorTestId: anc.testId },
|
|
@@ -12997,7 +13493,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
12997
13493
|
if (!idAnchorDone && anc.id && selectable("id") && !isAutoGenerated(anc.id) && anc.idCount === 1) {
|
|
12998
13494
|
idAnchorDone = true;
|
|
12999
13495
|
const anchorSelector = isCssSafeId(anc.id) ? `#${anc.id}` : `[id="${escCssAttrValue(anc.id)}"]`;
|
|
13000
|
-
|
|
13496
|
+
add2({
|
|
13001
13497
|
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
13002
13498
|
method: leaf.method,
|
|
13003
13499
|
args: { ...leaf.args, anchorSelector },
|
|
@@ -13008,7 +13504,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
13008
13504
|
dataAnchorDone = true;
|
|
13009
13505
|
const { name: name2, value } = anc.dataAttr;
|
|
13010
13506
|
const anchorSelector = `[${name2}="${escCssAttrValue(value)}"]`;
|
|
13011
|
-
|
|
13507
|
+
add2({
|
|
13012
13508
|
locator: `locator(${quoteJsString(anchorSelector)}).${leaf.expr}`,
|
|
13013
13509
|
method: leaf.method,
|
|
13014
13510
|
args: { ...leaf.args, anchorSelector },
|
|
@@ -13018,7 +13514,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
13018
13514
|
const ancestorRole = anc.role || TAG_TO_ROLE[anc.tag] || null;
|
|
13019
13515
|
if (!roleAnchorDone && ancestorRole && ancestorRole !== leaf.role && anc.roleCount === 1) {
|
|
13020
13516
|
roleAnchorDone = true;
|
|
13021
|
-
|
|
13517
|
+
add2({
|
|
13022
13518
|
locator: `getByRole(${quoteJsString(ancestorRole)}).${leaf.expr}`,
|
|
13023
13519
|
method: leaf.method,
|
|
13024
13520
|
args: { ...leaf.args, anchorRole: ancestorRole },
|
|
@@ -13027,7 +13523,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
13027
13523
|
}
|
|
13028
13524
|
if (!filterAnchorDone && ancestorRole && anc.filterText && anc.filterRoleCount === 1) {
|
|
13029
13525
|
filterAnchorDone = true;
|
|
13030
|
-
|
|
13526
|
+
add2({
|
|
13031
13527
|
locator: `getByRole(${quoteJsString(ancestorRole)}).filter({ hasText: ${quoteJsString(anc.filterText)} }).${leaf.expr}`,
|
|
13032
13528
|
method: leaf.method,
|
|
13033
13529
|
args: { ...leaf.args, anchorRole: ancestorRole, anchorHasText: anc.filterText },
|
|
@@ -13046,7 +13542,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
13046
13542
|
);
|
|
13047
13543
|
const pos = attrs.rolePosition;
|
|
13048
13544
|
if (pos && pos.role === role && (pos.count === 1 || level != null && pos.levelCount === 1)) {
|
|
13049
|
-
|
|
13545
|
+
add2({
|
|
13050
13546
|
locator: `getByRole(${rolePart})`,
|
|
13051
13547
|
method: "getByRole",
|
|
13052
13548
|
args: leafArgs,
|
|
@@ -13065,7 +13561,7 @@ function generateAlternatives(attrs, options = {}) {
|
|
|
13065
13561
|
const countsClasses = counts?.classes;
|
|
13066
13562
|
const classes = clsStr.split(/\s+/).filter((c) => c.length > 1 && /^[A-Za-z_-][A-Za-z0-9_-]*$/.test(c) && isUnique(countsClasses?.[c])).map((cls) => ({ cls, score: classifyCssStability(cls) })).sort((a, b) => b.score - a.score).slice(0, 3);
|
|
13067
13563
|
for (const { cls, score } of classes) {
|
|
13068
|
-
|
|
13564
|
+
add2({
|
|
13069
13565
|
locator: `locator(${quoteJsString(`.${cls}`)})`,
|
|
13070
13566
|
method: "locator",
|
|
13071
13567
|
args: { selector: `.${cls}` },
|
|
@@ -13122,7 +13618,7 @@ function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
|
|
|
13122
13618
|
const seen = /* @__PURE__ */ new Set();
|
|
13123
13619
|
const rolePart = leaf.level != null ? `${quoteJsString(leaf.role)}, { level: ${leaf.level} }` : quoteJsString(leaf.role);
|
|
13124
13620
|
const leafArgs = leaf.level != null ? { role: leaf.role, level: leaf.level } : { role: leaf.role };
|
|
13125
|
-
const
|
|
13621
|
+
const add2 = (l) => {
|
|
13126
13622
|
if (!seen.has(l.locator)) {
|
|
13127
13623
|
seen.add(l.locator);
|
|
13128
13624
|
out.push(l);
|
|
@@ -13132,7 +13628,7 @@ function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
|
|
|
13132
13628
|
if (anchor.scopedLeafCount !== 1) continue;
|
|
13133
13629
|
const seg = anchorSegment(anchor);
|
|
13134
13630
|
if (!seg) continue;
|
|
13135
|
-
|
|
13631
|
+
add2({
|
|
13136
13632
|
locator: `${seg.code}.getByRole(${rolePart})`,
|
|
13137
13633
|
method: "getByRole",
|
|
13138
13634
|
args: { ...leafArgs, ...seg.flatArgs },
|
|
@@ -13145,7 +13641,7 @@ function generateAnchoredAlternatives(leaf, anchors, chainLeafCount) {
|
|
|
13145
13641
|
if (segments.every((s) => s !== null)) {
|
|
13146
13642
|
const innermost = segments[segments.length - 1];
|
|
13147
13643
|
const score = Math.min(...segments.map((s) => ANCHOR_KIND_SCORES[s.kind]));
|
|
13148
|
-
|
|
13644
|
+
add2({
|
|
13149
13645
|
locator: `${segments.map((s) => s.code).join(".")}.getByRole(${rolePart})`,
|
|
13150
13646
|
method: "getByRole",
|
|
13151
13647
|
// Flat args describe the innermost anchor (what the healing fingerprint
|
|
@@ -13175,21 +13671,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
13175
13671
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
13176
13672
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
13177
13673
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
13178
|
-
function isElementPath(
|
|
13179
|
-
return typeof
|
|
13674
|
+
function isElementPath(path34) {
|
|
13675
|
+
return typeof path34 === "string" && path34.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path34);
|
|
13180
13676
|
}
|
|
13181
13677
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
13182
13678
|
function elementRef(v) {
|
|
13183
13679
|
if (!v || typeof v !== "object") return null;
|
|
13184
|
-
const { path:
|
|
13185
|
-
if (!isElementPath(
|
|
13186
|
-
return { path:
|
|
13680
|
+
const { path: path34, label } = v;
|
|
13681
|
+
if (!isElementPath(path34)) return null;
|
|
13682
|
+
return { path: path34, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
13187
13683
|
}
|
|
13188
13684
|
function textPoint(v) {
|
|
13189
13685
|
if (!v || typeof v !== "object") return null;
|
|
13190
|
-
const { path:
|
|
13686
|
+
const { path: path34, offset } = v;
|
|
13191
13687
|
const at = offsetOf(offset);
|
|
13192
|
-
return isElementPath(
|
|
13688
|
+
return isElementPath(path34) && at !== null ? { path: path34, offset: at } : null;
|
|
13193
13689
|
}
|
|
13194
13690
|
function sanitizePageInteraction(raw) {
|
|
13195
13691
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -13220,7 +13716,7 @@ function sanitizePageInteraction(raw) {
|
|
|
13220
13716
|
}
|
|
13221
13717
|
|
|
13222
13718
|
// src/internal/capture/locator-healing.ts
|
|
13223
|
-
var
|
|
13719
|
+
var path26 = __toESM(require("path"));
|
|
13224
13720
|
|
|
13225
13721
|
// ../core/src/locator-fingerprint.ts
|
|
13226
13722
|
var ELEMENT_MATCH_SCORES = { role: 60, text: 55, label: 50 };
|
|
@@ -13605,10 +14101,10 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
|
|
|
13605
14101
|
}
|
|
13606
14102
|
let rel = file;
|
|
13607
14103
|
try {
|
|
13608
|
-
rel =
|
|
14104
|
+
rel = path26.relative(process.cwd(), file);
|
|
13609
14105
|
} catch {
|
|
13610
14106
|
}
|
|
13611
|
-
rel = rel.split(
|
|
14107
|
+
rel = rel.split(path26.sep).join("/");
|
|
13612
14108
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
13613
14109
|
return `${rel}:${m[3]}:${m[4]}`;
|
|
13614
14110
|
}
|
|
@@ -13838,7 +14334,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
|
|
|
13838
14334
|
}
|
|
13839
14335
|
|
|
13840
14336
|
// src/internal/capture/pick-on-failure.ts
|
|
13841
|
-
var
|
|
14337
|
+
var path27 = __toESM(require("path"));
|
|
13842
14338
|
function parseLeafLocatorExpression(rawExpr) {
|
|
13843
14339
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
13844
14340
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -13853,37 +14349,60 @@ ${err.stack ?? ""}`));
|
|
|
13853
14349
|
const parsed2 = parseLeafLocatorExpression(header);
|
|
13854
14350
|
if (!parsed2) continue;
|
|
13855
14351
|
const loc = err.location;
|
|
13856
|
-
const location = loc ? `${
|
|
14352
|
+
const location = loc ? `${path27.relative(process.cwd(), loc.file).split(path27.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
13857
14353
|
return { method: parsed2.method, args: parsed2.args, location };
|
|
13858
14354
|
}
|
|
13859
14355
|
return null;
|
|
13860
14356
|
}
|
|
13861
|
-
|
|
14357
|
+
var PICK_ENDED = "__piwiPickEnded";
|
|
14358
|
+
async function cleanupPicker(page, ended = false) {
|
|
13862
14359
|
try {
|
|
13863
|
-
await page.evaluate(
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
14360
|
+
await page.evaluate(
|
|
14361
|
+
([endedKey, unanswered]) => {
|
|
14362
|
+
const g = globalThis;
|
|
14363
|
+
if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
|
|
14364
|
+
if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
|
|
14365
|
+
delete g.__piwiPickCleanup;
|
|
14366
|
+
delete g.__piwiAnchorCleanup;
|
|
14367
|
+
delete g.__piwiPickState;
|
|
14368
|
+
delete g.__piwiPickChoice;
|
|
14369
|
+
delete g.__piwiPickedElement;
|
|
14370
|
+
delete g.__piwiAnchorState;
|
|
14371
|
+
delete g.__piwiPickAnchors;
|
|
14372
|
+
delete g.__piwiPickChainCount;
|
|
14373
|
+
if (unanswered) g[endedKey] = true;
|
|
14374
|
+
else delete g[endedKey];
|
|
14375
|
+
},
|
|
14376
|
+
[PICK_ENDED, ended]
|
|
14377
|
+
);
|
|
13876
14378
|
} catch {
|
|
13877
14379
|
}
|
|
13878
14380
|
}
|
|
13879
|
-
|
|
14381
|
+
function unlessEnded(run, ended) {
|
|
14382
|
+
if (!ended) return run;
|
|
14383
|
+
return Promise.race([
|
|
14384
|
+
run,
|
|
14385
|
+
new Promise((resolve11) => {
|
|
14386
|
+
if (ended.aborted) resolve11(void 0);
|
|
14387
|
+
else ended.addEventListener("abort", () => resolve11(void 0), { once: true });
|
|
14388
|
+
})
|
|
14389
|
+
]);
|
|
14390
|
+
}
|
|
14391
|
+
async function pickLocator(page, probe, failing, ended) {
|
|
14392
|
+
const answered = async (name) => {
|
|
14393
|
+
const wait = page.waitForFunction(
|
|
14394
|
+
([key, endedKey]) => globalThis[key] !== void 0 || globalThis[endedKey] === true,
|
|
14395
|
+
[name, PICK_ENDED],
|
|
14396
|
+
{ timeout: 0, polling: 250 }
|
|
14397
|
+
);
|
|
14398
|
+
await unlessEnded(wait, ended);
|
|
14399
|
+
return !ended?.aborted;
|
|
14400
|
+
};
|
|
13880
14401
|
try {
|
|
13881
14402
|
const overlayArg = { transport: "global", failing };
|
|
14403
|
+
await page.evaluate((endedKey) => delete globalThis[endedKey], PICK_ENDED);
|
|
13882
14404
|
await page.evaluate(installPickerOverlay, overlayArg);
|
|
13883
|
-
await
|
|
13884
|
-
timeout: 0,
|
|
13885
|
-
polling: 250
|
|
13886
|
-
});
|
|
14405
|
+
if (!await answered("__piwiPickState")) return null;
|
|
13887
14406
|
const state = await page.evaluate(() => globalThis.__piwiPickState);
|
|
13888
14407
|
if (state !== "picked") return null;
|
|
13889
14408
|
const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
|
|
@@ -13904,10 +14423,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13904
14423
|
leafLevel: level,
|
|
13905
14424
|
leafTestId: attrs.attributes["data-testid"] ?? null
|
|
13906
14425
|
});
|
|
13907
|
-
await
|
|
13908
|
-
timeout: 0,
|
|
13909
|
-
polling: 250
|
|
13910
|
-
});
|
|
14426
|
+
if (!await answered("__piwiAnchorState")) return null;
|
|
13911
14427
|
const anchorResult = await page.evaluate(() => {
|
|
13912
14428
|
const g = globalThis;
|
|
13913
14429
|
return { state: g.__piwiAnchorState, anchors: g.__piwiPickAnchors ?? [], chainCount: g.__piwiPickChainCount };
|
|
@@ -13929,10 +14445,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13929
14445
|
failing,
|
|
13930
14446
|
choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
|
|
13931
14447
|
});
|
|
13932
|
-
await
|
|
13933
|
-
timeout: 0,
|
|
13934
|
-
polling: 250
|
|
13935
|
-
});
|
|
14448
|
+
if (!await answered("__piwiPickChoice")) return null;
|
|
13936
14449
|
const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
|
|
13937
14450
|
if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
|
|
13938
14451
|
const picked = { ...ranked[choice], pickedByUser: true };
|
|
@@ -13951,7 +14464,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13951
14464
|
...anchors.length > 0 ? { anchors } : {}
|
|
13952
14465
|
};
|
|
13953
14466
|
} finally {
|
|
13954
|
-
await cleanupPicker(page);
|
|
14467
|
+
await cleanupPicker(page, ended?.aborted);
|
|
13955
14468
|
}
|
|
13956
14469
|
}
|
|
13957
14470
|
async function runLocatorPicker(page, testInfo, failed, probe) {
|
|
@@ -14006,7 +14519,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
14006
14519
|
}
|
|
14007
14520
|
|
|
14008
14521
|
// src/internal/capture/pause-at.ts
|
|
14009
|
-
var
|
|
14522
|
+
var path28 = __toESM(require("path"));
|
|
14010
14523
|
|
|
14011
14524
|
// ../core/src/editor-send.ts
|
|
14012
14525
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -14047,18 +14560,32 @@ var PauseSet = class {
|
|
|
14047
14560
|
return col > 0 && this.lines.has(callerLocation.slice(0, col));
|
|
14048
14561
|
}
|
|
14049
14562
|
};
|
|
14050
|
-
var
|
|
14563
|
+
var NO_BREAKPOINTS = new PauseSet(/* @__PURE__ */ new Set());
|
|
14051
14564
|
function callSiteFile(file, cwd) {
|
|
14052
14565
|
const slashed = file.replace(/\\/g, "/");
|
|
14053
|
-
const absolute =
|
|
14054
|
-
let rel = absolute ?
|
|
14055
|
-
rel = rel.split(
|
|
14566
|
+
const absolute = path28.isAbsolute(file) || path28.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
|
|
14567
|
+
let rel = absolute ? path28.relative(cwd, file) : path28.normalize(slashed);
|
|
14568
|
+
rel = rel.split(path28.sep).join("/").replace(/\\/g, "/");
|
|
14056
14569
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
14057
14570
|
return rel;
|
|
14058
14571
|
}
|
|
14572
|
+
function stopCallSite(location, cwd = process.cwd()) {
|
|
14573
|
+
if (!location.line) return null;
|
|
14574
|
+
return `${callSiteFile(location.file, cwd)}:${location.line}:${location.column ?? 0}`;
|
|
14575
|
+
}
|
|
14576
|
+
function callSiteLine(callSite) {
|
|
14577
|
+
const col = callSite.lastIndexOf(":");
|
|
14578
|
+
return col > 0 ? callSite.slice(0, col) : callSite;
|
|
14579
|
+
}
|
|
14580
|
+
function inFlightCallSite(inFlight) {
|
|
14581
|
+
const [first] = inFlight;
|
|
14582
|
+
if (first === void 0) return null;
|
|
14583
|
+
const line = callSiteLine(first);
|
|
14584
|
+
return inFlight.every((site) => callSiteLine(site) === line) ? first : null;
|
|
14585
|
+
}
|
|
14059
14586
|
var ignoredLogged = /* @__PURE__ */ new Set();
|
|
14060
14587
|
function parsePauseAt(value, cwd = process.cwd()) {
|
|
14061
|
-
if (!value?.trim()) return
|
|
14588
|
+
if (!value?.trim()) return NO_BREAKPOINTS;
|
|
14062
14589
|
const lines = /* @__PURE__ */ new Set();
|
|
14063
14590
|
for (const raw of value.split(";")) {
|
|
14064
14591
|
const entry = raw.trim();
|
|
@@ -14074,7 +14601,7 @@ function parsePauseAt(value, cwd = process.cwd()) {
|
|
|
14074
14601
|
}
|
|
14075
14602
|
lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
|
|
14076
14603
|
}
|
|
14077
|
-
return lines.size ? new PauseSet(lines) :
|
|
14604
|
+
return lines.size ? new PauseSet(lines) : NO_BREAKPOINTS;
|
|
14078
14605
|
}
|
|
14079
14606
|
var parsed = null;
|
|
14080
14607
|
function currentPauseSet() {
|
|
@@ -14084,27 +14611,121 @@ function currentPauseSet() {
|
|
|
14084
14611
|
}
|
|
14085
14612
|
function pauseGateFromTestInfo(testInfo) {
|
|
14086
14613
|
const use = testInfo.project?.use ?? {};
|
|
14087
|
-
|
|
14614
|
+
const debug = testInfo._configInternal?.configCLIOverrides?.debug;
|
|
14615
|
+
return { ci: process.env.CI, headless: use.headless, pwdebug: process.env.PWDEBUG, debug };
|
|
14088
14616
|
}
|
|
14089
14617
|
function pauseSkipReason(gate) {
|
|
14090
14618
|
if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
|
|
14091
14619
|
if (gate.headless !== false)
|
|
14092
14620
|
return "the browser is headless \u2014 re-run with --headed (or set use: { headless: false })";
|
|
14621
|
+
if (gate.pwdebug && !["0", "false", "console"].includes(gate.pwdebug))
|
|
14622
|
+
return "Playwright's Inspector runs the test (PWDEBUG) \u2014 run without --debug";
|
|
14623
|
+
if (typeof gate.debug === "string" && gate.debug)
|
|
14624
|
+
return `Playwright's debugger runs the test (--debug=${gate.debug}) \u2014 run without --debug`;
|
|
14093
14625
|
return null;
|
|
14094
14626
|
}
|
|
14095
|
-
function
|
|
14096
|
-
|
|
14627
|
+
function atStop(state, callSite) {
|
|
14628
|
+
if (callSite && !state.finished) {
|
|
14629
|
+
if (state.step) {
|
|
14630
|
+
state.step = false;
|
|
14631
|
+
return { show: "step" };
|
|
14632
|
+
}
|
|
14633
|
+
if (state.set.matches(callSite)) return { show: "breakpoint" };
|
|
14634
|
+
}
|
|
14635
|
+
return { go: goOn(state) };
|
|
14636
|
+
}
|
|
14637
|
+
function goOn(state, answer) {
|
|
14638
|
+
if (answer === "step") state.step = true;
|
|
14639
|
+
else if (answer === "finish") state.finished = true;
|
|
14640
|
+
return !state.finished && (state.step || state.set.size > 0) ? "next" : "resume";
|
|
14641
|
+
}
|
|
14642
|
+
function contextDebugger(context) {
|
|
14643
|
+
const dbg = context.debugger;
|
|
14644
|
+
return typeof dbg?.requestPause === "function" && typeof dbg.on === "function" ? dbg : null;
|
|
14645
|
+
}
|
|
14646
|
+
var WATCHED = /* @__PURE__ */ new WeakSet();
|
|
14647
|
+
function watchStops(context, onStop) {
|
|
14648
|
+
const dbg = contextDebugger(context);
|
|
14649
|
+
if (!dbg) return false;
|
|
14650
|
+
if (WATCHED.has(context)) return true;
|
|
14651
|
+
WATCHED.add(context);
|
|
14652
|
+
inspectorSwitch.watching = true;
|
|
14653
|
+
syncInspectorSwitch();
|
|
14654
|
+
dbg.on("pausedstatechanged", () => onStop(context));
|
|
14655
|
+
return true;
|
|
14656
|
+
}
|
|
14657
|
+
var NO_INSPECTOR_ENV = "PW_CODEGEN_NO_INSPECTOR";
|
|
14658
|
+
var inspectorSwitch = {
|
|
14659
|
+
/** Set by Piwi, so Piwi's to clear. */
|
|
14660
|
+
set: false,
|
|
14661
|
+
/** Piwi answers the stops of a context in this worker. */
|
|
14662
|
+
watching: false,
|
|
14663
|
+
/** Every `page.pause()` of the worker's Playwright reaches it through Piwi (`markPausesWrapped`). */
|
|
14664
|
+
wrapped: false,
|
|
14665
|
+
/** The `page.pause()` calls left to Playwright that are running (`withInspector`). */
|
|
14666
|
+
open: 0
|
|
14667
|
+
};
|
|
14668
|
+
function syncInspectorSwitch() {
|
|
14669
|
+
const closed = inspectorSwitch.watching && inspectorSwitch.wrapped && inspectorSwitch.open === 0;
|
|
14670
|
+
if (closed && process.env[NO_INSPECTOR_ENV] === void 0) {
|
|
14671
|
+
process.env[NO_INSPECTOR_ENV] = "1";
|
|
14672
|
+
inspectorSwitch.set = true;
|
|
14673
|
+
} else if (!closed && inspectorSwitch.set) {
|
|
14674
|
+
if (process.env[NO_INSPECTOR_ENV] === "1") delete process.env[NO_INSPECTOR_ENV];
|
|
14675
|
+
inspectorSwitch.set = false;
|
|
14676
|
+
}
|
|
14677
|
+
}
|
|
14678
|
+
function markPausesWrapped() {
|
|
14679
|
+
inspectorSwitch.wrapped = true;
|
|
14680
|
+
syncInspectorSwitch();
|
|
14681
|
+
}
|
|
14682
|
+
async function withInspector(run) {
|
|
14683
|
+
inspectorSwitch.open++;
|
|
14684
|
+
syncInspectorSwitch();
|
|
14685
|
+
try {
|
|
14686
|
+
return await run();
|
|
14687
|
+
} finally {
|
|
14688
|
+
inspectorSwitch.open--;
|
|
14689
|
+
syncInspectorSwitch();
|
|
14690
|
+
}
|
|
14691
|
+
}
|
|
14692
|
+
async function steerDebugger(context, how) {
|
|
14693
|
+
const dbg = contextDebugger(context);
|
|
14694
|
+
if (!dbg) return;
|
|
14695
|
+
try {
|
|
14696
|
+
await internalCall(
|
|
14697
|
+
context,
|
|
14698
|
+
() => how === "pause" ? dbg.requestPause() : how === "next" ? dbg.next() : dbg.resume()
|
|
14699
|
+
);
|
|
14700
|
+
} catch {
|
|
14701
|
+
}
|
|
14702
|
+
}
|
|
14703
|
+
async function withTestTimeoutHeld(testInfo, run) {
|
|
14704
|
+
const set = testInfo._setIgnoreTimeouts;
|
|
14705
|
+
if (typeof set !== "function") return run();
|
|
14706
|
+
set.call(testInfo, true);
|
|
14707
|
+
try {
|
|
14708
|
+
return await run();
|
|
14709
|
+
} finally {
|
|
14710
|
+
set.call(testInfo, false);
|
|
14711
|
+
}
|
|
14712
|
+
}
|
|
14713
|
+
function testTimeSlot(testInfo) {
|
|
14714
|
+
try {
|
|
14715
|
+
const slot = testInfo._timeoutManager?.defaultSlot?.();
|
|
14716
|
+
return slot && typeof slot.elapsed === "number" ? slot : null;
|
|
14717
|
+
} catch {
|
|
14718
|
+
return null;
|
|
14719
|
+
}
|
|
14720
|
+
}
|
|
14721
|
+
function restoreTestTime(testInfo, heldAt) {
|
|
14722
|
+
const slot = testTimeSlot(testInfo);
|
|
14723
|
+
if (slot && Number.isFinite(heldAt) && heldAt >= 0 && heldAt < slot.elapsed) slot.elapsed = heldAt;
|
|
14097
14724
|
}
|
|
14098
14725
|
function pausePlace(callerLocation) {
|
|
14099
|
-
const
|
|
14100
|
-
const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
|
|
14726
|
+
const fileLine = callSiteLine(callerLocation);
|
|
14101
14727
|
return fileLine.slice(fileLine.lastIndexOf("/") + 1);
|
|
14102
14728
|
}
|
|
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
14729
|
function pickPlace(callerLocation) {
|
|
14109
14730
|
const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
|
|
14110
14731
|
if (!m) return null;
|
|
@@ -14149,57 +14770,571 @@ function logPauseSkipOnce(reason) {
|
|
|
14149
14770
|
skipLogged = true;
|
|
14150
14771
|
console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
|
|
14151
14772
|
}
|
|
14773
|
+
var NAVIGATED = /Execution context was destroyed|Cannot find context with specified id/;
|
|
14774
|
+
var MAX_NAVIGATED = 10;
|
|
14152
14775
|
var HIGHLIGHT_MS = 2e3;
|
|
14153
|
-
async function pauseHere(page,
|
|
14154
|
-
const before = testInfo.timeout;
|
|
14155
|
-
const started = Date.now();
|
|
14776
|
+
async function pauseHere(page, point, probe, onPick, onRecord, ended) {
|
|
14156
14777
|
let shown = null;
|
|
14157
14778
|
try {
|
|
14158
|
-
testInfo.setTimeout(0);
|
|
14159
14779
|
const highlight = point.target?.highlight;
|
|
14160
14780
|
if (typeof highlight === "function") {
|
|
14161
14781
|
let timer;
|
|
14162
14782
|
shown = await Promise.race([
|
|
14163
14783
|
Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
|
|
14164
|
-
new Promise((
|
|
14165
|
-
timer = setTimeout(() =>
|
|
14784
|
+
new Promise((resolve11) => {
|
|
14785
|
+
timer = setTimeout(() => resolve11(null), HIGHLIGHT_MS);
|
|
14166
14786
|
})
|
|
14167
14787
|
]);
|
|
14168
14788
|
clearTimeout(timer);
|
|
14169
14789
|
}
|
|
14170
14790
|
const arg = {
|
|
14171
14791
|
place: pausePlace(point.location),
|
|
14172
|
-
attempt:
|
|
14173
|
-
action: point.
|
|
14174
|
-
locator:
|
|
14792
|
+
attempt: point.attempt,
|
|
14793
|
+
action: point.call,
|
|
14794
|
+
locator: null,
|
|
14795
|
+
record: !!onRecord
|
|
14175
14796
|
};
|
|
14176
14797
|
console.log(
|
|
14177
|
-
`[piwi] Paused at ${point.location} before ${point.
|
|
14798
|
+
`[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
14799
|
);
|
|
14800
|
+
let navigated = 0;
|
|
14179
14801
|
for (; ; ) {
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14802
|
+
try {
|
|
14803
|
+
if (ended?.aborted) return "resume";
|
|
14804
|
+
await page.evaluate(showPauseBar, arg);
|
|
14805
|
+
await unlessEnded(page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 }), ended);
|
|
14806
|
+
if (ended?.aborted) return "resume";
|
|
14807
|
+
const choice = await page.evaluate(takePauseState);
|
|
14808
|
+
navigated = 0;
|
|
14809
|
+
if (choice === "resume" || choice === "step" || choice === "finish") return choice;
|
|
14810
|
+
if (choice === "record" && onRecord) {
|
|
14811
|
+
await unlessEnded(
|
|
14812
|
+
onRecord(ended).catch(() => void 0),
|
|
14813
|
+
ended
|
|
14814
|
+
);
|
|
14815
|
+
continue;
|
|
14816
|
+
}
|
|
14817
|
+
if (choice !== "pick") continue;
|
|
14818
|
+
const picked = await pickLocator(page, probe, null, ended).catch(() => null);
|
|
14819
|
+
if (picked && !ended?.aborted) await onPick(picked);
|
|
14820
|
+
} catch (error) {
|
|
14821
|
+
if (page.isClosed() || !NAVIGATED.test(String(error)) || ++navigated > MAX_NAVIGATED) throw error;
|
|
14822
|
+
await page.waitForLoadState("domcontentloaded").catch(() => void 0);
|
|
14823
|
+
}
|
|
14187
14824
|
}
|
|
14188
14825
|
} catch {
|
|
14189
14826
|
return "resume";
|
|
14190
14827
|
} finally {
|
|
14191
14828
|
try {
|
|
14829
|
+
if (ended?.aborted) await page.evaluate(takePauseState);
|
|
14192
14830
|
const dispose = shown?.dispose;
|
|
14193
14831
|
const hide = page.hideHighlight;
|
|
14194
14832
|
if (typeof dispose === "function") await dispose.call(shown);
|
|
14195
14833
|
else if (typeof hide === "function") await hide.call(page);
|
|
14196
14834
|
} catch {
|
|
14197
14835
|
}
|
|
14836
|
+
}
|
|
14837
|
+
}
|
|
14838
|
+
|
|
14839
|
+
// src/internal/capture/record-handoff.ts
|
|
14840
|
+
var fs26 = __toESM(require("fs"));
|
|
14841
|
+
var net2 = __toESM(require("net"));
|
|
14842
|
+
var path29 = __toESM(require("path"));
|
|
14843
|
+
|
|
14844
|
+
// ../core/src/ide-recorder.ts
|
|
14845
|
+
var IDE_RECORDER_BINDING = "__piwiRecorder";
|
|
14846
|
+
var IDE_DISPATCH_GLOBAL = "__piwiIdeDispatch";
|
|
14847
|
+
var RECORD_CHANNEL_ENV = "PIWI_RECORD_CHANNEL";
|
|
14848
|
+
var MAX_HANDOFF_LINE = 64e6;
|
|
14849
|
+
function parseRecordChannel(value) {
|
|
14850
|
+
const text = value?.trim() ?? "";
|
|
14851
|
+
const hash = text.lastIndexOf("#");
|
|
14852
|
+
if (hash <= 0) return null;
|
|
14853
|
+
const token = text.slice(hash + 1);
|
|
14854
|
+
const path34 = text.slice(0, hash);
|
|
14855
|
+
return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path34.includes("\0") ? { path: path34, token } : null;
|
|
14856
|
+
}
|
|
14857
|
+
function isObject2(value) {
|
|
14858
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14859
|
+
}
|
|
14860
|
+
function parseHandoffToTest(line) {
|
|
14861
|
+
let value;
|
|
14862
|
+
try {
|
|
14863
|
+
value = JSON.parse(line);
|
|
14864
|
+
} catch {
|
|
14865
|
+
return null;
|
|
14866
|
+
}
|
|
14867
|
+
if (!isObject2(value)) return null;
|
|
14868
|
+
switch (value.type) {
|
|
14869
|
+
case "refused":
|
|
14870
|
+
return typeof value.message === "string" ? { type: "refused", message: value.message } : null;
|
|
14871
|
+
case "load":
|
|
14872
|
+
return typeof value.bundle === "string" && value.bundle ? { type: "load", bundle: value.bundle } : null;
|
|
14873
|
+
case "answer":
|
|
14874
|
+
return typeof value.call === "number" ? { type: "answer", call: value.call, value: value.value } : null;
|
|
14875
|
+
case "dispatch":
|
|
14876
|
+
return { type: "dispatch", message: value.message };
|
|
14877
|
+
case "stop":
|
|
14878
|
+
return { type: "stop" };
|
|
14879
|
+
default:
|
|
14880
|
+
return null;
|
|
14881
|
+
}
|
|
14882
|
+
}
|
|
14883
|
+
function lineReader(onLine, onOverflow) {
|
|
14884
|
+
let buffered = "";
|
|
14885
|
+
return (chunk) => {
|
|
14886
|
+
buffered += chunk;
|
|
14887
|
+
for (let end = buffered.indexOf("\n"); end >= 0; end = buffered.indexOf("\n")) {
|
|
14888
|
+
const line = buffered.slice(0, end);
|
|
14889
|
+
buffered = buffered.slice(end + 1);
|
|
14890
|
+
if (line.trim()) onLine(line);
|
|
14891
|
+
}
|
|
14892
|
+
if (buffered.length > MAX_HANDOFF_LINE) {
|
|
14893
|
+
buffered = "";
|
|
14894
|
+
onOverflow();
|
|
14895
|
+
}
|
|
14896
|
+
};
|
|
14897
|
+
}
|
|
14898
|
+
|
|
14899
|
+
// src/internal/capture/record-handoff.ts
|
|
14900
|
+
var START_WAIT_MS = 6e4;
|
|
14901
|
+
var NEW_TAB_NOTICE = "A new tab opened: what you do there is not recorded yet. Record in the first tab.";
|
|
14902
|
+
var relays = /* @__PURE__ */ new WeakMap();
|
|
14903
|
+
function idleAnswer(payload) {
|
|
14904
|
+
const request3 = payload ?? {};
|
|
14905
|
+
if (request3.kind === "local-get") return {};
|
|
14906
|
+
if (request3.kind !== "message") return null;
|
|
14907
|
+
if (request3.message?.type === "piwi-ping") return { ok: true };
|
|
14908
|
+
if (request3.message?.type === "piwi-session-storage") {
|
|
14909
|
+
return request3.message.op === "get" ? { ok: true, items: {} } : { ok: true };
|
|
14910
|
+
}
|
|
14911
|
+
return { ok: false, error: "No recording runs in this page." };
|
|
14912
|
+
}
|
|
14913
|
+
function canRecordFromHere(channel = process.env[RECORD_CHANNEL_ENV]) {
|
|
14914
|
+
return parseRecordChannel(channel) !== null;
|
|
14915
|
+
}
|
|
14916
|
+
function handoffPlace(location, cwd = process.cwd()) {
|
|
14917
|
+
const m = /^(.+):(\d+):\d+$/.exec(location);
|
|
14918
|
+
if (!m) return null;
|
|
14919
|
+
return { file: path29.resolve(cwd, m[1]), line: Number(m[2]) };
|
|
14920
|
+
}
|
|
14921
|
+
async function recordFromHere(options) {
|
|
14922
|
+
const channel = parseRecordChannel(options.channel ?? process.env[RECORD_CHANNEL_ENV]);
|
|
14923
|
+
const place = handoffPlace(options.location);
|
|
14924
|
+
if (!channel || !place) return "this run names no editor to record into.";
|
|
14925
|
+
const { page, testInfo, ended } = options;
|
|
14926
|
+
if (ended?.aborted) return "the test ended.";
|
|
14927
|
+
const use = testInfo.project?.use ?? {};
|
|
14928
|
+
const context = page.context();
|
|
14929
|
+
return new Promise((resolve11) => {
|
|
14930
|
+
const socket = net2.connect(channel.path);
|
|
14931
|
+
socket.setEncoding("utf8");
|
|
14932
|
+
const answers = /* @__PURE__ */ new Map();
|
|
14933
|
+
let calls = 0;
|
|
14934
|
+
let attached = false;
|
|
14935
|
+
let recording = false;
|
|
14936
|
+
let settled = false;
|
|
14937
|
+
let initScript;
|
|
14938
|
+
const send = (message) => {
|
|
14939
|
+
if (!socket.destroyed) socket.write(`${JSON.stringify(message)}
|
|
14940
|
+
`);
|
|
14941
|
+
};
|
|
14942
|
+
const onPage = () => send({ type: "notice", message: NEW_TAB_NOTICE });
|
|
14943
|
+
const onGone = () => {
|
|
14944
|
+
if (attached) send({ type: "closed" });
|
|
14945
|
+
finish("the page closed.");
|
|
14946
|
+
};
|
|
14947
|
+
const onEnded = () => {
|
|
14948
|
+
try {
|
|
14949
|
+
if (recording) {
|
|
14950
|
+
void internalCall(
|
|
14951
|
+
page,
|
|
14952
|
+
() => page.evaluate(
|
|
14953
|
+
([name, value]) => {
|
|
14954
|
+
const dispatch = globalThis[name];
|
|
14955
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
14956
|
+
},
|
|
14957
|
+
[IDE_DISPATCH_GLOBAL, { type: "piwi-recording-stopped" }]
|
|
14958
|
+
)
|
|
14959
|
+
).catch(() => void 0);
|
|
14960
|
+
}
|
|
14961
|
+
if (attached) send({ type: "closed" });
|
|
14962
|
+
finish("the test ended.");
|
|
14963
|
+
} catch {
|
|
14964
|
+
}
|
|
14965
|
+
};
|
|
14966
|
+
page.once("close", onGone);
|
|
14967
|
+
page.once("crash", onGone);
|
|
14968
|
+
ended?.addEventListener("abort", onEnded, { once: true });
|
|
14969
|
+
const startWait = setTimeout(() => finish("the editor did not start the recording."), START_WAIT_MS);
|
|
14970
|
+
const release = () => {
|
|
14971
|
+
const relay = relays.get(context);
|
|
14972
|
+
if (relay && relay.recording === relayed) relay.recording = null;
|
|
14973
|
+
void Promise.resolve(initScript?.dispose?.()).catch(() => void 0);
|
|
14974
|
+
initScript = void 0;
|
|
14975
|
+
};
|
|
14976
|
+
const finish = (said) => {
|
|
14977
|
+
if (settled) return;
|
|
14978
|
+
settled = true;
|
|
14979
|
+
clearTimeout(startWait);
|
|
14980
|
+
release();
|
|
14981
|
+
for (const [call, answer] of answers) {
|
|
14982
|
+
answers.delete(call);
|
|
14983
|
+
answer(null);
|
|
14984
|
+
}
|
|
14985
|
+
context.off("page", onPage);
|
|
14986
|
+
page.off("close", onGone);
|
|
14987
|
+
page.off("crash", onGone);
|
|
14988
|
+
ended?.removeEventListener("abort", onEnded);
|
|
14989
|
+
socket.end();
|
|
14990
|
+
resolve11(recording ? null : said);
|
|
14991
|
+
};
|
|
14992
|
+
const relayed = (source, payload) => new Promise((answer) => {
|
|
14993
|
+
const call = ++calls;
|
|
14994
|
+
answers.set(call, answer);
|
|
14995
|
+
const outside = source.page !== page || source.frame !== page.mainFrame();
|
|
14996
|
+
send({ type: "binding", call, outside, payload });
|
|
14997
|
+
});
|
|
14998
|
+
const load = async (bundle) => {
|
|
14999
|
+
if (!/^https?:/i.test(page.url())) {
|
|
15000
|
+
const why = `the recorder records http(s) pages only, and this page is ${page.url() || "blank"}.`;
|
|
15001
|
+
send({ type: "notice", message: `Nothing can be recorded here: ${why}` });
|
|
15002
|
+
finish(why);
|
|
15003
|
+
return;
|
|
15004
|
+
}
|
|
15005
|
+
let relay = relays.get(context);
|
|
15006
|
+
if (!relay) {
|
|
15007
|
+
const created = { recording: null, lingering: false };
|
|
15008
|
+
await context.exposeBinding(
|
|
15009
|
+
IDE_RECORDER_BINDING,
|
|
15010
|
+
(source, payload) => created.recording ? created.recording(source, payload) : idleAnswer(payload)
|
|
15011
|
+
);
|
|
15012
|
+
relays.set(context, created);
|
|
15013
|
+
relay = created;
|
|
15014
|
+
}
|
|
15015
|
+
if (settled) return;
|
|
15016
|
+
relay.recording = relayed;
|
|
15017
|
+
if (!relay.lingering) {
|
|
15018
|
+
initScript = await context.addInitScript({ path: bundle });
|
|
15019
|
+
if (typeof initScript?.dispose !== "function") relay.lingering = true;
|
|
15020
|
+
}
|
|
15021
|
+
if (settled) return release();
|
|
15022
|
+
await page.evaluate(fs26.readFileSync(bundle, "utf-8"));
|
|
15023
|
+
if (settled) return release();
|
|
15024
|
+
recording = true;
|
|
15025
|
+
clearTimeout(startWait);
|
|
15026
|
+
context.on("page", onPage);
|
|
15027
|
+
send({ type: "loaded" });
|
|
15028
|
+
};
|
|
15029
|
+
socket.on("connect", () => {
|
|
15030
|
+
attached = true;
|
|
15031
|
+
send({
|
|
15032
|
+
type: "attach",
|
|
15033
|
+
token: channel.token,
|
|
15034
|
+
file: place.file,
|
|
15035
|
+
line: place.line,
|
|
15036
|
+
reason: options.reason,
|
|
15037
|
+
testIdAttribute: typeof use.testIdAttribute === "string" ? use.testIdAttribute : null,
|
|
15038
|
+
baseURL: typeof use.baseURL === "string" ? use.baseURL : null
|
|
15039
|
+
});
|
|
15040
|
+
});
|
|
15041
|
+
socket.on(
|
|
15042
|
+
"data",
|
|
15043
|
+
lineReader(
|
|
15044
|
+
(line) => {
|
|
15045
|
+
const message = parseHandoffToTest(line);
|
|
15046
|
+
if (!message || settled) return;
|
|
15047
|
+
switch (message.type) {
|
|
15048
|
+
case "refused":
|
|
15049
|
+
return finish(message.message);
|
|
15050
|
+
case "load":
|
|
15051
|
+
return void load(message.bundle).catch(
|
|
15052
|
+
(error) => finish(`the recorder could not be loaded: ${error instanceof Error ? error.message : String(error)}`)
|
|
15053
|
+
);
|
|
15054
|
+
case "answer": {
|
|
15055
|
+
const answer = answers.get(message.call);
|
|
15056
|
+
answers.delete(message.call);
|
|
15057
|
+
return answer?.(message.value);
|
|
15058
|
+
}
|
|
15059
|
+
case "dispatch":
|
|
15060
|
+
return void page.evaluate(
|
|
15061
|
+
([name, value]) => {
|
|
15062
|
+
const dispatch = globalThis[name];
|
|
15063
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
15064
|
+
},
|
|
15065
|
+
[IDE_DISPATCH_GLOBAL, message.message]
|
|
15066
|
+
).catch(() => void 0);
|
|
15067
|
+
case "stop":
|
|
15068
|
+
return finish(null);
|
|
15069
|
+
}
|
|
15070
|
+
},
|
|
15071
|
+
() => finish("the editor sent a message too long to read.")
|
|
15072
|
+
)
|
|
15073
|
+
);
|
|
15074
|
+
socket.on("error", (error) => finish(`the editor could not be reached: ${error.message}`));
|
|
15075
|
+
socket.on("close", () => finish("the editor closed the recording."));
|
|
15076
|
+
});
|
|
15077
|
+
}
|
|
15078
|
+
|
|
15079
|
+
// src/internal/capture/css-coverage.ts
|
|
15080
|
+
var crypto7 = __toESM(require("crypto"));
|
|
15081
|
+
var fs27 = __toESM(require("fs"));
|
|
15082
|
+
|
|
15083
|
+
// ../core/src/css-rules.ts
|
|
15084
|
+
var GROUPING = /* @__PURE__ */ new Set(["media", "supports", "layer", "container", "scope", "starting-style", "document"]);
|
|
15085
|
+
function parseCssRules(text) {
|
|
15086
|
+
const rules = [];
|
|
15087
|
+
const open = [];
|
|
15088
|
+
let preludeStart = -1;
|
|
15089
|
+
for (let i = 0; i < text.length; i++) {
|
|
15090
|
+
const c = text[i];
|
|
15091
|
+
if (c === "/" && text[i + 1] === "*") {
|
|
15092
|
+
const end = text.indexOf("*/", i + 2);
|
|
15093
|
+
i = end < 0 ? text.length : end + 1;
|
|
15094
|
+
continue;
|
|
15095
|
+
}
|
|
15096
|
+
if (c === '"' || c === "'") {
|
|
15097
|
+
if (preludeStart < 0) preludeStart = i;
|
|
15098
|
+
for (i++; i < text.length && text[i] !== c && text[i] !== "\n"; i++) if (text[i] === "\\") i++;
|
|
15099
|
+
continue;
|
|
15100
|
+
}
|
|
15101
|
+
if (c === "\\") {
|
|
15102
|
+
if (preludeStart < 0) preludeStart = i;
|
|
15103
|
+
i++;
|
|
15104
|
+
continue;
|
|
15105
|
+
}
|
|
15106
|
+
if ((c === "u" || c === "U") && /^url\(/i.test(text.slice(i, i + 4)) && !/[\w-]/.test(text[i - 1] ?? "")) {
|
|
15107
|
+
let j = i + 4;
|
|
15108
|
+
while (j < text.length && /\s/.test(text[j])) j++;
|
|
15109
|
+
if (text[j] !== '"' && text[j] !== "'") {
|
|
15110
|
+
if (preludeStart < 0) preludeStart = i;
|
|
15111
|
+
for (; j < text.length && text[j] !== ")"; j++) if (text[j] === "\\") j++;
|
|
15112
|
+
i = j;
|
|
15113
|
+
continue;
|
|
15114
|
+
}
|
|
15115
|
+
}
|
|
15116
|
+
if (text.startsWith("<!--", i) || text.startsWith("-->", i)) {
|
|
15117
|
+
i += text.startsWith("<!--", i) ? 3 : 2;
|
|
15118
|
+
continue;
|
|
15119
|
+
}
|
|
15120
|
+
if (c === "{") {
|
|
15121
|
+
const start = preludeStart < 0 ? i : preludeStart;
|
|
15122
|
+
const atRule = /^@([\w-]+)/.exec(text.slice(start, i));
|
|
15123
|
+
const parent = open[open.length - 1];
|
|
15124
|
+
const kind = atRule ? GROUPING.has(atRule[1].toLowerCase()) ? "group" : "other" : parent?.kind === "other" ? "other" : "style";
|
|
15125
|
+
open.push({ kind, start, open: i });
|
|
15126
|
+
preludeStart = -1;
|
|
15127
|
+
continue;
|
|
15128
|
+
}
|
|
15129
|
+
if (c === "}") {
|
|
15130
|
+
const block = open.pop();
|
|
15131
|
+
if (block && block.kind !== "other")
|
|
15132
|
+
rules.push({ kind: block.kind, start: block.start, open: block.open, end: i + 1 });
|
|
15133
|
+
preludeStart = -1;
|
|
15134
|
+
continue;
|
|
15135
|
+
}
|
|
15136
|
+
if (c === ";") {
|
|
15137
|
+
preludeStart = -1;
|
|
15138
|
+
continue;
|
|
15139
|
+
}
|
|
15140
|
+
if (preludeStart < 0 && !/\s/.test(c)) preludeStart = i;
|
|
15141
|
+
}
|
|
15142
|
+
for (const block of open) {
|
|
15143
|
+
if (block.kind !== "other")
|
|
15144
|
+
rules.push({ kind: block.kind, start: block.start, open: block.open, end: text.length });
|
|
15145
|
+
}
|
|
15146
|
+
return rules.sort((a, b) => a.start - b.start);
|
|
15147
|
+
}
|
|
15148
|
+
function usedCssRules(rules, used) {
|
|
15149
|
+
const styleAt = /* @__PURE__ */ new Map();
|
|
15150
|
+
const groups = [];
|
|
15151
|
+
for (const rule of rules) {
|
|
15152
|
+
if (rule.kind === "style") styleAt.set(rule.start, rule);
|
|
15153
|
+
else groups.push(rule);
|
|
15154
|
+
}
|
|
15155
|
+
const out = /* @__PURE__ */ new Map();
|
|
15156
|
+
for (const range of used) {
|
|
15157
|
+
const style = styleAt.get(range.startOffset);
|
|
15158
|
+
if (style) {
|
|
15159
|
+
out.set(`${style.start}`, [style.start, style.end]);
|
|
15160
|
+
continue;
|
|
15161
|
+
}
|
|
15162
|
+
const group = groups.find((rule) => rule.start <= range.startOffset && range.startOffset <= rule.open);
|
|
15163
|
+
if (group) out.set(`${group.start}`, [group.start, group.start + 1]);
|
|
15164
|
+
}
|
|
15165
|
+
return [...out.values()].sort((a, b) => a[0] - b[0]);
|
|
15166
|
+
}
|
|
15167
|
+
function allCssRules(rules) {
|
|
15168
|
+
return rules.map((rule) => rule.kind === "style" ? [rule.start, rule.end] : [rule.start, rule.start + 1]);
|
|
15169
|
+
}
|
|
15170
|
+
function cssRuleLines(text, offsets, options = {}) {
|
|
15171
|
+
const starts = lineStarts(text);
|
|
15172
|
+
const lineOffset = options.lineOffset ?? 0;
|
|
15173
|
+
const out = /* @__PURE__ */ new Map();
|
|
15174
|
+
const add2 = (source, range) => {
|
|
15175
|
+
const ranges = out.get(source);
|
|
15176
|
+
if (ranges) ranges.push(range);
|
|
15177
|
+
else out.set(source, [range]);
|
|
15178
|
+
};
|
|
15179
|
+
for (const [startOffset, endOffset] of offsets) {
|
|
15180
|
+
const start = offsetToPosition(starts, startOffset);
|
|
15181
|
+
const end = offsetToPosition(starts, Math.max(startOffset, endOffset - 1));
|
|
15182
|
+
if (!options.lookup) {
|
|
15183
|
+
add2(-1, [start.line + lineOffset + 1, end.line + lineOffset + 1]);
|
|
15184
|
+
continue;
|
|
15185
|
+
}
|
|
15186
|
+
const first = options.lookup.originalAt(start.line, start.column);
|
|
15187
|
+
if (!first) continue;
|
|
15188
|
+
const last = options.lookup.originalAt(end.line, end.column);
|
|
15189
|
+
const lastLine = last && last.source === first.source && last.line >= first.line ? last.line : first.line;
|
|
15190
|
+
add2(first.source, [first.line + 1, lastLine + 1]);
|
|
15191
|
+
}
|
|
15192
|
+
return out;
|
|
15193
|
+
}
|
|
15194
|
+
|
|
15195
|
+
// src/internal/capture/css-coverage.ts
|
|
15196
|
+
var STYLE_SOURCES = /\.(?:css|scss|sass|less|styl|stylus|pcss|postcss|vue|svelte|astro|html?)$/i;
|
|
15197
|
+
var BUILD_DIRS2 = /(?:^|\/)(?:dist|build|out|\.output|\.next|\.nuxt|\.svelte-kit)\//;
|
|
15198
|
+
var resolutions = /* @__PURE__ */ new Map();
|
|
15199
|
+
var MAX_CACHED_SHEETS = 2e3;
|
|
15200
|
+
function fileForStyleUrl(url, roots) {
|
|
15201
|
+
let pathname;
|
|
15202
|
+
try {
|
|
15203
|
+
const parsed2 = new URL(url);
|
|
15204
|
+
if (parsed2.protocol !== "http:" && parsed2.protocol !== "https:" && parsed2.protocol !== "file:") return null;
|
|
15205
|
+
pathname = parsed2.pathname;
|
|
15206
|
+
} catch {
|
|
15207
|
+
return null;
|
|
15208
|
+
}
|
|
15209
|
+
return sourceFile(decodeURIComponent(pathname), roots);
|
|
15210
|
+
}
|
|
15211
|
+
function sourceFile(source, roots) {
|
|
15212
|
+
const bare = source.replace(/[?#].*$/, "");
|
|
15213
|
+
if (!STYLE_SOURCES.test(bare) || /(?:^|\/)node_modules\//.test(bare)) return null;
|
|
15214
|
+
const file = resolveSourcePath(bare, roots);
|
|
15215
|
+
if (!file || BUILD_DIRS2.test(file.replace(/\\/g, "/")) || /(?:^|[\\/])node_modules[\\/]/.test(file)) return null;
|
|
15216
|
+
return file;
|
|
15217
|
+
}
|
|
15218
|
+
function styleBlockLine(file, index) {
|
|
15219
|
+
let text;
|
|
15220
|
+
try {
|
|
15221
|
+
text = fs27.readFileSync(file, "utf-8");
|
|
15222
|
+
} catch {
|
|
15223
|
+
return 0;
|
|
15224
|
+
}
|
|
15225
|
+
const blocks = [...text.matchAll(/^<style\b[^>]*>/gim)];
|
|
15226
|
+
const block = blocks[index];
|
|
15227
|
+
if (!block) return 0;
|
|
15228
|
+
return text.slice(0, block.index + block[0].length).split("\n").length - 1;
|
|
15229
|
+
}
|
|
15230
|
+
async function sheetMap(sheet, text, fetchMap) {
|
|
15231
|
+
const ref = sourceMappingUrl(text) ?? sheet.sourceMapURL ?? null;
|
|
15232
|
+
if (!ref) return null;
|
|
15233
|
+
let raw;
|
|
15234
|
+
if (ref.startsWith("data:")) raw = decodeDataUrl(ref);
|
|
15235
|
+
else {
|
|
14198
15236
|
try {
|
|
14199
|
-
|
|
15237
|
+
raw = await fetchMap(new URL(ref, sheet.sourceURL).href);
|
|
14200
15238
|
} catch {
|
|
15239
|
+
raw = null;
|
|
14201
15240
|
}
|
|
14202
15241
|
}
|
|
15242
|
+
if (!raw) return null;
|
|
15243
|
+
try {
|
|
15244
|
+
return decodeSourceMap(JSON.parse(raw));
|
|
15245
|
+
} catch {
|
|
15246
|
+
return null;
|
|
15247
|
+
}
|
|
15248
|
+
}
|
|
15249
|
+
function sameLines(text, rules, file, lineOffset) {
|
|
15250
|
+
let source;
|
|
15251
|
+
try {
|
|
15252
|
+
source = fs27.readFileSync(file, "utf-8");
|
|
15253
|
+
} catch {
|
|
15254
|
+
return false;
|
|
15255
|
+
}
|
|
15256
|
+
const fileLines = source.split(/\r?\n/);
|
|
15257
|
+
const sheetLineStarts = [0];
|
|
15258
|
+
for (let i = 0; i < text.length; i++) if (text[i] === "\n") sheetLineStarts.push(i + 1);
|
|
15259
|
+
let low = 0;
|
|
15260
|
+
for (const rule of rules) {
|
|
15261
|
+
while (low + 1 < sheetLineStarts.length && sheetLineStarts[low + 1] <= rule.start) low++;
|
|
15262
|
+
const head = text.slice(rule.start, rule.open).split("\n")[0].trim();
|
|
15263
|
+
if (head && !(fileLines[low + lineOffset] ?? "").includes(head)) return false;
|
|
15264
|
+
}
|
|
15265
|
+
return true;
|
|
15266
|
+
}
|
|
15267
|
+
async function resolveSheet(sheet, text, dirs, fetchMap) {
|
|
15268
|
+
const lookup = await sheetMap(sheet, text, fetchMap);
|
|
15269
|
+
const rules = parseCssRules(text);
|
|
15270
|
+
if (lookup) {
|
|
15271
|
+
const sources = lookup.sources.map((source) => {
|
|
15272
|
+
const direct = sourceFile(source, dirs);
|
|
15273
|
+
if (direct) return direct;
|
|
15274
|
+
try {
|
|
15275
|
+
return /^[a-z][\w+.-]*:/i.test(source) ? null : fileForStyleUrl(new URL(source, sheet.sourceURL).href, dirs);
|
|
15276
|
+
} catch {
|
|
15277
|
+
return null;
|
|
15278
|
+
}
|
|
15279
|
+
});
|
|
15280
|
+
return { rules, lookup, file: null, lineOffset: 0, sources, exact: true };
|
|
15281
|
+
}
|
|
15282
|
+
if (sheet.devId) {
|
|
15283
|
+
const file2 = sourceFile(sheet.devId, dirs);
|
|
15284
|
+
if (!file2) return null;
|
|
15285
|
+
const index = /[?&]index=(\d+)/.exec(sheet.devId);
|
|
15286
|
+
const lineOffset2 = /\.(?:vue|svelte|astro)$/i.test(file2) && index ? styleBlockLine(file2, Number(index[1])) : 0;
|
|
15287
|
+
return { rules, lookup: null, file: file2, lineOffset: lineOffset2, sources: [], exact: sameLines(text, rules, file2, lineOffset2) };
|
|
15288
|
+
}
|
|
15289
|
+
const file = fileForStyleUrl(sheet.sourceURL, dirs);
|
|
15290
|
+
if (!file) return null;
|
|
15291
|
+
const lineOffset = sheet.isInline ? sheet.startLine : 0;
|
|
15292
|
+
return { rules, lookup: null, file, lineOffset, sources: [], exact: sameLines(text, rules, file, lineOffset) };
|
|
15293
|
+
}
|
|
15294
|
+
function cachedResolution(sheet, text, dirs, fetchMap) {
|
|
15295
|
+
const version = crypto7.createHash("sha1").update(text).digest("hex");
|
|
15296
|
+
const key = [sheet.devId ?? sheet.sourceURL, sheet.isInline ? sheet.startLine : "", dirs.join("\0"), version].join(
|
|
15297
|
+
" "
|
|
15298
|
+
);
|
|
15299
|
+
let pending = resolutions.get(key);
|
|
15300
|
+
if (!pending) {
|
|
15301
|
+
pending = resolveSheet(sheet, text, dirs, fetchMap).catch(() => null);
|
|
15302
|
+
resolutions.set(key, pending);
|
|
15303
|
+
if (resolutions.size > MAX_CACHED_SHEETS) resolutions.delete(resolutions.keys().next().value);
|
|
15304
|
+
}
|
|
15305
|
+
return pending;
|
|
15306
|
+
}
|
|
15307
|
+
function add(into, file, ranges, repoRoot, fileLevel = false) {
|
|
15308
|
+
if (ranges.length === 0 && !fileLevel) return;
|
|
15309
|
+
const key = relativeToRepo(file, repoRoot);
|
|
15310
|
+
into.set(key, [...into.get(key) ?? [], ...ranges]);
|
|
15311
|
+
}
|
|
15312
|
+
async function resolveCssCoverage(sheets, roots, fetchMap) {
|
|
15313
|
+
const reached = /* @__PURE__ */ new Map();
|
|
15314
|
+
const known = /* @__PURE__ */ new Map();
|
|
15315
|
+
for (const sheet of sheets) {
|
|
15316
|
+
if (sheet.text === void 0) continue;
|
|
15317
|
+
const dirs = scriptRoots(roots, sheet.sourceURL, sheet.pageUrl);
|
|
15318
|
+
const resolution = await cachedResolution(sheet, sheet.text, dirs, fetchMap);
|
|
15319
|
+
if (!resolution) continue;
|
|
15320
|
+
const used = usedCssRules(resolution.rules, [...sheet.used.values()]);
|
|
15321
|
+
if (!resolution.exact) {
|
|
15322
|
+
if (resolution.file && used.length > 0) add(reached, resolution.file, [], roots.repoRoot, true);
|
|
15323
|
+
continue;
|
|
15324
|
+
}
|
|
15325
|
+
const options = { lookup: resolution.lookup, lineOffset: resolution.lineOffset };
|
|
15326
|
+
const fileOf2 = (source) => source === -1 ? resolution.file : resolution.sources[source] ?? null;
|
|
15327
|
+
const matched = cssRuleLines(sheet.text, used, options);
|
|
15328
|
+
for (const [source, ranges] of matched) {
|
|
15329
|
+
const file = fileOf2(source);
|
|
15330
|
+
if (file) add(reached, file, ranges, roots.repoRoot);
|
|
15331
|
+
}
|
|
15332
|
+
for (const [source, ranges] of cssRuleLines(sheet.text, allCssRules(resolution.rules), options)) {
|
|
15333
|
+
const file = fileOf2(source);
|
|
15334
|
+
if (file) add(known, file, ranges, roots.repoRoot);
|
|
15335
|
+
}
|
|
15336
|
+
}
|
|
15337
|
+
return { reached, known };
|
|
14203
15338
|
}
|
|
14204
15339
|
|
|
14205
15340
|
// src/internal/capture/page-interaction.ts
|
|
@@ -14497,17 +15632,27 @@ function createSink() {
|
|
|
14497
15632
|
pickOffered: false,
|
|
14498
15633
|
userPick: null,
|
|
14499
15634
|
pause: null,
|
|
15635
|
+
locatorCalls: /* @__PURE__ */ new Map(),
|
|
15636
|
+
stops: [],
|
|
15637
|
+
shownStops: /* @__PURE__ */ new Map(),
|
|
15638
|
+
released: [],
|
|
15639
|
+
releasedRequests: /* @__PURE__ */ new Set(),
|
|
15640
|
+
onReleasedRequestsEnded: null,
|
|
15641
|
+
releasedQuietSince: 0,
|
|
14500
15642
|
locatorPages: new LocatorPageLog(),
|
|
14501
|
-
codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
|
|
15643
|
+
codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map(), cssFiles: /* @__PURE__ */ new Map(), cssKnown: /* @__PURE__ */ new Map() },
|
|
14502
15644
|
serverCoverage: /* @__PURE__ */ new Map(),
|
|
14503
15645
|
baseURL: null
|
|
14504
15646
|
};
|
|
14505
15647
|
}
|
|
14506
15648
|
var MAX_CODE_REACH_PAGES = 16;
|
|
14507
15649
|
var CODE_REACH_START_WAIT_MS = 2e3;
|
|
15650
|
+
function pageCoverageInWorker() {
|
|
15651
|
+
return browserCoverageInWorker() || cssCoverageInWorker();
|
|
15652
|
+
}
|
|
14508
15653
|
function maybeStartCodeReach(page, alreadyOpen = false) {
|
|
14509
15654
|
const sink = currentSink;
|
|
14510
|
-
if (!sink || !
|
|
15655
|
+
if (!sink || !pageCoverageInWorker() || isPageClosed(page)) return Promise.resolve(false);
|
|
14511
15656
|
const known = sink.codeReach.sessions.get(page);
|
|
14512
15657
|
if (known) return known.started;
|
|
14513
15658
|
if (alreadyOpen) {
|
|
@@ -14520,9 +15665,9 @@ function maybeStartCodeReach(page, alreadyOpen = false) {
|
|
|
14520
15665
|
return started;
|
|
14521
15666
|
}
|
|
14522
15667
|
async function startCodeReachOnOpenPages() {
|
|
14523
|
-
if (!
|
|
15668
|
+
if (!pageCoverageInWorker() || OPEN_PAGES.size === 0) return;
|
|
14524
15669
|
const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
|
|
14525
|
-
await Promise.race([started, new Promise((
|
|
15670
|
+
await Promise.race([started, new Promise((resolve11) => setTimeout(resolve11, CODE_REACH_START_WAIT_MS).unref())]);
|
|
14526
15671
|
}
|
|
14527
15672
|
function configuredCodeReachRoots() {
|
|
14528
15673
|
const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
|
|
@@ -14536,7 +15681,7 @@ function configuredCodeReachRoots() {
|
|
|
14536
15681
|
}
|
|
14537
15682
|
} catch {
|
|
14538
15683
|
}
|
|
14539
|
-
return raw.split(
|
|
15684
|
+
return raw.split(path30.delimiter).filter(Boolean);
|
|
14540
15685
|
}
|
|
14541
15686
|
async function stopSinkCodeReach(sink, closing) {
|
|
14542
15687
|
const stopping = [];
|
|
@@ -14563,30 +15708,36 @@ async function stopSinkCodeReach(sink, closing) {
|
|
|
14563
15708
|
async function stopPageCodeReach(sink, page, session, roots) {
|
|
14564
15709
|
try {
|
|
14565
15710
|
if (!await session.started || isPageClosed(page)) return;
|
|
14566
|
-
const
|
|
14567
|
-
|
|
14568
|
-
const
|
|
15711
|
+
const { scripts, sheets } = await stopPageCoverage(page);
|
|
15712
|
+
const fetchMap = pageMapFetcher(page);
|
|
15713
|
+
const append = (into, from) => {
|
|
15714
|
+
for (const [file, ranges] of from) into.set(file, [...into.get(file) ?? [], ...ranges]);
|
|
15715
|
+
};
|
|
15716
|
+
if (sheets.length > 0) {
|
|
15717
|
+
const rules = await resolveCssCoverage(sheets, roots, fetchMap);
|
|
15718
|
+
append(sink.codeReach.cssFiles, rules.reached);
|
|
15719
|
+
append(sink.codeReach.cssKnown, rules.known);
|
|
15720
|
+
}
|
|
15721
|
+
if (!scripts) return;
|
|
15722
|
+
const { reached, known } = await resolveCodeReachFunctions(scripts, roots, fetchMap, page.url(), {
|
|
14569
15723
|
known: browserFunctionsInWorker()
|
|
14570
15724
|
});
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
}
|
|
14574
|
-
for (const [file, functions] of known) {
|
|
14575
|
-
sink.codeReach.known.set(file, [...sink.codeReach.known.get(file) ?? [], ...functions]);
|
|
14576
|
-
}
|
|
15725
|
+
append(sink.codeReach.files, reached);
|
|
15726
|
+
append(sink.codeReach.known, known);
|
|
14577
15727
|
} catch {
|
|
14578
15728
|
}
|
|
14579
15729
|
}
|
|
14580
15730
|
var currentSink = null;
|
|
14581
15731
|
var KNOWN_FUNCTIONS_SENT = /* @__PURE__ */ new Map();
|
|
15732
|
+
var KNOWN_CSS_RULES_SENT = /* @__PURE__ */ new Map();
|
|
14582
15733
|
var PENDING_PROBES = /* @__PURE__ */ new Set();
|
|
14583
15734
|
async function drainPendingProbes(capMs) {
|
|
14584
15735
|
if (PENDING_PROBES.size === 0) return;
|
|
14585
15736
|
let cap;
|
|
14586
15737
|
await Promise.race([
|
|
14587
15738
|
Promise.allSettled(PENDING_PROBES),
|
|
14588
|
-
new Promise((
|
|
14589
|
-
cap = setTimeout(
|
|
15739
|
+
new Promise((resolve11) => {
|
|
15740
|
+
cap = setTimeout(resolve11, capMs);
|
|
14590
15741
|
})
|
|
14591
15742
|
]);
|
|
14592
15743
|
clearTimeout(cap);
|
|
@@ -14626,10 +15777,10 @@ async function readWebVitals(page) {
|
|
|
14626
15777
|
const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
14627
15778
|
paint[key] = Math.round(entry.startTime);
|
|
14628
15779
|
}
|
|
14629
|
-
const readBuffered = (type, extra) => new Promise((
|
|
15780
|
+
const readBuffered = (type, extra) => new Promise((resolve11) => {
|
|
14630
15781
|
try {
|
|
14631
15782
|
const PO = globalThis.PerformanceObserver;
|
|
14632
|
-
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return
|
|
15783
|
+
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve11(null);
|
|
14633
15784
|
const out = [];
|
|
14634
15785
|
const po = new PO((list) => out.push(...list.getEntries()));
|
|
14635
15786
|
po.observe({ type, buffered: true, ...extra });
|
|
@@ -14639,10 +15790,10 @@ async function readWebVitals(page) {
|
|
|
14639
15790
|
po.disconnect();
|
|
14640
15791
|
} catch {
|
|
14641
15792
|
}
|
|
14642
|
-
|
|
15793
|
+
resolve11(out);
|
|
14643
15794
|
}, 0);
|
|
14644
15795
|
} catch {
|
|
14645
|
-
|
|
15796
|
+
resolve11(null);
|
|
14646
15797
|
}
|
|
14647
15798
|
});
|
|
14648
15799
|
const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
|
|
@@ -14757,12 +15908,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
|
|
|
14757
15908
|
var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
|
|
14758
15909
|
var MAX_PAGE_INVENTORY_WINDOWS = 64;
|
|
14759
15910
|
function withTimeout(promise, ms) {
|
|
14760
|
-
return new Promise((
|
|
15911
|
+
return new Promise((resolve11, reject) => {
|
|
14761
15912
|
const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
|
|
14762
15913
|
promise.then(
|
|
14763
15914
|
(value) => {
|
|
14764
15915
|
clearTimeout(timer);
|
|
14765
|
-
|
|
15916
|
+
resolve11(value);
|
|
14766
15917
|
},
|
|
14767
15918
|
(error) => {
|
|
14768
15919
|
clearTimeout(timer);
|
|
@@ -14877,6 +16028,7 @@ async function maybeOpenPicker(sink, closing) {
|
|
|
14877
16028
|
var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
14878
16029
|
var OPEN_PAGES = /* @__PURE__ */ new Set();
|
|
14879
16030
|
var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
|
|
16031
|
+
var OPEN_CONTEXTS = /* @__PURE__ */ new Set();
|
|
14880
16032
|
var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
14881
16033
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
14882
16034
|
var ACTION_METHOD_SET = new Set(ACTION_METHODS);
|
|
@@ -14997,8 +16149,20 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
|
|
|
14997
16149
|
} catch {
|
|
14998
16150
|
}
|
|
14999
16151
|
}
|
|
15000
|
-
function
|
|
15001
|
-
|
|
16152
|
+
function knowLocatorCall(sink, page, target, location, origin) {
|
|
16153
|
+
if (!sink?.pause || !location) return null;
|
|
16154
|
+
let settle = () => {
|
|
16155
|
+
};
|
|
16156
|
+
const settled = new Promise((resolve11) => settle = resolve11);
|
|
16157
|
+
const call = { page, target, location, origin, picked: false, settled, settle };
|
|
16158
|
+
sink.locatorCalls.set(callSiteLine(location), call);
|
|
16159
|
+
return call;
|
|
16160
|
+
}
|
|
16161
|
+
function forgetLocatorCall(sink, call) {
|
|
16162
|
+
if (!sink || !call) return;
|
|
16163
|
+
call.settle();
|
|
16164
|
+
const key = callSiteLine(call.location);
|
|
16165
|
+
if (sink.locatorCalls.get(key) === call) sink.locatorCalls.delete(key);
|
|
15002
16166
|
}
|
|
15003
16167
|
function locatorSource(target, method, args) {
|
|
15004
16168
|
try {
|
|
@@ -15008,42 +16172,220 @@ function locatorSource(target, method, args) {
|
|
|
15008
16172
|
}
|
|
15009
16173
|
return renderFailing({ method, args });
|
|
15010
16174
|
}
|
|
15011
|
-
|
|
15012
|
-
|
|
16175
|
+
var PAGE_PAUSES = /* @__PURE__ */ new WeakMap();
|
|
16176
|
+
var PLAYWRIGHT_PAUSES = /* @__PURE__ */ new WeakMap();
|
|
16177
|
+
async function armBreakpoints(contexts) {
|
|
16178
|
+
const state = currentSink?.pause;
|
|
16179
|
+
if (!state || state.finished || state.set.size === 0) return;
|
|
16180
|
+
await Promise.all(
|
|
16181
|
+
[...contexts].map(
|
|
16182
|
+
(context) => context && watchStops(context, onDebuggerStop) ? steerDebugger(context, "pause") : void 0
|
|
16183
|
+
)
|
|
16184
|
+
);
|
|
16185
|
+
}
|
|
16186
|
+
function onDebuggerStop(context) {
|
|
16187
|
+
const details = contextDebugger(context)?.pausedDetails();
|
|
16188
|
+
if (!details || PLAYWRIGHT_PAUSES.has(context)) return;
|
|
16189
|
+
const sink = currentSink;
|
|
16190
|
+
const state = sink?.pause ?? null;
|
|
16191
|
+
void (async () => {
|
|
16192
|
+
let how = "resume";
|
|
16193
|
+
try {
|
|
16194
|
+
if (!sink || !state) return;
|
|
16195
|
+
const callSite = stopSite(sink, details.location);
|
|
16196
|
+
const pagePause = PAGE_PAUSES.get(context);
|
|
16197
|
+
const decided = pagePause ? { show: "page-pause" } : atStop(state, callSite);
|
|
16198
|
+
if ("go" in decided) {
|
|
16199
|
+
how = decided.go;
|
|
16200
|
+
return;
|
|
16201
|
+
}
|
|
16202
|
+
how = goOn(state, await showStop(sink, context, details.title, callSite ?? pagePause, decided.show));
|
|
16203
|
+
} catch {
|
|
16204
|
+
if (state) how = goOn(state);
|
|
16205
|
+
} finally {
|
|
16206
|
+
await steerDebugger(context, how);
|
|
16207
|
+
}
|
|
16208
|
+
})();
|
|
16209
|
+
}
|
|
16210
|
+
function stopSite(sink, location) {
|
|
16211
|
+
if (path30.dirname(path30.resolve(location.file)) !== __dirname) return stopCallSite(location);
|
|
16212
|
+
return inFlightCallSite([...sink.locatorCalls.values()].map((call) => call.location));
|
|
16213
|
+
}
|
|
16214
|
+
function stopPage(sink, context, call) {
|
|
16215
|
+
for (const page of [call?.page, sink.lastActivePage]) {
|
|
16216
|
+
if (page && !isPageClosed(page) && pageContext(page) === context) return page;
|
|
16217
|
+
}
|
|
16218
|
+
const open = context.pages().filter((page) => !isPageClosed(page));
|
|
16219
|
+
return open[open.length - 1] ?? null;
|
|
16220
|
+
}
|
|
16221
|
+
async function showStop(sink, context, title, location, reason) {
|
|
15013
16222
|
const testInfo = sink.testInfo;
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
16223
|
+
const call = sink.locatorCalls.get(callSiteLine(location));
|
|
16224
|
+
const page = stopPage(sink, context, call);
|
|
16225
|
+
if (!testInfo || !page) return void 0;
|
|
16226
|
+
const onPick = async (picked) => {
|
|
16227
|
+
if (call) {
|
|
16228
|
+
call.picked = true;
|
|
16229
|
+
const { method, args } = call.origin;
|
|
16230
|
+
const rendered = locatorSource(call.target, method, args);
|
|
16231
|
+
applyPickToSnapshots(sink.capturedLocators, {
|
|
16232
|
+
failing: { method, args, rendered, location: call.location },
|
|
15021
16233
|
...picked
|
|
15022
|
-
};
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
16234
|
+
});
|
|
16235
|
+
sink.probedLocations.add(call.location);
|
|
16236
|
+
}
|
|
16237
|
+
console.log(`[piwi] Locator picked at ${callSiteLine(location)}: ${picked.picked.locator}`);
|
|
16238
|
+
await postPickToEditor(process.env[PIWI_LOCAL_DEBUG_ENV.editorSend], pickPayload(picked.picked.locator, location));
|
|
16239
|
+
};
|
|
16240
|
+
const record = { title, location, reason, startedAt: Date.now(), duration: 0 };
|
|
16241
|
+
sink.stops.push(record);
|
|
16242
|
+
const end = new AbortController();
|
|
16243
|
+
const shown = withTestTimeoutHeld(
|
|
16244
|
+
testInfo,
|
|
16245
|
+
() => internalCall(
|
|
15033
16246
|
page,
|
|
15034
16247
|
() => pauseHere(
|
|
15035
16248
|
page,
|
|
15036
|
-
|
|
15037
|
-
|
|
16249
|
+
{
|
|
16250
|
+
location,
|
|
16251
|
+
call: title,
|
|
16252
|
+
attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
|
|
16253
|
+
target: call?.page === page ? call.target : null
|
|
16254
|
+
},
|
|
15038
16255
|
{ fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
|
|
15039
|
-
onPick
|
|
16256
|
+
onPick,
|
|
16257
|
+
recorderAt(page, testInfo, location, reason === "page-pause" ? "pause" : "breakpoint"),
|
|
16258
|
+
end.signal
|
|
15040
16259
|
)
|
|
15041
|
-
)
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
16260
|
+
)
|
|
16261
|
+
).finally(() => {
|
|
16262
|
+
if (!end.signal.aborted) record.duration = Date.now() - record.startedAt;
|
|
16263
|
+
else if (end.signal.reason === "ended") releaseCall(sink, call?.settled ?? Promise.resolve());
|
|
16264
|
+
sink.shownStops.delete(end);
|
|
16265
|
+
});
|
|
16266
|
+
const heldAt = testTimeSlot(testInfo)?.elapsed ?? null;
|
|
16267
|
+
sink.shownStops.set(end, { page, record, done: shown.catch(() => void 0), heldAt });
|
|
16268
|
+
const answer = await shown;
|
|
16269
|
+
if (!end.signal.aborted) return answer;
|
|
16270
|
+
return end.signal.reason === "closed" ? "resume" : "finish";
|
|
16271
|
+
}
|
|
16272
|
+
var END_STOP_MS = 2e3;
|
|
16273
|
+
var RELEASED_CALL_MS = 2e3;
|
|
16274
|
+
var RELEASED_QUIET_MS = 200;
|
|
16275
|
+
async function endShownStops(sink, why, closing) {
|
|
16276
|
+
const ending = [];
|
|
16277
|
+
let heldAt = Infinity;
|
|
16278
|
+
for (const [end, { page, record, done, heldAt: stopHeldAt }] of sink.shownStops) {
|
|
16279
|
+
if (closing && closing.page !== page && (!closing.context || pageContext(page) !== closing.context)) continue;
|
|
16280
|
+
if (!end.signal.aborted) {
|
|
16281
|
+
record.duration = Date.now() - record.startedAt;
|
|
16282
|
+
end.abort(why);
|
|
16283
|
+
if (why === "ended" && stopHeldAt !== null) heldAt = Math.min(heldAt, stopHeldAt);
|
|
16284
|
+
}
|
|
16285
|
+
ending.push(done);
|
|
16286
|
+
}
|
|
16287
|
+
if (sink.testInfo && heldAt !== Infinity) restoreTestTime(sink.testInfo, heldAt);
|
|
16288
|
+
await settledWithin(ending, END_STOP_MS);
|
|
16289
|
+
}
|
|
16290
|
+
function releaseCall(sink, settled) {
|
|
16291
|
+
sink.releasedQuietSince = Date.now();
|
|
16292
|
+
sink.released.push(settled.then(() => void (sink.releasedQuietSince = Date.now())));
|
|
16293
|
+
}
|
|
16294
|
+
function releasedRequestsEnded(sink) {
|
|
16295
|
+
if (sink.releasedRequests.size === 0) return Promise.resolve();
|
|
16296
|
+
return new Promise((resolve11) => sink.onReleasedRequestsEnded = resolve11);
|
|
16297
|
+
}
|
|
16298
|
+
async function awaitReleasedCalls(sink) {
|
|
16299
|
+
if (sink.released.length === 0) return;
|
|
16300
|
+
const deadline = Date.now() + RELEASED_CALL_MS;
|
|
16301
|
+
await settledWithin(sink.released, RELEASED_CALL_MS);
|
|
16302
|
+
for (let now = Date.now(); now < deadline; now = Date.now()) {
|
|
16303
|
+
if (sink.releasedRequests.size > 0) {
|
|
16304
|
+
await settledWithin([releasedRequestsEnded(sink)], deadline - now);
|
|
16305
|
+
continue;
|
|
16306
|
+
}
|
|
16307
|
+
const quiet = now - sink.releasedQuietSince;
|
|
16308
|
+
if (quiet >= RELEASED_QUIET_MS) return;
|
|
16309
|
+
await new Promise((resolve11) => setTimeout(resolve11, Math.min(RELEASED_QUIET_MS - quiet, deadline - now)));
|
|
16310
|
+
}
|
|
16311
|
+
}
|
|
16312
|
+
async function settledWithin(pending, ms) {
|
|
16313
|
+
if (pending.length === 0) return;
|
|
16314
|
+
let timer;
|
|
16315
|
+
await Promise.race([Promise.allSettled(pending), new Promise((resolve11) => timer = setTimeout(resolve11, ms))]);
|
|
16316
|
+
clearTimeout(timer);
|
|
16317
|
+
}
|
|
16318
|
+
function endStopsWithTestFunction(testInfo, sink) {
|
|
16319
|
+
const callbacks = testInfo._onDidFinishTestFunctionCallbacks;
|
|
16320
|
+
if (!(callbacks instanceof Set)) return;
|
|
16321
|
+
const theirs = [...callbacks];
|
|
16322
|
+
callbacks.clear();
|
|
16323
|
+
callbacks.add(() => endShownStops(sink, "ended"));
|
|
16324
|
+
for (const callback of theirs) callbacks.add(callback);
|
|
16325
|
+
}
|
|
16326
|
+
function recorderAt(page, testInfo, location, reason) {
|
|
16327
|
+
if (!canRecordFromHere()) return void 0;
|
|
16328
|
+
return async (ended) => {
|
|
16329
|
+
console.log(`[piwi] Recording from ${callSiteLine(location)}: the editor writes the steps.`);
|
|
16330
|
+
const why = await recordFromHere({ page, testInfo, location, reason, ended });
|
|
16331
|
+
if (ended?.aborted) console.log("[piwi] The recording ended with the test.");
|
|
16332
|
+
else
|
|
16333
|
+
console.log(
|
|
16334
|
+
why ? `[piwi] Nothing was recorded: ${why}` : "[piwi] The recording ended: the test is paused again."
|
|
16335
|
+
);
|
|
16336
|
+
};
|
|
16337
|
+
}
|
|
16338
|
+
async function pauseInPlace(page, playwrightPause, location) {
|
|
16339
|
+
const sink = currentSink;
|
|
16340
|
+
const testInfo = sink?.testInfo;
|
|
16341
|
+
const context = pageContext(page);
|
|
16342
|
+
if (!sink || !testInfo || !location || !context || !contextDebugger(context)) {
|
|
16343
|
+
return leaveToPlaywright(context, playwrightPause);
|
|
16344
|
+
}
|
|
16345
|
+
if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo)))) {
|
|
16346
|
+
return leaveToPlaywright(context, playwrightPause);
|
|
16347
|
+
}
|
|
16348
|
+
sink.pause ??= { set: NO_BREAKPOINTS, step: false, finished: false };
|
|
16349
|
+
watchStops(context, onDebuggerStop);
|
|
16350
|
+
await steerDebugger(context, "pause");
|
|
16351
|
+
PAGE_PAUSES.set(context, location);
|
|
16352
|
+
try {
|
|
16353
|
+
await playwrightPause({ __testHookKeepTestTimeout: true });
|
|
16354
|
+
} finally {
|
|
16355
|
+
PAGE_PAUSES.delete(context);
|
|
15045
16356
|
}
|
|
15046
16357
|
}
|
|
16358
|
+
async function leaveToPlaywright(context, playwrightPause) {
|
|
16359
|
+
if (context) PLAYWRIGHT_PAUSES.set(context, (PLAYWRIGHT_PAUSES.get(context) ?? 0) + 1);
|
|
16360
|
+
try {
|
|
16361
|
+
await withInspector(() => playwrightPause());
|
|
16362
|
+
} finally {
|
|
16363
|
+
if (context) {
|
|
16364
|
+
const left = (PLAYWRIGHT_PAUSES.get(context) ?? 1) - 1;
|
|
16365
|
+
if (left > 0) PLAYWRIGHT_PAUSES.set(context, left);
|
|
16366
|
+
else PLAYWRIGHT_PAUSES.delete(context);
|
|
16367
|
+
}
|
|
16368
|
+
}
|
|
16369
|
+
}
|
|
16370
|
+
var PLAYWRIGHT_PAUSE = /* @__PURE__ */ Symbol.for("@piwitests/reporter:playwright-pause");
|
|
16371
|
+
function playwrightPauseOf(page) {
|
|
16372
|
+
const current = page.pause;
|
|
16373
|
+
if (typeof current !== "function") return null;
|
|
16374
|
+
const own = current[PLAYWRIGHT_PAUSE] ?? current;
|
|
16375
|
+
const proto = Object.getPrototypeOf(page);
|
|
16376
|
+
const theirs = proto?.pause;
|
|
16377
|
+
if (proto && typeof theirs === "function") {
|
|
16378
|
+
if (!theirs[PLAYWRIGHT_PAUSE]) {
|
|
16379
|
+
const wrapped = function(options) {
|
|
16380
|
+
return leaveToPlaywright(pageContext(this), () => theirs.call(this, options));
|
|
16381
|
+
};
|
|
16382
|
+
wrapped[PLAYWRIGHT_PAUSE] = theirs;
|
|
16383
|
+
proto.pause = wrapped;
|
|
16384
|
+
}
|
|
16385
|
+
markPausesWrapped();
|
|
16386
|
+
}
|
|
16387
|
+
return (options) => own.call(page, options);
|
|
16388
|
+
}
|
|
15047
16389
|
function wrapLocator(page, locator, originMethod, originArgs) {
|
|
15048
16390
|
return new Proxy(locator, {
|
|
15049
16391
|
get(target, prop) {
|
|
@@ -15065,36 +16407,35 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
15065
16407
|
const isNot = Boolean(callArgs[1]?.isNot);
|
|
15066
16408
|
const callerLocation = sink ? captureCallerLocation() : null;
|
|
15067
16409
|
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
15068
|
-
|
|
15069
|
-
|
|
16410
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, { method: originMethod, args: originArgs });
|
|
16411
|
+
try {
|
|
16412
|
+
if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
|
|
16413
|
+
return await callMethod(target, prop, callArgs);
|
|
16414
|
+
}
|
|
16415
|
+
sink.lastActivePage = page;
|
|
16416
|
+
const used = {
|
|
15070
16417
|
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 });
|
|
16418
|
+
args: originArgs,
|
|
16419
|
+
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
16420
|
+
};
|
|
16421
|
+
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
16422
|
+
let seq = -1;
|
|
16423
|
+
if (!alreadyProbed) {
|
|
16424
|
+
seq = sink.capturedLocators.length;
|
|
16425
|
+
sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
|
|
16426
|
+
}
|
|
16427
|
+
const result = await callMethod(target, prop, callArgs);
|
|
16428
|
+
const matches = result?.matches;
|
|
16429
|
+
if (matches === true && !alreadyProbed && !call?.picked) {
|
|
16430
|
+
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
16431
|
+
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
16432
|
+
} else if (matches === false) {
|
|
16433
|
+
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
16434
|
+
}
|
|
16435
|
+
return result;
|
|
16436
|
+
} finally {
|
|
16437
|
+
forgetLocatorCall(sink, call);
|
|
15096
16438
|
}
|
|
15097
|
-
return result;
|
|
15098
16439
|
};
|
|
15099
16440
|
}
|
|
15100
16441
|
if (!ACTION_METHOD_SET.has(prop)) return original;
|
|
@@ -15109,9 +16450,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
15109
16450
|
args: originArgs,
|
|
15110
16451
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
15111
16452
|
};
|
|
15112
|
-
|
|
15113
|
-
await pauseBefore(sink, page, target, callerLocation, String(prop), used);
|
|
15114
|
-
}
|
|
16453
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, used);
|
|
15115
16454
|
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
15116
16455
|
let seq = -1;
|
|
15117
16456
|
if (!alreadyProbed) {
|
|
@@ -15129,8 +16468,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
15129
16468
|
} catch (error) {
|
|
15130
16469
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
15131
16470
|
throw error;
|
|
16471
|
+
} finally {
|
|
16472
|
+
forgetLocatorCall(sink, call);
|
|
15132
16473
|
}
|
|
15133
|
-
if (!alreadyProbed) {
|
|
16474
|
+
if (!alreadyProbed && !call?.picked) {
|
|
15134
16475
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
15135
16476
|
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
15136
16477
|
}
|
|
@@ -15183,12 +16524,17 @@ function instrumentPage(page) {
|
|
|
15183
16524
|
}
|
|
15184
16525
|
const ctx = pageContext(page);
|
|
15185
16526
|
if (ctx) instrumentContext(ctx);
|
|
16527
|
+
const playwrightPause = playwrightPauseOf(page);
|
|
16528
|
+
if (playwrightPause) {
|
|
16529
|
+
page.pause = () => pauseInPlace(page, playwrightPause, captureCallerLocation());
|
|
16530
|
+
}
|
|
15186
16531
|
if (typeof page.close === "function") {
|
|
15187
16532
|
const originalClose = page.close.bind(page);
|
|
15188
16533
|
page.close = async (...args) => {
|
|
15189
16534
|
await drainPendingProbes(1e3);
|
|
15190
16535
|
const sink = currentSink;
|
|
15191
16536
|
if (sink) {
|
|
16537
|
+
await endShownStops(sink, "closed", { page });
|
|
15192
16538
|
await stashPageState(sink, { page });
|
|
15193
16539
|
await maybeOpenPicker(sink, { page });
|
|
15194
16540
|
await stopSinkCodeReach(sink, { page });
|
|
@@ -15277,8 +16623,18 @@ function instrumentPage(page) {
|
|
|
15277
16623
|
} catch {
|
|
15278
16624
|
}
|
|
15279
16625
|
});
|
|
16626
|
+
page.on("request", (request3) => {
|
|
16627
|
+
const sink = currentSink;
|
|
16628
|
+
if (!sink || sink.released.length === 0) return;
|
|
16629
|
+
sink.releasedRequests.add(request3);
|
|
16630
|
+
sink.releasedQuietSince = Date.now();
|
|
16631
|
+
});
|
|
15280
16632
|
const onRequestEnd = (request3, failed) => {
|
|
15281
16633
|
const sink = currentSink;
|
|
16634
|
+
if (sink?.releasedRequests.delete(request3)) {
|
|
16635
|
+
sink.releasedQuietSince = Date.now();
|
|
16636
|
+
if (sink.releasedRequests.size === 0) sink.onReleasedRequestsEnded?.();
|
|
16637
|
+
}
|
|
15282
16638
|
if (!sink || RECORDED_OPEN.has(request3)) return;
|
|
15283
16639
|
sink.lastActivePage = page;
|
|
15284
16640
|
const response = sink.openResponses.get(request3) ?? null;
|
|
@@ -15384,6 +16740,10 @@ async function awaitFlakeConditions() {
|
|
|
15384
16740
|
function instrumentContext(context) {
|
|
15385
16741
|
if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
|
|
15386
16742
|
INSTRUMENTED_CONTEXTS.add(context);
|
|
16743
|
+
if (typeof context.once === "function") {
|
|
16744
|
+
OPEN_CONTEXTS.add(context);
|
|
16745
|
+
context.once("close", () => OPEN_CONTEXTS.delete(context));
|
|
16746
|
+
}
|
|
15387
16747
|
observeOwnApiRequests(context);
|
|
15388
16748
|
if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
|
|
15389
16749
|
void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
|
|
@@ -15403,6 +16763,7 @@ function instrumentContext(context) {
|
|
|
15403
16763
|
await drainPendingProbes(1e3);
|
|
15404
16764
|
const sink = currentSink;
|
|
15405
16765
|
if (sink) {
|
|
16766
|
+
await endShownStops(sink, "closed", { context });
|
|
15406
16767
|
await stashPageState(sink, { context });
|
|
15407
16768
|
await maybeOpenPicker(sink, { context });
|
|
15408
16769
|
await stopSinkCodeReach(sink, { context });
|
|
@@ -15419,6 +16780,7 @@ function patchBrowser(browser) {
|
|
|
15419
16780
|
browser.newPage = async (...args) => {
|
|
15420
16781
|
const page = await originalNewPage(...args);
|
|
15421
16782
|
instrumentPage(page);
|
|
16783
|
+
await armBreakpoints([pageContext(page)]);
|
|
15422
16784
|
await awaitFlakeConditions();
|
|
15423
16785
|
await maybeStartCodeReach(page);
|
|
15424
16786
|
return page;
|
|
@@ -15427,6 +16789,7 @@ function patchBrowser(browser) {
|
|
|
15427
16789
|
browser.newContext = async (...args) => {
|
|
15428
16790
|
const context = await originalNewContext(...args);
|
|
15429
16791
|
instrumentContext(context);
|
|
16792
|
+
await armBreakpoints([context]);
|
|
15430
16793
|
return context;
|
|
15431
16794
|
};
|
|
15432
16795
|
if (typeof browser.close === "function") {
|
|
@@ -15478,14 +16841,20 @@ async function flushSink(sink, testInfo) {
|
|
|
15478
16841
|
let drainDeadline;
|
|
15479
16842
|
await Promise.race([
|
|
15480
16843
|
Promise.allSettled(sink.capturePromises),
|
|
15481
|
-
new Promise((
|
|
15482
|
-
drainDeadline = setTimeout(
|
|
16844
|
+
new Promise((resolve11) => {
|
|
16845
|
+
drainDeadline = setTimeout(resolve11, 2e3);
|
|
15483
16846
|
})
|
|
15484
16847
|
]);
|
|
15485
16848
|
clearTimeout(drainDeadline);
|
|
15486
16849
|
const page = sink.lastActivePage;
|
|
15487
16850
|
const pageReadable = page !== null && !isPageClosed(page);
|
|
15488
16851
|
if (pageReadable) await maybeOpenPicker(sink);
|
|
16852
|
+
if (sink.stops.length > 0) {
|
|
16853
|
+
await testInfo.attach(ATTACHMENT_NAMES.pauses, {
|
|
16854
|
+
contentType: "application/json",
|
|
16855
|
+
body: Buffer.from(JSON.stringify(sink.stops))
|
|
16856
|
+
});
|
|
16857
|
+
}
|
|
15489
16858
|
if (sink.capturedLocators.length > 0) {
|
|
15490
16859
|
await testInfo.attach(ATTACHMENT_NAMES.locators, {
|
|
15491
16860
|
contentType: "application/json",
|
|
@@ -15516,6 +16885,21 @@ async function flushSink(sink, testInfo) {
|
|
|
15516
16885
|
body: Buffer.from(JSON.stringify(part))
|
|
15517
16886
|
});
|
|
15518
16887
|
}
|
|
16888
|
+
if (cssCoverageInWorker()) {
|
|
16889
|
+
const rules = sink.codeReach.cssFiles.size > 0 ? browserFunctionCoverage(sink.codeReach.cssFiles) : null;
|
|
16890
|
+
if (rules) {
|
|
16891
|
+
await testInfo.attach(ATTACHMENT_NAMES.cssCoverage, {
|
|
16892
|
+
contentType: "application/json",
|
|
16893
|
+
body: Buffer.from(JSON.stringify(rules))
|
|
16894
|
+
});
|
|
16895
|
+
}
|
|
16896
|
+
for (const part of newKnownFunctions(sink.codeReach.cssKnown, KNOWN_CSS_RULES_SENT)) {
|
|
16897
|
+
await testInfo.attach(ATTACHMENT_NAMES.cssRules, {
|
|
16898
|
+
contentType: "application/json",
|
|
16899
|
+
body: Buffer.from(JSON.stringify(part))
|
|
16900
|
+
});
|
|
16901
|
+
}
|
|
16902
|
+
}
|
|
15519
16903
|
if (sink.serverCoverage.size > 0) {
|
|
15520
16904
|
const requests = {};
|
|
15521
16905
|
for (const [origin, ids] of sink.serverCoverage) requests[origin] = [...ids];
|
|
@@ -15686,6 +17070,7 @@ var piwiFixtures = {
|
|
|
15686
17070
|
// never double-wraps a page the browser patch already instrumented.
|
|
15687
17071
|
page: async ({ page }, use) => {
|
|
15688
17072
|
instrumentPage(page);
|
|
17073
|
+
await armBreakpoints([pageContext(page)]);
|
|
15689
17074
|
await awaitFlakeConditions();
|
|
15690
17075
|
await maybeStartCodeReach(page);
|
|
15691
17076
|
await use(page);
|
|
@@ -15706,8 +17091,9 @@ var piwiFixtures = {
|
|
|
15706
17091
|
if (pauseSet.size) {
|
|
15707
17092
|
const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
|
|
15708
17093
|
if (reason) logPauseSkipOnce(reason);
|
|
15709
|
-
else sink.pause = { set: pauseSet,
|
|
17094
|
+
else sink.pause = { set: pauseSet, step: false, finished: false };
|
|
15710
17095
|
}
|
|
17096
|
+
if (sink.pause || canRecordFromHere()) endStopsWithTestFunction(testInfo, sink);
|
|
15711
17097
|
if (isFlakeMode()) {
|
|
15712
17098
|
const plan = loadFlakePlan();
|
|
15713
17099
|
const role = flakeRoleForTest(plan, {
|
|
@@ -15732,9 +17118,12 @@ var piwiFixtures = {
|
|
|
15732
17118
|
activeResourceLedger()?.testStarted(testInfo);
|
|
15733
17119
|
currentSink = sink;
|
|
15734
17120
|
try {
|
|
17121
|
+
await armBreakpoints(OPEN_CONTEXTS);
|
|
15735
17122
|
await startCodeReachOnOpenPages();
|
|
15736
17123
|
await use();
|
|
15737
17124
|
} finally {
|
|
17125
|
+
await endShownStops(sink, "ended");
|
|
17126
|
+
await awaitReleasedCalls(sink);
|
|
15738
17127
|
currentSink = null;
|
|
15739
17128
|
if (sink.probeItem) {
|
|
15740
17129
|
const applied = sink.probeInterception?.applied() ?? false;
|
|
@@ -15792,13 +17181,13 @@ function extendPiwiFixtures(test) {
|
|
|
15792
17181
|
}
|
|
15793
17182
|
|
|
15794
17183
|
// src/internal/ai/ai-fixtures.ts
|
|
15795
|
-
var
|
|
15796
|
-
var
|
|
17184
|
+
var fs29 = __toESM(require("fs"));
|
|
17185
|
+
var path33 = __toESM(require("path"));
|
|
15797
17186
|
var import_test = require("@playwright/test");
|
|
15798
17187
|
|
|
15799
17188
|
// src/internal/ai/artifact.ts
|
|
15800
|
-
var
|
|
15801
|
-
var
|
|
17189
|
+
var fs28 = __toESM(require("fs"));
|
|
17190
|
+
var path31 = __toESM(require("path"));
|
|
15802
17191
|
var ARTIFACT_VERSION = 1;
|
|
15803
17192
|
var LOCATOR_METHOD_SET2 = new Set(LOCATOR_METHODS);
|
|
15804
17193
|
var ACTION_METHOD_SET2 = new Set(ACTION_METHODS);
|
|
@@ -15891,7 +17280,7 @@ function parseEntry(text) {
|
|
|
15891
17280
|
function readEntry(file) {
|
|
15892
17281
|
let text;
|
|
15893
17282
|
try {
|
|
15894
|
-
text =
|
|
17283
|
+
text = fs28.readFileSync(file, "utf8");
|
|
15895
17284
|
} catch (error) {
|
|
15896
17285
|
if (error.code === "ENOENT") return null;
|
|
15897
17286
|
throw error;
|
|
@@ -15901,28 +17290,28 @@ function readEntry(file) {
|
|
|
15901
17290
|
function writeEntry(file, entry) {
|
|
15902
17291
|
const canonical = serializeEntry(entry);
|
|
15903
17292
|
return withEntryLock(file, () => {
|
|
15904
|
-
if (
|
|
15905
|
-
|
|
17293
|
+
if (fs28.existsSync(file) && fs28.readFileSync(file, "utf8") === canonical) return { written: false };
|
|
17294
|
+
fs28.mkdirSync(path31.dirname(file), { recursive: true });
|
|
15906
17295
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
15907
|
-
|
|
15908
|
-
|
|
17296
|
+
fs28.writeFileSync(tmp, canonical);
|
|
17297
|
+
fs28.renameSync(tmp, file);
|
|
15909
17298
|
return { written: true };
|
|
15910
17299
|
});
|
|
15911
17300
|
}
|
|
15912
17301
|
function withEntryLock(file, fn) {
|
|
15913
17302
|
const lock = `${file}.lock`;
|
|
15914
|
-
|
|
17303
|
+
fs28.mkdirSync(path31.dirname(file), { recursive: true });
|
|
15915
17304
|
const deadline = Date.now() + 5e3;
|
|
15916
17305
|
for (; ; ) {
|
|
15917
17306
|
try {
|
|
15918
|
-
const fd =
|
|
15919
|
-
|
|
17307
|
+
const fd = fs28.openSync(lock, "wx");
|
|
17308
|
+
fs28.closeSync(fd);
|
|
15920
17309
|
break;
|
|
15921
17310
|
} catch (error) {
|
|
15922
17311
|
if (error.code !== "EEXIST") throw error;
|
|
15923
17312
|
if (Date.now() > deadline) {
|
|
15924
17313
|
try {
|
|
15925
|
-
|
|
17314
|
+
fs28.unlinkSync(lock);
|
|
15926
17315
|
} catch {
|
|
15927
17316
|
}
|
|
15928
17317
|
}
|
|
@@ -15932,35 +17321,35 @@ function withEntryLock(file, fn) {
|
|
|
15932
17321
|
return fn();
|
|
15933
17322
|
} finally {
|
|
15934
17323
|
try {
|
|
15935
|
-
|
|
17324
|
+
fs28.unlinkSync(lock);
|
|
15936
17325
|
} catch {
|
|
15937
17326
|
}
|
|
15938
17327
|
}
|
|
15939
17328
|
}
|
|
15940
17329
|
|
|
15941
17330
|
// src/internal/ai/keys.ts
|
|
15942
|
-
var
|
|
15943
|
-
var
|
|
17331
|
+
var crypto8 = __toESM(require("crypto"));
|
|
17332
|
+
var path32 = __toESM(require("path"));
|
|
15944
17333
|
var DEFAULT_AI_DIR = "__piwi__";
|
|
15945
17334
|
function normalizeTemplate(template) {
|
|
15946
17335
|
return template.trim().replace(/\s+/g, " ").toLowerCase();
|
|
15947
17336
|
}
|
|
15948
17337
|
function hashTemplate(normalized, ordinal = 0) {
|
|
15949
17338
|
const input = ordinal > 0 ? `${normalized}#${ordinal}` : normalized;
|
|
15950
|
-
return
|
|
17339
|
+
return crypto8.createHash("sha256").update(input).digest("hex").slice(0, 8);
|
|
15951
17340
|
}
|
|
15952
17341
|
function slug(text, maxLength = 40) {
|
|
15953
17342
|
const base = text.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
15954
17343
|
return (base || "x").slice(0, maxLength).replace(/-+$/g, "") || "x";
|
|
15955
17344
|
}
|
|
15956
17345
|
function entryDir(specFile, dir = DEFAULT_AI_DIR) {
|
|
15957
|
-
return
|
|
17346
|
+
return path32.join(path32.dirname(specFile), dir, path32.basename(specFile));
|
|
15958
17347
|
}
|
|
15959
17348
|
function entryPath(params) {
|
|
15960
17349
|
const basis = `${normalizeTemplate(params.testTitle)}::${normalizeTemplate(params.template)}`;
|
|
15961
17350
|
const hash = hashTemplate(basis, params.ordinal ?? 0);
|
|
15962
17351
|
const name = `${slug(params.testTitle)}.${slug(params.template)}.${hash}.json`;
|
|
15963
|
-
return
|
|
17352
|
+
return path32.join(entryDir(params.specFile, params.dir), name);
|
|
15964
17353
|
}
|
|
15965
17354
|
function parseLocation2(location) {
|
|
15966
17355
|
const match = /:(\d+):(\d+)$/.exec(location);
|
|
@@ -16563,18 +17952,18 @@ function testIdentity(testInfo) {
|
|
|
16563
17952
|
return titles.length > 0 ? titles.join(" \u203A ") : testInfo.title;
|
|
16564
17953
|
}
|
|
16565
17954
|
function relativeToCwd(file) {
|
|
16566
|
-
return
|
|
17955
|
+
return path33.relative(process.cwd(), file).split(path33.sep).join("/");
|
|
16567
17956
|
}
|
|
16568
17957
|
function recordIntents(intents, entry) {
|
|
16569
|
-
const
|
|
17958
|
+
const add2 = (locator, kind) => {
|
|
16570
17959
|
intents.set(`${entry.template}\0${locator}`, { template: entry.template, locator, kind });
|
|
16571
17960
|
};
|
|
16572
17961
|
if (entry.kind === "locator") {
|
|
16573
|
-
|
|
17962
|
+
add2(locatorSource2(entry.locator), "locator");
|
|
16574
17963
|
return;
|
|
16575
17964
|
}
|
|
16576
|
-
for (const step of entry.steps)
|
|
16577
|
-
if (entry.postcondition.locator)
|
|
17965
|
+
for (const step of entry.steps) add2(locatorSource2(step.locator), "run");
|
|
17966
|
+
if (entry.postcondition.locator) add2(locatorSource2(entry.postcondition.locator), "run");
|
|
16578
17967
|
}
|
|
16579
17968
|
function missMessage(template, testTitle, relPath) {
|
|
16580
17969
|
return [
|
|
@@ -16590,7 +17979,7 @@ function createAiApi(page, testInfo, config, used, intents) {
|
|
|
16590
17979
|
const readSource = () => {
|
|
16591
17980
|
if (source === null) {
|
|
16592
17981
|
try {
|
|
16593
|
-
source =
|
|
17982
|
+
source = fs29.readFileSync(testInfo.file, "utf8");
|
|
16594
17983
|
} catch {
|
|
16595
17984
|
source = "";
|
|
16596
17985
|
}
|