@piwitests/reporter 0.63.0 → 0.64.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.
@@ -61,7 +61,7 @@ __export(capture_fixtures_exports, {
61
61
  stopResourceLedger: () => stopResourceLedger
62
62
  });
63
63
  module.exports = __toCommonJS(capture_fixtures_exports);
64
- var path15 = __toESM(require("path"));
64
+ var path17 = __toESM(require("path"));
65
65
  var import_node_zlib2 = require("zlib");
66
66
 
67
67
  // ../picker-dom/src/probe.ts
@@ -520,8 +520,8 @@ function installPickerOverlay(arg) {
520
520
  return root && root !== doc && root.host ? root.host : null;
521
521
  };
522
522
  const targetOf = (e) => {
523
- const path16 = typeof e.composedPath === "function" ? e.composedPath() : null;
524
- const first = path16 && path16.length ? path16[0] : null;
523
+ const path18 = typeof e.composedPath === "function" ? e.composedPath() : null;
524
+ const first = path18 && path18.length ? path18[0] : null;
525
525
  return first && first.nodeType === 1 ? first : e.target;
526
526
  };
527
527
  const buildChain = (raw) => {
@@ -826,14 +826,14 @@ function showAnchorPicker(arg) {
826
826
  } catch {
827
827
  shadowRoots.length = 0;
828
828
  }
829
- const within3 = (node2, scope) => {
829
+ const within4 = (node2, scope) => {
830
830
  if (scope === doc) return true;
831
831
  for (let n = node2; n; n = parentOf(n)) if (n === scope) return true;
832
832
  return false;
833
833
  };
834
834
  const queryAll = (scope, sel) => {
835
835
  const found = Array.from(scope.querySelectorAll(sel));
836
- for (const sr of shadowRoots) if (within3(sr.host, scope)) found.push(...Array.from(sr.querySelectorAll(sel)));
836
+ for (const sr of shadowRoots) if (within4(sr.host, scope)) found.push(...Array.from(sr.querySelectorAll(sel)));
837
837
  return found;
838
838
  };
839
839
  const leafCountable = !!leafTestId || roleOf2(el) === leafRole;
@@ -1222,8 +1222,22 @@ function showPauseBar(arg) {
1222
1222
  cleanup();
1223
1223
  g.__piwiPauseState = choice;
1224
1224
  };
1225
+ const pageTakesEscape = (e) => {
1226
+ const path18 = typeof e.composedPath === "function" ? e.composedPath() : [e.target];
1227
+ const el = path18.find((node) => node && node.nodeType === 1);
1228
+ if (el && !bar.contains(el) && typeof el.closest === "function") {
1229
+ if (el.closest('dialog[open],[role="dialog"],[role="alertdialog"],[role="listbox"],[role="menu"]')) return true;
1230
+ const popup = '[aria-expanded="true"]:is([role="combobox"],[aria-haspopup]:not([aria-haspopup="false"]))';
1231
+ if (el.matches(popup)) return true;
1232
+ }
1233
+ try {
1234
+ return !!doc.querySelector("dialog:modal");
1235
+ } catch {
1236
+ return false;
1237
+ }
1238
+ };
1225
1239
  const onKey = (e) => {
1226
- if (e.key !== "Escape") return;
1240
+ if (e.key !== "Escape" || pageTakesEscape(e)) return;
1227
1241
  e.preventDefault();
1228
1242
  e.stopImmediatePropagation();
1229
1243
  done("resume");
@@ -1240,7 +1254,9 @@ function showPauseBar(arg) {
1240
1254
  ...arg.record ? [
1241
1255
  ["Record here", "record", "Record steps in this page: the editor writes them before this line", false]
1242
1256
  ] : [],
1243
- ...arg.failure ? [] : [["Finish", "finish", "Run on and pause no more in this test", false]],
1257
+ ...arg.failure ? [] : [
1258
+ ["Finish", "finish", "Run the test on without stopping at a breakpoint again in this attempt", false]
1259
+ ],
1244
1260
  ...arg.end && !arg.failure ? [
1245
1261
  ["End the test", "end", "Skip the rest of this test: it is reported as skipped", false]
1246
1262
  ] : []
@@ -1276,6 +1292,102 @@ function takePauseState() {
1276
1292
  return typeof state === "string" ? state : null;
1277
1293
  }
1278
1294
 
1295
+ // ../picker-dom/src/overlay-stop.ts
1296
+ function showStopHighlight(elements, arg) {
1297
+ const g = globalThis;
1298
+ const doc = g.document;
1299
+ const gone = g.__piwiStopHighlightsGone instanceof Set ? g.__piwiStopHighlightsGone : /* @__PURE__ */ new Set();
1300
+ g.__piwiStopHighlightsGone = gone;
1301
+ if (!doc || !doc.documentElement) return 0;
1302
+ const drawn = doc.getElementById(`__piwi_stop_highlight_${arg.id}`);
1303
+ const targets = gone.has(arg.id) ? [] : elements.slice(0, 500);
1304
+ const shownIn = (layer2) => Array.from(layer2.children).filter((box) => box.dataset.box && box.style.display !== "none").length;
1305
+ const raise = (layer2) => {
1306
+ if (!layer2.hasAttribute("popover")) return;
1307
+ try {
1308
+ if (layer2.matches(":popover-open")) layer2.hidePopover();
1309
+ layer2.showPopover();
1310
+ } catch {
1311
+ layer2.removeAttribute("popover");
1312
+ }
1313
+ };
1314
+ const before = drawn?.__piwiTargets;
1315
+ if (drawn && before && before.length === targets.length && before.every((el, i) => el === targets[i])) {
1316
+ if (targets.length) raise(drawn);
1317
+ drawn.__piwiPlace?.();
1318
+ return shownIn(drawn);
1319
+ }
1320
+ drawn?.remove();
1321
+ if (!targets.length) return 0;
1322
+ const Z = 2147483600;
1323
+ const MONO = "ui-monospace,SFMono-Regular,Menlo,Consolas,monospace";
1324
+ const layer = doc.createElement("div");
1325
+ layer.id = `__piwi_stop_highlight_${arg.id}`;
1326
+ layer.setAttribute("data-piwi-stop-highlight", arg.kind);
1327
+ layer.setAttribute("aria-hidden", "true");
1328
+ layer.setAttribute("popover", "manual");
1329
+ layer.style.cssText = `position:fixed;inset:auto;top:0;left:0;width:0;height:0;margin:0;padding:0;border:0;background:transparent;overflow:visible;pointer-events:none;z-index:${Z - 1};`;
1330
+ layer.__piwiTargets = targets;
1331
+ const look = arg.kind === "call" ? "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);" : "border:2px dashed #a78bfa;background:rgba(167,139,250,.15);border-radius:3px;box-shadow:0 0 0 1px rgba(255,255,255,.8);";
1332
+ const boxes = targets.map(() => {
1333
+ const box = doc.createElement("div");
1334
+ box.dataset.box = "1";
1335
+ box.style.cssText = `position:fixed;display:none;box-sizing:border-box;pointer-events:none;${look}`;
1336
+ layer.appendChild(box);
1337
+ return box;
1338
+ });
1339
+ const label = doc.createElement("div");
1340
+ label.style.cssText = `position:fixed;display:none;pointer-events:none;box-sizing:border-box;max-width:min(620px,92vw);background:#0b1120;color:#f9fafb;border:1px solid #7c3aed;border-radius:6px;padding:2px 7px;font:12px/1.45 ${MONO};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 2px 10px rgba(0,0,0,.4);`;
1341
+ label.textContent = arg.label;
1342
+ layer.appendChild(label);
1343
+ doc.documentElement.appendChild(layer);
1344
+ raise(layer);
1345
+ const place = () => {
1346
+ let first = null;
1347
+ targets.forEach((el, i) => {
1348
+ const box = boxes[i];
1349
+ const rect = el && el.isConnected && typeof el.getBoundingClientRect === "function" ? el.getBoundingClientRect() : null;
1350
+ if (!rect || rect.width === 0 && rect.height === 0) {
1351
+ box.style.display = "none";
1352
+ return;
1353
+ }
1354
+ box.style.display = "block";
1355
+ box.style.left = `${rect.left - 2}px`;
1356
+ box.style.top = `${rect.top - 2}px`;
1357
+ box.style.width = `${rect.width + 4}px`;
1358
+ box.style.height = `${rect.height + 4}px`;
1359
+ first ??= rect;
1360
+ });
1361
+ if (first && arg.label) {
1362
+ label.style.display = "block";
1363
+ const height = label.offsetHeight || 20;
1364
+ const above = first.top - 2 - height - 4;
1365
+ label.style.left = `${Math.max(4, first.left - 2)}px`;
1366
+ label.style.top = `${above >= 4 ? above : first.bottom + 6}px`;
1367
+ } else {
1368
+ label.style.display = "none";
1369
+ }
1370
+ };
1371
+ layer.__piwiPlace = place;
1372
+ const follow = () => {
1373
+ if (!layer.isConnected) return;
1374
+ place();
1375
+ g.requestAnimationFrame(follow);
1376
+ };
1377
+ follow();
1378
+ return shownIn(layer);
1379
+ }
1380
+ function removeStopHighlight(id) {
1381
+ const g = globalThis;
1382
+ const gone = g.__piwiStopHighlightsGone instanceof Set ? g.__piwiStopHighlightsGone : /* @__PURE__ */ new Set();
1383
+ g.__piwiStopHighlightsGone = gone;
1384
+ gone.add(id);
1385
+ const layer = g.document?.getElementById(`__piwi_stop_highlight_${id}`);
1386
+ if (!layer) return false;
1387
+ layer.remove();
1388
+ return true;
1389
+ }
1390
+
1279
1391
  // ../core/src/locator-chain.ts
1280
1392
  var LOCATING_METHODS = /* @__PURE__ */ new Set([
1281
1393
  "getByRole",
@@ -2004,21 +2116,21 @@ var MAX_INDETERMINATE = 20;
2004
2116
  var STEP = String.raw`[a-zA-Z][\w.-]*:[1-9]\d*`;
2005
2117
  var LEVEL = `${STEP}(?:/${STEP})*`;
2006
2118
  var ELEMENT_PATH_RE = new RegExp(`^${LEVEL}(?:\\|${LEVEL})*$`);
2007
- function isElementPath(path16) {
2008
- return typeof path16 === "string" && path16.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path16);
2119
+ function isElementPath(path18) {
2120
+ return typeof path18 === "string" && path18.length <= ELEMENT_PATH_MAX_CHARS && ELEMENT_PATH_RE.test(path18);
2009
2121
  }
2010
2122
  var offsetOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e7 ? v : null;
2011
2123
  function elementRef(v) {
2012
2124
  if (!v || typeof v !== "object") return null;
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) : "" };
2125
+ const { path: path18, label } = v;
2126
+ if (!isElementPath(path18)) return null;
2127
+ return { path: path18, label: typeof label === "string" ? label.slice(0, ELEMENT_LABEL_MAX_CHARS) : "" };
2016
2128
  }
2017
2129
  function textPoint(v) {
2018
2130
  if (!v || typeof v !== "object") return null;
2019
- const { path: path16, offset } = v;
2131
+ const { path: path18, offset } = v;
2020
2132
  const at = offsetOf(offset);
2021
- return isElementPath(path16) && at !== null ? { path: path16, offset: at } : null;
2133
+ return isElementPath(path18) && at !== null ? { path: path18, offset: at } : null;
2022
2134
  }
2023
2135
  function sanitizePageInteraction(raw) {
2024
2136
  if (!raw || typeof raw !== "object") return {};
@@ -2847,8 +2959,8 @@ function parseRouteKey(routeKey2) {
2847
2959
  return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
2848
2960
  }
2849
2961
  function patternToRegex(pattern) {
2850
- const path16 = pattern.split("?")[0] ?? pattern;
2851
- const escaped = path16.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
2962
+ const path18 = pattern.split("?")[0] ?? pattern;
2963
+ const escaped = path18.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
2852
2964
  return new RegExp(`^${escaped}$`);
2853
2965
  }
2854
2966
  function suitePathEqual(a, b) {
@@ -2871,13 +2983,13 @@ function matchProbeItem(plan, test) {
2871
2983
  function requestMatchesRoute(method, url, routeKey2) {
2872
2984
  const { method: rm, pattern } = parseRouteKey(routeKey2);
2873
2985
  if (rm && rm !== method.toUpperCase()) return false;
2874
- let path16;
2986
+ let path18;
2875
2987
  try {
2876
- path16 = new URL(url).pathname;
2988
+ path18 = new URL(url).pathname;
2877
2989
  } catch {
2878
- path16 = url.split("?")[0] ?? url;
2990
+ path18 = url.split("?")[0] ?? url;
2879
2991
  }
2880
- return patternToRegex(pattern).test(path16);
2992
+ return patternToRegex(pattern).test(path18);
2881
2993
  }
2882
2994
  function createProbeState() {
2883
2995
  return { navigated: false, matches: 0 };
@@ -2899,10 +3011,10 @@ function isProbeMode() {
2899
3011
  function loadPlan() {
2900
3012
  if (planCache !== void 0) return planCache;
2901
3013
  planCache = null;
2902
- const path16 = process.env[PIWI_PROBE_ENV.plan];
2903
- if (path16) {
3014
+ const path18 = process.env[PIWI_PROBE_ENV.plan];
3015
+ if (path18) {
2904
3016
  try {
2905
- planCache = JSON.parse(fs3.readFileSync(path16, "utf8"));
3017
+ planCache = JSON.parse(fs3.readFileSync(path18, "utf8"));
2906
3018
  } catch {
2907
3019
  planCache = null;
2908
3020
  }
@@ -2919,17 +3031,27 @@ function probeItemForTest(test) {
2919
3031
  function classifyProbeHandled(signals) {
2920
3032
  const blank = signals.blankPage === true;
2921
3033
  if ((signals.pageErrors ?? 0) > 0 || signals.backendError && blank) return "unhandled";
2922
- return signals.consoleErrors > 0 || signals.dialogs > 0 || signals.backendError || blank ? "degraded" : "graceful";
3034
+ const backendDegraded = signals.backendError && signals.faultIsTheError !== true;
3035
+ return signals.consoleErrors > 0 || signals.dialogs > 0 || backendDegraded || blank ? "degraded" : "graceful";
3036
+ }
3037
+ var SELF_ERROR_FAULTS = /* @__PURE__ */ new Set(["throw", "status"]);
3038
+ function faultIsTheError(fault) {
3039
+ return SELF_ERROR_FAULTS.has(fault);
3040
+ }
3041
+ var RESOURCE_ERROR_TEXT = /^Failed to load resource\b/;
3042
+ function probeConsoleErrors(entries) {
3043
+ return entries.filter((e) => e.type === "error" && !(typeof e.text === "string" && RESOURCE_ERROR_TEXT.test(e.text))).length;
2923
3044
  }
2924
3045
  function outcomeFromStatus(status, applied) {
2925
3046
  if (!applied) return "inconclusive";
2926
- return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
3047
+ if (status === "failed" || status === "timedOut") return "noticed";
3048
+ return status === "passed" ? "not-noticed" : "inconclusive";
2927
3049
  }
2928
3050
  function recordProbeOutcome(line) {
2929
- const path16 = process.env[PIWI_PROBE_ENV.results];
2930
- if (!path16) return;
3051
+ const path18 = process.env[PIWI_PROBE_ENV.results];
3052
+ if (!path18) return;
2931
3053
  try {
2932
- fs3.appendFileSync(path16, `${JSON.stringify(line)}
3054
+ fs3.appendFileSync(path18, `${JSON.stringify(line)}
2933
3055
  `);
2934
3056
  } catch {
2935
3057
  }
@@ -2966,23 +3088,26 @@ function dropOneField(body) {
2966
3088
  } catch {
2967
3089
  return body;
2968
3090
  }
3091
+ let dropped = false;
2969
3092
  if (Array.isArray(parsed2)) {
2970
3093
  let key;
2971
3094
  for (const el of parsed2) {
2972
3095
  if (el && typeof el === "object" && !Array.isArray(el)) {
2973
3096
  key ??= Object.keys(el)[0];
2974
- if (key) delete el[key];
3097
+ if (key && Object.prototype.hasOwnProperty.call(el, key)) {
3098
+ delete el[key];
3099
+ dropped = true;
3100
+ }
2975
3101
  }
2976
3102
  }
2977
- return JSON.stringify(parsed2);
2978
- }
2979
- if (parsed2 && typeof parsed2 === "object") {
2980
- const obj = parsed2;
2981
- const key = Object.keys(obj)[0];
2982
- if (key) delete obj[key];
2983
- return JSON.stringify(obj);
3103
+ } else if (parsed2 && typeof parsed2 === "object") {
3104
+ const key = Object.keys(parsed2)[0];
3105
+ if (key) {
3106
+ delete parsed2[key];
3107
+ dropped = true;
3108
+ }
2984
3109
  }
2985
- return body;
3110
+ return dropped ? JSON.stringify(parsed2) : body;
2986
3111
  }
2987
3112
  function computeFault(fault, input2) {
2988
3113
  const base = {
@@ -3059,7 +3184,7 @@ function originOf(url) {
3059
3184
  return null;
3060
3185
  }
3061
3186
  }
3062
- var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, Math.max(0, ms)));
3187
+ var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, Math.max(0, ms)));
3063
3188
  async function performRouteAction(route, action, startedAt) {
3064
3189
  switch (action.kind) {
3065
3190
  case "abort":
@@ -3640,84 +3765,507 @@ function recordFlakeResult(line) {
3640
3765
  }
3641
3766
  }
3642
3767
 
3643
- // ../core/src/error-text.ts
3644
- function joinErrorMessages(errors) {
3645
- if (!errors || errors.length === 0) return null;
3646
- const seen = /* @__PURE__ */ new Set();
3647
- const parts = [];
3648
- for (const err of errors) {
3649
- const msg = err.message ?? "";
3650
- if (!msg) continue;
3651
- const dedupeKey = msg.trim().slice(0, 200);
3652
- if (seen.has(dedupeKey)) continue;
3653
- seen.add(dedupeKey);
3654
- parts.push(msg);
3655
- }
3656
- return parts.join("\n---\n") || null;
3657
- }
3658
-
3659
- // ../core/src/coverage.ts
3660
- var MAX_COVERAGE_FILES = 2e3;
3661
- var MAX_COVERAGE_RANGES = 1e4;
3662
- var MAX_LINE = 1e6;
3663
- var MAX_PATH_LENGTH = 500;
3664
- var BLOB_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
3665
- function isRecord(value) {
3666
- return typeof value === "object" && value !== null && !Array.isArray(value);
3768
+ // ../core/src/mask.ts
3769
+ var DATA_URI_RE = /\bdata:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=]+/gi;
3770
+ var JWT_RE = /\beyJ[\w-]{10,}\.[\w-]{5,}\.[\w-]{5,}\b/g;
3771
+ var LONG_HEX_RE2 = /\b[0-9a-f]{32,}\b/gi;
3772
+ function maskTokenLike(text) {
3773
+ return text.replace(DATA_URI_RE, "data:[masked]").replace(JWT_RE, "[masked-token]").replace(LONG_HEX_RE2, "[masked-hex]");
3667
3774
  }
3668
- function isRuntime(value) {
3669
- return value === "browser" || value === "server";
3775
+ var CREDENTIAL_PARAM_RE = /token|code|secret|password|passwd|pwd|key|session|auth|sig|credential|assertion|nonce|otp|jwt/i;
3776
+ function maskUrlFragment(hash) {
3777
+ const body = hash.startsWith("#") ? hash.slice(1) : hash;
3778
+ const query = body.indexOf("?");
3779
+ const route = query >= 0 ? body.slice(0, query) : body.includes("=") ? "" : body;
3780
+ const params = query >= 0 ? body.slice(query + 1) : body.includes("=") ? body : null;
3781
+ const masked = params === null ? null : params.split("&").map((param) => {
3782
+ const eq = param.indexOf("=");
3783
+ if (eq < 0) return maskTokenLike(param);
3784
+ const name = param.slice(0, eq);
3785
+ const value = param.slice(eq + 1);
3786
+ return `${name}=${value && CREDENTIAL_PARAM_RE.test(name) ? "[masked]" : maskTokenLike(value)}`;
3787
+ }).join("&");
3788
+ const out = maskTokenLike(route) + (masked === null ? "" : query >= 0 ? `?${masked}` : masked);
3789
+ return hash.startsWith("#") ? `#${out}` : out;
3670
3790
  }
3671
- function isLine(value) {
3672
- return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_LINE;
3791
+ var HIDDEN_VALUE = "<hidden>";
3792
+ var SECRET_AUTOCOMPLETE = [
3793
+ "current-password",
3794
+ "new-password",
3795
+ "one-time-code",
3796
+ "cc-number",
3797
+ "cc-csc"
3798
+ ];
3799
+ var SECRET_FIELD_SELECTOR = [
3800
+ 'input[type="password" i]',
3801
+ ...SECRET_AUTOCOMPLETE.flatMap((token) => [
3802
+ `input[autocomplete~="${token}" i]`,
3803
+ `textarea[autocomplete~="${token}" i]`
3804
+ ])
3805
+ ].join(", ");
3806
+ var SECRET_WORDS = /* @__PURE__ */ new Set([
3807
+ "pass",
3808
+ "password",
3809
+ "passwd",
3810
+ "passphrase",
3811
+ "passcode",
3812
+ "pwd",
3813
+ "secret",
3814
+ "secrets",
3815
+ "token",
3816
+ "tokens",
3817
+ "jwt",
3818
+ "bearer",
3819
+ "sig",
3820
+ "signature",
3821
+ "key",
3822
+ "keys",
3823
+ "apikey",
3824
+ "auth",
3825
+ "oauth",
3826
+ "authorization",
3827
+ "session",
3828
+ "sessionid",
3829
+ "jsessionid",
3830
+ "sid",
3831
+ "cookie",
3832
+ "cookies",
3833
+ "credential",
3834
+ "credentials",
3835
+ "otp",
3836
+ "refresh",
3837
+ "csrf",
3838
+ "xsrf"
3839
+ ]);
3840
+ var SECRET_NAMES = /* @__PURE__ */ new Set(["code", "pin"]);
3841
+ function nameWords(name) {
3842
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])(?=[A-Z][a-z])/g, "$1 ").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
3673
3843
  }
3674
- function validRanges(raw) {
3675
- if (!Array.isArray(raw)) return [];
3676
- const out = [];
3677
- for (const item of raw) {
3678
- if (!Array.isArray(item) || item.length !== 2) continue;
3679
- const [start, end] = item;
3680
- if (isLine(start) && isLine(end) && start <= end) out.push([start, end]);
3844
+ function isSecretName(name) {
3845
+ let secret = namesRead.get(name);
3846
+ if (secret === void 0) {
3847
+ secret = SECRET_NAMES.has(name.trim().toLowerCase()) || nameWords(name).some((word) => SECRET_WORDS.has(word));
3848
+ if (namesRead.size >= 1024) namesRead.clear();
3849
+ namesRead.set(name, secret);
3681
3850
  }
3682
- return out.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
3851
+ return secret;
3683
3852
  }
3684
- function normalizeLineRanges(ranges) {
3685
- const out = [];
3686
- for (const range of validRanges(ranges)) {
3687
- const last = out[out.length - 1];
3688
- if (last && range[0] <= last[1] + 1) last[1] = Math.max(last[1], range[1]);
3689
- else out.push(range);
3853
+ var namesRead = /* @__PURE__ */ new Map();
3854
+ var SECRET_SHAPES = [
3855
+ /\beyJ[\w-]{5,}\.[\w-]{5,}\.[\w-]*/g,
3856
+ /\bBearer(?:\s+|%20)[\w.~+/-]{8,}=*/gi,
3857
+ /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,}/g,
3858
+ /\bwhsec_[A-Za-z0-9]{20,}/g,
3859
+ /\bsk-(?=[\w-]{0,200}\d)(?=[\w-]{0,200}[A-Z])[\w-]{32,}/g,
3860
+ /\bglpat-[\w-]{20,}/g,
3861
+ /\bnpm_[A-Za-z0-9]{36}\b/g,
3862
+ /\bSK[0-9a-f]{32}\b/g,
3863
+ /\bAKIA[0-9A-Z]{16}\b/g,
3864
+ /\bAIza[\w-]{30,}/g,
3865
+ /\bgh[opsu]_[A-Za-z0-9]{20,}/g,
3866
+ /\bgithub_pat_\w{20,}/g,
3867
+ /\bxox[abprs]-[\w-]{8,}/g
3868
+ ];
3869
+ var WHOLE_SHAPES = SECRET_SHAPES.map(
3870
+ (re) => new RegExp(`^(?:${re.source})$`, re.flags.replace("g", ""))
3871
+ );
3872
+ var BASIC = /\bBasic\s+([A-Za-z0-9+/]{4,}={0,2})(?![\w+/=])/g;
3873
+ var PEM_BEGIN = /-----BEGIN ([A-Z0-9 ]{0,40}PRIVATE KEY)-----/g;
3874
+ var SET_COOKIE = /(\bSet-Cookie:[ \t]*[^=;\s]{1,256}=)([^;\r\n]{1,4096})/gi;
3875
+ var COOKIE_LINE = /((?<![\w-])Cookie:[ \t]*)([^\r\n]{1,16384})/gi;
3876
+ var JSON_FIELD = /("((?:[^"\\\r\n]|\\.){1,256})"\s*:\s*")((?:[^"\\\r\n]|\\.){1,4096})"/g;
3877
+ var TOKEN_RUN = /[A-Za-z0-9+/_-]{40,}={0,2}/g;
3878
+ var MIN_RANDOM = 40;
3879
+ var BASE64_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3880
+ function base64OfText(run) {
3881
+ let bits = 0;
3882
+ let acc = 0;
3883
+ let bytes = 0;
3884
+ let printable = 0;
3885
+ for (const c of run.replace(/=+$/, "")) {
3886
+ const digit = BASE64_DIGITS.indexOf(c === "-" ? "+" : c === "_" ? "/" : c);
3887
+ if (digit < 0) return false;
3888
+ acc = (acc << 6 | digit) & 65535;
3889
+ bits += 6;
3890
+ if (bits < 8) continue;
3891
+ bits -= 8;
3892
+ const byte = acc >> bits & 255;
3893
+ bytes++;
3894
+ if (byte >= 32 && byte < 127 || byte === 9 || byte === 10 || byte === 13) printable++;
3690
3895
  }
3691
- return out;
3896
+ return bytes > 0 && printable * 10 >= bytes * 9;
3692
3897
  }
3693
- function normalizeFunctionRanges(ranges) {
3694
- const out = [];
3695
- for (const range of validRanges(ranges)) {
3696
- const last = out[out.length - 1];
3697
- if (!last || last[0] !== range[0] || last[1] !== range[1]) out.push(range);
3698
- }
3699
- return out;
3898
+ function bitsPerChar(text) {
3899
+ const counts = /* @__PURE__ */ new Map();
3900
+ for (const c of text) counts.set(c, (counts.get(c) ?? 0) + 1);
3901
+ let bits = 0;
3902
+ for (const n of counts.values()) bits -= n / text.length * Math.log2(n / text.length);
3903
+ return bits;
3700
3904
  }
3701
- function normalizeCoveragePath(raw) {
3702
- if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
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;
3905
+ function looksRandom(text) {
3906
+ if (text.length < MIN_RANDOM || !/[A-Z]/.test(text) || !/[a-z]/.test(text) || !/\d/.test(text)) return false;
3907
+ let letters = 0;
3908
+ for (const c of text) if (c >= "a" && c <= "z" || c >= "A" && c <= "Z") letters++;
3909
+ let wordy = 0;
3910
+ for (const run of text.matchAll(/[a-z]{6,}|[A-Z]{6,}/g)) wordy += run[0].length;
3911
+ return wordy * 5 <= letters && bitsPerChar(text) >= 4 && !base64OfText(text);
3707
3912
  }
3708
- function fileEntry(path16, runtime, blob, functions = [], lines = []) {
3709
- const file = { path: path16, runtime };
3710
- if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
3711
- if (functions.length > 0) file.functions = functions;
3712
- if (lines.length > 0) file.lines = lines;
3713
- return file;
3913
+ function isBasicCredential(encoded) {
3914
+ const text = encoded.replace(/=+$/, "");
3915
+ if (text.length % 4 === 1) return false;
3916
+ let decoded = "";
3917
+ let bits = 0;
3918
+ let acc = 0;
3919
+ for (const c of text) {
3920
+ const digit = BASE64_DIGITS.indexOf(c);
3921
+ if (digit < 0) return false;
3922
+ acc = (acc << 6 | digit) & 65535;
3923
+ bits += 6;
3924
+ if (bits < 8) continue;
3925
+ bits -= 8;
3926
+ decoded += String.fromCharCode(acc >> bits & 255);
3927
+ }
3928
+ return /^[\x20-\x7e]+:[\x20-\x7e]*$/.test(decoded);
3714
3929
  }
3715
- function captureOf(files, incomplete) {
3716
- return incomplete ? { v: 1, files, incomplete: true } : { v: 1, files };
3930
+ function isSecretValue(value) {
3931
+ const text = value.trim();
3932
+ if (text.length < 10 || text.length > 4096) return false;
3933
+ if (WHOLE_SHAPES.some((re) => re.test(text))) return true;
3934
+ const basic = /^Basic\s+([A-Za-z0-9+/]{4,}={0,2})$/.exec(text);
3935
+ if (basic && isBasicCredential(basic[1])) return true;
3936
+ if (/^-----BEGIN [A-Z0-9 ]{0,40}PRIVATE KEY-----/.test(text)) return true;
3937
+ return /^[A-Za-z0-9+/_-]{40,}={0,2}$/.test(text) && looksRandom(text);
3717
3938
  }
3718
- function compareFiles(a, b) {
3719
- if (a.path !== b.path) return a.path < b.path ? -1 : 1;
3720
- if (a.runtime !== b.runtime) return a.runtime < b.runtime ? -1 : 1;
3939
+ var URL_IN_TEXT = /\b[a-z][a-z0-9+.-]{0,31}:\/\/[^\s"'<>`]+/gi;
3940
+ var URL_PARTS = /^(?:([a-z][a-z0-9+.-]*:)?\/\/([^/?#]*))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?$/i;
3941
+ function decodeSafe(text) {
3942
+ try {
3943
+ return decodeURIComponent(text);
3944
+ } catch {
3945
+ return text;
3946
+ }
3947
+ }
3948
+ function hider(count) {
3949
+ return () => {
3950
+ count.n++;
3951
+ return HIDDEN_VALUE;
3952
+ };
3953
+ }
3954
+ function hideParams(params, count) {
3955
+ const hide = hider(count);
3956
+ return params.split(/([&;])/).map((part, i) => {
3957
+ const eq = part.indexOf("=");
3958
+ if (i % 2 === 1 || eq < 0) return part;
3959
+ const name = part.slice(0, eq);
3960
+ const value = part.slice(eq + 1);
3961
+ const decoded = decodeSafe(value);
3962
+ if (!value || decoded === HIDDEN_VALUE) return part;
3963
+ return isSecretName(decodeSafe(name)) || isSecretValue(decoded) ? `${name}=${hide()}` : part;
3964
+ }).join("");
3965
+ }
3966
+ function hidePath(path18, count) {
3967
+ const hide = hider(count);
3968
+ return path18.split("/").map((segment) => {
3969
+ const [own = "", ...params] = segment.split(";");
3970
+ const decoded = decodeSafe(own);
3971
+ const kept = decoded !== HIDDEN_VALUE && isSecretValue(decoded) ? hide() : own;
3972
+ return params.length ? `${kept};${hideParams(params.join(";"), count)}` : kept;
3973
+ }).join("/");
3974
+ }
3975
+ function hideSecretsInUrl(url, count = { n: 0 }) {
3976
+ const parts = URL_PARTS.exec(url);
3977
+ if (!parts) return url;
3978
+ const [, scheme = "", authority, path18 = "", query = "", fragment = ""] = parts;
3979
+ let out = "";
3980
+ if (authority !== void 0) {
3981
+ const at = authority.lastIndexOf("@");
3982
+ const colon = at >= 0 ? authority.slice(0, at).indexOf(":") : -1;
3983
+ out += colon >= 0 && colon < at - 1 ? `${scheme}//${authority.slice(0, colon + 1)}${hider(count)()}${authority.slice(at)}` : `${scheme}//${authority}`;
3984
+ }
3985
+ out += hidePath(path18, count);
3986
+ if (query) out += `?${hideParams(query.slice(1), count)}`;
3987
+ if (fragment) {
3988
+ const body = fragment.slice(1);
3989
+ const ask = body.indexOf("?");
3990
+ out += `#${ask >= 0 ? `${hidePath(body.slice(0, ask), count)}?${hideParams(body.slice(ask + 1), count)}` : body.includes("=") ? hideParams(body, count) : hidePath(body, count)}`;
3991
+ }
3992
+ return out;
3993
+ }
3994
+ var QUERY_PAIR = /([?&;#])([^=&#?;\s"'<>]{1,256})=([^&;#\s"'<>]*)/g;
3995
+ var URL_PASSWORD = /(\/\/[^/:@\s"'<>]{1,256}:)([^@/\s"'<>]{1,256})(@)/g;
3996
+ function hidePem(text, hide) {
3997
+ let out = "";
3998
+ let last = 0;
3999
+ let nextEnd = -2;
4000
+ PEM_BEGIN.lastIndex = 0;
4001
+ for (let begin = PEM_BEGIN.exec(text); begin; begin = PEM_BEGIN.exec(text)) {
4002
+ if (begin.index < last) continue;
4003
+ if (nextEnd !== -1 && nextEnd < begin.index) nextEnd = text.indexOf("-----END ", begin.index);
4004
+ const footer = nextEnd >= 0 ? /^-----END [A-Z0-9 ]{0,40}-----/.exec(text.slice(nextEnd, nextEnd + 60)) : null;
4005
+ const until = footer ? nextEnd + footer[0].length : begin.index + begin[0].length + (/^(?:\s*[A-Za-z0-9+/=]+)*/.exec(text.slice(begin.index + begin[0].length))?.[0].length ?? 0);
4006
+ out += `${text.slice(last, begin.index)}${hide()}`;
4007
+ last = until;
4008
+ PEM_BEGIN.lastIndex = until;
4009
+ }
4010
+ return out + text.slice(last);
4011
+ }
4012
+ function hideInText(text, count) {
4013
+ if (text.length < 4 || !/[A-Za-z0-9]/.test(text)) return text;
4014
+ const hide = hider(count);
4015
+ let out = hidePem(text, hide);
4016
+ out = out.replace(QUERY_PAIR, (all, sep8, name, value) => {
4017
+ const decoded = decodeSafe(value);
4018
+ if (!value || decoded === HIDDEN_VALUE) return all;
4019
+ return isSecretName(name) || isSecretValue(decoded) ? `${sep8}${name}=${hide()}` : all;
4020
+ });
4021
+ out = out.replace(URL_PASSWORD, (_all, before, _password, at) => `${before}${hide()}${at}`);
4022
+ out = out.replace(SET_COOKIE, (_all, before) => `${before}${hide()}`);
4023
+ out = out.replace(COOKIE_LINE, (_all, before, pairs) => {
4024
+ const values = pairs.replace(
4025
+ /([^=;\s]{1,256}=)([^;]*)/g,
4026
+ (pair, name, value) => value.trim() && value.trim() !== HIDDEN_VALUE ? `${name}${hide()}` : pair
4027
+ );
4028
+ return `${before}${values}`;
4029
+ });
4030
+ out = out.replace(
4031
+ JSON_FIELD,
4032
+ (all, before, name, value) => isSecretName(name) && value !== HIDDEN_VALUE ? `${before}${hide()}"` : all
4033
+ );
4034
+ out = out.replace(BASIC, (all, encoded) => isBasicCredential(encoded) ? hide() : all);
4035
+ for (const shape of SECRET_SHAPES) out = out.replace(shape, () => hide());
4036
+ return out.replace(
4037
+ TOKEN_RUN,
4038
+ (run) => run.includes("/") && !/[+=]/.test(run) ? run.split("/").map((part) => looksRandom(part) ? hide() : part).join("/") : looksRandom(run) ? hide() : run
4039
+ );
4040
+ }
4041
+ function hideSecretsInText(text, count = { n: 0 }) {
4042
+ let out = "";
4043
+ let last = 0;
4044
+ for (const url of text.matchAll(URL_IN_TEXT)) {
4045
+ out += hideInText(text.slice(last, url.index), count) + hideSecretsInUrl(url[0], count);
4046
+ last = url.index + url[0].length;
4047
+ }
4048
+ return out + hideInText(text.slice(last), count);
4049
+ }
4050
+ var TYPED_TITLE = /^(?:Fill|Type|Insert) "/;
4051
+ function hideTypedText(title) {
4052
+ const typing = TYPED_TITLE.exec(title);
4053
+ if (!typing) return title;
4054
+ const start = typing[0].length;
4055
+ const end = title.endsWith('"') ? title.length - 1 : title.lastIndexOf('" ');
4056
+ return end >= start ? `${title.slice(0, start)}${HIDDEN_VALUE}${title.slice(end)}` : `${typing[0]}${HIDDEN_VALUE}"`;
4057
+ }
4058
+ var SECRET_FIELD_WORDS = /* @__PURE__ */ new Set([
4059
+ "passe",
4060
+ "mdp",
4061
+ "passwort",
4062
+ "kennwort",
4063
+ "contrasena",
4064
+ "clave",
4065
+ "senha",
4066
+ "cvv",
4067
+ "cvc",
4068
+ "csc",
4069
+ "pin",
4070
+ "pw",
4071
+ "totp",
4072
+ "mfa",
4073
+ "2fa"
4074
+ ]);
4075
+ var SECRET_STEMS = ["password", "passwd", "passwort", "kennwort", "contrasena", "senha", "motdepasse"];
4076
+ var SECRET_CODE_KINDS = /* @__PURE__ */ new Set([
4077
+ "verification",
4078
+ "security",
4079
+ "confirmation",
4080
+ "auth",
4081
+ "authentication",
4082
+ "access",
4083
+ "time",
4084
+ "otp",
4085
+ "sms",
4086
+ "login",
4087
+ "mfa",
4088
+ "2fa"
4089
+ ]);
4090
+ function isSecretLocator(locator) {
4091
+ const plain = locator.normalize("NFD").replace(/[̀-ͯ]/g, "");
4092
+ if (/type\s*=\s*["']?password\b/i.test(plain)) return true;
4093
+ for (const quoted of plain.matchAll(/(['"])((?:(?!\1)[^\\]|\\.)*)\1/g)) if (isSecretName(quoted[2])) return true;
4094
+ const words = nameWords(plain);
4095
+ return words.some(
4096
+ (word) => SECRET_WORDS.has(word) || SECRET_FIELD_WORDS.has(word) || SECRET_STEMS.some((stem) => word.includes(stem))
4097
+ ) || words.includes("code") && words.some((word) => SECRET_CODE_KINDS.has(word));
4098
+ }
4099
+ var TYPED_CALL = (
4100
+ // eslint-disable-next-line no-control-regex -- matches the ESC byte of a color code
4101
+ /^((?:\u001b\[[\d;]*m)*[ \t]*(?:(?:-|\d+ ×)[ \t]+)?(?:\w+\.)?)(fill|type|insertText)\("/gm
4102
+ );
4103
+ var TYPED_END = /"\)(?=(?:\u001b\[[\d;]*m|[ \t])*(?:\r?\n|$))/g;
4104
+ function hideTypedTextInLog(text) {
4105
+ let out = "";
4106
+ let from = 0;
4107
+ TYPED_CALL.lastIndex = 0;
4108
+ for (let call = TYPED_CALL.exec(text); call; call = TYPED_CALL.exec(text)) {
4109
+ out += `${text.slice(from, call.index)}${call[1]}${call[2]}("${HIDDEN_VALUE}")`;
4110
+ TYPED_END.lastIndex = call.index + call[0].length;
4111
+ const end = TYPED_END.exec(text);
4112
+ if (!end) return out;
4113
+ from = end.index + 2;
4114
+ TYPED_CALL.lastIndex = from;
4115
+ }
4116
+ return out + text.slice(from);
4117
+ }
4118
+ var READ_LOCATOR = /(?:waiting for|Locator:)[ \t]+(.*)$/gm;
4119
+ var RESOLVED_PASSWORD = /resolved to <input\b[^>\n]*\btype="password"/i;
4120
+ var READ_VALUE = (
4121
+ // eslint-disable-next-line no-control-regex -- matches the ESC byte of a color code
4122
+ /(unexpected value |(?:Expected|Received)(?: string)?:[ \t]*(?:\u001b\[[\d;]*m)*(?:not[ \t]+)?)"(?:.*"|.*)/g
4123
+ );
4124
+ var RESOLVED_ELEMENT = /resolved to <[^>\n]*/g;
4125
+ var VALUE_ATTRIBUTE = /\bvalue="[^"\n]*"/g;
4126
+ var COLOR = /\u001b\[[\d;]*m/g;
4127
+ function namesSecretField(text) {
4128
+ if (RESOLVED_PASSWORD.test(text)) return true;
4129
+ for (const named of text.matchAll(READ_LOCATOR))
4130
+ if (isSecretLocator(named[1].replace(COLOR, "").trim())) return true;
4131
+ return false;
4132
+ }
4133
+ function hideReadValues(text) {
4134
+ return text.replace(READ_VALUE, (_, before) => `${before}"${HIDDEN_VALUE}"`);
4135
+ }
4136
+ function hideResolvedValues(text, secretField) {
4137
+ if (!text.includes("resolved to <")) return text;
4138
+ return text.replace(
4139
+ RESOLVED_ELEMENT,
4140
+ (element) => secretField || /\btype="password"/i.test(element) ? element.replace(VALUE_ATTRIBUTE, `value="${HIDDEN_VALUE}"`) : element
4141
+ );
4142
+ }
4143
+ var LOGGED_HEADER = (
4144
+ // eslint-disable-next-line no-control-regex -- matches the ESC byte of a color code
4145
+ /^(?:\u001b\[[\d;]*m)*(?:[ \t]+(?:(?:-|\d+ ×)[ \t]+)?|(?:-|\d+ ×)[ \t]+)([A-Za-z][\w-]*): /
4146
+ );
4147
+ function colorsAtEnd(line) {
4148
+ let end = line.length;
4149
+ while (end > 0 && line[end - 1] === "m") {
4150
+ const start = line.lastIndexOf("\x1B[", end - 1);
4151
+ if (start < 0 || !/^[\d;]*$/.test(line.slice(start + 2, end - 1))) break;
4152
+ end = start;
4153
+ }
4154
+ return end;
4155
+ }
4156
+ function hideSecretHeaders(text) {
4157
+ if (!text.includes(": ")) return text;
4158
+ return text.split("\n").map((line) => {
4159
+ const header = LOGGED_HEADER.exec(line);
4160
+ if (!header || !isSecretName(header[1])) return line;
4161
+ const rest = line.slice(header[0].length);
4162
+ const end = colorsAtEnd(rest);
4163
+ return end === 0 ? line : `${header[0]}${HIDDEN_VALUE}${rest.slice(end)}`;
4164
+ }).join("\n");
4165
+ }
4166
+ function hideSecretsInLog(text, secretField = false) {
4167
+ const secret = secretField || namesSecretField(text);
4168
+ let hidden2 = hideResolvedValues(hideTypedTextInLog(text), secret);
4169
+ if (secret) hidden2 = hideReadValues(hidden2);
4170
+ return hideSecretHeaders(hidden2);
4171
+ }
4172
+ var CALL_LOG = "\nCall log:";
4173
+ var CALL_LOG_END = /\n(?:---\n|[ \t]+at )/g;
4174
+ function hideSecretsInError(text) {
4175
+ const secretField = namesSecretField(text);
4176
+ let out = "";
4177
+ let from = 0;
4178
+ for (; ; ) {
4179
+ const log = text.indexOf(CALL_LOG, from);
4180
+ const message = text.slice(from, log === -1 ? text.length : log);
4181
+ out += secretField ? hideReadValues(message) : message;
4182
+ if (log === -1) return out;
4183
+ const start = log + CALL_LOG.length;
4184
+ CALL_LOG_END.lastIndex = start;
4185
+ const end = CALL_LOG_END.exec(text)?.index ?? text.length;
4186
+ out += CALL_LOG + hideSecretsInLog(text.slice(start, end), secretField);
4187
+ from = end;
4188
+ }
4189
+ }
4190
+
4191
+ // ../core/src/error-text.ts
4192
+ function joinErrorMessages(errors) {
4193
+ if (!errors || errors.length === 0) return null;
4194
+ const seen = /* @__PURE__ */ new Set();
4195
+ const parts = [];
4196
+ for (const err of errors) {
4197
+ const msg = err.message ?? "";
4198
+ if (!msg) continue;
4199
+ const dedupeKey = msg.trim().slice(0, 200);
4200
+ if (seen.has(dedupeKey)) continue;
4201
+ seen.add(dedupeKey);
4202
+ parts.push(hideSecretsInError(msg));
4203
+ }
4204
+ return parts.join("\n---\n") || null;
4205
+ }
4206
+
4207
+ // ../core/src/coverage.ts
4208
+ var MAX_COVERAGE_FILES = 2e3;
4209
+ var MAX_COVERAGE_RANGES = 1e4;
4210
+ var MAX_LINE = 1e6;
4211
+ var MAX_PATH_LENGTH = 500;
4212
+ var BLOB_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
4213
+ function isRecord(value) {
4214
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4215
+ }
4216
+ function isRuntime(value) {
4217
+ return value === "browser" || value === "server";
4218
+ }
4219
+ function isLine(value) {
4220
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_LINE;
4221
+ }
4222
+ function validRanges(raw) {
4223
+ if (!Array.isArray(raw)) return [];
4224
+ const out = [];
4225
+ for (const item of raw) {
4226
+ if (!Array.isArray(item) || item.length !== 2) continue;
4227
+ const [start, end] = item;
4228
+ if (isLine(start) && isLine(end) && start <= end) out.push([start, end]);
4229
+ }
4230
+ return out.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
4231
+ }
4232
+ function normalizeLineRanges(ranges) {
4233
+ const out = [];
4234
+ for (const range of validRanges(ranges)) {
4235
+ const last = out[out.length - 1];
4236
+ if (last && range[0] <= last[1] + 1) last[1] = Math.max(last[1], range[1]);
4237
+ else out.push(range);
4238
+ }
4239
+ return out;
4240
+ }
4241
+ function normalizeFunctionRanges(ranges) {
4242
+ const out = [];
4243
+ for (const range of validRanges(ranges)) {
4244
+ const last = out[out.length - 1];
4245
+ if (!last || last[0] !== range[0] || last[1] !== range[1]) out.push(range);
4246
+ }
4247
+ return out;
4248
+ }
4249
+ function normalizeCoveragePath(raw) {
4250
+ if (typeof raw !== "string" || raw.length > MAX_PATH_LENGTH || raw.includes("\0")) return null;
4251
+ const path18 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
4252
+ if (!path18 || path18.startsWith("../") || path18.startsWith("/") || /^[A-Za-z]:\//.test(path18)) return null;
4253
+ if (/(^|\/)node_modules\//.test(path18)) return null;
4254
+ return path18;
4255
+ }
4256
+ function fileEntry(path18, runtime, blob, functions = [], lines = []) {
4257
+ const file = { path: path18, runtime };
4258
+ if (blob && (functions.length > 0 || lines.length > 0)) file.blob = blob;
4259
+ if (functions.length > 0) file.functions = functions;
4260
+ if (lines.length > 0) file.lines = lines;
4261
+ return file;
4262
+ }
4263
+ function captureOf(files, incomplete) {
4264
+ return incomplete ? { v: 1, files, incomplete: true } : { v: 1, files };
4265
+ }
4266
+ function compareFiles(a, b) {
4267
+ if (a.path !== b.path) return a.path < b.path ? -1 : 1;
4268
+ if (a.runtime !== b.runtime) return a.runtime < b.runtime ? -1 : 1;
3721
4269
  return 0;
3722
4270
  }
3723
4271
  function sanitizeCoverage(raw) {
@@ -3725,11 +4273,11 @@ function sanitizeCoverage(raw) {
3725
4273
  const files = [];
3726
4274
  for (const item of raw.files) {
3727
4275
  if (!isRecord(item) || !isRuntime(item.runtime)) continue;
3728
- const path16 = normalizeCoveragePath(item.path);
3729
- if (path16 === null) continue;
4276
+ const path18 = normalizeCoveragePath(item.path);
4277
+ if (path18 === null) continue;
3730
4278
  const blob = typeof item.blob === "string" && BLOB_ID.test(item.blob) ? item.blob : void 0;
3731
4279
  files.push(
3732
- fileEntry(path16, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
4280
+ fileEntry(path18, item.runtime, blob, normalizeFunctionRanges(item.functions), normalizeLineRanges(item.lines))
3733
4281
  );
3734
4282
  }
3735
4283
  const merged = mergeCoverage(captureOf(files, raw.incomplete === true));
@@ -4009,351 +4557,75 @@ function normalizeSourcePath(source) {
4009
4557
  s = decodeURIComponent(s);
4010
4558
  } catch {
4011
4559
  }
4012
- s = s.replace(/\\/g, "/");
4013
- if (/^\/[A-Za-z]:\//.test(s)) s = s.slice(1);
4014
- s = s.replace(/(^|\/)\.\//g, "$1");
4015
- return s;
4016
- }
4017
-
4018
- // ../core/src/code-reach.ts
4019
- var MAX_CODE_REACH_FILES = 2e3;
4020
- function scriptText(source) {
4021
- return { length: source.length, starts: Uint32Array.from(lineStarts(source)) };
4022
- }
4023
- var textOf = (entry, text) => text ?? (entry.source === void 0 ? void 0 : scriptText(entry.source));
4024
- function executedFunctionRanges(entry, length, all = false) {
4025
- length ??= entry.source?.length ?? Number.POSITIVE_INFINITY;
4026
- const out = [];
4027
- entry.functions.forEach((fn, i) => {
4028
- const whole = fn.ranges[0];
4029
- if (!whole || !all && whole.count <= 0) return;
4030
- const topLevel = whole.startOffset === 0 && (whole.endOffset >= length || i === 0 && fn.functionName === "");
4031
- if (topLevel) return;
4032
- out.push({ start: whole.startOffset, end: whole.endOffset });
4033
- });
4034
- return out;
4035
- }
4036
- function scriptRan(entry, text) {
4037
- return executedFunctionRanges(entry, text?.length).length > 0;
4038
- }
4039
- function reachedFunctionLines(entry, map, text) {
4040
- return functionLines(entry, map, text, false);
4041
- }
4042
- function knownFunctionLines(entry, map, text) {
4043
- return functionLines(entry, map, text, true);
4044
- }
4045
- function functionLines(entry, map, text, all) {
4046
- const out = /* @__PURE__ */ new Map();
4047
- const read = textOf(entry, text);
4048
- const ranges = executedFunctionRanges(entry, read?.length, all);
4049
- if (!ranges.length || !read) return out;
4050
- const add2 = (source, range) => {
4051
- const list = out.get(source);
4052
- if (list) list.push(range);
4053
- else out.set(source, [range]);
4054
- };
4055
- const starts = read.starts;
4056
- for (const range of ranges) {
4057
- const start = offsetToPosition(starts, range.start);
4058
- if (!map) {
4059
- const last = offsetToPosition(starts, Math.max(range.start, range.end - 1));
4060
- add2(-1, [start.line + 1, last.line + 1]);
4061
- continue;
4062
- }
4063
- const at = map.originalAt(start.line, start.column);
4064
- if (at === null) continue;
4065
- const end = offsetToPosition(starts, range.end);
4066
- const first = map.firstOriginalLineBetween(at.source, start, end);
4067
- if (first === null) continue;
4068
- const header = first === at.line || first === at.line + 1 ? at.line : first;
4069
- const lines = map.originalLinesBetween(at.source, start, end, header);
4070
- if (lines) add2(at.source, [header + 1, lines[1] + 1]);
4071
- }
4072
- return out;
4073
- }
4074
- function finalizeCodeReach(files) {
4075
- const out = /* @__PURE__ */ new Set();
4076
- for (const raw of files) {
4077
- const file = normalizeCoveragePath(raw);
4078
- if (file !== null) out.add(file);
4079
- }
4080
- return [...out].sort().slice(0, MAX_CODE_REACH_FILES);
4081
- }
4082
-
4083
- // ../core/src/mask.ts
4084
- var DATA_URI_RE = /\bdata:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/=]+/gi;
4085
- var JWT_RE = /\beyJ[\w-]{10,}\.[\w-]{5,}\.[\w-]{5,}\b/g;
4086
- var LONG_HEX_RE2 = /\b[0-9a-f]{32,}\b/gi;
4087
- function maskTokenLike(text) {
4088
- return text.replace(DATA_URI_RE, "data:[masked]").replace(JWT_RE, "[masked-token]").replace(LONG_HEX_RE2, "[masked-hex]");
4089
- }
4090
- var CREDENTIAL_PARAM_RE = /token|code|secret|password|passwd|pwd|key|session|auth|sig|credential|assertion|nonce|otp|jwt/i;
4091
- function maskUrlFragment(hash) {
4092
- const body = hash.startsWith("#") ? hash.slice(1) : hash;
4093
- const query = body.indexOf("?");
4094
- const route = query >= 0 ? body.slice(0, query) : body.includes("=") ? "" : body;
4095
- const params = query >= 0 ? body.slice(query + 1) : body.includes("=") ? body : null;
4096
- const masked = params === null ? null : params.split("&").map((param) => {
4097
- const eq = param.indexOf("=");
4098
- if (eq < 0) return maskTokenLike(param);
4099
- const name = param.slice(0, eq);
4100
- const value = param.slice(eq + 1);
4101
- return `${name}=${value && CREDENTIAL_PARAM_RE.test(name) ? "[masked]" : maskTokenLike(value)}`;
4102
- }).join("&");
4103
- const out = maskTokenLike(route) + (masked === null ? "" : query >= 0 ? `?${masked}` : masked);
4104
- return hash.startsWith("#") ? `#${out}` : out;
4105
- }
4106
- var HIDDEN_VALUE = "<hidden>";
4107
- var SECRET_AUTOCOMPLETE = [
4108
- "current-password",
4109
- "new-password",
4110
- "one-time-code",
4111
- "cc-number",
4112
- "cc-csc"
4113
- ];
4114
- var SECRET_WORDS = /* @__PURE__ */ new Set([
4115
- "pass",
4116
- "password",
4117
- "passwd",
4118
- "passphrase",
4119
- "passcode",
4120
- "pwd",
4121
- "secret",
4122
- "secrets",
4123
- "token",
4124
- "tokens",
4125
- "jwt",
4126
- "bearer",
4127
- "sig",
4128
- "signature",
4129
- "key",
4130
- "keys",
4131
- "apikey",
4132
- "auth",
4133
- "oauth",
4134
- "authorization",
4135
- "session",
4136
- "sessionid",
4137
- "jsessionid",
4138
- "sid",
4139
- "cookie",
4140
- "cookies",
4141
- "credential",
4142
- "credentials",
4143
- "otp",
4144
- "refresh",
4145
- "csrf",
4146
- "xsrf"
4147
- ]);
4148
- var SECRET_NAMES = /* @__PURE__ */ new Set(["code", "pin"]);
4149
- function nameWords(name) {
4150
- return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
4151
- }
4152
- function isSecretName(name) {
4153
- let secret = namesRead.get(name);
4154
- if (secret === void 0) {
4155
- secret = SECRET_NAMES.has(name.trim().toLowerCase()) || nameWords(name).some((word) => SECRET_WORDS.has(word));
4156
- if (namesRead.size >= 1024) namesRead.clear();
4157
- namesRead.set(name, secret);
4158
- }
4159
- return secret;
4160
- }
4161
- var namesRead = /* @__PURE__ */ new Map();
4162
- var SECRET_SHAPES = [
4163
- /\beyJ[\w-]{5,}\.[\w-]{5,}\.[\w-]*/g,
4164
- /\bBearer(?:\s+|%20)[\w.~+/-]{8,}=*/gi,
4165
- /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,}/g,
4166
- /\bwhsec_[A-Za-z0-9]{20,}/g,
4167
- /\bsk-(?=[\w-]{0,200}\d)(?=[\w-]{0,200}[A-Z])[\w-]{32,}/g,
4168
- /\bglpat-[\w-]{20,}/g,
4169
- /\bnpm_[A-Za-z0-9]{36}\b/g,
4170
- /\bSK[0-9a-f]{32}\b/g,
4171
- /\bAKIA[0-9A-Z]{16}\b/g,
4172
- /\bAIza[\w-]{30,}/g,
4173
- /\bgh[opsu]_[A-Za-z0-9]{20,}/g,
4174
- /\bgithub_pat_\w{20,}/g,
4175
- /\bxox[abprs]-[\w-]{8,}/g
4176
- ];
4177
- var WHOLE_SHAPES = SECRET_SHAPES.map(
4178
- (re) => new RegExp(`^(?:${re.source})$`, re.flags.replace("g", ""))
4179
- );
4180
- var BASIC = /\bBasic\s+([A-Za-z0-9+/]{4,}={0,2})(?![\w+/=])/g;
4181
- var PEM_BEGIN = /-----BEGIN ([A-Z0-9 ]{0,40}PRIVATE KEY)-----/g;
4182
- var SET_COOKIE = /(\bSet-Cookie:[ \t]*[^=;\s]{1,256}=)([^;\r\n]{1,4096})/gi;
4183
- var COOKIE_LINE = /((?<![\w-])Cookie:[ \t]*)([^\r\n]{1,16384})/gi;
4184
- var JSON_FIELD = /("((?:[^"\\\r\n]|\\.){1,256})"\s*:\s*")((?:[^"\\\r\n]|\\.){1,4096})"/g;
4185
- var TOKEN_RUN = /[A-Za-z0-9+/_-]{40,}={0,2}/g;
4186
- var MIN_RANDOM = 40;
4187
- var BASE64_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4188
- function base64OfText(run) {
4189
- let bits = 0;
4190
- let acc = 0;
4191
- let bytes = 0;
4192
- let printable = 0;
4193
- for (const c of run.replace(/=+$/, "")) {
4194
- const digit = BASE64_DIGITS.indexOf(c === "-" ? "+" : c === "_" ? "/" : c);
4195
- if (digit < 0) return false;
4196
- acc = (acc << 6 | digit) & 65535;
4197
- bits += 6;
4198
- if (bits < 8) continue;
4199
- bits -= 8;
4200
- const byte = acc >> bits & 255;
4201
- bytes++;
4202
- if (byte >= 32 && byte < 127 || byte === 9 || byte === 10 || byte === 13) printable++;
4203
- }
4204
- return bytes > 0 && printable * 10 >= bytes * 9;
4205
- }
4206
- function bitsPerChar(text) {
4207
- const counts = /* @__PURE__ */ new Map();
4208
- for (const c of text) counts.set(c, (counts.get(c) ?? 0) + 1);
4209
- let bits = 0;
4210
- for (const n of counts.values()) bits -= n / text.length * Math.log2(n / text.length);
4211
- return bits;
4212
- }
4213
- function looksRandom(text) {
4214
- if (text.length < MIN_RANDOM || !/[A-Z]/.test(text) || !/[a-z]/.test(text) || !/\d/.test(text)) return false;
4215
- let letters = 0;
4216
- for (const c of text) if (c >= "a" && c <= "z" || c >= "A" && c <= "Z") letters++;
4217
- let wordy = 0;
4218
- for (const run of text.matchAll(/[a-z]{6,}|[A-Z]{6,}/g)) wordy += run[0].length;
4219
- return wordy * 5 <= letters && bitsPerChar(text) >= 4 && !base64OfText(text);
4220
- }
4221
- function isBasicCredential(encoded) {
4222
- const text = encoded.replace(/=+$/, "");
4223
- if (text.length % 4 === 1) return false;
4224
- let decoded = "";
4225
- let bits = 0;
4226
- let acc = 0;
4227
- for (const c of text) {
4228
- const digit = BASE64_DIGITS.indexOf(c);
4229
- if (digit < 0) return false;
4230
- acc = (acc << 6 | digit) & 65535;
4231
- bits += 6;
4232
- if (bits < 8) continue;
4233
- bits -= 8;
4234
- decoded += String.fromCharCode(acc >> bits & 255);
4235
- }
4236
- return /^[\x20-\x7e]+:[\x20-\x7e]*$/.test(decoded);
4237
- }
4238
- function isSecretValue(value) {
4239
- const text = value.trim();
4240
- if (text.length < 10 || text.length > 4096) return false;
4241
- if (WHOLE_SHAPES.some((re) => re.test(text))) return true;
4242
- const basic = /^Basic\s+([A-Za-z0-9+/]{4,}={0,2})$/.exec(text);
4243
- if (basic && isBasicCredential(basic[1])) return true;
4244
- if (/^-----BEGIN [A-Z0-9 ]{0,40}PRIVATE KEY-----/.test(text)) return true;
4245
- return /^[A-Za-z0-9+/_-]{40,}={0,2}$/.test(text) && looksRandom(text);
4246
- }
4247
- var URL_IN_TEXT = /\b[a-z][a-z0-9+.-]{0,31}:\/\/[^\s"'<>`]+/gi;
4248
- var URL_PARTS = /^(?:([a-z][a-z0-9+.-]*:)?\/\/([^/?#]*))?([^?#]*)(\?[^#]*)?(#[\s\S]*)?$/i;
4249
- function decodeSafe(text) {
4250
- try {
4251
- return decodeURIComponent(text);
4252
- } catch {
4253
- return text;
4254
- }
4560
+ s = s.replace(/\\/g, "/");
4561
+ if (/^\/[A-Za-z]:\//.test(s)) s = s.slice(1);
4562
+ s = s.replace(/(^|\/)\.\//g, "$1");
4563
+ return s;
4255
4564
  }
4256
- function hider(count) {
4257
- return () => {
4258
- count.n++;
4259
- return HIDDEN_VALUE;
4260
- };
4565
+
4566
+ // ../core/src/code-reach.ts
4567
+ var MAX_CODE_REACH_FILES = 2e3;
4568
+ function scriptText(source) {
4569
+ return { length: source.length, starts: Uint32Array.from(lineStarts(source)) };
4261
4570
  }
4262
- function hideParams(params, count) {
4263
- const hide = hider(count);
4264
- return params.split(/([&;])/).map((part, i) => {
4265
- const eq = part.indexOf("=");
4266
- if (i % 2 === 1 || eq < 0) return part;
4267
- const name = part.slice(0, eq);
4268
- const value = part.slice(eq + 1);
4269
- const decoded = decodeSafe(value);
4270
- if (!value || decoded === HIDDEN_VALUE) return part;
4271
- return isSecretName(decodeSafe(name)) || isSecretValue(decoded) ? `${name}=${hide()}` : part;
4272
- }).join("");
4571
+ var textOf = (entry, text) => text ?? (entry.source === void 0 ? void 0 : scriptText(entry.source));
4572
+ function executedFunctionRanges(entry, length, all = false) {
4573
+ length ??= entry.source?.length ?? Number.POSITIVE_INFINITY;
4574
+ const out = [];
4575
+ entry.functions.forEach((fn, i) => {
4576
+ const whole = fn.ranges[0];
4577
+ if (!whole || !all && whole.count <= 0) return;
4578
+ const topLevel = whole.startOffset === 0 && (whole.endOffset >= length || i === 0 && fn.functionName === "");
4579
+ if (topLevel) return;
4580
+ out.push({ start: whole.startOffset, end: whole.endOffset });
4581
+ });
4582
+ return out;
4273
4583
  }
4274
- function hidePath(path16, count) {
4275
- const hide = hider(count);
4276
- return path16.split("/").map((segment) => {
4277
- const [own = "", ...params] = segment.split(";");
4278
- const decoded = decodeSafe(own);
4279
- const kept = decoded !== HIDDEN_VALUE && isSecretValue(decoded) ? hide() : own;
4280
- return params.length ? `${kept};${hideParams(params.join(";"), count)}` : kept;
4281
- }).join("/");
4584
+ function scriptRan(entry, text) {
4585
+ return executedFunctionRanges(entry, text?.length).length > 0;
4282
4586
  }
4283
- function hideSecretsInUrl(url, count = { n: 0 }) {
4284
- const parts = URL_PARTS.exec(url);
4285
- if (!parts) return url;
4286
- const [, scheme = "", authority, path16 = "", query = "", fragment = ""] = parts;
4287
- let out = "";
4288
- if (authority !== void 0) {
4289
- const at = authority.lastIndexOf("@");
4290
- const colon = at >= 0 ? authority.slice(0, at).indexOf(":") : -1;
4291
- out += colon >= 0 && colon < at - 1 ? `${scheme}//${authority.slice(0, colon + 1)}${hider(count)()}${authority.slice(at)}` : `${scheme}//${authority}`;
4292
- }
4293
- out += hidePath(path16, count);
4294
- if (query) out += `?${hideParams(query.slice(1), count)}`;
4295
- if (fragment) {
4296
- const body = fragment.slice(1);
4297
- const ask = body.indexOf("?");
4298
- out += `#${ask >= 0 ? `${hidePath(body.slice(0, ask), count)}?${hideParams(body.slice(ask + 1), count)}` : body.includes("=") ? hideParams(body, count) : hidePath(body, count)}`;
4299
- }
4300
- return out;
4587
+ function reachedFunctionLines(entry, map, text) {
4588
+ return functionLines(entry, map, text, false);
4301
4589
  }
4302
- var QUERY_PAIR = /([?&;#])([^=&#?;\s"'<>]{1,256})=([^&;#\s"'<>]*)/g;
4303
- var URL_PASSWORD = /(\/\/[^/:@\s"'<>]{1,256}:)([^@/\s"'<>]{1,256})(@)/g;
4304
- function hidePem(text, hide) {
4305
- let out = "";
4306
- let last = 0;
4307
- let nextEnd = -2;
4308
- PEM_BEGIN.lastIndex = 0;
4309
- for (let begin = PEM_BEGIN.exec(text); begin; begin = PEM_BEGIN.exec(text)) {
4310
- if (begin.index < last) continue;
4311
- if (nextEnd !== -1 && nextEnd < begin.index) nextEnd = text.indexOf("-----END ", begin.index);
4312
- const footer = nextEnd >= 0 ? /^-----END [A-Z0-9 ]{0,40}-----/.exec(text.slice(nextEnd, nextEnd + 60)) : null;
4313
- const until = footer ? nextEnd + footer[0].length : begin.index + begin[0].length + (/^(?:\s*[A-Za-z0-9+/=]+)*/.exec(text.slice(begin.index + begin[0].length))?.[0].length ?? 0);
4314
- out += `${text.slice(last, begin.index)}${hide()}`;
4315
- last = until;
4316
- PEM_BEGIN.lastIndex = until;
4317
- }
4318
- return out + text.slice(last);
4590
+ function knownFunctionLines(entry, map, text) {
4591
+ return functionLines(entry, map, text, true);
4319
4592
  }
4320
- function hideInText(text, count) {
4321
- if (text.length < 4 || !/[A-Za-z0-9]/.test(text)) return text;
4322
- const hide = hider(count);
4323
- let out = hidePem(text, hide);
4324
- out = out.replace(QUERY_PAIR, (all, sep7, name, value) => {
4325
- const decoded = decodeSafe(value);
4326
- if (!value || decoded === HIDDEN_VALUE) return all;
4327
- return isSecretName(name) || isSecretValue(decoded) ? `${sep7}${name}=${hide()}` : all;
4328
- });
4329
- out = out.replace(URL_PASSWORD, (_all, before, _password, at) => `${before}${hide()}${at}`);
4330
- out = out.replace(SET_COOKIE, (_all, before) => `${before}${hide()}`);
4331
- out = out.replace(COOKIE_LINE, (_all, before, pairs) => {
4332
- const values = pairs.replace(
4333
- /([^=;\s]{1,256}=)([^;]*)/g,
4334
- (pair, name, value) => value.trim() && value.trim() !== HIDDEN_VALUE ? `${name}${hide()}` : pair
4335
- );
4336
- return `${before}${values}`;
4337
- });
4338
- out = out.replace(
4339
- JSON_FIELD,
4340
- (all, before, name, value) => isSecretName(name) && value !== HIDDEN_VALUE ? `${before}${hide()}"` : all
4341
- );
4342
- out = out.replace(BASIC, (all, encoded) => isBasicCredential(encoded) ? hide() : all);
4343
- for (const shape of SECRET_SHAPES) out = out.replace(shape, () => hide());
4344
- return out.replace(
4345
- TOKEN_RUN,
4346
- (run) => run.includes("/") && !/[+=]/.test(run) ? run.split("/").map((part) => looksRandom(part) ? hide() : part).join("/") : looksRandom(run) ? hide() : run
4347
- );
4593
+ function functionLines(entry, map, text, all) {
4594
+ const out = /* @__PURE__ */ new Map();
4595
+ const read = textOf(entry, text);
4596
+ const ranges = executedFunctionRanges(entry, read?.length, all);
4597
+ if (!ranges.length || !read) return out;
4598
+ const add2 = (source, range) => {
4599
+ const list = out.get(source);
4600
+ if (list) list.push(range);
4601
+ else out.set(source, [range]);
4602
+ };
4603
+ const starts = read.starts;
4604
+ for (const range of ranges) {
4605
+ const start = offsetToPosition(starts, range.start);
4606
+ if (!map) {
4607
+ const last = offsetToPosition(starts, Math.max(range.start, range.end - 1));
4608
+ add2(-1, [start.line + 1, last.line + 1]);
4609
+ continue;
4610
+ }
4611
+ const at = map.originalAt(start.line, start.column);
4612
+ if (at === null) continue;
4613
+ const end = offsetToPosition(starts, range.end);
4614
+ const first = map.firstOriginalLineBetween(at.source, start, end);
4615
+ if (first === null) continue;
4616
+ const header = first === at.line || first === at.line + 1 ? at.line : first;
4617
+ const lines = map.originalLinesBetween(at.source, start, end, header);
4618
+ if (lines) add2(at.source, [header + 1, lines[1] + 1]);
4619
+ }
4620
+ return out;
4348
4621
  }
4349
- function hideSecretsInText(text, count = { n: 0 }) {
4350
- let out = "";
4351
- let last = 0;
4352
- for (const url of text.matchAll(URL_IN_TEXT)) {
4353
- out += hideInText(text.slice(last, url.index), count) + hideSecretsInUrl(url[0], count);
4354
- last = url.index + url[0].length;
4622
+ function finalizeCodeReach(files) {
4623
+ const out = /* @__PURE__ */ new Set();
4624
+ for (const raw of files) {
4625
+ const file = normalizeCoveragePath(raw);
4626
+ if (file !== null) out.add(file);
4355
4627
  }
4356
- return out + hideInText(text.slice(last), count);
4628
+ return [...out].sort().slice(0, MAX_CODE_REACH_FILES);
4357
4629
  }
4358
4630
 
4359
4631
  // src/internal/capture/inspect-on-failure.ts
@@ -4442,9 +4714,9 @@ function unlessEnded(run, ended) {
4442
4714
  if (!ended) return run;
4443
4715
  return Promise.race([
4444
4716
  run,
4445
- new Promise((resolve5) => {
4446
- if (ended.aborted) resolve5(void 0);
4447
- else ended.addEventListener("abort", () => resolve5(void 0), { once: true });
4717
+ new Promise((resolve6) => {
4718
+ if (ended.aborted) resolve6(void 0);
4719
+ else ended.addEventListener("abort", () => resolve6(void 0), { once: true });
4448
4720
  })
4449
4721
  ]);
4450
4722
  }
@@ -4590,6 +4862,7 @@ function applyPickToSnapshots(snapshots, pick) {
4590
4862
  }
4591
4863
 
4592
4864
  // src/internal/capture/pause-at.ts
4865
+ var fs6 = __toESM(require("fs"));
4593
4866
  var path8 = __toESM(require("path"));
4594
4867
 
4595
4868
  // ../core/src/editor-send.ts
@@ -4709,10 +4982,23 @@ function internalCall(owner, run) {
4709
4982
  return wrap.call(owner, run, { internal: true, title: "Piwi capture" });
4710
4983
  }
4711
4984
 
4985
+ // src/internal/support/playwright-version.ts
4986
+ function playwrightVersionAtLeast(version, minimum) {
4987
+ const have = version ? /^(\d+)\.(\d+)/.exec(version) : null;
4988
+ const want = /^(\d+)\.(\d+)/.exec(minimum);
4989
+ if (!have || !want) return false;
4990
+ const major = Number(have[1]);
4991
+ const wantMajor = Number(want[1]);
4992
+ return major > wantMajor || major === wantMajor && Number(have[2]) >= Number(want[2]);
4993
+ }
4994
+
4712
4995
  // src/internal/capture/pause-at.ts
4996
+ function lineKey(line, windows = process.platform === "win32") {
4997
+ return windows ? line.toLowerCase() : line;
4998
+ }
4713
4999
  var PauseSet = class {
4714
5000
  constructor(lines) {
4715
- this.lines = lines;
5001
+ this.lines = new Set([...lines].map((line) => lineKey(line)));
4716
5002
  }
4717
5003
  get size() {
4718
5004
  return this.lines.size;
@@ -4721,18 +5007,27 @@ var PauseSet = class {
4721
5007
  matches(callerLocation) {
4722
5008
  if (!callerLocation) return false;
4723
5009
  const col = callerLocation.lastIndexOf(":");
4724
- return col > 0 && this.lines.has(callerLocation.slice(0, col));
5010
+ return col > 0 && this.lines.has(lineKey(callerLocation.slice(0, col)));
4725
5011
  }
4726
5012
  };
4727
5013
  var NO_BREAKPOINTS = new PauseSet(/* @__PURE__ */ new Set());
4728
5014
  function callSiteFile(file, cwd) {
4729
5015
  const slashed = file.replace(/\\/g, "/");
4730
5016
  const absolute = path8.isAbsolute(file) || path8.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
4731
- let rel = absolute ? path8.relative(cwd, file) : path8.normalize(slashed);
5017
+ const resolved = path8.resolve(cwd, absolute ? file : slashed);
5018
+ const real = realPath(resolved);
5019
+ let rel = real !== null && real !== resolved ? path8.relative(realPath(cwd) ?? cwd, real) : absolute ? path8.relative(cwd, file) : path8.normalize(slashed);
4732
5020
  rel = rel.split(path8.sep).join("/").replace(/\\/g, "/");
4733
5021
  if (rel.startsWith("./")) rel = rel.slice(2);
4734
5022
  return rel;
4735
5023
  }
5024
+ function realPath(file) {
5025
+ try {
5026
+ return fs6.realpathSync(file);
5027
+ } catch {
5028
+ return null;
5029
+ }
5030
+ }
4736
5031
  function stopCallSite(location, cwd = process.cwd()) {
4737
5032
  if (!location.line) return null;
4738
5033
  return `${callSiteFile(location.file, cwd)}:${location.line}:${location.column ?? 0}`;
@@ -4793,10 +5088,20 @@ function callSitePlace(callSite) {
4793
5088
  function placeLine(place, cwd = process.cwd()) {
4794
5089
  return `${callSiteFile(place.file, cwd)}:${place.line}`;
4795
5090
  }
4796
- function pauseGateFromTestInfo(testInfo) {
5091
+ function pauseGateFromTestInfo(testInfo, headless) {
4797
5092
  const use = testInfo.project?.use ?? {};
4798
5093
  const debug = testInfo._configInternal?.configCLIOverrides?.debug;
4799
- return { ci: process.env.CI, headless: use.headless, pwdebug: process.env.PWDEBUG, debug };
5094
+ return {
5095
+ ci: process.env.CI,
5096
+ headless: headless !== void 0 ? headless : use.headless,
5097
+ pwdebug: process.env.PWDEBUG,
5098
+ debug
5099
+ };
5100
+ }
5101
+ function mayPause(gate) {
5102
+ if (process.env.PIWI_PAUSE_AT) return true;
5103
+ if (gate.pwdebug && !["0", "false", "console"].includes(gate.pwdebug)) return true;
5104
+ return typeof gate.debug === "string" && gate.debug.length > 0;
4800
5105
  }
4801
5106
  function pauseSkipReason(gate) {
4802
5107
  if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
@@ -4819,7 +5124,7 @@ function atBreakpoint(state, callSite) {
4819
5124
  }
4820
5125
  function atStop(state, callSite) {
4821
5126
  if (callSite) {
4822
- const reason = state.step ? "step" : state.pause ? "pause" : state.runTo !== null && callSiteLine(callSite) === state.runTo ? "run-to" : atBreakpoint(state, callSite) ? "breakpoint" : null;
5127
+ const reason = state.step ? "step" : state.pause ? "pause" : state.runTo !== null && lineKey(callSiteLine(callSite)) === lineKey(state.runTo) ? "run-to" : atBreakpoint(state, callSite) ? "breakpoint" : null;
4823
5128
  if (reason) {
4824
5129
  stopShown(state);
4825
5130
  return { show: reason };
@@ -4850,6 +5155,9 @@ function goOn(state, answer) {
4850
5155
  return wantsStops(state) ? "next" : "resume";
4851
5156
  }
4852
5157
  var ENDED_REASON = "Ended from Piwi";
5158
+ function takesEndSignal(version) {
5159
+ return playwrightVersionAtLeast(version, "1.62");
5160
+ }
4853
5161
  var OPTIONS_AT = {
4854
5162
  fill: 1,
4855
5163
  selectOption: 1,
@@ -4907,11 +5215,23 @@ function syncInspectorSwitch() {
4907
5215
  if (closed && process.env[NO_INSPECTOR_ENV] === void 0) {
4908
5216
  process.env[NO_INSPECTOR_ENV] = "1";
4909
5217
  inspectorSwitch.set = true;
5218
+ clearSwitchAtStop();
4910
5219
  } else if (!closed && inspectorSwitch.set) {
4911
- if (process.env[NO_INSPECTOR_ENV] === "1") delete process.env[NO_INSPECTOR_ENV];
4912
- inspectorSwitch.set = false;
5220
+ clearSwitch();
4913
5221
  }
4914
5222
  }
5223
+ function clearSwitch() {
5224
+ if (inspectorSwitch.set && process.env[NO_INSPECTOR_ENV] === "1") delete process.env[NO_INSPECTOR_ENV];
5225
+ inspectorSwitch.set = false;
5226
+ }
5227
+ var clearsAtStop = false;
5228
+ function clearSwitchAtStop() {
5229
+ if (clearsAtStop) return;
5230
+ clearsAtStop = true;
5231
+ process.prependListener("message", (message) => {
5232
+ if (message?.method === "__stop__") clearSwitch();
5233
+ });
5234
+ }
4915
5235
  function markPausesWrapped() {
4916
5236
  inspectorSwitch.wrapped = true;
4917
5237
  syncInspectorSwitch();
@@ -5019,9 +5339,9 @@ var BarIntents = class {
5019
5339
  /** Hand the bar an intent; false while it runs one, or holds one already. */
5020
5340
  offer(intent) {
5021
5341
  if (this.busy || this.queued) return false;
5022
- const take = this.take;
5342
+ const take2 = this.take;
5023
5343
  this.take = null;
5024
- if (take) take(intent);
5344
+ if (take2) take2(intent);
5025
5345
  else this.queued = intent;
5026
5346
  return true;
5027
5347
  }
@@ -5031,8 +5351,8 @@ var BarIntents = class {
5031
5351
  const queued = this.queued;
5032
5352
  this.queued = null;
5033
5353
  if (queued) return Promise.resolve(queued);
5034
- return new Promise((resolve5) => {
5035
- this.take = resolve5;
5354
+ return new Promise((resolve6) => {
5355
+ this.take = resolve6;
5036
5356
  });
5037
5357
  }
5038
5358
  /** The bar got its answer: it takes no intent until it waits again. */
@@ -5047,7 +5367,7 @@ async function within(run, ms) {
5047
5367
  try {
5048
5368
  return await Promise.race([
5049
5369
  run,
5050
- new Promise((resolve5) => timer = setTimeout(() => resolve5(void 0), ms))
5370
+ new Promise((resolve6) => timer = setTimeout(() => resolve6(void 0), ms))
5051
5371
  ]);
5052
5372
  } finally {
5053
5373
  clearTimeout(timer);
@@ -5080,13 +5400,68 @@ async function viewHere(page, probe, onPick, onRecord, ended, intents, onIntent,
5080
5400
  }
5081
5401
  }
5082
5402
  }
5403
+ var REDRAW_MS = 500;
5404
+ var stopHighlights = 0;
5405
+ var FRAME_HOP = " >> internal:control=enter-frame >> ";
5406
+ async function frameOf(locator) {
5407
+ const own = locator;
5408
+ if (!own._frame || typeof own._selector !== "string") return null;
5409
+ const at = own._selector.lastIndexOf(FRAME_HOP);
5410
+ if (at < 0) return { frame: own._frame, selector: own._selector };
5411
+ const [iframe] = await own._frame.locator(own._selector.slice(0, at)).first().elementHandles();
5412
+ if (!iframe) return null;
5413
+ try {
5414
+ const frame = await iframe.contentFrame();
5415
+ return frame ? { frame, selector: own._selector.slice(at + FRAME_HOP.length) } : null;
5416
+ } finally {
5417
+ await iframe.dispose().catch(() => void 0);
5418
+ }
5419
+ }
5420
+ function highlightAtStop(page, locator, what, label) {
5421
+ const id = `${what}-${++stopHighlights}`;
5422
+ const arg = { id, kind: what, label };
5423
+ const drawnIn = /* @__PURE__ */ new Set();
5424
+ let hidden2 = false;
5425
+ let timer;
5426
+ const draw = async () => {
5427
+ const target = await frameOf(locator);
5428
+ if (hidden2) return 0;
5429
+ if (!target) return locator.evaluateAll(showStopHighlight, arg);
5430
+ drawnIn.add(target.frame);
5431
+ return target.frame.locator(target.selector).evaluateAll(showStopHighlight, arg);
5432
+ };
5433
+ const again = () => {
5434
+ if (hidden2 || page.isClosed?.()) return;
5435
+ timer = setTimeout(
5436
+ () => void internalCall(page, draw).catch(() => 0).then(again),
5437
+ REDRAW_MS
5438
+ );
5439
+ timer.unref?.();
5440
+ };
5441
+ const first = internalCall(page, draw).catch(() => 0);
5442
+ void first.then(again);
5443
+ const removeIn = (frame) => internalCall(page, async () => frame.evaluate(removeStopHighlight, id)).catch(() => false);
5444
+ return {
5445
+ shown: first.then((boxes) => boxes > 0),
5446
+ hide: async () => {
5447
+ hidden2 = true;
5448
+ clearTimeout(timer);
5449
+ try {
5450
+ const frames = drawnIn.size ? [...drawnIn] : [page.mainFrame()];
5451
+ const removed = await within(Promise.all(frames.map(removeIn)), TAKE_DOWN_MS);
5452
+ return removed?.includes(true) ?? false;
5453
+ } catch {
5454
+ return false;
5455
+ }
5456
+ }
5457
+ };
5458
+ }
5083
5459
  async function pauseHere(page, point, probe, onPick, onRecord, ended, intents, onIntent) {
5084
- let shown2 = null;
5460
+ let highlight = null;
5085
5461
  try {
5086
- const highlight = point.target?.highlight;
5087
- if (typeof highlight === "function") {
5088
- const highlighted = Promise.resolve().then(() => highlight.call(point.target)).catch(() => null);
5089
- shown2 = await within(highlighted, HIGHLIGHT_MS) ?? null;
5462
+ if (point.target) {
5463
+ highlight = point.target.highlight();
5464
+ await within(highlight.shown, HIGHLIGHT_MS);
5090
5465
  }
5091
5466
  const arg = {
5092
5467
  place: pausePlace(point.location),
@@ -5145,17 +5520,10 @@ async function pauseHere(page, point, probe, onPick, onRecord, ended, intents, o
5145
5520
  } catch {
5146
5521
  return "resume";
5147
5522
  } finally {
5148
- const takeDown = async () => {
5149
- try {
5150
- if (ended?.aborted) await page.evaluate(takePauseState);
5151
- const dispose = shown2?.dispose;
5152
- const hide = page.hideHighlight;
5153
- if (typeof dispose === "function") await dispose.call(shown2);
5154
- else if (typeof hide === "function") await hide.call(page);
5155
- } catch {
5156
- }
5157
- };
5158
- await within(takeDown(), TAKE_DOWN_MS);
5523
+ await Promise.all([
5524
+ ended?.aborted ? within(page.evaluate(takePauseState), TAKE_DOWN_MS).catch(() => void 0) : void 0,
5525
+ highlight?.hide()
5526
+ ]);
5159
5527
  }
5160
5528
  }
5161
5529
 
@@ -5201,6 +5569,12 @@ function locatorOn(page, source) {
5201
5569
  }
5202
5570
  }
5203
5571
 
5572
+ // src/internal/capture/aria-ref.ts
5573
+ function locatorOfRef(page, ref) {
5574
+ const getByRef = page.getByRef;
5575
+ return typeof getByRef === "function" ? getByRef.call(page, ref) : page.locator(`aria-ref=${ref}`);
5576
+ }
5577
+
5204
5578
  // src/internal/capture/pick-at.ts
5205
5579
  var LINE = /^(\s*)- ([a-z][\w-]*)(?: "(?:[^"\\]|\\.)*")?((?: \[[^\]\n]*\])*)(?::.*)?$/;
5206
5580
  var REF = /\[ref=(e\d+)\]/;
@@ -5240,18 +5614,16 @@ var PIWI_OVERLAY = '[id^="__piwi"], [id^="piwi-"][id$="-host"]';
5240
5614
  var MAX_UNDER = 24;
5241
5615
  var MAX_CHECKED = 8;
5242
5616
  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.";
5617
+ var PICK_AT_SINCE = "1.60";
5618
+ var PICK_AT_NEEDS = "Pick on the image needs Playwright 1.60 or later (the boxes of page.ariaSnapshot): update @playwright/test.";
5244
5619
  var NO_ELEMENT = "No element there: pick an element of the page, not its background.";
5245
5620
  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
5621
  var hitIn = ({ x, y, overlay, elements }) => {
5250
5622
  let scope = globalThis.document;
5251
5623
  let hit = null;
5252
5624
  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));
5625
+ const within4 = scope;
5626
+ const top = within4.elementsFromPoint(x, y).find((el) => el.getRootNode?.() === within4 && !el.closest?.(overlay));
5255
5627
  if (!top) break;
5256
5628
  hit = top;
5257
5629
  scope = top.shadowRoot ?? null;
@@ -5265,14 +5637,12 @@ var hitIn = ({ x, y, overlay, elements }) => {
5265
5637
  return -1;
5266
5638
  };
5267
5639
  async function pickAtPoint(page, probe, x, y) {
5268
- if (!canPickAt(page)) return { refusal: PICK_AT_NEEDS };
5269
5640
  const snapshot = await page.ariaSnapshot({ mode: "ai", boxes: true, timeout: 5e3 });
5270
5641
  if (!/\[box=/.test(snapshot)) return { refusal: PICK_AT_NEEDS };
5271
5642
  const under = boxesAt(snapshotBoxes(snapshot), x, y).slice(0, MAX_UNDER);
5272
- const byRef2 = (ref) => page.getByRef(ref);
5273
5643
  const handles = await Promise.all(
5274
5644
  under.map(
5275
- (box) => byRef2(box.ref).elementHandle({ timeout: 2e3 }).catch(() => null)
5645
+ (box) => locatorOfRef(page, box.ref).elementHandle({ timeout: 2e3 }).catch(() => null)
5276
5646
  )
5277
5647
  );
5278
5648
  try {
@@ -5289,7 +5659,7 @@ async function pickAtPoint(page, probe, x, y) {
5289
5659
  (el, overlay) => !!el.closest(overlay),
5290
5660
  PIWI_OVERLAY
5291
5661
  );
5292
- if (!piwi) return await rank(page, probe, byRef2(box.ref), handle);
5662
+ if (!piwi) return await rank(page, probe, locatorOfRef(page, box.ref), handle);
5293
5663
  }
5294
5664
  return { refusal: NO_ELEMENT };
5295
5665
  } finally {
@@ -5655,10 +6025,10 @@ var FrameAcks = class {
5655
6025
  const now = this.now();
5656
6026
  this.due = Math.min(Math.max(now, this.due + this.intervalMs), now + this.intervalMs);
5657
6027
  if (this.due <= now) return void 0;
5658
- return new Promise((resolve5) => {
6028
+ return new Promise((resolve6) => {
5659
6029
  const timer = setTimeout(() => this.release(), this.due - now);
5660
6030
  timer.unref?.();
5661
- this.waiting = { timer, resolve: resolve5 };
6031
+ this.waiting = { timer, resolve: resolve6 };
5662
6032
  });
5663
6033
  }
5664
6034
  /** Whether a frame waits for its acknowledgment. */
@@ -5682,6 +6052,7 @@ var FrameAcks = class {
5682
6052
  // ../core/src/run-channel.ts
5683
6053
  var RUN_CHANNEL_ENV = "PIWI_RUN_CHANNEL";
5684
6054
  var RUN_REF_ENV = "PIWI_RUN_REF";
6055
+ var TERMINAL_REF_PREFIX = "terminal-";
5685
6056
  var RUN_HOLDS = ["failure"];
5686
6057
  var RUN_INTENTS = ["pick", "record", "assert", "assert-region"];
5687
6058
  var RUN_TAKES = [
@@ -5822,65 +6193,394 @@ function parseHubToRun(line) {
5822
6193
  const act = readAct(value);
5823
6194
  return act ? { type: "request", request, what, stop, ...act } : null;
5824
6195
  }
5825
- if (what === "ref-locator") {
5826
- return isSnapshotRef(value.ref) ? { type: "request", request, what, stop, ref: value.ref } : null;
6196
+ if (what === "ref-locator") {
6197
+ return isSnapshotRef(value.ref) ? { type: "request", request, what, stop, ref: value.ref } : null;
6198
+ }
6199
+ if (what === "bind") {
6200
+ return typeof value.on === "boolean" ? { type: "request", request, what, stop, on: value.on } : null;
6201
+ }
6202
+ const { locator } = value;
6203
+ return locator === null || isLocatorText(locator) ? { type: "request", request, what: "locator", stop, locator } : null;
6204
+ }
6205
+ case "events":
6206
+ return typeof value.on === "boolean" ? { type: "events", on: value.on } : null;
6207
+ case "share":
6208
+ return typeof value.on === "boolean" ? { type: "share", on: value.on } : null;
6209
+ default:
6210
+ return typeof value.type === "string" && value.type ? unknown(value.type) : null;
6211
+ }
6212
+ }
6213
+ function readAct(value) {
6214
+ if (!isObject3(value) || !isSnapshotRef(value.ref)) return null;
6215
+ const action = value.action;
6216
+ if (typeof action !== "string" || !RUN_ACT_ACTIONS.includes(action)) return null;
6217
+ const given = value.value;
6218
+ switch (action) {
6219
+ case "fill":
6220
+ return typeof given === "string" && given.length <= MAX_ACT_VALUE ? { ref: value.ref, action: "fill", value: given } : null;
6221
+ case "press":
6222
+ return isViewKeyCombo(given) ? { ref: value.ref, action: "press", value: given } : null;
6223
+ case "select":
6224
+ return typeof given === "string" && given.length <= MAX_ACT_VALUE && !/[\r\n\0]/.test(given) ? { ref: value.ref, action: "select", value: given } : null;
6225
+ default:
6226
+ return given === null || given === void 0 ? { ref: value.ref, action, value: null } : null;
6227
+ }
6228
+ }
6229
+
6230
+ // src/internal/capture/live-mask.ts
6231
+ var import_node_module2 = require("module");
6232
+ var MASKED_SPACING_MS = 500;
6233
+ var FRESH_MS = 1e3;
6234
+ var READS_KEPT = 4;
6235
+ var MAX_FRAMES = 32;
6236
+ var PAD = 2;
6237
+ var SAME_WITHIN = 1;
6238
+ var FRAME_READ_MS = 1e3;
6239
+ var MAX_FAILED_READS = 3;
6240
+ var MASK_RGB = [96, 96, 96];
6241
+ var TOO_MANY_FRAMES = `This page has more than ${MAX_FRAMES} frames: the live view cannot check them all for secret fields, so no image of it goes.`;
6242
+ var NO_CODEC = "A secret field shows on this page, and this Playwright gives the reporter nothing to mask it with: no image goes while it shows.";
6243
+ var UNREAD = "The live view cannot read where the secret fields of this page are, so no image of it goes until it can.";
6244
+ var ZOOMED = "This page is zoomed in: no image of it goes until it is not, its secret fields shown larger than read.";
6245
+ var UNREADABLE_MASKS = "The project masks elements this reporter cannot read: no image goes. Update @piwitests/reporter.";
6246
+ var codec;
6247
+ function jpegCodec() {
6248
+ if (codec !== void 0) return codec;
6249
+ codec = null;
6250
+ try {
6251
+ const playwright = resolvePlaywrightPackageJson();
6252
+ const bundle = playwright ? (0, import_node_module2.createRequire)(playwright)("playwright-core/lib/utilsBundle") : null;
6253
+ const found = bundle?.jpegjs;
6254
+ if (typeof found?.decode === "function" && typeof found.encode === "function") codec = found;
6255
+ } catch {
6256
+ }
6257
+ return codec;
6258
+ }
6259
+ function maskedIn(frame, masks) {
6260
+ let locator = frame.locator(SECRET_FIELD_SELECTOR);
6261
+ for (const source of masks) {
6262
+ const own = locatorOn(frame, source);
6263
+ if (!own) throw new Error(`The locator ${source} cannot be built.`);
6264
+ locator = locator.or(own);
6265
+ }
6266
+ return locator;
6267
+ }
6268
+ var unbuilt = (source) => `The project masks ${source}, which this Playwright cannot find elements by: no image of the page goes.`;
6269
+ var near = (a, b) => Math.abs(a - b) <= SAME_WITHIN;
6270
+ function readFrameRects(page, frame, masks) {
6271
+ return internalCall(
6272
+ page,
6273
+ () => maskedIn(frame, masks).evaluateAll((elements) => {
6274
+ const view3 = globalThis;
6275
+ const visual = view3.visualViewport;
6276
+ const layout = view3.document.scrollingElement;
6277
+ const zoomed = !!visual && (Math.abs(visual.offsetLeft) > 0.5 || Math.abs(visual.offsetTop) > 0.5 || (layout ? visual.width < layout.clientWidth - 1 || visual.height < layout.clientHeight - 1 : visual.scale > 1.001));
6278
+ return {
6279
+ rects: elements.map((element) => {
6280
+ const rect = element.getBoundingClientRect();
6281
+ return [rect.x, rect.y, rect.width, rect.height];
6282
+ }).filter(([, , width, height]) => width > 0 && height > 0),
6283
+ width: view3.innerWidth,
6284
+ height: view3.innerHeight,
6285
+ zoomed
6286
+ };
6287
+ })
6288
+ );
6289
+ }
6290
+ async function childOrigin(page, frame, inner) {
6291
+ return internalCall(page, async () => {
6292
+ const element = await frame.frameElement();
6293
+ try {
6294
+ const outer = await element.boundingBox();
6295
+ if (!outer) return null;
6296
+ const own = await element.evaluate((iframe) => {
6297
+ const el = iframe;
6298
+ const style = globalThis.getComputedStyle(iframe);
6299
+ const pad = (side) => parseFloat(side) || 0;
6300
+ return {
6301
+ left: el.clientLeft + pad(style.paddingLeft),
6302
+ top: el.clientTop + pad(style.paddingTop),
6303
+ width: el.clientWidth - pad(style.paddingLeft) - pad(style.paddingRight),
6304
+ height: el.clientHeight - pad(style.paddingTop) - pad(style.paddingBottom),
6305
+ offsetWidth: el.offsetWidth,
6306
+ offsetHeight: el.offsetHeight
6307
+ };
6308
+ });
6309
+ const drawnAsLaidOut = near(outer.width, own.offsetWidth) && near(outer.height, own.offsetHeight);
6310
+ const contentIsViewport = near(own.width, inner.width) && near(own.height, inner.height);
6311
+ if (!drawnAsLaidOut || !contentIsViewport) return { whole: outer };
6312
+ return { x: outer.x + own.left, y: outer.y + own.top };
6313
+ } finally {
6314
+ await element.dispose();
6315
+ }
6316
+ });
6317
+ }
6318
+ async function within2(run, ms) {
6319
+ let timer;
6320
+ try {
6321
+ return await Promise.race([
6322
+ run,
6323
+ new Promise((resolve6) => timer = setTimeout(() => resolve6(void 0), ms))
6324
+ ]);
6325
+ } finally {
6326
+ clearTimeout(timer);
6327
+ }
6328
+ }
6329
+ async function wholeFrame(page, frame) {
6330
+ const box = await within2(
6331
+ internalCall(page, async () => {
6332
+ const element = await frame.frameElement();
6333
+ try {
6334
+ return await element.boundingBox();
6335
+ } finally {
6336
+ await element.dispose();
6337
+ }
6338
+ }),
6339
+ FRAME_READ_MS
6340
+ ).catch(() => void 0);
6341
+ if (box === void 0) return null;
6342
+ return { rects: box ? [[box.x, box.y, box.width, box.height]] : [], width: 0, height: 0 };
6343
+ }
6344
+ async function readFrame(page, frame, masks) {
6345
+ try {
6346
+ const read = await within2(readFrameRects(page, frame, masks), FRAME_READ_MS);
6347
+ if (!read) return frame === page.mainFrame() ? null : await wholeFrame(page, frame);
6348
+ if (!read.rects.length || frame === page.mainFrame()) return read;
6349
+ const origin = await childOrigin(page, frame, read);
6350
+ if (!origin) return { ...read, rects: [] };
6351
+ if ("whole" in origin) {
6352
+ const { x, y, width, height } = origin.whole;
6353
+ return { ...read, rects: [[x, y, width, height]] };
6354
+ }
6355
+ return {
6356
+ ...read,
6357
+ rects: read.rects.map(([left, top, width, height]) => [origin.x + left, origin.y + top, width, height])
6358
+ };
6359
+ } catch {
6360
+ return frame.isDetached() ? { rects: [], width: 0, height: 0 } : null;
6361
+ }
6362
+ }
6363
+ async function readBoxes(page, masks) {
6364
+ if (!masks) return UNREADABLE_MASKS;
6365
+ const frames = page.frames();
6366
+ if (frames.length > MAX_FRAMES) return TOO_MANY_FRAMES;
6367
+ for (const source of masks) if (!locatorOn(page.mainFrame(), source)) return unbuilt(source);
6368
+ const main = page.mainFrame();
6369
+ const read = await Promise.all(frames.map((frame) => readFrame(page, frame, masks)));
6370
+ if (read.some((frame) => frame === null)) return null;
6371
+ const top = read[frames.indexOf(main)];
6372
+ if (!top) return null;
6373
+ if (top.zoomed) return ZOOMED;
6374
+ return {
6375
+ boxes: read.flatMap(
6376
+ (frame) => frame.rects.map(([x, y, width, height]) => ({ x, y, width, height }))
6377
+ ),
6378
+ viewport: { width: top.width, height: top.height }
6379
+ };
6380
+ }
6381
+ function sameBoxes(a, b) {
6382
+ return a.length === b.length && a.every((box, i) => {
6383
+ const other = b[i];
6384
+ return Math.abs(box.x - other.x) <= SAME_WITHIN && Math.abs(box.y - other.y) <= SAME_WITHIN && Math.abs(box.width - other.width) <= SAME_WITHIN && Math.abs(box.height - other.height) <= SAME_WITHIN;
6385
+ });
6386
+ }
6387
+ function jpegSize(data) {
6388
+ for (let i = 2; i + 9 < data.length; ) {
6389
+ if (data[i] !== 255) return null;
6390
+ const marker = data[i + 1];
6391
+ if (marker >= 192 && marker <= 195) return { height: data.readUInt16BE(i + 5), width: data.readUInt16BE(i + 7) };
6392
+ i += 2 + data.readUInt16BE(i + 2);
6393
+ }
6394
+ return null;
6395
+ }
6396
+ function shrink(image, width, height) {
6397
+ const data = new Uint8Array(width * height * 4);
6398
+ const sx = image.width / width;
6399
+ const sy = image.height / height;
6400
+ for (let y = 0; y < height; y++) {
6401
+ const y0 = Math.floor(y * sy);
6402
+ const y1 = Math.max(y0 + 1, Math.min(image.height, Math.floor((y + 1) * sy)));
6403
+ for (let x = 0; x < width; x++) {
6404
+ const x0 = Math.floor(x * sx);
6405
+ const x1 = Math.max(x0 + 1, Math.min(image.width, Math.floor((x + 1) * sx)));
6406
+ let r = 0;
6407
+ let g = 0;
6408
+ let b = 0;
6409
+ for (let yy = y0; yy < y1; yy++) {
6410
+ for (let xx = x0; xx < x1; xx++) {
6411
+ const i = (yy * image.width + xx) * 4;
6412
+ r += image.data[i];
6413
+ g += image.data[i + 1];
6414
+ b += image.data[i + 2];
6415
+ }
5827
6416
  }
5828
- if (what === "bind") {
5829
- return typeof value.on === "boolean" ? { type: "request", request, what, stop, on: value.on } : null;
6417
+ const n = (y1 - y0) * (x1 - x0);
6418
+ const o = (y * width + x) * 4;
6419
+ data[o] = r / n;
6420
+ data[o + 1] = g / n;
6421
+ data[o + 2] = b / n;
6422
+ data[o + 3] = 255;
6423
+ }
6424
+ }
6425
+ return { width, height, data };
6426
+ }
6427
+ function paintBoxes(jpeg, frame, boxes, size, quality) {
6428
+ const image = jpeg.decode(frame.data, { useTArray: true, formatAsRGBA: true });
6429
+ const sx = image.width / frame.width;
6430
+ const sy = image.height / frame.height;
6431
+ for (const box of boxes) {
6432
+ const x0 = Math.max(0, Math.floor((box.x - PAD) * sx));
6433
+ const y0 = Math.max(0, Math.floor((box.y - PAD) * sy));
6434
+ const x1 = Math.min(image.width, Math.ceil((box.x + box.width + PAD) * sx));
6435
+ const y1 = Math.min(image.height, Math.ceil((box.y + box.height + PAD) * sy));
6436
+ for (let y = y0; y < y1; y++) {
6437
+ for (let x = x0; x < x1; x++) {
6438
+ const i = (y * image.width + x) * 4;
6439
+ image.data[i] = MASK_RGB[0];
6440
+ image.data[i + 1] = MASK_RGB[1];
6441
+ image.data[i + 2] = MASK_RGB[2];
6442
+ image.data[i + 3] = 255;
5830
6443
  }
5831
- const { locator } = value;
5832
- return locator === null || isLocatorText(locator) ? { type: "request", request, what: "locator", stop, locator } : null;
5833
6444
  }
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;
5838
- default:
5839
- return typeof value.type === "string" && value.type ? unknown(value.type) : null;
5840
6445
  }
6446
+ const fits = image.width <= size.width && image.height <= size.height;
6447
+ return jpeg.encode(fits ? image : shrink(image, size.width, size.height), quality).data;
5841
6448
  }
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;
6449
+ async function measureFrame(page, frame) {
6450
+ try {
6451
+ const measured = await within2(
6452
+ internalCall(page, async () => {
6453
+ const fields = await frame.locator(SECRET_FIELD_SELECTOR).elementHandles();
6454
+ try {
6455
+ return await Promise.all(fields.map((field) => field.boundingBox()));
6456
+ } finally {
6457
+ await Promise.all(fields.map((field) => field.dispose().catch(() => void 0)));
6458
+ }
6459
+ }),
6460
+ FRAME_READ_MS
6461
+ );
6462
+ if (measured) return measured.filter((box) => !!box && box.width > 0 && box.height > 0);
6463
+ const whole = frame === page.mainFrame() ? null : await wholeFrame(page, frame);
6464
+ return whole && whole.rects.map(([x, y, width, height]) => ({ x, y, width, height }));
6465
+ } catch {
6466
+ return frame.isDetached() ? [] : null;
6467
+ }
6468
+ }
6469
+ async function secretBoxes(page) {
6470
+ const [read, measured] = await Promise.all([
6471
+ readBoxes(page, []),
6472
+ Promise.all(
6473
+ page.frames().slice(0, MAX_FRAMES).map((frame) => measureFrame(page, frame))
6474
+ )
6475
+ ]);
6476
+ if (!read || typeof read === "string" || measured.some((boxes) => !boxes)) return null;
6477
+ return { ...read, boxes: [...read.boxes, ...measured.flat()] };
6478
+ }
6479
+ async function maskedImage(page, options) {
6480
+ const before = await secretBoxes(page);
6481
+ if (!before) return null;
6482
+ const shot = await internalCall(
6483
+ page,
6484
+ () => page.screenshot({
6485
+ type: "jpeg",
6486
+ quality: options.quality,
6487
+ timeout: options.timeout,
6488
+ ...options.style ? { style: options.style } : {}
6489
+ })
6490
+ ).catch(() => null);
6491
+ if (!shot) return null;
6492
+ const after = await secretBoxes(page);
6493
+ if (!after || !sameBoxes(before.boxes, after.boxes)) return null;
6494
+ const { width, height } = after.viewport;
6495
+ if (!near(width, before.viewport.width) || !near(height, before.viewport.height)) return null;
6496
+ if (!after.boxes.length) return shot;
6497
+ const jpeg = jpegCodec();
6498
+ const size = jpegSize(shot);
6499
+ if (!jpeg || !size) return null;
6500
+ try {
6501
+ return paintBoxes(jpeg, { data: shot, width, height, at: Date.now() }, after.boxes, size, options.quality);
6502
+ } catch {
6503
+ return null;
5856
6504
  }
5857
6505
  }
6506
+ function maskSecretFields(page, options) {
6507
+ const reads = [];
6508
+ let maskedAt = -Infinity;
6509
+ let failed2 = 0;
6510
+ const read = async () => {
6511
+ const began = Date.now();
6512
+ const found = await readBoxes(page, options.masks());
6513
+ if (typeof found === "string") return found;
6514
+ if (!found) return ++failed2 >= MAX_FAILED_READS ? UNREAD : null;
6515
+ failed2 = 0;
6516
+ const done = { began, ended: Date.now(), ...found };
6517
+ reads.push(done);
6518
+ if (reads.length > READS_KEPT) reads.shift();
6519
+ return done;
6520
+ };
6521
+ const showsViewportOf = (frame, read2) => near(frame.width, read2.viewport.width) && near(frame.height, read2.viewport.height);
6522
+ const shown2 = (frame, boxes, size) => {
6523
+ const own = jpegSize(frame.data);
6524
+ const fits = own === null || own.width <= size.width && own.height <= size.height;
6525
+ const jpeg = jpegCodec();
6526
+ if (!boxes.length && (fits || !jpeg)) return frame.data.length <= options.maxBytes ? frame : null;
6527
+ if (!jpeg) return NO_CODEC;
6528
+ let data;
6529
+ try {
6530
+ data = paintBoxes(jpeg, frame, boxes, size, options.quality);
6531
+ } catch {
6532
+ return null;
6533
+ }
6534
+ if (!boxes.length) return data.length <= options.maxBytes ? { ...frame, data } : null;
6535
+ if (data.length > options.maxBytes) return null;
6536
+ maskedAt = Date.now();
6537
+ return { ...frame, data };
6538
+ };
6539
+ return {
6540
+ get resting() {
6541
+ return Date.now() - maskedAt < MASKED_SPACING_MS;
6542
+ },
6543
+ async screen(frame) {
6544
+ const before = [...reads].reverse().find((r) => r.ended <= frame.at && r.ended >= frame.at - FRESH_MS) ?? null;
6545
+ const after = await read();
6546
+ if (typeof after === "string" || !after) return after;
6547
+ const size = jpegSize(frame.data) ?? frame;
6548
+ if (before && sameBoxes(before.boxes, after.boxes) && showsViewportOf(frame, before) && showsViewportOf(frame, after)) {
6549
+ return shown2(frame, after.boxes, size);
6550
+ }
6551
+ if (page.isClosed()) return null;
6552
+ const at = Date.now();
6553
+ const shot = await internalCall(
6554
+ page,
6555
+ () => page.screenshot({ type: "jpeg", quality: options.quality, scale: "css", caret: "initial", timeout: 5e3 })
6556
+ ).catch(() => null);
6557
+ if (!shot) return null;
6558
+ const again = await read();
6559
+ if (typeof again === "string" || !again) return again;
6560
+ if (!sameBoxes(after.boxes, again.boxes)) return null;
6561
+ const { width, height } = again.viewport;
6562
+ if (!near(width, after.viewport.width) || !near(height, after.viewport.height)) return null;
6563
+ return shown2(
6564
+ { data: shot, width, height, at },
6565
+ again.boxes,
6566
+ fitViewSize({ width, height }, size.width, size.height)
6567
+ );
6568
+ }
6569
+ };
6570
+ }
5858
6571
 
5859
6572
  // src/internal/capture/agent-page.ts
5860
6573
  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(", ");
6574
+ var SECRET_FIELDS = SECRET_FIELD_SELECTOR;
5868
6575
  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
6576
  function failed(prefix, error) {
5877
6577
  const first = errorMessage(error).split("\n")[0].trim();
5878
6578
  return hideSecretsInText(`${prefix}: ${first}`).slice(0, 300);
5879
6579
  }
5880
- var MAX_FRAMES = 32;
6580
+ var MAX_FRAMES2 = 32;
5881
6581
  async function fieldSecrets(page) {
5882
6582
  const values = await Promise.all(
5883
- page.frames().slice(0, MAX_FRAMES).map(
6583
+ page.frames().slice(0, MAX_FRAMES2).map(
5884
6584
  (frame) => frame.locator(SECRET_FIELDS).evaluateAll((fields) => fields.map((field) => field.value)).catch(() => [])
5885
6585
  )
5886
6586
  );
@@ -5934,13 +6634,7 @@ async function readSnapshot(page, depth, image) {
5934
6634
  const cut = hidden2.length > MAX_AGENT_SNAPSHOT;
5935
6635
  let shot = null;
5936
6636
  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);
6637
+ const data = await maskedImage(page, { quality: 60, style: BAR_HIDDEN, timeout: ACT_MS });
5944
6638
  const viewport = page.viewportSize();
5945
6639
  if (data) shot = { mime: "image/jpeg", data: data.toString("base64"), ...viewport };
5946
6640
  }
@@ -6009,14 +6703,13 @@ async function ranked(page, probe, locator, handle, testIdAttribute) {
6009
6703
  }
6010
6704
  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
6705
  async function handleOf(page, ref) {
6012
- return byRef(page, ref).elementHandle({ timeout: 2e3 }).catch(() => null);
6706
+ return locatorOfRef(page, ref).elementHandle({ timeout: 2e3 }).catch(() => null);
6013
6707
  }
6014
6708
  async function refLocators(page, probe, ref, testIdAttribute) {
6015
- if (!canActByRef(page)) return { ok: false, message: AGENT_NEEDS, element: null, locators: [] };
6016
6709
  const handle = await handleOf(page, ref);
6017
6710
  if (!handle) return { ok: false, message: NO_REF(ref), element: null, locators: [] };
6018
6711
  try {
6019
- const { element, locators } = await ranked(page, probe, byRef(page, ref), handle, testIdAttribute);
6712
+ const { element, locators } = await ranked(page, probe, locatorOfRef(page, ref), handle, testIdAttribute);
6020
6713
  const alone = locators.filter((l) => l.unique).length;
6021
6714
  return {
6022
6715
  ok: true,
@@ -6060,12 +6753,11 @@ var PLAYED = {
6060
6753
  select: "Selected an option of"
6061
6754
  };
6062
6755
  async function actOnRef(page, probe, act, testIdAttribute) {
6063
- if (!canActByRef(page)) return { ok: false, message: AGENT_NEEDS, element: null, locators: [] };
6064
6756
  const handle = await handleOf(page, act.ref);
6065
6757
  if (!handle) return { ok: false, message: NO_REF(act.ref), element: null, locators: [] };
6066
6758
  let before;
6067
6759
  try {
6068
- before = await ranked(page, probe, byRef(page, act.ref), handle, testIdAttribute);
6760
+ before = await ranked(page, probe, locatorOfRef(page, act.ref), handle, testIdAttribute);
6069
6761
  } catch (error) {
6070
6762
  return { ok: false, message: failed("The element could not be read", error), element: null, locators: [] };
6071
6763
  } finally {
@@ -6073,7 +6765,7 @@ async function actOnRef(page, probe, act, testIdAttribute) {
6073
6765
  }
6074
6766
  try {
6075
6767
  await page.evaluate(barLetsThrough, true).catch(() => void 0);
6076
- await play(byRef(page, act.ref), act.action, act.value);
6768
+ await play(locatorOfRef(page, act.ref), act.action, act.value);
6077
6769
  } catch (error) {
6078
6770
  return { ok: false, message: failed(`${PLAYED[act.action]} ${act.ref} failed`, error), ...before };
6079
6771
  } finally {
@@ -6117,7 +6809,7 @@ async function bindBrowser(browser, name, workspaceDir, on) {
6117
6809
  }
6118
6810
 
6119
6811
  // src/internal/capture/run-events.ts
6120
- var path9 = __toESM(require("path"));
6812
+ var path11 = __toESM(require("path"));
6121
6813
 
6122
6814
  // src/internal/capture/run-channel.ts
6123
6815
  var net = __toESM(require("net"));
@@ -6132,8 +6824,8 @@ function parseRecordChannel(value) {
6132
6824
  const hash = text.lastIndexOf("#");
6133
6825
  if (hash <= 0) return null;
6134
6826
  const token = text.slice(hash + 1);
6135
- const path16 = text.slice(0, hash);
6136
- return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path16.includes("\0") ? { path: path16, token } : null;
6827
+ const path18 = text.slice(0, hash);
6828
+ return /^[A-Za-z0-9_-]{16,}$/.test(token) && !path18.includes("\0") ? { path: path18, token } : null;
6137
6829
  }
6138
6830
  function isObject4(value) {
6139
6831
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -6177,6 +6869,16 @@ function lineReader(onLine, onOverflow) {
6177
6869
  };
6178
6870
  }
6179
6871
 
6872
+ // src/internal/config/editors.ts
6873
+ var fs7 = __toESM(require("fs"));
6874
+ var os2 = __toESM(require("os"));
6875
+ var path9 = __toESM(require("path"));
6876
+ var TEST_RUNNER_REPORTER_ENV = "PW_TEST_REPORTER";
6877
+ function startedByTestRunner(env) {
6878
+ const reporter = env[TEST_RUNNER_REPORTER_ENV]?.trim();
6879
+ return !!reporter && /[\\/]/.test(reporter);
6880
+ }
6881
+
6180
6882
  // src/internal/capture/kept-responses.ts
6181
6883
  var keeping = null;
6182
6884
  var TEXT_TYPE = /^(?:text\/[\w.+-]+|application\/(?:[\w.+-]+\+)?(?:json|xml)|application\/(?:javascript|x-www-form-urlencoded|graphql))$/i;
@@ -6261,6 +6963,8 @@ var LIVE_FRAME_BYTE_RATE = 256 * 1024;
6261
6963
  var MAX_LIVE_FRAME = 192 * 1024;
6262
6964
  var MAX_LIVE_FRAME_BASE64 = 4 * Math.ceil(MAX_LIVE_FRAME / 3);
6263
6965
  var MAX_LIVE_MESSAGE = MAX_LIVE_FRAME_BASE64 + 8 * 1024;
6966
+ var MAX_LIVE_CALL_LOG_LINES = 20;
6967
+ var LIVE_CALL_LOGS_PER_SECOND = 4;
6264
6968
  var KEY = /^pdl_[A-Za-z0-9_-]{43}$/;
6265
6969
  function isLiveViewKey(value) {
6266
6970
  return typeof value === "string" && KEY.test(value);
@@ -6318,12 +7022,68 @@ function liveShareRefusal(status) {
6318
7022
  return "Sharing stopped: the instance did not answer. Share the run again to try once more.";
6319
7023
  }
6320
7024
  }
7025
+ var MAX_LIVE_MASKS = 20;
7026
+ var MAX_LIVE_MASK_LENGTH = 200;
7027
+ var FRAME_CALLS = /* @__PURE__ */ new Set(["frameLocator", "contentFrame", "owner"]);
7028
+ var NARROWING_CALLS = /* @__PURE__ */ new Set(["filter", "and", "or", "visible", "first", "last", "nth"]);
7029
+ function liveMaskProblem(locator) {
7030
+ if (!locator.trim()) return "A masked element needs a locator.";
7031
+ if (locator.length > MAX_LIVE_MASK_LENGTH) return `A locator is ${MAX_LIVE_MASK_LENGTH} characters at most.`;
7032
+ const source = locator.trim();
7033
+ const chain = tryParseLocatorChain(source);
7034
+ if (!chain?.calls.length || NARROWING_CALLS.has(chain.calls[0].method)) {
7035
+ return `\`${source}\` is not a Playwright locator, such as getByLabel('IBAN').`;
7036
+ }
7037
+ if (chain.calls.some((call) => FRAME_CALLS.has(call.method))) {
7038
+ return `\`${source}\` enters a frame: masks are looked for in every frame of the page, so name the element alone, such as getByLabel('Card number'), or the iframe, such as locator('iframe#card').`;
7039
+ }
7040
+ return null;
7041
+ }
7042
+ function parseLiveMasks(value) {
7043
+ if (!Array.isArray(value) || value.length > MAX_LIVE_MASKS) return null;
7044
+ const locators = [];
7045
+ for (const locator of value) {
7046
+ if (typeof locator !== "string" || liveMaskProblem(locator)) return null;
7047
+ if (!locators.includes(locator.trim())) locators.push(locator.trim());
7048
+ }
7049
+ return locators;
7050
+ }
6321
7051
  function isObject5(value) {
6322
7052
  return typeof value === "object" && value !== null && !Array.isArray(value);
6323
7053
  }
6324
7054
  function isInt2(value, min, max) {
6325
7055
  return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
6326
7056
  }
7057
+ var LONGEST_REPEAT = 12;
7058
+ function sameLines(lines, a, b, size) {
7059
+ for (let k = 0; k < size; k++) if (lines[a + k] !== lines[b + k]) return false;
7060
+ return true;
7061
+ }
7062
+ function compressCallLog(lines) {
7063
+ const out = [];
7064
+ let i = 0;
7065
+ while (i < lines.length) {
7066
+ let size = 1;
7067
+ let count = 1;
7068
+ for (let length = 1; length <= LONGEST_REPEAT && i + length * 2 <= lines.length; length++) {
7069
+ let times = 1;
7070
+ while (i + length * (times + 1) <= lines.length && sameLines(lines, i, i + length * times, length)) times++;
7071
+ if (times > 1 && times * length > count * size) {
7072
+ size = length;
7073
+ count = times;
7074
+ }
7075
+ }
7076
+ for (let k = 0; k < size; k++) {
7077
+ const line = lines[i + k];
7078
+ const indent = /^\s*/.exec(line)[0];
7079
+ if (count === 1) out.push(`${indent}- ${line.trim()}`);
7080
+ else if (k === 0) out.push(`${indent}${count} \xD7 ${line.trim()}`);
7081
+ else out.push(`${indent}${" ".repeat(`${count} \xD7 `.length - 2)}- ${line.trim()}`);
7082
+ }
7083
+ i += size * count;
7084
+ }
7085
+ return out;
7086
+ }
6327
7087
  function parseInstanceToWorker(data) {
6328
7088
  let value;
6329
7089
  try {
@@ -6331,7 +7091,9 @@ function parseInstanceToWorker(data) {
6331
7091
  } catch {
6332
7092
  return null;
6333
7093
  }
6334
- if (!isObject5(value) || value.type !== "viewers" || !isInt2(value.count, 0, 1e6)) return null;
7094
+ if (!isObject5(value)) return null;
7095
+ if (value.type === "masks") return { type: "masks", locators: parseLiveMasks(value.locators) };
7096
+ if (value.type !== "viewers" || !isInt2(value.count, 0, 1e6)) return null;
6335
7097
  return { type: "viewers", count: value.count };
6336
7098
  }
6337
7099
 
@@ -6349,7 +7111,198 @@ function resolveShardInfo(config, env = process.env) {
6349
7111
  return parseShardSpec(env[PIWI_SHARD_ENV]);
6350
7112
  }
6351
7113
 
7114
+ // src/internal/capture/live-call-log.ts
7115
+ var path10 = __toESM(require("path"));
7116
+ var TARGET_EVERY_MS = 1e3;
7117
+ var KEPT_LINES = 1e3;
7118
+ var MAX_LINE2 = 600;
7119
+ var MAX_TITLE = 500;
7120
+ var MAX_HEARD = 200;
7121
+ function callStreamOf(context) {
7122
+ const dbg = context.debugger;
7123
+ return typeof dbg?._enable === "function" && typeof dbg.on === "function" && typeof dbg.off === "function" ? dbg : null;
7124
+ }
7125
+ var heard = /* @__PURE__ */ new Map();
7126
+ var tracked = /* @__PURE__ */ new WeakSet();
7127
+ function trackCalls(playwright) {
7128
+ const instrumentation = playwright?._instrumentation;
7129
+ if (!playwright || tracked.has(playwright) || typeof instrumentation?.addListener !== "function") return;
7130
+ tracked.add(playwright);
7131
+ instrumentation.addListener({
7132
+ onApiCallBegin: (zone, channel) => {
7133
+ if (channel?.method === "__waitInfo__") return;
7134
+ const selector = channel?.params?.selector;
7135
+ heard.set(zone, {
7136
+ zone,
7137
+ selector: typeof selector === "string" && selector ? selector : null,
7138
+ began: Date.now()
7139
+ });
7140
+ if (heard.size > MAX_HEARD) heard.delete(heard.keys().next().value);
7141
+ },
7142
+ onApiCallEnd: (zone) => {
7143
+ heard.delete(zone);
7144
+ }
7145
+ });
7146
+ }
7147
+ function heardAs(id) {
7148
+ for (const call of heard.values()) if (call.zone.callId === id) return call;
7149
+ return null;
7150
+ }
7151
+ function userFile(file) {
7152
+ if (typeof file !== "string" || !file || /[\\/]node_modules[\\/]/.test(file)) return null;
7153
+ const relative7 = path10.relative(process.cwd(), file).split(path10.sep).join("/");
7154
+ return relative7 && !relative7.startsWith("..") && !path10.isAbsolute(relative7) ? relative7 : null;
7155
+ }
7156
+ var followed = /* @__PURE__ */ new WeakMap();
7157
+ function take(state, update) {
7158
+ if (typeof update?.id !== "string" || typeof update.title !== "string") return;
7159
+ const status = update.status === "running" ? "running" : update.status === "error" ? "failed" : "passed";
7160
+ let call = state.calls.get(update.id);
7161
+ if (!call) {
7162
+ const heardOne = heardAs(update.id);
7163
+ call = {
7164
+ id: update.id,
7165
+ title: hideSecretsInText(hideTypedText(update.title)).slice(0, MAX_TITLE),
7166
+ file: userFile(update.location?.file),
7167
+ line: typeof update.location?.line === "number" ? update.location.line : null,
7168
+ status,
7169
+ lines: [],
7170
+ point: null,
7171
+ error: null,
7172
+ began: heardOne?.began ?? Date.now(),
7173
+ ended: null,
7174
+ order: ++state.told,
7175
+ selector: heardOne?.selector ?? null,
7176
+ secretField: heardOne?.selector ? isSecretLocator(heardOne.selector) : false
7177
+ };
7178
+ state.calls.set(update.id, call);
7179
+ }
7180
+ if (Array.isArray(update.newLogEntries)) {
7181
+ for (const entry of update.newLogEntries) {
7182
+ if (typeof entry !== "string") continue;
7183
+ call.secretField ||= namesSecretField(entry);
7184
+ call.lines.push(hideSecretsInText(hideSecretsInLog(entry, call.secretField)).slice(0, MAX_LINE2));
7185
+ }
7186
+ if (call.lines.length > KEPT_LINES) call.lines.splice(0, call.lines.length - KEPT_LINES);
7187
+ }
7188
+ const point = update.actionPoint;
7189
+ if (point && Number.isFinite(point.x) && Number.isFinite(point.y)) {
7190
+ call.point = { x: point.x, y: point.y };
7191
+ }
7192
+ if (typeof update.error === "string" && update.error) {
7193
+ const first = update.error.split("\n")[0];
7194
+ call.error = hideSecretsInText(hideSecretsInLog(first, call.secretField)).slice(0, MAX_LINE2);
7195
+ }
7196
+ call.status = status;
7197
+ if (status !== "running" && call.ended === null) call.ended = Date.now();
7198
+ }
7199
+ function shownCall(state, since) {
7200
+ let shown2 = null;
7201
+ for (const call of state.calls.values()) {
7202
+ if (call.ended !== null && call.ended < since) continue;
7203
+ if (!shown2) {
7204
+ shown2 = call;
7205
+ continue;
7206
+ }
7207
+ const running2 = call.status === "running";
7208
+ if (running2 !== (shown2.status === "running") ? running2 : call.order > shown2.order) shown2 = call;
7209
+ }
7210
+ return shown2;
7211
+ }
7212
+ function prune(state) {
7213
+ let last = null;
7214
+ for (const call of state.calls.values()) if (!last || call.order > last.order) last = call;
7215
+ for (const [id, call] of state.calls) if (call.status !== "running" && call !== last) state.calls.delete(id);
7216
+ }
7217
+ async function followContext(context) {
7218
+ const stream = callStreamOf(context);
7219
+ if (!stream) return null;
7220
+ const state = { calls: /* @__PURE__ */ new Map(), told: 0, views: /* @__PURE__ */ new Set() };
7221
+ const listener = (updates) => {
7222
+ if (!Array.isArray(updates)) return;
7223
+ for (const update of updates) take(state, update);
7224
+ prune(state);
7225
+ for (const view3 of state.views) view3();
7226
+ };
7227
+ stream.on("apicallsupdated", listener);
7228
+ try {
7229
+ await internalCall(context, () => stream._enable());
7230
+ } catch {
7231
+ stream.off("apicallsupdated", listener);
7232
+ return null;
7233
+ }
7234
+ return state;
7235
+ }
7236
+ async function followCallLog(page, since, onLog) {
7237
+ const context = page.context();
7238
+ let state = followed.get(context);
7239
+ if (!state) {
7240
+ state = followContext(context);
7241
+ followed.set(context, state);
7242
+ }
7243
+ const calls = await state;
7244
+ if (!calls) return null;
7245
+ let stopped = false;
7246
+ let reading = false;
7247
+ let target = null;
7248
+ const onThisPage = () => !page.isClosed() && context.pages().length === 1;
7249
+ const tell = () => {
7250
+ const shown2 = shownCall(calls, since);
7251
+ if (stopped || !shown2) return;
7252
+ const here = onThisPage();
7253
+ onLog({
7254
+ id: shown2.id,
7255
+ call: { title: shown2.title, file: shown2.file, line: shown2.line },
7256
+ status: shown2.status,
7257
+ lines: compressCallLog(shown2.lines).slice(-MAX_LIVE_CALL_LOG_LINES),
7258
+ point: here ? shown2.point : null,
7259
+ target: here && shown2.status === "running" && target?.id === shown2.id ? target.matches : null,
7260
+ viewport: page.viewportSize(),
7261
+ ms: (shown2.ended ?? Date.now()) - shown2.began,
7262
+ error: shown2.error
7263
+ });
7264
+ };
7265
+ calls.views.add(tell);
7266
+ tell();
7267
+ const timer = setInterval(() => {
7268
+ const shown2 = shownCall(calls, since);
7269
+ const selector = shown2?.selector;
7270
+ if (stopped || reading || !shown2 || shown2.status !== "running" || !selector || !onThisPage()) return;
7271
+ if (Date.now() - shown2.began < TARGET_EVERY_MS) return;
7272
+ reading = true;
7273
+ const framed = selector.includes("internal:control=enter-frame");
7274
+ void internalCall(
7275
+ page,
7276
+ () => page.locator(selector).evaluateAll((elements) => {
7277
+ const first = elements[0];
7278
+ if (!first) return { count: 0, box: null, enabled: null };
7279
+ const rect = first.getBoundingClientRect();
7280
+ const disabled = first.disabled === true || first.getAttribute("aria-disabled") === "true";
7281
+ return {
7282
+ count: elements.length,
7283
+ box: rect.width || rect.height ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null,
7284
+ enabled: !disabled
7285
+ };
7286
+ })
7287
+ ).then((matches) => {
7288
+ if (stopped || shownCall(calls, since) !== shown2 || shown2.status !== "running") return;
7289
+ if (!matches.count && shown2.lines.some((line) => line.includes("locator resolved to"))) return;
7290
+ target = { id: shown2.id, matches: framed ? { ...matches, box: null } : matches };
7291
+ tell();
7292
+ }).catch(() => void 0).finally(() => {
7293
+ reading = false;
7294
+ });
7295
+ }, TARGET_EVERY_MS);
7296
+ timer.unref?.();
7297
+ return () => {
7298
+ stopped = true;
7299
+ clearInterval(timer);
7300
+ calls.views.delete(tell);
7301
+ };
7302
+ }
7303
+
6352
7304
  // src/internal/capture/page-view.ts
7305
+ var SCREENCAST_LAG_MS = 100;
6353
7306
  var HOLDERS = /* @__PURE__ */ new WeakMap();
6354
7307
  var TEST_SCREENCASTS = /* @__PURE__ */ new WeakSet();
6355
7308
  var OWN_SCREENCASTS = /* @__PURE__ */ new WeakMap();
@@ -6402,16 +7355,39 @@ async function openPageView(page, options) {
6402
7355
  let last = null;
6403
7356
  let sent2 = null;
6404
7357
  let refused = false;
7358
+ let screening = false;
7359
+ let screened = null;
6405
7360
  const interval = 1e3 / options.fps;
7361
+ const deliver = (frame) => {
7362
+ if (closed || !options.live()) return;
7363
+ refused = false;
7364
+ sent2 = frame;
7365
+ options.send(frame);
7366
+ };
7367
+ const refuse2 = (why) => {
7368
+ if (closed || refused || !options.live()) return;
7369
+ refused = true;
7370
+ sent2 = { refused: why };
7371
+ options.refuse(why);
7372
+ };
6406
7373
  const pacer = new LatestPacer({
6407
7374
  intervalMs: interval,
6408
7375
  ...options.spacing ? { spacing: options.spacing } : {},
6409
- busy: options.busy,
7376
+ busy: () => screening || options.busy(),
6410
7377
  send: (frame) => {
6411
7378
  if (!options.live()) return;
6412
- refused = false;
6413
- sent2 = frame;
6414
- options.send(frame);
7379
+ if (!options.screen) return deliver(frame);
7380
+ screening = true;
7381
+ screened = options.screen(frame).then(
7382
+ (shown2) => {
7383
+ if (typeof shown2 === "string") refuse2(shown2);
7384
+ else if (shown2) deliver(shown2);
7385
+ },
7386
+ () => void 0
7387
+ ).finally(() => {
7388
+ screening = false;
7389
+ screened = null;
7390
+ });
6415
7391
  }
6416
7392
  });
6417
7393
  let lighter = null;
@@ -6426,23 +7402,20 @@ async function openPageView(page, options) {
6426
7402
  ).then(
6427
7403
  (shot) => {
6428
7404
  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();
7405
+ if (shot.length <= options.maxFrameBytes) return pacer.offer({ data: shot, ...viewport, at: now });
7406
+ refuse2();
6434
7407
  },
6435
7408
  () => void 0
6436
7409
  ).finally(() => {
6437
7410
  lighter = null;
6438
7411
  });
6439
7412
  };
6440
- const onFrame = (data, width, height) => {
7413
+ const onFrame = (data, width, height, at) => {
6441
7414
  if (closed || last?.equals(data)) return;
6442
7415
  last = data;
6443
7416
  if (data.length > options.maxFrameBytes) return lighten();
6444
7417
  const named = isViewSize(width, height);
6445
- pacer.offer({ data, width: named ? width : viewport.width, height: named ? height : viewport.height });
7418
+ pacer.offer({ data, width: named ? width : viewport.width, height: named ? height : viewport.height, at });
6446
7419
  };
6447
7420
  const screencast = screencastOf(page);
6448
7421
  let end = null;
@@ -6451,11 +7424,12 @@ async function openPageView(page, options) {
6451
7424
  const shoot = async () => {
6452
7425
  shots = null;
6453
7426
  if (closed || page.isClosed()) return;
7427
+ const at = Date.now();
6454
7428
  const shot = await internalCall(
6455
7429
  page,
6456
7430
  () => page.screenshot({ type: "jpeg", quality: options.quality, scale: "css", caret: "initial", timeout: 5e3 })
6457
7431
  ).catch(() => null);
6458
- if (shot) onFrame(shot, viewport.width, viewport.height);
7432
+ if (shot) onFrame(shot, viewport.width, viewport.height, at);
6459
7433
  if (!closed) {
6460
7434
  shots = setTimeout(() => void shoot(), options.shotMs);
6461
7435
  shots.unref?.();
@@ -6488,7 +7462,9 @@ async function openPageView(page, options) {
6488
7462
  size: options.size(viewport),
6489
7463
  quality: options.quality,
6490
7464
  onFrame: (frame) => {
6491
- onFrame(frame.data, frame.viewportWidth, frame.viewportHeight);
7465
+ const now = Date.now();
7466
+ const at = typeof frame.timestamp === "number" && Number.isFinite(frame.timestamp) ? Math.min(frame.timestamp, now) - SCREENCAST_LAG_MS : Number.NEGATIVE_INFINITY;
7467
+ onFrame(frame.data, frame.viewportWidth, frame.viewportHeight, at);
6492
7468
  return acks.next();
6493
7469
  }
6494
7470
  })
@@ -6512,9 +7488,22 @@ async function openPageView(page, options) {
6512
7488
  },
6513
7489
  resend: () => {
6514
7490
  if (closed || !sent2 || !options.live()) return;
6515
- if (sent2 === "refused") options.refuse();
7491
+ if ("refused" in sent2) options.refuse(sent2.refused);
6516
7492
  else options.send(sent2);
6517
7493
  },
7494
+ refresh: () => {
7495
+ if (closed) return;
7496
+ sent2 = null;
7497
+ last = null;
7498
+ const at = Date.now();
7499
+ void internalCall(
7500
+ page,
7501
+ () => page.screenshot({ type: "jpeg", quality: options.quality, scale: "css", caret: "initial", timeout: 5e3 })
7502
+ ).then(
7503
+ (shot) => onFrame(shot, viewport.width, viewport.height, at),
7504
+ () => void 0
7505
+ );
7506
+ },
6518
7507
  close: async () => {
6519
7508
  closed = true;
6520
7509
  sent2 = null;
@@ -6522,7 +7511,7 @@ async function openPageView(page, options) {
6522
7511
  pacer.close();
6523
7512
  if (shots) clearTimeout(shots);
6524
7513
  holders.delete(holder);
6525
- await lighter;
7514
+ await Promise.all([lighter, screened]);
6526
7515
  const stop = end;
6527
7516
  end = null;
6528
7517
  if (stop && !page.isClosed()) await stop().catch(() => void 0);
@@ -6540,6 +7529,7 @@ var MAX_EVENT_TEXT = 64 * 1024;
6540
7529
  var MAX_STATES_WAITING = 8;
6541
7530
  var LIGHT_QUALITY = 30;
6542
7531
  var SHOT_MS = 1e3;
7532
+ var LOG_SPACING_MS = Math.ceil(1e3 / LIVE_CALL_LOGS_PER_SECOND);
6543
7533
  var VIEW_END_MS = 1e3;
6544
7534
  var GIVE_UP = /* @__PURE__ */ new Set([401, 403, 404, 410]);
6545
7535
  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.`;
@@ -6612,7 +7602,8 @@ function openLiveView(worker, env = process.env, ci = detectCi(env)) {
6612
7602
  address,
6613
7603
  id: { shard: resolveShardInfo(worker.config, env)?.current ?? null, worker: worker.workerIndex },
6614
7604
  client,
6615
- agent: new client.Agent({ keepAlive: true, maxSockets: 2 }),
7605
+ // The stream, and a frame, a call log and a stop or an end in flight at once: none waits behind another.
7606
+ agent: new client.Agent({ keepAlive: true, maxSockets: 4 }),
6616
7607
  stream: null,
6617
7608
  timer: null,
6618
7609
  retries: 0,
@@ -6620,13 +7611,20 @@ function openLiveView(worker, env = process.env, ci = detectCi(env)) {
6620
7611
  ended: false,
6621
7612
  refused: null,
6622
7613
  sharing: ci,
7614
+ ci,
6623
7615
  lost: null,
6624
7616
  answered: null,
6625
7617
  viewers: 0,
6626
7618
  states: Promise.resolve(),
6627
7619
  statesWaiting: 0,
6628
7620
  stop: null,
6629
- posting: false
7621
+ posting: false,
7622
+ log: null,
7623
+ logPosting: false,
7624
+ logSentAt: 0,
7625
+ logTimer: null,
7626
+ logRefused: false,
7627
+ masks: []
6630
7628
  };
6631
7629
  if (ci) connect(link);
6632
7630
  changed();
@@ -6676,9 +7674,9 @@ async function stopSharing() {
6676
7674
  own.viewers = 0;
6677
7675
  changed();
6678
7676
  }
6679
- function liveViewTestBegan(title, call) {
7677
+ function liveViewTestBegan(title, call, mayPause2 = false) {
6680
7678
  if (!link) return;
6681
- running = { title, page: null, call };
7679
+ running = { title, page: null, call, mayPause: mayPause2, began: Date.now() };
6682
7680
  }
6683
7681
  function liveViewPage(page) {
6684
7682
  if (!link || !running || running.page) return;
@@ -6704,7 +7702,7 @@ function liveViewStopped(stop) {
6704
7702
  };
6705
7703
  }
6706
7704
  function hideCallSecrets(call) {
6707
- return { ...call, title: hideSecretsInText(call.title) };
7705
+ return { ...call, title: hideSecretsInText(hideTypedText(call.title)) };
6708
7706
  }
6709
7707
  function postState(own, message) {
6710
7708
  if (!active(own) || own.statesWaiting >= MAX_STATES_WAITING) return;
@@ -6772,7 +7770,8 @@ function connect(own) {
6772
7770
  text = text.slice(end + 2);
6773
7771
  const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
6774
7772
  const message = data ? parseInstanceToWorker(data) : null;
6775
- if (message) setViewers(own, message.count);
7773
+ if (message?.type === "masks") setMasks(own, message.locators);
7774
+ else if (message) setViewers(own, message.count);
6776
7775
  }
6777
7776
  });
6778
7777
  response.on("error", () => drop(null));
@@ -6812,11 +7811,19 @@ function setViewers(own, count) {
6812
7811
  return;
6813
7812
  }
6814
7813
  if (view) {
6815
- if (count > before) void view.opened.then((opened) => opened?.resend());
7814
+ if (count > before) {
7815
+ void view.opened.then((opened) => opened?.resend());
7816
+ if (own.log) postLog(own, own.log);
7817
+ }
6816
7818
  return;
6817
7819
  }
6818
7820
  startView();
6819
7821
  }
7822
+ function setMasks(own, locators) {
7823
+ if (JSON.stringify(locators) === JSON.stringify(own.masks)) return;
7824
+ own.masks = locators;
7825
+ if (own === link) void view?.opened.then((opened) => opened?.refresh());
7826
+ }
6820
7827
  function startView() {
6821
7828
  const own = link;
6822
7829
  const page = running?.page ?? null;
@@ -6826,6 +7833,11 @@ function startView() {
6826
7833
  return;
6827
7834
  }
6828
7835
  const live = () => view?.page === page && link === own && active(own) && own.viewers > 0;
7836
+ const mask = maskSecretFields(page, {
7837
+ masks: () => own.masks,
7838
+ quality: LIVE_FRAME_QUALITY,
7839
+ maxBytes: MAX_LIVE_FRAME
7840
+ });
6829
7841
  const opened = openPageView(page, {
6830
7842
  size: (viewport) => fitViewSize(viewport, LIVE_FRAME_WIDTH, LIVE_FRAME_HEIGHT),
6831
7843
  quality: LIVE_FRAME_QUALITY,
@@ -6834,28 +7846,39 @@ function startView() {
6834
7846
  maxFrameBytes: MAX_LIVE_FRAME,
6835
7847
  lightQuality: LIGHT_QUALITY,
6836
7848
  shotMs: SHOT_MS,
6837
- busy: () => own.posting,
7849
+ busy: () => own.posting || mask.resting,
6838
7850
  live,
7851
+ screen: (frame) => mask.screen(frame),
6839
7852
  send: (frame) => postFrame(own, frame),
6840
- refuse: () => void post(own, { type: "frame", ...own.id, refused: TOO_HEAVY })
7853
+ refuse: (why) => void post(own, { type: "frame", ...own.id, refused: why ?? TOO_HEAVY })
6841
7854
  }).catch(() => null);
6842
7855
  const onClose = () => {
6843
7856
  if (view?.page === page) void stopView();
6844
7857
  };
6845
7858
  page.once("close", onClose);
6846
- view = { page, opened, onClose };
7859
+ const following = own.ci && running && !running.mayPause && !own.logRefused ? followCallLog(page, running.began, (log) => {
7860
+ if (live()) postLog(own, log);
7861
+ }).catch(() => null) : null;
7862
+ view = { page, opened, onClose, following };
6847
7863
  }
6848
7864
  async function stopView() {
6849
7865
  const shown2 = view;
6850
7866
  view = null;
6851
7867
  if (!shown2) return;
6852
7868
  shown2.page.off("close", shown2.onClose);
7869
+ void shown2.following?.then((stop) => stop?.());
7870
+ const own = link;
7871
+ if (own) {
7872
+ own.log = null;
7873
+ if (own.logTimer) clearTimeout(own.logTimer);
7874
+ own.logTimer = null;
7875
+ }
6853
7876
  const closed = shown2.opened.then((opened) => opened?.close());
6854
7877
  let timer;
6855
7878
  await Promise.race([
6856
7879
  closed.catch(() => void 0),
6857
- new Promise((resolve5) => {
6858
- timer = setTimeout(resolve5, VIEW_END_MS);
7880
+ new Promise((resolve6) => {
7881
+ timer = setTimeout(resolve6, VIEW_END_MS);
6859
7882
  timer.unref?.();
6860
7883
  })
6861
7884
  ]);
@@ -6876,19 +7899,47 @@ function postFrame(own, frame) {
6876
7899
  own.posting = false;
6877
7900
  });
6878
7901
  }
7902
+ function postLog(own, log) {
7903
+ own.log = log;
7904
+ nextLog(own);
7905
+ }
7906
+ function nextLog(own) {
7907
+ if (own.logPosting || own.logTimer) return;
7908
+ const wait = LOG_SPACING_MS - (Date.now() - own.logSentAt);
7909
+ if (wait > 0) {
7910
+ own.logTimer = setTimeout(() => {
7911
+ own.logTimer = null;
7912
+ nextLog(own);
7913
+ }, wait);
7914
+ own.logTimer.unref?.();
7915
+ return;
7916
+ }
7917
+ const log = own.log;
7918
+ if (!active(own) || !view || !log || own.logRefused) return;
7919
+ own.logPosting = true;
7920
+ own.logSentAt = Date.now();
7921
+ let again = false;
7922
+ void post(own, { type: "call-log", ...own.id, log, test: running?.title ?? null }).then((status) => {
7923
+ if (status === 400) own.logRefused = true;
7924
+ again = status === 429;
7925
+ }).catch(() => void 0).finally(() => {
7926
+ own.logPosting = false;
7927
+ if (own.log && (own.log !== log || again)) nextLog(own);
7928
+ });
7929
+ }
6879
7930
  function post(own, message) {
6880
7931
  return active(own) ? send(own, message) : Promise.resolve(null);
6881
7932
  }
6882
7933
  function send(own, message) {
6883
7934
  if (own.ended || own.refused !== null) return Promise.resolve(null);
6884
7935
  const body = JSON.stringify(message);
6885
- return new Promise((resolve5) => {
7936
+ return new Promise((resolve6) => {
6886
7937
  let settled = false;
6887
7938
  const settle = (status) => {
6888
7939
  if (settled) return;
6889
7940
  settled = true;
6890
7941
  if (status !== null && GIVE_UP.has(status)) refuse(own, status);
6891
- resolve5(status);
7942
+ resolve6(status);
6892
7943
  };
6893
7944
  const request = own.client.request(own.address.url, {
6894
7945
  method: "POST",
@@ -6915,7 +7966,6 @@ function send(own, message) {
6915
7966
  // src/internal/capture/run-channel.ts
6916
7967
  var CONNECT_MS = 2e3;
6917
7968
  var BAR_HIDDEN2 = "#__piwi-pause-bar { display: none !important; }";
6918
- var OUTLINE_STYLE = "outline: 2px dashed #a78bfa; outline-offset: 2px; background: rgba(167, 139, 250, 0.15);";
6919
7969
  var WENT_ON = "The test went on.";
6920
7970
  var MAX_FRAME_BACKLOG = 1024 * 1024;
6921
7971
  var SHOT_MS2 = 500;
@@ -6923,6 +7973,7 @@ var LIGHT_QUALITY2 = 30;
6923
7973
  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
7974
  var VIEW_END_MS2 = 1e3;
6925
7975
  var PAGE_ANSWER_MS = 1e4;
7976
+ var OUTLINE_MS = 2e3;
6926
7977
  var NO_ANSWER = "The page did not answer in time.";
6927
7978
  var LATE_ACT = "The page did not answer in time: the action may still take effect, and is told when it does.";
6928
7979
  var MAX_INPUT_WAITING = 4;
@@ -6962,11 +8013,12 @@ var sharedHere = false;
6962
8013
  var liveSaid = "";
6963
8014
  var unhearLive = null;
6964
8015
  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) {
8016
+ function workerTakes(env = process.env, playwright) {
6966
8017
  const pairing = !!env[PIWI_LOCAL_DEBUG_ENV.editorSend];
6967
8018
  const recorder = parseRecordChannel(env[RECORD_CHANNEL_ENV]) !== null;
8019
+ const boxes = playwright === void 0 || playwrightVersionAtLeast(playwright, PICK_AT_SINCE);
6968
8020
  return RUN_TAKES.filter(
6969
- (take) => take === "pick" ? pairing : take === "record" || take === "assert" ? recorder : true
8021
+ (take2) => take2 === "pick" ? pairing : take2 === "record" || take2 === "assert" ? recorder : take2 === "pick-at" ? boxes : true
6970
8022
  );
6971
8023
  }
6972
8024
  async function openRunChannel(worker, handlers, env = process.env) {
@@ -6975,15 +8027,15 @@ async function openRunChannel(worker, handlers, env = process.env) {
6975
8027
  if (socket || !channel || !ref || !/^[\w-]{1,64}$/.test(ref)) return false;
6976
8028
  const opening = net.connect(channel.path);
6977
8029
  opening.setEncoding("utf8");
6978
- const opened = await new Promise((resolve5) => {
6979
- const timer = setTimeout(() => resolve5(false), CONNECT_MS);
8030
+ const opened = await new Promise((resolve6) => {
8031
+ const timer = setTimeout(() => resolve6(false), CONNECT_MS);
6980
8032
  opening.once("connect", () => {
6981
8033
  clearTimeout(timer);
6982
- resolve5(true);
8034
+ resolve6(true);
6983
8035
  });
6984
8036
  opening.once("error", () => {
6985
8037
  clearTimeout(timer);
6986
- resolve5(false);
8038
+ resolve6(false);
6987
8039
  });
6988
8040
  });
6989
8041
  if (!opened || opening.destroyed) {
@@ -7026,8 +8078,10 @@ async function openRunChannel(worker, handlers, env = process.env) {
7026
8078
  pid: process.pid,
7027
8079
  playwright: worker.config.version,
7028
8080
  reporter: getReporterVersion(),
7029
- takes: workerTakes(env),
7030
- holds: [...RUN_HOLDS]
8081
+ takes: workerTakes(env, worker.config.version),
8082
+ holds: [...RUN_HOLDS],
8083
+ // A run the reporter follows from outside the editor, which its own test runner started.
8084
+ ...ref.startsWith(TERMINAL_REF_PREFIX) && startedByTestRunner(env) ? { runner: true } : {}
7031
8085
  });
7032
8086
  liveSaid = "";
7033
8087
  tellLive();
@@ -7100,7 +8154,7 @@ async function unbind() {
7100
8154
  const shared = bound?.browser;
7101
8155
  bound = null;
7102
8156
  if (shared?.isConnected()) {
7103
- await within2(
8157
+ await within3(
7104
8158
  internalCall(shared, () => bindBrowser(shared, runName, process.cwd(), false)),
7105
8159
  VIEW_END_MS2
7106
8160
  );
@@ -7160,7 +8214,7 @@ function viewEnded(stop) {
7160
8214
  else viewEnds.add(() => ended.abort());
7161
8215
  return ended.signal;
7162
8216
  }
7163
- async function within2(run, ms) {
8217
+ async function within3(run, ms) {
7164
8218
  await answerWithin(run, ms, void 0);
7165
8219
  }
7166
8220
  async function answerWithin(run, ms, late) {
@@ -7168,20 +8222,20 @@ async function answerWithin(run, ms, late) {
7168
8222
  try {
7169
8223
  return await Promise.race([
7170
8224
  run.catch(() => late),
7171
- new Promise((resolve5) => timer = setTimeout(() => resolve5(late), ms))
8225
+ new Promise((resolve6) => timer = setTimeout(() => resolve6(late), ms))
7172
8226
  ]);
7173
8227
  } finally {
7174
8228
  clearTimeout(timer);
7175
8229
  }
7176
8230
  }
7177
8231
  async function endView(stop, page) {
7178
- if (viewStarting?.stop === stop) await within2(viewStarting.started, VIEW_END_MS2);
8232
+ if (viewStarting?.stop === stop) await within3(viewStarting.started, VIEW_END_MS2);
7179
8233
  if (view2?.stop === stop) await stopView2();
7180
8234
  const own = input;
7181
8235
  input = freshInput();
7182
- await within2(own.playing, VIEW_END_MS2);
8236
+ await within3(own.playing, VIEW_END_MS2);
7183
8237
  if (page && !page.isClosed() && (own.buttons.size || own.keys.size)) {
7184
- await within2(
8238
+ await within3(
7185
8239
  internalCall(page, async () => {
7186
8240
  for (const button of own.buttons) await page.mouse.up({ button });
7187
8241
  for (const key of own.keys) await page.keyboard.up(key);
@@ -7195,7 +8249,7 @@ async function stopView2() {
7195
8249
  view2 = null;
7196
8250
  for (const end of viewEnds) end();
7197
8251
  viewEnds.clear();
7198
- if (shown2) await within2(shown2.close(), VIEW_END_MS2);
8252
+ if (shown2) await within3(shown2.close(), VIEW_END_MS2);
7199
8253
  }
7200
8254
  function startView2(holding) {
7201
8255
  if (view2?.stop === holding.stop) {
@@ -7215,7 +8269,7 @@ function startView2(holding) {
7215
8269
  }
7216
8270
  if (!opened) return { ok: false, message: "The page could not be shown: it closed, or does not answer." };
7217
8271
  if (held !== holding || page.isClosed()) {
7218
- await within2(opened.close(), VIEW_END_MS2);
8272
+ await within3(opened.close(), VIEW_END_MS2);
7219
8273
  return { ok: false, message: WENT_ON };
7220
8274
  }
7221
8275
  view2 = opened;
@@ -7259,7 +8313,7 @@ async function openView(page, stop) {
7259
8313
  };
7260
8314
  }
7261
8315
  async function stopViewOf(holding) {
7262
- if (viewStarting?.stop === holding.stop) await within2(viewStarting.started, VIEW_END_MS2);
8316
+ if (viewStarting?.stop === holding.stop) await within3(viewStarting.started, VIEW_END_MS2);
7263
8317
  if (view2?.stop === holding.stop) await stopView2();
7264
8318
  return { ok: true, message: "The live view is off." };
7265
8319
  }
@@ -7350,18 +8404,17 @@ async function countLocator(holding, source) {
7350
8404
  if (!locator) return { ok: false, message: `${source} is not a locator Piwi can build.`, count: null };
7351
8405
  try {
7352
8406
  const count = await internalCall(page, () => locator.count());
8407
+ let outlined = false;
7353
8408
  if (holding.bar && count > 0 && held === holding) {
7354
- const shown2 = await internalCall(page, () => locator.highlight({ style: OUTLINE_STYLE }));
7355
- const dispose = shown2?.dispose;
7356
- outline = {
7357
- stop: holding.stop,
7358
- remove: () => internalCall(page, () => typeof dispose === "function" ? dispose.call(shown2) : page.hideHighlight())
7359
- };
8409
+ const shown2 = highlightAtStop(page, locator, "count", source);
8410
+ outline = { stop: holding.stop, remove: async () => void await shown2.hide() };
8411
+ outlined = await answerWithin(shown2.shown, OUTLINE_MS, false);
7360
8412
  }
7361
8413
  return {
7362
8414
  ok: true,
7363
8415
  message: count === 0 ? "No element matches." : count === 1 ? "1 element matches." : `${count} elements match.`,
7364
- count
8416
+ count,
8417
+ outlined
7365
8418
  };
7366
8419
  } catch (error) {
7367
8420
  return { ok: false, message: `The locator could not be counted: ${errorMessage(error)}`, count: null };
@@ -7548,17 +8601,17 @@ async function imageOf(page) {
7548
8601
 
7549
8602
  // src/internal/capture/run-events.ts
7550
8603
  var begun = /* @__PURE__ */ new WeakMap();
7551
- var heard = /* @__PURE__ */ new WeakSet();
8604
+ var heard2 = /* @__PURE__ */ new WeakSet();
7552
8605
  var watched = /* @__PURE__ */ new WeakSet();
7553
- function userFile(file, cwd, windows) {
8606
+ function userFile2(file, cwd, windows) {
7554
8607
  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;
8608
+ const relative7 = callSitePath(file, cwd, windows);
8609
+ if (!relative7 || (windows ? path11.win32 : path11.posix).isAbsolute(relative7) || relative7.startsWith("..")) return null;
8610
+ return relative7;
7558
8611
  }
7559
8612
  function callPlace(frames, cwd = process.cwd(), windows = process.platform === "win32") {
7560
8613
  for (const frame of frames ?? []) {
7561
- const file = userFile(frame.file, cwd, windows);
8614
+ const file = userFile2(frame.file, cwd, windows);
7562
8615
  if (!file || !frame.line || frame.line < 1) continue;
7563
8616
  return { file, line: frame.line, ...frame.column && frame.column > 0 ? { column: frame.column } : {} };
7564
8617
  }
@@ -7575,9 +8628,9 @@ function errorText(error) {
7575
8628
  }
7576
8629
  function watchCalls(playwright) {
7577
8630
  const instrumentation = playwright?._instrumentation;
7578
- if (!playwright || heard.has(playwright)) return true;
8631
+ if (!playwright || heard2.has(playwright)) return true;
7579
8632
  if (!instrumentation || typeof instrumentation.addListener !== "function") return false;
7580
- heard.add(playwright);
8633
+ heard2.add(playwright);
7581
8634
  const titles = /* @__PURE__ */ new WeakMap();
7582
8635
  instrumentation.addListener({
7583
8636
  onApiCallBegin: (zone, channel) => {
@@ -7694,9 +8747,9 @@ function yamlScalar(text) {
7694
8747
  }
7695
8748
 
7696
8749
  // src/internal/capture/record-handoff.ts
7697
- var fs6 = __toESM(require("fs"));
8750
+ var fs8 = __toESM(require("fs"));
7698
8751
  var net2 = __toESM(require("net"));
7699
- var path10 = __toESM(require("path"));
8752
+ var path12 = __toESM(require("path"));
7700
8753
  var START_WAIT_MS = 6e4;
7701
8754
  var NEW_TAB_NOTICE = "A new tab opened: what you do there is not recorded yet. Record in the first tab.";
7702
8755
  var relays = /* @__PURE__ */ new WeakMap();
@@ -7716,7 +8769,7 @@ function canRecordFromHere(channel = process.env[RECORD_CHANNEL_ENV]) {
7716
8769
  function handoffPlace(location, cwd = process.cwd()) {
7717
8770
  const m = /^(.+):(\d+):\d+$/.exec(location);
7718
8771
  if (!m) return null;
7719
- return { file: path10.resolve(cwd, m[1]), line: Number(m[2]) };
8772
+ return { file: path12.resolve(cwd, m[1]), line: Number(m[2]) };
7720
8773
  }
7721
8774
  async function recordFromHere(options) {
7722
8775
  const channel = parseRecordChannel(options.channel ?? process.env[RECORD_CHANNEL_ENV]);
@@ -7726,7 +8779,7 @@ async function recordFromHere(options) {
7726
8779
  if (ended?.aborted) return "the test ended.";
7727
8780
  const use = testInfo.project?.use ?? {};
7728
8781
  const context = page.context();
7729
- return new Promise((resolve5) => {
8782
+ return new Promise((resolve6) => {
7730
8783
  const socket2 = net2.connect(channel.path);
7731
8784
  socket2.setEncoding("utf8");
7732
8785
  const answers = /* @__PURE__ */ new Map();
@@ -7787,7 +8840,7 @@ async function recordFromHere(options) {
7787
8840
  page.off("crash", onGone);
7788
8841
  ended?.removeEventListener("abort", onEnded);
7789
8842
  socket2.end();
7790
- resolve5(recording ? null : said);
8843
+ resolve6(recording ? null : said);
7791
8844
  };
7792
8845
  const relayed = (source, payload) => new Promise((answer) => {
7793
8846
  const call = ++calls;
@@ -7819,7 +8872,7 @@ async function recordFromHere(options) {
7819
8872
  if (typeof initScript?.dispose !== "function") relay.lingering = true;
7820
8873
  }
7821
8874
  if (settled) return release();
7822
- await page.evaluate(fs6.readFileSync(bundle, "utf-8"));
8875
+ await page.evaluate(fs8.readFileSync(bundle, "utf-8"));
7823
8876
  if (settled) return release();
7824
8877
  recording = true;
7825
8878
  clearTimeout(startWait);
@@ -7895,7 +8948,7 @@ function browserFunctionsInWorker() {
7895
8948
  function browserFunctionCoverage(files) {
7896
8949
  return sanitizeCoverage({
7897
8950
  v: 1,
7898
- files: [...files].map(([path16, functions]) => ({ path: path16, runtime: "browser", functions }))
8951
+ files: [...files].map(([path18, functions]) => ({ path: path18, runtime: "browser", functions }))
7899
8952
  });
7900
8953
  }
7901
8954
  function newKnownFunctions(known, sent2) {
@@ -7908,19 +8961,19 @@ function newKnownFunctions(known, sent2) {
7908
8961
  budget = MAX_COVERAGE_RANGES;
7909
8962
  };
7910
8963
  for (const [raw, ranges] of known) {
7911
- const path16 = normalizeCoveragePath(raw);
7912
- if (path16 === null) continue;
7913
- let named = sent2.get(path16);
8964
+ const path18 = normalizeCoveragePath(raw);
8965
+ if (path18 === null) continue;
8966
+ let named = sent2.get(path18);
7914
8967
  let fresh = normalizeFunctionRanges(ranges).filter((range) => !named?.has(`${range[0]}:${range[1]}`));
7915
8968
  if (fresh.length === 0) continue;
7916
- if (!named) sent2.set(path16, named = /* @__PURE__ */ new Set());
8969
+ if (!named) sent2.set(path18, named = /* @__PURE__ */ new Set());
7917
8970
  for (const range of fresh) named.add(`${range[0]}:${range[1]}`);
7918
8971
  while (fresh.length > 0) {
7919
8972
  if (budget === 0 || files.length >= MAX_COVERAGE_FILES) close();
7920
8973
  const part = fresh.slice(0, budget);
7921
8974
  fresh = fresh.slice(part.length);
7922
8975
  budget -= part.length;
7923
- files.push({ path: path16, allFunctions: part });
8976
+ files.push({ path: path18, allFunctions: part });
7924
8977
  }
7925
8978
  }
7926
8979
  close();
@@ -7931,13 +8984,13 @@ function serverCoverageInWorker() {
7931
8984
  }
7932
8985
 
7933
8986
  // src/internal/support/aria-sampling.ts
7934
- var path11 = __toESM(require("path"));
7935
- var os3 = __toESM(require("os"));
7936
- var fs7 = __toESM(require("fs"));
8987
+ var path13 = __toESM(require("path"));
8988
+ var os4 = __toESM(require("os"));
8989
+ var fs9 = __toESM(require("fs"));
7937
8990
 
7938
8991
  // src/internal/support/instance-id.ts
7939
8992
  var crypto2 = __toESM(require("crypto"));
7940
- var os2 = __toESM(require("os"));
8993
+ var os3 = __toESM(require("os"));
7941
8994
  function hashForProject(projectName) {
7942
8995
  return crypto2.createHash("sha1").update(projectName).digest("hex").slice(0, 16);
7943
8996
  }
@@ -7947,14 +9000,14 @@ function ariaSampleIdentity(filePath2, title) {
7947
9000
  return `${filePath2}\0${title}`;
7948
9001
  }
7949
9002
  function getAriaSampleFilePath(projectName) {
7950
- return path11.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
9003
+ return path13.join(os4.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
7951
9004
  }
7952
9005
  var cachedSets = /* @__PURE__ */ new Map();
7953
9006
  function loadAriaSampleSet(projectName) {
7954
9007
  if (cachedSets.has(projectName)) return cachedSets.get(projectName);
7955
9008
  let set = null;
7956
9009
  try {
7957
- const raw = fs7.readFileSync(getAriaSampleFilePath(projectName), "utf8");
9010
+ const raw = fs9.readFileSync(getAriaSampleFilePath(projectName), "utf8");
7958
9011
  const parsed2 = JSON.parse(raw);
7959
9012
  if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
7960
9013
  set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
@@ -7977,8 +9030,8 @@ function isDueForAriaSample(testInfo) {
7977
9030
 
7978
9031
  // src/internal/capture/code-reach.ts
7979
9032
  var crypto3 = __toESM(require("crypto"));
7980
- var fs8 = __toESM(require("fs"));
7981
- var path12 = __toESM(require("path"));
9033
+ var fs10 = __toESM(require("fs"));
9034
+ var path14 = __toESM(require("path"));
7982
9035
  var import_node_child_process = require("child_process");
7983
9036
  var MAX_MAP_BYTES = 20 * 1024 * 1024;
7984
9037
  var MAP_TIMEOUT_MS = 5e3;
@@ -8021,18 +9074,18 @@ function repositoryRoot(from) {
8021
9074
  }).trim();
8022
9075
  } catch {
8023
9076
  }
8024
- cachedRepoRoot = { from, root: path12.resolve(root) };
9077
+ cachedRepoRoot = { from, root: path14.resolve(root) };
8025
9078
  return cachedRepoRoot.root;
8026
9079
  }
8027
9080
  function codeReachRoots(configDir, configured) {
8028
9081
  const repoRoot = repositoryRoot(configDir);
8029
- const resolve5 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path12.resolve(configDir, r));
8030
- const global = Array.isArray(configured) ? resolve5(configured) : resolve5(configured?.["*"]);
9082
+ const resolve6 = (dirs) => (Array.isArray(dirs) ? dirs : dirs ? [dirs] : []).filter(Boolean).map((r) => path14.resolve(configDir, r));
9083
+ const global = Array.isArray(configured) ? resolve6(configured) : resolve6(configured?.["*"]);
8031
9084
  const byOrigin = /* @__PURE__ */ new Map();
8032
9085
  if (configured && !Array.isArray(configured)) {
8033
9086
  for (const [key, dirs] of Object.entries(configured)) {
8034
9087
  const origin = key === "*" ? null : originOf2(key);
8035
- const resolved = resolve5(dirs);
9088
+ const resolved = resolve6(dirs);
8036
9089
  if (origin && resolved.length) byOrigin.set(origin, resolved);
8037
9090
  }
8038
9091
  }
@@ -8055,7 +9108,7 @@ function scriptRoots(roots, scriptUrl, pageUrl) {
8055
9108
  }
8056
9109
  function isFile(file) {
8057
9110
  try {
8058
- return fs8.statSync(file).isFile();
9111
+ return fs10.statSync(file).isFile();
8059
9112
  } catch {
8060
9113
  return false;
8061
9114
  }
@@ -8063,10 +9116,10 @@ function isFile(file) {
8063
9116
  function resolveSourcePath(source, roots) {
8064
9117
  const normalized = normalizeSourcePath(source);
8065
9118
  if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
8066
- if (path12.isAbsolute(normalized) && isFile(normalized)) return path12.resolve(normalized);
8067
- const relative6 = normalized.replace(/^\/+/, "");
9119
+ if (path14.isAbsolute(normalized) && isFile(normalized)) return path14.resolve(normalized);
9120
+ const relative7 = normalized.replace(/^\/+/, "");
8068
9121
  for (const root of roots) {
8069
- const candidate = path12.resolve(root, relative6);
9122
+ const candidate = path14.resolve(root, relative7);
8070
9123
  if (isFile(candidate)) return candidate;
8071
9124
  }
8072
9125
  return null;
@@ -8098,7 +9151,7 @@ function fileForUrl(url, roots, bases) {
8098
9151
  return file;
8099
9152
  }
8100
9153
  function relativeToRepo(file, repoRoot) {
8101
- return path12.relative(repoRoot, file).split(path12.sep).join("/");
9154
+ return path14.relative(repoRoot, file).split(path14.sep).join("/");
8102
9155
  }
8103
9156
  function scriptVersion(entry) {
8104
9157
  return entry.hash ?? crypto3.createHash("sha1").update(entry.source ?? "").digest("hex");
@@ -8161,10 +9214,10 @@ async function resolveFunctions(entries, roots, fetchMap, pageUrl, withKnown) {
8161
9214
  const files = /* @__PURE__ */ new Map();
8162
9215
  const known = /* @__PURE__ */ new Map();
8163
9216
  const addTo = (into, file, functions) => {
8164
- const path16 = relativeToRepo(file, roots.repoRoot);
8165
- const listed = into.get(path16);
9217
+ const path18 = relativeToRepo(file, roots.repoRoot);
9218
+ const listed = into.get(path18);
8166
9219
  if (listed) listed.push(...functions);
8167
- else into.set(path16, [...functions]);
9220
+ else into.set(path18, [...functions]);
8168
9221
  };
8169
9222
  const add2 = (file, functions) => addTo(files, file, functions);
8170
9223
  const bases = viteBases(entries);
@@ -8319,8 +9372,8 @@ function onScriptParsed(page, capture, event) {
8319
9372
  var READ_WAIT_MS = 5e3;
8320
9373
  function readCoverage(page, cdp) {
8321
9374
  let timer;
8322
- const late = new Promise((resolve5) => {
8323
- timer = setTimeout(() => resolve5([]), READ_WAIT_MS);
9375
+ const late = new Promise((resolve6) => {
9376
+ timer = setTimeout(() => resolve6([]), READ_WAIT_MS);
8324
9377
  });
8325
9378
  const taken = internalCall(page, () => cdp.send("Profiler.takePreciseCoverage")).then(
8326
9379
  (answer) => answer.result,
@@ -8508,7 +9561,7 @@ async function stopPageCoverage(page) {
8508
9561
 
8509
9562
  // src/internal/capture/css-coverage.ts
8510
9563
  var crypto4 = __toESM(require("crypto"));
8511
- var fs9 = __toESM(require("fs"));
9564
+ var fs11 = __toESM(require("fs"));
8512
9565
 
8513
9566
  // ../core/src/css-rules.ts
8514
9567
  var GROUPING = /* @__PURE__ */ new Set(["media", "supports", "layer", "container", "scope", "starting-style", "document"]);
@@ -8648,7 +9701,7 @@ function sourceFile(source, roots) {
8648
9701
  function styleBlockLine(file, index) {
8649
9702
  let text;
8650
9703
  try {
8651
- text = fs9.readFileSync(file, "utf-8");
9704
+ text = fs11.readFileSync(file, "utf-8");
8652
9705
  } catch {
8653
9706
  return 0;
8654
9707
  }
@@ -8676,10 +9729,10 @@ async function sheetMap(sheet, text, fetchMap) {
8676
9729
  return null;
8677
9730
  }
8678
9731
  }
8679
- function sameLines(text, rules, file, lineOffset) {
9732
+ function sameLines2(text, rules, file, lineOffset) {
8680
9733
  let source;
8681
9734
  try {
8682
- source = fs9.readFileSync(file, "utf-8");
9735
+ source = fs11.readFileSync(file, "utf-8");
8683
9736
  } catch {
8684
9737
  return false;
8685
9738
  }
@@ -8714,12 +9767,12 @@ async function resolveSheet(sheet, text, dirs, fetchMap) {
8714
9767
  if (!file2) return null;
8715
9768
  const index = /[?&]index=(\d+)/.exec(sheet.devId);
8716
9769
  const lineOffset2 = /\.(?:vue|svelte|astro)$/i.test(file2) && index ? styleBlockLine(file2, Number(index[1])) : 0;
8717
- return { rules, lookup: null, file: file2, lineOffset: lineOffset2, sources: [], exact: sameLines(text, rules, file2, lineOffset2) };
9770
+ return { rules, lookup: null, file: file2, lineOffset: lineOffset2, sources: [], exact: sameLines2(text, rules, file2, lineOffset2) };
8718
9771
  }
8719
9772
  const file = fileForStyleUrl(sheet.sourceURL, dirs);
8720
9773
  if (!file) return null;
8721
9774
  const lineOffset = sheet.isInline ? sheet.startLine : 0;
8722
- return { rules, lookup: null, file, lineOffset, sources: [], exact: sameLines(text, rules, file, lineOffset) };
9775
+ return { rules, lookup: null, file, lineOffset, sources: [], exact: sameLines2(text, rules, file, lineOffset) };
8723
9776
  }
8724
9777
  function cachedResolution(sheet, text, dirs, fetchMap) {
8725
9778
  const version = crypto4.createHash("sha1").update(text).digest("hex");
@@ -8769,21 +9822,21 @@ async function resolveCssCoverage(sheets, roots, fetchMap) {
8769
9822
 
8770
9823
  // src/internal/capture/resource-ledger.ts
8771
9824
  var childProcess = __toESM(require("child_process"));
8772
- var fs11 = __toESM(require("fs"));
9825
+ var fs13 = __toESM(require("fs"));
8773
9826
  var net3 = __toESM(require("net"));
8774
- var path14 = __toESM(require("path"));
8775
- var import_node_module2 = require("module");
9827
+ var path16 = __toESM(require("path"));
9828
+ var import_node_module3 = require("module");
8776
9829
 
8777
9830
  // src/internal/capture/worker-metrics.ts
8778
9831
  var import_node_perf_hooks = require("perf_hooks");
8779
9832
 
8780
9833
  // src/internal/support/system-readers.ts
8781
- var fs10 = __toESM(require("fs"));
8782
- var path13 = __toESM(require("path"));
9834
+ var fs12 = __toESM(require("fs"));
9835
+ var path15 = __toESM(require("path"));
8783
9836
  var CLK_TCK = 100;
8784
9837
  function readText(file) {
8785
9838
  try {
8786
- return fs10.readFileSync(file, "utf8");
9839
+ return fs12.readFileSync(file, "utf8");
8787
9840
  } catch {
8788
9841
  return null;
8789
9842
  }
@@ -8822,7 +9875,7 @@ function parsePsiTotal(text) {
8822
9875
  }
8823
9876
  function roleOf(comm, cmdline) {
8824
9877
  const command = `${comm} ${cmdline}`;
8825
- if (/workerProcessEntry/.test(cmdline)) return "worker";
9878
+ if (/workerProcessEntry|[\\/]playwright[\\/]lib[\\/]common[\\/]process\.js/.test(cmdline)) return "worker";
8826
9879
  if (/loaderProcessEntry/.test(cmdline)) return "runner";
8827
9880
  if (/(^|[\s/])ffmpeg/.test(command)) return "ffmpeg";
8828
9881
  const type = /--type=([\w-]+)/.exec(cmdline)?.[1];
@@ -8865,45 +9918,45 @@ var ProcFs = class {
8865
9918
  }
8866
9919
  /** Whether this looks like a Linux `/proc` at all. */
8867
9920
  available() {
8868
- return fs10.existsSync(path13.join(this.root, "stat"));
9921
+ return fs12.existsSync(path15.join(this.root, "stat"));
8869
9922
  }
8870
9923
  /** Every process, keyed by pid. */
8871
9924
  table() {
8872
9925
  const out = /* @__PURE__ */ new Map();
8873
9926
  let names;
8874
9927
  try {
8875
- names = fs10.readdirSync(this.root);
9928
+ names = fs12.readdirSync(this.root);
8876
9929
  } catch {
8877
9930
  return out;
8878
9931
  }
8879
9932
  for (const name of names) {
8880
9933
  if (!/^\d+$/.test(name)) continue;
8881
- const text = readText(path13.join(this.root, name, "stat"));
9934
+ const text = readText(path15.join(this.root, name, "stat"));
8882
9935
  const stat = text ? parseProcStat(Number(name), text) : null;
8883
9936
  if (stat) out.set(stat.pid, stat);
8884
9937
  }
8885
9938
  return out;
8886
9939
  }
8887
9940
  stat(pid) {
8888
- const text = readText(path13.join(this.root, String(pid), "stat"));
9941
+ const text = readText(path15.join(this.root, String(pid), "stat"));
8889
9942
  return text ? parseProcStat(pid, text) : null;
8890
9943
  }
8891
9944
  runWaitMs(pid) {
8892
- const text = readText(path13.join(this.root, String(pid), "schedstat"));
9945
+ const text = readText(path15.join(this.root, String(pid), "schedstat"));
8893
9946
  return text ? parseSchedstat(text) : null;
8894
9947
  }
8895
9948
  cmdline(pid) {
8896
- return (readText(path13.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
9949
+ return (readText(path15.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
8897
9950
  }
8898
9951
  /** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
8899
9952
  statusKb(pid, key) {
8900
- const text = readText(path13.join(this.root, String(pid), "status"));
9953
+ const text = readText(path15.join(this.root, String(pid), "status"));
8901
9954
  return text ? parseKbField(text, key) : null;
8902
9955
  }
8903
9956
  /** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
8904
9957
  async pssKb(pid) {
8905
9958
  try {
8906
- return parseKbField(await fs10.promises.readFile(path13.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
9959
+ return parseKbField(await fs12.promises.readFile(path15.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
8907
9960
  } catch {
8908
9961
  return null;
8909
9962
  }
@@ -8911,29 +9964,29 @@ var ProcFs = class {
8911
9964
  /** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
8912
9965
  resetPeakRss(pid) {
8913
9966
  try {
8914
- fs10.writeFileSync(path13.join(this.root, String(pid), "clear_refs"), "5");
9967
+ fs12.writeFileSync(path15.join(this.root, String(pid), "clear_refs"), "5");
8915
9968
  return true;
8916
9969
  } catch {
8917
9970
  return false;
8918
9971
  }
8919
9972
  }
8920
9973
  cpuTicks() {
8921
- const text = readText(path13.join(this.root, "stat"));
9974
+ const text = readText(path15.join(this.root, "stat"));
8922
9975
  return text ? parseCpuTicks(text) : null;
8923
9976
  }
8924
9977
  /** Total stall time of a resource's pressure file, in µs. */
8925
9978
  pressureUs(resource) {
8926
- const text = readText(path13.join(this.root, "pressure", resource));
9979
+ const text = readText(path15.join(this.root, "pressure", resource));
8927
9980
  return text ? parsePsiTotal(text) : null;
8928
9981
  }
8929
9982
  memAvailableKb() {
8930
- const text = readText(path13.join(this.root, "meminfo"));
9983
+ const text = readText(path15.join(this.root, "meminfo"));
8931
9984
  return text ? parseKbField(text, "MemAvailable") : null;
8932
9985
  }
8933
9986
  /** Open file descriptors of a process. */
8934
9987
  fdCount(pid) {
8935
9988
  try {
8936
- return fs10.readdirSync(path13.join(this.root, String(pid), "fd")).length;
9989
+ return fs12.readdirSync(path15.join(this.root, String(pid), "fd")).length;
8937
9990
  } catch {
8938
9991
  return null;
8939
9992
  }
@@ -9196,11 +10249,21 @@ function runnableOf(info) {
9196
10249
  function fallbackRunnable(window) {
9197
10250
  return { phase: window ? "test" : "worker", fixture: null };
9198
10251
  }
10252
+ var GLOBALS_FILES = [
10253
+ ["lib", "globals.js"],
10254
+ ["lib", "common", "globals.js"]
10255
+ ];
10256
+ function playwrightGlobalsFile() {
10257
+ const packageJson = resolvePlaywrightPackageJson();
10258
+ if (!packageJson) return null;
10259
+ const root = path16.dirname(packageJson);
10260
+ return GLOBALS_FILES.map((parts) => path16.join(root, ...parts)).find((file) => fs13.existsSync(file)) ?? null;
10261
+ }
9199
10262
  function playwrightTestInfoSource() {
9200
10263
  try {
9201
- const packageJson = resolvePlaywrightPackageJson();
9202
- if (!packageJson) return () => null;
9203
- const globals = (0, import_node_module2.createRequire)(packageJson)(path14.join(path14.dirname(packageJson), "lib", "globals.js"));
10264
+ const file = playwrightGlobalsFile();
10265
+ if (!file) return () => null;
10266
+ const globals = (0, import_node_module3.createRequire)(file)(file);
9204
10267
  const read = globals.currentTestInfo;
9205
10268
  if (typeof read !== "function") return () => null;
9206
10269
  return () => {
@@ -9229,7 +10292,7 @@ function countHandles(types = TRACKED_HANDLE_TYPES) {
9229
10292
  async function settledHandles(start, types = TRACKED_HANDLE_TYPES) {
9230
10293
  const end = countHandles(types);
9231
10294
  if (!Object.entries(end).some(([type, n]) => n > (start[type] ?? 0))) return end;
9232
- await new Promise((resolve5) => setTimeout(resolve5, 0));
10295
+ await new Promise((resolve6) => setTimeout(resolve6, 0));
9233
10296
  return countHandles(types);
9234
10297
  }
9235
10298
  function deepUserSite() {
@@ -9243,7 +10306,7 @@ function deepUserSite() {
9243
10306
  }
9244
10307
  var SHELLS = /^(?:sh|bash|dash|zsh|cmd|cmd\.exe|powershell|powershell\.exe|pwsh)$/i;
9245
10308
  function commandOf(spawnfile, spawnargs) {
9246
- const name = (file) => path14.basename(file.replace(/\\/g, "/").replace(/^["']|["']$/g, ""));
10309
+ const name = (file) => path16.basename(file.replace(/\\/g, "/").replace(/^["']|["']$/g, ""));
9247
10310
  const program = typeof spawnfile === "string" ? name(spawnfile) : "";
9248
10311
  const args = Array.isArray(spawnargs) ? spawnargs.map(String) : [];
9249
10312
  const script = args.findIndex((arg) => arg === "-c" || /^\/c$/i.test(arg));
@@ -9257,8 +10320,8 @@ function settleWithin(promise, ms) {
9257
10320
  let timer;
9258
10321
  return Promise.race([
9259
10322
  promise.catch(() => void 0),
9260
- new Promise((resolve5) => {
9261
- timer = setTimeout(resolve5, ms);
10323
+ new Promise((resolve6) => {
10324
+ timer = setTimeout(resolve6, ms);
9262
10325
  })
9263
10326
  ]).finally(() => clearTimeout(timer));
9264
10327
  }
@@ -9378,10 +10441,10 @@ var ResourceLedger = class {
9378
10441
  trackHandle(object, entry) {
9379
10442
  const info = this.testInfoSource();
9380
10443
  const { phase, fixture } = info ? runnableOf(info) : fallbackRunnable(this.window);
9381
- const tracked = { ...entry, phase, fixture, test: testRef2(info) ?? this.window };
9382
- this.handles.add(tracked);
9383
- this.byHandle.set(object, tracked);
9384
- return tracked;
10444
+ const tracked2 = { ...entry, phase, fixture, test: testRef2(info) ?? this.window };
10445
+ this.handles.add(tracked2);
10446
+ this.byHandle.set(object, tracked2);
10447
+ return tracked2;
9385
10448
  }
9386
10449
  onServer(server, site) {
9387
10450
  const known = this.byHandle.get(server);
@@ -9424,7 +10487,7 @@ var ResourceLedger = class {
9424
10487
  const own = () => [...this.handles].filter(
9425
10488
  (entry) => entry.test?.id === testId && !entry.fixture?.worker && (entry.phase === "test" || entry.phase === "beforeEach" || entry.phase === "afterEach")
9426
10489
  );
9427
- if (own().some((entry) => entry.pending?.())) await new Promise((resolve5) => setTimeout(resolve5, 0));
10490
+ if (own().some((entry) => entry.pending?.())) await new Promise((resolve6) => setTimeout(resolve6, 0));
9428
10491
  return own().filter((entry) => entry.running()).map((entry) => ({ type: entry.type, site: entry.site, ...entry.command ? { command: entry.command } : {} }));
9429
10492
  }
9430
10493
  /** Mark a page used: a locator was built on it. */
@@ -9492,7 +10555,7 @@ var ResourceLedger = class {
9492
10555
  const census = this.census(null);
9493
10556
  if (this.resultsFile) {
9494
10557
  try {
9495
- fs11.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
10558
+ fs13.appendFileSync(this.resultsFile, `${JSON.stringify(census)}
9496
10559
  `);
9497
10560
  } catch {
9498
10561
  }
@@ -10134,7 +11197,8 @@ function createSink() {
10134
11197
  locatorPages: new LocatorPageLog(),
10135
11198
  codeReach: { sessions: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map(), known: /* @__PURE__ */ new Map(), cssFiles: /* @__PURE__ */ new Map(), cssKnown: /* @__PURE__ */ new Map() },
10136
11199
  serverCoverage: /* @__PURE__ */ new Map(),
10137
- baseURL: null
11200
+ baseURL: null,
11201
+ headless: void 0
10138
11202
  };
10139
11203
  }
10140
11204
  var MAX_CODE_REACH_PAGES = 16;
@@ -10159,7 +11223,7 @@ function maybeStartCodeReach(page, alreadyOpen = false) {
10159
11223
  async function startCodeReachOnOpenPages() {
10160
11224
  if (!pageCoverageInWorker() || OPEN_PAGES.size === 0) return;
10161
11225
  const started = Promise.all([...OPEN_PAGES].map((page) => maybeStartCodeReach(page, true)));
10162
- await Promise.race([started, new Promise((resolve5) => setTimeout(resolve5, CODE_REACH_START_WAIT_MS).unref())]);
11226
+ await Promise.race([started, new Promise((resolve6) => setTimeout(resolve6, CODE_REACH_START_WAIT_MS).unref())]);
10163
11227
  }
10164
11228
  function configuredCodeReachRoots() {
10165
11229
  const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
@@ -10173,7 +11237,7 @@ function configuredCodeReachRoots() {
10173
11237
  }
10174
11238
  } catch {
10175
11239
  }
10176
- return raw.split(path15.delimiter).filter(Boolean);
11240
+ return raw.split(path17.delimiter).filter(Boolean);
10177
11241
  }
10178
11242
  async function stopSinkCodeReach(sink, closing) {
10179
11243
  const stopping = [];
@@ -10228,8 +11292,8 @@ async function drainPendingProbes(capMs) {
10228
11292
  let cap;
10229
11293
  await Promise.race([
10230
11294
  Promise.allSettled(PENDING_PROBES),
10231
- new Promise((resolve5) => {
10232
- cap = setTimeout(resolve5, capMs);
11295
+ new Promise((resolve6) => {
11296
+ cap = setTimeout(resolve6, capMs);
10233
11297
  })
10234
11298
  ]);
10235
11299
  clearTimeout(cap);
@@ -10288,10 +11352,10 @@ async function readWebVitals(page) {
10288
11352
  const key = entry.name.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
10289
11353
  paint[key] = Math.round(entry.startTime);
10290
11354
  }
10291
- const readBuffered = (type, extra) => new Promise((resolve5) => {
11355
+ const readBuffered = (type, extra) => new Promise((resolve6) => {
10292
11356
  try {
10293
11357
  const PO = globalThis.PerformanceObserver;
10294
- if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve5(null);
11358
+ if (!PO || !(PO.supportedEntryTypes || []).includes(type)) return resolve6(null);
10295
11359
  const out = [];
10296
11360
  const po = new PO((list) => out.push(...list.getEntries()));
10297
11361
  po.observe({ type, buffered: true, ...extra });
@@ -10301,10 +11365,10 @@ async function readWebVitals(page) {
10301
11365
  po.disconnect();
10302
11366
  } catch {
10303
11367
  }
10304
- resolve5(out);
11368
+ resolve6(out);
10305
11369
  }, 0);
10306
11370
  } catch {
10307
- resolve5(null);
11371
+ resolve6(null);
10308
11372
  }
10309
11373
  });
10310
11374
  const [lcpRaw, shiftRaw, eventRaw, firstInputRaw] = await Promise.all([
@@ -10421,12 +11485,12 @@ var PAGE_INVENTORY_IN_PAGE_CAP = 2e3;
10421
11485
  var PAGE_INVENTORY_EVAL_TIMEOUT_MS = 2e3;
10422
11486
  var MAX_PAGE_INVENTORY_WINDOWS = 64;
10423
11487
  function withTimeout(promise, ms) {
10424
- return new Promise((resolve5, reject) => {
11488
+ return new Promise((resolve6, reject) => {
10425
11489
  const timer = setTimeout(() => reject(new Error("page inventory read timed out")), ms);
10426
11490
  promise.then(
10427
11491
  (value) => {
10428
11492
  clearTimeout(timer);
10429
- resolve5(value);
11493
+ resolve6(value);
10430
11494
  },
10431
11495
  (error) => {
10432
11496
  clearTimeout(timer);
@@ -10545,6 +11609,20 @@ var PATCHED_BROWSERS = /* @__PURE__ */ new WeakSet();
10545
11609
  var CHAIN_METHOD_SET = new Set(CHAIN_METHODS);
10546
11610
  var ACTION_METHOD_SET = new Set(ACTION_METHODS);
10547
11611
  var LOCATOR_METHOD_SET = new Set(LOCATOR_METHODS);
11612
+ var EARLY_READ_ACTIONS = /* @__PURE__ */ new Set([
11613
+ "click",
11614
+ "dblclick",
11615
+ "tap",
11616
+ "press",
11617
+ "pressSequentially",
11618
+ "type",
11619
+ "check",
11620
+ "uncheck",
11621
+ "selectOption",
11622
+ "dispatchEvent"
11623
+ ]);
11624
+ var CAPTURE_DEADLINE_MS = 500;
11625
+ var EARLY_READ_TRUSTED_MS = 250;
10548
11626
  var CAPTURED_ATTRS_ARG = {
10549
11627
  keep: [...CAPTURED_ATTRIBUTES],
10550
11628
  tagRoles: TAG_TO_ROLE,
@@ -10563,15 +11641,36 @@ var PROBE_INIT_SCRIPT = `(() => {
10563
11641
  globalThis[${JSON.stringify(PROBE_GLOBAL)}] = (el) => probe(el, arg);
10564
11642
  })();`;
10565
11643
  var PROBE_UNSEEDED_PAGES = /* @__PURE__ */ new WeakSet();
10566
- function probeElement(page, target) {
10567
- const shipSource = () => target.evaluate(probeElementAttrs, CAPTURED_ATTRS_ARG);
11644
+ var PAGE_NAVIGATIONS = /* @__PURE__ */ new WeakMap();
11645
+ function readyProbeWorld(page) {
11646
+ if (!currentSink) return;
11647
+ try {
11648
+ const ready = internalCall(
11649
+ page,
11650
+ () => page.mainFrame().locator(":root").evaluate(() => 0, void 0, { timeout: CAPTURE_DEADLINE_MS })
11651
+ ).then(
11652
+ () => void 0,
11653
+ () => void 0
11654
+ );
11655
+ PENDING_PROBES.add(ready);
11656
+ void ready.then(() => PENDING_PROBES.delete(ready));
11657
+ } catch {
11658
+ }
11659
+ }
11660
+ function probeElement(page, target, timeout) {
11661
+ const options = timeout != null ? { timeout } : void 0;
11662
+ const shipSource = () => target.evaluate(probeElementAttrs, CAPTURED_ATTRS_ARG, options);
10568
11663
  if (!page || PROBE_UNSEEDED_PAGES.has(page)) return internalCall(page, shipSource);
10569
11664
  return internalCall(
10570
11665
  page,
10571
- () => target.evaluate((el, name) => {
10572
- const seeded = globalThis[name];
10573
- return typeof seeded === "function" ? seeded(el) : null;
10574
- }, PROBE_GLOBAL).then((attrs) => {
11666
+ () => target.evaluate(
11667
+ (el, name) => {
11668
+ const seeded = globalThis[name];
11669
+ return typeof seeded === "function" ? seeded(el) : null;
11670
+ },
11671
+ PROBE_GLOBAL,
11672
+ options
11673
+ ).then((attrs) => {
10575
11674
  if (attrs) return attrs;
10576
11675
  PROBE_UNSEEDED_PAGES.add(page);
10577
11676
  return shipSource();
@@ -10606,54 +11705,60 @@ async function ariaSnapshotJSONBestEffort(target, timeout) {
10606
11705
  return null;
10607
11706
  }
10608
11707
  }
10609
- function startElementCapture(sink, page, target, seq, callerLocation, used) {
10610
- const probe = probeElement(page, target);
10611
- const settledProbe = probe.then(
10612
- () => void 0,
10613
- () => void 0
10614
- );
10615
- PENDING_PROBES.add(settledProbe);
10616
- settledProbe.then(() => PENDING_PROBES.delete(settledProbe));
10617
- sink.capturePromises.push(settledProbe);
10618
- const resolveAttrs = (async () => {
10619
- let deadline;
10620
- try {
10621
- const attrs = await Promise.race([
10622
- probe,
10623
- new Promise((_, reject) => {
10624
- deadline = setTimeout(() => reject(new Error("locator capture timeout")), 500);
10625
- })
10626
- ]);
10627
- const role = resolveAriaRole({ ...attrs, accessibleName: null });
10628
- const isFormField = FORM_FIELD_TAGS.has(attrs.tagName);
10629
- const exactName = exactAccessibleName(attrs, role);
10630
- const aria = exactName === null && (role || isFormField) ? await internalCall(page, () => ariaSnapshotBestEffort(target, 500)) : null;
10631
- const accessibleName = exactName ?? (extractAccessibleName(aria) || approximateAccessibleName({ ...attrs, accessibleName: null }));
10632
- sink.capturedLocators[seq] = {
10633
- location: callerLocation,
10634
- used,
10635
- // hasLabel/selectorCounts inform alternative generation only —
10636
- // keep the stored element to the wire shape. rolePosition and
10637
- // ancestors ARE wire fields: the server's renamed-element match
10638
- // uses them at heal time.
10639
- element: {
10640
- tagName: attrs.tagName,
10641
- attributes: attrs.attributes,
10642
- textContent: attrs.textContent,
10643
- accessibleName,
10644
- center: attrs.center,
10645
- ...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
10646
- ...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
10647
- },
10648
- alternatives: generateAlternatives({ ...attrs, accessibleName })
10649
- };
10650
- } catch {
10651
- if (callerLocation) sink.probedLocations.delete(callerLocation);
10652
- } finally {
10653
- clearTimeout(deadline);
10654
- }
11708
+ async function readElementSnapshot(sink, page, target, callerLocation, used) {
11709
+ let deadline;
11710
+ try {
11711
+ const probe = probeElement(page, target, CAPTURE_DEADLINE_MS);
11712
+ const settledProbe = probe.then(
11713
+ () => void 0,
11714
+ () => void 0
11715
+ );
11716
+ PENDING_PROBES.add(settledProbe);
11717
+ settledProbe.then(() => PENDING_PROBES.delete(settledProbe));
11718
+ sink.capturePromises.push(settledProbe);
11719
+ const attrs = await Promise.race([
11720
+ probe,
11721
+ new Promise((_, reject) => {
11722
+ deadline = setTimeout(() => reject(new Error("locator capture timeout")), CAPTURE_DEADLINE_MS);
11723
+ })
11724
+ ]);
11725
+ const role = resolveAriaRole({ ...attrs, accessibleName: null });
11726
+ const isFormField = FORM_FIELD_TAGS.has(attrs.tagName);
11727
+ const exactName = exactAccessibleName(attrs, role);
11728
+ const aria = exactName === null && (role || isFormField) ? await internalCall(page, () => ariaSnapshotBestEffort(target, CAPTURE_DEADLINE_MS)) : null;
11729
+ const accessibleName = exactName ?? (extractAccessibleName(aria) || approximateAccessibleName({ ...attrs, accessibleName: null }));
11730
+ return {
11731
+ location: callerLocation,
11732
+ used,
11733
+ // hasLabel/selectorCounts inform alternative generation only —
11734
+ // keep the stored element to the wire shape. rolePosition and
11735
+ // ancestors ARE wire fields: the server's renamed-element match
11736
+ // uses them at heal time.
11737
+ element: {
11738
+ tagName: attrs.tagName,
11739
+ attributes: attrs.attributes,
11740
+ textContent: attrs.textContent,
11741
+ accessibleName,
11742
+ center: attrs.center,
11743
+ ...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
11744
+ ...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
11745
+ },
11746
+ alternatives: generateAlternatives({ ...attrs, accessibleName })
11747
+ };
11748
+ } catch {
11749
+ return null;
11750
+ } finally {
11751
+ clearTimeout(deadline);
11752
+ }
11753
+ }
11754
+ function startElementCapture(sink, page, target, seq, callerLocation, used, early = null, earlyFirst = false) {
11755
+ const capture = (async () => {
11756
+ const readNow = () => readElementSnapshot(sink, page, target, callerLocation, used);
11757
+ const snapshot = earlyFirst ? await early ?? await readNow() : await readNow() ?? await early;
11758
+ if (snapshot) sink.capturedLocators[seq] = snapshot;
11759
+ else if (callerLocation) sink.probedLocations.delete(callerLocation);
10655
11760
  })();
10656
- sink.capturePromises.push(resolveAttrs);
11761
+ sink.capturePromises.push(capture);
10657
11762
  }
10658
11763
  function recordLocatorPage(sink, page, locator, location, arrival) {
10659
11764
  try {
@@ -10665,7 +11770,7 @@ function knowLocatorCall(sink, page, target, location, origin) {
10665
11770
  if (!sink?.pause || !location) return null;
10666
11771
  let settle = () => {
10667
11772
  };
10668
- const settled = new Promise((resolve5) => settle = resolve5);
11773
+ const settled = new Promise((resolve6) => settle = resolve6);
10669
11774
  const call = { page, target, location, origin, picked: false, endable: false, settled, settle };
10670
11775
  sink.locatorCalls.set(callSiteLine(location), call);
10671
11776
  return call;
@@ -10773,7 +11878,7 @@ function endedWaiting(sink, call) {
10773
11878
  return "finish";
10774
11879
  }
10775
11880
  function stopSite(sink, location) {
10776
- if (path15.dirname(path15.resolve(location.file)) !== __dirname) return stopCallSite(location);
11881
+ if (path17.dirname(path17.resolve(location.file)) !== __dirname) return stopCallSite(location);
10777
11882
  return inFlightCallSite([...sink.locatorCalls.values()].map((call) => call.location));
10778
11883
  }
10779
11884
  function stopBarPage(sink, context, of) {
@@ -10788,7 +11893,7 @@ async function showStop(sink, context, title, location, reason, call, of, failur
10788
11893
  const page = stopBarPage(sink, context, of);
10789
11894
  const guessed = !("page" in of);
10790
11895
  if (!testInfo) return void 0;
10791
- const bar = !!page && testInfo.project?.use?.headless === false;
11896
+ const bar = !!page && pauseGateFromTestInfo(testInfo, sink.headless).headless === false;
10792
11897
  const place = runChannelOpen() ? callSitePlace(location) : null;
10793
11898
  if (!bar && !place) return void 0;
10794
11899
  const failing = failure?.failed ?? null;
@@ -10896,6 +12001,7 @@ async function showStop(sink, context, title, location, reason, call, of, failur
10896
12001
  }
10897
12002
  return "resume";
10898
12003
  }
12004
+ const stoppedOn = call?.page === page ? call : null;
10899
12005
  return internalCall(
10900
12006
  page,
10901
12007
  () => pauseHere(
@@ -10904,7 +12010,14 @@ async function showStop(sink, context, title, location, reason, call, of, failur
10904
12010
  location,
10905
12011
  call: title,
10906
12012
  attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
10907
- target: call?.page === page ? call.target : null,
12013
+ target: stoppedOn ? {
12014
+ highlight: () => highlightAtStop(
12015
+ page,
12016
+ stoppedOn.target,
12017
+ "call",
12018
+ locatorSource(stoppedOn.target, stoppedOn.origin.method, stoppedOn.origin.args)
12019
+ )
12020
+ } : null,
10908
12021
  ...failure ? { failure: true } : sink.functionEnded ? {} : { end: true }
10909
12022
  },
10910
12023
  probe,
@@ -10951,7 +12064,7 @@ function endTest(sink, call, title, location, reason) {
10951
12064
  return "end";
10952
12065
  }
10953
12066
  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.`
12067
+ call && !takesEndSignal(sink.testInfo.config?.version) ? `${title} at ${line} runs before the test ends: a locator call is given up from Playwright 1.62 on. The test is skipped as the call returns.` : `${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
12068
  );
10956
12069
  console.log(`[piwi] ${said}`);
10957
12070
  tellHub({ type: "notice", message: said });
@@ -11110,8 +12223,8 @@ function errorPlace(error, windows = process.platform === "win32") {
11110
12223
  for (const line of typeof stack === "string" ? stack.split("\n") : []) {
11111
12224
  const frame = /^\s*at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?$/.exec(line);
11112
12225
  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;
12226
+ if (!frame || !file || !(windows ? path17.win32 : path17.posix).isAbsolute(file)) continue;
12227
+ if (/[\\/]node_modules[\\/]/.test(file) || path17.dirname(path17.resolve(file)) === __dirname) continue;
11115
12228
  return { file, line: Number(frame[2]), column: Number(frame[3]) };
11116
12229
  }
11117
12230
  return null;
@@ -11122,7 +12235,7 @@ function callInProgress(testInfo) {
11122
12235
  if (!Array.isArray(steps)) return;
11123
12236
  for (const step of steps) {
11124
12237
  if (step.endWallTime) continue;
11125
- const at = step.stack?.[0];
12238
+ const at = step.stack?.[0] ?? step.location;
11126
12239
  const called = step.category === "pw:api" || step.category === "expect" || step.category === "test.step";
11127
12240
  if (called && typeof at?.file === "string" && typeof at.line === "number") {
11128
12241
  found = {
@@ -11142,13 +12255,13 @@ function stepInProgress(testInfo) {
11142
12255
  function runningCall(testInfo) {
11143
12256
  const call = callInProgress(testInfo);
11144
12257
  if (!call?.title) return null;
11145
- const file = path15.relative(process.cwd(), call.place.file).split(path15.sep).join("/");
12258
+ const file = path17.relative(process.cwd(), call.place.file).split(path17.sep).join("/");
11146
12259
  return { title: call.title, file, line: call.place.line };
11147
12260
  }
11148
12261
  function abortion(signal) {
11149
- return new Promise((resolve5) => {
11150
- if (signal.aborted) resolve5();
11151
- else signal.addEventListener("abort", () => resolve5(), { once: true });
12262
+ return new Promise((resolve6) => {
12263
+ if (signal.aborted) resolve6();
12264
+ else signal.addEventListener("abort", () => resolve6(), { once: true });
11152
12265
  });
11153
12266
  }
11154
12267
  var END_STOP_MS = 2e3;
@@ -11183,7 +12296,7 @@ function releaseCall(sink, settled) {
11183
12296
  }
11184
12297
  function releasedRequestsEnded(sink) {
11185
12298
  if (sink.releasedRequests.size === 0) return Promise.resolve();
11186
- return new Promise((resolve5) => sink.onReleasedRequestsEnded = resolve5);
12299
+ return new Promise((resolve6) => sink.onReleasedRequestsEnded = resolve6);
11187
12300
  }
11188
12301
  async function awaitReleasedCalls(sink) {
11189
12302
  if (sink.released.length === 0) return;
@@ -11196,13 +12309,13 @@ async function awaitReleasedCalls(sink) {
11196
12309
  }
11197
12310
  const quiet = now - sink.releasedQuietSince;
11198
12311
  if (quiet >= RELEASED_QUIET_MS) return;
11199
- await new Promise((resolve5) => setTimeout(resolve5, Math.min(RELEASED_QUIET_MS - quiet, deadline - now)));
12312
+ await new Promise((resolve6) => setTimeout(resolve6, Math.min(RELEASED_QUIET_MS - quiet, deadline - now)));
11200
12313
  }
11201
12314
  }
11202
12315
  async function settledWithin(pending, ms) {
11203
12316
  if (pending.length === 0) return;
11204
12317
  let timer;
11205
- await Promise.race([Promise.allSettled(pending), new Promise((resolve5) => timer = setTimeout(resolve5, ms))]);
12318
+ await Promise.race([Promise.allSettled(pending), new Promise((resolve6) => timer = setTimeout(resolve6, ms))]);
11206
12319
  clearTimeout(timer);
11207
12320
  }
11208
12321
  function endStopsWithTestFunction(testInfo, sink) {
@@ -11235,7 +12348,7 @@ async function pauseInPlace(page, playwrightPause, location) {
11235
12348
  if (!sink || !testInfo || !location || !context || !contextDebugger(context)) {
11236
12349
  return leaveToPlaywright(context, playwrightPause);
11237
12350
  }
11238
- if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo)))) {
12351
+ if (!sink.pause && (!canRecordFromHere() || pauseSkipReason(pauseGateFromTestInfo(testInfo, sink.headless)))) {
11239
12352
  return leaveToPlaywright(context, playwrightPause);
11240
12353
  }
11241
12354
  sink.pause ??= pauseState(NO_BREAKPOINTS);
@@ -11303,7 +12416,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
11303
12416
  const callerLocation = sink ? captureCallerLocation() : null;
11304
12417
  if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
11305
12418
  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;
12419
+ const ending = sink?.pause && !sink.ended && takesEndSignal(sink.testInfo?.config?.version) ? withEndSignal(EXPECT_METHOD, callArgs, sink.end.signal) : null;
11307
12420
  if (call && ending) call.endable = true;
11308
12421
  const expectArgs = ending ?? callArgs;
11309
12422
  try {
@@ -11356,7 +12469,7 @@ function wrapLocator(page, locator, originMethod, originArgs) {
11356
12469
  raw: `${originMethod}(${JSON.stringify(originArgs)})`
11357
12470
  };
11358
12471
  const call = knowLocatorCall(sink, page, target, callerLocation, used);
11359
- const ending = sink.pause && !sink.ended ? withEndSignal(String(prop), callArgs, sink.end.signal) : null;
12472
+ const ending = sink.pause && !sink.ended && takesEndSignal(sink.testInfo?.config?.version) ? withEndSignal(String(prop), callArgs, sink.end.signal) : null;
11360
12473
  if (call && ending) call.endable = true;
11361
12474
  const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
11362
12475
  let seq = -1;
@@ -11369,7 +12482,10 @@ function wrapLocator(page, locator, originMethod, originArgs) {
11369
12482
  alternatives: []
11370
12483
  });
11371
12484
  }
12485
+ const early = !alreadyProbed && EARLY_READ_ACTIONS.has(prop) ? readElementSnapshot(sink, page, target, callerLocation, used) : null;
11372
12486
  let result;
12487
+ const startedAt = Date.now();
12488
+ const navigations = PAGE_NAVIGATIONS.get(page) ?? 0;
11373
12489
  try {
11374
12490
  result = await callMethod(target, prop, ending ?? callArgs);
11375
12491
  } catch (error) {
@@ -11382,7 +12498,8 @@ function wrapLocator(page, locator, originMethod, originArgs) {
11382
12498
  if (endsNow(sink)) throw skipOf(sink);
11383
12499
  if (!alreadyProbed && !call?.picked) {
11384
12500
  if (callerLocation) sink.probedLocations.add(callerLocation);
11385
- startElementCapture(sink, page, target, seq, callerLocation, used);
12501
+ const earlyFirst = (PAGE_NAVIGATIONS.get(page) ?? 0) !== navigations || Date.now() - startedAt < EARLY_READ_TRUSTED_MS;
12502
+ startElementCapture(sink, page, target, seq, callerLocation, used, early, earlyFirst);
11386
12503
  }
11387
12504
  return result;
11388
12505
  };
@@ -11444,7 +12561,7 @@ function instrumentPage(page) {
11444
12561
  page.close = async (...args) => {
11445
12562
  let closed = () => {
11446
12563
  };
11447
- CLOSING_PAGES.set(page, new Promise((resolve5) => closed = resolve5));
12564
+ CLOSING_PAGES.set(page, new Promise((resolve6) => closed = resolve6));
11448
12565
  try {
11449
12566
  await settleBeforeClose({ page });
11450
12567
  return await originalClose(...args);
@@ -11472,10 +12589,14 @@ function instrumentPage(page) {
11472
12589
  };
11473
12590
  }
11474
12591
  if (typeof page.on === "function") {
12592
+ page.on("domcontentloaded", () => readyProbeWorld(page));
11475
12593
  page.on("framenavigated", (frame) => {
11476
12594
  PROBE_UNSEEDED_PAGES.delete(page);
11477
12595
  try {
11478
- if (frame === page.mainFrame()) noteNavigation(page);
12596
+ if (frame === page.mainFrame()) {
12597
+ PAGE_NAVIGATIONS.set(page, (PAGE_NAVIGATIONS.get(page) ?? 0) + 1);
12598
+ noteNavigation(page);
12599
+ }
11479
12600
  } catch {
11480
12601
  }
11481
12602
  });
@@ -11756,8 +12877,8 @@ async function flushSink(sink, testInfo) {
11756
12877
  let drainDeadline;
11757
12878
  await Promise.race([
11758
12879
  Promise.allSettled(sink.capturePromises),
11759
- new Promise((resolve5) => {
11760
- drainDeadline = setTimeout(resolve5, 2e3);
12880
+ new Promise((resolve6) => {
12881
+ drainDeadline = setTimeout(resolve6, 2e3);
11761
12882
  })
11762
12883
  ]);
11763
12884
  clearTimeout(drainDeadline);
@@ -11964,7 +13085,7 @@ var runControl = {
11964
13085
  return;
11965
13086
  }
11966
13087
  if (!sink.pause) {
11967
- const why = sink.testInfo ? pauseSkipReason({ ...pauseGateFromTestInfo(sink.testInfo), channel: true }) : null;
13088
+ const why = sink.testInfo ? pauseSkipReason({ ...pauseGateFromTestInfo(sink.testInfo, sink.headless), channel: true }) : null;
11968
13089
  tellHub({ type: "notice", message: `This test does not stop${why ? `: ${why}` : ""}.` });
11969
13090
  return;
11970
13091
  }
@@ -11986,7 +13107,7 @@ var runControl = {
11986
13107
  }
11987
13108
  };
11988
13109
  function runFile(file) {
11989
- return path15.relative(process.cwd(), file).split(path15.sep).join("/") || file;
13110
+ return path17.relative(process.cwd(), file).split(path17.sep).join("/") || file;
11990
13111
  }
11991
13112
  function testBegin(testInfo, cannotPause) {
11992
13113
  const title = testInfo.titlePath.slice(1);
@@ -12026,6 +13147,7 @@ var piwiFixtures = {
12026
13147
  startResourceLedger(playwright, workerInfo.workerIndex);
12027
13148
  observeApiRequests(playwright, apiCallRecorder);
12028
13149
  openLiveView(workerInfo);
13150
+ if (liveViewOpen()) trackCalls(playwright);
12029
13151
  if (await openRunChannel(workerInfo, runControl)) watchCalls(playwright);
12030
13152
  try {
12031
13153
  await use();
@@ -12063,17 +13185,18 @@ var piwiFixtures = {
12063
13185
  // (attach the collected data) at teardown. Runs for every test without being
12064
13186
  // requested, so suites that never destructure `page` are still captured.
12065
13187
  piwiCapture: [
12066
- async ({ screenshot, baseURL }, use, testInfo) => {
13188
+ async ({ screenshot, baseURL, headless }, use, testInfo) => {
12067
13189
  boxCaptureFrames();
12068
13190
  const conflict = labModeConflict();
12069
13191
  if (conflict) throw new Error(conflict);
12070
13192
  const sink = createSink();
12071
13193
  sink.testInfo = testInfo;
12072
13194
  sink.baseURL = baseURL ?? null;
13195
+ sink.headless = headless;
12073
13196
  if (keepsFailureScreenshots(screenshot, testInfo.retry)) sink.failureScreenshot = screenshot;
12074
13197
  const pauseSet = currentPauseSet();
12075
13198
  const channel = runChannelOpen();
12076
- const cannotPause = pauseSkipReason({ ...pauseGateFromTestInfo(testInfo), channel });
13199
+ const cannotPause = pauseSkipReason({ ...pauseGateFromTestInfo(testInfo, headless), channel });
12077
13200
  if (pauseSet.size || channel) {
12078
13201
  if (!cannotPause) sink.pause = pauseState(pauseSet, pauseNextTest);
12079
13202
  else if (pauseSet.size) logPauseSkipOnce(cannotPause);
@@ -12105,7 +13228,7 @@ var piwiFixtures = {
12105
13228
  });
12106
13229
  activeResourceLedger()?.testStarted(testInfo);
12107
13230
  currentSink = sink;
12108
- liveViewTestBegan(testInfo.title, () => runningCall(testInfo));
13231
+ liveViewTestBegan(testInfo.title, () => runningCall(testInfo), mayPause(pauseGateFromTestInfo(testInfo)));
12109
13232
  const showRunChannel = hideRunChannel();
12110
13233
  try {
12111
13234
  await armStops(OPEN_CONTEXTS);
@@ -12123,9 +13246,10 @@ var piwiFixtures = {
12123
13246
  const applied = sink.probeInterception?.applied() ?? false;
12124
13247
  const level = sink.probeItem.level ?? "client";
12125
13248
  const handled = level === "server" && applied ? classifyProbeHandled({
12126
- consoleErrors: sink.consoleEntries.filter((e) => e.type === "error").length,
13249
+ consoleErrors: probeConsoleErrors(sink.consoleEntries),
12127
13250
  dialogs: sink.dialogs.length,
12128
13251
  backendError: sink.probeInterception?.serverError() ?? false,
13252
+ faultIsTheError: faultIsTheError(sink.probeItem.fault),
12129
13253
  pageErrors: sink.probePageErrors,
12130
13254
  blankPage: sink.probeBlankPage
12131
13255
  }) : "n/a";