@piwitests/reporter 0.62.0 → 0.63.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 +14 -13
- package/dist/global-setup-module.js +27 -12
- package/dist/index.d.ts +8 -0
- package/dist/index.js +16258 -13784
- package/dist/internal/capture/capture-fixtures.d.ts +13 -1
- package/dist/internal/capture/capture-fixtures.js +2791 -560
- package/dist/internal/capture/locator-healing.d.ts +11 -1
- package/dist/internal/capture/locator-healing.js +22 -10
- package/dist/internal/capture/pick-on-failure.js +29 -5
- package/package.json +1 -1
|
@@ -41,6 +41,7 @@ __export(capture_fixtures_exports, {
|
|
|
41
41
|
codeReachRoots: () => codeReachRoots,
|
|
42
42
|
computeCoreVitals: () => computeCoreVitals,
|
|
43
43
|
configuredCodeReachRoots: () => configuredCodeReachRoots,
|
|
44
|
+
errorPlace: () => errorPlace,
|
|
44
45
|
extendPiwiFixtures: () => extendPiwiFixtures,
|
|
45
46
|
failureError: () => failureError,
|
|
46
47
|
intentAt: () => intentAt,
|
|
@@ -60,7 +61,7 @@ __export(capture_fixtures_exports, {
|
|
|
60
61
|
stopResourceLedger: () => stopResourceLedger
|
|
61
62
|
});
|
|
62
63
|
module.exports = __toCommonJS(capture_fixtures_exports);
|
|
63
|
-
var
|
|
64
|
+
var path15 = __toESM(require("path"));
|
|
64
65
|
var import_node_zlib2 = require("zlib");
|
|
65
66
|
|
|
66
67
|
// ../picker-dom/src/probe.ts
|
|
@@ -519,8 +520,8 @@ function installPickerOverlay(arg) {
|
|
|
519
520
|
return root && root !== doc && root.host ? root.host : null;
|
|
520
521
|
};
|
|
521
522
|
const targetOf = (e) => {
|
|
522
|
-
const
|
|
523
|
-
const first =
|
|
523
|
+
const path16 = typeof e.composedPath === "function" ? e.composedPath() : null;
|
|
524
|
+
const first = path16 && path16.length ? path16[0] : null;
|
|
524
525
|
return first && first.nodeType === 1 ? first : e.target;
|
|
525
526
|
};
|
|
526
527
|
const buildChain = (raw) => {
|
|
@@ -825,14 +826,14 @@ function showAnchorPicker(arg) {
|
|
|
825
826
|
} catch {
|
|
826
827
|
shadowRoots.length = 0;
|
|
827
828
|
}
|
|
828
|
-
const
|
|
829
|
+
const within3 = (node2, scope) => {
|
|
829
830
|
if (scope === doc) return true;
|
|
830
831
|
for (let n = node2; n; n = parentOf(n)) if (n === scope) return true;
|
|
831
832
|
return false;
|
|
832
833
|
};
|
|
833
834
|
const queryAll = (scope, sel) => {
|
|
834
835
|
const found = Array.from(scope.querySelectorAll(sel));
|
|
835
|
-
for (const sr of shadowRoots) if (
|
|
836
|
+
for (const sr of shadowRoots) if (within3(sr.host, scope)) found.push(...Array.from(sr.querySelectorAll(sel)));
|
|
836
837
|
return found;
|
|
837
838
|
};
|
|
838
839
|
const leafCountable = !!leafTestId || roleOf2(el) === leafRole;
|
|
@@ -914,13 +915,17 @@ function showAnchorPicker(arg) {
|
|
|
914
915
|
}
|
|
915
916
|
const MONO = "ui-monospace,SFMono-Regular,Menlo,Consolas,monospace";
|
|
916
917
|
const outline2 = doc.createElement("div");
|
|
918
|
+
outline2.id = "__piwi_anchor_outline";
|
|
917
919
|
outline2.style.cssText = `position:fixed;pointer-events:none;z-index:${Z};box-sizing:border-box;border:2px solid #22c55e;background:rgba(34,197,94,.10);border-radius:4px;display:none;box-shadow:0 0 0 1px rgba(255,255,255,.9),0 0 0 3px rgba(5,46,22,.5);`;
|
|
918
920
|
const outlineLabel = doc.createElement("div");
|
|
921
|
+
outlineLabel.id = "__piwi_anchor_label";
|
|
919
922
|
outlineLabel.style.cssText = `position:fixed;pointer-events:none;z-index:${Z + 1};display:none;box-sizing:border-box;max-width:min(420px,90vw);background:#0b1120;color:#f9fafb;border:1px solid #22c55e;border-radius:6px;padding:3px 7px;font:12px/1.45 ${MONO};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 4px 18px rgba(0,0,0,.5);`;
|
|
920
923
|
const pickedOutline = doc.createElement("div");
|
|
924
|
+
pickedOutline.id = "__piwi_anchor_picked";
|
|
921
925
|
const pr = el.getBoundingClientRect();
|
|
922
926
|
pickedOutline.style.cssText = `position:fixed;pointer-events:none;z-index:${Z};box-sizing:border-box;border:2px solid #a855f7;background:rgba(168,85,247,.14);border-radius:4px;box-shadow:0 0 0 1px rgba(255,255,255,.9),0 0 0 3px rgba(59,7,100,.55);left:${pr.left}px;top:${pr.top}px;width:${pr.width}px;height:${pr.height}px;`;
|
|
923
927
|
const panel = doc.createElement("div");
|
|
928
|
+
panel.id = "__piwi_anchor_panel";
|
|
924
929
|
panel.style.cssText = `position:fixed;top:12px;right:12px;z-index:${Z + 3};width:min(340px,calc(100vw - 56px));max-height:82vh;overflow:auto;background:#111827;color:#f9fafb;border-radius:10px;padding:16px;font:12px/1.5 system-ui,sans-serif;box-shadow:0 8px 40px rgba(0,0,0,.5);`;
|
|
925
930
|
const title = doc.createElement("div");
|
|
926
931
|
title.style.cssText = "font-weight:600;font-size:13px;margin-bottom:2px;";
|
|
@@ -1109,6 +1114,7 @@ function showPickerChoices(arg) {
|
|
|
1109
1114
|
}
|
|
1110
1115
|
const Z = 2147483600;
|
|
1111
1116
|
const wrap = doc.createElement("div");
|
|
1117
|
+
wrap.id = "__piwi_picker_choices";
|
|
1112
1118
|
wrap.style.cssText = `position:fixed;inset:0;z-index:${Z + 3};background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;font:13px/1.5 system-ui,sans-serif;`;
|
|
1113
1119
|
const panel = doc.createElement("div");
|
|
1114
1120
|
panel.style.cssText = "background:#111827;color:#f9fafb;border-radius:10px;padding:20px;max-width:640px;width:90vw;max-height:70vh;overflow:auto;box-shadow:0 8px 40px rgba(0,0,0,.5);";
|
|
@@ -1139,10 +1145,14 @@ function showPickerChoices(arg) {
|
|
|
1139
1145
|
}
|
|
1140
1146
|
panel.appendChild(title);
|
|
1141
1147
|
panel.appendChild(sub);
|
|
1142
|
-
const
|
|
1143
|
-
g.__piwiPickChoice = choice;
|
|
1148
|
+
const cleanup = () => {
|
|
1144
1149
|
doc.removeEventListener("keydown", onKey, true);
|
|
1145
1150
|
wrap.remove();
|
|
1151
|
+
if (g.__piwiChoiceCleanup === cleanup) delete g.__piwiChoiceCleanup;
|
|
1152
|
+
};
|
|
1153
|
+
const done = (choice) => {
|
|
1154
|
+
g.__piwiPickChoice = choice;
|
|
1155
|
+
cleanup();
|
|
1146
1156
|
};
|
|
1147
1157
|
const onKey = (e) => {
|
|
1148
1158
|
if (e.key !== "Escape") return;
|
|
@@ -1178,6 +1188,7 @@ function showPickerChoices(arg) {
|
|
|
1178
1188
|
});
|
|
1179
1189
|
panel.appendChild(skip);
|
|
1180
1190
|
doc.addEventListener("keydown", onKey, true);
|
|
1191
|
+
g.__piwiChoiceCleanup = cleanup;
|
|
1181
1192
|
wrap.appendChild(panel);
|
|
1182
1193
|
doc.body.appendChild(wrap);
|
|
1183
1194
|
}
|
|
@@ -1229,7 +1240,10 @@ function showPauseBar(arg) {
|
|
|
1229
1240
|
...arg.record ? [
|
|
1230
1241
|
["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
|
|
1231
1242
|
] : [],
|
|
1232
|
-
...arg.failure ? [] : [["Finish", "finish", "Run on and pause no more in this test", false]]
|
|
1243
|
+
...arg.failure ? [] : [["Finish", "finish", "Run on and pause no more in this test", false]],
|
|
1244
|
+
...arg.end && !arg.failure ? [
|
|
1245
|
+
["End the test", "end", "Skip the rest of this test: it is reported as skipped", false]
|
|
1246
|
+
] : []
|
|
1233
1247
|
];
|
|
1234
1248
|
for (const [label, choice, hint, primary] of buttons) {
|
|
1235
1249
|
const btn = doc.createElement("button");
|
|
@@ -1990,21 +2004,21 @@ var MAX_INDETERMINATE = 20;
|
|
|
1990
2004
|
var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
|
|
1991
2005
|
var LEVEL = `${STEP}(?:/${STEP})*`;
|
|
1992
2006
|
var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
|
|
1993
|
-
function isElementPath(
|
|
1994
|
-
return typeof
|
|
2007
|
+
function isElementPath(path16) {
|
|
2008
|
+
return typeof path16 === "string" && path16.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path16);
|
|
1995
2009
|
}
|
|
1996
2010
|
var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
|
|
1997
2011
|
function elementRef(v) {
|
|
1998
2012
|
if (!v || typeof v !== "object") return null;
|
|
1999
|
-
const { path:
|
|
2000
|
-
if (!isElementPath(
|
|
2001
|
-
return { path:
|
|
2013
|
+
const { path: path16, label } = v;
|
|
2014
|
+
if (!isElementPath(path16)) return null;
|
|
2015
|
+
return { path: path16, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
|
|
2002
2016
|
}
|
|
2003
2017
|
function textPoint(v) {
|
|
2004
2018
|
if (!v || typeof v !== "object") return null;
|
|
2005
|
-
const { path:
|
|
2019
|
+
const { path: path16, offset } = v;
|
|
2006
2020
|
const at = offsetOf(offset);
|
|
2007
|
-
return isElementPath(
|
|
2021
|
+
return isElementPath(path16) && at !== null ? { path: path16, offset: at } : null;
|
|
2008
2022
|
}
|
|
2009
2023
|
function sanitizePageInteraction(raw) {
|
|
2010
2024
|
if (!raw || typeof raw !== "object") return {};
|
|
@@ -2036,6 +2050,7 @@ function sanitizePageInteraction(raw) {
|
|
|
2036
2050
|
|
|
2037
2051
|
// src/internal/capture/locator-healing.ts
|
|
2038
2052
|
var path = __toESM(require("path"));
|
|
2053
|
+
var import_node_url = require("url");
|
|
2039
2054
|
|
|
2040
2055
|
// ../core/src/locator-fingerprint.ts
|
|
2041
2056
|
var PRESENT_SIMILARITY = 0.8;
|
|
@@ -2286,23 +2301,23 @@ var SUGG_TEXT_ROLES = /* @__PURE__ */ new Set([
|
|
|
2286
2301
|
"switch"
|
|
2287
2302
|
]);
|
|
2288
2303
|
var SUGG_FIELD_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox", "searchbox", "spinbutton", "slider"]);
|
|
2289
|
-
function failedNameAndRole(
|
|
2290
|
-
if (
|
|
2291
|
-
const role = typeof
|
|
2292
|
-
const opts =
|
|
2304
|
+
function failedNameAndRole(failed2) {
|
|
2305
|
+
if (failed2.method === "getByRole") {
|
|
2306
|
+
const role = typeof failed2.args[0] === "string" ? failed2.args[0] : null;
|
|
2307
|
+
const opts = failed2.args[1];
|
|
2293
2308
|
const name = opts && typeof opts.name === "string" ? opts.name : null;
|
|
2294
2309
|
const level = opts && typeof opts.level === "number" ? opts.level : null;
|
|
2295
2310
|
return { role, name, level };
|
|
2296
2311
|
}
|
|
2297
|
-
const first =
|
|
2312
|
+
const first = failed2.args.find((a) => typeof a === "string");
|
|
2298
2313
|
return { role: null, name: typeof first === "string" ? first : null, level: null };
|
|
2299
2314
|
}
|
|
2300
|
-
function renderFailing(
|
|
2301
|
-
const { role, name } = failedNameAndRole(
|
|
2302
|
-
if (
|
|
2315
|
+
function renderFailing(failed2) {
|
|
2316
|
+
const { role, name } = failedNameAndRole(failed2);
|
|
2317
|
+
if (failed2.method === "getByRole") {
|
|
2303
2318
|
return name ? `getByRole(${quoteJsString(role ?? "")}, { name: ${quoteJsString(name)} })` : `getByRole(${quoteJsString(role ?? "")})`;
|
|
2304
2319
|
}
|
|
2305
|
-
return `${
|
|
2320
|
+
return `${failed2.method}(${quoteJsString(name ?? "")})`;
|
|
2306
2321
|
}
|
|
2307
2322
|
function freshSuggestions(candidate, failedMethod) {
|
|
2308
2323
|
const out = [];
|
|
@@ -2322,9 +2337,9 @@ function freshSuggestions(candidate, failedMethod) {
|
|
|
2322
2337
|
else if (SUGG_FIELD_ROLES.has(role)) push(labelLoc);
|
|
2323
2338
|
return out;
|
|
2324
2339
|
}
|
|
2325
|
-
function suggestLocatorsFromAria(
|
|
2326
|
-
if (!ariaSnapshot || !NAME_BASED_METHODS.has(
|
|
2327
|
-
const { role, name, level } = failedNameAndRole(
|
|
2340
|
+
function suggestLocatorsFromAria(failed2, ariaSnapshot) {
|
|
2341
|
+
if (!ariaSnapshot || !NAME_BASED_METHODS.has(failed2.method)) return null;
|
|
2342
|
+
const { role, name, level } = failedNameAndRole(failed2);
|
|
2328
2343
|
if (!name) return null;
|
|
2329
2344
|
const candidates = parseAriaCandidates(ariaSnapshot);
|
|
2330
2345
|
if (candidates.length === 0) return null;
|
|
@@ -2332,9 +2347,23 @@ function suggestLocatorsFromAria(failed, ariaSnapshot) {
|
|
|
2332
2347
|
if (fingerprintPresent(fingerprint, candidates)) return null;
|
|
2333
2348
|
const best = matchRenamedElement(fingerprint, candidates)?.candidate;
|
|
2334
2349
|
if (!best || !best.name) return null;
|
|
2335
|
-
const suggestions = freshSuggestions({ role: best.role, name: best.name, level: best.level },
|
|
2350
|
+
const suggestions = freshSuggestions({ role: best.role, name: best.name, level: best.level }, failed2.method);
|
|
2336
2351
|
if (suggestions.length === 0) return null;
|
|
2337
|
-
return { failing: renderFailing(
|
|
2352
|
+
return { failing: renderFailing(failed2), suggestions };
|
|
2353
|
+
}
|
|
2354
|
+
function framePath(file, windows = process.platform === "win32") {
|
|
2355
|
+
if (!file.startsWith("file://")) return file;
|
|
2356
|
+
try {
|
|
2357
|
+
return (0, import_node_url.fileURLToPath)(file, { windows });
|
|
2358
|
+
} catch {
|
|
2359
|
+
return null;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
function callSitePath(file, cwd = process.cwd(), windows = process.platform === "win32") {
|
|
2363
|
+
const local = framePath(file, windows);
|
|
2364
|
+
if (local === null) return null;
|
|
2365
|
+
const paths = windows ? path.win32 : path.posix;
|
|
2366
|
+
return paths.relative(cwd, local).split(paths.sep).join("/");
|
|
2338
2367
|
}
|
|
2339
2368
|
function captureCallerLocation(stack = new Error().stack ?? "") {
|
|
2340
2369
|
const lines = stack.split("\n");
|
|
@@ -2348,13 +2377,8 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
|
|
|
2348
2377
|
prevWasCaptureModule = false;
|
|
2349
2378
|
continue;
|
|
2350
2379
|
}
|
|
2351
|
-
|
|
2352
|
-
if (!file || file.startsWith("node:")) {
|
|
2353
|
-
prevWasCaptureModule = false;
|
|
2354
|
-
continue;
|
|
2355
|
-
}
|
|
2356
|
-
file = file.replace(/^file:\/\/\/?/, "");
|
|
2357
|
-
if (!/\.[a-z]+$/i.test(file)) {
|
|
2380
|
+
const file = m[2] ? framePath(m[2]) : null;
|
|
2381
|
+
if (!file || file.startsWith("node:") || !/\.[a-z]+$/i.test(file)) {
|
|
2358
2382
|
prevWasCaptureModule = false;
|
|
2359
2383
|
continue;
|
|
2360
2384
|
}
|
|
@@ -2377,11 +2401,9 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
|
|
|
2377
2401
|
}
|
|
2378
2402
|
let rel = file;
|
|
2379
2403
|
try {
|
|
2380
|
-
rel =
|
|
2404
|
+
rel = callSitePath(file) ?? file;
|
|
2381
2405
|
} catch {
|
|
2382
2406
|
}
|
|
2383
|
-
rel = rel.split(path.sep).join("/");
|
|
2384
|
-
if (rel.startsWith("./")) rel = rel.slice(2);
|
|
2385
2407
|
return `${rel}:${m[3]}:${m[4]}`;
|
|
2386
2408
|
}
|
|
2387
2409
|
return null;
|
|
@@ -2661,6 +2683,13 @@ var path2 = __toESM(require("path"));
|
|
|
2661
2683
|
// src/internal/config/dotenv.ts
|
|
2662
2684
|
var fs2 = __toESM(require("fs"));
|
|
2663
2685
|
|
|
2686
|
+
// src/internal/support/ci.ts
|
|
2687
|
+
function detectCi(env) {
|
|
2688
|
+
return Boolean(
|
|
2689
|
+
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"
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2664
2693
|
// src/internal/config/env.ts
|
|
2665
2694
|
var DEFAULTS = {
|
|
2666
2695
|
projectName: "default-project",
|
|
@@ -2753,6 +2782,7 @@ var PIWI_ORIGIN_ENV = {
|
|
|
2753
2782
|
kind: "PIWI_ORIGIN",
|
|
2754
2783
|
ref: "PIWI_ORIGIN_REF"
|
|
2755
2784
|
};
|
|
2785
|
+
var PIWI_SHARD_ENV = "PIWI_SHARD";
|
|
2756
2786
|
var PIWI_PROBE_ENV = {
|
|
2757
2787
|
flag: "PIWI_PROBE",
|
|
2758
2788
|
plan: "PIWI_PROBE_PLAN",
|
|
@@ -2817,8 +2847,8 @@ function parseRouteKey(routeKey2) {
|
|
|
2817
2847
|
return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
|
|
2818
2848
|
}
|
|
2819
2849
|
function patternToRegex(pattern) {
|
|
2820
|
-
const
|
|
2821
|
-
const escaped =
|
|
2850
|
+
const path16 = pattern.split("?")[0] ?? pattern;
|
|
2851
|
+
const escaped = path16.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
|
|
2822
2852
|
return new RegExp(`^${escaped}$`);
|
|
2823
2853
|
}
|
|
2824
2854
|
function suitePathEqual(a, b) {
|
|
@@ -2841,13 +2871,13 @@ function matchProbeItem(plan, test) {
|
|
|
2841
2871
|
function requestMatchesRoute(method, url, routeKey2) {
|
|
2842
2872
|
const { method: rm, pattern } = parseRouteKey(routeKey2);
|
|
2843
2873
|
if (rm && rm !== method.toUpperCase()) return false;
|
|
2844
|
-
let
|
|
2874
|
+
let path16;
|
|
2845
2875
|
try {
|
|
2846
|
-
|
|
2876
|
+
path16 = new URL(url).pathname;
|
|
2847
2877
|
} catch {
|
|
2848
|
-
|
|
2878
|
+
path16 = url.split("?")[0] ?? url;
|
|
2849
2879
|
}
|
|
2850
|
-
return patternToRegex(pattern).test(
|
|
2880
|
+
return patternToRegex(pattern).test(path16);
|
|
2851
2881
|
}
|
|
2852
2882
|
function createProbeState() {
|
|
2853
2883
|
return { navigated: false, matches: 0 };
|
|
@@ -2869,10 +2899,10 @@ function isProbeMode() {
|
|
|
2869
2899
|
function loadPlan() {
|
|
2870
2900
|
if (planCache !== void 0) return planCache;
|
|
2871
2901
|
planCache = null;
|
|
2872
|
-
const
|
|
2873
|
-
if (
|
|
2902
|
+
const path16 = process.env[PIWI_PROBE_ENV.plan];
|
|
2903
|
+
if (path16) {
|
|
2874
2904
|
try {
|
|
2875
|
-
planCache = JSON.parse(fs3.readFileSync(
|
|
2905
|
+
planCache = JSON.parse(fs3.readFileSync(path16, "utf8"));
|
|
2876
2906
|
} catch {
|
|
2877
2907
|
planCache = null;
|
|
2878
2908
|
}
|
|
@@ -2896,10 +2926,10 @@ function outcomeFromStatus(status, applied) {
|
|
|
2896
2926
|
return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
|
|
2897
2927
|
}
|
|
2898
2928
|
function recordProbeOutcome(line) {
|
|
2899
|
-
const
|
|
2900
|
-
if (!
|
|
2929
|
+
const path16 = process.env[PIWI_PROBE_ENV.results];
|
|
2930
|
+
if (!path16) return;
|
|
2901
2931
|
try {
|
|
2902
|
-
fs3.appendFileSync(
|
|
2932
|
+
fs3.appendFileSync(path16, `${JSON.stringify(line)}
|
|
2903
2933
|
`);
|
|
2904
2934
|
} catch {
|
|
2905
2935
|
}
|
|
@@ -2954,26 +2984,26 @@ function dropOneField(body) {
|
|
|
2954
2984
|
}
|
|
2955
2985
|
return body;
|
|
2956
2986
|
}
|
|
2957
|
-
function computeFault(fault,
|
|
2987
|
+
function computeFault(fault, input2) {
|
|
2958
2988
|
const base = {
|
|
2959
|
-
status:
|
|
2960
|
-
body:
|
|
2961
|
-
contentType:
|
|
2989
|
+
status: input2.status,
|
|
2990
|
+
body: input2.body,
|
|
2991
|
+
contentType: input2.contentType ?? null,
|
|
2962
2992
|
delayMs: 0,
|
|
2963
2993
|
changed: false
|
|
2964
2994
|
};
|
|
2965
2995
|
switch (fault) {
|
|
2966
2996
|
case "status-500":
|
|
2967
|
-
return { ...base, status: 500, changed:
|
|
2997
|
+
return { ...base, status: 500, changed: input2.status !== 500 };
|
|
2968
2998
|
case "empty-body":
|
|
2969
|
-
return { ...base, body: "", changed:
|
|
2999
|
+
return { ...base, body: "", changed: input2.body.length > 0 };
|
|
2970
3000
|
case "drop-field": {
|
|
2971
|
-
const body = dropOneField(
|
|
2972
|
-
return { ...base, body, changed: body !==
|
|
3001
|
+
const body = dropOneField(input2.body);
|
|
3002
|
+
return { ...base, body, changed: body !== input2.body };
|
|
2973
3003
|
}
|
|
2974
3004
|
case "stale-value": {
|
|
2975
|
-
const body =
|
|
2976
|
-
return { ...base, body, changed: body !==
|
|
3005
|
+
const body = input2.priorBody ?? input2.body;
|
|
3006
|
+
return { ...base, body, changed: body !== input2.body };
|
|
2977
3007
|
}
|
|
2978
3008
|
case "slow":
|
|
2979
3009
|
return { ...base, delayMs: SLOW_FAULT_DELAY_MS, changed: true };
|
|
@@ -3467,16 +3497,16 @@ function relativeTestFile(file, base = process.cwd()) {
|
|
|
3467
3497
|
return file ? relativeTestPath(file, base) : null;
|
|
3468
3498
|
}
|
|
3469
3499
|
function flakeRoleForTest(plan, test) {
|
|
3470
|
-
const
|
|
3500
|
+
const running2 = {
|
|
3471
3501
|
file: relativeTestFile(test.file, test.base),
|
|
3472
3502
|
title: test.title,
|
|
3473
3503
|
suite: test.titlePath ? test.titlePath.slice(1, -1) : void 0
|
|
3474
3504
|
};
|
|
3475
|
-
if (flakeTestMatches(plan.test,
|
|
3505
|
+
if (flakeTestMatches(plan.test, running2) && (plan.test.project === null || plan.test.project === test.project)) {
|
|
3476
3506
|
return "target";
|
|
3477
3507
|
}
|
|
3478
3508
|
const companion = plan.arm.conditions.some(
|
|
3479
|
-
(c) => (c.kind === "alongside" || c.kind === "after") && flakeTestMatches(c.test,
|
|
3509
|
+
(c) => (c.kind === "alongside" || c.kind === "after") && flakeTestMatches(c.test, running2)
|
|
3480
3510
|
);
|
|
3481
3511
|
return companion ? "companion" : null;
|
|
3482
3512
|
}
|
|
@@ -3577,8 +3607,8 @@ async function installFlakeConditions(page, plan) {
|
|
|
3577
3607
|
}
|
|
3578
3608
|
var FAILED_STATUSES = /* @__PURE__ */ new Set(["failed", "timedOut", "interrupted"]);
|
|
3579
3609
|
function flakeResultLine(plan, attempt) {
|
|
3580
|
-
const
|
|
3581
|
-
const errorSignature =
|
|
3610
|
+
const failed2 = FAILED_STATUSES.has(attempt.status);
|
|
3611
|
+
const errorSignature = failed2 && attempt.errorText ? flakeErrorSignature(attempt.errorText) : null;
|
|
3582
3612
|
return {
|
|
3583
3613
|
version: FLAKE_PLAN_VERSION,
|
|
3584
3614
|
experimentId: plan.experimentId,
|
|
@@ -3670,13 +3700,13 @@ function normalizeFunctionRanges(ranges) {
|
|
|
3670
3700
|
}
|
|
3671
3701
|
function normalizeCoveragePath(raw) {
|
|
3672
3702
|
if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
|
|
3673
|
-
const
|
|
3674
|
-
if (!
|
|
3675
|
-
if (/(^|\/)node_modules\//.test(
|
|
3676
|
-
return
|
|
3703
|
+
const path16 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
3704
|
+
if (!path16 || path16.startsWith("../") || path16.startsWith("/") || /^[A-Za-z]:\//.test(path16)) return null;
|
|
3705
|
+
if (/(^|\/)node_modules\//.test(path16)) return null;
|
|
3706
|
+
return path16;
|
|
3677
3707
|
}
|
|
3678
|
-
function fileEntry(
|
|
3679
|
-
const file = { path:
|
|
3708
|
+
function fileEntry(path16, runtime, blob, functions = [], lines = []) {
|
|
3709
|
+
const file = { path: path16, runtime };
|
|
3680
3710
|
if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
|
|
3681
3711
|
if (functions.length > 0) file.functions = functions;
|
|
3682
3712
|
if (lines.length > 0) file.lines = lines;
|
|
@@ -3695,11 +3725,11 @@ function sanitizeCoverage(raw) {
|
|
|
3695
3725
|
const files = [];
|
|
3696
3726
|
for (const item of raw.files) {
|
|
3697
3727
|
if (!isRecord(item) || !isRuntime(item.runtime)) continue;
|
|
3698
|
-
const
|
|
3699
|
-
if (
|
|
3728
|
+
const path16 = normalizeCoveragePath(item.path);
|
|
3729
|
+
if (path16 === null) continue;
|
|
3700
3730
|
const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
|
|
3701
3731
|
files.push(
|
|
3702
|
-
fileEntry(
|
|
3732
|
+
fileEntry(path16, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
|
|
3703
3733
|
);
|
|
3704
3734
|
}
|
|
3705
3735
|
const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
|
|
@@ -4241,9 +4271,9 @@ function hideParams(params, count) {
|
|
|
4241
4271
|
return isSecretName(decodeSafe(name)) || isSecretValue(decoded) ? `${name}=${hide()}` : part;
|
|
4242
4272
|
}).join("");
|
|
4243
4273
|
}
|
|
4244
|
-
function hidePath(
|
|
4274
|
+
function hidePath(path16, count) {
|
|
4245
4275
|
const hide = hider(count);
|
|
4246
|
-
return
|
|
4276
|
+
return path16.split("/").map((segment) => {
|
|
4247
4277
|
const [own = "", ...params] = segment.split(";");
|
|
4248
4278
|
const decoded = decodeSafe(own);
|
|
4249
4279
|
const kept = decoded !== HIDDEN_VALUE && isSecretValue(decoded) ? hide() : own;
|
|
@@ -4253,14 +4283,14 @@ function hidePath(path15, count) {
|
|
|
4253
4283
|
function hideSecretsInUrl(url, count = { n: 0 }) {
|
|
4254
4284
|
const parts = URL_PARTS.exec(url);
|
|
4255
4285
|
if (!parts) return url;
|
|
4256
|
-
const [, scheme = "", authority,
|
|
4286
|
+
const [, scheme = "", authority, path16 = "", query = "", fragment = ""] = parts;
|
|
4257
4287
|
let out = "";
|
|
4258
4288
|
if (authority !== void 0) {
|
|
4259
4289
|
const at = authority.lastIndexOf("@");
|
|
4260
4290
|
const colon = at >= 0 ? authority.slice(0, at).indexOf(":") : -1;
|
|
4261
4291
|
out += colon >= 0 && colon < at - 1 ? `${scheme}//${authority.slice(0, colon + 1)}${hider(count)()}${authority.slice(at)}` : `${scheme}//${authority}`;
|
|
4262
4292
|
}
|
|
4263
|
-
out += hidePath(
|
|
4293
|
+
out += hidePath(path16, count);
|
|
4264
4294
|
if (query) out += `?${hideParams(query.slice(1), count)}`;
|
|
4265
4295
|
if (fragment) {
|
|
4266
4296
|
const body = fragment.slice(1);
|
|
@@ -4291,10 +4321,10 @@ function hideInText(text, count) {
|
|
|
4291
4321
|
if (text.length < 4 || !/[A-Za-z0-9]/.test(text)) return text;
|
|
4292
4322
|
const hide = hider(count);
|
|
4293
4323
|
let out = hidePem(text, hide);
|
|
4294
|
-
out = out.replace(QUERY_PAIR, (all,
|
|
4324
|
+
out = out.replace(QUERY_PAIR, (all, sep7, name, value) => {
|
|
4295
4325
|
const decoded = decodeSafe(value);
|
|
4296
4326
|
if (!value || decoded === HIDDEN_VALUE) return all;
|
|
4297
|
-
return isSecretName(name) || isSecretValue(decoded) ? `${
|
|
4327
|
+
return isSecretName(name) || isSecretValue(decoded) ? `${sep7}${name}=${hide()}` : all;
|
|
4298
4328
|
});
|
|
4299
4329
|
out = out.replace(URL_PASSWORD, (_all, before, _password, at) => `${before}${hide()}${at}`);
|
|
4300
4330
|
out = out.replace(SET_COOKIE, (_all, before) => `${before}${hide()}`);
|
|
@@ -4390,8 +4420,10 @@ async function cleanupPicker(page, ended = false) {
|
|
|
4390
4420
|
const g = globalThis;
|
|
4391
4421
|
if (typeof g.__piwiPickCleanup === "function") g.__piwiPickCleanup();
|
|
4392
4422
|
if (typeof g.__piwiAnchorCleanup === "function") g.__piwiAnchorCleanup();
|
|
4423
|
+
if (typeof g.__piwiChoiceCleanup === "function") g.__piwiChoiceCleanup();
|
|
4393
4424
|
delete g.__piwiPickCleanup;
|
|
4394
4425
|
delete g.__piwiAnchorCleanup;
|
|
4426
|
+
delete g.__piwiChoiceCleanup;
|
|
4395
4427
|
delete g.__piwiPickState;
|
|
4396
4428
|
delete g.__piwiPickChoice;
|
|
4397
4429
|
delete g.__piwiPickedElement;
|
|
@@ -4417,13 +4449,24 @@ function unlessEnded(run, ended) {
|
|
|
4417
4449
|
]);
|
|
4418
4450
|
}
|
|
4419
4451
|
async function pickLocator(page, probe, failing, ended) {
|
|
4452
|
+
let polling = false;
|
|
4420
4453
|
const answered = async (name) => {
|
|
4421
4454
|
const wait = page.waitForFunction(
|
|
4422
|
-
([key, endedKey]) =>
|
|
4455
|
+
([key, endedKey]) => {
|
|
4456
|
+
const g = globalThis;
|
|
4457
|
+
if (g[endedKey] !== true) return g[key] !== void 0;
|
|
4458
|
+
delete g[endedKey];
|
|
4459
|
+
return true;
|
|
4460
|
+
},
|
|
4423
4461
|
[name, PICK_ENDED],
|
|
4424
4462
|
{ timeout: 0, polling: 250 }
|
|
4425
4463
|
);
|
|
4426
|
-
|
|
4464
|
+
polling = true;
|
|
4465
|
+
const done = await unlessEnded(
|
|
4466
|
+
wait.then(() => true),
|
|
4467
|
+
ended
|
|
4468
|
+
);
|
|
4469
|
+
if (done) polling = false;
|
|
4427
4470
|
return !ended?.aborted;
|
|
4428
4471
|
};
|
|
4429
4472
|
try {
|
|
@@ -4461,25 +4504,25 @@ async function pickLocator(page, probe, failing, ended) {
|
|
|
4461
4504
|
chainLeafCount = anchorResult.chainCount;
|
|
4462
4505
|
}
|
|
4463
4506
|
}
|
|
4464
|
-
const
|
|
4507
|
+
const ranked2 = mergeCandidates(
|
|
4465
4508
|
generateAlternatives({ ...attrs, accessibleName }),
|
|
4466
4509
|
generateAnchoredAlternatives({ role, level }, anchors, chainLeafCount)
|
|
4467
4510
|
);
|
|
4468
|
-
if (
|
|
4511
|
+
if (ranked2.length === 0) {
|
|
4469
4512
|
console.log("[piwi] No stable locator could be generated for the picked element \u2014 nothing recorded.");
|
|
4470
4513
|
return null;
|
|
4471
4514
|
}
|
|
4472
4515
|
await page.evaluate(showPickerChoices, {
|
|
4473
4516
|
failing,
|
|
4474
|
-
choices:
|
|
4517
|
+
choices: ranked2.map((r) => ({ locator: r.locator, score: r.score }))
|
|
4475
4518
|
});
|
|
4476
4519
|
if (!await answered("__piwiPickChoice")) return null;
|
|
4477
4520
|
const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
|
|
4478
|
-
if (typeof choice !== "number" || choice < 0 || choice >=
|
|
4479
|
-
const picked = { ...
|
|
4521
|
+
if (typeof choice !== "number" || choice < 0 || choice >= ranked2.length) return null;
|
|
4522
|
+
const picked = { ...ranked2[choice], pickedByUser: true };
|
|
4480
4523
|
return {
|
|
4481
4524
|
picked,
|
|
4482
|
-
alternatives: [picked, ...
|
|
4525
|
+
alternatives: [picked, ...ranked2.filter((_, i) => i !== choice)],
|
|
4483
4526
|
element: {
|
|
4484
4527
|
tagName: attrs.tagName,
|
|
4485
4528
|
attributes: attrs.attributes,
|
|
@@ -4492,27 +4535,27 @@ async function pickLocator(page, probe, failing, ended) {
|
|
|
4492
4535
|
...anchors.length > 0 ? { anchors } : {}
|
|
4493
4536
|
};
|
|
4494
4537
|
} finally {
|
|
4495
|
-
await cleanupPicker(page,
|
|
4538
|
+
await cleanupPicker(page, polling);
|
|
4496
4539
|
}
|
|
4497
4540
|
}
|
|
4498
|
-
async function runLocatorPicker(page, testInfo,
|
|
4541
|
+
async function runLocatorPicker(page, testInfo, failed2, probe) {
|
|
4499
4542
|
try {
|
|
4500
4543
|
testInfo.setTimeout(0);
|
|
4501
|
-
const rendered =
|
|
4544
|
+
const rendered = failed2 ? renderFailing(failed2) : null;
|
|
4502
4545
|
console.log(
|
|
4503
4546
|
`
|
|
4504
4547
|
[piwi] "${testInfo.title}" ${testInfo.status} \u2014 ${rendered ? "locator picker" : "inspector"} open in the browser: ${rendered ? `click the element that should replace ${rendered}` : "click any element to generate locators for it"} (\u2191/\u2193 to select a parent/child, Esc to skip).`
|
|
4505
4548
|
);
|
|
4506
4549
|
const pick = await pickLocator(page, probe, rendered);
|
|
4507
4550
|
if (!pick) return null;
|
|
4508
|
-
const location =
|
|
4551
|
+
const location = failed2?.location ?? null;
|
|
4509
4552
|
console.log(
|
|
4510
|
-
|
|
4553
|
+
failed2 ? `[piwi] Replacement picked for ${rendered}${location ? ` at ${location}` : ""}:
|
|
4511
4554
|
[piwi] ${pick.picked.locator}` : `[piwi] Locator picked while inspecting:
|
|
4512
4555
|
[piwi] ${pick.picked.locator}`
|
|
4513
4556
|
);
|
|
4514
4557
|
return {
|
|
4515
|
-
failing:
|
|
4558
|
+
failing: failed2 ? { method: failed2.method, args: failed2.args, rendered, location } : null,
|
|
4516
4559
|
...pick
|
|
4517
4560
|
};
|
|
4518
4561
|
} catch {
|
|
@@ -4800,8 +4843,39 @@ function askAgain(state, asked) {
|
|
|
4800
4843
|
function goOn(state, answer) {
|
|
4801
4844
|
if (answer === "step") state.step = true;
|
|
4802
4845
|
else if (answer === "finish") state.finished = true;
|
|
4846
|
+
else if (answer === "end") {
|
|
4847
|
+
state.finished = true;
|
|
4848
|
+
stopShown(state);
|
|
4849
|
+
}
|
|
4803
4850
|
return wantsStops(state) ? "next" : "resume";
|
|
4804
4851
|
}
|
|
4852
|
+
var ENDED_REASON = "Ended from Piwi";
|
|
4853
|
+
var OPTIONS_AT = {
|
|
4854
|
+
fill: 1,
|
|
4855
|
+
selectOption: 1,
|
|
4856
|
+
press: 1,
|
|
4857
|
+
type: 1,
|
|
4858
|
+
pressSequentially: 1,
|
|
4859
|
+
setInputFiles: 1,
|
|
4860
|
+
dragTo: 1,
|
|
4861
|
+
dispatchEvent: 2,
|
|
4862
|
+
_expect: 1
|
|
4863
|
+
};
|
|
4864
|
+
function withEndSignal(method, args, signal) {
|
|
4865
|
+
const at = OPTIONS_AT[method] ?? 0;
|
|
4866
|
+
const given = args[at];
|
|
4867
|
+
if (given !== void 0 && (typeof given !== "object" || given === null || Array.isArray(given))) return null;
|
|
4868
|
+
const options = given ?? {};
|
|
4869
|
+
let combined = signal;
|
|
4870
|
+
if (options.signal !== void 0) {
|
|
4871
|
+
if (!(options.signal instanceof AbortSignal) || typeof AbortSignal.any !== "function") return null;
|
|
4872
|
+
combined = AbortSignal.any([options.signal, signal]);
|
|
4873
|
+
}
|
|
4874
|
+
const next = [...args];
|
|
4875
|
+
while (next.length < at) next.push(void 0);
|
|
4876
|
+
next[at] = { ...options, signal: combined };
|
|
4877
|
+
return next;
|
|
4878
|
+
}
|
|
4805
4879
|
function contextDebugger(context) {
|
|
4806
4880
|
const dbg = context.debugger;
|
|
4807
4881
|
return typeof dbg?.requestPause === "function" && typeof dbg.on === "function" ? dbg : null;
|
|
@@ -4901,12 +4975,12 @@ function pickPayload(locator, callerLocation) {
|
|
|
4901
4975
|
return at ? { kind: "locator", text: locator, at } : { kind: "locator", text: locator };
|
|
4902
4976
|
}
|
|
4903
4977
|
var postFailureLogged = false;
|
|
4904
|
-
async function postPickToEditor(pairingAddress, payload, fetchImpl = fetch) {
|
|
4978
|
+
async function postPickToEditor(pairingAddress, payload, fetchImpl = fetch, host2 = "the editor") {
|
|
4905
4979
|
if (!pairingAddress) return false;
|
|
4906
4980
|
const logOnce = (why) => {
|
|
4907
4981
|
if (postFailureLogged) return;
|
|
4908
4982
|
postFailureLogged = true;
|
|
4909
|
-
console.log(`[piwi] Could not send the picked locator to
|
|
4983
|
+
console.log(`[piwi] Could not send the picked locator to ${host2}: ${why}.`);
|
|
4910
4984
|
};
|
|
4911
4985
|
const pairing = parsePairing(pairingAddress);
|
|
4912
4986
|
if (!pairing) {
|
|
@@ -4921,7 +4995,7 @@ async function postPickToEditor(pairingAddress, payload, fetchImpl = fetch) {
|
|
|
4921
4995
|
signal: AbortSignal.timeout(5e3)
|
|
4922
4996
|
});
|
|
4923
4997
|
if (response.ok) return true;
|
|
4924
|
-
logOnce(
|
|
4998
|
+
logOnce(`${host2} answered ${response.status}`);
|
|
4925
4999
|
} catch (error) {
|
|
4926
5000
|
logOnce(error instanceof Error ? error.message : String(error));
|
|
4927
5001
|
}
|
|
@@ -4979,6 +5053,33 @@ async function within(run, ms) {
|
|
|
4979
5053
|
clearTimeout(timer);
|
|
4980
5054
|
}
|
|
4981
5055
|
}
|
|
5056
|
+
function eitherSignal(a, b) {
|
|
5057
|
+
const either = new AbortController();
|
|
5058
|
+
const abort = () => either.abort();
|
|
5059
|
+
if (a.aborted || b.aborted) abort();
|
|
5060
|
+
a.addEventListener("abort", abort, { once: true });
|
|
5061
|
+
b.addEventListener("abort", abort, { once: true });
|
|
5062
|
+
return either.signal;
|
|
5063
|
+
}
|
|
5064
|
+
async function viewHere(page, probe, onPick, onRecord, ended, intents, onIntent, viewEnded2) {
|
|
5065
|
+
for (; ; ) {
|
|
5066
|
+
const intent = await unlessEnded(intents.wait(), ended);
|
|
5067
|
+
intents.answered();
|
|
5068
|
+
if (!intent || ended.aborted) return;
|
|
5069
|
+
const signal = eitherSignal(ended, viewEnded2());
|
|
5070
|
+
try {
|
|
5071
|
+
if (intent === "pick") {
|
|
5072
|
+
const picked = await pickLocator(page, probe, null, signal).catch(() => null);
|
|
5073
|
+
if (picked && !signal.aborted) await onPick(picked);
|
|
5074
|
+
} else if (intent === "record") {
|
|
5075
|
+
await onRecord?.(signal);
|
|
5076
|
+
} else {
|
|
5077
|
+
await onIntent?.(intent, signal);
|
|
5078
|
+
}
|
|
5079
|
+
} catch {
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
4982
5083
|
async function pauseHere(page, point, probe, onPick, onRecord, ended, intents, onIntent) {
|
|
4983
5084
|
let shown2 = null;
|
|
4984
5085
|
try {
|
|
@@ -4993,10 +5094,11 @@ async function pauseHere(page, point, probe, onPick, onRecord, ended, intents, o
|
|
|
4993
5094
|
action: point.call,
|
|
4994
5095
|
locator: null,
|
|
4995
5096
|
record: !!onRecord,
|
|
4996
|
-
...point.failure ? { failure: true } : {}
|
|
5097
|
+
...point.failure ? { failure: true } : {},
|
|
5098
|
+
...point.end ? { end: true } : {}
|
|
4997
5099
|
};
|
|
4998
5100
|
console.log(
|
|
4999
|
-
point.failure ? `[piwi] The test failed at ${callSiteLine(point.location)}: Resume, Pick a locator${onRecord ? " or Record here" : ""} in the browser (Esc resumes, and the test ends as it failed).` : `[piwi] Paused at ${callSiteLine(point.location)} before ${point.call}: Resume, Step, Pick a locator${onRecord ? ", Record here" : ""} or
|
|
5101
|
+
point.failure ? `[piwi] The test failed at ${callSiteLine(point.location)}: Resume, Pick a locator${onRecord ? " or Record here" : ""} in the browser (Esc resumes, and the test ends as it failed).` : `[piwi] Paused at ${callSiteLine(point.location)} before ${point.call}: Resume, Step, Pick a locator${onRecord ? ", Record here" : ""}, Finish${point.end ? " or End the test" : ""} in the browser (Esc resumes).`
|
|
5000
5102
|
);
|
|
5001
5103
|
let navigated = 0;
|
|
5002
5104
|
for (; ; ) {
|
|
@@ -5014,7 +5116,7 @@ async function pauseHere(page, point, probe, onPick, onRecord, ended, intents, o
|
|
|
5014
5116
|
if (raised) await unlessEnded(raised, ended);
|
|
5015
5117
|
if (ended?.aborted) return "resume";
|
|
5016
5118
|
navigated = 0;
|
|
5017
|
-
if (choice === "resume" || choice === "step" || choice === "finish") return choice;
|
|
5119
|
+
if (choice === "resume" || choice === "step" || choice === "finish" || choice === "end") return choice;
|
|
5018
5120
|
if (choice === "record" && onRecord) {
|
|
5019
5121
|
await unlessEnded(
|
|
5020
5122
|
onRecord(ended).catch(() => void 0),
|
|
@@ -5099,52 +5201,504 @@ function locatorOn(page, source) {
|
|
|
5099
5201
|
}
|
|
5100
5202
|
}
|
|
5101
5203
|
|
|
5102
|
-
// src/internal/capture/
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5204
|
+
// src/internal/capture/pick-at.ts
|
|
5205
|
+
var LINE = /^(\s*)- ([a-z][\w-]*)(?: "(?:[^"\\]|\\.)*")?((?: \[[^\]\n]*\])*)(?::.*)?$/;
|
|
5206
|
+
var REF = /\[ref=(e\d+)\]/;
|
|
5207
|
+
var NUMBER = "(-?\\d+(?:\\.\\d+)?)";
|
|
5208
|
+
var BOX = new RegExp(`\\[box=${NUMBER},${NUMBER},${NUMBER},${NUMBER}\\]`);
|
|
5209
|
+
function snapshotBoxes(snapshot) {
|
|
5210
|
+
const boxes = [];
|
|
5211
|
+
const open = [];
|
|
5212
|
+
let unit = 0;
|
|
5106
5213
|
for (const line of snapshot.split("\n")) {
|
|
5107
|
-
const
|
|
5108
|
-
if (
|
|
5109
|
-
|
|
5110
|
-
|
|
5214
|
+
const m = LINE.exec(line);
|
|
5215
|
+
if (!m) continue;
|
|
5216
|
+
const indent = m[1].length;
|
|
5217
|
+
if (indent > 0 && unit === 0) unit = indent;
|
|
5218
|
+
while (open.length && open[open.length - 1].indent >= indent) open.pop();
|
|
5219
|
+
const attrs = m[3] ?? "";
|
|
5220
|
+
const ref = REF.exec(attrs)?.[1];
|
|
5221
|
+
const box = BOX.exec(attrs);
|
|
5222
|
+
if (!ref || !box) continue;
|
|
5223
|
+
const [x, y, width, height] = box.slice(1).map(Number);
|
|
5224
|
+
if (!(width > 0) || !(height > 0)) continue;
|
|
5225
|
+
const depth = unit ? Math.round(indent / unit) : 0;
|
|
5226
|
+
boxes.push({ ref, role: m[2], x, y, width, height, depth, ancestors: open.map((o) => o.ref) });
|
|
5227
|
+
open.push({ indent, ref });
|
|
5228
|
+
}
|
|
5229
|
+
return boxes;
|
|
5230
|
+
}
|
|
5231
|
+
function boxesAt(boxes, x, y) {
|
|
5232
|
+
const under = boxes.filter((box) => x >= box.x && x < box.x + box.width && y >= box.y && y < box.y + box.height);
|
|
5233
|
+
const holding = new Set(under.flatMap((box) => box.ancestors));
|
|
5234
|
+
const area = (box) => box.width * box.height;
|
|
5235
|
+
return under.sort(
|
|
5236
|
+
(a, b) => Number(holding.has(a.ref)) - Number(holding.has(b.ref)) || area(a) - area(b) || b.depth - a.depth
|
|
5237
|
+
);
|
|
5238
|
+
}
|
|
5239
|
+
var PIWI_OVERLAY = '[id^="__piwi"], [id^="piwi-"][id$="-host"]';
|
|
5240
|
+
var MAX_UNDER = 24;
|
|
5241
|
+
var MAX_CHECKED = 8;
|
|
5242
|
+
var NORMALIZED_SCORE = 60;
|
|
5243
|
+
var PICK_AT_NEEDS = "Pick on the image needs Playwright 1.64 or later (page.getByRef and the boxes of page.ariaSnapshot): update @playwright/test.";
|
|
5244
|
+
var NO_ELEMENT = "No element there: pick an element of the page, not its background.";
|
|
5245
|
+
var IN_IFRAME = "That point is in an iframe, whose elements Pick on the image does not reach: use Pick in the test's window.";
|
|
5246
|
+
function canPickAt(page) {
|
|
5247
|
+
return typeof page.getByRef === "function";
|
|
5248
|
+
}
|
|
5249
|
+
var hitIn = ({ x, y, overlay, elements }) => {
|
|
5250
|
+
let scope = globalThis.document;
|
|
5251
|
+
let hit = null;
|
|
5252
|
+
for (let depth = 0; scope && depth < 32; depth++) {
|
|
5253
|
+
const within3 = scope;
|
|
5254
|
+
const top = within3.elementsFromPoint(x, y).find((el) => el.getRootNode?.() === within3 && !el.closest?.(overlay));
|
|
5255
|
+
if (!top) break;
|
|
5256
|
+
hit = top;
|
|
5257
|
+
scope = top.shadowRoot ?? null;
|
|
5258
|
+
}
|
|
5259
|
+
if (!hit) return null;
|
|
5260
|
+
for (let node = hit; node; ) {
|
|
5261
|
+
const at = elements.indexOf(node);
|
|
5262
|
+
if (at >= 0) return at;
|
|
5263
|
+
node = node.assignedSlot ?? node.parentElement ?? node.parentNode?.host ?? null;
|
|
5264
|
+
}
|
|
5265
|
+
return -1;
|
|
5266
|
+
};
|
|
5267
|
+
async function pickAtPoint(page, probe, x, y) {
|
|
5268
|
+
if (!canPickAt(page)) return { refusal: PICK_AT_NEEDS };
|
|
5269
|
+
const snapshot = await page.ariaSnapshot({ mode: "ai", boxes: true, timeout: 5e3 });
|
|
5270
|
+
if (!/\[box=/.test(snapshot)) return { refusal: PICK_AT_NEEDS };
|
|
5271
|
+
const under = boxesAt(snapshotBoxes(snapshot), x, y).slice(0, MAX_UNDER);
|
|
5272
|
+
const byRef2 = (ref) => page.getByRef(ref);
|
|
5273
|
+
const handles = await Promise.all(
|
|
5274
|
+
under.map(
|
|
5275
|
+
(box) => byRef2(box.ref).elementHandle({ timeout: 2e3 }).catch(() => null)
|
|
5276
|
+
)
|
|
5277
|
+
);
|
|
5278
|
+
try {
|
|
5279
|
+
const hit = await page.evaluate(hitIn, { x, y, overlay: PIWI_OVERLAY, elements: handles });
|
|
5280
|
+
const tested = hit !== null && hit >= 0;
|
|
5281
|
+
for (const [i, box] of under.entries()) {
|
|
5282
|
+
if (tested && i !== hit) continue;
|
|
5283
|
+
if (box.role === "iframe") return { refusal: IN_IFRAME };
|
|
5284
|
+
if (box.depth === 0 && box.role === "generic") return { refusal: NO_ELEMENT };
|
|
5285
|
+
const handle = handles[i];
|
|
5286
|
+
if (!handle) continue;
|
|
5287
|
+
const piwi = !tested && await handle.evaluate(
|
|
5288
|
+
// A browser-side element, hence the cast: this package has no DOM types.
|
|
5289
|
+
(el, overlay) => !!el.closest(overlay),
|
|
5290
|
+
PIWI_OVERLAY
|
|
5291
|
+
);
|
|
5292
|
+
if (!piwi) return await rank(page, probe, byRef2(box.ref), handle);
|
|
5111
5293
|
}
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5294
|
+
return { refusal: NO_ELEMENT };
|
|
5295
|
+
} finally {
|
|
5296
|
+
await Promise.all(handles.map((handle) => handle?.dispose().catch(() => void 0)));
|
|
5297
|
+
}
|
|
5298
|
+
}
|
|
5299
|
+
async function rankedCandidates(probe, locator, handle, testIdAttribute = null) {
|
|
5300
|
+
const attrs = await handle.evaluate(probe.fn, probe.arg);
|
|
5301
|
+
const accessibleName = approximateAccessibleName({ ...attrs, accessibleName: null });
|
|
5302
|
+
const candidates = generateAlternatives({ ...attrs, accessibleName }, { testIdAttribute });
|
|
5303
|
+
const normalize2 = locator.normalize;
|
|
5304
|
+
const normalized = typeof normalize2 === "function" ? await normalize2.call(locator).then(String, () => null) : null;
|
|
5305
|
+
if (normalized && !candidates.some((c) => c.locator === normalized)) {
|
|
5306
|
+
const leaf = parseLeafLocatorCall(normalized);
|
|
5307
|
+
candidates.push({ locator: normalized, method: leaf?.method ?? "locator", args: {}, score: NORMALIZED_SCORE });
|
|
5308
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
5309
|
+
}
|
|
5310
|
+
return { attrs, accessibleName, candidates };
|
|
5311
|
+
}
|
|
5312
|
+
async function findsAlone(page, candidate, handle) {
|
|
5313
|
+
const built = locatorOn(page, candidate);
|
|
5314
|
+
if (!built || await built.count().catch(() => 0) !== 1) return false;
|
|
5315
|
+
return built.evaluate((el, target) => el === target, handle, { timeout: 2e3 }).catch(() => false);
|
|
5316
|
+
}
|
|
5317
|
+
async function rank(page, probe, locator, handle) {
|
|
5318
|
+
const { attrs, accessibleName, candidates } = await rankedCandidates(probe, locator, handle);
|
|
5319
|
+
let found = null;
|
|
5320
|
+
for (const candidate of candidates.slice(0, MAX_CHECKED)) {
|
|
5321
|
+
if (await findsAlone(page, candidate.locator, handle)) {
|
|
5322
|
+
found = candidate;
|
|
5323
|
+
break;
|
|
5118
5324
|
}
|
|
5119
|
-
kept.push({ line, lostChild: false });
|
|
5120
5325
|
}
|
|
5121
|
-
return
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5326
|
+
if (!found) return { refusal: "Piwi found no locator that finds that element alone on the page." };
|
|
5327
|
+
const picked = { ...found, pickedByUser: true };
|
|
5328
|
+
const element = {
|
|
5329
|
+
tagName: attrs.tagName,
|
|
5330
|
+
attributes: attrs.attributes,
|
|
5331
|
+
textContent: attrs.textContent,
|
|
5332
|
+
accessibleName,
|
|
5333
|
+
center: attrs.center,
|
|
5334
|
+
...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
|
|
5335
|
+
...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
|
|
5336
|
+
};
|
|
5337
|
+
return { picked: { picked, alternatives: [picked, ...candidates.filter((c) => c !== found)], element } };
|
|
5126
5338
|
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5339
|
+
|
|
5340
|
+
// ../core/src/run-view.ts
|
|
5341
|
+
var MAX_VIEW_WIDTH = 1280;
|
|
5342
|
+
var MAX_VIEW_HEIGHT = 800;
|
|
5343
|
+
var VIEW_QUALITY = 60;
|
|
5344
|
+
var MAX_VIEW_FRAME = 384 * 1024;
|
|
5345
|
+
var MAX_VIEW_FRAME_BASE64 = 4 * Math.ceil(MAX_VIEW_FRAME / 3);
|
|
5346
|
+
var MAX_VIEW_FPS = 10;
|
|
5347
|
+
var MAX_VIEW_EVENTS = 50;
|
|
5348
|
+
var MAX_VIEW_EVENT_RATE = 200;
|
|
5349
|
+
var MAX_VIEW_TEXT = 1e3;
|
|
5350
|
+
var MAX_VIEW_COORD = 1e4;
|
|
5351
|
+
var MAX_VIEW_WHEEL = 1e4;
|
|
5352
|
+
var MAX_COMBO_KEYS = 5;
|
|
5353
|
+
var KEY_NAMES = /* @__PURE__ */ new Set([
|
|
5354
|
+
"Escape",
|
|
5355
|
+
"F1",
|
|
5356
|
+
"F2",
|
|
5357
|
+
"F3",
|
|
5358
|
+
"F4",
|
|
5359
|
+
"F5",
|
|
5360
|
+
"F6",
|
|
5361
|
+
"F7",
|
|
5362
|
+
"F8",
|
|
5363
|
+
"F9",
|
|
5364
|
+
"F10",
|
|
5365
|
+
"F11",
|
|
5366
|
+
"F12",
|
|
5367
|
+
"Backquote",
|
|
5368
|
+
"Digit1",
|
|
5369
|
+
"Digit2",
|
|
5370
|
+
"Digit3",
|
|
5371
|
+
"Digit4",
|
|
5372
|
+
"Digit5",
|
|
5373
|
+
"Digit6",
|
|
5374
|
+
"Digit7",
|
|
5375
|
+
"Digit8",
|
|
5376
|
+
"Digit9",
|
|
5377
|
+
"Digit0",
|
|
5378
|
+
"Minus",
|
|
5379
|
+
"Equal",
|
|
5380
|
+
"Backslash",
|
|
5381
|
+
"Backspace",
|
|
5382
|
+
"Tab",
|
|
5383
|
+
"KeyQ",
|
|
5384
|
+
"KeyW",
|
|
5385
|
+
"KeyE",
|
|
5386
|
+
"KeyR",
|
|
5387
|
+
"KeyT",
|
|
5388
|
+
"KeyY",
|
|
5389
|
+
"KeyU",
|
|
5390
|
+
"KeyI",
|
|
5391
|
+
"KeyO",
|
|
5392
|
+
"KeyP",
|
|
5393
|
+
"BracketLeft",
|
|
5394
|
+
"BracketRight",
|
|
5395
|
+
"CapsLock",
|
|
5396
|
+
"KeyA",
|
|
5397
|
+
"KeyS",
|
|
5398
|
+
"KeyD",
|
|
5399
|
+
"KeyF",
|
|
5400
|
+
"KeyG",
|
|
5401
|
+
"KeyH",
|
|
5402
|
+
"KeyJ",
|
|
5403
|
+
"KeyK",
|
|
5404
|
+
"KeyL",
|
|
5405
|
+
"Semicolon",
|
|
5406
|
+
"Quote",
|
|
5407
|
+
"Enter",
|
|
5408
|
+
"ShiftLeft",
|
|
5409
|
+
"Shift",
|
|
5410
|
+
"KeyZ",
|
|
5411
|
+
"KeyX",
|
|
5412
|
+
"KeyC",
|
|
5413
|
+
"KeyV",
|
|
5414
|
+
"KeyB",
|
|
5415
|
+
"KeyN",
|
|
5416
|
+
"KeyM",
|
|
5417
|
+
"Comma",
|
|
5418
|
+
"Period",
|
|
5419
|
+
"Slash",
|
|
5420
|
+
"ShiftRight",
|
|
5421
|
+
"ControlLeft",
|
|
5422
|
+
"Control",
|
|
5423
|
+
"MetaLeft",
|
|
5424
|
+
"Meta",
|
|
5425
|
+
"AltLeft",
|
|
5426
|
+
"Alt",
|
|
5427
|
+
"Space",
|
|
5428
|
+
"AltRight",
|
|
5429
|
+
"AltGraph",
|
|
5430
|
+
"MetaRight",
|
|
5431
|
+
"ContextMenu",
|
|
5432
|
+
"ControlRight",
|
|
5433
|
+
"PrintScreen",
|
|
5434
|
+
"ScrollLock",
|
|
5435
|
+
"Pause",
|
|
5436
|
+
"PageUp",
|
|
5437
|
+
"PageDown",
|
|
5438
|
+
"Insert",
|
|
5439
|
+
"Delete",
|
|
5440
|
+
"Home",
|
|
5441
|
+
"End",
|
|
5442
|
+
"ArrowLeft",
|
|
5443
|
+
"ArrowUp",
|
|
5444
|
+
"ArrowRight",
|
|
5445
|
+
"ArrowDown",
|
|
5446
|
+
"AudioVolumeMute",
|
|
5447
|
+
"AudioVolumeDown",
|
|
5448
|
+
"AudioVolumeUp",
|
|
5449
|
+
"MediaTrackNext",
|
|
5450
|
+
"MediaTrackPrevious",
|
|
5451
|
+
"MediaPlayPause",
|
|
5452
|
+
"NumLock",
|
|
5453
|
+
"NumpadDivide",
|
|
5454
|
+
"NumpadMultiply",
|
|
5455
|
+
"NumpadSubtract",
|
|
5456
|
+
"Numpad7",
|
|
5457
|
+
"Numpad8",
|
|
5458
|
+
"Numpad9",
|
|
5459
|
+
"Numpad4",
|
|
5460
|
+
"Numpad5",
|
|
5461
|
+
"Numpad6",
|
|
5462
|
+
"NumpadAdd",
|
|
5463
|
+
"Numpad1",
|
|
5464
|
+
"Numpad2",
|
|
5465
|
+
"Numpad3",
|
|
5466
|
+
"Numpad0",
|
|
5467
|
+
"NumpadDecimal",
|
|
5468
|
+
"NumpadEnter",
|
|
5469
|
+
"ControlOrMeta"
|
|
5470
|
+
]);
|
|
5471
|
+
var MODIFIERS = /* @__PURE__ */ new Set(["Alt", "Control", "Meta", "Shift", "ControlOrMeta"]);
|
|
5472
|
+
function isViewTextControl(char) {
|
|
5473
|
+
const code = char.charCodeAt(0);
|
|
5474
|
+
return code < 32 && code !== 9 && code !== 10 || code === 127;
|
|
5475
|
+
}
|
|
5476
|
+
function isViewKey(key) {
|
|
5477
|
+
if (typeof key !== "string") return false;
|
|
5478
|
+
if (key.length === 1) return key >= " " && key <= "~";
|
|
5479
|
+
return KEY_NAMES.has(key);
|
|
5480
|
+
}
|
|
5481
|
+
function isViewKeyCombo(combo) {
|
|
5482
|
+
if (typeof combo !== "string" || !combo || combo.length > 64) return false;
|
|
5483
|
+
const keys = [];
|
|
5484
|
+
let building = "";
|
|
5485
|
+
for (const char of combo) {
|
|
5486
|
+
if (char === "+" && building) {
|
|
5487
|
+
keys.push(building);
|
|
5488
|
+
building = "";
|
|
5489
|
+
} else {
|
|
5490
|
+
building += char;
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
keys.push(building);
|
|
5494
|
+
if (keys.length > MAX_COMBO_KEYS) return false;
|
|
5495
|
+
const key = keys.pop();
|
|
5496
|
+
return isViewKey(key) && keys.every((modifier) => MODIFIERS.has(modifier));
|
|
5129
5497
|
}
|
|
5130
|
-
function
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5498
|
+
function isObject2(value) {
|
|
5499
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5500
|
+
}
|
|
5501
|
+
function isNumber(value, min, max) {
|
|
5502
|
+
return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max;
|
|
5503
|
+
}
|
|
5504
|
+
function isViewPoint(x, y) {
|
|
5505
|
+
return isNumber(x, 0, MAX_VIEW_COORD) && isNumber(y, 0, MAX_VIEW_COORD);
|
|
5506
|
+
}
|
|
5507
|
+
var BUTTONS = ["left", "right", "middle"];
|
|
5508
|
+
function readEvent(value) {
|
|
5509
|
+
if (!isObject2(value)) return "is not an event.";
|
|
5510
|
+
const { type, action } = value;
|
|
5511
|
+
if (type === "mouse" || type === "wheel") {
|
|
5512
|
+
const { x, y } = value;
|
|
5513
|
+
if (!isViewPoint(x, y)) return `needs x and y, CSS pixels from 0 to ${MAX_VIEW_COORD}.`;
|
|
5514
|
+
const at = { x, y };
|
|
5515
|
+
if (type === "wheel") {
|
|
5516
|
+
const { deltaX, deltaY } = value;
|
|
5517
|
+
if (!isNumber(deltaX, -MAX_VIEW_WHEEL, MAX_VIEW_WHEEL) || !isNumber(deltaY, -MAX_VIEW_WHEEL, MAX_VIEW_WHEEL)) {
|
|
5518
|
+
return `needs deltaX and deltaY, pixels from -${MAX_VIEW_WHEEL} to ${MAX_VIEW_WHEEL}.`;
|
|
5519
|
+
}
|
|
5520
|
+
return { type, ...at, deltaX, deltaY };
|
|
5521
|
+
}
|
|
5522
|
+
if (action === "move") return { type, action, ...at };
|
|
5523
|
+
if (action !== "down" && action !== "up" && action !== "click") {
|
|
5524
|
+
return "is a mouse event whose action is not move, down, up or click.";
|
|
5525
|
+
}
|
|
5526
|
+
const button = value.button === void 0 ? "left" : value.button;
|
|
5527
|
+
if (typeof button !== "string" || !BUTTONS.includes(button))
|
|
5528
|
+
return "names a button other than left, right or middle.";
|
|
5529
|
+
const clickCount = value.clickCount === void 0 && action !== "click" ? void 0 : value.clickCount ?? 1;
|
|
5530
|
+
if (clickCount !== void 0 && (!isNumber(clickCount, 1, 3) || !Number.isInteger(clickCount))) {
|
|
5531
|
+
return "needs a clickCount from 1 to 3.";
|
|
5532
|
+
}
|
|
5533
|
+
if (action !== "click") {
|
|
5534
|
+
return { type, action, ...at, button, ...clickCount === void 0 ? {} : { clickCount } };
|
|
5137
5535
|
}
|
|
5536
|
+
return { type, action, ...at, button, clickCount };
|
|
5138
5537
|
}
|
|
5139
|
-
if (
|
|
5140
|
-
|
|
5538
|
+
if (type === "key") {
|
|
5539
|
+
if (action !== "down" && action !== "up" && action !== "press") {
|
|
5540
|
+
return "is a key event whose action is not down, up or press.";
|
|
5541
|
+
}
|
|
5542
|
+
const { key } = value;
|
|
5543
|
+
const known = action === "press" ? isViewKeyCombo(key) : isViewKey(key);
|
|
5544
|
+
return known ? { type, action, key } : `names a key Playwright does not play: ${JSON.stringify(String(key).slice(0, 64))}.`;
|
|
5545
|
+
}
|
|
5546
|
+
if (type === "text") {
|
|
5547
|
+
const { text } = value;
|
|
5548
|
+
if (typeof text !== "string" || !text || text.length > MAX_VIEW_TEXT || [...text].some(isViewTextControl)) {
|
|
5549
|
+
return `needs a text of 1 to ${MAX_VIEW_TEXT} characters, with no control character but a tab or a line break.`;
|
|
5550
|
+
}
|
|
5551
|
+
return { type, text };
|
|
5552
|
+
}
|
|
5553
|
+
return "is of a type other than mouse, wheel, key or text.";
|
|
5554
|
+
}
|
|
5555
|
+
function readViewInput(value) {
|
|
5556
|
+
if (!Array.isArray(value) || value.length === 0) return "The input needs its events.";
|
|
5557
|
+
if (value.length > MAX_VIEW_EVENTS) return `The input carries ${MAX_VIEW_EVENTS} events at most.`;
|
|
5558
|
+
const events = [];
|
|
5559
|
+
for (const [i, raw] of value.entries()) {
|
|
5560
|
+
const event = readEvent(raw);
|
|
5561
|
+
if (typeof event === "string") return `Event ${i + 1} ${event}`;
|
|
5562
|
+
events.push(event);
|
|
5563
|
+
}
|
|
5564
|
+
return events;
|
|
5141
5565
|
}
|
|
5566
|
+
function isViewSize(width, height) {
|
|
5567
|
+
return isNumber(width, 1, MAX_VIEW_COORD) && Number.isInteger(width) && isNumber(height, 1, MAX_VIEW_COORD) && Number.isInteger(height);
|
|
5568
|
+
}
|
|
5569
|
+
function viewSize(viewport) {
|
|
5570
|
+
return fitViewSize(viewport, MAX_VIEW_WIDTH, MAX_VIEW_HEIGHT);
|
|
5571
|
+
}
|
|
5572
|
+
function fitViewSize(viewport, maxWidth, maxHeight) {
|
|
5573
|
+
const width = Math.max(2, Math.round(viewport.width));
|
|
5574
|
+
const height = Math.max(2, Math.round(viewport.height));
|
|
5575
|
+
const scale = Math.min(1, maxWidth / width, maxHeight / height);
|
|
5576
|
+
return { width: Math.max(2, Math.floor(width * scale)), height: Math.max(2, Math.floor(height * scale)) };
|
|
5577
|
+
}
|
|
5578
|
+
var RateWindow = class {
|
|
5579
|
+
constructor(limit, now = Date.now) {
|
|
5580
|
+
this.limit = limit;
|
|
5581
|
+
this.now = now;
|
|
5582
|
+
this.start = -Infinity;
|
|
5583
|
+
this.used = 0;
|
|
5584
|
+
}
|
|
5585
|
+
take(n) {
|
|
5586
|
+
const at = this.now();
|
|
5587
|
+
if (at - this.start >= 1e3) {
|
|
5588
|
+
this.start = at;
|
|
5589
|
+
this.used = 0;
|
|
5590
|
+
}
|
|
5591
|
+
if (this.used + n > this.limit) return false;
|
|
5592
|
+
this.used += n;
|
|
5593
|
+
return true;
|
|
5594
|
+
}
|
|
5595
|
+
};
|
|
5596
|
+
var LatestPacer = class {
|
|
5597
|
+
constructor(options) {
|
|
5598
|
+
this.options = options;
|
|
5599
|
+
this.pending = null;
|
|
5600
|
+
this.timer = null;
|
|
5601
|
+
this.last = -Infinity;
|
|
5602
|
+
this.closed = false;
|
|
5603
|
+
this.gap = options.intervalMs;
|
|
5604
|
+
}
|
|
5605
|
+
offer(item) {
|
|
5606
|
+
if (this.closed) return;
|
|
5607
|
+
this.pending = { item };
|
|
5608
|
+
this.pump();
|
|
5609
|
+
}
|
|
5610
|
+
/** Whether an item waits. */
|
|
5611
|
+
get waiting() {
|
|
5612
|
+
return this.pending !== null;
|
|
5613
|
+
}
|
|
5614
|
+
/** Send the item waiting when its time came and the way out is free; else try again later. */
|
|
5615
|
+
pump() {
|
|
5616
|
+
if (this.closed || !this.pending || this.timer) return;
|
|
5617
|
+
const now = this.options.now?.() ?? Date.now();
|
|
5618
|
+
const wait = this.last + this.gap - now;
|
|
5619
|
+
const busy = wait <= 0 && !!this.options.busy?.();
|
|
5620
|
+
if (wait > 0 || busy) {
|
|
5621
|
+
this.timer = setTimeout(
|
|
5622
|
+
() => {
|
|
5623
|
+
this.timer = null;
|
|
5624
|
+
this.pump();
|
|
5625
|
+
},
|
|
5626
|
+
busy ? this.options.intervalMs : wait
|
|
5627
|
+
);
|
|
5628
|
+
this.timer.unref?.();
|
|
5629
|
+
return;
|
|
5630
|
+
}
|
|
5631
|
+
const { item } = this.pending;
|
|
5632
|
+
this.pending = null;
|
|
5633
|
+
this.last = now;
|
|
5634
|
+
this.gap = Math.max(this.options.intervalMs, this.options.spacing?.(item) ?? 0);
|
|
5635
|
+
this.options.send(item);
|
|
5636
|
+
}
|
|
5637
|
+
/** Nothing more is sent: the item waiting is dropped. */
|
|
5638
|
+
close() {
|
|
5639
|
+
this.closed = true;
|
|
5640
|
+
this.pending = null;
|
|
5641
|
+
if (this.timer) clearTimeout(this.timer);
|
|
5642
|
+
this.timer = null;
|
|
5643
|
+
}
|
|
5644
|
+
};
|
|
5645
|
+
var FrameAcks = class {
|
|
5646
|
+
constructor(intervalMs, now = Date.now) {
|
|
5647
|
+
this.intervalMs = intervalMs;
|
|
5648
|
+
this.now = now;
|
|
5649
|
+
this.due = -Infinity;
|
|
5650
|
+
this.waiting = null;
|
|
5651
|
+
this.closed = false;
|
|
5652
|
+
}
|
|
5653
|
+
next() {
|
|
5654
|
+
if (this.closed || this.waiting) return void 0;
|
|
5655
|
+
const now = this.now();
|
|
5656
|
+
this.due = Math.min(Math.max(now, this.due + this.intervalMs), now + this.intervalMs);
|
|
5657
|
+
if (this.due <= now) return void 0;
|
|
5658
|
+
return new Promise((resolve5) => {
|
|
5659
|
+
const timer = setTimeout(() => this.release(), this.due - now);
|
|
5660
|
+
timer.unref?.();
|
|
5661
|
+
this.waiting = { timer, resolve: resolve5 };
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
/** Whether a frame waits for its acknowledgment. */
|
|
5665
|
+
get holding() {
|
|
5666
|
+
return this.waiting !== null;
|
|
5667
|
+
}
|
|
5668
|
+
/** The frame waiting is acknowledged now, and every later one at once. */
|
|
5669
|
+
close() {
|
|
5670
|
+
this.closed = true;
|
|
5671
|
+
this.release();
|
|
5672
|
+
}
|
|
5673
|
+
release() {
|
|
5674
|
+
const waiting = this.waiting;
|
|
5675
|
+
this.waiting = null;
|
|
5676
|
+
if (!waiting) return;
|
|
5677
|
+
clearTimeout(waiting.timer);
|
|
5678
|
+
waiting.resolve();
|
|
5679
|
+
}
|
|
5680
|
+
};
|
|
5142
5681
|
|
|
5143
5682
|
// ../core/src/run-channel.ts
|
|
5144
5683
|
var RUN_CHANNEL_ENV = "PIWI_RUN_CHANNEL";
|
|
5684
|
+
var RUN_REF_ENV = "PIWI_RUN_REF";
|
|
5145
5685
|
var RUN_HOLDS = ["failure"];
|
|
5146
5686
|
var RUN_INTENTS = ["pick", "record", "assert", "assert-region"];
|
|
5147
|
-
var RUN_TAKES = [
|
|
5687
|
+
var RUN_TAKES = [
|
|
5688
|
+
...RUN_INTENTS,
|
|
5689
|
+
"requests",
|
|
5690
|
+
"mock",
|
|
5691
|
+
"locator",
|
|
5692
|
+
"view",
|
|
5693
|
+
"input",
|
|
5694
|
+
"input-clicks",
|
|
5695
|
+
"pick-at",
|
|
5696
|
+
"agent",
|
|
5697
|
+
"events",
|
|
5698
|
+
"bind",
|
|
5699
|
+
"end",
|
|
5700
|
+
"share"
|
|
5701
|
+
];
|
|
5148
5702
|
var RUN_INTENT_NAMES = {
|
|
5149
5703
|
pick: "Pick a locator",
|
|
5150
5704
|
record: "Record here",
|
|
@@ -5155,13 +5709,23 @@ var MAX_KEPT_RESPONSES = 50;
|
|
|
5155
5709
|
var MAX_KEPT_BODY = 256 * 1024;
|
|
5156
5710
|
var MAX_REGION_SNAPSHOT = 1024 * 1024;
|
|
5157
5711
|
var MAX_STOP_ERROR = 1e3;
|
|
5158
|
-
|
|
5712
|
+
var MAX_AGENT_SNAPSHOT = 1024 * 1024;
|
|
5713
|
+
var MAX_SNAPSHOT_DEPTH = 100;
|
|
5714
|
+
var RUN_ACT_ACTIONS = ["click", "fill", "press", "check", "uncheck", "select"];
|
|
5715
|
+
var MAX_ACT_VALUE = 1e4;
|
|
5716
|
+
function isSnapshotRef(ref) {
|
|
5717
|
+
return typeof ref === "string" && /^(?:f\d{1,6})?e\d{1,9}$/.test(ref);
|
|
5718
|
+
}
|
|
5719
|
+
var MAX_ELEMENT_TEXT = 1e3;
|
|
5720
|
+
var MAX_RUN_EVENT_TEXT = 1e3;
|
|
5721
|
+
var MAX_RUN_EVENT_RATE = 50;
|
|
5722
|
+
function isObject3(value) {
|
|
5159
5723
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5160
5724
|
}
|
|
5161
5725
|
function readJson(line) {
|
|
5162
5726
|
try {
|
|
5163
5727
|
const value = JSON.parse(line);
|
|
5164
|
-
return
|
|
5728
|
+
return isObject3(value) ? value : null;
|
|
5165
5729
|
} catch {
|
|
5166
5730
|
return null;
|
|
5167
5731
|
}
|
|
@@ -5173,7 +5737,7 @@ function filePath(value) {
|
|
|
5173
5737
|
return typeof value === "string" && value.length > 0 && value.length <= 4096 && !value.includes("\0");
|
|
5174
5738
|
}
|
|
5175
5739
|
function readPlace(value) {
|
|
5176
|
-
if (!
|
|
5740
|
+
if (!isObject3(value) || !filePath(value.file) || !isInt(value.line, 1)) return null;
|
|
5177
5741
|
if (value.column === void 0) return { file: value.file, line: value.line };
|
|
5178
5742
|
return isInt(value.column, 1) ? { file: value.file, line: value.line, column: value.column } : null;
|
|
5179
5743
|
}
|
|
@@ -5188,6 +5752,18 @@ function isLocatorText(value) {
|
|
|
5188
5752
|
return typeof value === "string" && value.length > 0 && value.length <= 1e3 && !/[\r\n\u2028\u2029]/.test(value);
|
|
5189
5753
|
}
|
|
5190
5754
|
var MAX_PLACES = 1e4;
|
|
5755
|
+
var QUERIES = [
|
|
5756
|
+
"requests",
|
|
5757
|
+
"mock",
|
|
5758
|
+
"locator",
|
|
5759
|
+
"view",
|
|
5760
|
+
"input",
|
|
5761
|
+
"pick-at",
|
|
5762
|
+
"snapshot",
|
|
5763
|
+
"act",
|
|
5764
|
+
"ref-locator",
|
|
5765
|
+
"bind"
|
|
5766
|
+
];
|
|
5191
5767
|
function parseHubToRun(line) {
|
|
5192
5768
|
const value = readJson(line);
|
|
5193
5769
|
if (!value) return null;
|
|
@@ -5195,7 +5771,9 @@ function parseHubToRun(line) {
|
|
|
5195
5771
|
switch (value.type) {
|
|
5196
5772
|
case "attached": {
|
|
5197
5773
|
const holds = readHolds(value.holds);
|
|
5198
|
-
|
|
5774
|
+
if (!holds) return null;
|
|
5775
|
+
const host2 = typeof value.host === "string" && /^[A-Za-z0-9][\w '.-]{0,63}$/.test(value.host) ? value.host : null;
|
|
5776
|
+
return host2 ? { type: "attached", holds, host: host2 } : { type: "attached", holds };
|
|
5199
5777
|
}
|
|
5200
5778
|
case "breakpoints": {
|
|
5201
5779
|
if (!Array.isArray(value.places) || value.places.length > MAX_PLACES) return null;
|
|
@@ -5205,7 +5783,7 @@ function parseHubToRun(line) {
|
|
|
5205
5783
|
case "control": {
|
|
5206
5784
|
const { action, stop } = value;
|
|
5207
5785
|
if (action === "pause") return { type: "control", action };
|
|
5208
|
-
if (action === "step" || action === "resume" || action === "finish") {
|
|
5786
|
+
if (action === "step" || action === "resume" || action === "finish" || action === "end") {
|
|
5209
5787
|
return isInt(stop, 1) ? { type: "control", action, stop } : null;
|
|
5210
5788
|
}
|
|
5211
5789
|
return typeof action === "string" ? unknown(`control ${action}`) : null;
|
|
@@ -5219,95 +5797,331 @@ function parseHubToRun(line) {
|
|
|
5219
5797
|
if (!isInt(request, 1)) return null;
|
|
5220
5798
|
if (what === "image") return { type: "request", request, what: "image" };
|
|
5221
5799
|
if (typeof what !== "string") return null;
|
|
5222
|
-
if (!isIntent(what) && what
|
|
5223
|
-
return unknown(`request ${what}`);
|
|
5224
|
-
}
|
|
5800
|
+
if (!isIntent(what) && !QUERIES.includes(what)) return unknown(`request ${what}`);
|
|
5225
5801
|
if (!isInt(stop, 1)) return null;
|
|
5226
5802
|
if (isIntent(what) || what === "requests") return { type: "request", request, what, stop };
|
|
5227
5803
|
if (what === "mock") return isInt(value.id, 1) ? { type: "request", request, what, stop, id: value.id } : null;
|
|
5804
|
+
if (what === "view")
|
|
5805
|
+
return typeof value.on === "boolean" ? { type: "request", request, what, stop, on: value.on } : null;
|
|
5806
|
+
if (what === "input") {
|
|
5807
|
+
const events = readViewInput(value.events);
|
|
5808
|
+
return typeof events === "string" ? null : { type: "request", request, what, stop, events };
|
|
5809
|
+
}
|
|
5810
|
+
if (what === "pick-at") {
|
|
5811
|
+
const { x, y } = value;
|
|
5812
|
+
return isViewPoint(x, y) ? { type: "request", request, what, stop, x, y } : null;
|
|
5813
|
+
}
|
|
5814
|
+
if (what === "snapshot") {
|
|
5815
|
+
const { depth, image } = value;
|
|
5816
|
+
if (depth !== null && (!isInt(depth, 1) || depth > MAX_SNAPSHOT_DEPTH) || typeof image !== "boolean") {
|
|
5817
|
+
return null;
|
|
5818
|
+
}
|
|
5819
|
+
return { type: "request", request, what, stop, depth, image };
|
|
5820
|
+
}
|
|
5821
|
+
if (what === "act") {
|
|
5822
|
+
const act = readAct(value);
|
|
5823
|
+
return act ? { type: "request", request, what, stop, ...act } : null;
|
|
5824
|
+
}
|
|
5825
|
+
if (what === "ref-locator") {
|
|
5826
|
+
return isSnapshotRef(value.ref) ? { type: "request", request, what, stop, ref: value.ref } : null;
|
|
5827
|
+
}
|
|
5828
|
+
if (what === "bind") {
|
|
5829
|
+
return typeof value.on === "boolean" ? { type: "request", request, what, stop, on: value.on } : null;
|
|
5830
|
+
}
|
|
5228
5831
|
const { locator } = value;
|
|
5229
|
-
return locator === null || isLocatorText(locator) ? { type: "request", request, what, stop, locator } : null;
|
|
5832
|
+
return locator === null || isLocatorText(locator) ? { type: "request", request, what: "locator", stop, locator } : null;
|
|
5230
5833
|
}
|
|
5834
|
+
case "events":
|
|
5835
|
+
return typeof value.on === "boolean" ? { type: "events", on: value.on } : null;
|
|
5836
|
+
case "share":
|
|
5837
|
+
return typeof value.on === "boolean" ? { type: "share", on: value.on } : null;
|
|
5231
5838
|
default:
|
|
5232
5839
|
return typeof value.type === "string" && value.type ? unknown(value.type) : null;
|
|
5233
5840
|
}
|
|
5234
5841
|
}
|
|
5842
|
+
function readAct(value) {
|
|
5843
|
+
if (!isObject3(value) || !isSnapshotRef(value.ref)) return null;
|
|
5844
|
+
const action = value.action;
|
|
5845
|
+
if (typeof action !== "string" || !RUN_ACT_ACTIONS.includes(action)) return null;
|
|
5846
|
+
const given = value.value;
|
|
5847
|
+
switch (action) {
|
|
5848
|
+
case "fill":
|
|
5849
|
+
return typeof given === "string" && given.length <= MAX_ACT_VALUE ? { ref: value.ref, action: "fill", value: given } : null;
|
|
5850
|
+
case "press":
|
|
5851
|
+
return isViewKeyCombo(given) ? { ref: value.ref, action: "press", value: given } : null;
|
|
5852
|
+
case "select":
|
|
5853
|
+
return typeof given === "string" && given.length <= MAX_ACT_VALUE && !/[\r\n\0]/.test(given) ? { ref: value.ref, action: "select", value: given } : null;
|
|
5854
|
+
default:
|
|
5855
|
+
return given === null || given === void 0 ? { ref: value.ref, action, value: null } : null;
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5235
5858
|
|
|
5236
|
-
// src/internal/capture/
|
|
5237
|
-
var
|
|
5238
|
-
var
|
|
5239
|
-
|
|
5240
|
-
|
|
5859
|
+
// src/internal/capture/agent-page.ts
|
|
5860
|
+
var ACT_MS = 5e3;
|
|
5861
|
+
var SECRET_FIELDS = [
|
|
5862
|
+
'input[type="password" i]',
|
|
5863
|
+
...SECRET_AUTOCOMPLETE.flatMap((token) => [
|
|
5864
|
+
`input[autocomplete~="${token}" i]`,
|
|
5865
|
+
`textarea[autocomplete~="${token}" i]`
|
|
5866
|
+
])
|
|
5867
|
+
].join(", ");
|
|
5868
|
+
var BAR_HIDDEN = "#__piwi-pause-bar { display: none !important; }";
|
|
5869
|
+
var AGENT_NEEDS = "An agent reads and acts on a stopped page with Playwright 1.64 or later (page.getByRef and the AI snapshot): update @playwright/test.";
|
|
5870
|
+
function canActByRef(page) {
|
|
5871
|
+
return typeof page.getByRef === "function";
|
|
5872
|
+
}
|
|
5873
|
+
function byRef(page, ref) {
|
|
5874
|
+
return page.getByRef(ref);
|
|
5875
|
+
}
|
|
5876
|
+
function failed(prefix, error) {
|
|
5877
|
+
const first = errorMessage(error).split("\n")[0].trim();
|
|
5878
|
+
return hideSecretsInText(`${prefix}: ${first}`).slice(0, 300);
|
|
5879
|
+
}
|
|
5880
|
+
var MAX_FRAMES = 32;
|
|
5881
|
+
async function fieldSecrets(page) {
|
|
5882
|
+
const values = await Promise.all(
|
|
5883
|
+
page.frames().slice(0, MAX_FRAMES).map(
|
|
5884
|
+
(frame) => frame.locator(SECRET_FIELDS).evaluateAll((fields) => fields.map((field) => field.value)).catch(() => [])
|
|
5885
|
+
)
|
|
5886
|
+
);
|
|
5887
|
+
return [...new Set(values.flat().filter((v) => typeof v === "string" && v.length > 0))];
|
|
5888
|
+
}
|
|
5889
|
+
var VALUED_NODE = /^(\s*- [\w-]+(?: "(?:[^"\\]|\\.)*")?(?: \[[^\]]*\])*): (.+)$/;
|
|
5890
|
+
var ESCAPES = {
|
|
5891
|
+
"\\": "\\\\",
|
|
5892
|
+
'"': '\\"',
|
|
5893
|
+
"\b": "\\b",
|
|
5894
|
+
"\f": "\\f",
|
|
5895
|
+
"\n": "\\n",
|
|
5896
|
+
"\r": "\\r",
|
|
5897
|
+
" ": "\\t"
|
|
5898
|
+
};
|
|
5899
|
+
function escapedInQuotes(value) {
|
|
5900
|
+
let out = "";
|
|
5901
|
+
for (const c of value) {
|
|
5902
|
+
const code = c.charCodeAt(0);
|
|
5903
|
+
const control2 = code < 32 || code >= 127 && code <= 159;
|
|
5904
|
+
out += ESCAPES[c] ?? (control2 ? `\\x${code.toString(16).padStart(2, "0")}` : c);
|
|
5905
|
+
}
|
|
5906
|
+
return out;
|
|
5241
5907
|
}
|
|
5242
|
-
function
|
|
5908
|
+
function withoutFieldSecrets(snapshot, secrets) {
|
|
5909
|
+
const values = [...new Set(secrets.filter(Boolean))];
|
|
5910
|
+
if (!values.length) return snapshot;
|
|
5911
|
+
const shown2 = new Set(values.flatMap((value) => [value, `"${escapedInQuotes(value)}"`]));
|
|
5912
|
+
let out = snapshot.split("\n").map((line) => {
|
|
5913
|
+
const node = VALUED_NODE.exec(line);
|
|
5914
|
+
return node && shown2.has(node[2].trim()) ? `${node[1]}: ${HIDDEN_VALUE}` : line;
|
|
5915
|
+
}).join("\n");
|
|
5916
|
+
for (const value of values) {
|
|
5917
|
+
if (value.length < 8) continue;
|
|
5918
|
+
for (const written of /* @__PURE__ */ new Set([value, escapedInQuotes(value)])) out = out.split(written).join(HIDDEN_VALUE);
|
|
5919
|
+
}
|
|
5920
|
+
return out;
|
|
5921
|
+
}
|
|
5922
|
+
async function readSnapshot(page, depth, image) {
|
|
5923
|
+
const none = { url: "", title: "", snapshot: null, cut: false, image: null };
|
|
5924
|
+
if (page.isClosed()) return { ok: false, message: "The stopped page closed.", ...none };
|
|
5925
|
+
const url = hideSecretsInUrl(page.url()).slice(0, 8192);
|
|
5926
|
+
const title = hideSecretsInText(await page.title().catch(() => "")).slice(0, 1e3);
|
|
5927
|
+
let snapshot;
|
|
5243
5928
|
try {
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5929
|
+
snapshot = await page.ariaSnapshot({ mode: "ai", timeout: ACT_MS, ...depth === null ? {} : { depth } });
|
|
5930
|
+
} catch (error) {
|
|
5931
|
+
return { ok: false, message: failed("The stopped page could not be read", error), ...none, url, title };
|
|
5932
|
+
}
|
|
5933
|
+
const hidden2 = hideSecretsInText(withoutFieldSecrets(snapshot, await fieldSecrets(page)));
|
|
5934
|
+
const cut = hidden2.length > MAX_AGENT_SNAPSHOT;
|
|
5935
|
+
let shot = null;
|
|
5936
|
+
if (image) {
|
|
5937
|
+
const data = await page.screenshot({
|
|
5938
|
+
type: "jpeg",
|
|
5939
|
+
quality: 60,
|
|
5940
|
+
style: BAR_HIDDEN,
|
|
5941
|
+
mask: page.frames().slice(0, MAX_FRAMES).map((frame) => frame.locator(SECRET_FIELDS)),
|
|
5942
|
+
timeout: ACT_MS
|
|
5943
|
+
}).catch(() => null);
|
|
5944
|
+
const viewport = page.viewportSize();
|
|
5945
|
+
if (data) shot = { mime: "image/jpeg", data: data.toString("base64"), ...viewport };
|
|
5248
5946
|
}
|
|
5947
|
+
return {
|
|
5948
|
+
ok: true,
|
|
5949
|
+
message: image && !shot ? "The stopped page, without an image: none could be taken." : "The stopped page.",
|
|
5950
|
+
url,
|
|
5951
|
+
title,
|
|
5952
|
+
snapshot: cut ? hidden2.slice(0, MAX_AGENT_SNAPSHOT) : hidden2,
|
|
5953
|
+
cut,
|
|
5954
|
+
image: shot
|
|
5955
|
+
};
|
|
5249
5956
|
}
|
|
5250
|
-
|
|
5251
|
-
|
|
5957
|
+
var heldIn = (el, args) => {
|
|
5958
|
+
const field = el;
|
|
5959
|
+
const tag = (field.tagName ?? "").toLowerCase();
|
|
5960
|
+
const control2 = tag === "input" || tag === "textarea" || tag === "select";
|
|
5961
|
+
const type = typeof field.type === "string" ? field.type.toLowerCase() : null;
|
|
5962
|
+
const secret = typeof field.matches === "function" && field.matches(args.secretFields);
|
|
5963
|
+
const words = typeof field.textContent === "string" ? field.textContent.replace(/\u200b/g, "").replace(/\s+/g, " ").trim() : "";
|
|
5964
|
+
const value = control2 && !secret && typeof field.value === "string" ? field.value : null;
|
|
5965
|
+
return {
|
|
5966
|
+
secret,
|
|
5967
|
+
text: words ? { read: words.slice(0, args.read), length: words.length } : null,
|
|
5968
|
+
value: value === null ? null : { read: value.slice(0, args.read), length: value.length },
|
|
5969
|
+
checked: tag === "input" && (type === "checkbox" || type === "radio") ? field.checked === true : null
|
|
5970
|
+
};
|
|
5971
|
+
};
|
|
5972
|
+
var READ_PAST = 1e3;
|
|
5973
|
+
function hiddenAndCut(held2, max) {
|
|
5974
|
+
const hidden2 = hideSecretsInText(held2.read);
|
|
5975
|
+
return { text: hidden2.slice(0, max), cut: held2.length > max || hidden2.length > max };
|
|
5976
|
+
}
|
|
5977
|
+
async function ranked(page, probe, locator, handle, testIdAttribute) {
|
|
5978
|
+
const { attrs, accessibleName, candidates } = await rankedCandidates(probe, locator, handle, testIdAttribute);
|
|
5979
|
+
const held2 = await handle.evaluate(heldIn, { secretFields: SECRET_FIELDS, read: MAX_ACT_VALUE + READ_PAST });
|
|
5980
|
+
const checked = [];
|
|
5981
|
+
for (const candidate of candidates.slice(0, MAX_CHECKED)) {
|
|
5982
|
+
checked.push({
|
|
5983
|
+
locator: candidate.locator,
|
|
5984
|
+
method: candidate.method,
|
|
5985
|
+
score: candidate.score,
|
|
5986
|
+
unique: await findsAlone(page, candidate.locator, handle)
|
|
5987
|
+
});
|
|
5988
|
+
}
|
|
5989
|
+
const locators = [...checked.filter((l) => l.unique), ...checked.filter((l) => !l.unique)];
|
|
5990
|
+
const { secret } = held2;
|
|
5991
|
+
const text = held2.text ? hiddenAndCut(held2.text, MAX_ELEMENT_TEXT) : null;
|
|
5992
|
+
const value = held2.value && !secret ? hiddenAndCut(held2.value, MAX_ACT_VALUE) : null;
|
|
5993
|
+
const role = resolveAriaRole({ ...attrs, accessibleName });
|
|
5994
|
+
return {
|
|
5995
|
+
element: {
|
|
5996
|
+
tagName: attrs.tagName.toLowerCase(),
|
|
5997
|
+
role,
|
|
5998
|
+
name: accessibleName ? hideSecretsInText(accessibleName).slice(0, 1e3) : null,
|
|
5999
|
+
testId: attrs.attributes[testIdAttribute ?? "data-testid"] ?? null,
|
|
6000
|
+
text: text?.text ?? null,
|
|
6001
|
+
textCut: text?.cut ?? false,
|
|
6002
|
+
value: value?.text ?? null,
|
|
6003
|
+
valueCut: value?.cut ?? false,
|
|
6004
|
+
checked: held2.checked,
|
|
6005
|
+
secret
|
|
6006
|
+
},
|
|
6007
|
+
locators
|
|
6008
|
+
};
|
|
5252
6009
|
}
|
|
5253
|
-
|
|
5254
|
-
|
|
6010
|
+
var NO_REF = (ref) => `No element of the stopped page has the ref ${ref}: take the page's snapshot again, its refs may have changed.`;
|
|
6011
|
+
async function handleOf(page, ref) {
|
|
6012
|
+
return byRef(page, ref).elementHandle({ timeout: 2e3 }).catch(() => null);
|
|
5255
6013
|
}
|
|
5256
|
-
async function
|
|
5257
|
-
|
|
5258
|
-
|
|
6014
|
+
async function refLocators(page, probe, ref, testIdAttribute) {
|
|
6015
|
+
if (!canActByRef(page)) return { ok: false, message: AGENT_NEEDS, element: null, locators: [] };
|
|
6016
|
+
const handle = await handleOf(page, ref);
|
|
6017
|
+
if (!handle) return { ok: false, message: NO_REF(ref), element: null, locators: [] };
|
|
5259
6018
|
try {
|
|
5260
|
-
const
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
6019
|
+
const { element, locators } = await ranked(page, probe, byRef(page, ref), handle, testIdAttribute);
|
|
6020
|
+
const alone = locators.filter((l) => l.unique).length;
|
|
6021
|
+
return {
|
|
6022
|
+
ok: true,
|
|
6023
|
+
message: alone ? `${alone === 1 ? "1 locator finds" : `${alone} locators find`} that element alone.` : "No locator Piwi tried finds that element alone on the page.",
|
|
6024
|
+
element,
|
|
6025
|
+
locators
|
|
6026
|
+
};
|
|
6027
|
+
} catch (error) {
|
|
6028
|
+
return { ok: false, message: failed("The element could not be read", error), element: null, locators: [] };
|
|
6029
|
+
} finally {
|
|
6030
|
+
await handle.dispose().catch(() => void 0);
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
async function play(locator, action, value) {
|
|
6034
|
+
const options = { timeout: ACT_MS };
|
|
6035
|
+
switch (action) {
|
|
6036
|
+
case "click":
|
|
6037
|
+
return locator.click(options);
|
|
6038
|
+
case "fill":
|
|
6039
|
+
return locator.fill(value ?? "", options);
|
|
6040
|
+
case "press":
|
|
6041
|
+
return locator.press(value ?? "", options);
|
|
6042
|
+
case "check":
|
|
6043
|
+
return locator.check(options);
|
|
6044
|
+
case "uncheck":
|
|
6045
|
+
return locator.uncheck(options);
|
|
6046
|
+
case "select":
|
|
6047
|
+
await locator.selectOption(value ?? "", options);
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
function barLetsThrough(through) {
|
|
6051
|
+
const bar = globalThis.document?.getElementById("__piwi-pause-bar");
|
|
6052
|
+
if (bar) bar.style.pointerEvents = through ? "none" : "";
|
|
6053
|
+
}
|
|
6054
|
+
var PLAYED = {
|
|
6055
|
+
click: "Clicked",
|
|
6056
|
+
fill: "Filled",
|
|
6057
|
+
press: "Pressed a key on",
|
|
6058
|
+
check: "Checked",
|
|
6059
|
+
uncheck: "Unchecked",
|
|
6060
|
+
select: "Selected an option of"
|
|
6061
|
+
};
|
|
6062
|
+
async function actOnRef(page, probe, act, testIdAttribute) {
|
|
6063
|
+
if (!canActByRef(page)) return { ok: false, message: AGENT_NEEDS, element: null, locators: [] };
|
|
6064
|
+
const handle = await handleOf(page, act.ref);
|
|
6065
|
+
if (!handle) return { ok: false, message: NO_REF(act.ref), element: null, locators: [] };
|
|
6066
|
+
let before;
|
|
6067
|
+
try {
|
|
6068
|
+
before = await ranked(page, probe, byRef(page, act.ref), handle, testIdAttribute);
|
|
6069
|
+
} catch (error) {
|
|
6070
|
+
return { ok: false, message: failed("The element could not be read", error), element: null, locators: [] };
|
|
6071
|
+
} finally {
|
|
6072
|
+
await handle.dispose().catch(() => void 0);
|
|
6073
|
+
}
|
|
6074
|
+
try {
|
|
6075
|
+
await page.evaluate(barLetsThrough, true).catch(() => void 0);
|
|
6076
|
+
await play(byRef(page, act.ref), act.action, act.value);
|
|
6077
|
+
} catch (error) {
|
|
6078
|
+
return { ok: false, message: failed(`${PLAYED[act.action]} ${act.ref} failed`, error), ...before };
|
|
6079
|
+
} finally {
|
|
6080
|
+
await page.evaluate(barLetsThrough, false).catch(() => void 0);
|
|
5276
6081
|
}
|
|
6082
|
+
return { ok: true, message: `${PLAYED[act.action]} ${act.ref}.`, ...before };
|
|
5277
6083
|
}
|
|
5278
|
-
function
|
|
5279
|
-
return
|
|
5280
|
-
id,
|
|
5281
|
-
method,
|
|
5282
|
-
url,
|
|
5283
|
-
status,
|
|
5284
|
-
type,
|
|
5285
|
-
size,
|
|
5286
|
-
time
|
|
5287
|
-
}));
|
|
6084
|
+
function canBind(browser) {
|
|
6085
|
+
return typeof browser?.bind === "function";
|
|
5288
6086
|
}
|
|
5289
|
-
async function
|
|
5290
|
-
const
|
|
5291
|
-
if (!
|
|
5292
|
-
|
|
6087
|
+
async function bindBrowser(browser, name, workspaceDir, on) {
|
|
6088
|
+
const bindable = browser;
|
|
6089
|
+
if (!bindable || !canBind(browser)) {
|
|
6090
|
+
return {
|
|
6091
|
+
ok: false,
|
|
6092
|
+
message: "Sharing with Playwright tools needs Playwright 1.59 or later (browser.bind): update @playwright/test.",
|
|
6093
|
+
name: null,
|
|
6094
|
+
endpoint: null
|
|
6095
|
+
};
|
|
6096
|
+
}
|
|
5293
6097
|
try {
|
|
5294
|
-
|
|
5295
|
-
|
|
6098
|
+
if (!on) {
|
|
6099
|
+
await bindable.unbind?.();
|
|
6100
|
+
return {
|
|
6101
|
+
ok: true,
|
|
6102
|
+
message: "The browser is no longer shared with Playwright tools.",
|
|
6103
|
+
name: null,
|
|
6104
|
+
endpoint: null
|
|
6105
|
+
};
|
|
6106
|
+
}
|
|
6107
|
+
const { endpoint } = await bindable.bind(name, { workspaceDir });
|
|
5296
6108
|
return {
|
|
5297
|
-
|
|
6109
|
+
ok: true,
|
|
6110
|
+
message: `The browser is shared with Playwright tools as ${name} until the test ends: any process of yours can drive it.`,
|
|
6111
|
+
name,
|
|
6112
|
+
endpoint: endpoint.slice(0, 4096)
|
|
5298
6113
|
};
|
|
6114
|
+
} catch (error) {
|
|
6115
|
+
return { ok: false, message: failed("The browser could not be shared", error), name: null, endpoint: null };
|
|
5299
6116
|
}
|
|
5300
|
-
if (body.length > MAX_KEPT_BODY) return { refusal: "This answer is larger than 256 KB: Piwi mocks a smaller one." };
|
|
5301
|
-
const text = textOf2(body);
|
|
5302
|
-
if (text === null) return { refusal: "This answer is not text: Piwi mocks a text answer." };
|
|
5303
|
-
return { method: found.method, url: found.url, status: found.status, type: found.type, body: text };
|
|
5304
6117
|
}
|
|
5305
6118
|
|
|
5306
|
-
// src/internal/capture/
|
|
5307
|
-
var fs6 = __toESM(require("fs"));
|
|
5308
|
-
var net = __toESM(require("net"));
|
|
6119
|
+
// src/internal/capture/run-events.ts
|
|
5309
6120
|
var path9 = __toESM(require("path"));
|
|
5310
6121
|
|
|
6122
|
+
// src/internal/capture/run-channel.ts
|
|
6123
|
+
var net = __toESM(require("net"));
|
|
6124
|
+
|
|
5311
6125
|
// ../core/src/ide-recorder.ts
|
|
5312
6126
|
var IDE_RECORDER_BINDING = "__piwiRecorder";
|
|
5313
6127
|
var IDE_DISPATCH_GLOBAL = "__piwiIdeDispatch";
|
|
@@ -5318,10 +6132,10 @@ function parseRecordChannel(value) {
|
|
|
5318
6132
|
const hash = text.lastIndexOf("#");
|
|
5319
6133
|
if (hash <= 0) return null;
|
|
5320
6134
|
const token = text.slice(hash + 1);
|
|
5321
|
-
const
|
|
5322
|
-
return /^[A-Za-z0-9_-]{16,}$/.test(token) && !
|
|
6135
|
+
const path16 = text.slice(0, hash);
|
|
6136
|
+
return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path16.includes("\0") ? { path: path16, token } : null;
|
|
5323
6137
|
}
|
|
5324
|
-
function
|
|
6138
|
+
function isObject4(value) {
|
|
5325
6139
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5326
6140
|
}
|
|
5327
6141
|
function parseHandoffToTest(line) {
|
|
@@ -5331,7 +6145,7 @@ function parseHandoffToTest(line) {
|
|
|
5331
6145
|
} catch {
|
|
5332
6146
|
return null;
|
|
5333
6147
|
}
|
|
5334
|
-
if (!
|
|
6148
|
+
if (!isObject4(value)) return null;
|
|
5335
6149
|
switch (value.type) {
|
|
5336
6150
|
case "refused":
|
|
5337
6151
|
return typeof value.message === "string" ? { type: "refused", message: value.message } : null;
|
|
@@ -5363,205 +6177,803 @@ function lineReader(onLine, onOverflow) {
|
|
|
5363
6177
|
};
|
|
5364
6178
|
}
|
|
5365
6179
|
|
|
5366
|
-
// src/internal/capture/
|
|
5367
|
-
var
|
|
5368
|
-
var
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
6180
|
+
// src/internal/capture/kept-responses.ts
|
|
6181
|
+
var keeping = null;
|
|
6182
|
+
var TEXT_TYPE = /^(?:text\/[\w.+-]+|application\/(?:[\w.+-]+\+)?(?:json|xml)|application\/(?:javascript|x-www-form-urlencoded|graphql))$/i;
|
|
6183
|
+
function mimeOf(contentType) {
|
|
6184
|
+
return (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
6185
|
+
}
|
|
6186
|
+
function textOf2(body) {
|
|
6187
|
+
try {
|
|
6188
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
6189
|
+
return text.includes("\0") ? null : text;
|
|
6190
|
+
} catch {
|
|
6191
|
+
return null;
|
|
5377
6192
|
}
|
|
5378
|
-
return { ok: false, error: "No recording runs in this page." };
|
|
5379
6193
|
}
|
|
5380
|
-
function
|
|
5381
|
-
|
|
6194
|
+
function startKeeping(now = Date.now()) {
|
|
6195
|
+
keeping = { startedAt: now, kept: [], last: 0 };
|
|
5382
6196
|
}
|
|
5383
|
-
function
|
|
5384
|
-
|
|
5385
|
-
if (!m) return null;
|
|
5386
|
-
return { file: path9.resolve(cwd, m[1]), line: Number(m[2]) };
|
|
6197
|
+
function stopKeeping() {
|
|
6198
|
+
keeping = null;
|
|
5387
6199
|
}
|
|
5388
|
-
async function
|
|
5389
|
-
const
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
const
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
6200
|
+
async function keepResponse(request, response) {
|
|
6201
|
+
const into = keeping;
|
|
6202
|
+
if (!into || !response) return;
|
|
6203
|
+
try {
|
|
6204
|
+
const kind = request.resourceType();
|
|
6205
|
+
if (kind !== "fetch" && kind !== "xhr") return;
|
|
6206
|
+
const url = hideSecretsInUrl(request.url());
|
|
6207
|
+
const method = request.method().toUpperCase();
|
|
6208
|
+
if (url.length > 8192 || !/^[A-Z]{1,16}$/.test(method)) return;
|
|
6209
|
+
const headers = response.headers();
|
|
6210
|
+
const type = mimeOf(headers["content-type"]);
|
|
6211
|
+
if (type && !TEXT_TYPE.test(type)) return;
|
|
6212
|
+
const status = response.status();
|
|
6213
|
+
if (status < 100 || status > 599) return;
|
|
6214
|
+
const time = Math.max(0, Math.round(Date.now() - into.startedAt));
|
|
6215
|
+
const size = /^\d+$/.test(headers["content-length"] ?? "") ? Number(headers["content-length"]) : null;
|
|
6216
|
+
if (size !== null && size > MAX_KEPT_BODY) return;
|
|
6217
|
+
into.kept.push({ id: ++into.last, method, url, status, type, size, time, response });
|
|
6218
|
+
if (into.kept.length > MAX_KEPT_RESPONSES) into.kept.splice(0, into.kept.length - MAX_KEPT_RESPONSES);
|
|
6219
|
+
} catch {
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
function keptRequests() {
|
|
6223
|
+
return (keeping?.kept ?? []).map(({ id, method, url, status, type, size, time }) => ({
|
|
6224
|
+
id,
|
|
6225
|
+
method,
|
|
6226
|
+
url,
|
|
6227
|
+
status,
|
|
6228
|
+
type,
|
|
6229
|
+
size,
|
|
6230
|
+
time
|
|
6231
|
+
}));
|
|
6232
|
+
}
|
|
6233
|
+
async function keptResponse(id) {
|
|
6234
|
+
const found = keeping?.kept.find((kept) => kept.id === id);
|
|
6235
|
+
if (!found) return { refusal: "The test kept no such response: it keeps the latest 50." };
|
|
6236
|
+
let body;
|
|
6237
|
+
try {
|
|
6238
|
+
body = await internalCall(found.response, () => found.response.body());
|
|
6239
|
+
} catch {
|
|
6240
|
+
return {
|
|
6241
|
+
refusal: "The browser no longer holds this answer's body: mock one the page received since it last loaded."
|
|
5413
6242
|
};
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
6243
|
+
}
|
|
6244
|
+
if (body.length > MAX_KEPT_BODY) return { refusal: "This answer is larger than 256 KB: Piwi mocks a smaller one." };
|
|
6245
|
+
const text = textOf2(body);
|
|
6246
|
+
if (text === null) return { refusal: "This answer is not text: Piwi mocks a text answer." };
|
|
6247
|
+
return { method: found.method, url: found.url, status: found.status, type: found.type, body: text };
|
|
6248
|
+
}
|
|
6249
|
+
|
|
6250
|
+
// src/internal/capture/live-view.ts
|
|
6251
|
+
var http = __toESM(require("http"));
|
|
6252
|
+
var https = __toESM(require("https"));
|
|
6253
|
+
|
|
6254
|
+
// ../core/src/live-view.ts
|
|
6255
|
+
var LIVE_VIEW_ENV = "PIWI_LIVE_VIEW";
|
|
6256
|
+
var LIVE_FRAME_WIDTH = 640;
|
|
6257
|
+
var LIVE_FRAME_HEIGHT = 400;
|
|
6258
|
+
var LIVE_FRAME_QUALITY = 50;
|
|
6259
|
+
var LIVE_FRAME_FPS = 4;
|
|
6260
|
+
var LIVE_FRAME_BYTE_RATE = 256 * 1024;
|
|
6261
|
+
var MAX_LIVE_FRAME = 192 * 1024;
|
|
6262
|
+
var MAX_LIVE_FRAME_BASE64 = 4 * Math.ceil(MAX_LIVE_FRAME / 3);
|
|
6263
|
+
var MAX_LIVE_MESSAGE = MAX_LIVE_FRAME_BASE64 + 8 * 1024;
|
|
6264
|
+
var KEY = /^pdl_[A-Za-z0-9_-]{43}$/;
|
|
6265
|
+
function isLiveViewKey(value) {
|
|
6266
|
+
return typeof value === "string" && KEY.test(value);
|
|
6267
|
+
}
|
|
6268
|
+
function parseLiveViewEnv(value) {
|
|
6269
|
+
if (!value || value.length > 4096) return null;
|
|
6270
|
+
const hash = value.lastIndexOf("#");
|
|
6271
|
+
if (hash < 0) return null;
|
|
6272
|
+
const key = value.slice(hash + 1);
|
|
6273
|
+
if (!isLiveViewKey(key)) return null;
|
|
6274
|
+
let url;
|
|
6275
|
+
try {
|
|
6276
|
+
url = new URL(value.slice(0, hash));
|
|
6277
|
+
} catch {
|
|
6278
|
+
return null;
|
|
6279
|
+
}
|
|
6280
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
6281
|
+
if (url.search || url.username || url.password) return null;
|
|
6282
|
+
const run = /\/api\/test-runs\/(\d{1,15})\/live-view$/.exec(url.pathname);
|
|
6283
|
+
if (!run) return null;
|
|
6284
|
+
const runId = Number(run[1]);
|
|
6285
|
+
if (!Number.isSafeInteger(runId) || runId < 1) return null;
|
|
6286
|
+
return { url: url.toString(), runId, key };
|
|
6287
|
+
}
|
|
6288
|
+
function liveRunPage(address) {
|
|
6289
|
+
const url = new URL(address.url);
|
|
6290
|
+
url.pathname = url.pathname.replace(/\/api\/test-runs\/\d+\/live-view$/, `/test-runs/${address.runId}`);
|
|
6291
|
+
url.hash = "";
|
|
6292
|
+
return url.toString();
|
|
6293
|
+
}
|
|
6294
|
+
var MISSING = ["no-instance", "unregistered", "off", "local"];
|
|
6295
|
+
function parseLiveViewMissing(value) {
|
|
6296
|
+
const why = value?.startsWith("none:") ? value.slice(5) : null;
|
|
6297
|
+
return why && MISSING.includes(why) ? why : null;
|
|
6298
|
+
}
|
|
6299
|
+
var LIVE_SHARE_MISSING = {
|
|
6300
|
+
"no-instance": "This run cannot be shared live: it reports to no Piwi instance. Name your team's instance in PIWI_DASHBOARD_URL.",
|
|
6301
|
+
unregistered: "This run cannot be shared live: the reporter's global setup did not register it with the instance as it started (wrapConfig adds it).",
|
|
6302
|
+
off: "This run cannot be shared live: its project's live view is off on the instance. A Project admin turns it on in Project \u2192 Settings \u2192 Live view.",
|
|
6303
|
+
local: "This run cannot be shared live: it reports to the Piwi desktop app on this machine, which no teammate reaches. Name your team's instance in PIWI_DASHBOARD_URL to share a run there."
|
|
6304
|
+
};
|
|
6305
|
+
var LIVE_SHARE_UNKNOWN = "This run cannot be shared live: the Piwi reporter gave its workers no live view key.";
|
|
6306
|
+
function liveShareRefusal(status) {
|
|
6307
|
+
switch (status) {
|
|
6308
|
+
case 404:
|
|
6309
|
+
return "Sharing stopped: the project's live view was turned off on the instance.";
|
|
6310
|
+
case 401:
|
|
6311
|
+
case 403:
|
|
6312
|
+
return "Sharing stopped: the instance no longer takes this run's live view key (it started again, or gave the key up).";
|
|
6313
|
+
case 410:
|
|
6314
|
+
return "Sharing stopped: the run is no longer live on the instance.";
|
|
6315
|
+
case 409:
|
|
6316
|
+
return "Sharing stopped: the instance holds the run as interrupted, having heard nothing from it for a while. Share the run again once it reports there again.";
|
|
6317
|
+
default:
|
|
6318
|
+
return "Sharing stopped: the instance did not answer. Share the run again to try once more.";
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
function isObject5(value) {
|
|
6322
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6323
|
+
}
|
|
6324
|
+
function isInt2(value, min, max) {
|
|
6325
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
6326
|
+
}
|
|
6327
|
+
function parseInstanceToWorker(data) {
|
|
6328
|
+
let value;
|
|
6329
|
+
try {
|
|
6330
|
+
value = JSON.parse(data);
|
|
6331
|
+
} catch {
|
|
6332
|
+
return null;
|
|
6333
|
+
}
|
|
6334
|
+
if (!isObject5(value) || value.type !== "viewers" || !isInt2(value.count, 0, 1e6)) return null;
|
|
6335
|
+
return { type: "viewers", count: value.count };
|
|
6336
|
+
}
|
|
6337
|
+
|
|
6338
|
+
// src/internal/support/shard-info.ts
|
|
6339
|
+
function parseShardSpec(raw) {
|
|
6340
|
+
const match = raw?.trim().match(/^(\d+)\s*\/\s*(\d+)$/);
|
|
6341
|
+
if (!match) return null;
|
|
6342
|
+
const current = Number(match[1]);
|
|
6343
|
+
const total = Number(match[2]);
|
|
6344
|
+
return current >= 1 && current <= total && total > 1 ? { current, total } : null;
|
|
6345
|
+
}
|
|
6346
|
+
function resolveShardInfo(config, env = process.env) {
|
|
6347
|
+
const pwShard = config.shard;
|
|
6348
|
+
if (pwShard && pwShard.total > 1) return { current: pwShard.current, total: pwShard.total };
|
|
6349
|
+
return parseShardSpec(env[PIWI_SHARD_ENV]);
|
|
6350
|
+
}
|
|
6351
|
+
|
|
6352
|
+
// src/internal/capture/page-view.ts
|
|
6353
|
+
var HOLDERS = /* @__PURE__ */ new WeakMap();
|
|
6354
|
+
var TEST_SCREENCASTS = /* @__PURE__ */ new WeakSet();
|
|
6355
|
+
var OWN_SCREENCASTS = /* @__PURE__ */ new WeakMap();
|
|
6356
|
+
function screencastOf(page) {
|
|
6357
|
+
const seen = page.screencast;
|
|
6358
|
+
return (seen && OWN_SCREENCASTS.get(seen)) ?? seen;
|
|
6359
|
+
}
|
|
6360
|
+
function yieldToTest(page) {
|
|
6361
|
+
TEST_SCREENCASTS.add(page);
|
|
6362
|
+
const holders = HOLDERS.get(page);
|
|
6363
|
+
if (!holders?.size) return null;
|
|
6364
|
+
return Promise.all([...holders].map((holder) => holder.release()));
|
|
6365
|
+
}
|
|
6366
|
+
function guardScreencast(page) {
|
|
6367
|
+
const own = page.screencast;
|
|
6368
|
+
if (!own || typeof own !== "object" || OWN_SCREENCASTS.has(own)) return;
|
|
6369
|
+
const guarded = new Proxy(own, {
|
|
6370
|
+
get(target, prop) {
|
|
6371
|
+
const value = Reflect.get(target, prop);
|
|
6372
|
+
if (typeof value !== "function") return value;
|
|
6373
|
+
if (prop !== "start" && prop !== "stop") return (...args) => callMethod(target, prop, args);
|
|
6374
|
+
return (...args) => {
|
|
6375
|
+
const yielded = yieldToTest(page);
|
|
6376
|
+
return yielded ? yielded.then(() => callMethod(target, prop, args)) : callMethod(target, prop, args);
|
|
6377
|
+
};
|
|
6378
|
+
}
|
|
6379
|
+
});
|
|
6380
|
+
OWN_SCREENCASTS.set(guarded, own);
|
|
6381
|
+
try {
|
|
6382
|
+
page.screencast = guarded;
|
|
6383
|
+
} catch {
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
async function viewportOf(page) {
|
|
6387
|
+
const size = page.viewportSize();
|
|
6388
|
+
if (size) return size;
|
|
6389
|
+
const read = await internalCall(
|
|
6390
|
+
page,
|
|
6391
|
+
() => page.evaluate(() => {
|
|
6392
|
+
const { innerWidth, innerHeight } = globalThis;
|
|
6393
|
+
return { width: innerWidth, height: innerHeight };
|
|
6394
|
+
})
|
|
6395
|
+
).catch(() => null);
|
|
6396
|
+
return read && isViewSize(read.width, read.height) ? read : null;
|
|
6397
|
+
}
|
|
6398
|
+
async function openPageView(page, options) {
|
|
6399
|
+
const viewport = await viewportOf(page);
|
|
6400
|
+
if (!viewport) return null;
|
|
6401
|
+
let closed = false;
|
|
6402
|
+
let last = null;
|
|
6403
|
+
let sent2 = null;
|
|
6404
|
+
let refused = false;
|
|
6405
|
+
const interval = 1e3 / options.fps;
|
|
6406
|
+
const pacer = new LatestPacer({
|
|
6407
|
+
intervalMs: interval,
|
|
6408
|
+
...options.spacing ? { spacing: options.spacing } : {},
|
|
6409
|
+
busy: options.busy,
|
|
6410
|
+
send: (frame) => {
|
|
6411
|
+
if (!options.live()) return;
|
|
6412
|
+
refused = false;
|
|
6413
|
+
sent2 = frame;
|
|
6414
|
+
options.send(frame);
|
|
6415
|
+
}
|
|
6416
|
+
});
|
|
6417
|
+
let lighter = null;
|
|
6418
|
+
let lighterAt = -Infinity;
|
|
6419
|
+
const lighten = () => {
|
|
6420
|
+
const now = Date.now();
|
|
6421
|
+
if (lighter || now - lighterAt < options.shotMs) return;
|
|
6422
|
+
lighterAt = now;
|
|
6423
|
+
lighter = internalCall(
|
|
6424
|
+
page,
|
|
6425
|
+
() => page.screenshot({ type: "jpeg", quality: options.lightQuality, scale: "css", caret: "initial", timeout: 5e3 })
|
|
6426
|
+
).then(
|
|
6427
|
+
(shot) => {
|
|
6428
|
+
if (closed) return;
|
|
6429
|
+
if (shot.length <= options.maxFrameBytes) return pacer.offer({ data: shot, ...viewport });
|
|
6430
|
+
if (refused || !options.live()) return;
|
|
6431
|
+
refused = true;
|
|
6432
|
+
sent2 = "refused";
|
|
6433
|
+
options.refuse();
|
|
6434
|
+
},
|
|
6435
|
+
() => void 0
|
|
6436
|
+
).finally(() => {
|
|
6437
|
+
lighter = null;
|
|
6438
|
+
});
|
|
6439
|
+
};
|
|
6440
|
+
const onFrame = (data, width, height) => {
|
|
6441
|
+
if (closed || last?.equals(data)) return;
|
|
6442
|
+
last = data;
|
|
6443
|
+
if (data.length > options.maxFrameBytes) return lighten();
|
|
6444
|
+
const named = isViewSize(width, height);
|
|
6445
|
+
pacer.offer({ data, width: named ? width : viewport.width, height: named ? height : viewport.height });
|
|
6446
|
+
};
|
|
6447
|
+
const screencast = screencastOf(page);
|
|
6448
|
+
let end = null;
|
|
6449
|
+
let shots = null;
|
|
6450
|
+
let shooting = false;
|
|
6451
|
+
const shoot = async () => {
|
|
6452
|
+
shots = null;
|
|
6453
|
+
if (closed || page.isClosed()) return;
|
|
6454
|
+
const shot = await internalCall(
|
|
6455
|
+
page,
|
|
6456
|
+
() => page.screenshot({ type: "jpeg", quality: options.quality, scale: "css", caret: "initial", timeout: 5e3 })
|
|
6457
|
+
).catch(() => null);
|
|
6458
|
+
if (shot) onFrame(shot, viewport.width, viewport.height);
|
|
6459
|
+
if (!closed) {
|
|
6460
|
+
shots = setTimeout(() => void shoot(), options.shotMs);
|
|
6461
|
+
shots.unref?.();
|
|
6462
|
+
}
|
|
6463
|
+
};
|
|
6464
|
+
const takeShots = () => {
|
|
6465
|
+
if (shooting || closed) return;
|
|
6466
|
+
shooting = true;
|
|
6467
|
+
void shoot();
|
|
6468
|
+
};
|
|
6469
|
+
const acks = new FrameAcks(interval);
|
|
6470
|
+
const holders = HOLDERS.get(page) ?? /* @__PURE__ */ new Set();
|
|
6471
|
+
HOLDERS.set(page, holders);
|
|
6472
|
+
let starting = null;
|
|
6473
|
+
const holder = {
|
|
6474
|
+
release: async () => {
|
|
6475
|
+
holders.delete(holder);
|
|
6476
|
+
await starting?.catch(() => void 0);
|
|
6477
|
+
const stop = end;
|
|
6478
|
+
end = null;
|
|
6479
|
+
if (stop && !page.isClosed()) await stop().catch(() => void 0);
|
|
6480
|
+
takeShots();
|
|
6481
|
+
}
|
|
6482
|
+
};
|
|
6483
|
+
if (typeof screencast?.start === "function" && !TEST_SCREENCASTS.has(page)) {
|
|
6484
|
+
holders.add(holder);
|
|
6485
|
+
starting = internalCall(
|
|
6486
|
+
page,
|
|
6487
|
+
() => screencast.start({
|
|
6488
|
+
size: options.size(viewport),
|
|
6489
|
+
quality: options.quality,
|
|
6490
|
+
onFrame: (frame) => {
|
|
6491
|
+
onFrame(frame.data, frame.viewportWidth, frame.viewportHeight);
|
|
6492
|
+
return acks.next();
|
|
5427
6493
|
}
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
const
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
6494
|
+
})
|
|
6495
|
+
);
|
|
6496
|
+
try {
|
|
6497
|
+
const started = await starting;
|
|
6498
|
+
end = () => internalCall(page, async () => {
|
|
6499
|
+
if (typeof started?.dispose === "function") await started.dispose();
|
|
6500
|
+
else await screencast.stop?.();
|
|
6501
|
+
});
|
|
6502
|
+
} catch {
|
|
6503
|
+
holders.delete(holder);
|
|
6504
|
+
takeShots();
|
|
6505
|
+
}
|
|
6506
|
+
} else {
|
|
6507
|
+
takeShots();
|
|
6508
|
+
}
|
|
6509
|
+
return {
|
|
6510
|
+
get screencast() {
|
|
6511
|
+
return end !== null;
|
|
6512
|
+
},
|
|
6513
|
+
resend: () => {
|
|
6514
|
+
if (closed || !sent2 || !options.live()) return;
|
|
6515
|
+
if (sent2 === "refused") options.refuse();
|
|
6516
|
+
else options.send(sent2);
|
|
6517
|
+
},
|
|
6518
|
+
close: async () => {
|
|
6519
|
+
closed = true;
|
|
6520
|
+
sent2 = null;
|
|
6521
|
+
acks.close();
|
|
6522
|
+
pacer.close();
|
|
6523
|
+
if (shots) clearTimeout(shots);
|
|
6524
|
+
holders.delete(holder);
|
|
6525
|
+
await lighter;
|
|
6526
|
+
const stop = end;
|
|
6527
|
+
end = null;
|
|
6528
|
+
if (stop && !page.isClosed()) await stop().catch(() => void 0);
|
|
6529
|
+
}
|
|
6530
|
+
};
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6533
|
+
// src/internal/capture/live-view.ts
|
|
6534
|
+
var RETRY_MS = [1e3, 2e3, 4e3, 8e3, 16e3];
|
|
6535
|
+
var MAX_RECONNECTS = 20;
|
|
6536
|
+
var STEADY_MS = 3e4;
|
|
6537
|
+
var STREAM_IDLE_MS = 45e3;
|
|
6538
|
+
var POST_TIMEOUT_MS = 1e4;
|
|
6539
|
+
var MAX_EVENT_TEXT = 64 * 1024;
|
|
6540
|
+
var MAX_STATES_WAITING = 8;
|
|
6541
|
+
var LIGHT_QUALITY = 30;
|
|
6542
|
+
var SHOT_MS = 1e3;
|
|
6543
|
+
var VIEW_END_MS = 1e3;
|
|
6544
|
+
var GIVE_UP = /* @__PURE__ */ new Set([401, 403, 404, 410]);
|
|
6545
|
+
var TOO_HEAVY = `This page is too heavy for the live view: each image of it is over ${MAX_LIVE_FRAME / 1024} KB, a lighter one included.`;
|
|
6546
|
+
var NO_PAGE = "This test has no page to show yet.";
|
|
6547
|
+
var NO_TEST = "No test runs on this worker now: the next one shows once it opens its page.";
|
|
6548
|
+
var link = null;
|
|
6549
|
+
var missing = null;
|
|
6550
|
+
var changes = /* @__PURE__ */ new Set();
|
|
6551
|
+
var shareTurn = Promise.resolve();
|
|
6552
|
+
var running = null;
|
|
6553
|
+
var view = null;
|
|
6554
|
+
var hidden = null;
|
|
6555
|
+
function hideKey(env) {
|
|
6556
|
+
const value = env[LIVE_VIEW_ENV];
|
|
6557
|
+
if (value === void 0 || hidden) return;
|
|
6558
|
+
delete env[LIVE_VIEW_ENV];
|
|
6559
|
+
const onStop = (message) => {
|
|
6560
|
+
if (message?.method !== "__stop__") return;
|
|
6561
|
+
env[LIVE_VIEW_ENV] = value;
|
|
6562
|
+
process.nextTick(() => {
|
|
6563
|
+
if (hidden?.onStop === onStop) delete env[LIVE_VIEW_ENV];
|
|
5464
6564
|
});
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
6565
|
+
};
|
|
6566
|
+
process.prependListener("message", onStop);
|
|
6567
|
+
hidden = { env, value, onStop };
|
|
6568
|
+
}
|
|
6569
|
+
function showKey() {
|
|
6570
|
+
const own = hidden;
|
|
6571
|
+
hidden = null;
|
|
6572
|
+
if (!own) return;
|
|
6573
|
+
process.off("message", own.onStop);
|
|
6574
|
+
own.env[LIVE_VIEW_ENV] = own.value;
|
|
6575
|
+
}
|
|
6576
|
+
function liveViewOpen() {
|
|
6577
|
+
return link !== null;
|
|
6578
|
+
}
|
|
6579
|
+
function active(own) {
|
|
6580
|
+
return !own.ended && own.refused === null && own.sharing;
|
|
6581
|
+
}
|
|
6582
|
+
function changed() {
|
|
6583
|
+
for (const listener of changes) listener();
|
|
6584
|
+
}
|
|
6585
|
+
function onLiveViewChange(listener) {
|
|
6586
|
+
changes.add(listener);
|
|
6587
|
+
return () => changes.delete(listener);
|
|
6588
|
+
}
|
|
6589
|
+
function liveViewState() {
|
|
6590
|
+
const own = link;
|
|
6591
|
+
if (!own) return { can: false, on: false, viewers: 0, page: null, why: missing ?? LIVE_SHARE_UNKNOWN };
|
|
6592
|
+
const page = liveRunPage(own.address);
|
|
6593
|
+
if (own.refused) return { can: false, on: false, viewers: 0, page, why: own.refused };
|
|
6594
|
+
const on = active(own);
|
|
6595
|
+
return { can: true, on, viewers: on ? own.viewers : 0, page, why: own.lost };
|
|
6596
|
+
}
|
|
6597
|
+
function openLiveView(worker, env = process.env, ci = detectCi(env)) {
|
|
6598
|
+
if (link) return false;
|
|
6599
|
+
const value = env[LIVE_VIEW_ENV];
|
|
6600
|
+
const address = parseLiveViewEnv(value);
|
|
6601
|
+
hideKey(env);
|
|
6602
|
+
if (!address) {
|
|
6603
|
+
const why = parseLiveViewMissing(value);
|
|
6604
|
+
missing = why ? LIVE_SHARE_MISSING[why] : LIVE_SHARE_UNKNOWN;
|
|
6605
|
+
changed();
|
|
6606
|
+
return false;
|
|
6607
|
+
}
|
|
6608
|
+
missing = null;
|
|
6609
|
+
const secure = address.url.startsWith("https:");
|
|
6610
|
+
const client = secure ? https : http;
|
|
6611
|
+
link = {
|
|
6612
|
+
address,
|
|
6613
|
+
id: { shard: resolveShardInfo(worker.config, env)?.current ?? null, worker: worker.workerIndex },
|
|
6614
|
+
client,
|
|
6615
|
+
agent: new client.Agent({ keepAlive: true, maxSockets: 2 }),
|
|
6616
|
+
stream: null,
|
|
6617
|
+
timer: null,
|
|
6618
|
+
retries: 0,
|
|
6619
|
+
reconnects: 0,
|
|
6620
|
+
ended: false,
|
|
6621
|
+
refused: null,
|
|
6622
|
+
sharing: ci,
|
|
6623
|
+
lost: null,
|
|
6624
|
+
answered: null,
|
|
6625
|
+
viewers: 0,
|
|
6626
|
+
states: Promise.resolve(),
|
|
6627
|
+
statesWaiting: 0,
|
|
6628
|
+
stop: null,
|
|
6629
|
+
posting: false
|
|
6630
|
+
};
|
|
6631
|
+
if (ci) connect(link);
|
|
6632
|
+
changed();
|
|
6633
|
+
return true;
|
|
6634
|
+
}
|
|
6635
|
+
function closeLiveView() {
|
|
6636
|
+
const own = link;
|
|
6637
|
+
link = null;
|
|
6638
|
+
running = null;
|
|
6639
|
+
missing = null;
|
|
6640
|
+
showKey();
|
|
6641
|
+
void stopView();
|
|
6642
|
+
if (!own) return;
|
|
6643
|
+
own.ended = true;
|
|
6644
|
+
if (own.timer) clearTimeout(own.timer);
|
|
6645
|
+
own.stream?.destroy();
|
|
6646
|
+
own.agent.destroy();
|
|
6647
|
+
}
|
|
6648
|
+
function shareLiveView(on) {
|
|
6649
|
+
const turn = shareTurn.then(() => on ? startSharing() : stopSharing());
|
|
6650
|
+
shareTurn = turn.catch(() => void 0);
|
|
6651
|
+
return turn;
|
|
6652
|
+
}
|
|
6653
|
+
function startSharing() {
|
|
6654
|
+
const own = link;
|
|
6655
|
+
if (!own || own.ended || own.refused || own.sharing) return;
|
|
6656
|
+
own.sharing = true;
|
|
6657
|
+
own.lost = null;
|
|
6658
|
+
own.answered = null;
|
|
6659
|
+
own.retries = 0;
|
|
6660
|
+
own.reconnects = 0;
|
|
6661
|
+
connect(own);
|
|
6662
|
+
changed();
|
|
6663
|
+
}
|
|
6664
|
+
async function stopSharing() {
|
|
6665
|
+
const own = link;
|
|
6666
|
+
if (!own || own.ended || !own.sharing) return;
|
|
6667
|
+
const streamed = own.stream !== null && own.refused === null;
|
|
6668
|
+
own.sharing = false;
|
|
6669
|
+
own.lost = null;
|
|
6670
|
+
if (own.timer) clearTimeout(own.timer);
|
|
6671
|
+
own.timer = null;
|
|
6672
|
+
await stopView();
|
|
6673
|
+
if (streamed) await send(own, { type: "unshared", ...own.id });
|
|
6674
|
+
own.stream?.destroy();
|
|
6675
|
+
own.stream = null;
|
|
6676
|
+
own.viewers = 0;
|
|
6677
|
+
changed();
|
|
6678
|
+
}
|
|
6679
|
+
function liveViewTestBegan(title, call) {
|
|
6680
|
+
if (!link) return;
|
|
6681
|
+
running = { title, page: null, call };
|
|
6682
|
+
}
|
|
6683
|
+
function liveViewPage(page) {
|
|
6684
|
+
if (!link || !running || running.page) return;
|
|
6685
|
+
running.page = page;
|
|
6686
|
+
if (link.viewers > 0 && active(link)) startView();
|
|
6687
|
+
}
|
|
6688
|
+
async function liveViewTestEnded() {
|
|
6689
|
+
if (!running) return;
|
|
6690
|
+
running = null;
|
|
6691
|
+
await stopView();
|
|
6692
|
+
const own = link;
|
|
6693
|
+
if (own && own.viewers > 0 && active(own)) void post(own, { type: "ended", ...own.id });
|
|
6694
|
+
}
|
|
6695
|
+
function liveViewStopped(stop) {
|
|
6696
|
+
const own = link;
|
|
6697
|
+
if (!own || own.ended) return () => void 0;
|
|
6698
|
+
const shown2 = { ...stop, call: hideCallSecrets(stop.call) };
|
|
6699
|
+
own.stop = shown2;
|
|
6700
|
+
postState(own, { type: "stop", ...own.id, stop: shown2 });
|
|
6701
|
+
return () => {
|
|
6702
|
+
if (own.stop === shown2) own.stop = null;
|
|
6703
|
+
postState(own, { type: "stop", ...own.id, stop: null });
|
|
6704
|
+
};
|
|
6705
|
+
}
|
|
6706
|
+
function hideCallSecrets(call) {
|
|
6707
|
+
return { ...call, title: hideSecretsInText(call.title) };
|
|
6708
|
+
}
|
|
6709
|
+
function postState(own, message) {
|
|
6710
|
+
if (!active(own) || own.statesWaiting >= MAX_STATES_WAITING) return;
|
|
6711
|
+
own.statesWaiting++;
|
|
6712
|
+
own.states = own.states.then(() => post(own, message)).finally(() => {
|
|
6713
|
+
own.statesWaiting--;
|
|
6714
|
+
});
|
|
6715
|
+
}
|
|
6716
|
+
function connect(own) {
|
|
6717
|
+
if (!active(own)) return;
|
|
6718
|
+
const url = new URL(own.address.url);
|
|
6719
|
+
url.searchParams.set("worker", String(own.id.worker));
|
|
6720
|
+
if (own.id.shard !== null) url.searchParams.set("shard", String(own.id.shard));
|
|
6721
|
+
let openedAt = null;
|
|
6722
|
+
let dropped = false;
|
|
6723
|
+
const drop = (status) => {
|
|
6724
|
+
if (dropped) return;
|
|
6725
|
+
dropped = true;
|
|
6726
|
+
if (own.stream !== request) return;
|
|
6727
|
+
own.stream = null;
|
|
6728
|
+
setViewers(own, 0);
|
|
6729
|
+
if (!active(own)) return;
|
|
6730
|
+
if (openedAt !== null && Date.now() - openedAt >= STEADY_MS) own.retries = 0;
|
|
6731
|
+
if (status !== null) own.answered = status;
|
|
6732
|
+
if (status !== null && GIVE_UP.has(status)) return void refuse(own, status);
|
|
6733
|
+
if (own.retries >= RETRY_MS.length || own.reconnects >= MAX_RECONNECTS) return void giveUp(own);
|
|
6734
|
+
const wait = RETRY_MS[own.retries++];
|
|
6735
|
+
own.reconnects++;
|
|
6736
|
+
own.timer = setTimeout(() => {
|
|
6737
|
+
own.timer = null;
|
|
6738
|
+
connect(own);
|
|
6739
|
+
}, wait);
|
|
6740
|
+
own.timer.unref?.();
|
|
6741
|
+
};
|
|
6742
|
+
const request = own.client.request(url, {
|
|
6743
|
+
method: "GET",
|
|
6744
|
+
agent: own.agent,
|
|
6745
|
+
headers: {
|
|
6746
|
+
Accept: "text/event-stream",
|
|
6747
|
+
Authorization: `Bearer ${own.address.key}`,
|
|
6748
|
+
"Cache-Control": "no-cache"
|
|
6749
|
+
}
|
|
6750
|
+
});
|
|
6751
|
+
own.stream = request;
|
|
6752
|
+
request.on("socket", (socket2) => socket2.unref());
|
|
6753
|
+
request.setTimeout(STREAM_IDLE_MS, () => request.destroy());
|
|
6754
|
+
request.on("error", () => drop(null));
|
|
6755
|
+
request.on("close", () => drop(null));
|
|
6756
|
+
request.on("response", (response) => {
|
|
6757
|
+
if (response.statusCode !== 200) {
|
|
6758
|
+
response.resume();
|
|
6759
|
+
return drop(response.statusCode ?? null);
|
|
6760
|
+
}
|
|
6761
|
+
openedAt = Date.now();
|
|
6762
|
+
own.answered = null;
|
|
6763
|
+
if (own.stop) postState(own, { type: "stop", ...own.id, stop: own.stop });
|
|
6764
|
+
response.setEncoding("utf8");
|
|
6765
|
+
let text = "";
|
|
6766
|
+
response.on("data", (chunk) => {
|
|
6767
|
+
text += chunk;
|
|
6768
|
+
if (text.length > MAX_EVENT_TEXT && !text.includes("\n\n")) return void request.destroy();
|
|
6769
|
+
let end;
|
|
6770
|
+
while ((end = text.indexOf("\n\n")) >= 0) {
|
|
6771
|
+
const block = text.slice(0, end);
|
|
6772
|
+
text = text.slice(end + 2);
|
|
6773
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
6774
|
+
const message = data ? parseInstanceToWorker(data) : null;
|
|
6775
|
+
if (message) setViewers(own, message.count);
|
|
5487
6776
|
}
|
|
5488
|
-
if (settled) return release();
|
|
5489
|
-
await page.evaluate(fs6.readFileSync(bundle, "utf-8"));
|
|
5490
|
-
if (settled) return release();
|
|
5491
|
-
recording = true;
|
|
5492
|
-
clearTimeout(startWait);
|
|
5493
|
-
context.on("page", onPage);
|
|
5494
|
-
send({ type: "loaded" });
|
|
5495
|
-
};
|
|
5496
|
-
socket2.on("connect", () => {
|
|
5497
|
-
attached = true;
|
|
5498
|
-
send({
|
|
5499
|
-
type: "attach",
|
|
5500
|
-
token: channel.token,
|
|
5501
|
-
file: place.file,
|
|
5502
|
-
line: place.line,
|
|
5503
|
-
reason: options.reason,
|
|
5504
|
-
testIdAttribute: typeof use.testIdAttribute === "string" ? use.testIdAttribute : null,
|
|
5505
|
-
baseURL: typeof use.baseURL === "string" ? use.baseURL : null,
|
|
5506
|
-
...options.mode === "check" ? { mode: "check", stop: options.stop ?? null } : {}
|
|
5507
|
-
});
|
|
5508
6777
|
});
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
6778
|
+
response.on("error", () => drop(null));
|
|
6779
|
+
response.on("close", () => drop(null));
|
|
6780
|
+
});
|
|
6781
|
+
request.end();
|
|
6782
|
+
}
|
|
6783
|
+
function refuse(own, status) {
|
|
6784
|
+
if (own.refused !== null) return;
|
|
6785
|
+
own.refused = liveShareRefusal(status);
|
|
6786
|
+
own.viewers = 0;
|
|
6787
|
+
own.stream?.destroy();
|
|
6788
|
+
own.agent.destroy();
|
|
6789
|
+
void stopView();
|
|
6790
|
+
changed();
|
|
6791
|
+
}
|
|
6792
|
+
function giveUp(own) {
|
|
6793
|
+
own.sharing = false;
|
|
6794
|
+
own.lost = liveShareRefusal(own.answered);
|
|
6795
|
+
own.viewers = 0;
|
|
6796
|
+
if (own.timer) clearTimeout(own.timer);
|
|
6797
|
+
own.timer = null;
|
|
6798
|
+
own.stream?.destroy();
|
|
6799
|
+
own.stream = null;
|
|
6800
|
+
void stopView();
|
|
6801
|
+
changed();
|
|
6802
|
+
}
|
|
6803
|
+
function setViewers(own, count) {
|
|
6804
|
+
const before = own.viewers;
|
|
6805
|
+
own.viewers = count;
|
|
6806
|
+
if (own !== link) return;
|
|
6807
|
+
if (count !== before) changed();
|
|
6808
|
+
if (count === 0) return void stopView();
|
|
6809
|
+
if (!active(own)) return;
|
|
6810
|
+
if (!running) {
|
|
6811
|
+
if (count > before) void post(own, { type: "frame", ...own.id, refused: NO_TEST });
|
|
6812
|
+
return;
|
|
6813
|
+
}
|
|
6814
|
+
if (view) {
|
|
6815
|
+
if (count > before) void view.opened.then((opened) => opened?.resend());
|
|
6816
|
+
return;
|
|
6817
|
+
}
|
|
6818
|
+
startView();
|
|
6819
|
+
}
|
|
6820
|
+
function startView() {
|
|
6821
|
+
const own = link;
|
|
6822
|
+
const page = running?.page ?? null;
|
|
6823
|
+
if (!own || !active(own) || view) return;
|
|
6824
|
+
if (!page || page.isClosed()) {
|
|
6825
|
+
void post(own, { type: "frame", ...own.id, refused: NO_PAGE });
|
|
6826
|
+
return;
|
|
6827
|
+
}
|
|
6828
|
+
const live = () => view?.page === page && link === own && active(own) && own.viewers > 0;
|
|
6829
|
+
const opened = openPageView(page, {
|
|
6830
|
+
size: (viewport) => fitViewSize(viewport, LIVE_FRAME_WIDTH, LIVE_FRAME_HEIGHT),
|
|
6831
|
+
quality: LIVE_FRAME_QUALITY,
|
|
6832
|
+
fps: LIVE_FRAME_FPS,
|
|
6833
|
+
spacing: (frame) => frame.data.length * 1e3 / LIVE_FRAME_BYTE_RATE,
|
|
6834
|
+
maxFrameBytes: MAX_LIVE_FRAME,
|
|
6835
|
+
lightQuality: LIGHT_QUALITY,
|
|
6836
|
+
shotMs: SHOT_MS,
|
|
6837
|
+
busy: () => own.posting,
|
|
6838
|
+
live,
|
|
6839
|
+
send: (frame) => postFrame(own, frame),
|
|
6840
|
+
refuse: () => void post(own, { type: "frame", ...own.id, refused: TOO_HEAVY })
|
|
6841
|
+
}).catch(() => null);
|
|
6842
|
+
const onClose = () => {
|
|
6843
|
+
if (view?.page === page) void stopView();
|
|
6844
|
+
};
|
|
6845
|
+
page.once("close", onClose);
|
|
6846
|
+
view = { page, opened, onClose };
|
|
6847
|
+
}
|
|
6848
|
+
async function stopView() {
|
|
6849
|
+
const shown2 = view;
|
|
6850
|
+
view = null;
|
|
6851
|
+
if (!shown2) return;
|
|
6852
|
+
shown2.page.off("close", shown2.onClose);
|
|
6853
|
+
const closed = shown2.opened.then((opened) => opened?.close());
|
|
6854
|
+
let timer;
|
|
6855
|
+
await Promise.race([
|
|
6856
|
+
closed.catch(() => void 0),
|
|
6857
|
+
new Promise((resolve5) => {
|
|
6858
|
+
timer = setTimeout(resolve5, VIEW_END_MS);
|
|
6859
|
+
timer.unref?.();
|
|
6860
|
+
})
|
|
6861
|
+
]);
|
|
6862
|
+
clearTimeout(timer);
|
|
6863
|
+
}
|
|
6864
|
+
function postFrame(own, frame) {
|
|
6865
|
+
if (!active(own)) return;
|
|
6866
|
+
own.posting = true;
|
|
6867
|
+
const call = running?.call() ?? null;
|
|
6868
|
+
const data = frame.data.toString("base64");
|
|
6869
|
+
void post(own, {
|
|
6870
|
+
type: "frame",
|
|
6871
|
+
...own.id,
|
|
6872
|
+
data,
|
|
6873
|
+
call: call && hideCallSecrets(call),
|
|
6874
|
+
test: running?.title ?? null
|
|
6875
|
+
}).catch(() => void 0).finally(() => {
|
|
6876
|
+
own.posting = false;
|
|
6877
|
+
});
|
|
6878
|
+
}
|
|
6879
|
+
function post(own, message) {
|
|
6880
|
+
return active(own) ? send(own, message) : Promise.resolve(null);
|
|
6881
|
+
}
|
|
6882
|
+
function send(own, message) {
|
|
6883
|
+
if (own.ended || own.refused !== null) return Promise.resolve(null);
|
|
6884
|
+
const body = JSON.stringify(message);
|
|
6885
|
+
return new Promise((resolve5) => {
|
|
6886
|
+
let settled = false;
|
|
6887
|
+
const settle = (status) => {
|
|
6888
|
+
if (settled) return;
|
|
6889
|
+
settled = true;
|
|
6890
|
+
if (status !== null && GIVE_UP.has(status)) refuse(own, status);
|
|
6891
|
+
resolve5(status);
|
|
6892
|
+
};
|
|
6893
|
+
const request = own.client.request(own.address.url, {
|
|
6894
|
+
method: "POST",
|
|
6895
|
+
agent: own.agent,
|
|
6896
|
+
headers: {
|
|
6897
|
+
"Content-Type": "application/json",
|
|
6898
|
+
"Content-Length": Buffer.byteLength(body),
|
|
6899
|
+
Authorization: `Bearer ${own.address.key}`
|
|
6900
|
+
}
|
|
6901
|
+
});
|
|
6902
|
+
request.on("socket", (socket2) => socket2.unref());
|
|
6903
|
+
request.setTimeout(POST_TIMEOUT_MS, () => request.destroy());
|
|
6904
|
+
request.on("error", () => settle(null));
|
|
6905
|
+
request.on("close", () => settle(null));
|
|
6906
|
+
request.on("response", (response) => {
|
|
6907
|
+
response.resume();
|
|
6908
|
+
response.on("end", () => settle(response.statusCode ?? null));
|
|
6909
|
+
response.on("error", () => settle(null));
|
|
6910
|
+
});
|
|
6911
|
+
request.end(body);
|
|
5544
6912
|
});
|
|
5545
6913
|
}
|
|
5546
6914
|
|
|
5547
6915
|
// src/internal/capture/run-channel.ts
|
|
5548
|
-
var net2 = __toESM(require("net"));
|
|
5549
6916
|
var CONNECT_MS = 2e3;
|
|
5550
|
-
var
|
|
6917
|
+
var BAR_HIDDEN2 = "#__piwi-pause-bar { display: none !important; }";
|
|
5551
6918
|
var OUTLINE_STYLE = "outline: 2px dashed #a78bfa; outline-offset: 2px; background: rgba(167, 139, 250, 0.15);";
|
|
5552
6919
|
var WENT_ON = "The test went on.";
|
|
6920
|
+
var MAX_FRAME_BACKLOG = 1024 * 1024;
|
|
6921
|
+
var SHOT_MS2 = 500;
|
|
6922
|
+
var LIGHT_QUALITY2 = 30;
|
|
6923
|
+
var TOO_HEAVY2 = `This page is too heavy for the live view: each image of it is over ${MAX_VIEW_FRAME / 1024} KB, a lighter one included. The image of the stop still shows it.`;
|
|
6924
|
+
var VIEW_END_MS2 = 1e3;
|
|
6925
|
+
var PAGE_ANSWER_MS = 1e4;
|
|
6926
|
+
var NO_ANSWER = "The page did not answer in time.";
|
|
6927
|
+
var LATE_ACT = "The page did not answer in time: the action may still take effect, and is told when it does.";
|
|
6928
|
+
var MAX_INPUT_WAITING = 4;
|
|
6929
|
+
var NEEDS_VIEW = "The input needs the live view of this stop: watch it first.";
|
|
6930
|
+
var NO_PAGE2 = "This stop has no page to show: its call does not name one among its context's pages. Use the pause bar in the test's window.";
|
|
5553
6931
|
var socket = null;
|
|
5554
6932
|
var control = null;
|
|
5555
6933
|
var shown = /* @__PURE__ */ new Set();
|
|
6934
|
+
var host = null;
|
|
5556
6935
|
var stops = 0;
|
|
5557
6936
|
var held = null;
|
|
5558
6937
|
var outline = null;
|
|
5559
6938
|
var counting = Promise.resolve();
|
|
6939
|
+
var view2 = null;
|
|
6940
|
+
var viewStarting = null;
|
|
6941
|
+
var viewTurn = Promise.resolve();
|
|
6942
|
+
var viewEnds = /* @__PURE__ */ new Set();
|
|
6943
|
+
function freshInput() {
|
|
6944
|
+
return {
|
|
6945
|
+
rate: new RateWindow(MAX_VIEW_EVENT_RATE),
|
|
6946
|
+
playing: Promise.resolve(),
|
|
6947
|
+
waiting: 0,
|
|
6948
|
+
buttons: /* @__PURE__ */ new Set(),
|
|
6949
|
+
keys: /* @__PURE__ */ new Set()
|
|
6950
|
+
};
|
|
6951
|
+
}
|
|
6952
|
+
var input = freshInput();
|
|
6953
|
+
var pickingAt = false;
|
|
6954
|
+
var runName = "";
|
|
6955
|
+
var eventsOn = false;
|
|
6956
|
+
var testRunning = false;
|
|
6957
|
+
var eventRate = new RateWindow(MAX_RUN_EVENT_RATE);
|
|
6958
|
+
var droppedEvents = 0;
|
|
6959
|
+
var agentTurn = Promise.resolve();
|
|
6960
|
+
var bound = null;
|
|
6961
|
+
var sharedHere = false;
|
|
6962
|
+
var liveSaid = "";
|
|
6963
|
+
var unhearLive = null;
|
|
6964
|
+
var NO_AGENT_PAGE = "This stop has no page an agent can read: its call does not name one among its context's pages.";
|
|
6965
|
+
function workerTakes(env = process.env) {
|
|
6966
|
+
const pairing = !!env[PIWI_LOCAL_DEBUG_ENV.editorSend];
|
|
6967
|
+
const recorder = parseRecordChannel(env[RECORD_CHANNEL_ENV]) !== null;
|
|
6968
|
+
return RUN_TAKES.filter(
|
|
6969
|
+
(take) => take === "pick" ? pairing : take === "record" || take === "assert" ? recorder : true
|
|
6970
|
+
);
|
|
6971
|
+
}
|
|
5560
6972
|
async function openRunChannel(worker, handlers, env = process.env) {
|
|
5561
6973
|
const channel = parseRecordChannel(env[RUN_CHANNEL_ENV]);
|
|
5562
|
-
const ref = env[PIWI_ORIGIN_ENV.ref];
|
|
6974
|
+
const ref = env[RUN_REF_ENV] ?? env[PIWI_ORIGIN_ENV.ref];
|
|
5563
6975
|
if (socket || !channel || !ref || !/^[\w-]{1,64}$/.test(ref)) return false;
|
|
5564
|
-
const opening =
|
|
6976
|
+
const opening = net.connect(channel.path);
|
|
5565
6977
|
opening.setEncoding("utf8");
|
|
5566
6978
|
const opened = await new Promise((resolve5) => {
|
|
5567
6979
|
const timer = setTimeout(() => resolve5(false), CONNECT_MS);
|
|
@@ -5581,6 +6993,9 @@ async function openRunChannel(worker, handlers, env = process.env) {
|
|
|
5581
6993
|
socket = opening;
|
|
5582
6994
|
control = handlers;
|
|
5583
6995
|
shown = /* @__PURE__ */ new Set();
|
|
6996
|
+
runName = `piwi-${ref}-w${worker.workerIndex}`;
|
|
6997
|
+
eventsOn = false;
|
|
6998
|
+
host = null;
|
|
5584
6999
|
opening.unref();
|
|
5585
7000
|
opening.on(
|
|
5586
7001
|
"data",
|
|
@@ -5592,8 +7007,13 @@ async function openRunChannel(worker, handlers, env = process.env) {
|
|
|
5592
7007
|
opening.on("error", () => opening.destroy());
|
|
5593
7008
|
opening.on("close", () => {
|
|
5594
7009
|
if (socket === opening) socket = null;
|
|
7010
|
+
unhearLive?.();
|
|
7011
|
+
unhearLive = null;
|
|
7012
|
+
if (sharedHere) void shareLiveView(false);
|
|
7013
|
+
sharedHere = false;
|
|
7014
|
+
if (view2) void stopView2();
|
|
5595
7015
|
if (!held || held.bar) return;
|
|
5596
|
-
console.log(
|
|
7016
|
+
console.log(`[piwi] The connection to ${runHost()} closed: the test goes on.`);
|
|
5597
7017
|
held.answer("resume");
|
|
5598
7018
|
});
|
|
5599
7019
|
tellHub({
|
|
@@ -5606,25 +7026,40 @@ async function openRunChannel(worker, handlers, env = process.env) {
|
|
|
5606
7026
|
pid: process.pid,
|
|
5607
7027
|
playwright: worker.config.version,
|
|
5608
7028
|
reporter: getReporterVersion(),
|
|
5609
|
-
takes:
|
|
7029
|
+
takes: workerTakes(env),
|
|
5610
7030
|
holds: [...RUN_HOLDS]
|
|
5611
7031
|
});
|
|
7032
|
+
liveSaid = "";
|
|
7033
|
+
tellLive();
|
|
7034
|
+
unhearLive?.();
|
|
7035
|
+
unhearLive = onLiveViewChange(tellLive);
|
|
5612
7036
|
return true;
|
|
5613
7037
|
}
|
|
7038
|
+
function tellLive() {
|
|
7039
|
+
if (!runChannelOpen()) return;
|
|
7040
|
+
const state = liveViewState();
|
|
7041
|
+
const said = JSON.stringify(state);
|
|
7042
|
+
if (said === liveSaid) return;
|
|
7043
|
+
liveSaid = said;
|
|
7044
|
+
tellHub({ type: "live", ...state });
|
|
7045
|
+
}
|
|
5614
7046
|
function editorShows(what) {
|
|
5615
7047
|
return runChannelOpen() && shown.has(what);
|
|
5616
7048
|
}
|
|
7049
|
+
function runHost() {
|
|
7050
|
+
return host ?? "the editor";
|
|
7051
|
+
}
|
|
5617
7052
|
var HIDDEN_ENV = [RUN_CHANNEL_ENV, PIWI_LOCAL_DEBUG_ENV.editorSend];
|
|
5618
7053
|
var hiddenEditorSend;
|
|
5619
7054
|
function hideRunChannel(env = process.env) {
|
|
5620
|
-
const
|
|
7055
|
+
const hidden2 = HIDDEN_ENV.flatMap((name) => {
|
|
5621
7056
|
const value = env[name];
|
|
5622
7057
|
return value === void 0 ? [] : [{ name, value }];
|
|
5623
7058
|
});
|
|
5624
|
-
for (const { name } of
|
|
5625
|
-
hiddenEditorSend =
|
|
7059
|
+
for (const { name } of hidden2) delete env[name];
|
|
7060
|
+
hiddenEditorSend = hidden2.find(({ name }) => name === PIWI_LOCAL_DEBUG_ENV.editorSend)?.value;
|
|
5626
7061
|
return () => {
|
|
5627
|
-
for (const { name, value } of
|
|
7062
|
+
for (const { name, value } of hidden2) env[name] = value;
|
|
5628
7063
|
hiddenEditorSend = void 0;
|
|
5629
7064
|
};
|
|
5630
7065
|
}
|
|
@@ -5640,12 +7075,44 @@ function runChannelOpen() {
|
|
|
5640
7075
|
return !!socket && !socket.destroyed;
|
|
5641
7076
|
}
|
|
5642
7077
|
function tellHub(message) {
|
|
7078
|
+
if (message.type === "test-begin") testRunning = true;
|
|
7079
|
+
if (message.type === "test-end") {
|
|
7080
|
+
testRunning = false;
|
|
7081
|
+
void unbind();
|
|
7082
|
+
}
|
|
5643
7083
|
if (socket && !socket.destroyed) socket.write(`${JSON.stringify(message)}
|
|
5644
7084
|
`);
|
|
5645
7085
|
}
|
|
7086
|
+
function runEventsWanted() {
|
|
7087
|
+
return eventsOn && testRunning && runChannelOpen();
|
|
7088
|
+
}
|
|
7089
|
+
function tellEvent(event) {
|
|
7090
|
+
if (!runEventsWanted()) return;
|
|
7091
|
+
if (!eventRate.take(droppedEvents ? 2 : 1)) {
|
|
7092
|
+
droppedEvents++;
|
|
7093
|
+
return;
|
|
7094
|
+
}
|
|
7095
|
+
if (droppedEvents) tellHub({ type: "event", event: { kind: "dropped", count: droppedEvents } });
|
|
7096
|
+
droppedEvents = 0;
|
|
7097
|
+
tellHub({ type: "event", event });
|
|
7098
|
+
}
|
|
7099
|
+
async function unbind() {
|
|
7100
|
+
const shared = bound?.browser;
|
|
7101
|
+
bound = null;
|
|
7102
|
+
if (shared?.isConnected()) {
|
|
7103
|
+
await within2(
|
|
7104
|
+
internalCall(shared, () => bindBrowser(shared, runName, process.cwd(), false)),
|
|
7105
|
+
VIEW_END_MS2
|
|
7106
|
+
);
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
5646
7109
|
function holdStop(stop, onAnswer) {
|
|
5647
7110
|
const id = ++stops;
|
|
5648
7111
|
let answered = false;
|
|
7112
|
+
const needsBar = (what) => ({
|
|
7113
|
+
ok: false,
|
|
7114
|
+
message: `${RUN_INTENT_NAMES[what]} needs the pause bar in the test's window, and this stop shows none: run the test headed.`
|
|
7115
|
+
});
|
|
5649
7116
|
const holding = {
|
|
5650
7117
|
stop: id,
|
|
5651
7118
|
page: stop.page,
|
|
@@ -5655,10 +7122,13 @@ function holdStop(stop, onAnswer) {
|
|
|
5655
7122
|
answered = true;
|
|
5656
7123
|
onAnswer(choice);
|
|
5657
7124
|
},
|
|
5658
|
-
intent: (what) => answered ? { ok: false, message: WENT_ON } :
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
7125
|
+
intent: (what) => answered ? { ok: false, message: WENT_ON } : (
|
|
7126
|
+
// A stop no bar shows takes an intent only while its live view is watched, which drives it.
|
|
7127
|
+
!stop.bar && view2?.stop !== id ? needsBar(what) : stop.intent?.(what) ?? needsBar(what)
|
|
7128
|
+
),
|
|
7129
|
+
...stop.pickAt ? { pickAt: stop.pickAt } : {},
|
|
7130
|
+
...stop.agent ? { agent: stop.agent } : {},
|
|
7131
|
+
browser: stop.browser ?? null
|
|
5662
7132
|
};
|
|
5663
7133
|
held = holding;
|
|
5664
7134
|
tellHub({
|
|
@@ -5679,10 +7149,193 @@ function holdStop(stop, onAnswer) {
|
|
|
5679
7149
|
if (held === holding) held = null;
|
|
5680
7150
|
await counting;
|
|
5681
7151
|
if (outline?.stop === id) await removeOutline();
|
|
7152
|
+
await endView(id, stop.page);
|
|
5682
7153
|
tellHub({ type: "running", stop: id });
|
|
5683
7154
|
}
|
|
5684
7155
|
};
|
|
5685
7156
|
}
|
|
7157
|
+
function viewEnded(stop) {
|
|
7158
|
+
const ended = new AbortController();
|
|
7159
|
+
if (view2?.stop !== stop) ended.abort();
|
|
7160
|
+
else viewEnds.add(() => ended.abort());
|
|
7161
|
+
return ended.signal;
|
|
7162
|
+
}
|
|
7163
|
+
async function within2(run, ms) {
|
|
7164
|
+
await answerWithin(run, ms, void 0);
|
|
7165
|
+
}
|
|
7166
|
+
async function answerWithin(run, ms, late) {
|
|
7167
|
+
let timer;
|
|
7168
|
+
try {
|
|
7169
|
+
return await Promise.race([
|
|
7170
|
+
run.catch(() => late),
|
|
7171
|
+
new Promise((resolve5) => timer = setTimeout(() => resolve5(late), ms))
|
|
7172
|
+
]);
|
|
7173
|
+
} finally {
|
|
7174
|
+
clearTimeout(timer);
|
|
7175
|
+
}
|
|
7176
|
+
}
|
|
7177
|
+
async function endView(stop, page) {
|
|
7178
|
+
if (viewStarting?.stop === stop) await within2(viewStarting.started, VIEW_END_MS2);
|
|
7179
|
+
if (view2?.stop === stop) await stopView2();
|
|
7180
|
+
const own = input;
|
|
7181
|
+
input = freshInput();
|
|
7182
|
+
await within2(own.playing, VIEW_END_MS2);
|
|
7183
|
+
if (page && !page.isClosed() && (own.buttons.size || own.keys.size)) {
|
|
7184
|
+
await within2(
|
|
7185
|
+
internalCall(page, async () => {
|
|
7186
|
+
for (const button of own.buttons) await page.mouse.up({ button });
|
|
7187
|
+
for (const key of own.keys) await page.keyboard.up(key);
|
|
7188
|
+
}),
|
|
7189
|
+
VIEW_END_MS2
|
|
7190
|
+
);
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
async function stopView2() {
|
|
7194
|
+
const shown2 = view2;
|
|
7195
|
+
view2 = null;
|
|
7196
|
+
for (const end of viewEnds) end();
|
|
7197
|
+
viewEnds.clear();
|
|
7198
|
+
if (shown2) await within2(shown2.close(), VIEW_END_MS2);
|
|
7199
|
+
}
|
|
7200
|
+
function startView2(holding) {
|
|
7201
|
+
if (view2?.stop === holding.stop) {
|
|
7202
|
+
view2.resend();
|
|
7203
|
+
return Promise.resolve({ ok: true, message: "The live view is on." });
|
|
7204
|
+
}
|
|
7205
|
+
if (viewStarting?.stop === holding.stop) return viewStarting.started;
|
|
7206
|
+
const page = holding.page;
|
|
7207
|
+
if (!page || page.isClosed()) return Promise.resolve({ ok: false, message: NO_PAGE2 });
|
|
7208
|
+
const started = (async () => {
|
|
7209
|
+
if (view2) await stopView2();
|
|
7210
|
+
const opening = openView(page, holding.stop);
|
|
7211
|
+
const opened = await answerWithin(opening, PAGE_ANSWER_MS, "late");
|
|
7212
|
+
if (opened === "late") {
|
|
7213
|
+
void opening.then((late) => late?.close());
|
|
7214
|
+
return { ok: false, message: NO_ANSWER };
|
|
7215
|
+
}
|
|
7216
|
+
if (!opened) return { ok: false, message: "The page could not be shown: it closed, or does not answer." };
|
|
7217
|
+
if (held !== holding || page.isClosed()) {
|
|
7218
|
+
await within2(opened.close(), VIEW_END_MS2);
|
|
7219
|
+
return { ok: false, message: WENT_ON };
|
|
7220
|
+
}
|
|
7221
|
+
view2 = opened;
|
|
7222
|
+
return {
|
|
7223
|
+
ok: true,
|
|
7224
|
+
message: opened.screencast ? "The live view is on." : "The live view is on, as images of the page twice a second: the page's screencast is in use."
|
|
7225
|
+
};
|
|
7226
|
+
})().finally(() => {
|
|
7227
|
+
if (viewStarting?.started === started) viewStarting = null;
|
|
7228
|
+
});
|
|
7229
|
+
viewStarting = { stop: holding.stop, started };
|
|
7230
|
+
return started;
|
|
7231
|
+
}
|
|
7232
|
+
async function openView(page, stop) {
|
|
7233
|
+
const opened = await openPageView(page, {
|
|
7234
|
+
size: viewSize,
|
|
7235
|
+
quality: VIEW_QUALITY,
|
|
7236
|
+
fps: MAX_VIEW_FPS,
|
|
7237
|
+
maxFrameBytes: MAX_VIEW_FRAME,
|
|
7238
|
+
lightQuality: LIGHT_QUALITY2,
|
|
7239
|
+
shotMs: SHOT_MS2,
|
|
7240
|
+
busy: () => !!socket && socket.writableLength > MAX_FRAME_BACKLOG,
|
|
7241
|
+
// Once its stop began to end, no frame of it goes.
|
|
7242
|
+
live: () => held?.stop === stop,
|
|
7243
|
+
send: (frame) => tellHub({ type: "frame", stop, width: frame.width, height: frame.height, data: frame.data.toString("base64") }),
|
|
7244
|
+
refuse: () => tellHub({ type: "frame", stop, refused: TOO_HEAVY2 })
|
|
7245
|
+
});
|
|
7246
|
+
if (!opened) return null;
|
|
7247
|
+
const onClose = () => {
|
|
7248
|
+
if (view2?.stop === stop) void stopView2();
|
|
7249
|
+
};
|
|
7250
|
+
page.once("close", onClose);
|
|
7251
|
+
return {
|
|
7252
|
+
stop,
|
|
7253
|
+
screencast: opened.screencast,
|
|
7254
|
+
resend: opened.resend,
|
|
7255
|
+
close: async () => {
|
|
7256
|
+
page.off("close", onClose);
|
|
7257
|
+
await opened.close();
|
|
7258
|
+
}
|
|
7259
|
+
};
|
|
7260
|
+
}
|
|
7261
|
+
async function stopViewOf(holding) {
|
|
7262
|
+
if (viewStarting?.stop === holding.stop) await within2(viewStarting.started, VIEW_END_MS2);
|
|
7263
|
+
if (view2?.stop === holding.stop) await stopView2();
|
|
7264
|
+
return { ok: true, message: "The live view is off." };
|
|
7265
|
+
}
|
|
7266
|
+
function playInput(holding, events) {
|
|
7267
|
+
const page = holding.page;
|
|
7268
|
+
if (!page || view2?.stop !== holding.stop) return Promise.resolve({ ok: false, message: NEEDS_VIEW });
|
|
7269
|
+
const own = input;
|
|
7270
|
+
if (own.waiting >= MAX_INPUT_WAITING) {
|
|
7271
|
+
return Promise.resolve({ ok: false, message: "The page still plays the input sent before: send less at once." });
|
|
7272
|
+
}
|
|
7273
|
+
if (!own.rate.take(events.length)) {
|
|
7274
|
+
return Promise.resolve({ ok: false, message: `Too much input: ${MAX_VIEW_EVENT_RATE} events a second at most.` });
|
|
7275
|
+
}
|
|
7276
|
+
own.waiting++;
|
|
7277
|
+
const played = own.playing.then(async () => {
|
|
7278
|
+
for (const [i, event] of events.entries()) {
|
|
7279
|
+
if (held !== holding || input !== own || view2?.stop !== holding.stop || page.isClosed()) {
|
|
7280
|
+
return { ok: false, message: i ? `The stop ended after ${i} of the events.` : WENT_ON };
|
|
7281
|
+
}
|
|
7282
|
+
try {
|
|
7283
|
+
await internalCall(page, () => playEvent(page, event, own));
|
|
7284
|
+
} catch (error) {
|
|
7285
|
+
return {
|
|
7286
|
+
ok: false,
|
|
7287
|
+
message: hideSecretsInText(`Event ${i + 1} could not be played: ${errorMessage(error)}`)
|
|
7288
|
+
};
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
return { ok: true, message: events.length === 1 ? "Played 1 event." : `Played ${events.length} events.` };
|
|
7292
|
+
}).finally(() => own.waiting--);
|
|
7293
|
+
own.playing = played.catch(() => void 0);
|
|
7294
|
+
return played;
|
|
7295
|
+
}
|
|
7296
|
+
async function playEvent(page, event, own) {
|
|
7297
|
+
switch (event.type) {
|
|
7298
|
+
case "mouse":
|
|
7299
|
+
if (event.action === "move") return page.mouse.move(event.x, event.y);
|
|
7300
|
+
if (event.action === "click") {
|
|
7301
|
+
return page.mouse.click(event.x, event.y, { button: event.button, clickCount: event.clickCount });
|
|
7302
|
+
}
|
|
7303
|
+
await page.mouse.move(event.x, event.y);
|
|
7304
|
+
const pressed = {
|
|
7305
|
+
button: event.button,
|
|
7306
|
+
...event.clickCount === void 0 ? {} : { clickCount: event.clickCount }
|
|
7307
|
+
};
|
|
7308
|
+
if (event.action === "down") {
|
|
7309
|
+
own.buttons.add(event.button);
|
|
7310
|
+
return page.mouse.down(pressed);
|
|
7311
|
+
}
|
|
7312
|
+
own.buttons.delete(event.button);
|
|
7313
|
+
return page.mouse.up(pressed);
|
|
7314
|
+
case "wheel":
|
|
7315
|
+
await page.mouse.move(event.x, event.y);
|
|
7316
|
+
return page.mouse.wheel(event.deltaX, event.deltaY);
|
|
7317
|
+
case "key":
|
|
7318
|
+
if (event.action === "press") return page.keyboard.press(event.key);
|
|
7319
|
+
if (event.action === "down") {
|
|
7320
|
+
own.keys.add(event.key);
|
|
7321
|
+
return page.keyboard.down(event.key);
|
|
7322
|
+
}
|
|
7323
|
+
own.keys.delete(event.key);
|
|
7324
|
+
return page.keyboard.up(event.key);
|
|
7325
|
+
case "text":
|
|
7326
|
+
return page.keyboard.insertText(event.text);
|
|
7327
|
+
}
|
|
7328
|
+
}
|
|
7329
|
+
async function pickAtStop(holding, x, y) {
|
|
7330
|
+
if (!holding.pickAt) return { ok: false, message: NO_PAGE2, locator: null };
|
|
7331
|
+
if (pickingAt) return { ok: false, message: "A pick on the image runs: wait for its answer.", locator: null };
|
|
7332
|
+
pickingAt = true;
|
|
7333
|
+
try {
|
|
7334
|
+
return await answerWithin(holding.pickAt(x, y), PAGE_ANSWER_MS, { ok: false, message: NO_ANSWER, locator: null });
|
|
7335
|
+
} finally {
|
|
7336
|
+
pickingAt = false;
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
5686
7339
|
async function removeOutline() {
|
|
5687
7340
|
const shown2 = outline;
|
|
5688
7341
|
outline = null;
|
|
@@ -5714,78 +7367,514 @@ async function countLocator(holding, source) {
|
|
|
5714
7367
|
return { ok: false, message: `The locator could not be counted: ${errorMessage(error)}`, count: null };
|
|
5715
7368
|
}
|
|
5716
7369
|
}
|
|
5717
|
-
function onMessage(message) {
|
|
5718
|
-
if (!message) return;
|
|
5719
|
-
switch (message.type) {
|
|
5720
|
-
case "attached":
|
|
5721
|
-
shown = new Set(message.holds);
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
return;
|
|
5736
|
-
case "
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
7370
|
+
function onMessage(message) {
|
|
7371
|
+
if (!message) return;
|
|
7372
|
+
switch (message.type) {
|
|
7373
|
+
case "attached":
|
|
7374
|
+
shown = new Set(message.holds);
|
|
7375
|
+
host = message.host ?? null;
|
|
7376
|
+
return;
|
|
7377
|
+
case "events":
|
|
7378
|
+
eventsOn = message.on;
|
|
7379
|
+
eventRate = new RateWindow(MAX_RUN_EVENT_RATE);
|
|
7380
|
+
droppedEvents = 0;
|
|
7381
|
+
return;
|
|
7382
|
+
case "share":
|
|
7383
|
+
sharedHere = message.on;
|
|
7384
|
+
void shareLiveView(message.on).then(tellLive);
|
|
7385
|
+
return;
|
|
7386
|
+
case "breakpoints":
|
|
7387
|
+
control?.breakpoints(message.places);
|
|
7388
|
+
return;
|
|
7389
|
+
case "control":
|
|
7390
|
+
if (message.action === "pause") {
|
|
7391
|
+
if (!held) control?.pause();
|
|
7392
|
+
} else if (held?.stop === message.stop) held.answer(message.action);
|
|
7393
|
+
return;
|
|
7394
|
+
case "run-to":
|
|
7395
|
+
if (held?.stop !== message.stop) return;
|
|
7396
|
+
control?.runTo(message.place);
|
|
7397
|
+
held.answer("resume");
|
|
7398
|
+
return;
|
|
7399
|
+
case "request": {
|
|
7400
|
+
const { request } = message;
|
|
7401
|
+
const at = message.what !== "image" && held?.stop === message.stop ? held : null;
|
|
7402
|
+
switch (message.what) {
|
|
7403
|
+
case "image":
|
|
7404
|
+
void imageOf(held?.page ?? null).then((value) => tellHub({ type: "answer", request, what: "image", value }));
|
|
7405
|
+
return;
|
|
7406
|
+
case "requests": {
|
|
7407
|
+
const requests = at ? keptRequests() : [];
|
|
7408
|
+
const said = !at ? WENT_ON : requests.length ? `The test kept ${requests.length === 1 ? "1 response" : `${requests.length} responses`}.` : "The test kept no response yet: Piwi keeps the text answers of its fetch and XHR requests.";
|
|
7409
|
+
tellHub({ type: "answer", request, what: "requests", value: { ok: !!at, message: said, requests } });
|
|
7410
|
+
return;
|
|
7411
|
+
}
|
|
7412
|
+
case "mock": {
|
|
7413
|
+
void (at ? keptResponse(message.id) : Promise.resolve({ refusal: WENT_ON })).then((found) => {
|
|
7414
|
+
const value = "refusal" in found ? { ok: false, message: found.refusal, response: null } : { ok: true, message: `${found.method} ${found.url}`, response: found };
|
|
7415
|
+
tellHub({ type: "answer", request, what: "mock", value });
|
|
7416
|
+
});
|
|
7417
|
+
return;
|
|
7418
|
+
}
|
|
7419
|
+
case "locator": {
|
|
7420
|
+
const counted = at ? counting.then(
|
|
7421
|
+
() => held === at ? countLocator(at, message.locator) : { ok: false, message: WENT_ON, count: null }
|
|
7422
|
+
) : Promise.resolve({ ok: false, message: WENT_ON, count: null });
|
|
7423
|
+
counting = counted.catch(() => void 0);
|
|
7424
|
+
void counted.then((value) => tellHub({ type: "answer", request, what: "locator", value }));
|
|
7425
|
+
return;
|
|
7426
|
+
}
|
|
7427
|
+
case "view": {
|
|
7428
|
+
const on = message.on;
|
|
7429
|
+
const answered = viewTurn.then(
|
|
7430
|
+
() => !at ? Promise.resolve({ ok: false, message: WENT_ON }) : on ? startView2(at) : stopViewOf(at)
|
|
7431
|
+
);
|
|
7432
|
+
viewTurn = answered.catch(() => void 0);
|
|
7433
|
+
void answered.then((value) => tellHub({ type: "answer", request, what: "view", value }));
|
|
7434
|
+
return;
|
|
7435
|
+
}
|
|
7436
|
+
case "input": {
|
|
7437
|
+
const played = at ? playInput(at, message.events) : Promise.resolve({ ok: false, message: WENT_ON });
|
|
7438
|
+
void played.then((value) => tellHub({ type: "answer", request, what: "input", value }));
|
|
7439
|
+
return;
|
|
7440
|
+
}
|
|
7441
|
+
case "pick-at": {
|
|
7442
|
+
const picked = at ? pickAtStop(at, message.x, message.y) : Promise.resolve({ ok: false, message: WENT_ON, locator: null });
|
|
7443
|
+
void picked.then((value) => tellHub({ type: "answer", request, what: "pick-at", value }));
|
|
7444
|
+
return;
|
|
7445
|
+
}
|
|
7446
|
+
case "snapshot": {
|
|
7447
|
+
const { depth, image } = message;
|
|
7448
|
+
const none = { url: "", title: "", snapshot: null, cut: false, image: null };
|
|
7449
|
+
void agentAt(
|
|
7450
|
+
at,
|
|
7451
|
+
(agent) => agent.snapshot(depth, image),
|
|
7452
|
+
(refused) => ({ ...refused, ...none })
|
|
7453
|
+
).then((value) => tellHub({ type: "answer", request, what: "snapshot", value }));
|
|
7454
|
+
return;
|
|
7455
|
+
}
|
|
7456
|
+
case "ref-locator": {
|
|
7457
|
+
const { ref } = message;
|
|
7458
|
+
void agentAt(
|
|
7459
|
+
at,
|
|
7460
|
+
(agent) => agent.refLocator(ref),
|
|
7461
|
+
(refused) => ({ ...refused, element: null, locators: [] })
|
|
7462
|
+
).then((value) => tellHub({ type: "answer", request, what: "ref-locator", value }));
|
|
7463
|
+
return;
|
|
7464
|
+
}
|
|
7465
|
+
case "act": {
|
|
7466
|
+
const { ref, action, value: given } = message;
|
|
7467
|
+
const answer = (value) => tellHub({ type: "answer", request, what: "act", value });
|
|
7468
|
+
void agentAt(
|
|
7469
|
+
at,
|
|
7470
|
+
(agent) => agent.act({ ref, action, value: given }),
|
|
7471
|
+
(refused) => ({ ...refused, element: null, locators: [] }),
|
|
7472
|
+
{ answer: { ok: false, message: LATE_ACT, element: null, locators: [], late: true }, again: answer }
|
|
7473
|
+
).then(answer);
|
|
7474
|
+
return;
|
|
7475
|
+
}
|
|
7476
|
+
case "bind": {
|
|
7477
|
+
void bindAt(at, message.on).then((value) => tellHub({ type: "answer", request, what: "bind", value }));
|
|
7478
|
+
return;
|
|
7479
|
+
}
|
|
7480
|
+
default: {
|
|
7481
|
+
const value = at ? at.intent(message.what) : { ok: false, message: WENT_ON };
|
|
7482
|
+
tellHub({ type: "answer", request, what: message.what, value });
|
|
7483
|
+
return;
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
7487
|
+
case "unknown":
|
|
7488
|
+
tellHub({
|
|
7489
|
+
type: "notice",
|
|
7490
|
+
message: `@piwitests/reporter ${getReporterVersion()} does not take the editor's ${message.what}: update it.`
|
|
7491
|
+
});
|
|
7492
|
+
}
|
|
7493
|
+
}
|
|
7494
|
+
function agentAt(at, run, refuse2, late) {
|
|
7495
|
+
if (!at) return Promise.resolve(refuse2({ ok: false, message: WENT_ON }));
|
|
7496
|
+
const agent = at.agent;
|
|
7497
|
+
if (!agent) return Promise.resolve(refuse2({ ok: false, message: NO_AGENT_PAGE }));
|
|
7498
|
+
const answered = agentTurn.then(async () => {
|
|
7499
|
+
if (held !== at) return refuse2({ ok: false, message: WENT_ON });
|
|
7500
|
+
const running2 = run(agent);
|
|
7501
|
+
const settled = running2.then(
|
|
7502
|
+
(value) => ({ value }),
|
|
7503
|
+
() => ({ value: refuse2({ ok: false, message: NO_ANSWER }) })
|
|
7504
|
+
);
|
|
7505
|
+
const said = await answerWithin(settled, PAGE_ANSWER_MS, null);
|
|
7506
|
+
if (said) return said.value;
|
|
7507
|
+
if (!late) return refuse2({ ok: false, message: NO_ANSWER });
|
|
7508
|
+
void running2.then((value) => value.ok && held === at && late.again(value)).catch(() => void 0);
|
|
7509
|
+
return late.answer;
|
|
7510
|
+
});
|
|
7511
|
+
agentTurn = answered.catch(() => void 0);
|
|
7512
|
+
return answered;
|
|
7513
|
+
}
|
|
7514
|
+
async function bindAt(at, on) {
|
|
7515
|
+
if (!at) return { ok: false, message: WENT_ON, name: null, endpoint: null };
|
|
7516
|
+
const browser = at.browser;
|
|
7517
|
+
if (!on) {
|
|
7518
|
+
await unbind();
|
|
7519
|
+
return { ok: true, message: "The browser is no longer shared with Playwright tools.", name: null, endpoint: null };
|
|
7520
|
+
}
|
|
7521
|
+
if (!browser) return { ok: false, message: NO_AGENT_PAGE, name: null, endpoint: null };
|
|
7522
|
+
if (bound?.browser === browser) {
|
|
7523
|
+
return {
|
|
7524
|
+
ok: true,
|
|
7525
|
+
message: `The browser is shared with Playwright tools as ${runName} until the test ends: any process of yours can drive it.`,
|
|
7526
|
+
name: runName,
|
|
7527
|
+
endpoint: bound.endpoint
|
|
7528
|
+
};
|
|
7529
|
+
}
|
|
7530
|
+
if (bound) await unbind();
|
|
7531
|
+
const answer = await internalCall(browser, () => bindBrowser(browser, runName, process.cwd(), true));
|
|
7532
|
+
if (answer.ok) bound = { browser, endpoint: answer.endpoint };
|
|
7533
|
+
return answer;
|
|
7534
|
+
}
|
|
7535
|
+
async function imageOf(page) {
|
|
7536
|
+
if (!page || page.isClosed()) return null;
|
|
7537
|
+
try {
|
|
7538
|
+
const shot = await internalCall(
|
|
7539
|
+
page,
|
|
7540
|
+
() => page.screenshot({ type: "jpeg", quality: 60, style: BAR_HIDDEN2, timeout: 5e3 })
|
|
7541
|
+
);
|
|
7542
|
+
const viewport = await viewportOf(page);
|
|
7543
|
+
return { mime: "image/jpeg", data: shot.toString("base64"), ...viewport };
|
|
7544
|
+
} catch {
|
|
7545
|
+
return null;
|
|
7546
|
+
}
|
|
7547
|
+
}
|
|
7548
|
+
|
|
7549
|
+
// src/internal/capture/run-events.ts
|
|
7550
|
+
var begun = /* @__PURE__ */ new WeakMap();
|
|
7551
|
+
var heard = /* @__PURE__ */ new WeakSet();
|
|
7552
|
+
var watched = /* @__PURE__ */ new WeakSet();
|
|
7553
|
+
function userFile(file, cwd, windows) {
|
|
7554
|
+
if (!file || /[\\/]node_modules[\\/]/.test(file)) return null;
|
|
7555
|
+
const relative6 = callSitePath(file, cwd, windows);
|
|
7556
|
+
if (!relative6 || (windows ? path9.win32 : path9.posix).isAbsolute(relative6) || relative6.startsWith("..")) return null;
|
|
7557
|
+
return relative6;
|
|
7558
|
+
}
|
|
7559
|
+
function callPlace(frames, cwd = process.cwd(), windows = process.platform === "win32") {
|
|
7560
|
+
for (const frame of frames ?? []) {
|
|
7561
|
+
const file = userFile(frame.file, cwd, windows);
|
|
7562
|
+
if (!file || !frame.line || frame.line < 1) continue;
|
|
7563
|
+
return { file, line: frame.line, ...frame.column && frame.column > 0 ? { column: frame.column } : {} };
|
|
7564
|
+
}
|
|
7565
|
+
return null;
|
|
7566
|
+
}
|
|
7567
|
+
function callTitle(zone, channel) {
|
|
7568
|
+
const selector = typeof channel?.params?.selector === "string" ? ` ${channel.params.selector}` : "";
|
|
7569
|
+
const title = zone.title || `${zone.apiName || "call"}${selector}`;
|
|
7570
|
+
return hideSecretsInText(title).slice(0, 300);
|
|
7571
|
+
}
|
|
7572
|
+
function errorText(error) {
|
|
7573
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7574
|
+
return hideSecretsInText(message.split("\n").slice(0, 4).join("\n")).slice(0, MAX_RUN_EVENT_TEXT);
|
|
7575
|
+
}
|
|
7576
|
+
function watchCalls(playwright) {
|
|
7577
|
+
const instrumentation = playwright?._instrumentation;
|
|
7578
|
+
if (!playwright || heard.has(playwright)) return true;
|
|
7579
|
+
if (!instrumentation || typeof instrumentation.addListener !== "function") return false;
|
|
7580
|
+
heard.add(playwright);
|
|
7581
|
+
const titles = /* @__PURE__ */ new WeakMap();
|
|
7582
|
+
instrumentation.addListener({
|
|
7583
|
+
onApiCallBegin: (zone, channel) => {
|
|
7584
|
+
if (!runEventsWanted()) return;
|
|
7585
|
+
begun.set(zone, Date.now());
|
|
7586
|
+
titles.set(zone, callTitle(zone, channel));
|
|
7587
|
+
},
|
|
7588
|
+
onApiCallEnd: (zone) => {
|
|
7589
|
+
const start = begun.get(zone);
|
|
7590
|
+
if (start === void 0) return;
|
|
7591
|
+
begun.delete(zone);
|
|
7592
|
+
if (!runEventsWanted()) return;
|
|
7593
|
+
tellEvent({
|
|
7594
|
+
kind: "call",
|
|
7595
|
+
title: titles.get(zone) ?? callTitle(zone, void 0),
|
|
7596
|
+
place: callPlace(zone.frames),
|
|
7597
|
+
ok: zone.error === void 0,
|
|
7598
|
+
error: zone.error === void 0 ? null : errorText(zone.error),
|
|
7599
|
+
ms: Date.now() - start
|
|
7600
|
+
});
|
|
7601
|
+
}
|
|
7602
|
+
});
|
|
7603
|
+
return true;
|
|
7604
|
+
}
|
|
7605
|
+
function watchPageEvents(page) {
|
|
7606
|
+
if (watched.has(page) || typeof page.on !== "function") return;
|
|
7607
|
+
watched.add(page);
|
|
7608
|
+
const text = (value) => hideSecretsInText(value).slice(0, MAX_RUN_EVENT_TEXT);
|
|
7609
|
+
const url = (value) => hideSecretsInUrl(value).slice(0, 8192);
|
|
7610
|
+
const guard = (tell) => {
|
|
7611
|
+
if (!runEventsWanted()) return;
|
|
7612
|
+
try {
|
|
7613
|
+
tell();
|
|
7614
|
+
} catch {
|
|
7615
|
+
}
|
|
7616
|
+
};
|
|
7617
|
+
page.on(
|
|
7618
|
+
"framenavigated",
|
|
7619
|
+
(frame) => guard(() => {
|
|
7620
|
+
if (frame === page.mainFrame()) tellEvent({ kind: "navigation", url: url(frame.url()) });
|
|
7621
|
+
})
|
|
7622
|
+
);
|
|
7623
|
+
page.on(
|
|
7624
|
+
"console",
|
|
7625
|
+
(message) => guard(() => {
|
|
7626
|
+
const type = message.type();
|
|
7627
|
+
if (type === "error" || type === "warning")
|
|
7628
|
+
tellEvent({ kind: "console", level: type, text: text(message.text()) });
|
|
7629
|
+
})
|
|
7630
|
+
);
|
|
7631
|
+
page.on("pageerror", (error) => guard(() => tellEvent({ kind: "page-error", text: text(errorText(error)) })));
|
|
7632
|
+
page.on(
|
|
7633
|
+
"requestfailed",
|
|
7634
|
+
(request) => guard(
|
|
7635
|
+
() => tellEvent({
|
|
7636
|
+
kind: "request-failed",
|
|
7637
|
+
method: request.method(),
|
|
7638
|
+
url: url(request.url()),
|
|
7639
|
+
status: null,
|
|
7640
|
+
failure: request.failure()?.errorText ? text(request.failure().errorText).slice(0, 500) : null
|
|
7641
|
+
})
|
|
7642
|
+
)
|
|
7643
|
+
);
|
|
7644
|
+
page.on(
|
|
7645
|
+
"response",
|
|
7646
|
+
(response) => guard(() => {
|
|
7647
|
+
const status = response.status();
|
|
7648
|
+
if (status < 400 || status > 599) return;
|
|
7649
|
+
const request = response.request();
|
|
7650
|
+
tellEvent({ kind: "request-failed", method: request.method(), url: url(response.url()), status, failure: null });
|
|
7651
|
+
})
|
|
7652
|
+
);
|
|
7653
|
+
}
|
|
7654
|
+
|
|
7655
|
+
// src/internal/capture/region-snapshot.ts
|
|
7656
|
+
function regionSnapshotWithoutSecrets(snapshot, secrets) {
|
|
7657
|
+
const hidden2 = new Set(secrets.filter(Boolean));
|
|
7658
|
+
const kept = [];
|
|
7659
|
+
for (const line of snapshot.split("\n")) {
|
|
7660
|
+
const box = /^(\s*- textbox(?: "(?:[^"\\]|\\.)*")?(?: \[[^\]]*\])*): (.*)$/.exec(line);
|
|
7661
|
+
if (box && hidden2.size && hidden2.has(yamlScalar(box[2]))) {
|
|
7662
|
+
kept.push({ line: box[1], lostChild: false });
|
|
7663
|
+
continue;
|
|
7664
|
+
}
|
|
7665
|
+
const url = /^\s*- \/url: (.*)$/.exec(line);
|
|
7666
|
+
if (url && hideSecretsInUrl(yamlScalar(url[1])) !== yamlScalar(url[1])) {
|
|
7667
|
+
let parent = kept.length - 1;
|
|
7668
|
+
while (parent >= 0 && indentOf(kept[parent].line) >= indentOf(line)) parent--;
|
|
7669
|
+
if (parent >= 0) kept[parent].lostChild = true;
|
|
7670
|
+
continue;
|
|
7671
|
+
}
|
|
7672
|
+
kept.push({ line, lostChild: false });
|
|
7673
|
+
}
|
|
7674
|
+
return kept.map(({ line, lostChild }, i) => {
|
|
7675
|
+
const next = kept[i + 1];
|
|
7676
|
+
const childless = !next || indentOf(next.line) <= indentOf(line);
|
|
7677
|
+
return lostChild && childless && line.endsWith(":") ? line.slice(0, -1) : line;
|
|
7678
|
+
}).join("\n");
|
|
7679
|
+
}
|
|
7680
|
+
function indentOf(line) {
|
|
7681
|
+
return line.length - line.trimStart().length;
|
|
7682
|
+
}
|
|
7683
|
+
function yamlScalar(text) {
|
|
7684
|
+
const value = text.trim();
|
|
7685
|
+
if (value.startsWith('"')) {
|
|
7686
|
+
try {
|
|
7687
|
+
return String(JSON.parse(value));
|
|
7688
|
+
} catch {
|
|
7689
|
+
return value;
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
if (value.startsWith("'") && value.endsWith("'") && value.length > 1) return value.slice(1, -1).replace(/''/g, "'");
|
|
7693
|
+
return value;
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7696
|
+
// src/internal/capture/record-handoff.ts
|
|
7697
|
+
var fs6 = __toESM(require("fs"));
|
|
7698
|
+
var net2 = __toESM(require("net"));
|
|
7699
|
+
var path10 = __toESM(require("path"));
|
|
7700
|
+
var START_WAIT_MS = 6e4;
|
|
7701
|
+
var NEW_TAB_NOTICE = "A new tab opened: what you do there is not recorded yet. Record in the first tab.";
|
|
7702
|
+
var relays = /* @__PURE__ */ new WeakMap();
|
|
7703
|
+
function idleAnswer(payload) {
|
|
7704
|
+
const request = payload ?? {};
|
|
7705
|
+
if (request.kind === "local-get") return {};
|
|
7706
|
+
if (request.kind !== "message") return null;
|
|
7707
|
+
if (request.message?.type === "piwi-ping") return { ok: true };
|
|
7708
|
+
if (request.message?.type === "piwi-session-storage") {
|
|
7709
|
+
return request.message.op === "get" ? { ok: true, items: {} } : { ok: true };
|
|
7710
|
+
}
|
|
7711
|
+
return { ok: false, error: "No recording runs in this page." };
|
|
7712
|
+
}
|
|
7713
|
+
function canRecordFromHere(channel = process.env[RECORD_CHANNEL_ENV]) {
|
|
7714
|
+
return parseRecordChannel(channel) !== null;
|
|
7715
|
+
}
|
|
7716
|
+
function handoffPlace(location, cwd = process.cwd()) {
|
|
7717
|
+
const m = /^(.+):(\d+):\d+$/.exec(location);
|
|
7718
|
+
if (!m) return null;
|
|
7719
|
+
return { file: path10.resolve(cwd, m[1]), line: Number(m[2]) };
|
|
7720
|
+
}
|
|
7721
|
+
async function recordFromHere(options) {
|
|
7722
|
+
const channel = parseRecordChannel(options.channel ?? process.env[RECORD_CHANNEL_ENV]);
|
|
7723
|
+
const place = handoffPlace(options.location);
|
|
7724
|
+
if (!channel || !place) return "this run names no editor to record into.";
|
|
7725
|
+
const { page, testInfo, ended } = options;
|
|
7726
|
+
if (ended?.aborted) return "the test ended.";
|
|
7727
|
+
const use = testInfo.project?.use ?? {};
|
|
7728
|
+
const context = page.context();
|
|
7729
|
+
return new Promise((resolve5) => {
|
|
7730
|
+
const socket2 = net2.connect(channel.path);
|
|
7731
|
+
socket2.setEncoding("utf8");
|
|
7732
|
+
const answers = /* @__PURE__ */ new Map();
|
|
7733
|
+
let calls = 0;
|
|
7734
|
+
let attached = false;
|
|
7735
|
+
let recording = false;
|
|
7736
|
+
let settled = false;
|
|
7737
|
+
let initScript;
|
|
7738
|
+
const send2 = (message) => {
|
|
7739
|
+
if (!socket2.destroyed) socket2.write(`${JSON.stringify(message)}
|
|
7740
|
+
`);
|
|
7741
|
+
};
|
|
7742
|
+
const onPage = () => send2({ type: "notice", message: NEW_TAB_NOTICE });
|
|
7743
|
+
const onGone = () => {
|
|
7744
|
+
if (attached) send2({ type: "closed" });
|
|
7745
|
+
finish("the page closed.");
|
|
7746
|
+
};
|
|
7747
|
+
const onEnded = () => {
|
|
7748
|
+
try {
|
|
7749
|
+
if (recording) {
|
|
7750
|
+
void internalCall(
|
|
7751
|
+
page,
|
|
7752
|
+
() => page.evaluate(
|
|
7753
|
+
([name, value]) => {
|
|
7754
|
+
const dispatch = globalThis[name];
|
|
7755
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
7756
|
+
},
|
|
7757
|
+
[IDE_DISPATCH_GLOBAL, { type: "piwi-recording-stopped" }]
|
|
7758
|
+
)
|
|
7759
|
+
).catch(() => void 0);
|
|
5768
7760
|
}
|
|
7761
|
+
if (attached) send2({ type: "closed" });
|
|
7762
|
+
finish("the test ended.");
|
|
7763
|
+
} catch {
|
|
5769
7764
|
}
|
|
5770
|
-
}
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
7765
|
+
};
|
|
7766
|
+
page.once("close", onGone);
|
|
7767
|
+
page.once("crash", onGone);
|
|
7768
|
+
ended?.addEventListener("abort", onEnded, { once: true });
|
|
7769
|
+
const startWait = setTimeout(() => finish("the editor did not start the recording."), START_WAIT_MS);
|
|
7770
|
+
const release = () => {
|
|
7771
|
+
const relay = relays.get(context);
|
|
7772
|
+
if (relay && relay.recording === relayed) relay.recording = null;
|
|
7773
|
+
void Promise.resolve(initScript?.dispose?.()).catch(() => void 0);
|
|
7774
|
+
initScript = void 0;
|
|
7775
|
+
};
|
|
7776
|
+
const finish = (said) => {
|
|
7777
|
+
if (settled) return;
|
|
7778
|
+
settled = true;
|
|
7779
|
+
clearTimeout(startWait);
|
|
7780
|
+
release();
|
|
7781
|
+
for (const [call, answer] of answers) {
|
|
7782
|
+
answers.delete(call);
|
|
7783
|
+
answer(null);
|
|
7784
|
+
}
|
|
7785
|
+
context.off("page", onPage);
|
|
7786
|
+
page.off("close", onGone);
|
|
7787
|
+
page.off("crash", onGone);
|
|
7788
|
+
ended?.removeEventListener("abort", onEnded);
|
|
7789
|
+
socket2.end();
|
|
7790
|
+
resolve5(recording ? null : said);
|
|
7791
|
+
};
|
|
7792
|
+
const relayed = (source, payload) => new Promise((answer) => {
|
|
7793
|
+
const call = ++calls;
|
|
7794
|
+
answers.set(call, answer);
|
|
7795
|
+
const outside = source.page !== page || source.frame !== page.mainFrame();
|
|
7796
|
+
send2({ type: "binding", call, outside, payload });
|
|
7797
|
+
});
|
|
7798
|
+
const load = async (bundle) => {
|
|
7799
|
+
if (!/^https?:/i.test(page.url())) {
|
|
7800
|
+
const why = `the recorder records http(s) pages only, and this page is ${page.url() || "blank"}.`;
|
|
7801
|
+
send2({ type: "notice", message: `Nothing can be recorded here: ${why}` });
|
|
7802
|
+
finish(why);
|
|
7803
|
+
return;
|
|
7804
|
+
}
|
|
7805
|
+
let relay = relays.get(context);
|
|
7806
|
+
if (!relay) {
|
|
7807
|
+
const created = { recording: null, lingering: false };
|
|
7808
|
+
await context.exposeBinding(
|
|
7809
|
+
IDE_RECORDER_BINDING,
|
|
7810
|
+
(source, payload) => created.recording ? created.recording(source, payload) : idleAnswer(payload)
|
|
7811
|
+
);
|
|
7812
|
+
relays.set(context, created);
|
|
7813
|
+
relay = created;
|
|
7814
|
+
}
|
|
7815
|
+
if (settled) return;
|
|
7816
|
+
relay.recording = relayed;
|
|
7817
|
+
if (!relay.lingering) {
|
|
7818
|
+
initScript = await context.addInitScript({ path: bundle });
|
|
7819
|
+
if (typeof initScript?.dispose !== "function") relay.lingering = true;
|
|
7820
|
+
}
|
|
7821
|
+
if (settled) return release();
|
|
7822
|
+
await page.evaluate(fs6.readFileSync(bundle, "utf-8"));
|
|
7823
|
+
if (settled) return release();
|
|
7824
|
+
recording = true;
|
|
7825
|
+
clearTimeout(startWait);
|
|
7826
|
+
context.on("page", onPage);
|
|
7827
|
+
send2({ type: "loaded" });
|
|
7828
|
+
};
|
|
7829
|
+
socket2.on("connect", () => {
|
|
7830
|
+
attached = true;
|
|
7831
|
+
send2({
|
|
7832
|
+
type: "attach",
|
|
7833
|
+
token: channel.token,
|
|
7834
|
+
file: place.file,
|
|
7835
|
+
line: place.line,
|
|
7836
|
+
reason: options.reason,
|
|
7837
|
+
testIdAttribute: typeof use.testIdAttribute === "string" ? use.testIdAttribute : null,
|
|
7838
|
+
baseURL: typeof use.baseURL === "string" ? use.baseURL : null,
|
|
7839
|
+
...options.mode === "check" ? { mode: "check", stop: options.stop ?? null } : {}
|
|
5775
7840
|
});
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
7841
|
+
});
|
|
7842
|
+
socket2.on(
|
|
7843
|
+
"data",
|
|
7844
|
+
lineReader(
|
|
7845
|
+
(line) => {
|
|
7846
|
+
const message = parseHandoffToTest(line);
|
|
7847
|
+
if (!message || settled) return;
|
|
7848
|
+
switch (message.type) {
|
|
7849
|
+
case "refused":
|
|
7850
|
+
return finish(message.message);
|
|
7851
|
+
case "load":
|
|
7852
|
+
return void load(message.bundle).catch(
|
|
7853
|
+
(error) => finish(`the recorder could not be loaded: ${error instanceof Error ? error.message : String(error)}`)
|
|
7854
|
+
);
|
|
7855
|
+
case "answer": {
|
|
7856
|
+
const answer = answers.get(message.call);
|
|
7857
|
+
answers.delete(message.call);
|
|
7858
|
+
return answer?.(message.value);
|
|
7859
|
+
}
|
|
7860
|
+
case "dispatch":
|
|
7861
|
+
return void page.evaluate(
|
|
7862
|
+
([name, value]) => {
|
|
7863
|
+
const dispatch = globalThis[name];
|
|
7864
|
+
if (typeof dispatch === "function") dispatch(value);
|
|
7865
|
+
},
|
|
7866
|
+
[IDE_DISPATCH_GLOBAL, message.message]
|
|
7867
|
+
).catch(() => void 0);
|
|
7868
|
+
case "stop":
|
|
7869
|
+
return finish(null);
|
|
7870
|
+
}
|
|
7871
|
+
},
|
|
7872
|
+
() => finish("the editor sent a message too long to read.")
|
|
7873
|
+
)
|
|
5784
7874
|
);
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
}
|
|
7875
|
+
socket2.on("error", (error) => finish(`the editor could not be reached: ${error.message}`));
|
|
7876
|
+
socket2.on("close", () => finish("the editor closed the recording."));
|
|
7877
|
+
});
|
|
5789
7878
|
}
|
|
5790
7879
|
|
|
5791
7880
|
// src/internal/collect/coverage-settings.ts
|
|
@@ -5806,7 +7895,7 @@ function browserFunctionsInWorker() {
|
|
|
5806
7895
|
function browserFunctionCoverage(files) {
|
|
5807
7896
|
return sanitizeCoverage({
|
|
5808
7897
|
v: 1,
|
|
5809
|
-
files: [...files].map(([
|
|
7898
|
+
files: [...files].map(([path16, functions]) => ({ path: path16, runtime: "browser", functions }))
|
|
5810
7899
|
});
|
|
5811
7900
|
}
|
|
5812
7901
|
function newKnownFunctions(known, sent2) {
|
|
@@ -5819,19 +7908,19 @@ function newKnownFunctions(known, sent2) {
|
|
|
5819
7908
|
budget = MAX_COVERAGE_RANGES;
|
|
5820
7909
|
};
|
|
5821
7910
|
for (const [raw, ranges] of known) {
|
|
5822
|
-
const
|
|
5823
|
-
if (
|
|
5824
|
-
let named = sent2.get(
|
|
7911
|
+
const path16 = normalizeCoveragePath(raw);
|
|
7912
|
+
if (path16 === null) continue;
|
|
7913
|
+
let named = sent2.get(path16);
|
|
5825
7914
|
let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
|
|
5826
7915
|
if (fresh.length === 0) continue;
|
|
5827
|
-
if (!named) sent2.set(
|
|
7916
|
+
if (!named) sent2.set(path16, named = /* @__PURE__ */ new Set());
|
|
5828
7917
|
for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
|
|
5829
7918
|
while (fresh.length > 0) {
|
|
5830
7919
|
if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
|
|
5831
7920
|
const part = fresh.slice(0, budget);
|
|
5832
7921
|
fresh = fresh.slice(part.length);
|
|
5833
7922
|
budget -= part.length;
|
|
5834
|
-
files.push({ path:
|
|
7923
|
+
files.push({ path: path16, allFunctions: part });
|
|
5835
7924
|
}
|
|
5836
7925
|
}
|
|
5837
7926
|
close();
|
|
@@ -5842,7 +7931,7 @@ function serverCoverageInWorker() {
|
|
|
5842
7931
|
}
|
|
5843
7932
|
|
|
5844
7933
|
// src/internal/support/aria-sampling.ts
|
|
5845
|
-
var
|
|
7934
|
+
var path11 = __toESM(require("path"));
|
|
5846
7935
|
var os3 = __toESM(require("os"));
|
|
5847
7936
|
var fs7 = __toESM(require("fs"));
|
|
5848
7937
|
|
|
@@ -5858,7 +7947,7 @@ function ariaSampleIdentity(filePath2, title) {
|
|
|
5858
7947
|
return `${filePath2}\0${title}`;
|
|
5859
7948
|
}
|
|
5860
7949
|
function getAriaSampleFilePath(projectName) {
|
|
5861
|
-
return
|
|
7950
|
+
return path11.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
|
|
5862
7951
|
}
|
|
5863
7952
|
var cachedSets = /* @__PURE__ */ new Map();
|
|
5864
7953
|
function loadAriaSampleSet(projectName) {
|
|
@@ -5889,7 +7978,7 @@ function isDueForAriaSample(testInfo) {
|
|
|
5889
7978
|
// src/internal/capture/code-reach.ts
|
|
5890
7979
|
var crypto3 = __toESM(require("crypto"));
|
|
5891
7980
|
var fs8 = __toESM(require("fs"));
|
|
5892
|
-
var
|
|
7981
|
+
var path12 = __toESM(require("path"));
|
|
5893
7982
|
var import_node_child_process = require("child_process");
|
|
5894
7983
|
var MAX_MAP_BYTES = 20 * 1024 * 1024;
|
|
5895
7984
|
var MAP_TIMEOUT_MS = 5e3;
|
|
@@ -5932,12 +8021,12 @@ function repositoryRoot(from) {
|
|
|
5932
8021
|
}).trim();
|
|
5933
8022
|
} catch {
|
|
5934
8023
|
}
|
|
5935
|
-
cachedRepoRoot = { from, root:
|
|
8024
|
+
cachedRepoRoot = { from, root: path12.resolve(root) };
|
|
5936
8025
|
return cachedRepoRoot.root;
|
|
5937
8026
|
}
|
|
5938
8027
|
function codeReachRoots(configDir, configured) {
|
|
5939
8028
|
const repoRoot = repositoryRoot(configDir);
|
|
5940
|
-
const resolve5 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) =>
|
|
8029
|
+
const resolve5 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path12.resolve(configDir, r));
|
|
5941
8030
|
const global = Array.isArray(configured) ? resolve5(configured) : resolve5(configured?.["*"]);
|
|
5942
8031
|
const byOrigin = /* @__PURE__ */ new Map();
|
|
5943
8032
|
if (configured && !Array.isArray(configured)) {
|
|
@@ -5974,10 +8063,10 @@ function isFile(file) {
|
|
|
5974
8063
|
function resolveSourcePath(source, roots) {
|
|
5975
8064
|
const normalized = normalizeSourcePath(source);
|
|
5976
8065
|
if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
|
|
5977
|
-
if (
|
|
5978
|
-
const
|
|
8066
|
+
if (path12.isAbsolute(normalized) && isFile(normalized)) return path12.resolve(normalized);
|
|
8067
|
+
const relative6 = normalized.replace(/^\/+/, "");
|
|
5979
8068
|
for (const root of roots) {
|
|
5980
|
-
const candidate =
|
|
8069
|
+
const candidate = path12.resolve(root, relative6);
|
|
5981
8070
|
if (isFile(candidate)) return candidate;
|
|
5982
8071
|
}
|
|
5983
8072
|
return null;
|
|
@@ -6009,7 +8098,7 @@ function fileForUrl(url, roots, bases) {
|
|
|
6009
8098
|
return file;
|
|
6010
8099
|
}
|
|
6011
8100
|
function relativeToRepo(file, repoRoot) {
|
|
6012
|
-
return
|
|
8101
|
+
return path12.relative(repoRoot, file).split(path12.sep).join("/");
|
|
6013
8102
|
}
|
|
6014
8103
|
function scriptVersion(entry) {
|
|
6015
8104
|
return entry.hash ?? crypto3.createHash("sha1").update(entry.source ?? "").digest("hex");
|
|
@@ -6072,10 +8161,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
|
|
|
6072
8161
|
const files = /* @__PURE__ */ new Map();
|
|
6073
8162
|
const known = /* @__PURE__ */ new Map();
|
|
6074
8163
|
const addTo = (into, file, functions) => {
|
|
6075
|
-
const
|
|
6076
|
-
const listed = into.get(
|
|
8164
|
+
const path16 = relativeToRepo(file, roots.repoRoot);
|
|
8165
|
+
const listed = into.get(path16);
|
|
6077
8166
|
if (listed) listed.push(...functions);
|
|
6078
|
-
else into.set(
|
|
8167
|
+
else into.set(path16, [...functions]);
|
|
6079
8168
|
};
|
|
6080
8169
|
const add2 = (file, functions) => addTo(files, file, functions);
|
|
6081
8170
|
const bases = viteBases(entries);
|
|
@@ -6682,7 +8771,7 @@ async function resolveCssCoverage(sheets, roots, fetchMap) {
|
|
|
6682
8771
|
var childProcess = __toESM(require("child_process"));
|
|
6683
8772
|
var fs11 = __toESM(require("fs"));
|
|
6684
8773
|
var net3 = __toESM(require("net"));
|
|
6685
|
-
var
|
|
8774
|
+
var path14 = __toESM(require("path"));
|
|
6686
8775
|
var import_node_module2 = require("module");
|
|
6687
8776
|
|
|
6688
8777
|
// src/internal/capture/worker-metrics.ts
|
|
@@ -6690,7 +8779,7 @@ var import_node_perf_hooks = require("perf_hooks");
|
|
|
6690
8779
|
|
|
6691
8780
|
// src/internal/support/system-readers.ts
|
|
6692
8781
|
var fs10 = __toESM(require("fs"));
|
|
6693
|
-
var
|
|
8782
|
+
var path13 = __toESM(require("path"));
|
|
6694
8783
|
var CLK_TCK = 100;
|
|
6695
8784
|
function readText(file) {
|
|
6696
8785
|
try {
|
|
@@ -6776,7 +8865,7 @@ var ProcFs = class {
|
|
|
6776
8865
|
}
|
|
6777
8866
|
/** Whether this looks like a Linux `/proc` at all. */
|
|
6778
8867
|
available() {
|
|
6779
|
-
return fs10.existsSync(
|
|
8868
|
+
return fs10.existsSync(path13.join(this.root, "stat"));
|
|
6780
8869
|
}
|
|
6781
8870
|
/** Every process, keyed by pid. */
|
|
6782
8871
|
table() {
|
|
@@ -6789,32 +8878,32 @@ var ProcFs = class {
|
|
|
6789
8878
|
}
|
|
6790
8879
|
for (const name of names) {
|
|
6791
8880
|
if (!/^\d+$/.test(name)) continue;
|
|
6792
|
-
const text = readText(
|
|
8881
|
+
const text = readText(path13.join(this.root, name, "stat"));
|
|
6793
8882
|
const stat = text ? parseProcStat(Number(name), text) : null;
|
|
6794
8883
|
if (stat) out.set(stat.pid, stat);
|
|
6795
8884
|
}
|
|
6796
8885
|
return out;
|
|
6797
8886
|
}
|
|
6798
8887
|
stat(pid) {
|
|
6799
|
-
const text = readText(
|
|
8888
|
+
const text = readText(path13.join(this.root, String(pid), "stat"));
|
|
6800
8889
|
return text ? parseProcStat(pid, text) : null;
|
|
6801
8890
|
}
|
|
6802
8891
|
runWaitMs(pid) {
|
|
6803
|
-
const text = readText(
|
|
8892
|
+
const text = readText(path13.join(this.root, String(pid), "schedstat"));
|
|
6804
8893
|
return text ? parseSchedstat(text) : null;
|
|
6805
8894
|
}
|
|
6806
8895
|
cmdline(pid) {
|
|
6807
|
-
return (readText(
|
|
8896
|
+
return (readText(path13.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
|
|
6808
8897
|
}
|
|
6809
8898
|
/** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
|
|
6810
8899
|
statusKb(pid, key) {
|
|
6811
|
-
const text = readText(
|
|
8900
|
+
const text = readText(path13.join(this.root, String(pid), "status"));
|
|
6812
8901
|
return text ? parseKbField(text, key) : null;
|
|
6813
8902
|
}
|
|
6814
8903
|
/** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
|
|
6815
8904
|
async pssKb(pid) {
|
|
6816
8905
|
try {
|
|
6817
|
-
return parseKbField(await fs10.promises.readFile(
|
|
8906
|
+
return parseKbField(await fs10.promises.readFile(path13.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
|
|
6818
8907
|
} catch {
|
|
6819
8908
|
return null;
|
|
6820
8909
|
}
|
|
@@ -6822,29 +8911,29 @@ var ProcFs = class {
|
|
|
6822
8911
|
/** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
|
|
6823
8912
|
resetPeakRss(pid) {
|
|
6824
8913
|
try {
|
|
6825
|
-
fs10.writeFileSync(
|
|
8914
|
+
fs10.writeFileSync(path13.join(this.root, String(pid), "clear_refs"), "5");
|
|
6826
8915
|
return true;
|
|
6827
8916
|
} catch {
|
|
6828
8917
|
return false;
|
|
6829
8918
|
}
|
|
6830
8919
|
}
|
|
6831
8920
|
cpuTicks() {
|
|
6832
|
-
const text = readText(
|
|
8921
|
+
const text = readText(path13.join(this.root, "stat"));
|
|
6833
8922
|
return text ? parseCpuTicks(text) : null;
|
|
6834
8923
|
}
|
|
6835
8924
|
/** Total stall time of a resource's pressure file, in µs. */
|
|
6836
8925
|
pressureUs(resource) {
|
|
6837
|
-
const text = readText(
|
|
8926
|
+
const text = readText(path13.join(this.root, "pressure", resource));
|
|
6838
8927
|
return text ? parsePsiTotal(text) : null;
|
|
6839
8928
|
}
|
|
6840
8929
|
memAvailableKb() {
|
|
6841
|
-
const text = readText(
|
|
8930
|
+
const text = readText(path13.join(this.root, "meminfo"));
|
|
6842
8931
|
return text ? parseKbField(text, "MemAvailable") : null;
|
|
6843
8932
|
}
|
|
6844
8933
|
/** Open file descriptors of a process. */
|
|
6845
8934
|
fdCount(pid) {
|
|
6846
8935
|
try {
|
|
6847
|
-
return fs10.readdirSync(
|
|
8936
|
+
return fs10.readdirSync(path13.join(this.root, String(pid), "fd")).length;
|
|
6848
8937
|
} catch {
|
|
6849
8938
|
return null;
|
|
6850
8939
|
}
|
|
@@ -7059,19 +9148,19 @@ var PAGE_READ_CAP = 50;
|
|
|
7059
9148
|
var PAGE_READ_TIMEOUT_MS = 1e3;
|
|
7060
9149
|
var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
|
|
7061
9150
|
var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
|
|
7062
|
-
function relativePath(file) {
|
|
7063
|
-
return
|
|
9151
|
+
function relativePath(file, cwd = process.cwd(), windows = process.platform === "win32") {
|
|
9152
|
+
return callSitePath(file, cwd, windows) ?? file;
|
|
7064
9153
|
}
|
|
7065
9154
|
function isLibraryFile(file) {
|
|
7066
|
-
return file.startsWith("node:") || file.startsWith("internal/") ||
|
|
9155
|
+
return file.startsWith("node:") || file.startsWith("internal/") || /[\\/]node_modules[\\/]/.test(file) || file === __filename;
|
|
7067
9156
|
}
|
|
7068
|
-
function userSite(stack) {
|
|
9157
|
+
function userSite(stack, cwd = process.cwd(), windows = process.platform === "win32") {
|
|
7069
9158
|
for (const line of (stack ?? "").split("\n").slice(1)) {
|
|
7070
9159
|
const match = /^\s*at\s+(?:.*?\s+\()?(.+?):(\d+):\d+\)?$/.exec(line);
|
|
7071
9160
|
if (!match) continue;
|
|
7072
|
-
const file = match[1]
|
|
7073
|
-
if (isLibraryFile(file) || !/\.[a-z]+$/i.test(file)) continue;
|
|
7074
|
-
return `${relativePath(file)}:${match[2]}`;
|
|
9161
|
+
const file = framePath(match[1], windows);
|
|
9162
|
+
if (!file || isLibraryFile(file) || !/\.[a-z]+$/i.test(file)) continue;
|
|
9163
|
+
return `${relativePath(file, cwd, windows)}:${match[2]}`;
|
|
7075
9164
|
}
|
|
7076
9165
|
return null;
|
|
7077
9166
|
}
|
|
@@ -7111,7 +9200,7 @@ function playwrightTestInfoSource() {
|
|
|
7111
9200
|
try {
|
|
7112
9201
|
const packageJson = resolvePlaywrightPackageJson();
|
|
7113
9202
|
if (!packageJson) return () => null;
|
|
7114
|
-
const globals = (0, import_node_module2.createRequire)(packageJson)(
|
|
9203
|
+
const globals = (0, import_node_module2.createRequire)(packageJson)(path14.join(path14.dirname(packageJson), "lib", "globals.js"));
|
|
7115
9204
|
const read = globals.currentTestInfo;
|
|
7116
9205
|
if (typeof read !== "function") return () => null;
|
|
7117
9206
|
return () => {
|
|
@@ -7154,7 +9243,7 @@ function deepUserSite() {
|
|
|
7154
9243
|
}
|
|
7155
9244
|
var SHELLS = /^(?:sh|bash|dash|zsh|cmd|cmd\.exe|powershell|powershell\.exe|pwsh)$/i;
|
|
7156
9245
|
function commandOf(spawnfile, spawnargs) {
|
|
7157
|
-
const name = (file) =>
|
|
9246
|
+
const name = (file) => path14.basename(file.replace(/\\/g, "/").replace(/^["']|["']$/g, ""));
|
|
7158
9247
|
const program = typeof spawnfile === "string" ? name(spawnfile) : "";
|
|
7159
9248
|
const args = Array.isArray(spawnargs) ? spawnargs.map(String) : [];
|
|
7160
9249
|
const script = args.findIndex((arg) => arg === "-c" || /^\/c$/i.test(arg));
|
|
@@ -7297,17 +9386,17 @@ var ResourceLedger = class {
|
|
|
7297
9386
|
onServer(server, site) {
|
|
7298
9387
|
const known = this.byHandle.get(server);
|
|
7299
9388
|
if (known && this.handles.has(known)) return;
|
|
7300
|
-
let
|
|
9389
|
+
let bound2 = false;
|
|
7301
9390
|
let closed = false;
|
|
7302
9391
|
const entry = this.trackHandle(server, {
|
|
7303
9392
|
type: "TCPServerWrap",
|
|
7304
9393
|
site,
|
|
7305
9394
|
// `close()` unbinds at once, while its 'close' event waits for open connections.
|
|
7306
9395
|
running: () => server.listening,
|
|
7307
|
-
pending: () => !
|
|
9396
|
+
pending: () => !bound2 && !closed && !server.listening
|
|
7308
9397
|
});
|
|
7309
9398
|
server.once("listening", () => {
|
|
7310
|
-
|
|
9399
|
+
bound2 = true;
|
|
7311
9400
|
});
|
|
7312
9401
|
server.once("close", () => {
|
|
7313
9402
|
closed = true;
|
|
@@ -7361,22 +9450,22 @@ var ResourceLedger = class {
|
|
|
7361
9450
|
*/
|
|
7362
9451
|
async testEnded(info) {
|
|
7363
9452
|
const test = testRef2(info) ?? this.window;
|
|
7364
|
-
const
|
|
7365
|
-
const leaks = test && !
|
|
9453
|
+
const failed2 = typeof info.status === "string" && typeof info.expectedStatus === "string" && info.status !== info.expectedStatus;
|
|
9454
|
+
const leaks = test && !failed2 ? this.leaksOf(test.id) : [];
|
|
7366
9455
|
if (this.leakCheck === "close" && leaks.length > 0) await this.closeLeaks(leaks);
|
|
7367
|
-
const ref = test ? { ...test, title: typeof info.title === "string" ? info.title : "", ...
|
|
9456
|
+
const ref = test ? { ...test, title: typeof info.title === "string" ? info.title : "", ...failed2 ? { failed: true } : {} } : null;
|
|
7368
9457
|
const metrics = this.readers.metrics?.end(this.sawBrowser) ?? null;
|
|
7369
9458
|
const handles = {
|
|
7370
9459
|
start: this.handlesAtStart,
|
|
7371
9460
|
end: await settledHandles(this.handlesAtStart, this.countedHandleTypes())
|
|
7372
9461
|
};
|
|
7373
|
-
const
|
|
9462
|
+
const running2 = this.trackingHandles && test ? await this.runningOf(test.id) : null;
|
|
7374
9463
|
const census = this.census(ref, handles, await this.readPages());
|
|
7375
9464
|
if (metrics) census.metrics = metrics;
|
|
7376
|
-
if (
|
|
9465
|
+
if (running2) census.running = running2;
|
|
7377
9466
|
if (test) {
|
|
7378
9467
|
census.openAtStart = this.openAtStart;
|
|
7379
|
-
if (!
|
|
9468
|
+
if (!failed2) census.leftOpen = leaks.length;
|
|
7380
9469
|
}
|
|
7381
9470
|
this.window = null;
|
|
7382
9471
|
return { census, leaks };
|
|
@@ -7426,10 +9515,10 @@ var ResourceLedger = class {
|
|
|
7426
9515
|
for (const method of ["launch", "connect", "connectOverCDP"]) {
|
|
7427
9516
|
const original = type[method];
|
|
7428
9517
|
if (typeof original !== "function") continue;
|
|
7429
|
-
const
|
|
9518
|
+
const bound2 = original.bind(type);
|
|
7430
9519
|
type[method] = async (...args) => {
|
|
7431
9520
|
const site = userSite(new Error().stack);
|
|
7432
|
-
const browser = await
|
|
9521
|
+
const browser = await bound2(...args);
|
|
7433
9522
|
this.guard(() => this.onBrowser(browser, site));
|
|
7434
9523
|
return browser;
|
|
7435
9524
|
};
|
|
@@ -7506,12 +9595,12 @@ var ResourceLedger = class {
|
|
|
7506
9595
|
for (const method of USE_METHODS) {
|
|
7507
9596
|
const original = methods[method];
|
|
7508
9597
|
if (typeof original !== "function") continue;
|
|
7509
|
-
const
|
|
9598
|
+
const bound2 = original.bind(page);
|
|
7510
9599
|
methods[method] = (...args) => {
|
|
7511
9600
|
const previous = this.usingPage;
|
|
7512
9601
|
this.usingPage = entry;
|
|
7513
9602
|
try {
|
|
7514
|
-
return
|
|
9603
|
+
return bound2(...args);
|
|
7515
9604
|
} finally {
|
|
7516
9605
|
this.usingPage = previous;
|
|
7517
9606
|
}
|
|
@@ -7780,18 +9869,18 @@ function readPageInteraction() {
|
|
|
7780
9869
|
return { path: pathOf(node), offset: textOffset(node, node.childNodes[offset] ?? null, 0) };
|
|
7781
9870
|
};
|
|
7782
9871
|
const out = {};
|
|
7783
|
-
let
|
|
7784
|
-
while (
|
|
7785
|
-
if (
|
|
9872
|
+
let active2 = doc.activeElement;
|
|
9873
|
+
while (active2?.shadowRoot?.activeElement) active2 = active2.shadowRoot.activeElement;
|
|
9874
|
+
if (active2 && active2 !== doc.body && active2 !== doc.documentElement) {
|
|
7786
9875
|
let visible = false;
|
|
7787
9876
|
try {
|
|
7788
|
-
visible =
|
|
9877
|
+
visible = active2.matches(":focus-visible");
|
|
7789
9878
|
} catch {
|
|
7790
9879
|
}
|
|
7791
|
-
out.focus = { element: ref(
|
|
9880
|
+
out.focus = { element: ref(active2), visible };
|
|
7792
9881
|
try {
|
|
7793
|
-
if (typeof
|
|
7794
|
-
out.focus.selection = [
|
|
9882
|
+
if (typeof active2.selectionStart === "number" && typeof active2.selectionEnd === "number") {
|
|
9883
|
+
out.focus.selection = [active2.selectionStart, active2.selectionEnd];
|
|
7795
9884
|
}
|
|
7796
9885
|
} catch {
|
|
7797
9886
|
}
|
|
@@ -7806,7 +9895,7 @@ function readPageInteraction() {
|
|
|
7806
9895
|
scope = innermost.shadowRoot;
|
|
7807
9896
|
}
|
|
7808
9897
|
const selection = doc.getSelection?.();
|
|
7809
|
-
if (selection && selection.rangeCount > 0 && !selection.isCollapsed && !(
|
|
9898
|
+
if (selection && selection.rangeCount > 0 && !selection.isCollapsed && !(active2 && isField(active2))) {
|
|
7810
9899
|
const range = selection.getRangeAt(0);
|
|
7811
9900
|
const start = range.startContainer.nodeType === 3 ? range.startContainer.parentElement : range.startContainer;
|
|
7812
9901
|
const end = range.endContainer.nodeType === 3 ? range.endContainer.parentElement : range.endContainer;
|
|
@@ -7868,7 +9957,7 @@ function observeApiRequestContext(context, recorder, baseURL = null) {
|
|
|
7868
9957
|
return proxy;
|
|
7869
9958
|
}
|
|
7870
9959
|
function sendingMethod(target, prop, recorder, baseURL) {
|
|
7871
|
-
return function
|
|
9960
|
+
return function send2(...args) {
|
|
7872
9961
|
const record = recorder();
|
|
7873
9962
|
if (!record) return callMethod(target, prop, args);
|
|
7874
9963
|
const startedAt = Date.now();
|
|
@@ -8033,6 +10122,10 @@ function createSink() {
|
|
|
8033
10122
|
frozenAt: null,
|
|
8034
10123
|
stopEnds: 0,
|
|
8035
10124
|
ending: false,
|
|
10125
|
+
functionEnded: false,
|
|
10126
|
+
ended: false,
|
|
10127
|
+
end: new AbortController(),
|
|
10128
|
+
skip: void 0,
|
|
8036
10129
|
released: [],
|
|
8037
10130
|
releasedRequests: /* @__PURE__ */ new Set(),
|
|
8038
10131
|
onReleasedRequestsEnded: null,
|
|
@@ -8080,7 +10173,7 @@ function configuredCodeReachRoots() {
|
|
|
8080
10173
|
}
|
|
8081
10174
|
} catch {
|
|
8082
10175
|
}
|
|
8083
|
-
return raw.split(
|
|
10176
|
+
return raw.split(path15.delimiter).filter(Boolean);
|
|
8084
10177
|
}
|
|
8085
10178
|
async function stopSinkCodeReach(sink, closing) {
|
|
8086
10179
|
const stopping = [];
|
|
@@ -8431,14 +10524,14 @@ async function maybeOpenPicker(sink, closing) {
|
|
|
8431
10524
|
const inspectGate = shouldInspectOnFailure(inspectionGateFromTestInfo(testInfo, process.env.PIWI_INSPECT_ON_FAIL));
|
|
8432
10525
|
if (!pickGate && !inspectGate) return;
|
|
8433
10526
|
sink.pickOffered = true;
|
|
8434
|
-
const
|
|
8435
|
-
if (!
|
|
10527
|
+
const failed2 = sink.failedLocators[sink.failedLocators.length - 1] ?? deriveFailedLocator(testInfo);
|
|
10528
|
+
if (!failed2 && !inspectGate) {
|
|
8436
10529
|
console.log("[piwi] locator picker: no failing locator could be identified in this failure \u2014 nothing to replace.");
|
|
8437
10530
|
return;
|
|
8438
10531
|
}
|
|
8439
10532
|
const pick = await internalCall(
|
|
8440
10533
|
page,
|
|
8441
|
-
() => runLocatorPicker(page, testInfo,
|
|
10534
|
+
() => runLocatorPicker(page, testInfo, failed2, { fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG })
|
|
8442
10535
|
);
|
|
8443
10536
|
if (!pick) return;
|
|
8444
10537
|
sink.userPick = pick;
|
|
@@ -8573,7 +10666,7 @@ function knowLocatorCall(sink, page, target, location, origin) {
|
|
|
8573
10666
|
let settle = () => {
|
|
8574
10667
|
};
|
|
8575
10668
|
const settled = new Promise((resolve5) => settle = resolve5);
|
|
8576
|
-
const call = { page, target, location, origin, picked: false, settled, settle };
|
|
10669
|
+
const call = { page, target, location, origin, picked: false, endable: false, settled, settle };
|
|
8577
10670
|
sink.locatorCalls.set(callSiteLine(location), call);
|
|
8578
10671
|
return call;
|
|
8579
10672
|
}
|
|
@@ -8640,6 +10733,10 @@ function onDebuggerStop(context) {
|
|
|
8640
10733
|
return;
|
|
8641
10734
|
}
|
|
8642
10735
|
if (pagePause) stopShown(state);
|
|
10736
|
+
if (sink.ended) {
|
|
10737
|
+
how = goOn(state, "end");
|
|
10738
|
+
return;
|
|
10739
|
+
}
|
|
8643
10740
|
if (sink.ending || CLOSING_CONTEXTS.has(context)) {
|
|
8644
10741
|
askAgain(state, asked);
|
|
8645
10742
|
how = goOn(state, sink.ending ? endedWaiting(sink, call) : void 0);
|
|
@@ -8648,6 +10745,7 @@ function onDebuggerStop(context) {
|
|
|
8648
10745
|
const of = stopPageOf(context, call, pagePause?.page);
|
|
8649
10746
|
const ends = sink.stopEnds;
|
|
8650
10747
|
const shown2 = stopTurn.then(async () => {
|
|
10748
|
+
if (sink.ended) return "end";
|
|
8651
10749
|
const ended = currentSink !== sink || sink.ending || sink.stopEnds !== ends;
|
|
8652
10750
|
const gone = decided.show === "breakpoint" && !atBreakpoint(state, callSite);
|
|
8653
10751
|
if (!ended && !gone && stopCanShow(context, of)) {
|
|
@@ -8675,7 +10773,7 @@ function endedWaiting(sink, call) {
|
|
|
8675
10773
|
return "finish";
|
|
8676
10774
|
}
|
|
8677
10775
|
function stopSite(sink, location) {
|
|
8678
|
-
if (
|
|
10776
|
+
if (path15.dirname(path15.resolve(location.file)) !== __dirname) return stopCallSite(location);
|
|
8679
10777
|
return inFlightCallSite([...sink.locatorCalls.values()].map((call) => call.location));
|
|
8680
10778
|
}
|
|
8681
10779
|
function stopBarPage(sink, context, of) {
|
|
@@ -8694,7 +10792,7 @@ async function showStop(sink, context, title, location, reason, call, of, failur
|
|
|
8694
10792
|
const place = runChannelOpen() ? callSitePlace(location) : null;
|
|
8695
10793
|
if (!bar && !place) return void 0;
|
|
8696
10794
|
const failing = failure?.failed ?? null;
|
|
8697
|
-
const
|
|
10795
|
+
const pickTaken = async (picked) => {
|
|
8698
10796
|
if (call) {
|
|
8699
10797
|
call.picked = true;
|
|
8700
10798
|
const { method, args } = call.origin;
|
|
@@ -8713,17 +10811,50 @@ async function showStop(sink, context, title, location, reason, call, of, failur
|
|
|
8713
10811
|
applyPickToSnapshots(sink.capturedLocators, pick);
|
|
8714
10812
|
}
|
|
8715
10813
|
console.log(`[piwi] Locator picked at ${callSiteLine(location)}: ${picked.picked.locator}`);
|
|
8716
|
-
|
|
10814
|
+
return postPickToEditor(editorSendAddress(), pickPayload(picked.picked.locator, location), fetch, runHost());
|
|
8717
10815
|
};
|
|
10816
|
+
const onPick = async (picked) => void await pickTaken(picked);
|
|
10817
|
+
const probe = { fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG };
|
|
8718
10818
|
const handoffReason = reason === "page-pause" ? "pause" : "breakpoint";
|
|
8719
|
-
const onRecord =
|
|
8720
|
-
const intents =
|
|
10819
|
+
const onRecord = page ? recorderAt(page, testInfo, location, handoffReason) : void 0;
|
|
10820
|
+
const intents = page ? new BarIntents() : null;
|
|
8721
10821
|
const record = { title, location, reason, startedAt: Date.now(), duration: 0 };
|
|
8722
10822
|
sink.stops.push(record);
|
|
10823
|
+
const stoppedAt = callSitePlace(location);
|
|
10824
|
+
const goneOn = liveViewStopped({
|
|
10825
|
+
reason,
|
|
10826
|
+
call: { title, file: stoppedAt?.file ?? null, line: stoppedAt?.line ?? null },
|
|
10827
|
+
test: testInfo.title
|
|
10828
|
+
});
|
|
8723
10829
|
const end = new AbortController();
|
|
8724
10830
|
const over = new AbortController();
|
|
8725
10831
|
end.signal.addEventListener("abort", () => over.abort(), { once: true });
|
|
8726
10832
|
let fromEditor;
|
|
10833
|
+
const pickAt = page && !guessed ? async (x, y) => {
|
|
10834
|
+
try {
|
|
10835
|
+
const found = await internalCall(page, () => pickAtPoint(page, probe, x, y));
|
|
10836
|
+
if ("refusal" in found) return { ok: false, message: found.refusal, locator: null };
|
|
10837
|
+
if (over.signal.aborted) return { ok: false, message: "The test went on.", locator: null };
|
|
10838
|
+
const locator = found.picked.picked.locator;
|
|
10839
|
+
const sent2 = await pickTaken(found.picked);
|
|
10840
|
+
return {
|
|
10841
|
+
ok: true,
|
|
10842
|
+
message: sent2 ? `${locator} went to the editor, in place of the locator on the stopped line.` : `${locator}: no editor took it, as this run names no Send to editor pairing that answers.`,
|
|
10843
|
+
locator
|
|
10844
|
+
};
|
|
10845
|
+
} catch (error) {
|
|
10846
|
+
const message = hideSecretsInText(`The stopped page could not be read: ${errorMessage(error)}`);
|
|
10847
|
+
return { ok: false, message, locator: null };
|
|
10848
|
+
}
|
|
10849
|
+
} : void 0;
|
|
10850
|
+
const use = testInfo.project?.use ?? {};
|
|
10851
|
+
const testIdAttribute = typeof use.testIdAttribute === "string" ? use.testIdAttribute : null;
|
|
10852
|
+
const agentProbe = { fn: probeElementAttrs, arg: { ...CAPTURED_ATTRS_ARG, testIdAttribute } };
|
|
10853
|
+
const agent = page && !guessed ? {
|
|
10854
|
+
snapshot: (depth, image) => internalCall(page, () => readSnapshot(page, depth, image)),
|
|
10855
|
+
refLocator: (ref) => internalCall(page, () => refLocators(page, agentProbe, ref, testIdAttribute)),
|
|
10856
|
+
act: (act) => internalCall(page, () => actOnRef(page, agentProbe, act, testIdAttribute))
|
|
10857
|
+
} : void 0;
|
|
8727
10858
|
const held2 = place ? holdStop(
|
|
8728
10859
|
{
|
|
8729
10860
|
reason,
|
|
@@ -8738,20 +10869,31 @@ async function showStop(sink, context, title, location, reason, call, of, failur
|
|
|
8738
10869
|
guessed,
|
|
8739
10870
|
// A failure's stop is held in the teardown it ends with.
|
|
8740
10871
|
holds: () => !over.signal.aborted && (!!failure || !sink.ending && stopCanShow(context, of))
|
|
8741
|
-
}) : void 0
|
|
10872
|
+
}) : void 0,
|
|
10873
|
+
...pickAt ? { pickAt } : {},
|
|
10874
|
+
...agent ? { agent } : {},
|
|
10875
|
+
browser: context.browser()
|
|
8742
10876
|
},
|
|
8743
10877
|
(answer2) => {
|
|
8744
10878
|
fromEditor = answer2;
|
|
8745
10879
|
over.abort();
|
|
8746
10880
|
}
|
|
8747
10881
|
) : null;
|
|
8748
|
-
const onIntent =
|
|
10882
|
+
const onIntent = page && held2 ? (intent, ended) => intent === "assert" ? checkAt(page, testInfo, location, handoffReason, held2.stop, ended) : regionAt(page, location, held2.stop, ended) : void 0;
|
|
8749
10883
|
const shown2 = withTestTimeoutHeld(testInfo, async () => {
|
|
8750
10884
|
if (!bar || !page) {
|
|
8751
10885
|
console.log(
|
|
8752
|
-
failure ? `[piwi] The test failed at ${callSiteLine(location)}: Resume in
|
|
10886
|
+
failure ? `[piwi] The test failed at ${callSiteLine(location)}: Resume in ${runHost()} lets it end as it failed.` : `[piwi] Paused at ${callSiteLine(location)} before ${title}: Resume, Step or Finish in ${runHost()}.`
|
|
8753
10887
|
);
|
|
8754
|
-
|
|
10888
|
+
if (page && intents && held2) {
|
|
10889
|
+
const stop2 = held2.stop;
|
|
10890
|
+
await internalCall(
|
|
10891
|
+
page,
|
|
10892
|
+
() => viewHere(page, probe, onPick, onRecord, over.signal, intents, onIntent, () => viewEnded(stop2))
|
|
10893
|
+
);
|
|
10894
|
+
} else {
|
|
10895
|
+
await abortion(over.signal);
|
|
10896
|
+
}
|
|
8755
10897
|
return "resume";
|
|
8756
10898
|
}
|
|
8757
10899
|
return internalCall(
|
|
@@ -8763,9 +10905,9 @@ async function showStop(sink, context, title, location, reason, call, of, failur
|
|
|
8763
10905
|
call: title,
|
|
8764
10906
|
attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
|
|
8765
10907
|
target: call?.page === page ? call.target : null,
|
|
8766
|
-
...failure ? { failure: true } : {}
|
|
10908
|
+
...failure ? { failure: true } : sink.functionEnded ? {} : { end: true }
|
|
8767
10909
|
},
|
|
8768
|
-
|
|
10910
|
+
probe,
|
|
8769
10911
|
onPick,
|
|
8770
10912
|
onRecord,
|
|
8771
10913
|
over.signal,
|
|
@@ -8777,17 +10919,65 @@ async function showStop(sink, context, title, location, reason, call, of, failur
|
|
|
8777
10919
|
if (!end.signal.aborted) record.duration = Date.now() - record.startedAt;
|
|
8778
10920
|
else if (end.signal.reason === "ended") releaseCall(sink, call?.settled ?? Promise.resolve());
|
|
8779
10921
|
sink.shownStops.delete(end);
|
|
10922
|
+
goneOn();
|
|
8780
10923
|
await held2?.release();
|
|
8781
10924
|
});
|
|
8782
10925
|
const heldAt = testTimeSlot(testInfo)?.elapsed ?? null;
|
|
8783
10926
|
const stop = { page, guessed, context, record, done: shown2.catch(() => void 0), heldAt, after: null };
|
|
8784
10927
|
if (!failure) sink.shownStops.set(end, stop);
|
|
8785
10928
|
const answer = await shown2;
|
|
8786
|
-
if (!end.signal.aborted)
|
|
10929
|
+
if (!end.signal.aborted) {
|
|
10930
|
+
const chosen = fromEditor ?? answer;
|
|
10931
|
+
return chosen === "end" ? endTest(sink, call, title, location, reason) : chosen;
|
|
10932
|
+
}
|
|
8787
10933
|
if (end.signal.reason === "ended") return "finish";
|
|
8788
10934
|
await settledWithin(stop.after ? [stop.after] : [], END_STOP_MS);
|
|
8789
10935
|
return "resume";
|
|
8790
10936
|
}
|
|
10937
|
+
function endTest(sink, call, title, location, reason) {
|
|
10938
|
+
const line = callSiteLine(location);
|
|
10939
|
+
if (reason === "failure" || sink.functionEnded || !sink.testInfo) {
|
|
10940
|
+
const said2 = reason === "failure" ? `The test failed at ${line}: End the test lets it end as it failed.` : `The test function already ended: End the test at ${line} lets it end as it is.`;
|
|
10941
|
+
console.log(`[piwi] ${said2}`);
|
|
10942
|
+
if (reason !== "failure") tellHub({ type: "notice", message: said2 });
|
|
10943
|
+
return "finish";
|
|
10944
|
+
}
|
|
10945
|
+
sink.ended = true;
|
|
10946
|
+
skipAtFunctionEnd(sink);
|
|
10947
|
+
sink.end.abort(new Error(ENDED_REASON));
|
|
10948
|
+
if (reason === "page-pause" || call?.endable) {
|
|
10949
|
+
const what = reason === "page-pause" ? "nothing after its page.pause() runs" : `${title} never runs`;
|
|
10950
|
+
console.log(`[piwi] Ended the test at ${line}: ${what}, and the test is skipped.`);
|
|
10951
|
+
return "end";
|
|
10952
|
+
}
|
|
10953
|
+
const said = hideSecretsInText(
|
|
10954
|
+
`${title} at ${line} runs before the test ends: Piwi holds no locator call there. The test is skipped at its next locator call, or as it ends.`
|
|
10955
|
+
);
|
|
10956
|
+
console.log(`[piwi] ${said}`);
|
|
10957
|
+
tellHub({ type: "notice", message: said });
|
|
10958
|
+
return "end";
|
|
10959
|
+
}
|
|
10960
|
+
function endsNow(sink) {
|
|
10961
|
+
return sink.ended && !sink.functionEnded;
|
|
10962
|
+
}
|
|
10963
|
+
function skipOf(sink) {
|
|
10964
|
+
if (sink.skip === void 0) {
|
|
10965
|
+
try {
|
|
10966
|
+
sink.testInfo?.skip(true, ENDED_REASON);
|
|
10967
|
+
sink.skip = new Error(`Test is skipped: ${ENDED_REASON}`);
|
|
10968
|
+
} catch (error) {
|
|
10969
|
+
sink.skip = error;
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10972
|
+
return sink.skip;
|
|
10973
|
+
}
|
|
10974
|
+
function skipAtFunctionEnd(sink) {
|
|
10975
|
+
const callbacks = sink.testInfo?._onDidFinishTestFunctionCallbacks;
|
|
10976
|
+
if (!(callbacks instanceof Set)) return;
|
|
10977
|
+
callbacks.add(() => {
|
|
10978
|
+
if (sink.testInfo?.status === "passed") throw skipOf(sink);
|
|
10979
|
+
});
|
|
10980
|
+
}
|
|
8791
10981
|
function intentAt(intents, what, stop) {
|
|
8792
10982
|
if (!stop.holds()) return { ok: false, message: "The test went on." };
|
|
8793
10983
|
if (stop.guessed) {
|
|
@@ -8879,7 +11069,7 @@ async function holdFailure(sink, fallback) {
|
|
|
8879
11069
|
const context = page ? pageContext(page) : null;
|
|
8880
11070
|
if (!page || !context) return;
|
|
8881
11071
|
sink.failureHeld = true;
|
|
8882
|
-
const
|
|
11072
|
+
const failed2 = sink.failedLocators[sink.failedLocators.length - 1] ?? deriveFailedLocator(testInfo);
|
|
8883
11073
|
const errors = testInfo.errors.length ? testInfo.errors : testInfo.error ? [testInfo.error] : [];
|
|
8884
11074
|
const declared = (place2) => place2.file === testInfo.file && place2.line === testInfo.line;
|
|
8885
11075
|
const placed = errors.flatMap((error2) => {
|
|
@@ -8887,8 +11077,8 @@ async function holdFailure(sink, fallback) {
|
|
|
8887
11077
|
return place2 && !declared(place2) ? [{ error: error2, place: place2 }] : [];
|
|
8888
11078
|
})[0];
|
|
8889
11079
|
const error = placed?.error ?? errors[0];
|
|
8890
|
-
const place = placed?.place ?? (
|
|
8891
|
-
const location = (place ? stopCallSite(place) : null) ??
|
|
11080
|
+
const place = placed?.place ?? (failed2?.location ? null : stepInProgress(testInfo));
|
|
11081
|
+
const location = (place ? stopCallSite(place) : null) ?? failed2?.location ?? stopCallSite({ file: testInfo.file, line: testInfo.line, column: testInfo.column });
|
|
8892
11082
|
if (!location) return;
|
|
8893
11083
|
const said = stripAnsi(error?.message ?? "").split("\n").find((line) => line.trim()) ?? status;
|
|
8894
11084
|
if (page === sink.lastActivePage)
|
|
@@ -8896,9 +11086,9 @@ async function holdFailure(sink, fallback) {
|
|
|
8896
11086
|
sink.frozenAt = Date.now();
|
|
8897
11087
|
await stopSinkCodeReach(sink, { context });
|
|
8898
11088
|
const title = hideSecretsInText(`Failed: ${said.trim().slice(0, 300)}`).slice(0, 1e3);
|
|
8899
|
-
const
|
|
8900
|
-
const named =
|
|
8901
|
-
await showStop(sink, context, title, location, "failure", void 0, { page }, { failed, error: named });
|
|
11089
|
+
const errorText2 = failureError(error?.message ?? error?.value);
|
|
11090
|
+
const named = errorText2 === null ? null : hideSecretsInText(errorText2).slice(0, MAX_STOP_ERROR);
|
|
11091
|
+
await showStop(sink, context, title, location, "failure", void 0, { page }, { failed: failed2, error: named });
|
|
8902
11092
|
}
|
|
8903
11093
|
var FAILURE_ERROR_LINES = 4;
|
|
8904
11094
|
function failureError(message) {
|
|
@@ -8911,22 +11101,22 @@ function failureError(message) {
|
|
|
8911
11101
|
const text = lines.join("\n").slice(0, MAX_STOP_ERROR);
|
|
8912
11102
|
return text.trim() ? text : null;
|
|
8913
11103
|
}
|
|
8914
|
-
function errorPlace(error) {
|
|
11104
|
+
function errorPlace(error, windows = process.platform === "win32") {
|
|
8915
11105
|
const given = error?.location;
|
|
8916
11106
|
if (typeof given?.file === "string" && typeof given.line === "number") {
|
|
8917
11107
|
return { file: given.file, line: given.line, column: given.column ?? 0 };
|
|
8918
11108
|
}
|
|
8919
11109
|
const stack = error?.stack;
|
|
8920
11110
|
for (const line of typeof stack === "string" ? stack.split("\n") : []) {
|
|
8921
|
-
const frame = /^\s*at\s+(?:.*?\s+\()?(
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
if (/[\\/]node_modules[\\/]/.test(file) ||
|
|
11111
|
+
const frame = /^\s*at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?$/.exec(line);
|
|
11112
|
+
const file = frame ? framePath(frame[1], windows) : null;
|
|
11113
|
+
if (!frame || !file || !(windows ? path15.win32 : path15.posix).isAbsolute(file)) continue;
|
|
11114
|
+
if (/[\\/]node_modules[\\/]/.test(file) || path15.dirname(path15.resolve(file)) === __dirname) continue;
|
|
8925
11115
|
return { file, line: Number(frame[2]), column: Number(frame[3]) };
|
|
8926
11116
|
}
|
|
8927
11117
|
return null;
|
|
8928
11118
|
}
|
|
8929
|
-
function
|
|
11119
|
+
function callInProgress(testInfo) {
|
|
8930
11120
|
let found = null;
|
|
8931
11121
|
const visit = (steps) => {
|
|
8932
11122
|
if (!Array.isArray(steps)) return;
|
|
@@ -8935,7 +11125,10 @@ function stepInProgress(testInfo) {
|
|
|
8935
11125
|
const at = step.stack?.[0];
|
|
8936
11126
|
const called = step.category === "pw:api" || step.category === "expect" || step.category === "test.step";
|
|
8937
11127
|
if (called && typeof at?.file === "string" && typeof at.line === "number") {
|
|
8938
|
-
found = {
|
|
11128
|
+
found = {
|
|
11129
|
+
title: typeof step.title === "string" ? step.title : null,
|
|
11130
|
+
place: { file: at.file, line: at.line, column: typeof at.column === "number" ? at.column : 0 }
|
|
11131
|
+
};
|
|
8939
11132
|
}
|
|
8940
11133
|
visit(step.steps);
|
|
8941
11134
|
}
|
|
@@ -8943,6 +11136,15 @@ function stepInProgress(testInfo) {
|
|
|
8943
11136
|
visit(testInfo._steps);
|
|
8944
11137
|
return found;
|
|
8945
11138
|
}
|
|
11139
|
+
function stepInProgress(testInfo) {
|
|
11140
|
+
return callInProgress(testInfo)?.place ?? null;
|
|
11141
|
+
}
|
|
11142
|
+
function runningCall(testInfo) {
|
|
11143
|
+
const call = callInProgress(testInfo);
|
|
11144
|
+
if (!call?.title) return null;
|
|
11145
|
+
const file = path15.relative(process.cwd(), call.place.file).split(path15.sep).join("/");
|
|
11146
|
+
return { title: call.title, file, line: call.place.line };
|
|
11147
|
+
}
|
|
8946
11148
|
function abortion(signal) {
|
|
8947
11149
|
return new Promise((resolve5) => {
|
|
8948
11150
|
if (signal.aborted) resolve5();
|
|
@@ -9008,7 +11210,10 @@ function endStopsWithTestFunction(testInfo, sink) {
|
|
|
9008
11210
|
if (!(callbacks instanceof Set)) return;
|
|
9009
11211
|
const theirs = [...callbacks];
|
|
9010
11212
|
callbacks.clear();
|
|
9011
|
-
callbacks.add(() =>
|
|
11213
|
+
callbacks.add(() => {
|
|
11214
|
+
sink.functionEnded = true;
|
|
11215
|
+
return endShownStops(sink, "ended");
|
|
11216
|
+
});
|
|
9012
11217
|
for (const callback of theirs) callbacks.add(callback);
|
|
9013
11218
|
}
|
|
9014
11219
|
function recorderAt(page, testInfo, location, reason) {
|
|
@@ -9042,6 +11247,7 @@ async function pauseInPlace(page, playwrightPause, location) {
|
|
|
9042
11247
|
} finally {
|
|
9043
11248
|
PAGE_PAUSES.delete(context);
|
|
9044
11249
|
}
|
|
11250
|
+
if (endsNow(sink)) throw skipOf(sink);
|
|
9045
11251
|
}
|
|
9046
11252
|
async function leaveToPlaywright(context, playwrightPause) {
|
|
9047
11253
|
if (context) PLAYWRIGHT_PAUSES.set(context, (PLAYWRIGHT_PAUSES.get(context) ?? 0) + 1);
|
|
@@ -9091,14 +11297,20 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
9091
11297
|
if (prop === EXPECT_METHOD) {
|
|
9092
11298
|
return async (...callArgs) => {
|
|
9093
11299
|
const sink = currentSink;
|
|
11300
|
+
if (sink && endsNow(sink)) throw skipOf(sink);
|
|
9094
11301
|
const expression = typeof callArgs[0] === "string" ? callArgs[0] : "";
|
|
9095
11302
|
const isNot = Boolean(callArgs[1]?.isNot);
|
|
9096
11303
|
const callerLocation = sink ? captureCallerLocation() : null;
|
|
9097
11304
|
if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
|
|
9098
11305
|
const call = knowLocatorCall(sink, page, target, callerLocation, { method: originMethod, args: originArgs });
|
|
11306
|
+
const ending = sink?.pause && !sink.ended ? withEndSignal(EXPECT_METHOD, callArgs, sink.end.signal) : null;
|
|
11307
|
+
if (call && ending) call.endable = true;
|
|
11308
|
+
const expectArgs = ending ?? callArgs;
|
|
9099
11309
|
try {
|
|
9100
11310
|
if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
|
|
9101
|
-
|
|
11311
|
+
const result2 = await callMethod(target, prop, expectArgs);
|
|
11312
|
+
if (sink && endsNow(sink)) throw skipOf(sink);
|
|
11313
|
+
return result2;
|
|
9102
11314
|
}
|
|
9103
11315
|
sink.lastActivePage = page;
|
|
9104
11316
|
const used = {
|
|
@@ -9112,7 +11324,8 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
9112
11324
|
seq = sink.capturedLocators.length;
|
|
9113
11325
|
sink.capturedLocators.push({ location: callerLocation, used, element: null, alternatives: [] });
|
|
9114
11326
|
}
|
|
9115
|
-
const result = await callMethod(target, prop,
|
|
11327
|
+
const result = await callMethod(target, prop, expectArgs);
|
|
11328
|
+
if (endsNow(sink)) throw skipOf(sink);
|
|
9116
11329
|
const matches = result?.matches;
|
|
9117
11330
|
if (matches === true && !alreadyProbed && !call?.picked) {
|
|
9118
11331
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
@@ -9121,6 +11334,9 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
9121
11334
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
9122
11335
|
}
|
|
9123
11336
|
return result;
|
|
11337
|
+
} catch (error) {
|
|
11338
|
+
if (sink && endsNow(sink)) throw skipOf(sink);
|
|
11339
|
+
throw error;
|
|
9124
11340
|
} finally {
|
|
9125
11341
|
forgetLocatorCall(sink, call);
|
|
9126
11342
|
}
|
|
@@ -9130,6 +11346,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
9130
11346
|
return async (...callArgs) => {
|
|
9131
11347
|
const sink = currentSink;
|
|
9132
11348
|
if (!sink) return callMethod(target, prop, callArgs);
|
|
11349
|
+
if (endsNow(sink)) throw skipOf(sink);
|
|
9133
11350
|
sink.lastActivePage = page;
|
|
9134
11351
|
const callerLocation = captureCallerLocation();
|
|
9135
11352
|
recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, String(prop)));
|
|
@@ -9139,6 +11356,8 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
9139
11356
|
raw: `${originMethod}(${JSON.stringify(originArgs)})`
|
|
9140
11357
|
};
|
|
9141
11358
|
const call = knowLocatorCall(sink, page, target, callerLocation, used);
|
|
11359
|
+
const ending = sink.pause && !sink.ended ? withEndSignal(String(prop), callArgs, sink.end.signal) : null;
|
|
11360
|
+
if (call && ending) call.endable = true;
|
|
9142
11361
|
const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
|
|
9143
11362
|
let seq = -1;
|
|
9144
11363
|
if (!alreadyProbed) {
|
|
@@ -9152,13 +11371,15 @@ function wrapLocator(page, locator, originMethod, originArgs) {
|
|
|
9152
11371
|
}
|
|
9153
11372
|
let result;
|
|
9154
11373
|
try {
|
|
9155
|
-
result = await callMethod(target, prop, callArgs);
|
|
11374
|
+
result = await callMethod(target, prop, ending ?? callArgs);
|
|
9156
11375
|
} catch (error) {
|
|
11376
|
+
if (endsNow(sink)) throw skipOf(sink);
|
|
9157
11377
|
sink.failedLocators.push({ method: originMethod, args: originArgs, location: callerLocation });
|
|
9158
11378
|
throw error;
|
|
9159
11379
|
} finally {
|
|
9160
11380
|
forgetLocatorCall(sink, call);
|
|
9161
11381
|
}
|
|
11382
|
+
if (endsNow(sink)) throw skipOf(sink);
|
|
9162
11383
|
if (!alreadyProbed && !call?.picked) {
|
|
9163
11384
|
if (callerLocation) sink.probedLocations.add(callerLocation);
|
|
9164
11385
|
startElementCapture(sink, page, target, seq, callerLocation, used);
|
|
@@ -9191,6 +11412,7 @@ function instrumentPage(page) {
|
|
|
9191
11412
|
}
|
|
9192
11413
|
void maybeStartCodeReach(page);
|
|
9193
11414
|
observeOwnApiRequests(page);
|
|
11415
|
+
watchPageEvents(page);
|
|
9194
11416
|
const probeSink = currentSink;
|
|
9195
11417
|
if (probeSink?.probeItem && !probeSink.probeInterception) {
|
|
9196
11418
|
void installProbeInterception(page, probeSink.probeItem).then((interception) => {
|
|
@@ -9216,6 +11438,7 @@ function instrumentPage(page) {
|
|
|
9216
11438
|
if (playwrightPause) {
|
|
9217
11439
|
page.pause = () => pauseInPlace(page, playwrightPause, captureCallerLocation());
|
|
9218
11440
|
}
|
|
11441
|
+
if (runChannelOpen() || liveViewOpen()) guardScreencast(page);
|
|
9219
11442
|
if (typeof page.close === "function") {
|
|
9220
11443
|
const originalClose = page.close.bind(page);
|
|
9221
11444
|
page.close = async (...args) => {
|
|
@@ -9319,7 +11542,7 @@ function instrumentPage(page) {
|
|
|
9319
11542
|
sink.releasedRequests.add(request);
|
|
9320
11543
|
sink.releasedQuietSince = Date.now();
|
|
9321
11544
|
});
|
|
9322
|
-
const onRequestEnd = (request,
|
|
11545
|
+
const onRequestEnd = (request, failed2) => {
|
|
9323
11546
|
const sink = currentSink;
|
|
9324
11547
|
if (sink?.releasedRequests.delete(request)) {
|
|
9325
11548
|
sink.releasedQuietSince = Date.now();
|
|
@@ -9329,8 +11552,8 @@ function instrumentPage(page) {
|
|
|
9329
11552
|
sink.lastActivePage = page;
|
|
9330
11553
|
const response = sink.openResponses.get(request) ?? null;
|
|
9331
11554
|
sink.openResponses.delete(request);
|
|
9332
|
-
recordRequest(sink, request,
|
|
9333
|
-
if (!
|
|
11555
|
+
recordRequest(sink, request, failed2 ? "failed" : "finished", response);
|
|
11556
|
+
if (!failed2) void keepResponse(request, response);
|
|
9334
11557
|
};
|
|
9335
11558
|
page.on("requestfinished", (request) => onRequestEnd(request, false));
|
|
9336
11559
|
page.on("requestfailed", (request) => onRequestEnd(request, true));
|
|
@@ -9625,8 +11848,8 @@ async function flushSink(sink, testInfo) {
|
|
|
9625
11848
|
body: snapshotJson
|
|
9626
11849
|
});
|
|
9627
11850
|
}
|
|
9628
|
-
const
|
|
9629
|
-
const suggestion =
|
|
11851
|
+
const failed2 = sink.failedLocators[sink.failedLocators.length - 1];
|
|
11852
|
+
const suggestion = failed2 ? suggestLocatorsFromAria(failed2, snapshot) : null;
|
|
9630
11853
|
if (suggestion) {
|
|
9631
11854
|
testInfo.annotations.push({
|
|
9632
11855
|
type: LOCATOR_SUGGESTION_ANNOTATION,
|
|
@@ -9763,7 +11986,7 @@ var runControl = {
|
|
|
9763
11986
|
}
|
|
9764
11987
|
};
|
|
9765
11988
|
function runFile(file) {
|
|
9766
|
-
return
|
|
11989
|
+
return path15.relative(process.cwd(), file).split(path15.sep).join("/") || file;
|
|
9767
11990
|
}
|
|
9768
11991
|
function testBegin(testInfo, cannotPause) {
|
|
9769
11992
|
const title = testInfo.titlePath.slice(1);
|
|
@@ -9793,17 +12016,21 @@ var piwiFixtures = {
|
|
|
9793
12016
|
// ledger and records the calls the tests make through it, and torn down after
|
|
9794
12017
|
// the browser closed, so its census sees what only the worker's shutdown
|
|
9795
12018
|
// closed. Depends on `playwright` alone, which launches nothing. The worker's
|
|
9796
|
-
//
|
|
9797
|
-
//
|
|
12019
|
+
// connection to the instance's live view (`PIWI_LIVE_VIEW`) and its run
|
|
12020
|
+
// connection to the editor (`PIWI_RUN_CHANNEL`), which says how the first
|
|
12021
|
+
// stands and shares it, open here, before the first test, and close as the
|
|
12022
|
+
// worker ends.
|
|
9798
12023
|
piwiResources: [
|
|
9799
12024
|
async ({ playwright }, use, workerInfo) => {
|
|
9800
12025
|
boxCaptureFrames();
|
|
9801
12026
|
startResourceLedger(playwright, workerInfo.workerIndex);
|
|
9802
12027
|
observeApiRequests(playwright, apiCallRecorder);
|
|
9803
|
-
|
|
12028
|
+
openLiveView(workerInfo);
|
|
12029
|
+
if (await openRunChannel(workerInfo, runControl)) watchCalls(playwright);
|
|
9804
12030
|
try {
|
|
9805
12031
|
await use();
|
|
9806
12032
|
} finally {
|
|
12033
|
+
closeLiveView();
|
|
9807
12034
|
closeRunChannel();
|
|
9808
12035
|
stopResourceLedger();
|
|
9809
12036
|
}
|
|
@@ -9827,6 +12054,7 @@ var piwiFixtures = {
|
|
|
9827
12054
|
await armStops([pageContext(page)]);
|
|
9828
12055
|
await awaitFlakeConditions();
|
|
9829
12056
|
await maybeStartCodeReach(page);
|
|
12057
|
+
liveViewPage(page);
|
|
9830
12058
|
await use(page);
|
|
9831
12059
|
const sink = currentSink;
|
|
9832
12060
|
if (sink) await holdFailure(sink, page);
|
|
@@ -9877,6 +12105,7 @@ var piwiFixtures = {
|
|
|
9877
12105
|
});
|
|
9878
12106
|
activeResourceLedger()?.testStarted(testInfo);
|
|
9879
12107
|
currentSink = sink;
|
|
12108
|
+
liveViewTestBegan(testInfo.title, () => runningCall(testInfo));
|
|
9880
12109
|
const showRunChannel = hideRunChannel();
|
|
9881
12110
|
try {
|
|
9882
12111
|
await armStops(OPEN_CONTEXTS);
|
|
@@ -9886,6 +12115,7 @@ var piwiFixtures = {
|
|
|
9886
12115
|
sink.ending = true;
|
|
9887
12116
|
await endShownStops(sink, "ended");
|
|
9888
12117
|
await awaitReleasedCalls(sink);
|
|
12118
|
+
await liveViewTestEnded();
|
|
9889
12119
|
showRunChannel();
|
|
9890
12120
|
currentSink = null;
|
|
9891
12121
|
if (sink.pause?.pause) pauseNextTest = true;
|
|
@@ -9961,6 +12191,7 @@ function extendPiwiFixtures(test) {
|
|
|
9961
12191
|
codeReachRoots,
|
|
9962
12192
|
computeCoreVitals,
|
|
9963
12193
|
configuredCodeReachRoots,
|
|
12194
|
+
errorPlace,
|
|
9964
12195
|
extendPiwiFixtures,
|
|
9965
12196
|
failureError,
|
|
9966
12197
|
intentAt,
|