@piwitests/reporter 0.58.0 → 0.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1456 -1305
- package/dist/global-setup-module.js +137 -54
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1321 -494
- package/dist/internal/capture/attachments.d.ts +1 -0
- package/dist/internal/capture/attachments.js +2 -1
- package/dist/internal/capture/capture-fixtures.js +1011 -333
- package/dist/internal/capture/pick-on-failure.d.ts +6 -3
- package/dist/internal/capture/pick-on-failure.js +123 -45
- package/package.json +5 -5
- package/templates/skills/run-the-right-tests/SKILL.md +19 -8
- package/templates/skills/setup-piwi/SKILL.md +4 -1
package/dist/index.js
CHANGED
|
@@ -43,9 +43,12 @@ __export(index_exports, {
|
|
|
43
43
|
module.exports = __toCommonJS(index_exports);
|
|
44
44
|
|
|
45
45
|
// src/public/reporter.ts
|
|
46
|
-
var
|
|
46
|
+
var fs25 = __toESM(require("fs"));
|
|
47
47
|
var os9 = __toESM(require("os"));
|
|
48
|
-
var
|
|
48
|
+
var path25 = __toESM(require("path"));
|
|
49
|
+
|
|
50
|
+
// src/internal/config/env.ts
|
|
51
|
+
var path2 = __toESM(require("path"));
|
|
49
52
|
|
|
50
53
|
// src/internal/config/desktop.ts
|
|
51
54
|
var fs = __toESM(require("fs"));
|
|
@@ -54,6 +57,14 @@ var path = __toESM(require("path"));
|
|
|
54
57
|
function defaultDesktopConfigPath() {
|
|
55
58
|
return path.join(os.homedir(), ".piwi", "desktop.json");
|
|
56
59
|
}
|
|
60
|
+
function processAlive(pid) {
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, 0);
|
|
63
|
+
return true;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return error.code === "EPERM";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
57
68
|
function readDesktopConfig(filePath2) {
|
|
58
69
|
try {
|
|
59
70
|
const parsed2 = JSON.parse(fs.readFileSync(filePath2, "utf8"));
|
|
@@ -61,12 +72,75 @@ function readDesktopConfig(filePath2) {
|
|
|
61
72
|
const token = parsed2?.token;
|
|
62
73
|
if (typeof url !== "string" || !url) return null;
|
|
63
74
|
if (typeof token !== "string" || !token) return null;
|
|
75
|
+
if (Number.isInteger(parsed2?.pid) && parsed2.pid > 0 && !processAlive(parsed2.pid)) return null;
|
|
64
76
|
return { url, token };
|
|
65
77
|
} catch {
|
|
66
78
|
return null;
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
81
|
|
|
82
|
+
// src/internal/config/dotenv.ts
|
|
83
|
+
var fs2 = __toESM(require("fs"));
|
|
84
|
+
|
|
85
|
+
// ../core/src/dotenv.ts
|
|
86
|
+
function parseDotEnv(text) {
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
89
|
+
const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/.exec(raw);
|
|
90
|
+
if (!m) continue;
|
|
91
|
+
let value = m[2];
|
|
92
|
+
const quote = value[0];
|
|
93
|
+
if ((quote === '"' || quote === "'") && value.length > 1 && value.endsWith(quote)) {
|
|
94
|
+
value = value.slice(1, -1);
|
|
95
|
+
if (quote === '"') value = value.replace(/\\n/g, "\n").replace(/\\"/g, '"');
|
|
96
|
+
} else {
|
|
97
|
+
value = value.replace(/\s+#.*$/, "");
|
|
98
|
+
}
|
|
99
|
+
out[m[1]] = value;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/internal/config/dotenv.ts
|
|
105
|
+
function readDotEnv(filePath2) {
|
|
106
|
+
try {
|
|
107
|
+
return parseDotEnv(fs2.readFileSync(filePath2, "utf-8"));
|
|
108
|
+
} catch {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/internal/support/ci.ts
|
|
114
|
+
function detectCi(env) {
|
|
115
|
+
return Boolean(
|
|
116
|
+
env.JENKINS_URL || env.GITHUB_ACTIONS || env.GITLAB_CI || env.CIRCLECI || env.TRAVIS || env.TF_BUILD || env.BITBUCKET_BUILD_NUMBER || env.TEAMCITY_VERSION || env.CODEBUILD_BUILD_ID || env.bamboo_buildKey || env.CI && env.CI.toLowerCase() !== "false" && env.CI !== "0"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function detectCiIds(env) {
|
|
120
|
+
if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) {
|
|
121
|
+
const run = env.GITHUB_RUN_ATTEMPT ? `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` : env.GITHUB_RUN_ID;
|
|
122
|
+
return { run, job: env.GITHUB_JOB || null };
|
|
123
|
+
}
|
|
124
|
+
if (env.GITLAB_CI && env.CI_PIPELINE_ID) return { run: env.CI_PIPELINE_ID, job: env.CI_JOB_ID || null };
|
|
125
|
+
if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return { run: env.CIRCLE_WORKFLOW_ID, job: env.CIRCLE_BUILD_NUM || null };
|
|
126
|
+
if (env.TRAVIS && env.TRAVIS_BUILD_ID) return { run: env.TRAVIS_BUILD_ID, job: env.TRAVIS_JOB_ID || null };
|
|
127
|
+
if (env.TF_BUILD && env.BUILD_BUILDID) return { run: env.BUILD_BUILDID, job: env.SYSTEM_JOBID || null };
|
|
128
|
+
if (env.JENKINS_URL && env.BUILD_ID) return { run: env.BUILD_ID, job: null };
|
|
129
|
+
if (env.BUILDKITE_BUILD_ID) return { run: env.BUILDKITE_BUILD_ID, job: env.BUILDKITE_JOB_ID || null };
|
|
130
|
+
if (env.TEAMCITY_BUILD_ID) return { run: env.TEAMCITY_BUILD_ID, job: null };
|
|
131
|
+
if (env.BITBUCKET_BUILD_NUMBER) return { run: env.BITBUCKET_BUILD_NUMBER, job: env.BITBUCKET_STEP_UUID || null };
|
|
132
|
+
if (env.SEMAPHORE_WORKFLOW_ID) return { run: env.SEMAPHORE_WORKFLOW_ID, job: env.SEMAPHORE_JOB_ID || null };
|
|
133
|
+
if (env.APPVEYOR_BUILD_ID) return { run: env.APPVEYOR_BUILD_ID, job: env.APPVEYOR_JOB_ID || null };
|
|
134
|
+
if (env.DRONE_BUILD_NUMBER) return { run: env.DRONE_BUILD_NUMBER, job: null };
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function resolveRunLabel(configured, sharded) {
|
|
138
|
+
if (configured) return configured;
|
|
139
|
+
const ci = detectCiIds(process.env);
|
|
140
|
+
if (!ci) return null;
|
|
141
|
+
return !sharded && ci.job ? `${ci.run}|${ci.job}` : ci.run;
|
|
142
|
+
}
|
|
143
|
+
|
|
70
144
|
// src/internal/config/env.ts
|
|
71
145
|
var DEFAULTS = {
|
|
72
146
|
projectName: "default-project",
|
|
@@ -103,6 +177,7 @@ var DEFAULTS = {
|
|
|
103
177
|
};
|
|
104
178
|
var PIWI_ENV_KEYS = {
|
|
105
179
|
serverUrl: "PIWI_DASHBOARD_URL",
|
|
180
|
+
desktopApp: "PIWI_DESKTOP_APP",
|
|
106
181
|
projectName: "PIWI_PROJECT_NAME",
|
|
107
182
|
verbose: "PIWI_VERBOSE",
|
|
108
183
|
apiKey: "PIWI_API_KEY",
|
|
@@ -215,11 +290,27 @@ var ENV_FALLBACK_SPECS = [
|
|
|
215
290
|
{ option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
|
|
216
291
|
{ option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
|
|
217
292
|
];
|
|
293
|
+
var DOTENV_OPTIONS = ["serverUrl", "apiKey", "projectName"];
|
|
294
|
+
var DESKTOP_APP_MODES = ["prefer", "fallback", "off"];
|
|
295
|
+
function desktopAppMode(option, envValue) {
|
|
296
|
+
const fromEnv = envValue?.trim().toLowerCase();
|
|
297
|
+
if (DESKTOP_APP_MODES.includes(fromEnv)) return fromEnv;
|
|
298
|
+
if (DESKTOP_APP_MODES.includes(option)) return option;
|
|
299
|
+
return "fallback";
|
|
300
|
+
}
|
|
218
301
|
var desktopDiscovered = false;
|
|
302
|
+
var desktopReplacedServerUrl = null;
|
|
303
|
+
var dotEnvUse = null;
|
|
219
304
|
function usedDesktopDiscovery() {
|
|
220
305
|
return desktopDiscovered;
|
|
221
306
|
}
|
|
222
|
-
function
|
|
307
|
+
function desktopReplacedServer() {
|
|
308
|
+
return desktopReplacedServerUrl;
|
|
309
|
+
}
|
|
310
|
+
function usedDotEnv() {
|
|
311
|
+
return dotEnvUse;
|
|
312
|
+
}
|
|
313
|
+
function resolveOptions(raw, configDir) {
|
|
223
314
|
const env = process.env;
|
|
224
315
|
const mergedRaw = { ...raw };
|
|
225
316
|
for (const spec of ENV_FALLBACK_SPECS) {
|
|
@@ -231,14 +322,35 @@ function resolveOptions(raw) {
|
|
|
231
322
|
mergedRaw[spec.option] = spec.kind === "number" ? Number(value) : value;
|
|
232
323
|
}
|
|
233
324
|
}
|
|
325
|
+
dotEnvUse = null;
|
|
326
|
+
if (configDir) {
|
|
327
|
+
const file = path2.join(configDir, ".env");
|
|
328
|
+
const dotEnv = readDotEnv(file);
|
|
329
|
+
const keys = [];
|
|
330
|
+
for (const option of DOTENV_OPTIONS) {
|
|
331
|
+
const value = dotEnv[PIWI_ENV_KEYS[option]];
|
|
332
|
+
if (mergedRaw[option] !== void 0 || !value) continue;
|
|
333
|
+
mergedRaw[option] = value;
|
|
334
|
+
keys.push(PIWI_ENV_KEYS[option]);
|
|
335
|
+
}
|
|
336
|
+
if (keys.length > 0) dotEnvUse = { file, keys };
|
|
337
|
+
}
|
|
234
338
|
desktopDiscovered = false;
|
|
339
|
+
desktopReplacedServerUrl = null;
|
|
340
|
+
const mode = desktopAppMode(mergedRaw.desktopApp, env[PIWI_ENV_KEYS.desktopApp]);
|
|
341
|
+
mergedRaw.desktopApp = mode;
|
|
235
342
|
const serverUrlUnset = mergedRaw.serverUrl === void 0;
|
|
236
343
|
const apiKeyUnset = mergedRaw.apiKey === void 0 || mergedRaw.apiKey === null;
|
|
237
|
-
|
|
344
|
+
const nothingConfigured = serverUrlUnset && apiKeyUnset;
|
|
345
|
+
const preferLocally = mode === "prefer" && !detectCi(env);
|
|
346
|
+
if (mode !== "off" && (nothingConfigured || preferLocally)) {
|
|
238
347
|
const desktop = readDesktopConfig(env[PIWI_DESKTOP_CONFIG_ENV] || defaultDesktopConfigPath());
|
|
239
348
|
if (desktop) {
|
|
349
|
+
if (!serverUrlUnset && mergedRaw.serverUrl !== desktop.url) desktopReplacedServerUrl = mergedRaw.serverUrl;
|
|
240
350
|
mergedRaw.serverUrl = desktop.url;
|
|
241
351
|
mergedRaw.apiKey = desktop.token;
|
|
352
|
+
mergedRaw.username = null;
|
|
353
|
+
mergedRaw.password = null;
|
|
242
354
|
desktopDiscovered = true;
|
|
243
355
|
}
|
|
244
356
|
}
|
|
@@ -263,6 +375,8 @@ function resolveOptions(raw) {
|
|
|
263
375
|
function applyOptionsToEnv(options) {
|
|
264
376
|
const env = process.env;
|
|
265
377
|
if (options.serverUrl !== void 0) env[PIWI_ENV_KEYS.serverUrl] = options.serverUrl;
|
|
378
|
+
if (options.desktopApp !== void 0 && !env[PIWI_ENV_KEYS.desktopApp])
|
|
379
|
+
env[PIWI_ENV_KEYS.desktopApp] = options.desktopApp;
|
|
266
380
|
if (options.projectName !== void 0) env[PIWI_ENV_KEYS.projectName] = options.projectName;
|
|
267
381
|
if (options.verbose !== void 0) env[PIWI_ENV_KEYS.verbose] = String(options.verbose);
|
|
268
382
|
if (options.apiKey) env[PIWI_ENV_KEYS.apiKey] = options.apiKey;
|
|
@@ -545,7 +659,7 @@ var HttpClient = class {
|
|
|
545
659
|
}
|
|
546
660
|
/** Unified request core: transport, headers, auth, response accumulation, timeout. */
|
|
547
661
|
request(method, pathname, opts) {
|
|
548
|
-
return new Promise((
|
|
662
|
+
return new Promise((resolve11, reject) => {
|
|
549
663
|
if (this.closedReason !== null) {
|
|
550
664
|
reject(new Error(this.closedReason));
|
|
551
665
|
return;
|
|
@@ -568,7 +682,7 @@ var HttpClient = class {
|
|
|
568
682
|
data += chunk;
|
|
569
683
|
});
|
|
570
684
|
res.on("end", () => {
|
|
571
|
-
|
|
685
|
+
resolve11({ status: res.statusCode ?? 0, text: data, headers: res.headers });
|
|
572
686
|
});
|
|
573
687
|
res.on("error", reject);
|
|
574
688
|
res.on("close", () => {
|
|
@@ -607,8 +721,8 @@ var HttpClient = class {
|
|
|
607
721
|
};
|
|
608
722
|
|
|
609
723
|
// src/internal/submit/uploader.ts
|
|
610
|
-
var
|
|
611
|
-
var
|
|
724
|
+
var fs3 = __toESM(require("fs"));
|
|
725
|
+
var path3 = __toESM(require("path"));
|
|
612
726
|
var import_form_data2 = __toESM(require("form-data"));
|
|
613
727
|
|
|
614
728
|
// src/internal/submit/serializer.ts
|
|
@@ -634,6 +748,7 @@ function toWireTestCase(tc) {
|
|
|
634
748
|
timeout: rest.timeout ?? null,
|
|
635
749
|
error: rest.error,
|
|
636
750
|
retries: rest.retries,
|
|
751
|
+
repeatEachIndex: rest.repeatEachIndex ?? 0,
|
|
637
752
|
attempts: rest.attempts || null,
|
|
638
753
|
workerIndex: rest.workerIndex ?? null,
|
|
639
754
|
shardIndex: rest.shardIndex ?? null,
|
|
@@ -686,7 +801,13 @@ function isFailingStatus(status) {
|
|
|
686
801
|
function computeDistinctRunCounts(testCases) {
|
|
687
802
|
const final = /* @__PURE__ */ new Map();
|
|
688
803
|
for (const tc of testCases) {
|
|
689
|
-
const key = tc.testId ?? [
|
|
804
|
+
const key = tc.testId ?? [
|
|
805
|
+
tc.title,
|
|
806
|
+
tc.location,
|
|
807
|
+
(tc.suitePath ?? []).join(""),
|
|
808
|
+
browserProjectName(tc.browser),
|
|
809
|
+
tc.repeatEachIndex ?? 0
|
|
810
|
+
].join("\0");
|
|
690
811
|
const status = tc.status ?? "";
|
|
691
812
|
const retries = tc.retries ?? 0;
|
|
692
813
|
const prev = final.get(key);
|
|
@@ -844,14 +965,15 @@ var Uploader = class {
|
|
|
844
965
|
title: testCase.title,
|
|
845
966
|
location: testCase.location,
|
|
846
967
|
retries: testCase.retries ?? 0,
|
|
968
|
+
repeatEachIndex: testCase.repeatEachIndex ?? 0,
|
|
847
969
|
suitePath: testCase.suitePath ?? null
|
|
848
970
|
})
|
|
849
971
|
);
|
|
850
972
|
if (traceInfo) {
|
|
851
973
|
form.append("trace_hash", traceInfo.hash);
|
|
852
974
|
if (includeTraceFile2) {
|
|
853
|
-
form.append("trace",
|
|
854
|
-
filename:
|
|
975
|
+
form.append("trace", fs3.createReadStream(traceInfo.tracePath), {
|
|
976
|
+
filename: path3.basename(traceInfo.tracePath)
|
|
855
977
|
});
|
|
856
978
|
}
|
|
857
979
|
}
|
|
@@ -863,7 +985,7 @@ var Uploader = class {
|
|
|
863
985
|
)
|
|
864
986
|
);
|
|
865
987
|
for (const a of attachments) {
|
|
866
|
-
form.append("attach_file",
|
|
988
|
+
form.append("attach_file", fs3.createReadStream(a.path), { filename: a.originalName });
|
|
867
989
|
}
|
|
868
990
|
}
|
|
869
991
|
return form;
|
|
@@ -916,7 +1038,7 @@ var Uploader = class {
|
|
|
916
1038
|
)
|
|
917
1039
|
);
|
|
918
1040
|
for (const a of attachments) {
|
|
919
|
-
form.append(`attach_file_${i}`,
|
|
1041
|
+
form.append(`attach_file_${i}`, fs3.createReadStream(a.path), { filename: a.originalName });
|
|
920
1042
|
attachmentCount++;
|
|
921
1043
|
}
|
|
922
1044
|
}
|
|
@@ -925,8 +1047,8 @@ var Uploader = class {
|
|
|
925
1047
|
let traceCount = 0;
|
|
926
1048
|
for (const [i, tc] of testCases.entries()) {
|
|
927
1049
|
for (const tp of this.fileHandler.findTraceFiles(tc)) {
|
|
928
|
-
if (
|
|
929
|
-
form.append(`trace_${i}`,
|
|
1050
|
+
if (fs3.existsSync(tp)) {
|
|
1051
|
+
form.append(`trace_${i}`, fs3.createReadStream(tp), { filename: path3.basename(tp) });
|
|
930
1052
|
traceCount++;
|
|
931
1053
|
}
|
|
932
1054
|
}
|
|
@@ -936,9 +1058,9 @@ var Uploader = class {
|
|
|
936
1058
|
};
|
|
937
1059
|
|
|
938
1060
|
// src/internal/streaming/stream-buffer.ts
|
|
939
|
-
var
|
|
1061
|
+
var path4 = __toESM(require("path"));
|
|
940
1062
|
var os3 = __toESM(require("os"));
|
|
941
|
-
var
|
|
1063
|
+
var fs4 = __toESM(require("fs"));
|
|
942
1064
|
|
|
943
1065
|
// src/internal/support/instance-id.ts
|
|
944
1066
|
var crypto = __toESM(require("crypto"));
|
|
@@ -959,14 +1081,14 @@ var StreamBuffer = class {
|
|
|
959
1081
|
}
|
|
960
1082
|
/** Scope the buffer to a run. Until then the buffer is empty and appends are dropped. */
|
|
961
1083
|
bindRun(runId) {
|
|
962
|
-
this.filePath =
|
|
1084
|
+
this.filePath = path4.join(os3.tmpdir(), `${this.prefix}-${runId}.jsonl`);
|
|
963
1085
|
}
|
|
964
1086
|
/** Append one or more events to the on-disk buffer */
|
|
965
1087
|
append(events) {
|
|
966
1088
|
if (!this.filePath || events.length === 0) return;
|
|
967
1089
|
try {
|
|
968
1090
|
const lines = events.map((e) => JSON.stringify(e) + "\n").join("");
|
|
969
|
-
|
|
1091
|
+
fs4.appendFileSync(this.filePath, lines, "utf8");
|
|
970
1092
|
} catch {
|
|
971
1093
|
}
|
|
972
1094
|
}
|
|
@@ -974,8 +1096,8 @@ var StreamBuffer = class {
|
|
|
974
1096
|
load() {
|
|
975
1097
|
if (!this.filePath) return [];
|
|
976
1098
|
try {
|
|
977
|
-
if (
|
|
978
|
-
const content =
|
|
1099
|
+
if (fs4.existsSync(this.filePath)) {
|
|
1100
|
+
const content = fs4.readFileSync(this.filePath, "utf8");
|
|
979
1101
|
return content.split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
980
1102
|
}
|
|
981
1103
|
} catch {
|
|
@@ -986,7 +1108,7 @@ var StreamBuffer = class {
|
|
|
986
1108
|
clear() {
|
|
987
1109
|
if (!this.filePath) return;
|
|
988
1110
|
try {
|
|
989
|
-
if (
|
|
1111
|
+
if (fs4.existsSync(this.filePath)) fs4.unlinkSync(this.filePath);
|
|
990
1112
|
} catch {
|
|
991
1113
|
}
|
|
992
1114
|
}
|
|
@@ -995,14 +1117,14 @@ var StreamBuffer = class {
|
|
|
995
1117
|
const dir = os3.tmpdir();
|
|
996
1118
|
let names;
|
|
997
1119
|
try {
|
|
998
|
-
names =
|
|
1120
|
+
names = fs4.readdirSync(dir).filter((name) => name.startsWith(this.prefix));
|
|
999
1121
|
} catch {
|
|
1000
1122
|
return;
|
|
1001
1123
|
}
|
|
1002
1124
|
for (const name of names) {
|
|
1003
1125
|
try {
|
|
1004
|
-
const file =
|
|
1005
|
-
if (Date.now() -
|
|
1126
|
+
const file = path4.join(dir, name);
|
|
1127
|
+
if (Date.now() - fs4.statSync(file).mtimeMs > maxAgeMs) fs4.unlinkSync(file);
|
|
1006
1128
|
} catch {
|
|
1007
1129
|
}
|
|
1008
1130
|
}
|
|
@@ -1011,9 +1133,9 @@ var StreamBuffer = class {
|
|
|
1011
1133
|
|
|
1012
1134
|
// src/internal/streaming/crash-recovery.ts
|
|
1013
1135
|
var crypto2 = __toESM(require("crypto"));
|
|
1014
|
-
var
|
|
1136
|
+
var fs5 = __toESM(require("fs"));
|
|
1015
1137
|
var os4 = __toESM(require("os"));
|
|
1016
|
-
var
|
|
1138
|
+
var path5 = __toESM(require("path"));
|
|
1017
1139
|
|
|
1018
1140
|
// src/internal/support/errors.ts
|
|
1019
1141
|
function errorMessage(error) {
|
|
@@ -1028,7 +1150,7 @@ function recoveryServer(serverUrl) {
|
|
|
1028
1150
|
function recoveryFilePath(serverUrl, projectName) {
|
|
1029
1151
|
const key = crypto2.createHash("sha256").update(`${recoveryServer(serverUrl)}
|
|
1030
1152
|
${projectName}`).digest("hex").slice(0, 16);
|
|
1031
|
-
return
|
|
1153
|
+
return path5.join(os4.tmpdir(), `piwi-dashboard-recovery-${key}.json`);
|
|
1032
1154
|
}
|
|
1033
1155
|
function refusesRun(error) {
|
|
1034
1156
|
return error instanceof HttpError && [400, 409, 422].includes(error.status);
|
|
@@ -1049,11 +1171,11 @@ var CrashRecovery = class {
|
|
|
1049
1171
|
const file = { v: 1, serverUrl: this.serverUrl, payload };
|
|
1050
1172
|
const temp = `${this.filePath}.${process.pid}.${crypto2.randomBytes(6).toString("hex")}.tmp`;
|
|
1051
1173
|
try {
|
|
1052
|
-
|
|
1053
|
-
|
|
1174
|
+
fs5.writeFileSync(temp, JSON.stringify(file), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
1175
|
+
fs5.renameSync(temp, this.filePath);
|
|
1054
1176
|
this.logger.info("Saved recovery data for later upload");
|
|
1055
1177
|
} catch (error) {
|
|
1056
|
-
|
|
1178
|
+
fs5.rmSync(temp, { force: true });
|
|
1057
1179
|
this.logger.error(`Failed to save recovery data: ${errorMessage(error)}`);
|
|
1058
1180
|
}
|
|
1059
1181
|
}
|
|
@@ -1064,26 +1186,26 @@ var CrashRecovery = class {
|
|
|
1064
1186
|
load() {
|
|
1065
1187
|
let fd;
|
|
1066
1188
|
try {
|
|
1067
|
-
fd =
|
|
1189
|
+
fd = fs5.openSync(this.filePath, fs5.constants.O_RDONLY | (fs5.constants.O_NOFOLLOW ?? 0));
|
|
1068
1190
|
} catch {
|
|
1069
1191
|
return null;
|
|
1070
1192
|
}
|
|
1071
1193
|
try {
|
|
1072
|
-
const stat =
|
|
1194
|
+
const stat = fs5.fstatSync(fd);
|
|
1073
1195
|
if (!stat.isFile() || typeof process.getuid === "function" && stat.uid !== process.getuid()) return null;
|
|
1074
|
-
const file = JSON.parse(
|
|
1196
|
+
const file = JSON.parse(fs5.readFileSync(fd, "utf8"));
|
|
1075
1197
|
if (file?.v !== 1 || file.serverUrl !== this.serverUrl) return null;
|
|
1076
1198
|
return file.payload && typeof file.payload === "object" ? file.payload : null;
|
|
1077
1199
|
} catch {
|
|
1078
1200
|
return null;
|
|
1079
1201
|
} finally {
|
|
1080
|
-
|
|
1202
|
+
fs5.closeSync(fd);
|
|
1081
1203
|
}
|
|
1082
1204
|
}
|
|
1083
1205
|
/** Delete the recovery file from disk */
|
|
1084
1206
|
clear() {
|
|
1085
1207
|
try {
|
|
1086
|
-
|
|
1208
|
+
fs5.rmSync(this.filePath, { force: true });
|
|
1087
1209
|
} catch {
|
|
1088
1210
|
}
|
|
1089
1211
|
}
|
|
@@ -1115,25 +1237,25 @@ var CrashRecovery = class {
|
|
|
1115
1237
|
};
|
|
1116
1238
|
|
|
1117
1239
|
// src/internal/files/file-handler.ts
|
|
1118
|
-
var
|
|
1240
|
+
var fs7 = __toESM(require("fs"));
|
|
1119
1241
|
var os5 = __toESM(require("os"));
|
|
1120
|
-
var
|
|
1242
|
+
var path7 = __toESM(require("path"));
|
|
1121
1243
|
var crypto3 = __toESM(require("crypto"));
|
|
1122
1244
|
|
|
1123
1245
|
// src/internal/files/compression.ts
|
|
1124
|
-
var
|
|
1125
|
-
var
|
|
1246
|
+
var fs6 = __toESM(require("fs"));
|
|
1247
|
+
var path6 = __toESM(require("path"));
|
|
1126
1248
|
var zlib = __toESM(require("zlib"));
|
|
1127
1249
|
var import_node_util = require("util");
|
|
1128
1250
|
var gzipAsync = (0, import_node_util.promisify)(zlib.gzip);
|
|
1129
1251
|
async function compressDirectory(sourceDir) {
|
|
1130
1252
|
const files = [];
|
|
1131
1253
|
function collect(dir, baseDir = "") {
|
|
1132
|
-
for (const entry of
|
|
1133
|
-
const full =
|
|
1134
|
-
const rel =
|
|
1254
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
1255
|
+
const full = path6.join(dir, entry.name);
|
|
1256
|
+
const rel = path6.join(baseDir, entry.name);
|
|
1135
1257
|
if (entry.isDirectory()) collect(full, rel);
|
|
1136
|
-
else if (entry.isFile()) files.push({ path: rel, content:
|
|
1258
|
+
else if (entry.isFile()) files.push({ path: rel, content: fs6.readFileSync(full) });
|
|
1137
1259
|
}
|
|
1138
1260
|
}
|
|
1139
1261
|
collect(sourceDir);
|
|
@@ -1197,7 +1319,8 @@ var ATTACHMENT_NAMES = {
|
|
|
1197
1319
|
userPick: "piwi-user-pick",
|
|
1198
1320
|
aiUsage: "piwi-ai-usage",
|
|
1199
1321
|
aiMeta: "piwi-ai-meta",
|
|
1200
|
-
resources: "piwi-resources"
|
|
1322
|
+
resources: "piwi-resources",
|
|
1323
|
+
pauses: "piwi-pauses"
|
|
1201
1324
|
};
|
|
1202
1325
|
var INTERNAL_ATTACHMENT_NAMES = new Set(Object.values(ATTACHMENT_NAMES));
|
|
1203
1326
|
var LOCATOR_SUGGESTION_ANNOTATION = ATTACHMENT_NAMES.locatorSuggestion;
|
|
@@ -1231,19 +1354,19 @@ var FileHandler = class {
|
|
|
1231
1354
|
}
|
|
1232
1355
|
/** Locate a Playwright HTML report directory containing `index.html`. Optionally override the search path. */
|
|
1233
1356
|
findHTMLReportDirectory(customDir) {
|
|
1234
|
-
const possibleDirs = customDir ? [customDir,
|
|
1357
|
+
const possibleDirs = customDir ? [customDir, path7.join(process.cwd(), customDir)] : ["playwright-report", "./playwright-report", path7.join(process.cwd(), "playwright-report")];
|
|
1235
1358
|
for (const reportDir of possibleDirs) {
|
|
1236
|
-
if (
|
|
1237
|
-
if (
|
|
1359
|
+
if (fs7.existsSync(reportDir) && fs7.statSync(reportDir).isDirectory()) {
|
|
1360
|
+
if (fs7.existsSync(path7.join(reportDir, "index.html"))) return reportDir;
|
|
1238
1361
|
}
|
|
1239
1362
|
}
|
|
1240
1363
|
return null;
|
|
1241
1364
|
}
|
|
1242
1365
|
/** Check if `dir` exists and is a directory; also checks `path.join(process.cwd(), dir)` */
|
|
1243
1366
|
findReportDirectory(dir) {
|
|
1244
|
-
const candidates = [dir,
|
|
1367
|
+
const candidates = [dir, path7.join(process.cwd(), dir)];
|
|
1245
1368
|
for (const candidate of candidates) {
|
|
1246
|
-
if (
|
|
1369
|
+
if (fs7.existsSync(candidate) && fs7.statSync(candidate).isDirectory()) return candidate;
|
|
1247
1370
|
}
|
|
1248
1371
|
return null;
|
|
1249
1372
|
}
|
|
@@ -1261,7 +1384,7 @@ var FileHandler = class {
|
|
|
1261
1384
|
const set = /* @__PURE__ */ new Set();
|
|
1262
1385
|
if (testCase.attachments) {
|
|
1263
1386
|
for (const a of testCase.attachments) {
|
|
1264
|
-
if (a.name === "trace" && a.path) set.add(
|
|
1387
|
+
if (a.name === "trace" && a.path) set.add(path7.resolve(a.path));
|
|
1265
1388
|
}
|
|
1266
1389
|
}
|
|
1267
1390
|
return Array.from(set);
|
|
@@ -1289,24 +1412,24 @@ var FileHandler = class {
|
|
|
1289
1412
|
cleanupBodyAttachments() {
|
|
1290
1413
|
if (!this.bodyDir) return;
|
|
1291
1414
|
try {
|
|
1292
|
-
|
|
1415
|
+
fs7.rmSync(this.bodyDir, { recursive: true, force: true });
|
|
1293
1416
|
} catch (error) {
|
|
1294
1417
|
this.logger.debug(`Could not remove temp attachments at ${this.bodyDir}: ${errorMessage(error)}`);
|
|
1295
1418
|
}
|
|
1296
1419
|
this.bodyDir = null;
|
|
1297
1420
|
}
|
|
1298
1421
|
resolvePathAttachment(a, testCase) {
|
|
1299
|
-
if (!a.path || !
|
|
1300
|
-
const size =
|
|
1422
|
+
if (!a.path || !fs7.existsSync(a.path)) return null;
|
|
1423
|
+
const size = fs7.statSync(a.path).size;
|
|
1301
1424
|
if (size > this.maxAttachmentBytes) {
|
|
1302
1425
|
this.warnOversized(a, testCase, size);
|
|
1303
1426
|
return null;
|
|
1304
1427
|
}
|
|
1305
1428
|
return {
|
|
1306
1429
|
name: a.name || "attachment",
|
|
1307
|
-
path:
|
|
1430
|
+
path: path7.resolve(a.path),
|
|
1308
1431
|
contentType: a.contentType || "application/octet-stream",
|
|
1309
|
-
originalName:
|
|
1432
|
+
originalName: path7.basename(a.path)
|
|
1310
1433
|
};
|
|
1311
1434
|
}
|
|
1312
1435
|
resolveBodyAttachment(a, testCase) {
|
|
@@ -1321,8 +1444,8 @@ var FileHandler = class {
|
|
|
1321
1444
|
let filePath2 = this.bodyFiles.get(a);
|
|
1322
1445
|
if (!filePath2) {
|
|
1323
1446
|
try {
|
|
1324
|
-
filePath2 =
|
|
1325
|
-
|
|
1447
|
+
filePath2 = path7.join(this.bodyAttachmentDir(), `${++this.bodyFileCount}-${originalName}`);
|
|
1448
|
+
fs7.writeFileSync(filePath2, body);
|
|
1326
1449
|
this.bodyFiles.set(a, filePath2);
|
|
1327
1450
|
} catch (error) {
|
|
1328
1451
|
this.logger.warn(`Could not stage attachment "${a.name}" for upload: ${errorMessage(error)}`);
|
|
@@ -1332,7 +1455,7 @@ var FileHandler = class {
|
|
|
1332
1455
|
return { name: a.name || "attachment", path: filePath2, contentType, originalName };
|
|
1333
1456
|
}
|
|
1334
1457
|
bodyAttachmentDir() {
|
|
1335
|
-
if (!this.bodyDir) this.bodyDir =
|
|
1458
|
+
if (!this.bodyDir) this.bodyDir = fs7.mkdtempSync(path7.join(os5.tmpdir(), "piwi-dashboard-attachments-"));
|
|
1336
1459
|
return this.bodyDir;
|
|
1337
1460
|
}
|
|
1338
1461
|
warnOversized(a, testCase, size) {
|
|
@@ -1355,10 +1478,10 @@ var FileHandler = class {
|
|
|
1355
1478
|
/** Parse Piwi-internal attachment bodies (`piwi-network`, `piwi-web-vitals`, `piwi-console`, `piwi-aria-snapshot`, `piwi-aria-snapshot-json`) into structured fields on the test case */
|
|
1356
1479
|
parsePerformanceAttachments(testCase, attachments) {
|
|
1357
1480
|
const find = (name) => attachments.find((a) => a.name === name);
|
|
1358
|
-
const
|
|
1359
|
-
if (
|
|
1481
|
+
const net2 = find(ATTACHMENT_NAMES.network);
|
|
1482
|
+
if (net2?.body) {
|
|
1360
1483
|
try {
|
|
1361
|
-
testCase.networkRequests = JSON.parse(
|
|
1484
|
+
testCase.networkRequests = JSON.parse(net2.body.toString());
|
|
1362
1485
|
} catch {
|
|
1363
1486
|
}
|
|
1364
1487
|
}
|
|
@@ -1414,17 +1537,17 @@ var FileHandler = class {
|
|
|
1414
1537
|
const tracePaths = this.findTraceFiles(testCase);
|
|
1415
1538
|
let lastPath = null;
|
|
1416
1539
|
for (const tp of tracePaths) {
|
|
1417
|
-
if (
|
|
1540
|
+
if (fs7.existsSync(tp)) lastPath = tp;
|
|
1418
1541
|
}
|
|
1419
1542
|
if (!lastPath) return null;
|
|
1420
1543
|
const hash = crypto3.createHash("sha256");
|
|
1421
|
-
await new Promise((
|
|
1422
|
-
|
|
1544
|
+
await new Promise((resolve11, reject) => {
|
|
1545
|
+
fs7.createReadStream(lastPath).on("data", (chunk) => hash.update(chunk)).on("end", resolve11).on("error", reject);
|
|
1423
1546
|
});
|
|
1424
1547
|
return {
|
|
1425
1548
|
tracePath: lastPath,
|
|
1426
1549
|
hash: hash.digest("hex"),
|
|
1427
|
-
size:
|
|
1550
|
+
size: fs7.statSync(lastPath).size
|
|
1428
1551
|
};
|
|
1429
1552
|
}
|
|
1430
1553
|
/** Ask the server which trace hashes it already has, returning the set of hashes that are missing */
|
|
@@ -1451,14 +1574,14 @@ function formatMiB(bytes) {
|
|
|
1451
1574
|
var import_node_child_process = require("child_process");
|
|
1452
1575
|
|
|
1453
1576
|
// src/internal/support/test-paths.ts
|
|
1454
|
-
var
|
|
1577
|
+
var path8 = __toESM(require("path"));
|
|
1455
1578
|
function configDirFromConfig(config) {
|
|
1456
|
-
if (config?.configFile) return
|
|
1579
|
+
if (config?.configFile) return path8.dirname(config.configFile);
|
|
1457
1580
|
if (config?.rootDir) return config.rootDir;
|
|
1458
1581
|
return process.cwd();
|
|
1459
1582
|
}
|
|
1460
1583
|
function relativeTestPath(file, base) {
|
|
1461
|
-
return
|
|
1584
|
+
return path8.relative(base, file).split(path8.sep).join("/");
|
|
1462
1585
|
}
|
|
1463
1586
|
|
|
1464
1587
|
// src/internal/collect/metadata-collector.ts
|
|
@@ -1518,11 +1641,6 @@ function resolveScmBaseBranch(env) {
|
|
|
1518
1641
|
const trimmed = target?.trim();
|
|
1519
1642
|
return trimmed ? normalizeRef(trimmed) : void 0;
|
|
1520
1643
|
}
|
|
1521
|
-
function detectCi(env) {
|
|
1522
|
-
return Boolean(
|
|
1523
|
-
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"
|
|
1524
|
-
);
|
|
1525
|
-
}
|
|
1526
1644
|
function resolveRunOrigin(env, warn) {
|
|
1527
1645
|
const raw = env[PIWI_ORIGIN_ENV.kind]?.trim();
|
|
1528
1646
|
let kind = detectCi(env) ? "ci" : "local";
|
|
@@ -1761,7 +1879,7 @@ var MetadataCollector = class {
|
|
|
1761
1879
|
};
|
|
1762
1880
|
|
|
1763
1881
|
// src/internal/streaming/stream-manager.ts
|
|
1764
|
-
var
|
|
1882
|
+
var fs9 = __toESM(require("fs"));
|
|
1765
1883
|
|
|
1766
1884
|
// src/internal/streaming/bounded-queue.ts
|
|
1767
1885
|
var DROP_TIER = {
|
|
@@ -1900,10 +2018,10 @@ function createLimiter(maxConcurrent) {
|
|
|
1900
2018
|
if (run) run();
|
|
1901
2019
|
};
|
|
1902
2020
|
return function limit(fn) {
|
|
1903
|
-
return new Promise((
|
|
2021
|
+
return new Promise((resolve11, reject) => {
|
|
1904
2022
|
const run = () => {
|
|
1905
2023
|
active++;
|
|
1906
|
-
Promise.resolve().then(fn).then(
|
|
2024
|
+
Promise.resolve().then(fn).then(resolve11, reject).finally(next);
|
|
1907
2025
|
};
|
|
1908
2026
|
if (active < limitValue) run();
|
|
1909
2027
|
else queue.push(run);
|
|
@@ -1912,18 +2030,18 @@ function createLimiter(maxConcurrent) {
|
|
|
1912
2030
|
}
|
|
1913
2031
|
|
|
1914
2032
|
// src/internal/support/setup-file.ts
|
|
1915
|
-
var
|
|
2033
|
+
var path9 = __toESM(require("path"));
|
|
1916
2034
|
var os6 = __toESM(require("os"));
|
|
1917
|
-
var
|
|
2035
|
+
var fs8 = __toESM(require("fs"));
|
|
1918
2036
|
function getSetupFilePath(projectName) {
|
|
1919
|
-
return
|
|
2037
|
+
return path9.join(os6.tmpdir(), `piwi-dashboard-setup-${hashForProject(projectName)}.json`);
|
|
1920
2038
|
}
|
|
1921
2039
|
function readSetupInfo(projectName) {
|
|
1922
2040
|
const setupFile = getSetupFilePath(projectName);
|
|
1923
2041
|
try {
|
|
1924
|
-
if (
|
|
1925
|
-
const info = JSON.parse(
|
|
1926
|
-
|
|
2042
|
+
if (fs8.existsSync(setupFile)) {
|
|
2043
|
+
const info = JSON.parse(fs8.readFileSync(setupFile, "utf8"));
|
|
2044
|
+
fs8.unlinkSync(setupFile);
|
|
1927
2045
|
if (info.projectName === projectName) return info;
|
|
1928
2046
|
}
|
|
1929
2047
|
} catch {
|
|
@@ -2142,7 +2260,7 @@ var StreamManager = class {
|
|
|
2142
2260
|
} catch (error) {
|
|
2143
2261
|
if (error instanceof HttpError && (error.status === 401 || error.status === 403)) {
|
|
2144
2262
|
this.logger.warn(
|
|
2145
|
-
`Live streaming could not start: the dashboard at ${this.options.serverUrl} requires authentication. Set the reporter's \`apiKey\` option
|
|
2263
|
+
`Live streaming could not start: the dashboard at ${this.options.serverUrl} requires authentication. Set the reporter's \`apiKey\` option, or PIWI_API_KEY in the environment or in the .env file next to the Playwright config \u2014 create a key under Settings \u2192 Users on the dashboard. Falling back to batch upload at the end of the run.`
|
|
2146
2264
|
);
|
|
2147
2265
|
} else {
|
|
2148
2266
|
this.logger.warn(
|
|
@@ -2379,7 +2497,7 @@ var StreamManager = class {
|
|
|
2379
2497
|
this.logger.debugError(
|
|
2380
2498
|
`${this.pendingEvents.length} events pending, retrying (attempt ${attempt + 1}/${MAX_ATTEMPTS})...`
|
|
2381
2499
|
);
|
|
2382
|
-
await new Promise((
|
|
2500
|
+
await new Promise((resolve11) => setTimeout(resolve11, Math.min(1e3 * Math.pow(2, attempt), 1e4)));
|
|
2383
2501
|
}
|
|
2384
2502
|
if (!this.pendingEvents.isEmpty) {
|
|
2385
2503
|
const remaining = this.pendingEvents.takeAll();
|
|
@@ -2423,7 +2541,7 @@ var StreamManager = class {
|
|
|
2423
2541
|
}
|
|
2424
2542
|
/** Schedule a live upload of trace and attachment files for a test case. Skips cases with no files. Concurrency is limited to 2 simultaneous uploads. */
|
|
2425
2543
|
scheduleLiveUpload(tc) {
|
|
2426
|
-
const hasTrace = !!this.options.uploadTraces && this.fileHandler.findTraceFiles(tc).some((p) =>
|
|
2544
|
+
const hasTrace = !!this.options.uploadTraces && this.fileHandler.findTraceFiles(tc).some((p) => fs9.existsSync(p));
|
|
2427
2545
|
const hasAttachments = this.fileHandler.findAllAttachments(tc).length > 0;
|
|
2428
2546
|
if (!hasTrace && !hasAttachments) return;
|
|
2429
2547
|
const promise = (async () => {
|
|
@@ -2436,7 +2554,7 @@ var StreamManager = class {
|
|
|
2436
2554
|
if (flush) await flush;
|
|
2437
2555
|
const delays = [0, 1e3, 3e3];
|
|
2438
2556
|
for (let attempt = 0; attempt < delays.length; attempt++) {
|
|
2439
|
-
if (delays[attempt]) await new Promise((
|
|
2557
|
+
if (delays[attempt]) await new Promise((resolve11) => setTimeout(resolve11, delays[attempt]));
|
|
2440
2558
|
try {
|
|
2441
2559
|
await this.limitLiveUpload(
|
|
2442
2560
|
() => this.uploader.uploadCaseFiles(
|
|
@@ -3251,7 +3369,7 @@ function maskUrlFragment(hash) {
|
|
|
3251
3369
|
}
|
|
3252
3370
|
|
|
3253
3371
|
// ../core/src/locator-break.ts
|
|
3254
|
-
var filePath = (
|
|
3372
|
+
var filePath = (path34) => path34.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3255
3373
|
function sameFilePath(a, b) {
|
|
3256
3374
|
const x = filePath(a);
|
|
3257
3375
|
const y = filePath(b);
|
|
@@ -3546,23 +3664,48 @@ function flattenSteps(steps, depth = 0) {
|
|
|
3546
3664
|
}
|
|
3547
3665
|
return result;
|
|
3548
3666
|
}
|
|
3549
|
-
function
|
|
3667
|
+
function epochMs(value) {
|
|
3668
|
+
if (value instanceof Date) return value.getTime();
|
|
3669
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3670
|
+
}
|
|
3671
|
+
function pausedOverlap(start, duration, pauses) {
|
|
3672
|
+
const from = epochMs(start);
|
|
3673
|
+
const length = typeof duration === "number" && duration > 0 ? duration : 0;
|
|
3674
|
+
if (from === null || length === 0 || pauses.length === 0) return 0;
|
|
3675
|
+
const to = from + length;
|
|
3676
|
+
const spans = pauses.filter((p) => p.duration > 0).map((p) => [Math.max(from, p.startedAt), Math.min(to, p.startedAt + p.duration)]).filter(([a, b]) => b > a).sort((x, y) => x[0] - y[0]);
|
|
3677
|
+
let ms = 0;
|
|
3678
|
+
let reached = from;
|
|
3679
|
+
for (const [a, b] of spans) {
|
|
3680
|
+
const begin = Math.max(a, reached);
|
|
3681
|
+
if (b > begin) ms += b - begin;
|
|
3682
|
+
reached = Math.max(reached, b);
|
|
3683
|
+
}
|
|
3684
|
+
return ms;
|
|
3685
|
+
}
|
|
3686
|
+
function unpausedDuration(step, pauses) {
|
|
3687
|
+
const duration = typeof step.duration === "number" && step.duration > 0 ? step.duration : 0;
|
|
3688
|
+
return duration - pausedOverlap(step.startTime, duration, pauses);
|
|
3689
|
+
}
|
|
3690
|
+
function collectStepMetrics(steps, errors, pauses = []) {
|
|
3550
3691
|
const flatSteps = flattenSteps(steps);
|
|
3551
3692
|
if (errors) markRecoveredSteps(flatSteps, errors);
|
|
3552
|
-
const
|
|
3693
|
+
const own = (s) => pauses.length > 0 && s.duration > 0 ? unpausedDuration(s, pauses) : s.duration;
|
|
3694
|
+
const totalStepDuration = steps.reduce((sum, s) => sum + (own(s) || 0), 0);
|
|
3553
3695
|
let slowestStep = null;
|
|
3554
3696
|
for (const s of flatSteps) {
|
|
3555
|
-
|
|
3697
|
+
const duration = own(s);
|
|
3698
|
+
if (!slowestStep || duration > slowestStep.duration) slowestStep = { title: s.title, duration };
|
|
3556
3699
|
}
|
|
3557
3700
|
const navSteps = flatSteps.filter((s) => s.category === "navigation");
|
|
3558
3701
|
const waitSteps = flatSteps.filter((s) => s.category === "wait");
|
|
3559
|
-
const waitTotalDuration = waitSteps.reduce((sum, s) => sum + (s
|
|
3702
|
+
const waitTotalDuration = waitSteps.reduce((sum, s) => sum + (own(s) || 0), 0);
|
|
3560
3703
|
return {
|
|
3561
3704
|
steps: flatSteps,
|
|
3562
3705
|
totalStepDuration,
|
|
3563
3706
|
slowestStep,
|
|
3564
3707
|
navigationCount: navSteps.length,
|
|
3565
|
-
navigationTotalDuration: navSteps.reduce((sum, s) => sum + (s
|
|
3708
|
+
navigationTotalDuration: navSteps.reduce((sum, s) => sum + (own(s) || 0), 0),
|
|
3566
3709
|
waitTotalDuration,
|
|
3567
3710
|
waitCount: waitSteps.length
|
|
3568
3711
|
};
|
|
@@ -3678,19 +3821,43 @@ function extractWaitEvents(steps, insideWait = false) {
|
|
|
3678
3821
|
return events;
|
|
3679
3822
|
}
|
|
3680
3823
|
|
|
3824
|
+
// src/internal/collect/pause-events.ts
|
|
3825
|
+
function readPauses(body) {
|
|
3826
|
+
if (!body) return [];
|
|
3827
|
+
try {
|
|
3828
|
+
const value = JSON.parse(body.toString("utf8"));
|
|
3829
|
+
if (!Array.isArray(value)) return [];
|
|
3830
|
+
return value.filter(
|
|
3831
|
+
(p) => !!p && typeof p === "object" && typeof p.location === "string" && typeof p.startedAt === "number" && typeof p.duration === "number"
|
|
3832
|
+
);
|
|
3833
|
+
} catch {
|
|
3834
|
+
return [];
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
function pauseStepEvents(pauses) {
|
|
3838
|
+
return pauses.map((pause) => ({
|
|
3839
|
+
title: typeof pause.title === "string" ? pause.title : "",
|
|
3840
|
+
category: "pause",
|
|
3841
|
+
startedAt: pause.startedAt,
|
|
3842
|
+
duration: Math.max(0, pause.duration),
|
|
3843
|
+
status: typeof pause.reason === "string" ? pause.reason : "breakpoint",
|
|
3844
|
+
location: pause.location
|
|
3845
|
+
}));
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3681
3848
|
// src/internal/support/reporter-version.ts
|
|
3682
|
-
var
|
|
3683
|
-
var
|
|
3849
|
+
var fs10 = __toESM(require("fs"));
|
|
3850
|
+
var path10 = __toESM(require("path"));
|
|
3684
3851
|
var cachedVersion = null;
|
|
3685
3852
|
var CANDIDATE_OFFSETS = ["..", "../../.."];
|
|
3686
3853
|
function findOwnPackageJson(fromDir) {
|
|
3687
3854
|
for (const offset of CANDIDATE_OFFSETS) {
|
|
3688
3855
|
try {
|
|
3689
|
-
const pkgPath =
|
|
3690
|
-
const pkg = JSON.parse(
|
|
3856
|
+
const pkgPath = path10.resolve(fromDir, offset, "package.json");
|
|
3857
|
+
const pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
|
|
3691
3858
|
const { name, version } = pkg;
|
|
3692
3859
|
if (name === "@piwitests/reporter" && typeof version === "string")
|
|
3693
|
-
return { version, root:
|
|
3860
|
+
return { version, root: path10.dirname(pkgPath) };
|
|
3694
3861
|
} catch {
|
|
3695
3862
|
}
|
|
3696
3863
|
}
|
|
@@ -3703,11 +3870,11 @@ function getReporterVersion() {
|
|
|
3703
3870
|
}
|
|
3704
3871
|
|
|
3705
3872
|
// src/internal/support/source-snippet.ts
|
|
3706
|
-
var
|
|
3707
|
-
var
|
|
3873
|
+
var fs11 = __toESM(require("fs"));
|
|
3874
|
+
var path11 = __toESM(require("path"));
|
|
3708
3875
|
function readSourceSnippet(file, declLine, context, failingLine) {
|
|
3709
3876
|
try {
|
|
3710
|
-
const content =
|
|
3877
|
+
const content = fs11.readFileSync(file, "utf-8");
|
|
3711
3878
|
const lines = content.split("\n");
|
|
3712
3879
|
const anchor = failingLine ?? declLine;
|
|
3713
3880
|
const start = Math.max(0, anchor - context - 1);
|
|
@@ -3747,16 +3914,16 @@ function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
|
3747
3914
|
if (picked.length >= maxFrames) break;
|
|
3748
3915
|
let abs;
|
|
3749
3916
|
try {
|
|
3750
|
-
abs =
|
|
3917
|
+
abs = path11.resolve(frame.file);
|
|
3751
3918
|
} catch {
|
|
3752
3919
|
continue;
|
|
3753
3920
|
}
|
|
3754
|
-
const rel =
|
|
3755
|
-
if (rel.startsWith("..") ||
|
|
3921
|
+
const rel = path11.relative(projectRoot, abs);
|
|
3922
|
+
if (rel.startsWith("..") || path11.isAbsolute(rel)) continue;
|
|
3756
3923
|
if (!isNaN(frame.line)) add(abs, frame.line);
|
|
3757
3924
|
}
|
|
3758
3925
|
}
|
|
3759
|
-
const absTest =
|
|
3926
|
+
const absTest = path11.resolve(testFile2);
|
|
3760
3927
|
if (!picked.some((p) => p.absFile === absTest)) {
|
|
3761
3928
|
add(absTest, extractFailingLine(errorText, testFile2, declLine));
|
|
3762
3929
|
}
|
|
@@ -3764,49 +3931,23 @@ function collectSourceFrames(errorText, testFile2, declLine, opts = {}) {
|
|
|
3764
3931
|
for (const p of picked.slice(0, maxFrames)) {
|
|
3765
3932
|
const snippet = readSourceSnippet(p.absFile, p.line, context, p.line);
|
|
3766
3933
|
if (snippet) {
|
|
3767
|
-
frames.push({ file:
|
|
3934
|
+
frames.push({ file: path11.relative(projectRoot, p.absFile) || path11.basename(p.absFile), line: p.line, snippet });
|
|
3768
3935
|
}
|
|
3769
3936
|
}
|
|
3770
3937
|
return frames;
|
|
3771
3938
|
}
|
|
3772
3939
|
function extractFailingLine(errorText, testFile2, declarationLine) {
|
|
3773
3940
|
if (!errorText) return declarationLine;
|
|
3774
|
-
const expectedFile =
|
|
3941
|
+
const expectedFile = path11.resolve(testFile2);
|
|
3775
3942
|
for (const frame of stackFrames(errorText)) {
|
|
3776
3943
|
try {
|
|
3777
|
-
if (
|
|
3944
|
+
if (path11.resolve(frame.file) === expectedFile && !isNaN(frame.line)) return frame.line;
|
|
3778
3945
|
} catch {
|
|
3779
3946
|
}
|
|
3780
3947
|
}
|
|
3781
3948
|
return declarationLine;
|
|
3782
3949
|
}
|
|
3783
3950
|
|
|
3784
|
-
// src/internal/support/ci.ts
|
|
3785
|
-
function detectCiIds(env) {
|
|
3786
|
-
if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) {
|
|
3787
|
-
const run = env.GITHUB_RUN_ATTEMPT ? `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` : env.GITHUB_RUN_ID;
|
|
3788
|
-
return { run, job: env.GITHUB_JOB || null };
|
|
3789
|
-
}
|
|
3790
|
-
if (env.GITLAB_CI && env.CI_PIPELINE_ID) return { run: env.CI_PIPELINE_ID, job: env.CI_JOB_ID || null };
|
|
3791
|
-
if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return { run: env.CIRCLE_WORKFLOW_ID, job: env.CIRCLE_BUILD_NUM || null };
|
|
3792
|
-
if (env.TRAVIS && env.TRAVIS_BUILD_ID) return { run: env.TRAVIS_BUILD_ID, job: env.TRAVIS_JOB_ID || null };
|
|
3793
|
-
if (env.TF_BUILD && env.BUILD_BUILDID) return { run: env.BUILD_BUILDID, job: env.SYSTEM_JOBID || null };
|
|
3794
|
-
if (env.JENKINS_URL && env.BUILD_ID) return { run: env.BUILD_ID, job: null };
|
|
3795
|
-
if (env.BUILDKITE_BUILD_ID) return { run: env.BUILDKITE_BUILD_ID, job: env.BUILDKITE_JOB_ID || null };
|
|
3796
|
-
if (env.TEAMCITY_BUILD_ID) return { run: env.TEAMCITY_BUILD_ID, job: null };
|
|
3797
|
-
if (env.BITBUCKET_BUILD_NUMBER) return { run: env.BITBUCKET_BUILD_NUMBER, job: env.BITBUCKET_STEP_UUID || null };
|
|
3798
|
-
if (env.SEMAPHORE_WORKFLOW_ID) return { run: env.SEMAPHORE_WORKFLOW_ID, job: env.SEMAPHORE_JOB_ID || null };
|
|
3799
|
-
if (env.APPVEYOR_BUILD_ID) return { run: env.APPVEYOR_BUILD_ID, job: env.APPVEYOR_JOB_ID || null };
|
|
3800
|
-
if (env.DRONE_BUILD_NUMBER) return { run: env.DRONE_BUILD_NUMBER, job: null };
|
|
3801
|
-
return null;
|
|
3802
|
-
}
|
|
3803
|
-
function resolveRunLabel(configured, sharded) {
|
|
3804
|
-
if (configured) return configured;
|
|
3805
|
-
const ci = detectCiIds(process.env);
|
|
3806
|
-
if (!ci) return null;
|
|
3807
|
-
return !sharded && ci.job ? `${ci.run}|${ci.job}` : ci.run;
|
|
3808
|
-
}
|
|
3809
|
-
|
|
3810
3951
|
// src/internal/support/worker-index.ts
|
|
3811
3952
|
function workerIndexOf(result) {
|
|
3812
3953
|
if (!result) return null;
|
|
@@ -3892,28 +4033,28 @@ function isListMode(argv = process.argv) {
|
|
|
3892
4033
|
}
|
|
3893
4034
|
|
|
3894
4035
|
// src/public/global-setup.ts
|
|
3895
|
-
var
|
|
3896
|
-
var
|
|
4036
|
+
var path15 = __toESM(require("path"));
|
|
4037
|
+
var fs15 = __toESM(require("fs"));
|
|
3897
4038
|
|
|
3898
4039
|
// src/internal/support/aria-sampling.ts
|
|
3899
|
-
var
|
|
4040
|
+
var path12 = __toESM(require("path"));
|
|
3900
4041
|
var os7 = __toESM(require("os"));
|
|
3901
|
-
var
|
|
4042
|
+
var fs12 = __toESM(require("fs"));
|
|
3902
4043
|
function ariaSampleIdentity(filePath2, title) {
|
|
3903
4044
|
return `${filePath2}\0${title}`;
|
|
3904
4045
|
}
|
|
3905
4046
|
function getAriaSampleFilePath(projectName) {
|
|
3906
|
-
return
|
|
4047
|
+
return path12.join(os7.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
3907
4048
|
}
|
|
3908
4049
|
function writeAriaSampleFile(projectName, identities) {
|
|
3909
4050
|
try {
|
|
3910
|
-
|
|
4051
|
+
fs12.writeFileSync(getAriaSampleFilePath(projectName), JSON.stringify({ projectName, identities }));
|
|
3911
4052
|
} catch {
|
|
3912
4053
|
}
|
|
3913
4054
|
}
|
|
3914
4055
|
function clearAriaSampleFile(projectName) {
|
|
3915
4056
|
try {
|
|
3916
|
-
|
|
4057
|
+
fs12.rmSync(getAriaSampleFilePath(projectName), { force: true });
|
|
3917
4058
|
} catch {
|
|
3918
4059
|
}
|
|
3919
4060
|
}
|
|
@@ -3922,7 +4063,7 @@ function loadAriaSampleSet(projectName) {
|
|
|
3922
4063
|
if (cachedSets.has(projectName)) return cachedSets.get(projectName);
|
|
3923
4064
|
let set = null;
|
|
3924
4065
|
try {
|
|
3925
|
-
const raw =
|
|
4066
|
+
const raw = fs12.readFileSync(getAriaSampleFilePath(projectName), "utf8");
|
|
3926
4067
|
const parsed2 = JSON.parse(raw);
|
|
3927
4068
|
if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
|
|
3928
4069
|
set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
|
|
@@ -4052,7 +4193,7 @@ async function checkBaseUrl(target, options = {}) {
|
|
|
4052
4193
|
} else {
|
|
4053
4194
|
reason = outcome.error;
|
|
4054
4195
|
}
|
|
4055
|
-
if (attempt < attempts) await new Promise((
|
|
4196
|
+
if (attempt < attempts) await new Promise((resolve11) => setTimeout(resolve11, delayMs));
|
|
4056
4197
|
}
|
|
4057
4198
|
return `${reason} (${attempts} ${attempts === 1 ? "attempt" : "attempts"})`;
|
|
4058
4199
|
}
|
|
@@ -4076,14 +4217,14 @@ async function checkBaseUrls(config, options = {}) {
|
|
|
4076
4217
|
}
|
|
4077
4218
|
|
|
4078
4219
|
// src/internal/manifest/collect.ts
|
|
4079
|
-
var
|
|
4080
|
-
var
|
|
4220
|
+
var fs13 = __toESM(require("fs"));
|
|
4221
|
+
var path13 = __toESM(require("path"));
|
|
4081
4222
|
|
|
4082
4223
|
// src/internal/transport/backend-request.ts
|
|
4083
4224
|
var http2 = __toESM(require("http"));
|
|
4084
4225
|
var https2 = __toESM(require("https"));
|
|
4085
4226
|
function backendRequest(url, init) {
|
|
4086
|
-
return new Promise((
|
|
4227
|
+
return new Promise((resolve11, reject) => {
|
|
4087
4228
|
const target = new URL(url);
|
|
4088
4229
|
const headers = { ...init.headers };
|
|
4089
4230
|
if (init.body !== void 0) headers["Content-Length"] = Buffer.byteLength(init.body);
|
|
@@ -4098,7 +4239,7 @@ function backendRequest(url, init) {
|
|
|
4098
4239
|
});
|
|
4099
4240
|
res.on(
|
|
4100
4241
|
"end",
|
|
4101
|
-
() =>
|
|
4242
|
+
() => resolve11({ status: res.statusCode ?? 0, headers: res.headers, text: Buffer.concat(chunks).toString("utf8") })
|
|
4102
4243
|
);
|
|
4103
4244
|
res.on("error", reject);
|
|
4104
4245
|
res.on("close", () => {
|
|
@@ -4143,10 +4284,10 @@ function hasInstrumentationHeader(headers) {
|
|
|
4143
4284
|
return INSTRUMENTATION_HEADERS.some((h) => get(h) != null && get(h) !== "");
|
|
4144
4285
|
}
|
|
4145
4286
|
function readCommittedManifest(configDir) {
|
|
4146
|
-
const file =
|
|
4287
|
+
const file = path13.join(configDir, COMMITTED_MANIFEST_FILE);
|
|
4147
4288
|
try {
|
|
4148
|
-
if (!
|
|
4149
|
-
return parseManifestJson(
|
|
4289
|
+
if (!fs13.existsSync(file)) return null;
|
|
4290
|
+
return parseManifestJson(fs13.readFileSync(file, "utf8"));
|
|
4150
4291
|
} catch {
|
|
4151
4292
|
return null;
|
|
4152
4293
|
}
|
|
@@ -4172,8 +4313,8 @@ function backendManifestFetch(insecure) {
|
|
|
4172
4313
|
function instrumentationManifestUrls(baseUrl) {
|
|
4173
4314
|
const base = new URL(baseUrl);
|
|
4174
4315
|
const root = new URL(INSTRUMENTATION_MANIFEST_PATH, base).toString();
|
|
4175
|
-
const
|
|
4176
|
-
return
|
|
4316
|
+
const path34 = base.pathname.replace(/\/+$/, "");
|
|
4317
|
+
return path34 ? [new URL(`${path34}${INSTRUMENTATION_MANIFEST_PATH}`, base).toString(), root] : [root];
|
|
4177
4318
|
}
|
|
4178
4319
|
async function fetchInstrumentationManifest(baseUrl, fetchImpl = fetch) {
|
|
4179
4320
|
try {
|
|
@@ -4311,13 +4452,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
4311
4452
|
}
|
|
4312
4453
|
function normalizeCoveragePath(raw) {
|
|
4313
4454
|
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
4314
|
-
const
|
|
4315
|
-
if (!
|
|
4316
|
-
if (/(^|\/)node_modules\//.test(
|
|
4317
|
-
return
|
|
4455
|
+
const path34 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4456
|
+
if (!path34 || path34.startsWith("../") || path34.startsWith("/") || /^[A-Za-z]:\//.test(path34)) return null;
|
|
4457
|
+
if (/(^|\/)node_modules\//.test(path34)) return null;
|
|
4458
|
+
return path34;
|
|
4318
4459
|
}
|
|
4319
|
-
function fileEntry(
|
|
4320
|
-
const file = { path:
|
|
4460
|
+
function fileEntry(path34, runtime, blob, functions = [], lines = []) {
|
|
4461
|
+
const file = { path: path34, runtime };
|
|
4321
4462
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
4322
4463
|
if (functions.length > 0) file.functions = functions;
|
|
4323
4464
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -4336,11 +4477,11 @@ function sanitizeCoverage(raw) {
|
|
|
4336
4477
|
const files = [];
|
|
4337
4478
|
for (const item of raw.files) {
|
|
4338
4479
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
4339
|
-
const
|
|
4340
|
-
if (
|
|
4480
|
+
const path34 = normalizeCoveragePath(item.path);
|
|
4481
|
+
if (path34 === null) continue;
|
|
4341
4482
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
4342
4483
|
files.push(
|
|
4343
|
-
fileEntry(
|
|
4484
|
+
fileEntry(path34, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
4344
4485
|
);
|
|
4345
4486
|
}
|
|
4346
4487
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -4405,8 +4546,8 @@ function reduceCoverage(capture, granularity) {
|
|
|
4405
4546
|
|
|
4406
4547
|
// src/internal/collect/source-checksums.ts
|
|
4407
4548
|
var crypto4 = __toESM(require("crypto"));
|
|
4408
|
-
var
|
|
4409
|
-
var
|
|
4549
|
+
var fs14 = __toESM(require("fs"));
|
|
4550
|
+
var path14 = __toESM(require("path"));
|
|
4410
4551
|
var CHECKSUM = /^(sha1|sha256):[0-9a-f]{40,64}$/;
|
|
4411
4552
|
function checksumsOf(raw) {
|
|
4412
4553
|
if (!Array.isArray(raw)) return [];
|
|
@@ -4471,10 +4612,10 @@ var SourceChecksums = class {
|
|
|
4471
4612
|
}
|
|
4472
4613
|
async read(relativePath2) {
|
|
4473
4614
|
try {
|
|
4474
|
-
const root =
|
|
4475
|
-
const file =
|
|
4476
|
-
if (!file.startsWith(root +
|
|
4477
|
-
return contentChecksums(await
|
|
4615
|
+
const root = path14.resolve(this.repoRoot());
|
|
4616
|
+
const file = path14.resolve(root, relativePath2);
|
|
4617
|
+
if (!file.startsWith(root + path14.sep)) return null;
|
|
4618
|
+
return contentChecksums(await fs14.promises.readFile(file));
|
|
4478
4619
|
} catch {
|
|
4479
4620
|
return null;
|
|
4480
4621
|
}
|
|
@@ -4619,13 +4760,13 @@ var ServerCoverageCollector = class {
|
|
|
4619
4760
|
if (parts.length === 0 || this.closer.signal.aborted) {
|
|
4620
4761
|
return Promise.resolve({ files: [], incomplete: truncated || parts.length > 0 });
|
|
4621
4762
|
}
|
|
4622
|
-
return new Promise((
|
|
4763
|
+
return new Promise((resolve11) => {
|
|
4623
4764
|
const entry = {
|
|
4624
4765
|
pending: parts.length,
|
|
4625
4766
|
captures: [],
|
|
4626
4767
|
checksums: /* @__PURE__ */ new Map(),
|
|
4627
4768
|
incomplete: truncated,
|
|
4628
|
-
resolve:
|
|
4769
|
+
resolve: resolve11
|
|
4629
4770
|
};
|
|
4630
4771
|
for (const [origin, ids] of parts) this.enqueue(origin, { entry, ids });
|
|
4631
4772
|
});
|
|
@@ -4951,14 +5092,14 @@ var ServerCoverageCollector = class {
|
|
|
4951
5092
|
// src/public/global-setup.ts
|
|
4952
5093
|
function createGlobalSetup(options, userSetup) {
|
|
4953
5094
|
return async function globalSetupFn(config) {
|
|
4954
|
-
const piwiReporterPath =
|
|
5095
|
+
const piwiReporterPath = path15.resolve(__dirname, "./index.js");
|
|
4955
5096
|
let inlineReporterOptions = {};
|
|
4956
5097
|
if (Array.isArray(config?.reporter)) {
|
|
4957
5098
|
for (const r of config.reporter) {
|
|
4958
5099
|
if (!Array.isArray(r) || typeof r[0] !== "string") continue;
|
|
4959
5100
|
const isPiwi = r[0].toLowerCase().includes("piwi") || (() => {
|
|
4960
5101
|
try {
|
|
4961
|
-
return
|
|
5102
|
+
return path15.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
4962
5103
|
} catch {
|
|
4963
5104
|
return false;
|
|
4964
5105
|
}
|
|
@@ -4969,7 +5110,10 @@ function createGlobalSetup(options, userSetup) {
|
|
|
4969
5110
|
}
|
|
4970
5111
|
}
|
|
4971
5112
|
}
|
|
4972
|
-
const opts = resolveOptions(
|
|
5113
|
+
const opts = resolveOptions(
|
|
5114
|
+
{ ...inlineReporterOptions, ...options },
|
|
5115
|
+
configDirFromConfig(config)
|
|
5116
|
+
);
|
|
4973
5117
|
const logger = new Logger(opts.verbose ?? false);
|
|
4974
5118
|
if (isUiMode()) {
|
|
4975
5119
|
logger.debug("UI mode detected \u2014 skipping run registration.");
|
|
@@ -4994,7 +5138,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
4994
5138
|
if (!Array.isArray(r) || typeof r[0] !== "string") return false;
|
|
4995
5139
|
if (r[0].toLowerCase().includes("piwi")) return true;
|
|
4996
5140
|
try {
|
|
4997
|
-
return
|
|
5141
|
+
return path15.resolve(require.resolve(r[0])) === piwiReporterPath;
|
|
4998
5142
|
} catch {
|
|
4999
5143
|
return false;
|
|
5000
5144
|
}
|
|
@@ -5024,7 +5168,7 @@ function createGlobalSetup(options, userSetup) {
|
|
|
5024
5168
|
auth
|
|
5025
5169
|
);
|
|
5026
5170
|
if (response?.runId && response?.setupToken) {
|
|
5027
|
-
|
|
5171
|
+
fs15.writeFileSync(
|
|
5028
5172
|
getSetupFilePath(opts.projectName),
|
|
5029
5173
|
JSON.stringify({
|
|
5030
5174
|
runId: response.runId,
|
|
@@ -5078,11 +5222,11 @@ function createGlobalSetup(options, userSetup) {
|
|
|
5078
5222
|
}
|
|
5079
5223
|
|
|
5080
5224
|
// src/public/config-wrapper.ts
|
|
5081
|
-
var
|
|
5082
|
-
var
|
|
5225
|
+
var path16 = __toESM(require("path"));
|
|
5226
|
+
var fs18 = __toESM(require("fs"));
|
|
5083
5227
|
|
|
5084
5228
|
// src/internal/probe/mode.ts
|
|
5085
|
-
var
|
|
5229
|
+
var fs16 = __toESM(require("fs"));
|
|
5086
5230
|
|
|
5087
5231
|
// src/internal/probe/plan.ts
|
|
5088
5232
|
function probeRunMetadata(base = {}) {
|
|
@@ -5094,8 +5238,8 @@ function parseRouteKey(routeKey2) {
|
|
|
5094
5238
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
5095
5239
|
}
|
|
5096
5240
|
function patternToRegex(pattern) {
|
|
5097
|
-
const
|
|
5098
|
-
const escaped =
|
|
5241
|
+
const path34 = pattern.split("?")[0] ?? pattern;
|
|
5242
|
+
const escaped = path34.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
5099
5243
|
return new RegExp(`^${escaped}$`);
|
|
5100
5244
|
}
|
|
5101
5245
|
function suitePathEqual(a, b) {
|
|
@@ -5118,13 +5262,13 @@ function matchProbeItem(plan, test) {
|
|
|
5118
5262
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
5119
5263
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
5120
5264
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
5121
|
-
let
|
|
5265
|
+
let path34;
|
|
5122
5266
|
try {
|
|
5123
|
-
|
|
5267
|
+
path34 = new URL(url).pathname;
|
|
5124
5268
|
} catch {
|
|
5125
|
-
|
|
5269
|
+
path34 = url.split("?")[0] ?? url;
|
|
5126
5270
|
}
|
|
5127
|
-
return patternToRegex(pattern).test(
|
|
5271
|
+
return patternToRegex(pattern).test(path34);
|
|
5128
5272
|
}
|
|
5129
5273
|
function createProbeState() {
|
|
5130
5274
|
return { navigated: false, matches: 0 };
|
|
@@ -5146,10 +5290,10 @@ function isProbeMode() {
|
|
|
5146
5290
|
function loadPlan() {
|
|
5147
5291
|
if (planCache !== void 0) return planCache;
|
|
5148
5292
|
planCache = null;
|
|
5149
|
-
const
|
|
5150
|
-
if (
|
|
5293
|
+
const path34 = process.env[PIWI_PROBE_ENV.plan];
|
|
5294
|
+
if (path34) {
|
|
5151
5295
|
try {
|
|
5152
|
-
planCache = JSON.parse(
|
|
5296
|
+
planCache = JSON.parse(fs16.readFileSync(path34, "utf8"));
|
|
5153
5297
|
} catch {
|
|
5154
5298
|
planCache = null;
|
|
5155
5299
|
}
|
|
@@ -5173,17 +5317,17 @@ function outcomeFromStatus(status, applied) {
|
|
|
5173
5317
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
5174
5318
|
}
|
|
5175
5319
|
function recordProbeOutcome(line) {
|
|
5176
|
-
const
|
|
5177
|
-
if (!
|
|
5320
|
+
const path34 = process.env[PIWI_PROBE_ENV.results];
|
|
5321
|
+
if (!path34) return;
|
|
5178
5322
|
try {
|
|
5179
|
-
|
|
5323
|
+
fs16.appendFileSync(path34, `${JSON.stringify(line)}
|
|
5180
5324
|
`);
|
|
5181
5325
|
} catch {
|
|
5182
5326
|
}
|
|
5183
5327
|
}
|
|
5184
5328
|
|
|
5185
5329
|
// src/internal/flake/mode.ts
|
|
5186
|
-
var
|
|
5330
|
+
var fs17 = __toESM(require("fs"));
|
|
5187
5331
|
|
|
5188
5332
|
// ../core/src/error-signature.ts
|
|
5189
5333
|
var UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
|
|
@@ -5559,7 +5703,7 @@ function originOf2(url) {
|
|
|
5559
5703
|
return null;
|
|
5560
5704
|
}
|
|
5561
5705
|
}
|
|
5562
|
-
var sleep = (ms) => new Promise((
|
|
5706
|
+
var sleep = (ms) => new Promise((resolve11) => setTimeout(resolve11, Math.max(0, ms)));
|
|
5563
5707
|
async function performRouteAction(route, action, startedAt) {
|
|
5564
5708
|
switch (action.kind) {
|
|
5565
5709
|
case "abort":
|
|
@@ -5683,7 +5827,7 @@ function loadFlakePlan() {
|
|
|
5683
5827
|
if (planCache2 && planCache2.file === file) return planCache2.plan;
|
|
5684
5828
|
let text;
|
|
5685
5829
|
try {
|
|
5686
|
-
text =
|
|
5830
|
+
text = fs17.readFileSync(file, "utf8");
|
|
5687
5831
|
} catch (error) {
|
|
5688
5832
|
throw new Error(`[Piwi Dashboard] Cannot read the flake plan ${file}: ${errorMessage(error)}`);
|
|
5689
5833
|
}
|
|
@@ -5844,7 +5988,7 @@ function recordFlakeResult(line) {
|
|
|
5844
5988
|
const file = process.env[PIWI_FLAKE_ENV.results]?.trim();
|
|
5845
5989
|
if (!file) return;
|
|
5846
5990
|
try {
|
|
5847
|
-
|
|
5991
|
+
fs17.appendFileSync(file, `${JSON.stringify(line)}
|
|
5848
5992
|
`);
|
|
5849
5993
|
} catch {
|
|
5850
5994
|
}
|
|
@@ -5915,11 +6059,11 @@ function injectReporter(reporter, piwiOptions) {
|
|
|
5915
6059
|
}
|
|
5916
6060
|
function resolveSetupModule() {
|
|
5917
6061
|
const candidates = [
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
6062
|
+
path16.join(__dirname, "global-setup-module.js"),
|
|
6063
|
+
path16.join(__dirname, "..", "global-setup-module.js"),
|
|
6064
|
+
path16.join(__dirname, "..", "global-setup-module.ts")
|
|
5921
6065
|
];
|
|
5922
|
-
return candidates.find((candidate) =>
|
|
6066
|
+
return candidates.find((candidate) => fs18.existsSync(candidate)) ?? candidates[0];
|
|
5923
6067
|
}
|
|
5924
6068
|
function wrapConfig(config, piwiOptions) {
|
|
5925
6069
|
if (piwiOptions) applyOptionsToEnv(piwiOptions);
|
|
@@ -6315,8 +6459,8 @@ function buildErrorText(result, base = process.cwd()) {
|
|
|
6315
6459
|
var import_node_crypto2 = require("crypto");
|
|
6316
6460
|
|
|
6317
6461
|
// src/internal/support/ci-output.ts
|
|
6318
|
-
var
|
|
6319
|
-
var
|
|
6462
|
+
var fs19 = __toESM(require("fs"));
|
|
6463
|
+
var path17 = __toESM(require("path"));
|
|
6320
6464
|
var SUMMARY_MAX_FAILURES = 20;
|
|
6321
6465
|
function emitRunOutputs(output, logger, outputFile, env = process.env) {
|
|
6322
6466
|
logger.info(`View run: ${output.runUrl}`);
|
|
@@ -6326,9 +6470,9 @@ function emitRunOutputs(output, logger, outputFile, env = process.env) {
|
|
|
6326
6470
|
}
|
|
6327
6471
|
function writeOutputFile(file, output, logger) {
|
|
6328
6472
|
try {
|
|
6329
|
-
const dir =
|
|
6330
|
-
if (dir && dir !== ".")
|
|
6331
|
-
|
|
6473
|
+
const dir = path17.dirname(file);
|
|
6474
|
+
if (dir && dir !== ".") fs19.mkdirSync(dir, { recursive: true });
|
|
6475
|
+
fs19.writeFileSync(
|
|
6332
6476
|
file,
|
|
6333
6477
|
JSON.stringify(
|
|
6334
6478
|
{
|
|
@@ -6408,7 +6552,7 @@ function emitGitLabDotenv(output, env, logger) {
|
|
|
6408
6552
|
if (output.projectId != null) lines.push(`PIWI_PROJECT_ID=${output.projectId}`);
|
|
6409
6553
|
if (output.ciBuildUrl) lines.push(`PIWI_CI_BUILD_URL=${output.ciBuildUrl}`);
|
|
6410
6554
|
try {
|
|
6411
|
-
|
|
6555
|
+
fs19.writeFileSync(file, lines.join("\n") + "\n");
|
|
6412
6556
|
logger.info(`Wrote GitLab dotenv report to ${file} (declare it as artifacts:reports:dotenv)`);
|
|
6413
6557
|
} catch (error) {
|
|
6414
6558
|
logger.warn(`Failed to write GitLab dotenv file '${file}': ${errorMessage(error)}`);
|
|
@@ -6425,7 +6569,7 @@ function emitResourceSummary(lines, logger, env = process.env) {
|
|
|
6425
6569
|
}
|
|
6426
6570
|
function appendFileLines(file, lines, logger, label) {
|
|
6427
6571
|
try {
|
|
6428
|
-
|
|
6572
|
+
fs19.appendFileSync(file, lines.join("\n") + "\n");
|
|
6429
6573
|
} catch (error) {
|
|
6430
6574
|
logger.warn(`Failed to write GitHub Actions ${label}: ${errorMessage(error)}`);
|
|
6431
6575
|
}
|
|
@@ -6438,8 +6582,8 @@ function ciBuildUrlFromMetadata(metadata) {
|
|
|
6438
6582
|
|
|
6439
6583
|
// src/internal/submit/run-submitter.ts
|
|
6440
6584
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
6441
|
-
function markSubmitFallback(run,
|
|
6442
|
-
run.metadata.ingestHealth = { submitFallback: { path:
|
|
6585
|
+
function markSubmitFallback(run, path34, reason) {
|
|
6586
|
+
run.metadata.ingestHealth = { submitFallback: { path: path34, reason } };
|
|
6443
6587
|
}
|
|
6444
6588
|
var RunSubmitter = class {
|
|
6445
6589
|
/**
|
|
@@ -6700,7 +6844,7 @@ var RunSubmitter = class {
|
|
|
6700
6844
|
*/
|
|
6701
6845
|
stopForMissingAuth(run, payload) {
|
|
6702
6846
|
this.logger.error(
|
|
6703
|
-
`Authentication is required by ${run.options.serverUrl ?? "the dashboard"} but no credentials were configured. Create an API key (Settings \u2192 Users on the dashboard) and set the reporter's \`apiKey\` option or the
|
|
6847
|
+
`Authentication is required by ${run.options.serverUrl ?? "the dashboard"} but no credentials were configured. Create an API key (Settings \u2192 Users on the dashboard) and set the reporter's \`apiKey\` option, or PIWI_API_KEY in the environment or in the .env file next to the Playwright config.`
|
|
6704
6848
|
);
|
|
6705
6849
|
this.logRecoverySaved();
|
|
6706
6850
|
this.saveRecovery(payload);
|
|
@@ -6715,13 +6859,13 @@ var RunSubmitter = class {
|
|
|
6715
6859
|
};
|
|
6716
6860
|
|
|
6717
6861
|
// src/internal/capture/resource-ledger.ts
|
|
6718
|
-
var
|
|
6719
|
-
var
|
|
6862
|
+
var fs21 = __toESM(require("fs"));
|
|
6863
|
+
var path20 = __toESM(require("path"));
|
|
6720
6864
|
var import_node_module2 = require("module");
|
|
6721
6865
|
|
|
6722
6866
|
// src/internal/capture/quiet-capture.ts
|
|
6723
6867
|
var import_node_module = require("module");
|
|
6724
|
-
var
|
|
6868
|
+
var path18 = __toESM(require("path"));
|
|
6725
6869
|
var BOXED_PREFIX_EXPORTS = [
|
|
6726
6870
|
[
|
|
6727
6871
|
"playwright-core/lib/coreBundle",
|
|
@@ -6758,7 +6902,7 @@ function resolveBoxing() {
|
|
|
6758
6902
|
if (typeof set !== "function") continue;
|
|
6759
6903
|
return {
|
|
6760
6904
|
set,
|
|
6761
|
-
prefixes: [
|
|
6905
|
+
prefixes: [path18.dirname(playwrightPackageJson), path18.join(root, "dist") + path18.sep]
|
|
6762
6906
|
};
|
|
6763
6907
|
}
|
|
6764
6908
|
} catch {
|
|
@@ -6790,12 +6934,12 @@ function internalCall(owner, run) {
|
|
|
6790
6934
|
var import_node_perf_hooks = require("perf_hooks");
|
|
6791
6935
|
|
|
6792
6936
|
// src/internal/support/system-readers.ts
|
|
6793
|
-
var
|
|
6794
|
-
var
|
|
6937
|
+
var fs20 = __toESM(require("fs"));
|
|
6938
|
+
var path19 = __toESM(require("path"));
|
|
6795
6939
|
var CLK_TCK = 100;
|
|
6796
6940
|
function readText(file) {
|
|
6797
6941
|
try {
|
|
6798
|
-
return
|
|
6942
|
+
return fs20.readFileSync(file, "utf8");
|
|
6799
6943
|
} catch {
|
|
6800
6944
|
return null;
|
|
6801
6945
|
}
|
|
@@ -6929,45 +7073,45 @@ var ProcFs = class {
|
|
|
6929
7073
|
}
|
|
6930
7074
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
6931
7075
|
available() {
|
|
6932
|
-
return
|
|
7076
|
+
return fs20.existsSync(path19.join(this.root, "stat"));
|
|
6933
7077
|
}
|
|
6934
7078
|
/** Every process, keyed by pid. */
|
|
6935
7079
|
table() {
|
|
6936
7080
|
const out = /* @__PURE__ */ new Map();
|
|
6937
7081
|
let names;
|
|
6938
7082
|
try {
|
|
6939
|
-
names =
|
|
7083
|
+
names = fs20.readdirSync(this.root);
|
|
6940
7084
|
} catch {
|
|
6941
7085
|
return out;
|
|
6942
7086
|
}
|
|
6943
7087
|
for (const name of names) {
|
|
6944
7088
|
if (!/^\d+$/.test(name)) continue;
|
|
6945
|
-
const text = readText(
|
|
7089
|
+
const text = readText(path19.join(this.root, name, "stat"));
|
|
6946
7090
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
6947
7091
|
if (stat) out.set(stat.pid, stat);
|
|
6948
7092
|
}
|
|
6949
7093
|
return out;
|
|
6950
7094
|
}
|
|
6951
7095
|
stat(pid) {
|
|
6952
|
-
const text = readText(
|
|
7096
|
+
const text = readText(path19.join(this.root, String(pid), "stat"));
|
|
6953
7097
|
return text ? parseProcStat(pid, text) : null;
|
|
6954
7098
|
}
|
|
6955
7099
|
runWaitMs(pid) {
|
|
6956
|
-
const text = readText(
|
|
7100
|
+
const text = readText(path19.join(this.root, String(pid), "schedstat"));
|
|
6957
7101
|
return text ? parseSchedstat(text) : null;
|
|
6958
7102
|
}
|
|
6959
7103
|
cmdline(pid) {
|
|
6960
|
-
return (readText(
|
|
7104
|
+
return (readText(path19.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
6961
7105
|
}
|
|
6962
7106
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
6963
7107
|
statusKb(pid, key) {
|
|
6964
|
-
const text = readText(
|
|
7108
|
+
const text = readText(path19.join(this.root, String(pid), "status"));
|
|
6965
7109
|
return text ? parseKbField(text, key) : null;
|
|
6966
7110
|
}
|
|
6967
7111
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
6968
7112
|
async pssKb(pid) {
|
|
6969
7113
|
try {
|
|
6970
|
-
return parseKbField(await
|
|
7114
|
+
return parseKbField(await fs20.promises.readFile(path19.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
6971
7115
|
} catch {
|
|
6972
7116
|
return null;
|
|
6973
7117
|
}
|
|
@@ -6975,29 +7119,29 @@ var ProcFs = class {
|
|
|
6975
7119
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
6976
7120
|
resetPeakRss(pid) {
|
|
6977
7121
|
try {
|
|
6978
|
-
|
|
7122
|
+
fs20.writeFileSync(path19.join(this.root, String(pid), "clear_refs"), "5");
|
|
6979
7123
|
return true;
|
|
6980
7124
|
} catch {
|
|
6981
7125
|
return false;
|
|
6982
7126
|
}
|
|
6983
7127
|
}
|
|
6984
7128
|
cpuTicks() {
|
|
6985
|
-
const text = readText(
|
|
7129
|
+
const text = readText(path19.join(this.root, "stat"));
|
|
6986
7130
|
return text ? parseCpuTicks(text) : null;
|
|
6987
7131
|
}
|
|
6988
7132
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
6989
7133
|
pressureUs(resource) {
|
|
6990
|
-
const text = readText(
|
|
7134
|
+
const text = readText(path19.join(this.root, "pressure", resource));
|
|
6991
7135
|
return text ? parsePsiTotal(text) : null;
|
|
6992
7136
|
}
|
|
6993
7137
|
memAvailableKb() {
|
|
6994
|
-
const text = readText(
|
|
7138
|
+
const text = readText(path19.join(this.root, "meminfo"));
|
|
6995
7139
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
6996
7140
|
}
|
|
6997
7141
|
/** Open file descriptors of a process. */
|
|
6998
7142
|
fdCount(pid) {
|
|
6999
7143
|
try {
|
|
7000
|
-
return
|
|
7144
|
+
return fs20.readdirSync(path19.join(this.root, String(pid), "fd")).length;
|
|
7001
7145
|
} catch {
|
|
7002
7146
|
return null;
|
|
7003
7147
|
}
|
|
@@ -7018,7 +7162,7 @@ var CgroupFs = class {
|
|
|
7018
7162
|
}
|
|
7019
7163
|
/** The directory of a controller (v1) or of the unified hierarchy (v2), when it exists. */
|
|
7020
7164
|
dirs() {
|
|
7021
|
-
const text = readText(
|
|
7165
|
+
const text = readText(path19.join(this.procRoot, String(this.pid), "cgroup"));
|
|
7022
7166
|
if (!text) return null;
|
|
7023
7167
|
const v1 = /* @__PURE__ */ new Map();
|
|
7024
7168
|
let v2 = null;
|
|
@@ -7029,21 +7173,21 @@ var CgroupFs = class {
|
|
|
7029
7173
|
else for (const controller of match[1].split(",")) v1.set(controller, match[2]);
|
|
7030
7174
|
}
|
|
7031
7175
|
const pick = (mount, sub) => {
|
|
7032
|
-
const nested =
|
|
7033
|
-
if (
|
|
7034
|
-
return
|
|
7176
|
+
const nested = path19.join(mount, sub);
|
|
7177
|
+
if (fs20.existsSync(nested)) return nested;
|
|
7178
|
+
return fs20.existsSync(mount) ? mount : null;
|
|
7035
7179
|
};
|
|
7036
7180
|
if (v1.has("memory") || v1.has("cpu")) {
|
|
7037
7181
|
const memoryPath = v1.get("memory");
|
|
7038
7182
|
const cpuPath = v1.get("cpu");
|
|
7039
|
-
const cpuMount = ["cpu,cpuacct", "cpu"].map((name) =>
|
|
7183
|
+
const cpuMount = ["cpu,cpuacct", "cpu"].map((name) => path19.join(this.cgroupRoot, name)).find((dir) => fs20.existsSync(dir));
|
|
7040
7184
|
return {
|
|
7041
7185
|
version: 1,
|
|
7042
|
-
memory: memoryPath !== void 0 ? pick(
|
|
7186
|
+
memory: memoryPath !== void 0 ? pick(path19.join(this.cgroupRoot, "memory"), memoryPath) : null,
|
|
7043
7187
|
cpu: cpuPath !== void 0 && cpuMount ? pick(cpuMount, cpuPath) : null
|
|
7044
7188
|
};
|
|
7045
7189
|
}
|
|
7046
|
-
if (v2 !== null &&
|
|
7190
|
+
if (v2 !== null && fs20.existsSync(path19.join(this.cgroupRoot, "cgroup.controllers"))) {
|
|
7047
7191
|
const dir = pick(this.cgroupRoot, v2);
|
|
7048
7192
|
return { version: 2, memory: dir, cpu: dir };
|
|
7049
7193
|
}
|
|
@@ -7052,7 +7196,7 @@ var CgroupFs = class {
|
|
|
7052
7196
|
read() {
|
|
7053
7197
|
const dirs = this.dirs();
|
|
7054
7198
|
if (!dirs) return null;
|
|
7055
|
-
const file = (dir, name) => dir ? readText(
|
|
7199
|
+
const file = (dir, name) => dir ? readText(path19.join(dir, name)) : null;
|
|
7056
7200
|
if (dirs.version === 2) {
|
|
7057
7201
|
const max = file(dirs.memory, "memory.max");
|
|
7058
7202
|
const cpuMax = file(dirs.cpu, "cpu.max")?.trim().split(/\s+/);
|
|
@@ -7238,10 +7382,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
|
7238
7382
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
7239
7383
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
7240
7384
|
function relativePath(file) {
|
|
7241
|
-
return
|
|
7385
|
+
return path20.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path20.sep).join("/");
|
|
7242
7386
|
}
|
|
7243
7387
|
function isLibraryFile(file) {
|
|
7244
|
-
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${
|
|
7388
|
+
return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path20.sep}node_modules${path20.sep}`) || file.includes("/node_modules/") || file === __filename;
|
|
7245
7389
|
}
|
|
7246
7390
|
function userSite(stack) {
|
|
7247
7391
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
@@ -7289,7 +7433,7 @@ function playwrightTestInfoSource() {
|
|
|
7289
7433
|
try {
|
|
7290
7434
|
const packageJson = resolvePlaywrightPackageJson();
|
|
7291
7435
|
if (!packageJson) return () => null;
|
|
7292
|
-
const globals = (0, import_node_module2.createRequire)(packageJson)(
|
|
7436
|
+
const globals = (0, import_node_module2.createRequire)(packageJson)(path20.join(path20.dirname(packageJson), "lib", "globals.js"));
|
|
7293
7437
|
const read = globals.currentTestInfo;
|
|
7294
7438
|
if (typeof read !== "function") return () => null;
|
|
7295
7439
|
return () => {
|
|
@@ -7318,15 +7462,15 @@ function countHandles() {
|
|
|
7318
7462
|
async function settledHandles(start) {
|
|
7319
7463
|
const end = countHandles();
|
|
7320
7464
|
if (!Object.entries(end).some(([type, n]) => n > (start[type] ?? 0))) return end;
|
|
7321
|
-
await new Promise((
|
|
7465
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
7322
7466
|
return countHandles();
|
|
7323
7467
|
}
|
|
7324
7468
|
function settleWithin(promise, ms) {
|
|
7325
7469
|
let timer;
|
|
7326
7470
|
return Promise.race([
|
|
7327
7471
|
promise.catch(() => void 0),
|
|
7328
|
-
new Promise((
|
|
7329
|
-
timer = setTimeout(
|
|
7472
|
+
new Promise((resolve11) => {
|
|
7473
|
+
timer = setTimeout(resolve11, ms);
|
|
7330
7474
|
})
|
|
7331
7475
|
]).finally(() => clearTimeout(timer));
|
|
7332
7476
|
}
|
|
@@ -7455,7 +7599,7 @@ var ResourceLedger = class {
|
|
|
7455
7599
|
const census = this.census(null);
|
|
7456
7600
|
if (this.resultsFile) {
|
|
7457
7601
|
try {
|
|
7458
|
-
|
|
7602
|
+
fs21.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
|
|
7459
7603
|
`);
|
|
7460
7604
|
} catch {
|
|
7461
7605
|
}
|
|
@@ -7778,7 +7922,7 @@ async function recordResourceCensus(testInfo) {
|
|
|
7778
7922
|
}
|
|
7779
7923
|
|
|
7780
7924
|
// src/internal/collect/resource-verdicts.ts
|
|
7781
|
-
var
|
|
7925
|
+
var path21 = __toESM(require("path"));
|
|
7782
7926
|
var OPEN_STEP_TITLES = {
|
|
7783
7927
|
"Create context": "context",
|
|
7784
7928
|
"Launch persistent context": "context",
|
|
@@ -7810,7 +7954,7 @@ function userFixturesOf(steps) {
|
|
|
7810
7954
|
const walk = (list) => {
|
|
7811
7955
|
for (const step of list) {
|
|
7812
7956
|
const file = step.location?.file;
|
|
7813
|
-
if (step.category === "fixture" && file && !file.includes(`${
|
|
7957
|
+
if (step.category === "fixture" && file && !file.includes(`${path21.sep}node_modules${path21.sep}`)) {
|
|
7814
7958
|
const name = step.title.replace(/^(?:Fixture\s+|fixture:\s*)/, "").replace(/^"(.*)"$/, "$1");
|
|
7815
7959
|
if (!name.startsWith("piwi")) out.push(`${name} (${relativeFile(file)}:${step.location.line})`);
|
|
7816
7960
|
}
|
|
@@ -7843,7 +7987,7 @@ function tallyLifecycleSteps(file, steps) {
|
|
|
7843
7987
|
return tally;
|
|
7844
7988
|
}
|
|
7845
7989
|
function relativeFile(file) {
|
|
7846
|
-
return
|
|
7990
|
+
return path21.relative(process.cwd(), file).split(path21.sep).join("/");
|
|
7847
7991
|
}
|
|
7848
7992
|
function buildResourceReport(input) {
|
|
7849
7993
|
const findings = [];
|
|
@@ -8201,9 +8345,9 @@ function formatResourceSummary(report, leakCheck, maxLines = 10) {
|
|
|
8201
8345
|
}
|
|
8202
8346
|
|
|
8203
8347
|
// src/internal/collect/process-sampler.ts
|
|
8204
|
-
var
|
|
8348
|
+
var fs22 = __toESM(require("fs"));
|
|
8205
8349
|
var os8 = __toESM(require("os"));
|
|
8206
|
-
var
|
|
8350
|
+
var path22 = __toESM(require("path"));
|
|
8207
8351
|
var import_node_child_process2 = require("child_process");
|
|
8208
8352
|
var WALK_FILE_CAP = 1e4;
|
|
8209
8353
|
var SERIES_CAP = 3600;
|
|
@@ -8243,17 +8387,17 @@ function nodeCpuTicks() {
|
|
|
8243
8387
|
return { busy, idle, iowait: 0, steal: 0, total: busy + idle };
|
|
8244
8388
|
}
|
|
8245
8389
|
function defaultPs() {
|
|
8246
|
-
return new Promise((
|
|
8390
|
+
return new Promise((resolve11, reject) => {
|
|
8247
8391
|
(0, import_node_child_process2.execFile)(
|
|
8248
8392
|
"ps",
|
|
8249
8393
|
["-A", "-o", "pid=,ppid=,rss=,time=,command="],
|
|
8250
8394
|
{ timeout: 2e3, maxBuffer: 8 * 1024 * 1024 },
|
|
8251
|
-
(error, stdout) => error ? reject(error) :
|
|
8395
|
+
(error, stdout) => error ? reject(error) : resolve11(stdout)
|
|
8252
8396
|
);
|
|
8253
8397
|
});
|
|
8254
8398
|
}
|
|
8255
8399
|
function defaultWindowsQuery() {
|
|
8256
|
-
return new Promise((
|
|
8400
|
+
return new Promise((resolve11, reject) => {
|
|
8257
8401
|
(0, import_node_child_process2.execFile)(
|
|
8258
8402
|
"powershell.exe",
|
|
8259
8403
|
[
|
|
@@ -8263,18 +8407,18 @@ function defaultWindowsQuery() {
|
|
|
8263
8407
|
"[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $PID -and $_.ParentProcessId -ne $PID } | Select-Object ProcessId,ParentProcessId,WorkingSetSize,UserModeTime,KernelModeTime,Name,CommandLine | ConvertTo-Json -Compress"
|
|
8264
8408
|
],
|
|
8265
8409
|
{ timeout: 1e4, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
|
|
8266
|
-
(error, stdout) => error ? reject(error) :
|
|
8410
|
+
(error, stdout) => error ? reject(error) : resolve11(stdout)
|
|
8267
8411
|
);
|
|
8268
8412
|
});
|
|
8269
8413
|
}
|
|
8270
8414
|
function freeBytes(dir) {
|
|
8271
|
-
let current =
|
|
8415
|
+
let current = path22.resolve(dir);
|
|
8272
8416
|
for (; ; ) {
|
|
8273
8417
|
try {
|
|
8274
|
-
const stats =
|
|
8418
|
+
const stats = fs22.statfsSync(current);
|
|
8275
8419
|
return stats.bavail * stats.bsize;
|
|
8276
8420
|
} catch {
|
|
8277
|
-
const parent =
|
|
8421
|
+
const parent = path22.dirname(current);
|
|
8278
8422
|
if (parent === current) return null;
|
|
8279
8423
|
current = parent;
|
|
8280
8424
|
}
|
|
@@ -8284,18 +8428,18 @@ async function dirBytes(dir, budget) {
|
|
|
8284
8428
|
let bytes = 0;
|
|
8285
8429
|
let entries;
|
|
8286
8430
|
try {
|
|
8287
|
-
entries = await
|
|
8431
|
+
entries = await fs22.promises.readdir(dir, { withFileTypes: true });
|
|
8288
8432
|
} catch {
|
|
8289
8433
|
return 0;
|
|
8290
8434
|
}
|
|
8291
8435
|
for (const entry of entries) {
|
|
8292
8436
|
if (budget.files <= 0) return bytes;
|
|
8293
|
-
const full =
|
|
8437
|
+
const full = path22.join(dir, entry.name);
|
|
8294
8438
|
if (entry.isDirectory()) bytes += await dirBytes(full, budget);
|
|
8295
8439
|
else if (entry.isFile()) {
|
|
8296
8440
|
budget.files--;
|
|
8297
8441
|
try {
|
|
8298
|
-
bytes += (await
|
|
8442
|
+
bytes += (await fs22.promises.lstat(full)).size;
|
|
8299
8443
|
} catch {
|
|
8300
8444
|
}
|
|
8301
8445
|
}
|
|
@@ -8335,7 +8479,7 @@ var RunSampler = class {
|
|
|
8335
8479
|
this.platform = options.platform ?? process.platform;
|
|
8336
8480
|
this.proc = new ProcFs(options.procRoot);
|
|
8337
8481
|
this.cgroup = new CgroupFs(options.procRoot, options.cgroupRoot);
|
|
8338
|
-
this.outputDirs = [...new Set((options.outputDirs ?? []).map((dir) =>
|
|
8482
|
+
this.outputDirs = [...new Set((options.outputDirs ?? []).map((dir) => path22.resolve(dir)))];
|
|
8339
8483
|
this.tmpDir = options.tmpDir ?? os8.tmpdir();
|
|
8340
8484
|
this.intervalMs = options.intervalMs ?? 1e3;
|
|
8341
8485
|
this.heavyEvery = Math.max(1, options.heavyEvery ?? 5);
|
|
@@ -8537,7 +8681,7 @@ var RunSampler = class {
|
|
|
8537
8681
|
} catch {
|
|
8538
8682
|
return;
|
|
8539
8683
|
}
|
|
8540
|
-
this.noteTree(list, (proc) => roleOf(
|
|
8684
|
+
this.noteTree(list, (proc) => roleOf(path22.basename(proc.command.split(" ")[0] ?? ""), proc.command), at);
|
|
8541
8685
|
}
|
|
8542
8686
|
async sampleWindowsTree(at) {
|
|
8543
8687
|
let list;
|
|
@@ -8593,7 +8737,7 @@ var RunSampler = class {
|
|
|
8593
8737
|
let bytes = 0;
|
|
8594
8738
|
for (const dir of this.outputDirs) bytes += await dirBytes(dir, budget);
|
|
8595
8739
|
for (const name of await this.playwrightTempEntries((mtimeMs) => mtimeMs >= this.startedAt - 1e3)) {
|
|
8596
|
-
bytes += await dirBytes(
|
|
8740
|
+
bytes += await dirBytes(path22.join(this.tmpDir, name), budget);
|
|
8597
8741
|
}
|
|
8598
8742
|
if (budget.files <= 0) this.peakInUseIsLowerBound = true;
|
|
8599
8743
|
this.peakInUse = Math.max(this.peakInUse ?? 0, bytes);
|
|
@@ -8602,7 +8746,7 @@ var RunSampler = class {
|
|
|
8602
8746
|
async playwrightTempEntries(keep) {
|
|
8603
8747
|
let names;
|
|
8604
8748
|
try {
|
|
8605
|
-
names = await
|
|
8749
|
+
names = await fs22.promises.readdir(this.tmpDir);
|
|
8606
8750
|
} catch {
|
|
8607
8751
|
return [];
|
|
8608
8752
|
}
|
|
@@ -8610,7 +8754,7 @@ var RunSampler = class {
|
|
|
8610
8754
|
for (const name of names) {
|
|
8611
8755
|
if (!PLAYWRIGHT_TEMP.test(name)) continue;
|
|
8612
8756
|
try {
|
|
8613
|
-
if (keep((await
|
|
8757
|
+
if (keep((await fs22.promises.stat(path22.join(this.tmpDir, name))).mtimeMs)) out.push(name);
|
|
8614
8758
|
} catch {
|
|
8615
8759
|
}
|
|
8616
8760
|
}
|
|
@@ -8621,7 +8765,7 @@ var RunSampler = class {
|
|
|
8621
8765
|
const budget = { files: WALK_FILE_CAP };
|
|
8622
8766
|
let bytes = 0;
|
|
8623
8767
|
for (const name of await this.playwrightTempEntries((mtimeMs) => mtimeMs < this.startedAt - 1e3)) {
|
|
8624
|
-
bytes += await dirBytes(
|
|
8768
|
+
bytes += await dirBytes(path22.join(this.tmpDir, name), budget);
|
|
8625
8769
|
}
|
|
8626
8770
|
return bytes;
|
|
8627
8771
|
} catch {
|
|
@@ -9541,6 +9685,7 @@ function caseLocateUrl(serverUrl, runId, test) {
|
|
|
9541
9685
|
`retry=${test.retry}`
|
|
9542
9686
|
];
|
|
9543
9687
|
if (test.browser) params.push(`browser=${encodeURIComponent(test.browser)}`);
|
|
9688
|
+
if (test.repeatEachIndex) params.push(`repeat=${test.repeatEachIndex}`);
|
|
9544
9689
|
return `${serverUrl.replace(/\/+$/, "")}/test-runs/${runId}/locate?${params.join("&")}`;
|
|
9545
9690
|
}
|
|
9546
9691
|
function formatFailureLine(link) {
|
|
@@ -9594,7 +9739,7 @@ function browserFunctionsInWorker() {
|
|
|
9594
9739
|
function browserFunctionCoverage(files) {
|
|
9595
9740
|
return sanitizeCoverage({
|
|
9596
9741
|
v: 1,
|
|
9597
|
-
files: [...files].map(([
|
|
9742
|
+
files: [...files].map(([path34, functions]) => ({ path: path34, runtime: "browser", functions }))
|
|
9598
9743
|
});
|
|
9599
9744
|
}
|
|
9600
9745
|
function newKnownFunctions(known, sent) {
|
|
@@ -9607,19 +9752,19 @@ function newKnownFunctions(known, sent) {
|
|
|
9607
9752
|
budget = MAX_COVERAGE_RANGES;
|
|
9608
9753
|
};
|
|
9609
9754
|
for (const [raw, ranges] of known) {
|
|
9610
|
-
const
|
|
9611
|
-
if (
|
|
9612
|
-
let named = sent.get(
|
|
9755
|
+
const path34 = normalizeCoveragePath(raw);
|
|
9756
|
+
if (path34 === null) continue;
|
|
9757
|
+
let named = sent.get(path34);
|
|
9613
9758
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
9614
9759
|
if (fresh.length === 0) continue;
|
|
9615
|
-
if (!named) sent.set(
|
|
9760
|
+
if (!named) sent.set(path34, named = /* @__PURE__ */ new Set());
|
|
9616
9761
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
9617
9762
|
while (fresh.length > 0) {
|
|
9618
9763
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
9619
9764
|
const part = fresh.slice(0, budget);
|
|
9620
9765
|
fresh = fresh.slice(part.length);
|
|
9621
9766
|
budget -= part.length;
|
|
9622
|
-
files.push({ path:
|
|
9767
|
+
files.push({ path: path34, allFunctions: part });
|
|
9623
9768
|
}
|
|
9624
9769
|
}
|
|
9625
9770
|
close();
|
|
@@ -9631,8 +9776,8 @@ function serverCoverageInWorker() {
|
|
|
9631
9776
|
|
|
9632
9777
|
// src/internal/capture/code-reach.ts
|
|
9633
9778
|
var crypto5 = __toESM(require("crypto"));
|
|
9634
|
-
var
|
|
9635
|
-
var
|
|
9779
|
+
var fs23 = __toESM(require("fs"));
|
|
9780
|
+
var path23 = __toESM(require("path"));
|
|
9636
9781
|
var import_node_child_process3 = require("child_process");
|
|
9637
9782
|
|
|
9638
9783
|
// ../core/src/source-map.ts
|
|
@@ -9977,18 +10122,18 @@ function repositoryRoot(from) {
|
|
|
9977
10122
|
}).trim();
|
|
9978
10123
|
} catch {
|
|
9979
10124
|
}
|
|
9980
|
-
cachedRepoRoot = { from, root:
|
|
10125
|
+
cachedRepoRoot = { from, root: path23.resolve(root) };
|
|
9981
10126
|
return cachedRepoRoot.root;
|
|
9982
10127
|
}
|
|
9983
10128
|
function codeReachRoots(configDir, configured) {
|
|
9984
10129
|
const repoRoot = repositoryRoot(configDir);
|
|
9985
|
-
const
|
|
9986
|
-
const global = Array.isArray(configured) ?
|
|
10130
|
+
const resolve11 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path23.resolve(configDir, r));
|
|
10131
|
+
const global = Array.isArray(configured) ? resolve11(configured) : resolve11(configured?.["*"]);
|
|
9987
10132
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
9988
10133
|
if (configured && !Array.isArray(configured)) {
|
|
9989
10134
|
for (const [key, dirs] of Object.entries(configured)) {
|
|
9990
10135
|
const origin = key === "*" ? null : originOf3(key);
|
|
9991
|
-
const resolved =
|
|
10136
|
+
const resolved = resolve11(dirs);
|
|
9992
10137
|
if (origin && resolved.length) byOrigin.set(origin, resolved);
|
|
9993
10138
|
}
|
|
9994
10139
|
}
|
|
@@ -10011,7 +10156,7 @@ function scriptRoots(roots, scriptUrl, pageUrl) {
|
|
|
10011
10156
|
}
|
|
10012
10157
|
function isFile(file) {
|
|
10013
10158
|
try {
|
|
10014
|
-
return
|
|
10159
|
+
return fs23.statSync(file).isFile();
|
|
10015
10160
|
} catch {
|
|
10016
10161
|
return false;
|
|
10017
10162
|
}
|
|
@@ -10019,10 +10164,10 @@ function isFile(file) {
|
|
|
10019
10164
|
function resolveSourcePath(source, roots) {
|
|
10020
10165
|
const normalized = normalizeSourcePath(source);
|
|
10021
10166
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
10022
|
-
if (
|
|
10167
|
+
if (path23.isAbsolute(normalized) && isFile(normalized)) return path23.resolve(normalized);
|
|
10023
10168
|
const relative11 = normalized.replace(/^\/+/, "");
|
|
10024
10169
|
for (const root of roots) {
|
|
10025
|
-
const candidate =
|
|
10170
|
+
const candidate = path23.resolve(root, relative11);
|
|
10026
10171
|
if (isFile(candidate)) return candidate;
|
|
10027
10172
|
}
|
|
10028
10173
|
return null;
|
|
@@ -10054,7 +10199,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
10054
10199
|
return file;
|
|
10055
10200
|
}
|
|
10056
10201
|
function relativeToRepo(file, repoRoot) {
|
|
10057
|
-
return
|
|
10202
|
+
return path23.relative(repoRoot, file).split(path23.sep).join("/");
|
|
10058
10203
|
}
|
|
10059
10204
|
function scriptVersion(entry) {
|
|
10060
10205
|
return entry.hash ?? crypto5.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -10111,10 +10256,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
10111
10256
|
const files = /* @__PURE__ */ new Map();
|
|
10112
10257
|
const known = /* @__PURE__ */ new Map();
|
|
10113
10258
|
const addTo = (into, file, functions) => {
|
|
10114
|
-
const
|
|
10115
|
-
const listed = into.get(
|
|
10259
|
+
const path34 = relativeToRepo(file, roots.repoRoot);
|
|
10260
|
+
const listed = into.get(path34);
|
|
10116
10261
|
if (listed) listed.push(...functions);
|
|
10117
|
-
else into.set(
|
|
10262
|
+
else into.set(path34, [...functions]);
|
|
10118
10263
|
};
|
|
10119
10264
|
const add = (file, functions) => addTo(files, file, functions);
|
|
10120
10265
|
const bases = viteBases(entries);
|
|
@@ -10266,8 +10411,8 @@ function onScriptParsed(page, capture, event) {
|
|
|
10266
10411
|
var READ_WAIT_MS = 5e3;
|
|
10267
10412
|
function readCoverage(page, cdp) {
|
|
10268
10413
|
let timer;
|
|
10269
|
-
const late = new Promise((
|
|
10270
|
-
timer = setTimeout(() =>
|
|
10414
|
+
const late = new Promise((resolve11) => {
|
|
10415
|
+
timer = setTimeout(() => resolve11([]), READ_WAIT_MS);
|
|
10271
10416
|
});
|
|
10272
10417
|
const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
|
|
10273
10418
|
(answer) => answer.result,
|
|
@@ -10379,8 +10524,8 @@ async function stopCodeReach(page) {
|
|
|
10379
10524
|
// src/internal/collect/blob-ids.ts
|
|
10380
10525
|
var import_node_child_process4 = require("child_process");
|
|
10381
10526
|
var crypto6 = __toESM(require("crypto"));
|
|
10382
|
-
var
|
|
10383
|
-
var
|
|
10527
|
+
var fs24 = __toESM(require("fs"));
|
|
10528
|
+
var path24 = __toESM(require("path"));
|
|
10384
10529
|
function gitBlobId(content) {
|
|
10385
10530
|
return crypto6.createHash("sha1").update(`blob ${content.length}\0`).update(content).digest("hex");
|
|
10386
10531
|
}
|
|
@@ -10389,14 +10534,14 @@ function namesLines(file) {
|
|
|
10389
10534
|
}
|
|
10390
10535
|
var GIT_BATCH = 500;
|
|
10391
10536
|
function gitHashObjects(root, paths) {
|
|
10392
|
-
return new Promise((
|
|
10537
|
+
return new Promise((resolve11) => {
|
|
10393
10538
|
const child = (0, import_node_child_process4.execFile)(
|
|
10394
10539
|
"git",
|
|
10395
10540
|
["hash-object", "--stdin-paths"],
|
|
10396
10541
|
{ cwd: root, encoding: "utf8", timeout: 3e4, windowsHide: true },
|
|
10397
10542
|
(error, stdout) => {
|
|
10398
10543
|
const ids = error ? [] : stdout.split("\n").filter(Boolean);
|
|
10399
|
-
|
|
10544
|
+
resolve11(ids.length === paths.length ? ids : null);
|
|
10400
10545
|
}
|
|
10401
10546
|
);
|
|
10402
10547
|
child.stdin?.on("error", () => {
|
|
@@ -10447,15 +10592,15 @@ var BlobIds = class {
|
|
|
10447
10592
|
async read(relativePaths) {
|
|
10448
10593
|
let root;
|
|
10449
10594
|
try {
|
|
10450
|
-
root =
|
|
10595
|
+
root = path24.resolve(this.repoRoot());
|
|
10451
10596
|
} catch {
|
|
10452
10597
|
return relativePaths.map(() => null);
|
|
10453
10598
|
}
|
|
10454
10599
|
const files = await Promise.all(
|
|
10455
10600
|
relativePaths.map(async (relativePath2) => {
|
|
10456
|
-
const file =
|
|
10457
|
-
if (!file.startsWith(root +
|
|
10458
|
-
const stat = await
|
|
10601
|
+
const file = path24.resolve(root, relativePath2);
|
|
10602
|
+
if (!file.startsWith(root + path24.sep)) return null;
|
|
10603
|
+
const stat = await fs24.promises.stat(file).catch(() => null);
|
|
10459
10604
|
return stat?.isFile() ? file : null;
|
|
10460
10605
|
})
|
|
10461
10606
|
);
|
|
@@ -10463,7 +10608,7 @@ var BlobIds = class {
|
|
|
10463
10608
|
const fromGit = /* @__PURE__ */ new Map();
|
|
10464
10609
|
const ids = forGit.length > 0 ? await this.hashObjects(
|
|
10465
10610
|
root,
|
|
10466
|
-
forGit.map((file) =>
|
|
10611
|
+
forGit.map((file) => path24.relative(root, file))
|
|
10467
10612
|
) : null;
|
|
10468
10613
|
if (ids) forGit.forEach((file, i) => fromGit.set(file, ids[i]));
|
|
10469
10614
|
return Promise.all(
|
|
@@ -10472,7 +10617,7 @@ var BlobIds = class {
|
|
|
10472
10617
|
const id = fromGit.get(file);
|
|
10473
10618
|
if (id) return id;
|
|
10474
10619
|
try {
|
|
10475
|
-
return gitBlobId(await
|
|
10620
|
+
return gitBlobId(await fs24.promises.readFile(file));
|
|
10476
10621
|
} catch {
|
|
10477
10622
|
return null;
|
|
10478
10623
|
}
|
|
@@ -10669,7 +10814,7 @@ var CoverageCollection = class {
|
|
|
10669
10814
|
}
|
|
10670
10815
|
let timer;
|
|
10671
10816
|
let finish;
|
|
10672
|
-
const done = new Promise((
|
|
10817
|
+
const done = new Promise((resolve11) => {
|
|
10673
10818
|
let settled = false;
|
|
10674
10819
|
finish = (server) => {
|
|
10675
10820
|
if (settled) return;
|
|
@@ -10678,7 +10823,7 @@ var CoverageCollection = class {
|
|
|
10678
10823
|
this.pending.delete(entry);
|
|
10679
10824
|
void Promise.resolve(browser).then((files) => {
|
|
10680
10825
|
this.assign(testCase, composeTestCoverage(server, files, this.options.settings));
|
|
10681
|
-
|
|
10826
|
+
resolve11();
|
|
10682
10827
|
});
|
|
10683
10828
|
};
|
|
10684
10829
|
});
|
|
@@ -10710,8 +10855,8 @@ var CoverageCollection = class {
|
|
|
10710
10855
|
let timer;
|
|
10711
10856
|
await Promise.race([
|
|
10712
10857
|
Promise.all(waiting),
|
|
10713
|
-
new Promise((
|
|
10714
|
-
timer = setTimeout(
|
|
10858
|
+
new Promise((resolve11) => {
|
|
10859
|
+
timer = setTimeout(resolve11, boundMs);
|
|
10715
10860
|
})
|
|
10716
10861
|
]);
|
|
10717
10862
|
clearTimeout(timer);
|
|
@@ -10829,10 +10974,13 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10829
10974
|
this.streamManager = null;
|
|
10830
10975
|
/** Track suite-level setup steps (beforeAll/afterAll) not tied to any test */
|
|
10831
10976
|
this.setupSteps = [];
|
|
10832
|
-
|
|
10977
|
+
const configDir = typeof rawOptions.configDir === "string" ? rawOptions.configDir : void 0;
|
|
10978
|
+
this.options = resolveOptions(rawOptions, configDir);
|
|
10833
10979
|
this.enabled = this.options.enabled !== false && !!this.options.serverUrl;
|
|
10834
10980
|
this.listMode = isListMode();
|
|
10835
10981
|
this.viaDesktopApp = usedDesktopDiscovery();
|
|
10982
|
+
this.desktopReplaced = desktopReplacedServer();
|
|
10983
|
+
this.viaDotEnv = usedDotEnv();
|
|
10836
10984
|
this.coverageSettings = coverageSettings(this.options);
|
|
10837
10985
|
const logger = new Logger(this.options.verbose ?? false);
|
|
10838
10986
|
this.logger = logger;
|
|
@@ -10890,14 +11038,17 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
10890
11038
|
return;
|
|
10891
11039
|
}
|
|
10892
11040
|
if (this.viaDesktopApp) {
|
|
10893
|
-
this.logger.info(
|
|
11041
|
+
this.logger.info(
|
|
11042
|
+
this.desktopReplaced ? `Using the desktop app running at ${this.options.serverUrl} instead of ${this.desktopReplaced} (desktopApp: 'prefer', not on CI).` : `Using the desktop app running at ${this.options.serverUrl} (no serverUrl configured).`
|
|
11043
|
+
);
|
|
10894
11044
|
}
|
|
11045
|
+
if (this.viaDotEnv) this.logger.debug(`Read ${this.viaDotEnv.keys.join(", ")} from ${this.viaDotEnv.file}.`);
|
|
10895
11046
|
this.startTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
10896
11047
|
this.testPathBase = configDirFromConfig(config);
|
|
10897
11048
|
this.playwrightVersion = config.version;
|
|
10898
11049
|
this.maxFailures = config.maxFailures ?? 0;
|
|
10899
11050
|
if (this.options.captureResources !== false) {
|
|
10900
|
-
this.resourcesFile =
|
|
11051
|
+
this.resourcesFile = path25.join(os9.tmpdir(), `piwi-resources-${process.pid}-${Date.now()}.jsonl`);
|
|
10901
11052
|
process.env[PIWI_RESOURCES_RESULTS_ENV] = this.resourcesFile;
|
|
10902
11053
|
try {
|
|
10903
11054
|
this.sampler = new RunSampler({ outputDirs: (config.projects ?? []).map((project) => project.outputDir) });
|
|
@@ -11039,6 +11190,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11039
11190
|
type: "begin",
|
|
11040
11191
|
title: test.title,
|
|
11041
11192
|
location: testLocation(test, this.testPathBase),
|
|
11193
|
+
repeatEachIndex: test.repeatEachIndex,
|
|
11042
11194
|
workerIndex: workerIndexOf(result),
|
|
11043
11195
|
shardIndex: this.shardInfo?.current ?? null,
|
|
11044
11196
|
browser: this.metadataCollector.getBrowserConfig(test) || void 0,
|
|
@@ -11146,6 +11298,8 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11146
11298
|
// Playwright reports the same line, so synthesize it.
|
|
11147
11299
|
error: expectedFailureError(result.status, annotations) ?? buildErrorText(result, this.testPathBase),
|
|
11148
11300
|
retries: result.retry,
|
|
11301
|
+
// Each `--repeat-each` repetition is a test of its own, with its own attempts.
|
|
11302
|
+
repeatEachIndex: test.repeatEachIndex,
|
|
11149
11303
|
attempts: attempts.map((a) => ({ ...a })),
|
|
11150
11304
|
workerIndex: workerIndexOf(result),
|
|
11151
11305
|
shardIndex: this.shardInfo?.current ?? null,
|
|
@@ -11172,10 +11326,11 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11172
11326
|
if (frames.length) testCase.testSourceFrames = frames;
|
|
11173
11327
|
}
|
|
11174
11328
|
if (this.options.collectPerformanceMetrics && result.steps?.length > 0) {
|
|
11175
|
-
|
|
11329
|
+
const pauses = readPauses(result.attachments?.find((a) => a.name === ATTACHMENT_NAMES.pauses)?.body);
|
|
11330
|
+
testCase.performanceMetrics = collectStepMetrics(result.steps, result.errors, pauses);
|
|
11176
11331
|
const stepEvents = extractTestStepEvents(result.steps, result.startTime);
|
|
11177
11332
|
const waitEvents = extractWaitEvents(result.steps);
|
|
11178
|
-
const allEvents = [...stepEvents, ...waitEvents];
|
|
11333
|
+
const allEvents = [...stepEvents, ...pauseStepEvents(pauses), ...waitEvents];
|
|
11179
11334
|
if (allEvents.length > 0) testCase.stepEvents = allEvents;
|
|
11180
11335
|
}
|
|
11181
11336
|
if (this.options.collectPerformanceMetrics && result.attachments) {
|
|
@@ -11232,6 +11387,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11232
11387
|
file: testFile(test, this.testPathBase),
|
|
11233
11388
|
retry: result.retry,
|
|
11234
11389
|
browser: typeof testCase.browser?.projectName === "string" ? testCase.browser.projectName : null,
|
|
11390
|
+
repeatEachIndex: test.repeatEachIndex,
|
|
11235
11391
|
headline: failureHeadline(testCase.error, testCase.performanceMetrics?.steps)
|
|
11236
11392
|
});
|
|
11237
11393
|
}
|
|
@@ -11262,7 +11418,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11262
11418
|
if (!file.path || INTERNAL_ATTACHMENT_NAMES.has(file.name)) continue;
|
|
11263
11419
|
try {
|
|
11264
11420
|
const kind = artifactKind(file.name, file.contentType);
|
|
11265
|
-
const bytes =
|
|
11421
|
+
const bytes = fs25.statSync(file.path).size;
|
|
11266
11422
|
artifacts[kind] += bytes;
|
|
11267
11423
|
this.artifactBytes[kind] += bytes;
|
|
11268
11424
|
} catch {
|
|
@@ -11295,8 +11451,8 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11295
11451
|
const censuses = [...this.resourceCensuses];
|
|
11296
11452
|
if (this.resourcesFile) {
|
|
11297
11453
|
try {
|
|
11298
|
-
censuses.push(...parseResourceResults(
|
|
11299
|
-
|
|
11454
|
+
censuses.push(...parseResourceResults(fs25.readFileSync(this.resourcesFile, "utf8")));
|
|
11455
|
+
fs25.rmSync(this.resourcesFile, { force: true });
|
|
11300
11456
|
} catch {
|
|
11301
11457
|
}
|
|
11302
11458
|
}
|
|
@@ -11350,6 +11506,7 @@ var PiwiDashboardReporter = class _PiwiDashboardReporter {
|
|
|
11350
11506
|
timeout: test.timeout ?? null,
|
|
11351
11507
|
error: null,
|
|
11352
11508
|
retries: 0,
|
|
11509
|
+
repeatEachIndex: test.repeatEachIndex,
|
|
11353
11510
|
workerIndex: null,
|
|
11354
11511
|
shardIndex: this.shardInfo?.current ?? null,
|
|
11355
11512
|
startedAt: null,
|
|
@@ -11520,7 +11677,7 @@ async function resolveSelection(options = {}) {
|
|
|
11520
11677
|
}
|
|
11521
11678
|
|
|
11522
11679
|
// src/internal/capture/capture-fixtures.ts
|
|
11523
|
-
var
|
|
11680
|
+
var path30 = __toESM(require("path"));
|
|
11524
11681
|
var import_node_zlib2 = require("zlib");
|
|
11525
11682
|
|
|
11526
11683
|
// ../picker-dom/src/probe.ts
|
|
@@ -11874,10 +12031,11 @@ function installPickerOverlay(arg) {
|
|
|
11874
12031
|
}
|
|
11875
12032
|
const Z = 2147483600;
|
|
11876
12033
|
const testIdAttr = arg.testIdAttribute || "data-testid";
|
|
12034
|
+
const enterReaches = arg.transport === "global";
|
|
11877
12035
|
const words = {
|
|
11878
12036
|
banner: "Piwi inspector \u2014 click any element to generate locators for it",
|
|
11879
|
-
keys: "\u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
11880
|
-
keysHovering: "click to pick \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
12037
|
+
keys: enterReaches ? "Enter picks the focused element \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip" : "\u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
12038
|
+
keysHovering: enterReaches ? "click or Enter to pick \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip" : "click to pick \xB7 \u2191 parent \xB7 \u2193 child \xB7 Esc skip",
|
|
11881
12039
|
analyzing: "Analyzing element\u2026"
|
|
11882
12040
|
};
|
|
11883
12041
|
for (const key of Object.keys(words)) {
|
|
@@ -11972,6 +12130,16 @@ function installPickerOverlay(arg) {
|
|
|
11972
12130
|
const cls = (el.getAttribute && el.getAttribute("class") || "").split(/\s+/).find((c) => c.length > 1);
|
|
11973
12131
|
return cls ? `locator(${quoteJs(`.${cls}`)})` : tag;
|
|
11974
12132
|
};
|
|
12133
|
+
const parentOf = (node) => {
|
|
12134
|
+
if (node.parentElement) return node.parentElement;
|
|
12135
|
+
const root = node.getRootNode ? node.getRootNode() : null;
|
|
12136
|
+
return root && root !== doc && root.host ? root.host : null;
|
|
12137
|
+
};
|
|
12138
|
+
const targetOf = (e) => {
|
|
12139
|
+
const path34 = typeof e.composedPath === "function" ? e.composedPath() : null;
|
|
12140
|
+
const first = path34 && path34.length ? path34[0] : null;
|
|
12141
|
+
return first && first.nodeType === 1 ? first : e.target;
|
|
12142
|
+
};
|
|
11975
12143
|
const buildChain = (raw) => {
|
|
11976
12144
|
const chain2 = [];
|
|
11977
12145
|
let node = raw;
|
|
@@ -11979,7 +12147,7 @@ function installPickerOverlay(arg) {
|
|
|
11979
12147
|
const tag = (node.tagName || "").toLowerCase();
|
|
11980
12148
|
if (tag === "body" || tag === "html") break;
|
|
11981
12149
|
chain2.push(node);
|
|
11982
|
-
node = node
|
|
12150
|
+
node = parentOf(node);
|
|
11983
12151
|
}
|
|
11984
12152
|
return chain2.length ? chain2 : [raw];
|
|
11985
12153
|
};
|
|
@@ -12070,10 +12238,14 @@ function installPickerOverlay(arg) {
|
|
|
12070
12238
|
}
|
|
12071
12239
|
};
|
|
12072
12240
|
const onMove = (e) => {
|
|
12073
|
-
const raw = e
|
|
12241
|
+
const raw = targetOf(e);
|
|
12074
12242
|
if (!raw || isOwn(raw)) {
|
|
12243
|
+
chain = [];
|
|
12244
|
+
idx = 0;
|
|
12245
|
+
lastRaw = null;
|
|
12075
12246
|
highlight.style.display = "none";
|
|
12076
12247
|
label.style.display = "none";
|
|
12248
|
+
foot.textContent = words.keys;
|
|
12077
12249
|
return;
|
|
12078
12250
|
}
|
|
12079
12251
|
if (raw !== lastRaw) {
|
|
@@ -12152,17 +12324,30 @@ function installPickerOverlay(arg) {
|
|
|
12152
12324
|
const onClick = (e) => {
|
|
12153
12325
|
stop(e);
|
|
12154
12326
|
const el = current();
|
|
12155
|
-
if (!el || isOwn(e
|
|
12327
|
+
if (!el || isOwn(targetOf(e))) return;
|
|
12156
12328
|
reportPicked(el);
|
|
12157
12329
|
};
|
|
12330
|
+
const focused = () => {
|
|
12331
|
+
let el = doc.activeElement;
|
|
12332
|
+
while (el && el.shadowRoot && el.shadowRoot.activeElement) el = el.shadowRoot.activeElement;
|
|
12333
|
+
const tag = (el && el.tagName || "").toLowerCase();
|
|
12334
|
+
return !el || tag === "body" || tag === "html" || isOwn(el) ? null : el;
|
|
12335
|
+
};
|
|
12158
12336
|
const onKey = (e) => {
|
|
12159
12337
|
if (e.key === "Escape") {
|
|
12160
12338
|
stop(e);
|
|
12161
12339
|
reportSkipped();
|
|
12162
12340
|
return;
|
|
12163
12341
|
}
|
|
12342
|
+
if (e.key === "Enter" && !e.isComposing) {
|
|
12343
|
+
stop(e);
|
|
12344
|
+
const el = current() || focused();
|
|
12345
|
+
if (el) reportPicked(el);
|
|
12346
|
+
return;
|
|
12347
|
+
}
|
|
12164
12348
|
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
|
12165
12349
|
stop(e);
|
|
12350
|
+
if (!chain.length) return;
|
|
12166
12351
|
if (e.key === "ArrowUp") idx = Math.min(idx + 1, chain.length - 1);
|
|
12167
12352
|
else idx = Math.max(idx - 1, 0);
|
|
12168
12353
|
refresh();
|
|
@@ -12236,12 +12421,43 @@ function showAnchorPicker(arg) {
|
|
|
12236
12421
|
const al = n.getAttribute && n.getAttribute("aria-level");
|
|
12237
12422
|
return al && /^\d+$/.test(al) ? Number(al) : null;
|
|
12238
12423
|
};
|
|
12424
|
+
const parentOf = (n) => {
|
|
12425
|
+
if (n.parentElement) return n.parentElement;
|
|
12426
|
+
const root = n.getRootNode ? n.getRootNode() : null;
|
|
12427
|
+
return root && root !== doc && root.host ? root.host : null;
|
|
12428
|
+
};
|
|
12429
|
+
const shadowRoots = [];
|
|
12430
|
+
const collectRoots = (root) => {
|
|
12431
|
+
const all = root.querySelectorAll("*");
|
|
12432
|
+
for (let i = 0; i < all.length; i++) {
|
|
12433
|
+
const sr = all[i].shadowRoot;
|
|
12434
|
+
if (sr) {
|
|
12435
|
+
shadowRoots.push(sr);
|
|
12436
|
+
collectRoots(sr);
|
|
12437
|
+
}
|
|
12438
|
+
}
|
|
12439
|
+
};
|
|
12440
|
+
try {
|
|
12441
|
+
collectRoots(doc);
|
|
12442
|
+
} catch {
|
|
12443
|
+
shadowRoots.length = 0;
|
|
12444
|
+
}
|
|
12445
|
+
const within = (node2, scope) => {
|
|
12446
|
+
if (scope === doc) return true;
|
|
12447
|
+
for (let n = node2; n; n = parentOf(n)) if (n === scope) return true;
|
|
12448
|
+
return false;
|
|
12449
|
+
};
|
|
12450
|
+
const queryAll = (scope, sel) => {
|
|
12451
|
+
const found = Array.from(scope.querySelectorAll(sel));
|
|
12452
|
+
for (const sr of shadowRoots) if (within(sr.host, scope)) found.push(...Array.from(sr.querySelectorAll(sel)));
|
|
12453
|
+
return found;
|
|
12454
|
+
};
|
|
12239
12455
|
const leafCountable = !!leafTestId || roleOf2(el) === leafRole;
|
|
12240
12456
|
const leafMatches = (scope) => {
|
|
12241
12457
|
try {
|
|
12242
|
-
if (leafTestId) return scope
|
|
12458
|
+
if (leafTestId) return queryAll(scope, `[${testIdAttr}=${JSON.stringify(leafTestId)}]`).length;
|
|
12243
12459
|
if (!leafCountable) return -1;
|
|
12244
|
-
const nodes = scope
|
|
12460
|
+
const nodes = queryAll(scope, roleSources);
|
|
12245
12461
|
if (nodes.length > 2e3) return -1;
|
|
12246
12462
|
let matched = 0;
|
|
12247
12463
|
for (let i = 0; i < nodes.length; i++) {
|
|
@@ -12257,20 +12473,20 @@ function showAnchorPicker(arg) {
|
|
|
12257
12473
|
};
|
|
12258
12474
|
let roleNodes = [];
|
|
12259
12475
|
try {
|
|
12260
|
-
const all = doc
|
|
12261
|
-
if (all.length <= 4e3) roleNodes =
|
|
12476
|
+
const all = queryAll(doc, roleSources);
|
|
12477
|
+
if (all.length <= 4e3) roleNodes = all;
|
|
12262
12478
|
} catch {
|
|
12263
12479
|
roleNodes = [];
|
|
12264
12480
|
}
|
|
12265
12481
|
const count = (sel) => {
|
|
12266
12482
|
try {
|
|
12267
|
-
return doc
|
|
12483
|
+
return queryAll(doc, sel).length;
|
|
12268
12484
|
} catch {
|
|
12269
12485
|
return void 0;
|
|
12270
12486
|
}
|
|
12271
12487
|
};
|
|
12272
12488
|
const rows = [];
|
|
12273
|
-
let node = el
|
|
12489
|
+
let node = parentOf(el);
|
|
12274
12490
|
let depth = 0;
|
|
12275
12491
|
while (node && depth < 12) {
|
|
12276
12492
|
depth++;
|
|
@@ -12307,7 +12523,7 @@ function showAnchorPicker(arg) {
|
|
|
12307
12523
|
hookLabel,
|
|
12308
12524
|
selectable: !!(testId || id || role && (ariaLabel || info.roleCount === 1))
|
|
12309
12525
|
});
|
|
12310
|
-
node = node
|
|
12526
|
+
node = parentOf(node);
|
|
12311
12527
|
}
|
|
12312
12528
|
if (rows.length === 0) {
|
|
12313
12529
|
g.__piwiAnchorState = "skipped";
|
|
@@ -12336,9 +12552,9 @@ function showAnchorPicker(arg) {
|
|
|
12336
12552
|
footer.style.cssText = "margin:10px 0;font-weight:600;";
|
|
12337
12553
|
const segMatches = (scope, info) => {
|
|
12338
12554
|
try {
|
|
12339
|
-
if (info.testId) return
|
|
12340
|
-
if (info.id) return
|
|
12341
|
-
const nodes =
|
|
12555
|
+
if (info.testId) return queryAll(scope, `[${testIdAttr}=${JSON.stringify(info.testId)}]`);
|
|
12556
|
+
if (info.id) return queryAll(scope, `#${doc.defaultView.CSS.escape(info.id)}`);
|
|
12557
|
+
const nodes = queryAll(scope, roleSources);
|
|
12342
12558
|
if (nodes.length > 2e3) return [];
|
|
12343
12559
|
return nodes.filter(
|
|
12344
12560
|
(n) => roleOf2(n) === info.role && (!info.ariaLabel || n.getAttribute && n.getAttribute("aria-label") === info.ariaLabel)
|
|
@@ -12625,8 +12841,11 @@ function showPauseBar(arg) {
|
|
|
12625
12841
|
};
|
|
12626
12842
|
const buttons = [
|
|
12627
12843
|
["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
|
|
12628
|
-
["Step", "step", "Pause again
|
|
12844
|
+
["Step", "step", "Pause again before the next call", false],
|
|
12629
12845
|
["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
|
|
12846
|
+
...arg.record ? [
|
|
12847
|
+
["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
|
|
12848
|
+
] : [],
|
|
12630
12849
|
["Finish", "finish", "Run on and pause no more in this test", false]
|
|
12631
12850
|
];
|
|
12632
12851
|
for (const [label, choice, hint, primary] of buttons) {
|
|
@@ -13102,21 +13321,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
13102
13321
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
13103
13322
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
13104
13323
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
13105
|
-
function isElementPath(
|
|
13106
|
-
return typeof
|
|
13324
|
+
function isElementPath(path34) {
|
|
13325
|
+
return typeof path34 === "string" && path34.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path34);
|
|
13107
13326
|
}
|
|
13108
13327
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
13109
13328
|
function elementRef(v) {
|
|
13110
13329
|
if (!v || typeof v !== "object") return null;
|
|
13111
|
-
const { path:
|
|
13112
|
-
if (!isElementPath(
|
|
13113
|
-
return { path:
|
|
13330
|
+
const { path: path34, label } = v;
|
|
13331
|
+
if (!isElementPath(path34)) return null;
|
|
13332
|
+
return { path: path34, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
13114
13333
|
}
|
|
13115
13334
|
function textPoint(v) {
|
|
13116
13335
|
if (!v || typeof v !== "object") return null;
|
|
13117
|
-
const { path:
|
|
13336
|
+
const { path: path34, offset } = v;
|
|
13118
13337
|
const at = offsetOf(offset);
|
|
13119
|
-
return isElementPath(
|
|
13338
|
+
return isElementPath(path34) && at !== null ? { path: path34, offset: at } : null;
|
|
13120
13339
|
}
|
|
13121
13340
|
function sanitizePageInteraction(raw) {
|
|
13122
13341
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -13147,7 +13366,7 @@ function sanitizePageInteraction(raw) {
|
|
|
13147
13366
|
}
|
|
13148
13367
|
|
|
13149
13368
|
// src/internal/capture/locator-healing.ts
|
|
13150
|
-
var
|
|
13369
|
+
var path26 = __toESM(require("path"));
|
|
13151
13370
|
|
|
13152
13371
|
// ../core/src/locator-fingerprint.ts
|
|
13153
13372
|
var ELEMENT_MATCH_SCORES = { role: 60, text: 55, label: 50 };
|
|
@@ -13532,10 +13751,10 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
|
|
|
13532
13751
|
}
|
|
13533
13752
|
let rel = file;
|
|
13534
13753
|
try {
|
|
13535
|
-
rel =
|
|
13754
|
+
rel = path26.relative(process.cwd(), file);
|
|
13536
13755
|
} catch {
|
|
13537
13756
|
}
|
|
13538
|
-
rel = rel.split(
|
|
13757
|
+
rel = rel.split(path26.sep).join("/");
|
|
13539
13758
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
13540
13759
|
return `${rel}:${m[3]}:${m[4]}`;
|
|
13541
13760
|
}
|
|
@@ -13765,7 +13984,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
|
|
|
13765
13984
|
}
|
|
13766
13985
|
|
|
13767
13986
|
// src/internal/capture/pick-on-failure.ts
|
|
13768
|
-
var
|
|
13987
|
+
var path27 = __toESM(require("path"));
|
|
13769
13988
|
function parseLeafLocatorExpression(rawExpr) {
|
|
13770
13989
|
const leaf = parseLeafLocatorCall(rawExpr);
|
|
13771
13990
|
return leaf ? { method: leaf.method, args: locatorCallValues(leaf) } : null;
|
|
@@ -13780,37 +13999,60 @@ ${err.stack ?? ""}`));
|
|
|
13780
13999
|
const parsed2 = parseLeafLocatorExpression(header);
|
|
13781
14000
|
if (!parsed2) continue;
|
|
13782
14001
|
const loc = err.location;
|
|
13783
|
-
const location = loc ? `${
|
|
14002
|
+
const location = loc ? `${path27.relative(process.cwd(), loc.file).split(path27.sep).join("/")}:${loc.line}:${loc.column}` : null;
|
|
13784
14003
|
return { method: parsed2.method, args: parsed2.args, location };
|
|
13785
14004
|
}
|
|
13786
14005
|
return null;
|
|
13787
14006
|
}
|
|
13788
|
-
|
|
14007
|
+
var PICK_ENDED = "__piwiPickEnded";
|
|
14008
|
+
async function cleanupPicker(page, ended = false) {
|
|
13789
14009
|
try {
|
|
13790
|
-
await page.evaluate(
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
13795
|
-
|
|
13796
|
-
|
|
13797
|
-
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
13801
|
-
|
|
13802
|
-
|
|
14010
|
+
await page.evaluate(
|
|
14011
|
+
([endedKey, unanswered]) => {
|
|
14012
|
+
const g = globalThis;
|
|
14013
|
+
if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
|
|
14014
|
+
if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
|
|
14015
|
+
delete g.__piwiPickCleanup;
|
|
14016
|
+
delete g.__piwiAnchorCleanup;
|
|
14017
|
+
delete g.__piwiPickState;
|
|
14018
|
+
delete g.__piwiPickChoice;
|
|
14019
|
+
delete g.__piwiPickedElement;
|
|
14020
|
+
delete g.__piwiAnchorState;
|
|
14021
|
+
delete g.__piwiPickAnchors;
|
|
14022
|
+
delete g.__piwiPickChainCount;
|
|
14023
|
+
if (unanswered) g[endedKey] = true;
|
|
14024
|
+
else delete g[endedKey];
|
|
14025
|
+
},
|
|
14026
|
+
[PICK_ENDED, ended]
|
|
14027
|
+
);
|
|
13803
14028
|
} catch {
|
|
13804
14029
|
}
|
|
13805
14030
|
}
|
|
13806
|
-
|
|
14031
|
+
function unlessEnded(run, ended) {
|
|
14032
|
+
if (!ended) return run;
|
|
14033
|
+
return Promise.race([
|
|
14034
|
+
run,
|
|
14035
|
+
new Promise((resolve11) => {
|
|
14036
|
+
if (ended.aborted) resolve11(void 0);
|
|
14037
|
+
else ended.addEventListener("abort", () => resolve11(void 0), { once: true });
|
|
14038
|
+
})
|
|
14039
|
+
]);
|
|
14040
|
+
}
|
|
14041
|
+
async function pickLocator(page, probe, failing, ended) {
|
|
14042
|
+
const answered = async (name) => {
|
|
14043
|
+
const wait = page.waitForFunction(
|
|
14044
|
+
([key, endedKey]) => globalThis[key] !== void 0 || globalThis[endedKey] === true,
|
|
14045
|
+
[name, PICK_ENDED],
|
|
14046
|
+
{ timeout: 0, polling: 250 }
|
|
14047
|
+
);
|
|
14048
|
+
await unlessEnded(wait, ended);
|
|
14049
|
+
return !ended?.aborted;
|
|
14050
|
+
};
|
|
13807
14051
|
try {
|
|
13808
14052
|
const overlayArg = { transport: "global", failing };
|
|
14053
|
+
await page.evaluate((endedKey) => delete globalThis[endedKey], PICK_ENDED);
|
|
13809
14054
|
await page.evaluate(installPickerOverlay, overlayArg);
|
|
13810
|
-
await
|
|
13811
|
-
timeout: 0,
|
|
13812
|
-
polling: 250
|
|
13813
|
-
});
|
|
14055
|
+
if (!await answered("__piwiPickState")) return null;
|
|
13814
14056
|
const state = await page.evaluate(() => globalThis.__piwiPickState);
|
|
13815
14057
|
if (state !== "picked") return null;
|
|
13816
14058
|
const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
|
|
@@ -13831,10 +14073,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13831
14073
|
leafLevel: level,
|
|
13832
14074
|
leafTestId: attrs.attributes["data-testid"] ?? null
|
|
13833
14075
|
});
|
|
13834
|
-
await
|
|
13835
|
-
timeout: 0,
|
|
13836
|
-
polling: 250
|
|
13837
|
-
});
|
|
14076
|
+
if (!await answered("__piwiAnchorState")) return null;
|
|
13838
14077
|
const anchorResult = await page.evaluate(() => {
|
|
13839
14078
|
const g = globalThis;
|
|
13840
14079
|
return { state: g.__piwiAnchorState, anchors: g.__piwiPickAnchors ?? [], chainCount: g.__piwiPickChainCount };
|
|
@@ -13856,10 +14095,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13856
14095
|
failing,
|
|
13857
14096
|
choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
|
|
13858
14097
|
});
|
|
13859
|
-
await
|
|
13860
|
-
timeout: 0,
|
|
13861
|
-
polling: 250
|
|
13862
|
-
});
|
|
14098
|
+
if (!await answered("__piwiPickChoice")) return null;
|
|
13863
14099
|
const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
|
|
13864
14100
|
if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
|
|
13865
14101
|
const picked = { ...ranked[choice], pickedByUser: true };
|
|
@@ -13878,7 +14114,7 @@ async function pickLocator(page, probe, failing) {
|
|
|
13878
14114
|
...anchors.length > 0 ? { anchors } : {}
|
|
13879
14115
|
};
|
|
13880
14116
|
} finally {
|
|
13881
|
-
await cleanupPicker(page);
|
|
14117
|
+
await cleanupPicker(page, ended?.aborted);
|
|
13882
14118
|
}
|
|
13883
14119
|
}
|
|
13884
14120
|
async function runLocatorPicker(page, testInfo, failed, probe) {
|
|
@@ -13933,7 +14169,7 @@ function applyPickToSnapshots(snapshots, pick) {
|
|
|
13933
14169
|
}
|
|
13934
14170
|
|
|
13935
14171
|
// src/internal/capture/pause-at.ts
|
|
13936
|
-
var
|
|
14172
|
+
var path28 = __toESM(require("path"));
|
|
13937
14173
|
|
|
13938
14174
|
// ../core/src/editor-send.ts
|
|
13939
14175
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -13974,18 +14210,32 @@ var PauseSet = class {
|
|
|
13974
14210
|
return col > 0 && this.lines.has(callerLocation.slice(0, col));
|
|
13975
14211
|
}
|
|
13976
14212
|
};
|
|
13977
|
-
var
|
|
14213
|
+
var NO_BREAKPOINTS = new PauseSet(/* @__PURE__ */ new Set());
|
|
13978
14214
|
function callSiteFile(file, cwd) {
|
|
13979
14215
|
const slashed = file.replace(/\\/g, "/");
|
|
13980
|
-
const absolute =
|
|
13981
|
-
let rel = absolute ?
|
|
13982
|
-
rel = rel.split(
|
|
14216
|
+
const absolute = path28.isAbsolute(file) || path28.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
|
|
14217
|
+
let rel = absolute ? path28.relative(cwd, file) : path28.normalize(slashed);
|
|
14218
|
+
rel = rel.split(path28.sep).join("/").replace(/\\/g, "/");
|
|
13983
14219
|
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
13984
14220
|
return rel;
|
|
13985
14221
|
}
|
|
14222
|
+
function stopCallSite(location, cwd = process.cwd()) {
|
|
14223
|
+
if (!location.line) return null;
|
|
14224
|
+
return `${callSiteFile(location.file, cwd)}:${location.line}:${location.column ?? 0}`;
|
|
14225
|
+
}
|
|
14226
|
+
function callSiteLine(callSite) {
|
|
14227
|
+
const col = callSite.lastIndexOf(":");
|
|
14228
|
+
return col > 0 ? callSite.slice(0, col) : callSite;
|
|
14229
|
+
}
|
|
14230
|
+
function inFlightCallSite(inFlight) {
|
|
14231
|
+
const [first] = inFlight;
|
|
14232
|
+
if (first === void 0) return null;
|
|
14233
|
+
const line = callSiteLine(first);
|
|
14234
|
+
return inFlight.every((site) => callSiteLine(site) === line) ? first : null;
|
|
14235
|
+
}
|
|
13986
14236
|
var ignoredLogged = /* @__PURE__ */ new Set();
|
|
13987
14237
|
function parsePauseAt(value, cwd = process.cwd()) {
|
|
13988
|
-
if (!value?.trim()) return
|
|
14238
|
+
if (!value?.trim()) return NO_BREAKPOINTS;
|
|
13989
14239
|
const lines = /* @__PURE__ */ new Set();
|
|
13990
14240
|
for (const raw of value.split(";")) {
|
|
13991
14241
|
const entry = raw.trim();
|
|
@@ -14001,7 +14251,7 @@ function parsePauseAt(value, cwd = process.cwd()) {
|
|
|
14001
14251
|
}
|
|
14002
14252
|
lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
|
|
14003
14253
|
}
|
|
14004
|
-
return lines.size ? new PauseSet(lines) :
|
|
14254
|
+
return lines.size ? new PauseSet(lines) : NO_BREAKPOINTS;
|
|
14005
14255
|
}
|
|
14006
14256
|
var parsed = null;
|
|
14007
14257
|
function currentPauseSet() {
|
|
@@ -14011,27 +14261,121 @@ function currentPauseSet() {
|
|
|
14011
14261
|
}
|
|
14012
14262
|
function pauseGateFromTestInfo(testInfo) {
|
|
14013
14263
|
const use = testInfo.project?.use ?? {};
|
|
14014
|
-
|
|
14264
|
+
const debug = testInfo._configInternal?.configCLIOverrides?.debug;
|
|
14265
|
+
return { ci: process.env.CI, headless: use.headless, pwdebug: process.env.PWDEBUG, debug };
|
|
14015
14266
|
}
|
|
14016
14267
|
function pauseSkipReason(gate) {
|
|
14017
14268
|
if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
|
|
14018
14269
|
if (gate.headless !== false)
|
|
14019
14270
|
return "the browser is headless \u2014 re-run with --headed (or set use: { headless: false })";
|
|
14271
|
+
if (gate.pwdebug && !["0", "false", "console"].includes(gate.pwdebug))
|
|
14272
|
+
return "Playwright's Inspector runs the test (PWDEBUG) \u2014 run without --debug";
|
|
14273
|
+
if (typeof gate.debug === "string" && gate.debug)
|
|
14274
|
+
return `Playwright's debugger runs the test (--debug=${gate.debug}) \u2014 run without --debug`;
|
|
14020
14275
|
return null;
|
|
14021
14276
|
}
|
|
14022
|
-
function
|
|
14023
|
-
|
|
14277
|
+
function atStop(state, callSite) {
|
|
14278
|
+
if (callSite && !state.finished) {
|
|
14279
|
+
if (state.step) {
|
|
14280
|
+
state.step = false;
|
|
14281
|
+
return { show: "step" };
|
|
14282
|
+
}
|
|
14283
|
+
if (state.set.matches(callSite)) return { show: "breakpoint" };
|
|
14284
|
+
}
|
|
14285
|
+
return { go: goOn(state) };
|
|
14286
|
+
}
|
|
14287
|
+
function goOn(state, answer) {
|
|
14288
|
+
if (answer === "step") state.step = true;
|
|
14289
|
+
else if (answer === "finish") state.finished = true;
|
|
14290
|
+
return !state.finished && (state.step || state.set.size > 0) ? "next" : "resume";
|
|
14291
|
+
}
|
|
14292
|
+
function contextDebugger(context) {
|
|
14293
|
+
const dbg = context.debugger;
|
|
14294
|
+
return typeof dbg?.requestPause === "function" && typeof dbg.on === "function" ? dbg : null;
|
|
14295
|
+
}
|
|
14296
|
+
var WATCHED = /* @__PURE__ */ new WeakSet();
|
|
14297
|
+
function watchStops(context, onStop) {
|
|
14298
|
+
const dbg = contextDebugger(context);
|
|
14299
|
+
if (!dbg) return false;
|
|
14300
|
+
if (WATCHED.has(context)) return true;
|
|
14301
|
+
WATCHED.add(context);
|
|
14302
|
+
inspectorSwitch.watching = true;
|
|
14303
|
+
syncInspectorSwitch();
|
|
14304
|
+
dbg.on("pausedstatechanged", () => onStop(context));
|
|
14305
|
+
return true;
|
|
14306
|
+
}
|
|
14307
|
+
var NO_INSPECTOR_ENV = "PW_CODEGEN_NO_INSPECTOR";
|
|
14308
|
+
var inspectorSwitch = {
|
|
14309
|
+
/** Set by Piwi, so Piwi's to clear. */
|
|
14310
|
+
set: false,
|
|
14311
|
+
/** Piwi answers the stops of a context in this worker. */
|
|
14312
|
+
watching: false,
|
|
14313
|
+
/** Every `page.pause()` of the worker's Playwright reaches it through Piwi (`markPausesWrapped`). */
|
|
14314
|
+
wrapped: false,
|
|
14315
|
+
/** The `page.pause()` calls left to Playwright that are running (`withInspector`). */
|
|
14316
|
+
open: 0
|
|
14317
|
+
};
|
|
14318
|
+
function syncInspectorSwitch() {
|
|
14319
|
+
const closed = inspectorSwitch.watching && inspectorSwitch.wrapped && inspectorSwitch.open === 0;
|
|
14320
|
+
if (closed && process.env[NO_INSPECTOR_ENV] === void 0) {
|
|
14321
|
+
process.env[NO_INSPECTOR_ENV] = "1";
|
|
14322
|
+
inspectorSwitch.set = true;
|
|
14323
|
+
} else if (!closed && inspectorSwitch.set) {
|
|
14324
|
+
if (process.env[NO_INSPECTOR_ENV] === "1") delete process.env[NO_INSPECTOR_ENV];
|
|
14325
|
+
inspectorSwitch.set = false;
|
|
14326
|
+
}
|
|
14327
|
+
}
|
|
14328
|
+
function markPausesWrapped() {
|
|
14329
|
+
inspectorSwitch.wrapped = true;
|
|
14330
|
+
syncInspectorSwitch();
|
|
14331
|
+
}
|
|
14332
|
+
async function withInspector(run) {
|
|
14333
|
+
inspectorSwitch.open++;
|
|
14334
|
+
syncInspectorSwitch();
|
|
14335
|
+
try {
|
|
14336
|
+
return await run();
|
|
14337
|
+
} finally {
|
|
14338
|
+
inspectorSwitch.open--;
|
|
14339
|
+
syncInspectorSwitch();
|
|
14340
|
+
}
|
|
14341
|
+
}
|
|
14342
|
+
async function steerDebugger(context, how) {
|
|
14343
|
+
const dbg = contextDebugger(context);
|
|
14344
|
+
if (!dbg) return;
|
|
14345
|
+
try {
|
|
14346
|
+
await internalCall(
|
|
14347
|
+
context,
|
|
14348
|
+
() => how === "pause" ? dbg.requestPause() : how === "next" ? dbg.next() : dbg.resume()
|
|
14349
|
+
);
|
|
14350
|
+
} catch {
|
|
14351
|
+
}
|
|
14352
|
+
}
|
|
14353
|
+
async function withTestTimeoutHeld(testInfo, run) {
|
|
14354
|
+
const set = testInfo._setIgnoreTimeouts;
|
|
14355
|
+
if (typeof set !== "function") return run();
|
|
14356
|
+
set.call(testInfo, true);
|
|
14357
|
+
try {
|
|
14358
|
+
return await run();
|
|
14359
|
+
} finally {
|
|
14360
|
+
set.call(testInfo, false);
|
|
14361
|
+
}
|
|
14362
|
+
}
|
|
14363
|
+
function testTimeSlot(testInfo) {
|
|
14364
|
+
try {
|
|
14365
|
+
const slot = testInfo._timeoutManager?.defaultSlot?.();
|
|
14366
|
+
return slot && typeof slot.elapsed === "number" ? slot : null;
|
|
14367
|
+
} catch {
|
|
14368
|
+
return null;
|
|
14369
|
+
}
|
|
14370
|
+
}
|
|
14371
|
+
function restoreTestTime(testInfo, heldAt) {
|
|
14372
|
+
const slot = testTimeSlot(testInfo);
|
|
14373
|
+
if (slot && Number.isFinite(heldAt) && heldAt >= 0 && heldAt < slot.elapsed) slot.elapsed = heldAt;
|
|
14024
14374
|
}
|
|
14025
14375
|
function pausePlace(callerLocation) {
|
|
14026
|
-
const
|
|
14027
|
-
const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
|
|
14376
|
+
const fileLine = callSiteLine(callerLocation);
|
|
14028
14377
|
return fileLine.slice(fileLine.lastIndexOf("/") + 1);
|
|
14029
14378
|
}
|
|
14030
|
-
function assertionLabel(expression, isNot) {
|
|
14031
|
-
const [first = "", ...rest] = expression.split(".").filter(Boolean);
|
|
14032
|
-
const name = first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
14033
|
-
return `${isNot ? "not." : ""}${name || "expect"}`;
|
|
14034
|
-
}
|
|
14035
14379
|
function pickPlace(callerLocation) {
|
|
14036
14380
|
const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
|
|
14037
14381
|
if (!m) return null;
|
|
@@ -14076,59 +14420,312 @@ function logPauseSkipOnce(reason) {
|
|
|
14076
14420
|
skipLogged = true;
|
|
14077
14421
|
console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
|
|
14078
14422
|
}
|
|
14423
|
+
var NAVIGATED = /Execution context was destroyed|Cannot find context with specified id/;
|
|
14424
|
+
var MAX_NAVIGATED = 10;
|
|
14079
14425
|
var HIGHLIGHT_MS = 2e3;
|
|
14080
|
-
async function pauseHere(page,
|
|
14081
|
-
const before = testInfo.timeout;
|
|
14082
|
-
const started = Date.now();
|
|
14426
|
+
async function pauseHere(page, point, probe, onPick, onRecord, ended) {
|
|
14083
14427
|
let shown = null;
|
|
14084
14428
|
try {
|
|
14085
|
-
testInfo.setTimeout(0);
|
|
14086
14429
|
const highlight = point.target?.highlight;
|
|
14087
14430
|
if (typeof highlight === "function") {
|
|
14088
14431
|
let timer;
|
|
14089
14432
|
shown = await Promise.race([
|
|
14090
14433
|
Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
|
|
14091
|
-
new Promise((
|
|
14092
|
-
timer = setTimeout(() =>
|
|
14434
|
+
new Promise((resolve11) => {
|
|
14435
|
+
timer = setTimeout(() => resolve11(null), HIGHLIGHT_MS);
|
|
14093
14436
|
})
|
|
14094
14437
|
]);
|
|
14095
14438
|
clearTimeout(timer);
|
|
14096
14439
|
}
|
|
14097
14440
|
const arg = {
|
|
14098
14441
|
place: pausePlace(point.location),
|
|
14099
|
-
attempt:
|
|
14100
|
-
action: point.
|
|
14101
|
-
locator:
|
|
14442
|
+
attempt: point.attempt,
|
|
14443
|
+
action: point.call,
|
|
14444
|
+
locator: null,
|
|
14445
|
+
record: !!onRecord
|
|
14102
14446
|
};
|
|
14103
14447
|
console.log(
|
|
14104
|
-
`[piwi] Paused at ${point.location} before ${point.
|
|
14448
|
+
`[piwi] Paused at ${callSiteLine(point.location)} before ${point.call}: Resume, Step, Pick a locator${onRecord ? ", Record here" : ""} or Finish in the browser (Esc resumes).`
|
|
14105
14449
|
);
|
|
14450
|
+
let navigated = 0;
|
|
14106
14451
|
for (; ; ) {
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14452
|
+
try {
|
|
14453
|
+
if (ended?.aborted) return "resume";
|
|
14454
|
+
await page.evaluate(showPauseBar, arg);
|
|
14455
|
+
await unlessEnded(page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 }), ended);
|
|
14456
|
+
if (ended?.aborted) return "resume";
|
|
14457
|
+
const choice = await page.evaluate(takePauseState);
|
|
14458
|
+
navigated = 0;
|
|
14459
|
+
if (choice === "resume" || choice === "step" || choice === "finish") return choice;
|
|
14460
|
+
if (choice === "record" && onRecord) {
|
|
14461
|
+
await unlessEnded(
|
|
14462
|
+
onRecord(ended).catch(() => void 0),
|
|
14463
|
+
ended
|
|
14464
|
+
);
|
|
14465
|
+
continue;
|
|
14466
|
+
}
|
|
14467
|
+
if (choice !== "pick") continue;
|
|
14468
|
+
const picked = await pickLocator(page, probe, null, ended).catch(() => null);
|
|
14469
|
+
if (picked && !ended?.aborted) await onPick(picked);
|
|
14470
|
+
} catch (error) {
|
|
14471
|
+
if (page.isClosed() || !NAVIGATED.test(String(error)) || ++navigated > MAX_NAVIGATED) throw error;
|
|
14472
|
+
await page.waitForLoadState("domcontentloaded").catch(() => void 0);
|
|
14473
|
+
}
|
|
14114
14474
|
}
|
|
14115
14475
|
} catch {
|
|
14116
14476
|
return "resume";
|
|
14117
14477
|
} finally {
|
|
14118
14478
|
try {
|
|
14479
|
+
if (ended?.aborted) await page.evaluate(takePauseState);
|
|
14119
14480
|
const dispose = shown?.dispose;
|
|
14120
14481
|
const hide = page.hideHighlight;
|
|
14121
14482
|
if (typeof dispose === "function") await dispose.call(shown);
|
|
14122
14483
|
else if (typeof hide === "function") await hide.call(page);
|
|
14123
14484
|
} catch {
|
|
14124
14485
|
}
|
|
14125
|
-
try {
|
|
14126
|
-
testInfo.setTimeout(rearmedTimeout(before, Date.now() - started));
|
|
14127
|
-
} catch {
|
|
14128
|
-
}
|
|
14129
14486
|
}
|
|
14130
14487
|
}
|
|
14131
14488
|
|
|
14489
|
+
// src/internal/capture/record-handoff.ts
|
|
14490
|
+
var fs26 = __toESM(require("fs"));
|
|
14491
|
+
var net = __toESM(require("net"));
|
|
14492
|
+
var path29 = __toESM(require("path"));
|
|
14493
|
+
|
|
14494
|
+
// ../core/src/ide-recorder.ts
|
|
14495
|
+
var IDE_RECORDER_BINDING = "__piwiRecorder";
|
|
14496
|
+
var IDE_DISPATCH_GLOBAL = "__piwiIdeDispatch";
|
|
14497
|
+
var RECORD_CHANNEL_ENV = "PIWI_RECORD_CHANNEL";
|
|
14498
|
+
var MAX_HANDOFF_LINE = 64e6;
|
|
14499
|
+
function parseRecordChannel(value) {
|
|
14500
|
+
const text = value?.trim() ?? "";
|
|
14501
|
+
const hash = text.lastIndexOf("#");
|
|
14502
|
+
if (hash <= 0) return null;
|
|
14503
|
+
const token = text.slice(hash + 1);
|
|
14504
|
+
const path34 = text.slice(0, hash);
|
|
14505
|
+
return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path34.includes("\0") ? { path: path34, token } : null;
|
|
14506
|
+
}
|
|
14507
|
+
function isObject2(value) {
|
|
14508
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14509
|
+
}
|
|
14510
|
+
function parseHandoffToTest(line) {
|
|
14511
|
+
let value;
|
|
14512
|
+
try {
|
|
14513
|
+
value = JSON.parse(line);
|
|
14514
|
+
} catch {
|
|
14515
|
+
return null;
|
|
14516
|
+
}
|
|
14517
|
+
if (!isObject2(value)) return null;
|
|
14518
|
+
switch (value.type) {
|
|
14519
|
+
case "refused":
|
|
14520
|
+
return typeof value.message === "string" ? { type: "refused", message: value.message } : null;
|
|
14521
|
+
case "load":
|
|
14522
|
+
return typeof value.bundle === "string" && value.bundle ? { type: "load", bundle: value.bundle } : null;
|
|
14523
|
+
case "answer":
|
|
14524
|
+
return typeof value.call === "number" ? { type: "answer", call: value.call, value: value.value } : null;
|
|
14525
|
+
case "dispatch":
|
|
14526
|
+
return { type: "dispatch", message: value.message };
|
|
14527
|
+
case "stop":
|
|
14528
|
+
return { type: "stop" };
|
|
14529
|
+
default:
|
|
14530
|
+
return null;
|
|
14531
|
+
}
|
|
14532
|
+
}
|
|
14533
|
+
function lineReader(onLine, onOverflow) {
|
|
14534
|
+
let buffered = "";
|
|
14535
|
+
return (chunk) => {
|
|
14536
|
+
buffered += chunk;
|
|
14537
|
+
for (let end = buffered.indexOf("\n"); end >= 0; end = buffered.indexOf("\n")) {
|
|
14538
|
+
const line = buffered.slice(0, end);
|
|
14539
|
+
buffered = buffered.slice(end + 1);
|
|
14540
|
+
if (line.trim()) onLine(line);
|
|
14541
|
+
}
|
|
14542
|
+
if (buffered.length > MAX_HANDOFF_LINE) {
|
|
14543
|
+
buffered = "";
|
|
14544
|
+
onOverflow();
|
|
14545
|
+
}
|
|
14546
|
+
};
|
|
14547
|
+
}
|
|
14548
|
+
|
|
14549
|
+
// src/internal/capture/record-handoff.ts
|
|
14550
|
+
var START_WAIT_MS = 6e4;
|
|
14551
|
+
var NEW_TAB_NOTICE = "A new tab opened: what you do there is not recorded yet. Record in the first tab.";
|
|
14552
|
+
var relays = /* @__PURE__ */ new WeakMap();
|
|
14553
|
+
function idleAnswer(payload) {
|
|
14554
|
+
const request3 = payload ?? {};
|
|
14555
|
+
if (request3.kind === "local-get") return {};
|
|
14556
|
+
if (request3.kind !== "message") return null;
|
|
14557
|
+
if (request3.message?.type === "piwi-ping") return { ok: true };
|
|
14558
|
+
if (request3.message?.type === "piwi-session-storage") {
|
|
14559
|
+
return request3.message.op === "get" ? { ok: true, items: {} } : { ok: true };
|
|
14560
|
+
}
|
|
14561
|
+
return { ok: false, error: "No recording runs in this page." };
|
|
14562
|
+
}
|
|
14563
|
+
function canRecordFromHere(channel = process.env[RECORD_CHANNEL_ENV]) {
|
|
14564
|
+
return parseRecordChannel(channel) !== null;
|
|
14565
|
+
}
|
|
14566
|
+
function handoffPlace(location, cwd = process.cwd()) {
|
|
14567
|
+
const m = /^(.+):(\d+):\d+$/.exec(location);
|
|
14568
|
+
if (!m) return null;
|
|
14569
|
+
return { file: path29.resolve(cwd, m[1]), line: Number(m[2]) };
|
|
14570
|
+
}
|
|
14571
|
+
async function recordFromHere(options) {
|
|
14572
|
+
const channel = parseRecordChannel(options.channel ?? process.env[RECORD_CHANNEL_ENV]);
|
|
14573
|
+
const place = handoffPlace(options.location);
|
|
14574
|
+
if (!channel || !place) return "this run names no editor to record into.";
|
|
14575
|
+
const { page, testInfo, ended } = options;
|
|
14576
|
+
if (ended?.aborted) return "the test ended.";
|
|
14577
|
+
const use = testInfo.project?.use ?? {};
|
|
14578
|
+
const context = page.context();
|
|
14579
|
+
return new Promise((resolve11) => {
|
|
14580
|
+
const socket = net.connect(channel.path);
|
|
14581
|
+
socket.setEncoding("utf8");
|
|
14582
|
+
const answers = /* @__PURE__ */ new Map();
|
|
14583
|
+
let calls = 0;
|
|
14584
|
+
let attached = false;
|
|
14585
|
+
let recording = false;
|
|
14586
|
+
let settled = false;
|
|
14587
|
+
let initScript;
|
|
14588
|
+
const send = (message) => {
|
|
14589
|
+
if (!socket.destroyed) socket.write(`${JSON.stringify(message)}
|
|
14590
|
+
`);
|
|
14591
|
+
};
|
|
14592
|
+
const onPage = () => send({ type: "notice", message: NEW_TAB_NOTICE });
|
|
14593
|
+
const onGone = () => {
|
|
14594
|
+
if (attached) send({ type: "closed" });
|
|
14595
|
+
finish("the page closed.");
|
|
14596
|
+
};
|
|
14597
|
+
const onEnded = () => {
|
|
14598
|
+
try {
|
|
14599
|
+
if (recording) {
|
|
14600
|
+
void internalCall(
|
|
14601
|
+
page,
|
|
14602
|
+
() => page.evaluate(
|
|
14603
|
+
([name, value]) => {
|
|
14604
|
+
const dispatch = globalThis[name];
|
|
14605
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
14606
|
+
},
|
|
14607
|
+
[IDE_DISPATCH_GLOBAL, { type: "piwi-recording-stopped" }]
|
|
14608
|
+
)
|
|
14609
|
+
).catch(() => void 0);
|
|
14610
|
+
}
|
|
14611
|
+
if (attached) send({ type: "closed" });
|
|
14612
|
+
finish("the test ended.");
|
|
14613
|
+
} catch {
|
|
14614
|
+
}
|
|
14615
|
+
};
|
|
14616
|
+
page.once("close", onGone);
|
|
14617
|
+
page.once("crash", onGone);
|
|
14618
|
+
ended?.addEventListener("abort", onEnded, { once: true });
|
|
14619
|
+
const startWait = setTimeout(() => finish("the editor did not start the recording."), START_WAIT_MS);
|
|
14620
|
+
const release = () => {
|
|
14621
|
+
const relay = relays.get(context);
|
|
14622
|
+
if (relay && relay.recording === relayed) relay.recording = null;
|
|
14623
|
+
void Promise.resolve(initScript?.dispose?.()).catch(() => void 0);
|
|
14624
|
+
initScript = void 0;
|
|
14625
|
+
};
|
|
14626
|
+
const finish = (said) => {
|
|
14627
|
+
if (settled) return;
|
|
14628
|
+
settled = true;
|
|
14629
|
+
clearTimeout(startWait);
|
|
14630
|
+
release();
|
|
14631
|
+
for (const [call, answer] of answers) {
|
|
14632
|
+
answers.delete(call);
|
|
14633
|
+
answer(null);
|
|
14634
|
+
}
|
|
14635
|
+
context.off("page", onPage);
|
|
14636
|
+
page.off("close", onGone);
|
|
14637
|
+
page.off("crash", onGone);
|
|
14638
|
+
ended?.removeEventListener("abort", onEnded);
|
|
14639
|
+
socket.end();
|
|
14640
|
+
resolve11(recording ? null : said);
|
|
14641
|
+
};
|
|
14642
|
+
const relayed = (source, payload) => new Promise((answer) => {
|
|
14643
|
+
const call = ++calls;
|
|
14644
|
+
answers.set(call, answer);
|
|
14645
|
+
const outside = source.page !== page || source.frame !== page.mainFrame();
|
|
14646
|
+
send({ type: "binding", call, outside, payload });
|
|
14647
|
+
});
|
|
14648
|
+
const load = async (bundle) => {
|
|
14649
|
+
if (!/^https?:/i.test(page.url())) {
|
|
14650
|
+
const why = `the recorder records http(s) pages only, and this page is ${page.url() || "blank"}.`;
|
|
14651
|
+
send({ type: "notice", message: `Nothing can be recorded here: ${why}` });
|
|
14652
|
+
finish(why);
|
|
14653
|
+
return;
|
|
14654
|
+
}
|
|
14655
|
+
let relay = relays.get(context);
|
|
14656
|
+
if (!relay) {
|
|
14657
|
+
const created = { recording: null, lingering: false };
|
|
14658
|
+
await context.exposeBinding(
|
|
14659
|
+
IDE_RECORDER_BINDING,
|
|
14660
|
+
(source, payload) => created.recording ? created.recording(source, payload) : idleAnswer(payload)
|
|
14661
|
+
);
|
|
14662
|
+
relays.set(context, created);
|
|
14663
|
+
relay = created;
|
|
14664
|
+
}
|
|
14665
|
+
if (settled) return;
|
|
14666
|
+
relay.recording = relayed;
|
|
14667
|
+
if (!relay.lingering) {
|
|
14668
|
+
initScript = await context.addInitScript({ path: bundle });
|
|
14669
|
+
if (typeof initScript?.dispose !== "function") relay.lingering = true;
|
|
14670
|
+
}
|
|
14671
|
+
if (settled) return release();
|
|
14672
|
+
await page.evaluate(fs26.readFileSync(bundle, "utf-8"));
|
|
14673
|
+
if (settled) return release();
|
|
14674
|
+
recording = true;
|
|
14675
|
+
clearTimeout(startWait);
|
|
14676
|
+
context.on("page", onPage);
|
|
14677
|
+
send({ type: "loaded" });
|
|
14678
|
+
};
|
|
14679
|
+
socket.on("connect", () => {
|
|
14680
|
+
attached = true;
|
|
14681
|
+
send({
|
|
14682
|
+
type: "attach",
|
|
14683
|
+
token: channel.token,
|
|
14684
|
+
file: place.file,
|
|
14685
|
+
line: place.line,
|
|
14686
|
+
reason: options.reason,
|
|
14687
|
+
testIdAttribute: typeof use.testIdAttribute === "string" ? use.testIdAttribute : null,
|
|
14688
|
+
baseURL: typeof use.baseURL === "string" ? use.baseURL : null
|
|
14689
|
+
});
|
|
14690
|
+
});
|
|
14691
|
+
socket.on(
|
|
14692
|
+
"data",
|
|
14693
|
+
lineReader(
|
|
14694
|
+
(line) => {
|
|
14695
|
+
const message = parseHandoffToTest(line);
|
|
14696
|
+
if (!message || settled) return;
|
|
14697
|
+
switch (message.type) {
|
|
14698
|
+
case "refused":
|
|
14699
|
+
return finish(message.message);
|
|
14700
|
+
case "load":
|
|
14701
|
+
return void load(message.bundle).catch(
|
|
14702
|
+
(error) => finish(`the recorder could not be loaded: ${error instanceof Error ? error.message : String(error)}`)
|
|
14703
|
+
);
|
|
14704
|
+
case "answer": {
|
|
14705
|
+
const answer = answers.get(message.call);
|
|
14706
|
+
answers.delete(message.call);
|
|
14707
|
+
return answer?.(message.value);
|
|
14708
|
+
}
|
|
14709
|
+
case "dispatch":
|
|
14710
|
+
return void page.evaluate(
|
|
14711
|
+
([name, value]) => {
|
|
14712
|
+
const dispatch = globalThis[name];
|
|
14713
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
14714
|
+
},
|
|
14715
|
+
[IDE_DISPATCH_GLOBAL, message.message]
|
|
14716
|
+
).catch(() => void 0);
|
|
14717
|
+
case "stop":
|
|
14718
|
+
return finish(null);
|
|
14719
|
+
}
|
|
14720
|
+
},
|
|
14721
|
+
() => finish("the editor sent a message too long to read.")
|
|
14722
|
+
)
|
|
14723
|
+
);
|
|
14724
|
+
socket.on("error", (error) => finish(`the editor could not be reached: ${error.message}`));
|
|
14725
|
+
socket.on("close", () => finish("the editor closed the recording."));
|
|
14726
|
+
});
|
|
14727
|
+
}
|
|
14728
|
+
|
|
14132
14729
|
// src/internal/capture/page-interaction.ts
|
|
14133
14730
|
function readPageInteraction() {
|
|
14134
14731
|
const doc = globalThis.document;
|
|
@@ -14424,6 +15021,13 @@ function createSink() {
|
|
|
14424
15021
|
pickOffered: false,
|
|
14425
15022
|
userPick: null,
|
|
14426
15023
|
pause: null,
|
|
15024
|
+
locatorCalls: /* @__PURE__ */ new Map(),
|
|
15025
|
+
stops: [],
|
|
15026
|
+
shownStops: /* @__PURE__ */ new Map(),
|
|
15027
|
+
released: [],
|
|
15028
|
+
releasedRequests: /* @__PURE__ */ new Set(),
|
|
15029
|
+
onReleasedRequestsEnded: null,
|
|
15030
|
+
releasedQuietSince: 0,
|
|
14427
15031
|
locatorPages: new LocatorPageLog(),
|
|
14428
15032
|
codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map() },
|
|
14429
15033
|
serverCoverage: /* @__PURE__ */ new Map(),
|
|
@@ -14449,7 +15053,7 @@ function maybeStartCodeReach(page, alreadyOpen = false) {
|
|
|
14449
15053
|
async function startCodeReachOnOpenPages() {
|
|
14450
15054
|
if (!browserCoverageInWorker() || OPEN_PAGES.size === 0) return;
|
|
14451
15055
|
const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
|
|
14452
|
-
await Promise.race([started, new Promise((
|
|
15056
|
+
await Promise.race([started, new Promise((resolve11) => setTimeout(resolve11, CODE_REACH_START_WAIT_MS).unref())]);
|
|
14453
15057
|
}
|
|
14454
15058
|
function configuredCodeReachRoots() {
|
|
14455
15059
|
const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
|
|
@@ -14463,7 +15067,7 @@ function configuredCodeReachRoots() {
|
|
|
14463
15067
|
}
|
|
14464
15068
|
} catch {
|
|
14465
15069
|
}
|
|
14466
|
-
return raw.split(
|
|
15070
|
+
return raw.split(path30.delimiter).filter(Boolean);
|
|
14467
15071
|
}
|
|
14468
15072
|
async function stopSinkCodeReach(sink, closing) {
|
|
14469
15073
|
const stopping = [];
|
|
@@ -14512,8 +15116,8 @@ async function drainPendingProbes(capMs) {
|
|
|
14512
15116
|
let cap;
|
|
14513
15117
|
await Promise.race([
|
|
14514
15118
|
Promise.allSettled(PENDING_PROBES),
|
|
14515
|
-
new Promise((
|
|
14516
|
-
cap = setTimeout(
|
|
15119
|
+
new Promise((resolve11) => {
|
|
15120
|
+
cap = setTimeout(resolve11, capMs);
|
|
14517
15121
|
})
|
|
14518
15122
|
]);
|
|
14519
15123
|
clearTimeout(cap);
|
|
@@ -14553,10 +15157,10 @@ async function readWebVitals(page) {
|
|
|
14553
15157
|
const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
|
|
14554
15158
|
paint[key] = Math.round(entry.startTime);
|
|
14555
15159
|
}
|
|
14556
|
-
const readBuffered = (type, extra) => new Promise((
|
|
15160
|
+
const readBuffered = (type, extra) => new Promise((resolve11) => {
|
|
14557
15161
|
try {
|
|
14558
15162
|
const PO = globalThis.PerformanceObserver;
|
|
14559
|
-
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return
|
|
15163
|
+
if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve11(null);
|
|
14560
15164
|
const out = [];
|
|
14561
15165
|
const po = new PO((list) => out.push(...list.getEntries()));
|
|
14562
15166
|
po.observe({ type, buffered: true, ...extra });
|
|
@@ -14566,10 +15170,10 @@ async function readWebVitals(page) {
|
|
|
14566
15170
|
po.disconnect();
|
|
14567
15171
|
} catch {
|
|
14568
15172
|
}
|
|
14569
|
-
|
|
15173
|
+
resolve11(out);
|
|
14570
15174
|
}, 0);
|
|
14571
15175
|
} catch {
|
|
14572
|
-
|
|
15176
|
+
resolve11(null);
|
|
14573
15177
|
}
|
|
14574
15178
|
});
|
|
14575
15179
|
const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
|
|
@@ -14684,12 +15288,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
|
|
|
14684
15288
|
var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
|
|
14685
15289
|
var MAX_PAGE_INVENTORY_WINDOWS = 64;
|
|
14686
15290
|
function withTimeout(promise, ms) {
|
|
14687
|
-
return new Promise((
|
|
15291
|
+
return new Promise((resolve11, reject) => {
|
|
14688
15292
|
const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
|
|
14689
15293
|
promise.then(
|
|
14690
15294
|
(value) => {
|
|
14691
15295
|
clearTimeout(timer);
|
|
14692
|
-
|
|
15296
|
+
resolve11(value);
|
|
14693
15297
|
},
|
|
14694
15298
|
(error) => {
|
|
14695
15299
|
clearTimeout(timer);
|
|
@@ -14804,6 +15408,7 @@ async function maybeOpenPicker(sink, closing) {
|
|
|
14804
15408
|
var INSTRUMENTED_PAGES = /* @__PURE__ */ new WeakSet();
|
|
14805
15409
|
var OPEN_PAGES = /* @__PURE__ */ new Set();
|
|
14806
15410
|
var INSTRUMENTED_CONTEXTS = /* @__PURE__ */ new WeakSet();
|
|
15411
|
+
var OPEN_CONTEXTS = /* @__PURE__ */ new Set();
|
|
14807
15412
|
var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
|
|
14808
15413
|
var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
|
|
14809
15414
|
var ACTION_METHOD_SET = new Set(ACTION_METHODS);
|
|
@@ -14924,8 +15529,20 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
|
|
|
14924
15529
|
} catch {
|
|
14925
15530
|
}
|
|
14926
15531
|
}
|
|
14927
|
-
function
|
|
14928
|
-
|
|
15532
|
+
function knowLocatorCall(sink, page, target, location, origin) {
|
|
15533
|
+
if (!sink?.pause || !location) return null;
|
|
15534
|
+
let settle = () => {
|
|
15535
|
+
};
|
|
15536
|
+
const settled = new Promise((resolve11) => settle = resolve11);
|
|
15537
|
+
const call = { page, target, location, origin, picked: false, settled, settle };
|
|
15538
|
+
sink.locatorCalls.set(callSiteLine(location), call);
|
|
15539
|
+
return call;
|
|
15540
|
+
}
|
|
15541
|
+
function forgetLocatorCall(sink, call) {
|
|
15542
|
+
if (!sink || !call) return;
|
|
15543
|
+
call.settle();
|
|
15544
|
+
const key = callSiteLine(call.location);
|
|
15545
|
+
if (sink.locatorCalls.get(key) === call) sink.locatorCalls.delete(key);
|
|
14929
15546
|
}
|
|
14930
15547
|
function locatorSource(target, method, args) {
|
|
14931
15548
|
try {
|
|
@@ -14935,42 +15552,220 @@ function locatorSource(target, method, args) {
|
|
|
14935
15552
|
}
|
|
14936
15553
|
return renderFailing({ method, args });
|
|
14937
15554
|
}
|
|
14938
|
-
|
|
14939
|
-
|
|
15555
|
+
var PAGE_PAUSES = /* @__PURE__ */ new WeakMap();
|
|
15556
|
+
var PLAYWRIGHT_PAUSES = /* @__PURE__ */ new WeakMap();
|
|
15557
|
+
async function armBreakpoints(contexts) {
|
|
15558
|
+
const state = currentSink?.pause;
|
|
15559
|
+
if (!state || state.finished || state.set.size === 0) return;
|
|
15560
|
+
await Promise.all(
|
|
15561
|
+
[...contexts].map(
|
|
15562
|
+
(context) => context && watchStops(context, onDebuggerStop) ? steerDebugger(context, "pause") : void 0
|
|
15563
|
+
)
|
|
15564
|
+
);
|
|
15565
|
+
}
|
|
15566
|
+
function onDebuggerStop(context) {
|
|
15567
|
+
const details = contextDebugger(context)?.pausedDetails();
|
|
15568
|
+
if (!details || PLAYWRIGHT_PAUSES.has(context)) return;
|
|
15569
|
+
const sink = currentSink;
|
|
15570
|
+
const state = sink?.pause ?? null;
|
|
15571
|
+
void (async () => {
|
|
15572
|
+
let how = "resume";
|
|
15573
|
+
try {
|
|
15574
|
+
if (!sink || !state) return;
|
|
15575
|
+
const callSite = stopSite(sink, details.location);
|
|
15576
|
+
const pagePause = PAGE_PAUSES.get(context);
|
|
15577
|
+
const decided = pagePause ? { show: "page-pause" } : atStop(state, callSite);
|
|
15578
|
+
if ("go" in decided) {
|
|
15579
|
+
how = decided.go;
|
|
15580
|
+
return;
|
|
15581
|
+
}
|
|
15582
|
+
how = goOn(state, await showStop(sink, context, details.title, callSite ?? pagePause, decided.show));
|
|
15583
|
+
} catch {
|
|
15584
|
+
if (state) how = goOn(state);
|
|
15585
|
+
} finally {
|
|
15586
|
+
await steerDebugger(context, how);
|
|
15587
|
+
}
|
|
15588
|
+
})();
|
|
15589
|
+
}
|
|
15590
|
+
function stopSite(sink, location) {
|
|
15591
|
+
if (path30.dirname(path30.resolve(location.file)) !== __dirname) return stopCallSite(location);
|
|
15592
|
+
return inFlightCallSite([...sink.locatorCalls.values()].map((call) => call.location));
|
|
15593
|
+
}
|
|
15594
|
+
function stopPage(sink, context, call) {
|
|
15595
|
+
for (const page of [call?.page, sink.lastActivePage]) {
|
|
15596
|
+
if (page && !isPageClosed(page) && pageContext(page) === context) return page;
|
|
15597
|
+
}
|
|
15598
|
+
const open = context.pages().filter((page) => !isPageClosed(page));
|
|
15599
|
+
return open[open.length - 1] ?? null;
|
|
15600
|
+
}
|
|
15601
|
+
async function showStop(sink, context, title, location, reason) {
|
|
14940
15602
|
const testInfo = sink.testInfo;
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
15603
|
+
const call = sink.locatorCalls.get(callSiteLine(location));
|
|
15604
|
+
const page = stopPage(sink, context, call);
|
|
15605
|
+
if (!testInfo || !page) return void 0;
|
|
15606
|
+
const onPick = async (picked) => {
|
|
15607
|
+
if (call) {
|
|
15608
|
+
call.picked = true;
|
|
15609
|
+
const { method, args } = call.origin;
|
|
15610
|
+
const rendered = locatorSource(call.target, method, args);
|
|
15611
|
+
applyPickToSnapshots(sink.capturedLocators, {
|
|
15612
|
+
failing: { method, args, rendered, location: call.location },
|
|
14948
15613
|
...picked
|
|
14949
|
-
};
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
15614
|
+
});
|
|
15615
|
+
sink.probedLocations.add(call.location);
|
|
15616
|
+
}
|
|
15617
|
+
console.log(`[piwi] Locator picked at ${callSiteLine(location)}: ${picked.picked.locator}`);
|
|
15618
|
+
await postPickToEditor(process.env[PIWI_LOCAL_DEBUG_ENV.editorSend], pickPayload(picked.picked.locator, location));
|
|
15619
|
+
};
|
|
15620
|
+
const record = { title, location, reason, startedAt: Date.now(), duration: 0 };
|
|
15621
|
+
sink.stops.push(record);
|
|
15622
|
+
const end = new AbortController();
|
|
15623
|
+
const shown = withTestTimeoutHeld(
|
|
15624
|
+
testInfo,
|
|
15625
|
+
() => internalCall(
|
|
14960
15626
|
page,
|
|
14961
15627
|
() => pauseHere(
|
|
14962
15628
|
page,
|
|
14963
|
-
|
|
14964
|
-
|
|
15629
|
+
{
|
|
15630
|
+
location,
|
|
15631
|
+
call: title,
|
|
15632
|
+
attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
|
|
15633
|
+
target: call?.page === page ? call.target : null
|
|
15634
|
+
},
|
|
14965
15635
|
{ fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
|
|
14966
|
-
onPick
|
|
15636
|
+
onPick,
|
|
15637
|
+
recorderAt(page, testInfo, location, reason === "page-pause" ? "pause" : "breakpoint"),
|
|
15638
|
+
end.signal
|
|
14967
15639
|
)
|
|
14968
|
-
)
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
15640
|
+
)
|
|
15641
|
+
).finally(() => {
|
|
15642
|
+
if (!end.signal.aborted) record.duration = Date.now() - record.startedAt;
|
|
15643
|
+
else if (end.signal.reason === "ended") releaseCall(sink, call?.settled ?? Promise.resolve());
|
|
15644
|
+
sink.shownStops.delete(end);
|
|
15645
|
+
});
|
|
15646
|
+
const heldAt = testTimeSlot(testInfo)?.elapsed ?? null;
|
|
15647
|
+
sink.shownStops.set(end, { page, record, done: shown.catch(() => void 0), heldAt });
|
|
15648
|
+
const answer = await shown;
|
|
15649
|
+
if (!end.signal.aborted) return answer;
|
|
15650
|
+
return end.signal.reason === "closed" ? "resume" : "finish";
|
|
15651
|
+
}
|
|
15652
|
+
var END_STOP_MS = 2e3;
|
|
15653
|
+
var RELEASED_CALL_MS = 2e3;
|
|
15654
|
+
var RELEASED_QUIET_MS = 200;
|
|
15655
|
+
async function endShownStops(sink, why, closing) {
|
|
15656
|
+
const ending = [];
|
|
15657
|
+
let heldAt = Infinity;
|
|
15658
|
+
for (const [end, { page, record, done, heldAt: stopHeldAt }] of sink.shownStops) {
|
|
15659
|
+
if (closing && closing.page !== page && (!closing.context || pageContext(page) !== closing.context)) continue;
|
|
15660
|
+
if (!end.signal.aborted) {
|
|
15661
|
+
record.duration = Date.now() - record.startedAt;
|
|
15662
|
+
end.abort(why);
|
|
15663
|
+
if (why === "ended" && stopHeldAt !== null) heldAt = Math.min(heldAt, stopHeldAt);
|
|
15664
|
+
}
|
|
15665
|
+
ending.push(done);
|
|
15666
|
+
}
|
|
15667
|
+
if (sink.testInfo && heldAt !== Infinity) restoreTestTime(sink.testInfo, heldAt);
|
|
15668
|
+
await settledWithin(ending, END_STOP_MS);
|
|
15669
|
+
}
|
|
15670
|
+
function releaseCall(sink, settled) {
|
|
15671
|
+
sink.releasedQuietSince = Date.now();
|
|
15672
|
+
sink.released.push(settled.then(() => void (sink.releasedQuietSince = Date.now())));
|
|
15673
|
+
}
|
|
15674
|
+
function releasedRequestsEnded(sink) {
|
|
15675
|
+
if (sink.releasedRequests.size === 0) return Promise.resolve();
|
|
15676
|
+
return new Promise((resolve11) => sink.onReleasedRequestsEnded = resolve11);
|
|
15677
|
+
}
|
|
15678
|
+
async function awaitReleasedCalls(sink) {
|
|
15679
|
+
if (sink.released.length === 0) return;
|
|
15680
|
+
const deadline = Date.now() + RELEASED_CALL_MS;
|
|
15681
|
+
await settledWithin(sink.released, RELEASED_CALL_MS);
|
|
15682
|
+
for (let now = Date.now(); now < deadline; now = Date.now()) {
|
|
15683
|
+
if (sink.releasedRequests.size > 0) {
|
|
15684
|
+
await settledWithin([releasedRequestsEnded(sink)], deadline - now);
|
|
15685
|
+
continue;
|
|
15686
|
+
}
|
|
15687
|
+
const quiet = now - sink.releasedQuietSince;
|
|
15688
|
+
if (quiet >= RELEASED_QUIET_MS) return;
|
|
15689
|
+
await new Promise((resolve11) => setTimeout(resolve11, Math.min(RELEASED_QUIET_MS - quiet, deadline - now)));
|
|
14972
15690
|
}
|
|
14973
15691
|
}
|
|
15692
|
+
async function settledWithin(pending, ms) {
|
|
15693
|
+
if (pending.length === 0) return;
|
|
15694
|
+
let timer;
|
|
15695
|
+
await Promise.race([Promise.allSettled(pending), new Promise((resolve11) => timer = setTimeout(resolve11, ms))]);
|
|
15696
|
+
clearTimeout(timer);
|
|
15697
|
+
}
|
|
15698
|
+
function endStopsWithTestFunction(testInfo, sink) {
|
|
15699
|
+
const callbacks = testInfo._onDidFinishTestFunctionCallbacks;
|
|
15700
|
+
if (!(callbacks instanceof Set)) return;
|
|
15701
|
+
const theirs = [...callbacks];
|
|
15702
|
+
callbacks.clear();
|
|
15703
|
+
callbacks.add(() => endShownStops(sink, "ended"));
|
|
15704
|
+
for (const callback of theirs) callbacks.add(callback);
|
|
15705
|
+
}
|
|
15706
|
+
function recorderAt(page, testInfo, location, reason) {
|
|
15707
|
+
if (!canRecordFromHere()) return void 0;
|
|
15708
|
+
return async (ended) => {
|
|
15709
|
+
console.log(`[piwi] Recording from ${callSiteLine(location)}: the editor writes the steps.`);
|
|
15710
|
+
const why = await recordFromHere({ page, testInfo, location, reason, ended });
|
|
15711
|
+
if (ended?.aborted) console.log("[piwi] The recording ended with the test.");
|
|
15712
|
+
else
|
|
15713
|
+
console.log(
|
|
15714
|
+
why ? `[piwi] Nothing was recorded: ${why}` : "[piwi] The recording ended: the test is paused again."
|
|
15715
|
+
);
|
|
15716
|
+
};
|
|
15717
|
+
}
|
|
15718
|
+
async function pauseInPlace(page, playwrightPause, location) {
|
|
15719
|
+
const sink = currentSink;
|
|
15720
|
+
const testInfo = sink?.testInfo;
|
|
15721
|
+
const context = pageContext(page);
|
|
15722
|
+
if (!sink || !testInfo || !location || !context || !contextDebugger(context)) {
|
|
15723
|
+
return leaveToPlaywright(context, playwrightPause);
|
|
15724
|
+
}
|
|
15725
|
+
if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo)))) {
|
|
15726
|
+
return leaveToPlaywright(context, playwrightPause);
|
|
15727
|
+
}
|
|
15728
|
+
sink.pause ??= { set: NO_BREAKPOINTS, step: false, finished: false };
|
|
15729
|
+
watchStops(context, onDebuggerStop);
|
|
15730
|
+
await steerDebugger(context, "pause");
|
|
15731
|
+
PAGE_PAUSES.set(context, location);
|
|
15732
|
+
try {
|
|
15733
|
+
await playwrightPause({ __testHookKeepTestTimeout: true });
|
|
15734
|
+
} finally {
|
|
15735
|
+
PAGE_PAUSES.delete(context);
|
|
15736
|
+
}
|
|
15737
|
+
}
|
|
15738
|
+
async function leaveToPlaywright(context, playwrightPause) {
|
|
15739
|
+
if (context) PLAYWRIGHT_PAUSES.set(context, (PLAYWRIGHT_PAUSES.get(context) ?? 0) + 1);
|
|
15740
|
+
try {
|
|
15741
|
+
await withInspector(() => playwrightPause());
|
|
15742
|
+
} finally {
|
|
15743
|
+
if (context) {
|
|
15744
|
+
const left = (PLAYWRIGHT_PAUSES.get(context) ?? 1) - 1;
|
|
15745
|
+
if (left > 0) PLAYWRIGHT_PAUSES.set(context, left);
|
|
15746
|
+
else PLAYWRIGHT_PAUSES.delete(context);
|
|
15747
|
+
}
|
|
15748
|
+
}
|
|
15749
|
+
}
|
|
15750
|
+
var PLAYWRIGHT_PAUSE = /* @__PURE__ */ Symbol.for("@piwitests/reporter:playwright-pause");
|
|
15751
|
+
function playwrightPauseOf(page) {
|
|
15752
|
+
const current = page.pause;
|
|
15753
|
+
if (typeof current !== "function") return null;
|
|
15754
|
+
const own = current[PLAYWRIGHT_PAUSE] ?? current;
|
|
15755
|
+
const proto = Object.getPrototypeOf(page);
|
|
15756
|
+
const theirs = proto?.pause;
|
|
15757
|
+
if (proto && typeof theirs === "function") {
|
|
15758
|
+
if (!theirs[PLAYWRIGHT_PAUSE]) {
|
|
15759
|
+
const wrapped = function(options) {
|
|
15760
|
+
return leaveToPlaywright(pageContext(this), () => theirs.call(this, options));
|
|
15761
|
+
};
|
|
15762
|
+
wrapped[PLAYWRIGHT_PAUSE] = theirs;
|
|
15763
|
+
proto.pause = wrapped;
|
|
15764
|
+
}
|
|
15765
|
+
markPausesWrapped();
|
|
15766
|
+
}
|
|
15767
|
+
return (options) => own.call(page, options);
|
|
15768
|
+
}
|
|
14974
15769
|
function wrapLocator(page, locator, originMethod, originArgs) {
|
|
14975
15770
|
return new Proxy(locator, {
|
|
14976
15771
|
get(target, prop) {
|
|
@@ -14992,36 +15787,35 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
14992
15787
|
const isNot = Boolean(callArgs[1]?.isNot);
|
|
14993
15788
|
const callerLocation = sink ? captureCallerLocation() : null;
|
|
14994
15789
|
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
14995
|
-
|
|
14996
|
-
|
|
15790
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, { method: originMethod, args: originArgs });
|
|
15791
|
+
try {
|
|
15792
|
+
if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
|
|
15793
|
+
return await callMethod(target, prop, callArgs);
|
|
15794
|
+
}
|
|
15795
|
+
sink.lastActivePage = page;
|
|
15796
|
+
const used = {
|
|
14997
15797
|
method: originMethod,
|
|
14998
|
-
args: originArgs
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
if (matches === true && !alreadyProbed) {
|
|
15019
|
-
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
15020
|
-
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
15021
|
-
} else if (matches === false) {
|
|
15022
|
-
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
15798
|
+
args: originArgs,
|
|
15799
|
+
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
15800
|
+
};
|
|
15801
|
+
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
15802
|
+
let seq = -1;
|
|
15803
|
+
if (!alreadyProbed) {
|
|
15804
|
+
seq = sink.capturedLocators.length;
|
|
15805
|
+
sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
|
|
15806
|
+
}
|
|
15807
|
+
const result = await callMethod(target, prop, callArgs);
|
|
15808
|
+
const matches = result?.matches;
|
|
15809
|
+
if (matches === true && !alreadyProbed && !call?.picked) {
|
|
15810
|
+
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
15811
|
+
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
15812
|
+
} else if (matches === false) {
|
|
15813
|
+
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
15814
|
+
}
|
|
15815
|
+
return result;
|
|
15816
|
+
} finally {
|
|
15817
|
+
forgetLocatorCall(sink, call);
|
|
15023
15818
|
}
|
|
15024
|
-
return result;
|
|
15025
15819
|
};
|
|
15026
15820
|
}
|
|
15027
15821
|
if (!ACTION_METHOD_SET.has(prop)) return original;
|
|
@@ -15036,9 +15830,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
15036
15830
|
args: originArgs,
|
|
15037
15831
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
15038
15832
|
};
|
|
15039
|
-
|
|
15040
|
-
await pauseBefore(sink, page, target, callerLocation, String(prop), used);
|
|
15041
|
-
}
|
|
15833
|
+
const call = knowLocatorCall(sink, page, target, callerLocation, used);
|
|
15042
15834
|
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
15043
15835
|
let seq = -1;
|
|
15044
15836
|
if (!alreadyProbed) {
|
|
@@ -15056,8 +15848,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
15056
15848
|
} catch (error) {
|
|
15057
15849
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
15058
15850
|
throw error;
|
|
15851
|
+
} finally {
|
|
15852
|
+
forgetLocatorCall(sink, call);
|
|
15059
15853
|
}
|
|
15060
|
-
if (!alreadyProbed) {
|
|
15854
|
+
if (!alreadyProbed && !call?.picked) {
|
|
15061
15855
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
15062
15856
|
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
15063
15857
|
}
|
|
@@ -15110,12 +15904,17 @@ function instrumentPage(page) {
|
|
|
15110
15904
|
}
|
|
15111
15905
|
const ctx = pageContext(page);
|
|
15112
15906
|
if (ctx) instrumentContext(ctx);
|
|
15907
|
+
const playwrightPause = playwrightPauseOf(page);
|
|
15908
|
+
if (playwrightPause) {
|
|
15909
|
+
page.pause = () => pauseInPlace(page, playwrightPause, captureCallerLocation());
|
|
15910
|
+
}
|
|
15113
15911
|
if (typeof page.close === "function") {
|
|
15114
15912
|
const originalClose = page.close.bind(page);
|
|
15115
15913
|
page.close = async (...args) => {
|
|
15116
15914
|
await drainPendingProbes(1e3);
|
|
15117
15915
|
const sink = currentSink;
|
|
15118
15916
|
if (sink) {
|
|
15917
|
+
await endShownStops(sink, "closed", { page });
|
|
15119
15918
|
await stashPageState(sink, { page });
|
|
15120
15919
|
await maybeOpenPicker(sink, { page });
|
|
15121
15920
|
await stopSinkCodeReach(sink, { page });
|
|
@@ -15204,8 +16003,18 @@ function instrumentPage(page) {
|
|
|
15204
16003
|
} catch {
|
|
15205
16004
|
}
|
|
15206
16005
|
});
|
|
16006
|
+
page.on("request", (request3) => {
|
|
16007
|
+
const sink = currentSink;
|
|
16008
|
+
if (!sink || sink.released.length === 0) return;
|
|
16009
|
+
sink.releasedRequests.add(request3);
|
|
16010
|
+
sink.releasedQuietSince = Date.now();
|
|
16011
|
+
});
|
|
15207
16012
|
const onRequestEnd = (request3, failed) => {
|
|
15208
16013
|
const sink = currentSink;
|
|
16014
|
+
if (sink?.releasedRequests.delete(request3)) {
|
|
16015
|
+
sink.releasedQuietSince = Date.now();
|
|
16016
|
+
if (sink.releasedRequests.size === 0) sink.onReleasedRequestsEnded?.();
|
|
16017
|
+
}
|
|
15209
16018
|
if (!sink || RECORDED_OPEN.has(request3)) return;
|
|
15210
16019
|
sink.lastActivePage = page;
|
|
15211
16020
|
const response = sink.openResponses.get(request3) ?? null;
|
|
@@ -15311,6 +16120,10 @@ async function awaitFlakeConditions() {
|
|
|
15311
16120
|
function instrumentContext(context) {
|
|
15312
16121
|
if (!context || INSTRUMENTED_CONTEXTS.has(context)) return;
|
|
15313
16122
|
INSTRUMENTED_CONTEXTS.add(context);
|
|
16123
|
+
if (typeof context.once === "function") {
|
|
16124
|
+
OPEN_CONTEXTS.add(context);
|
|
16125
|
+
context.once("close", () => OPEN_CONTEXTS.delete(context));
|
|
16126
|
+
}
|
|
15314
16127
|
observeOwnApiRequests(context);
|
|
15315
16128
|
if (process.env.PIWI_CAPTURE_LOCATORS !== "false" && typeof context.addInitScript === "function") {
|
|
15316
16129
|
void Promise.resolve(context.addInitScript({ content: PROBE_INIT_SCRIPT })).catch(() => {
|
|
@@ -15330,6 +16143,7 @@ function instrumentContext(context) {
|
|
|
15330
16143
|
await drainPendingProbes(1e3);
|
|
15331
16144
|
const sink = currentSink;
|
|
15332
16145
|
if (sink) {
|
|
16146
|
+
await endShownStops(sink, "closed", { context });
|
|
15333
16147
|
await stashPageState(sink, { context });
|
|
15334
16148
|
await maybeOpenPicker(sink, { context });
|
|
15335
16149
|
await stopSinkCodeReach(sink, { context });
|
|
@@ -15346,6 +16160,7 @@ function patchBrowser(browser) {
|
|
|
15346
16160
|
browser.newPage = async (...args) => {
|
|
15347
16161
|
const page = await originalNewPage(...args);
|
|
15348
16162
|
instrumentPage(page);
|
|
16163
|
+
await armBreakpoints([pageContext(page)]);
|
|
15349
16164
|
await awaitFlakeConditions();
|
|
15350
16165
|
await maybeStartCodeReach(page);
|
|
15351
16166
|
return page;
|
|
@@ -15354,6 +16169,7 @@ function patchBrowser(browser) {
|
|
|
15354
16169
|
browser.newContext = async (...args) => {
|
|
15355
16170
|
const context = await originalNewContext(...args);
|
|
15356
16171
|
instrumentContext(context);
|
|
16172
|
+
await armBreakpoints([context]);
|
|
15357
16173
|
return context;
|
|
15358
16174
|
};
|
|
15359
16175
|
if (typeof browser.close === "function") {
|
|
@@ -15405,14 +16221,20 @@ async function flushSink(sink, testInfo) {
|
|
|
15405
16221
|
let drainDeadline;
|
|
15406
16222
|
await Promise.race([
|
|
15407
16223
|
Promise.allSettled(sink.capturePromises),
|
|
15408
|
-
new Promise((
|
|
15409
|
-
drainDeadline = setTimeout(
|
|
16224
|
+
new Promise((resolve11) => {
|
|
16225
|
+
drainDeadline = setTimeout(resolve11, 2e3);
|
|
15410
16226
|
})
|
|
15411
16227
|
]);
|
|
15412
16228
|
clearTimeout(drainDeadline);
|
|
15413
16229
|
const page = sink.lastActivePage;
|
|
15414
16230
|
const pageReadable = page !== null && !isPageClosed(page);
|
|
15415
16231
|
if (pageReadable) await maybeOpenPicker(sink);
|
|
16232
|
+
if (sink.stops.length > 0) {
|
|
16233
|
+
await testInfo.attach(ATTACHMENT_NAMES.pauses, {
|
|
16234
|
+
contentType: "application/json",
|
|
16235
|
+
body: Buffer.from(JSON.stringify(sink.stops))
|
|
16236
|
+
});
|
|
16237
|
+
}
|
|
15416
16238
|
if (sink.capturedLocators.length > 0) {
|
|
15417
16239
|
await testInfo.attach(ATTACHMENT_NAMES.locators, {
|
|
15418
16240
|
contentType: "application/json",
|
|
@@ -15613,6 +16435,7 @@ var piwiFixtures = {
|
|
|
15613
16435
|
// never double-wraps a page the browser patch already instrumented.
|
|
15614
16436
|
page: async ({ page }, use) => {
|
|
15615
16437
|
instrumentPage(page);
|
|
16438
|
+
await armBreakpoints([pageContext(page)]);
|
|
15616
16439
|
await awaitFlakeConditions();
|
|
15617
16440
|
await maybeStartCodeReach(page);
|
|
15618
16441
|
await use(page);
|
|
@@ -15633,8 +16456,9 @@ var piwiFixtures = {
|
|
|
15633
16456
|
if (pauseSet.size) {
|
|
15634
16457
|
const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
|
|
15635
16458
|
if (reason) logPauseSkipOnce(reason);
|
|
15636
|
-
else sink.pause = { set: pauseSet,
|
|
16459
|
+
else sink.pause = { set: pauseSet, step: false, finished: false };
|
|
15637
16460
|
}
|
|
16461
|
+
if (sink.pause || canRecordFromHere()) endStopsWithTestFunction(testInfo, sink);
|
|
15638
16462
|
if (isFlakeMode()) {
|
|
15639
16463
|
const plan = loadFlakePlan();
|
|
15640
16464
|
const role = flakeRoleForTest(plan, {
|
|
@@ -15659,9 +16483,12 @@ var piwiFixtures = {
|
|
|
15659
16483
|
activeResourceLedger()?.testStarted(testInfo);
|
|
15660
16484
|
currentSink = sink;
|
|
15661
16485
|
try {
|
|
16486
|
+
await armBreakpoints(OPEN_CONTEXTS);
|
|
15662
16487
|
await startCodeReachOnOpenPages();
|
|
15663
16488
|
await use();
|
|
15664
16489
|
} finally {
|
|
16490
|
+
await endShownStops(sink, "ended");
|
|
16491
|
+
await awaitReleasedCalls(sink);
|
|
15665
16492
|
currentSink = null;
|
|
15666
16493
|
if (sink.probeItem) {
|
|
15667
16494
|
const applied = sink.probeInterception?.applied() ?? false;
|
|
@@ -15719,13 +16546,13 @@ function extendPiwiFixtures(test) {
|
|
|
15719
16546
|
}
|
|
15720
16547
|
|
|
15721
16548
|
// src/internal/ai/ai-fixtures.ts
|
|
15722
|
-
var
|
|
15723
|
-
var
|
|
16549
|
+
var fs28 = __toESM(require("fs"));
|
|
16550
|
+
var path33 = __toESM(require("path"));
|
|
15724
16551
|
var import_test = require("@playwright/test");
|
|
15725
16552
|
|
|
15726
16553
|
// src/internal/ai/artifact.ts
|
|
15727
|
-
var
|
|
15728
|
-
var
|
|
16554
|
+
var fs27 = __toESM(require("fs"));
|
|
16555
|
+
var path31 = __toESM(require("path"));
|
|
15729
16556
|
var ARTIFACT_VERSION = 1;
|
|
15730
16557
|
var LOCATOR_METHOD_SET2 = new Set(LOCATOR_METHODS);
|
|
15731
16558
|
var ACTION_METHOD_SET2 = new Set(ACTION_METHODS);
|
|
@@ -15818,7 +16645,7 @@ function parseEntry(text) {
|
|
|
15818
16645
|
function readEntry(file) {
|
|
15819
16646
|
let text;
|
|
15820
16647
|
try {
|
|
15821
|
-
text =
|
|
16648
|
+
text = fs27.readFileSync(file, "utf8");
|
|
15822
16649
|
} catch (error) {
|
|
15823
16650
|
if (error.code === "ENOENT") return null;
|
|
15824
16651
|
throw error;
|
|
@@ -15828,28 +16655,28 @@ function readEntry(file) {
|
|
|
15828
16655
|
function writeEntry(file, entry) {
|
|
15829
16656
|
const canonical = serializeEntry(entry);
|
|
15830
16657
|
return withEntryLock(file, () => {
|
|
15831
|
-
if (
|
|
15832
|
-
|
|
16658
|
+
if (fs27.existsSync(file) && fs27.readFileSync(file, "utf8") === canonical) return { written: false };
|
|
16659
|
+
fs27.mkdirSync(path31.dirname(file), { recursive: true });
|
|
15833
16660
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
15834
|
-
|
|
15835
|
-
|
|
16661
|
+
fs27.writeFileSync(tmp, canonical);
|
|
16662
|
+
fs27.renameSync(tmp, file);
|
|
15836
16663
|
return { written: true };
|
|
15837
16664
|
});
|
|
15838
16665
|
}
|
|
15839
16666
|
function withEntryLock(file, fn) {
|
|
15840
16667
|
const lock = `${file}.lock`;
|
|
15841
|
-
|
|
16668
|
+
fs27.mkdirSync(path31.dirname(file), { recursive: true });
|
|
15842
16669
|
const deadline = Date.now() + 5e3;
|
|
15843
16670
|
for (; ; ) {
|
|
15844
16671
|
try {
|
|
15845
|
-
const fd =
|
|
15846
|
-
|
|
16672
|
+
const fd = fs27.openSync(lock, "wx");
|
|
16673
|
+
fs27.closeSync(fd);
|
|
15847
16674
|
break;
|
|
15848
16675
|
} catch (error) {
|
|
15849
16676
|
if (error.code !== "EEXIST") throw error;
|
|
15850
16677
|
if (Date.now() > deadline) {
|
|
15851
16678
|
try {
|
|
15852
|
-
|
|
16679
|
+
fs27.unlinkSync(lock);
|
|
15853
16680
|
} catch {
|
|
15854
16681
|
}
|
|
15855
16682
|
}
|
|
@@ -15859,7 +16686,7 @@ function withEntryLock(file, fn) {
|
|
|
15859
16686
|
return fn();
|
|
15860
16687
|
} finally {
|
|
15861
16688
|
try {
|
|
15862
|
-
|
|
16689
|
+
fs27.unlinkSync(lock);
|
|
15863
16690
|
} catch {
|
|
15864
16691
|
}
|
|
15865
16692
|
}
|
|
@@ -15867,7 +16694,7 @@ function withEntryLock(file, fn) {
|
|
|
15867
16694
|
|
|
15868
16695
|
// src/internal/ai/keys.ts
|
|
15869
16696
|
var crypto7 = __toESM(require("crypto"));
|
|
15870
|
-
var
|
|
16697
|
+
var path32 = __toESM(require("path"));
|
|
15871
16698
|
var DEFAULT_AI_DIR = "__piwi__";
|
|
15872
16699
|
function normalizeTemplate(template) {
|
|
15873
16700
|
return template.trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -15881,13 +16708,13 @@ function slug(text, maxLength = 40) {
|
|
|
15881
16708
|
return (base || "x").slice(0, maxLength).replace(/-+$/g, "") || "x";
|
|
15882
16709
|
}
|
|
15883
16710
|
function entryDir(specFile, dir = DEFAULT_AI_DIR) {
|
|
15884
|
-
return
|
|
16711
|
+
return path32.join(path32.dirname(specFile), dir, path32.basename(specFile));
|
|
15885
16712
|
}
|
|
15886
16713
|
function entryPath(params) {
|
|
15887
16714
|
const basis = `${normalizeTemplate(params.testTitle)}::${normalizeTemplate(params.template)}`;
|
|
15888
16715
|
const hash = hashTemplate(basis, params.ordinal ?? 0);
|
|
15889
16716
|
const name = `${slug(params.testTitle)}.${slug(params.template)}.${hash}.json`;
|
|
15890
|
-
return
|
|
16717
|
+
return path32.join(entryDir(params.specFile, params.dir), name);
|
|
15891
16718
|
}
|
|
15892
16719
|
function parseLocation2(location) {
|
|
15893
16720
|
const match = /:(\d+):(\d+)$/.exec(location);
|
|
@@ -16490,7 +17317,7 @@ function testIdentity(testInfo) {
|
|
|
16490
17317
|
return titles.length > 0 ? titles.join(" \u203A ") : testInfo.title;
|
|
16491
17318
|
}
|
|
16492
17319
|
function relativeToCwd(file) {
|
|
16493
|
-
return
|
|
17320
|
+
return path33.relative(process.cwd(), file).split(path33.sep).join("/");
|
|
16494
17321
|
}
|
|
16495
17322
|
function recordIntents(intents, entry) {
|
|
16496
17323
|
const add = (locator, kind) => {
|
|
@@ -16517,7 +17344,7 @@ function createAiApi(page, testInfo, config, used, intents) {
|
|
|
16517
17344
|
const readSource = () => {
|
|
16518
17345
|
if (source === null) {
|
|
16519
17346
|
try {
|
|
16520
|
-
source =
|
|
17347
|
+
source = fs28.readFileSync(testInfo.file, "utf8");
|
|
16521
17348
|
} catch {
|
|
16522
17349
|
source = "";
|
|
16523
17350
|
}
|