@piwitests/reporter 0.47.0 → 0.49.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.
@@ -57,7 +57,7 @@ __export(capture_fixtures_exports, {
57
57
  stopResourceLedger: () => stopResourceLedger
58
58
  });
59
59
  module.exports = __toCommonJS(capture_fixtures_exports);
60
- var path12 = __toESM(require("path"));
60
+ var path13 = __toESM(require("path"));
61
61
  var import_node_zlib2 = require("zlib");
62
62
 
63
63
  // ../picker-dom/src/probe.ts
@@ -1120,6 +1120,83 @@ function showPickerChoices(arg) {
1120
1120
  doc.body.appendChild(wrap);
1121
1121
  }
1122
1122
 
1123
+ // ../picker-dom/src/overlay-pause.ts
1124
+ function showPauseBar(arg) {
1125
+ const g = globalThis;
1126
+ const doc = g.document;
1127
+ delete g.__piwiPauseState;
1128
+ if (!doc || !doc.body) {
1129
+ g.__piwiPauseState = "resume";
1130
+ return;
1131
+ }
1132
+ if (typeof g.__piwiPauseCleanup === "function") g.__piwiPauseCleanup();
1133
+ const Z = 2147483600;
1134
+ const bar = doc.createElement("div");
1135
+ bar.id = "__piwi-pause-bar";
1136
+ bar.setAttribute("role", "toolbar");
1137
+ bar.setAttribute("aria-label", "Piwi: test paused");
1138
+ bar.style.cssText = `position:fixed;top:0;left:0;right:0;z-index:${Z + 4};display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:8px 12px;background:#111827;color:#f9fafb;border-bottom:2px solid #a78bfa;box-shadow:0 4px 20px rgba(0,0,0,.35);font:13px/1.5 system-ui,sans-serif;`;
1139
+ const where = doc.createElement("div");
1140
+ where.style.cssText = "font-weight:600;white-space:nowrap;";
1141
+ where.textContent = `Paused at ${arg.place}${arg.attempt ? ` \xB7 attempt ${arg.attempt}` : ""}`;
1142
+ const what = doc.createElement("code");
1143
+ what.style.cssText = "flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#d8b4fe;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;";
1144
+ what.textContent = arg.locator ? `${arg.action} \xB7 ${arg.locator}` : arg.action;
1145
+ what.title = what.textContent;
1146
+ bar.appendChild(where);
1147
+ bar.appendChild(what);
1148
+ const done = (choice) => {
1149
+ cleanup();
1150
+ g.__piwiPauseState = choice;
1151
+ };
1152
+ const onKey = (e) => {
1153
+ if (e.key !== "Escape") return;
1154
+ e.preventDefault();
1155
+ e.stopImmediatePropagation();
1156
+ done("resume");
1157
+ };
1158
+ const cleanup = () => {
1159
+ doc.removeEventListener("keydown", onKey, true);
1160
+ bar.remove();
1161
+ delete g.__piwiPauseCleanup;
1162
+ };
1163
+ const buttons = [
1164
+ ["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
1165
+ ["Step", "step", "Pause again at the next action", false],
1166
+ ["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
1167
+ ["Finish", "finish", "Run on and pause no more in this test", false]
1168
+ ];
1169
+ for (const [label, choice, hint, primary] of buttons) {
1170
+ const btn = doc.createElement("button");
1171
+ btn.type = "button";
1172
+ btn.textContent = label;
1173
+ btn.title = hint;
1174
+ btn.style.cssText = "border-radius:6px;padding:4px 12px;cursor:pointer;font:600 12px/1.5 system-ui,sans-serif;" + (primary ? "background:#7c3aed;color:#fff;border:1px solid #7c3aed;" : "background:#1f2937;color:#f3f4f6;border:1px solid #374151;");
1175
+ btn.addEventListener("click", (e) => {
1176
+ e.preventDefault();
1177
+ e.stopImmediatePropagation();
1178
+ done(choice);
1179
+ });
1180
+ bar.appendChild(btn);
1181
+ }
1182
+ g.__piwiPauseCleanup = cleanup;
1183
+ doc.addEventListener("keydown", onKey, true);
1184
+ doc.body.appendChild(bar);
1185
+ }
1186
+ function pauseAnswered() {
1187
+ const g = globalThis;
1188
+ if (g.__piwiPauseState !== void 0) return true;
1189
+ return !g.document?.getElementById("__piwi-pause-bar");
1190
+ }
1191
+ function takePauseState() {
1192
+ const g = globalThis;
1193
+ const state = g.__piwiPauseState;
1194
+ if (typeof g.__piwiPauseCleanup === "function") g.__piwiPauseCleanup();
1195
+ delete g.__piwiPauseCleanup;
1196
+ delete g.__piwiPauseState;
1197
+ return typeof state === "string" ? state : null;
1198
+ }
1199
+
1123
1200
  // ../core/src/locator-generation.ts
1124
1201
  var TAG_TO_ROLE = {
1125
1202
  a: "link",
@@ -2035,8 +2112,8 @@ function inventoryPageKey(url) {
2035
2112
  // ../core/src/page-key.ts
2036
2113
  function normalizeRoute(url) {
2037
2114
  try {
2038
- const parsed = new URL(url);
2039
- let pathname = parsed.pathname;
2115
+ const parsed2 = new URL(url);
2116
+ let pathname = parsed2.pathname;
2040
2117
  pathname = pathname.replace(/\/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(?=\/|$)/g, "/:jwt");
2041
2118
  pathname = pathname.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?=\/|$)/gi, "/:uuid");
2042
2119
  pathname = pathname.replace(/\/[0-9ABCDEFGHJKMNPQRSTVWXYZ]{26}(?=\/|$)/gi, "/:ulid");
@@ -2044,8 +2121,8 @@ function normalizeRoute(url) {
2044
2121
  pathname = pathname.replace(/\/[0-9a-f]{32,}(?=\/|$)/gi, "/:token");
2045
2122
  pathname = pathname.replace(/\/\d+(?=\/|$)/g, "/:id");
2046
2123
  let queryString = "";
2047
- if (parsed.search) {
2048
- const params = new URLSearchParams(parsed.search);
2124
+ if (parsed2.search) {
2125
+ const params = new URLSearchParams(parsed2.search);
2049
2126
  const redacted = new URLSearchParams();
2050
2127
  for (const [key] of params) {
2051
2128
  redacted.set(key, "<redacted>");
@@ -2158,7 +2235,7 @@ var DEFAULTS = {
2158
2235
  defaultCapture: true,
2159
2236
  checkBaseUrl: false,
2160
2237
  streaming: true,
2161
- streamingBatchSize: 5,
2238
+ streamingBatchSize: 20,
2162
2239
  streamingBatchDelay: 2e3,
2163
2240
  maxStreamBufferBytes: 100 * 1024 * 1024,
2164
2241
  submitTimeout: 15 * 60 * 1e3,
@@ -2212,6 +2289,10 @@ var PIWI_ENV_KEYS = {
2212
2289
  aiResponseWaitTimeout: "PIWI_AI_RESPONSE_WAIT_TIMEOUT",
2213
2290
  aiScreenshotFallback: "PIWI_AI_SCREENSHOT_FALLBACK"
2214
2291
  };
2292
+ var PIWI_LOCAL_DEBUG_ENV = {
2293
+ pauseAt: "PIWI_PAUSE_AT",
2294
+ editorSend: "PIWI_EDITOR_SEND"
2295
+ };
2215
2296
  var PIWI_PROBE_ENV = {
2216
2297
  flag: "PIWI_PROBE",
2217
2298
  plan: "PIWI_PROBE_PLAN",
@@ -2265,8 +2346,8 @@ function parseRouteKey(routeKey2) {
2265
2346
  return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
2266
2347
  }
2267
2348
  function patternToRegex(pattern) {
2268
- const path13 = pattern.split("?")[0] ?? pattern;
2269
- const escaped = path13.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
2349
+ const path14 = pattern.split("?")[0] ?? pattern;
2350
+ const escaped = path14.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
2270
2351
  return new RegExp(`^${escaped}$`);
2271
2352
  }
2272
2353
  function suitePathEqual(a, b) {
@@ -2289,13 +2370,13 @@ function matchProbeItem(plan, test) {
2289
2370
  function requestMatchesRoute(method, url, routeKey2) {
2290
2371
  const { method: rm, pattern } = parseRouteKey(routeKey2);
2291
2372
  if (rm && rm !== method.toUpperCase()) return false;
2292
- let path13;
2373
+ let path14;
2293
2374
  try {
2294
- path13 = new URL(url).pathname;
2375
+ path14 = new URL(url).pathname;
2295
2376
  } catch {
2296
- path13 = url.split("?")[0] ?? url;
2377
+ path14 = url.split("?")[0] ?? url;
2297
2378
  }
2298
- return patternToRegex(pattern).test(path13);
2379
+ return patternToRegex(pattern).test(path14);
2299
2380
  }
2300
2381
  function createProbeState() {
2301
2382
  return { navigated: false, matches: 0 };
@@ -2317,10 +2398,10 @@ function isProbeMode() {
2317
2398
  function loadPlan() {
2318
2399
  if (planCache !== void 0) return planCache;
2319
2400
  planCache = null;
2320
- const path13 = process.env[PIWI_PROBE_ENV.plan];
2321
- if (path13) {
2401
+ const path14 = process.env[PIWI_PROBE_ENV.plan];
2402
+ if (path14) {
2322
2403
  try {
2323
- planCache = JSON.parse(fs2.readFileSync(path13, "utf8"));
2404
+ planCache = JSON.parse(fs2.readFileSync(path14, "utf8"));
2324
2405
  } catch {
2325
2406
  planCache = null;
2326
2407
  }
@@ -2342,10 +2423,10 @@ function outcomeFromStatus(status, applied) {
2342
2423
  return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
2343
2424
  }
2344
2425
  function recordProbeOutcome(line) {
2345
- const path13 = process.env[PIWI_PROBE_ENV.results];
2346
- if (!path13) return;
2426
+ const path14 = process.env[PIWI_PROBE_ENV.results];
2427
+ if (!path14) return;
2347
2428
  try {
2348
- fs2.appendFileSync(path13, `${JSON.stringify(line)}
2429
+ fs2.appendFileSync(path14, `${JSON.stringify(line)}
2349
2430
  `);
2350
2431
  } catch {
2351
2432
  }
@@ -2376,24 +2457,24 @@ function faultRouteAction(fault) {
2376
2457
  return fault === "slow" ? { kind: "delay", ms: SLOW_FAULT_DELAY_MS } : null;
2377
2458
  }
2378
2459
  function dropOneField(body) {
2379
- let parsed;
2460
+ let parsed2;
2380
2461
  try {
2381
- parsed = JSON.parse(body);
2462
+ parsed2 = JSON.parse(body);
2382
2463
  } catch {
2383
2464
  return body;
2384
2465
  }
2385
- if (Array.isArray(parsed)) {
2466
+ if (Array.isArray(parsed2)) {
2386
2467
  let key;
2387
- for (const el of parsed) {
2468
+ for (const el of parsed2) {
2388
2469
  if (el && typeof el === "object" && !Array.isArray(el)) {
2389
2470
  key ??= Object.keys(el)[0];
2390
2471
  if (key) delete el[key];
2391
2472
  }
2392
2473
  }
2393
- return JSON.stringify(parsed);
2474
+ return JSON.stringify(parsed2);
2394
2475
  }
2395
- if (parsed && typeof parsed === "object") {
2396
- const obj = parsed;
2476
+ if (parsed2 && typeof parsed2 === "object") {
2477
+ const obj = parsed2;
2397
2478
  const key = Object.keys(obj)[0];
2398
2479
  if (key) delete obj[key];
2399
2480
  return JSON.stringify(obj);
@@ -3391,11 +3472,11 @@ function deriveFailedLocator(testInfo) {
3391
3472
  ${err.stack ?? ""}`.replace(ANSI_RE2, "");
3392
3473
  const line = /^\s*Locator:\s*(.+)$/m.exec(text);
3393
3474
  if (!line) continue;
3394
- const parsed = parseLeafLocatorExpression(line[1].trim());
3395
- if (!parsed) continue;
3475
+ const parsed2 = parseLeafLocatorExpression(line[1].trim());
3476
+ if (!parsed2) continue;
3396
3477
  const loc = err.location;
3397
3478
  const location = loc ? `${path5.relative(process.cwd(), loc.file).split(path5.sep).join("/")}:${loc.line}:${loc.column}` : null;
3398
- return { method: parsed.method, args: parsed.args, location };
3479
+ return { method: parsed2.method, args: parsed2.args, location };
3399
3480
  }
3400
3481
  return null;
3401
3482
  }
@@ -3417,25 +3498,16 @@ async function cleanupPicker(page) {
3417
3498
  } catch {
3418
3499
  }
3419
3500
  }
3420
- async function runLocatorPicker(page, testInfo, failed, probe) {
3501
+ async function pickLocator(page, probe, failing) {
3421
3502
  try {
3422
- testInfo.setTimeout(0);
3423
- const rendered = failed ? renderFailing(failed) : null;
3424
- console.log(
3425
- `
3426
- [piwi] "${testInfo.title}" ${testInfo.status} \u2014 ${rendered ? "locator picker" : "inspector"} open in the browser: ${rendered ? `click the element that should replace ${rendered}` : "click any element to generate locators for it"} (\u2191/\u2193 to select a parent/child, Esc to skip).`
3427
- );
3428
- const overlayArg = { transport: "global", failing: rendered };
3503
+ const overlayArg = { transport: "global", failing };
3429
3504
  await page.evaluate(installPickerOverlay, overlayArg);
3430
3505
  await page.waitForFunction(() => globalThis.__piwiPickState !== void 0, void 0, {
3431
3506
  timeout: 0,
3432
3507
  polling: 250
3433
3508
  });
3434
3509
  const state = await page.evaluate(() => globalThis.__piwiPickState);
3435
- if (state !== "picked") {
3436
- await cleanupPicker(page);
3437
- return null;
3438
- }
3510
+ if (state !== "picked") return null;
3439
3511
  const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
3440
3512
  const attrs = await handle.evaluate(probe.fn, probe.arg);
3441
3513
  await handle.dispose();
@@ -3473,11 +3545,10 @@ async function runLocatorPicker(page, testInfo, failed, probe) {
3473
3545
  );
3474
3546
  if (ranked.length === 0) {
3475
3547
  console.log("[piwi] No stable locator could be generated for the picked element \u2014 nothing recorded.");
3476
- await cleanupPicker(page);
3477
3548
  return null;
3478
3549
  }
3479
3550
  await page.evaluate(showPickerChoices, {
3480
- failing: rendered,
3551
+ failing,
3481
3552
  choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
3482
3553
  });
3483
3554
  await page.waitForFunction(() => globalThis.__piwiPickChoice !== void 0, void 0, {
@@ -3485,31 +3556,45 @@ async function runLocatorPicker(page, testInfo, failed, probe) {
3485
3556
  polling: 250
3486
3557
  });
3487
3558
  const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
3488
- await cleanupPicker(page);
3489
3559
  if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
3490
3560
  const picked = { ...ranked[choice], pickedByUser: true };
3491
- const alternatives = [picked, ...ranked.filter((_, i) => i !== choice)];
3492
- const element = {
3493
- tagName: attrs.tagName,
3494
- attributes: attrs.attributes,
3495
- textContent: attrs.textContent,
3496
- accessibleName,
3497
- center: attrs.center,
3498
- ...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
3499
- ...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
3561
+ return {
3562
+ picked,
3563
+ alternatives: [picked, ...ranked.filter((_, i) => i !== choice)],
3564
+ element: {
3565
+ tagName: attrs.tagName,
3566
+ attributes: attrs.attributes,
3567
+ textContent: attrs.textContent,
3568
+ accessibleName,
3569
+ center: attrs.center,
3570
+ ...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
3571
+ ...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
3572
+ },
3573
+ ...anchors.length > 0 ? { anchors } : {}
3500
3574
  };
3575
+ } finally {
3576
+ await cleanupPicker(page);
3577
+ }
3578
+ }
3579
+ async function runLocatorPicker(page, testInfo, failed, probe) {
3580
+ try {
3581
+ testInfo.setTimeout(0);
3582
+ const rendered = failed ? renderFailing(failed) : null;
3583
+ console.log(
3584
+ `
3585
+ [piwi] "${testInfo.title}" ${testInfo.status} \u2014 ${rendered ? "locator picker" : "inspector"} open in the browser: ${rendered ? `click the element that should replace ${rendered}` : "click any element to generate locators for it"} (\u2191/\u2193 to select a parent/child, Esc to skip).`
3586
+ );
3587
+ const pick = await pickLocator(page, probe, rendered);
3588
+ if (!pick) return null;
3501
3589
  const location = failed?.location ?? null;
3502
3590
  console.log(
3503
3591
  failed ? `[piwi] Replacement picked for ${rendered}${location ? ` at ${location}` : ""}:
3504
- [piwi] ${picked.locator}` : `[piwi] Locator picked while inspecting:
3505
- [piwi] ${picked.locator}`
3592
+ [piwi] ${pick.picked.locator}` : `[piwi] Locator picked while inspecting:
3593
+ [piwi] ${pick.picked.locator}`
3506
3594
  );
3507
3595
  return {
3508
3596
  failing: failed ? { method: failed.method, args: failed.args, rendered, location } : null,
3509
- picked,
3510
- alternatives,
3511
- element,
3512
- ...anchors.length > 0 ? { anchors } : {}
3597
+ ...pick
3513
3598
  };
3514
3599
  } catch {
3515
3600
  return null;
@@ -3542,8 +3627,205 @@ function applyPickToSnapshots(snapshots, pick) {
3542
3627
  return true;
3543
3628
  }
3544
3629
 
3545
- // src/internal/support/aria-sampling.ts
3630
+ // src/internal/capture/pause-at.ts
3546
3631
  var path6 = __toESM(require("path"));
3632
+
3633
+ // ../core/src/editor-send.ts
3634
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
3635
+ function parsePairing(text) {
3636
+ const trimmed = text.trim();
3637
+ const hash = trimmed.indexOf("#");
3638
+ if (hash < 0) return null;
3639
+ const url = trimmed.slice(0, hash);
3640
+ const token = trimmed.slice(hash + 1);
3641
+ if (!/^[A-Za-z0-9_-]{16,}$/.test(token)) return null;
3642
+ let parsed2;
3643
+ try {
3644
+ parsed2 = new URL(url);
3645
+ } catch {
3646
+ return null;
3647
+ }
3648
+ if (parsed2.protocol !== "http:" || !LOOPBACK_HOSTS.has(parsed2.hostname) || !parsed2.port) return null;
3649
+ return { url: `${parsed2.origin}${parsed2.pathname}`, token };
3650
+ }
3651
+ function isInsidePath(file) {
3652
+ if (!file || file.length > 1e3 || file.includes("\0")) return false;
3653
+ if (file.startsWith("/") || file.startsWith("\\") || /^[A-Za-z]:/.test(file)) return false;
3654
+ return file.split(/[\\/]/).every((part) => part !== "..");
3655
+ }
3656
+
3657
+ // src/internal/capture/pause-at.ts
3658
+ var PauseSet = class {
3659
+ constructor(lines) {
3660
+ this.lines = lines;
3661
+ }
3662
+ get size() {
3663
+ return this.lines.size;
3664
+ }
3665
+ /** Whether a call site (`file:line:col`, as `captureCallerLocation` writes it) is on one of the lines. */
3666
+ matches(callerLocation) {
3667
+ if (!callerLocation) return false;
3668
+ const col = callerLocation.lastIndexOf(":");
3669
+ return col > 0 && this.lines.has(callerLocation.slice(0, col));
3670
+ }
3671
+ };
3672
+ var EMPTY = new PauseSet(/* @__PURE__ */ new Set());
3673
+ function callSiteFile(file, cwd) {
3674
+ const slashed = file.replace(/\\/g, "/");
3675
+ const absolute = path6.isAbsolute(file) || path6.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
3676
+ let rel = absolute ? path6.relative(cwd, file) : path6.normalize(slashed);
3677
+ rel = rel.split(path6.sep).join("/").replace(/\\/g, "/");
3678
+ if (rel.startsWith("./")) rel = rel.slice(2);
3679
+ return rel;
3680
+ }
3681
+ var ignoredLogged = /* @__PURE__ */ new Set();
3682
+ function parsePauseAt(value, cwd = process.cwd()) {
3683
+ if (!value?.trim()) return EMPTY;
3684
+ const lines = /* @__PURE__ */ new Set();
3685
+ for (const raw of value.split(";")) {
3686
+ const entry = raw.trim();
3687
+ if (!entry) continue;
3688
+ const m = /^(.+):(\d+)$/.exec(entry);
3689
+ const line = m ? Number(m[2]) : 0;
3690
+ if (!m?.[1] || line < 1) {
3691
+ if (!ignoredLogged.has(entry)) {
3692
+ ignoredLogged.add(entry);
3693
+ console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt}: ignoring "${entry}": expected file:line`);
3694
+ }
3695
+ continue;
3696
+ }
3697
+ lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
3698
+ }
3699
+ return lines.size ? new PauseSet(lines) : EMPTY;
3700
+ }
3701
+ var parsed = null;
3702
+ function currentPauseSet() {
3703
+ const value = process.env[PIWI_LOCAL_DEBUG_ENV.pauseAt];
3704
+ if (!parsed || parsed.value !== value) parsed = { value, set: parsePauseAt(value) };
3705
+ return parsed.set;
3706
+ }
3707
+ function pauseGateFromTestInfo(testInfo) {
3708
+ const use = testInfo.project?.use ?? {};
3709
+ return { ci: process.env.CI, headless: use.headless };
3710
+ }
3711
+ function pauseSkipReason(gate) {
3712
+ if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
3713
+ if (gate.headless !== false)
3714
+ return "the browser is headless \u2014 re-run with --headed (or set use: { headless: false })";
3715
+ return null;
3716
+ }
3717
+ function rearmedTimeout(before, pausedMs) {
3718
+ return before > 0 ? before + Math.max(0, Math.round(pausedMs)) : 0;
3719
+ }
3720
+ function pausePlace(callerLocation) {
3721
+ const col = callerLocation.lastIndexOf(":");
3722
+ const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
3723
+ return fileLine.slice(fileLine.lastIndexOf("/") + 1);
3724
+ }
3725
+ function assertionLabel(expression, isNot) {
3726
+ const [first = "", ...rest] = expression.split(".").filter(Boolean);
3727
+ const name = first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
3728
+ return `${isNot ? "not." : ""}${name || "expect"}`;
3729
+ }
3730
+ function pickPlace(callerLocation) {
3731
+ const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
3732
+ if (!m) return null;
3733
+ const file = m[1];
3734
+ if (!isInsidePath(file)) return null;
3735
+ return { file, line: Number(m[2]) };
3736
+ }
3737
+ function pickPayload(locator, callerLocation) {
3738
+ const at = pickPlace(callerLocation);
3739
+ return at ? { kind: "locator", text: locator, at } : { kind: "locator", text: locator };
3740
+ }
3741
+ var postFailureLogged = false;
3742
+ async function postPickToEditor(pairingAddress, payload, fetchImpl = fetch) {
3743
+ if (!pairingAddress) return false;
3744
+ const logOnce = (why) => {
3745
+ if (postFailureLogged) return;
3746
+ postFailureLogged = true;
3747
+ console.log(`[piwi] Could not send the picked locator to the editor: ${why}.`);
3748
+ };
3749
+ const pairing = parsePairing(pairingAddress);
3750
+ if (!pairing) {
3751
+ logOnce(`${PIWI_LOCAL_DEBUG_ENV.editorSend} is not a pairing address`);
3752
+ return false;
3753
+ }
3754
+ try {
3755
+ const response = await fetchImpl(pairing.url, {
3756
+ method: "POST",
3757
+ headers: { Authorization: `Bearer ${pairing.token}`, "Content-Type": "application/json" },
3758
+ body: JSON.stringify(payload),
3759
+ signal: AbortSignal.timeout(5e3)
3760
+ });
3761
+ if (response.ok) return true;
3762
+ logOnce(`the editor answered ${response.status}`);
3763
+ } catch (error) {
3764
+ logOnce(error instanceof Error ? error.message : String(error));
3765
+ }
3766
+ return false;
3767
+ }
3768
+ var skipLogged = false;
3769
+ function logPauseSkipOnce(reason) {
3770
+ if (skipLogged) return;
3771
+ skipLogged = true;
3772
+ console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
3773
+ }
3774
+ var HIGHLIGHT_MS = 2e3;
3775
+ async function pauseHere(page, testInfo, point, probe, onPick) {
3776
+ const before = testInfo.timeout;
3777
+ const started = Date.now();
3778
+ let shown = null;
3779
+ try {
3780
+ testInfo.setTimeout(0);
3781
+ const highlight = point.target?.highlight;
3782
+ if (typeof highlight === "function") {
3783
+ let timer;
3784
+ shown = await Promise.race([
3785
+ Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
3786
+ new Promise((resolve3) => {
3787
+ timer = setTimeout(() => resolve3(null), HIGHLIGHT_MS);
3788
+ })
3789
+ ]);
3790
+ clearTimeout(timer);
3791
+ }
3792
+ const arg = {
3793
+ place: pausePlace(point.location),
3794
+ attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
3795
+ action: point.action,
3796
+ locator: point.locator
3797
+ };
3798
+ console.log(
3799
+ `[piwi] Paused at ${point.location} before ${point.locator ? `${point.action} \xB7 ${point.locator}` : point.action}: Resume, Step, Pick a locator or Finish in the browser (Esc resumes).`
3800
+ );
3801
+ for (; ; ) {
3802
+ await page.evaluate(showPauseBar, arg);
3803
+ await page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 });
3804
+ const choice = await page.evaluate(takePauseState);
3805
+ if (choice === "resume" || choice === "step" || choice === "finish") return choice;
3806
+ if (choice !== "pick") continue;
3807
+ const picked = await pickLocator(page, probe, null).catch(() => null);
3808
+ if (picked) await onPick(picked);
3809
+ }
3810
+ } catch {
3811
+ return "resume";
3812
+ } finally {
3813
+ try {
3814
+ const dispose = shown?.dispose;
3815
+ const hide = page.hideHighlight;
3816
+ if (typeof dispose === "function") await dispose.call(shown);
3817
+ else if (typeof hide === "function") await hide.call(page);
3818
+ } catch {
3819
+ }
3820
+ try {
3821
+ testInfo.setTimeout(rearmedTimeout(before, Date.now() - started));
3822
+ } catch {
3823
+ }
3824
+ }
3825
+ }
3826
+
3827
+ // src/internal/support/aria-sampling.ts
3828
+ var path7 = __toESM(require("path"));
3547
3829
  var os3 = __toESM(require("os"));
3548
3830
  var fs4 = __toESM(require("fs"));
3549
3831
 
@@ -3559,7 +3841,7 @@ function ariaSampleIdentity(filePath, title) {
3559
3841
  return `${filePath}\0${title}`;
3560
3842
  }
3561
3843
  function getAriaSampleFilePath(projectName) {
3562
- return path6.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
3844
+ return path7.join(os3.tmpdir(), `piwi-dashboard-aria-sample-${hashForProject(projectName)}.json`);
3563
3845
  }
3564
3846
  var cachedSets = /* @__PURE__ */ new Map();
3565
3847
  function loadAriaSampleSet(projectName) {
@@ -3567,9 +3849,9 @@ function loadAriaSampleSet(projectName) {
3567
3849
  let set = null;
3568
3850
  try {
3569
3851
  const raw = fs4.readFileSync(getAriaSampleFilePath(projectName), "utf8");
3570
- const parsed = JSON.parse(raw);
3571
- if (parsed.projectName === projectName && Array.isArray(parsed.identities)) {
3572
- set = new Set(parsed.identities.filter((x) => typeof x === "string"));
3852
+ const parsed2 = JSON.parse(raw);
3853
+ if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
3854
+ set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
3573
3855
  }
3574
3856
  } catch {
3575
3857
  set = null;
@@ -3578,7 +3860,7 @@ function loadAriaSampleSet(projectName) {
3578
3860
  return set;
3579
3861
  }
3580
3862
  function relativeTestFile2(file) {
3581
- return path6.relative(process.cwd(), file).split(path6.sep).join("/");
3863
+ return path7.relative(process.cwd(), file).split(path7.sep).join("/");
3582
3864
  }
3583
3865
  function isDueForAriaSample(testInfo) {
3584
3866
  const projectName = process.env.PIWI_PROJECT_NAME;
@@ -3590,20 +3872,20 @@ function isDueForAriaSample(testInfo) {
3590
3872
 
3591
3873
  // src/internal/capture/quiet-capture.ts
3592
3874
  var import_node_module = require("module");
3593
- var path8 = __toESM(require("path"));
3875
+ var path9 = __toESM(require("path"));
3594
3876
 
3595
3877
  // src/internal/support/reporter-version.ts
3596
3878
  var fs5 = __toESM(require("fs"));
3597
- var path7 = __toESM(require("path"));
3879
+ var path8 = __toESM(require("path"));
3598
3880
  var CANDIDATE_OFFSETS = ["..", "../../.."];
3599
3881
  function findOwnPackageJson(fromDir) {
3600
3882
  for (const offset of CANDIDATE_OFFSETS) {
3601
3883
  try {
3602
- const pkgPath = path7.resolve(fromDir, offset, "package.json");
3884
+ const pkgPath = path8.resolve(fromDir, offset, "package.json");
3603
3885
  const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
3604
3886
  const { name, version } = pkg;
3605
3887
  if (name === "@piwitests/reporter" && typeof version === "string")
3606
- return { version, root: path7.dirname(pkgPath) };
3888
+ return { version, root: path8.dirname(pkgPath) };
3607
3889
  } catch {
3608
3890
  }
3609
3891
  }
@@ -3647,7 +3929,7 @@ function resolveBoxing() {
3647
3929
  if (typeof set !== "function") continue;
3648
3930
  return {
3649
3931
  set,
3650
- prefixes: [path8.dirname(playwrightPackageJson), path8.join(root, "dist") + path8.sep]
3932
+ prefixes: [path9.dirname(playwrightPackageJson), path9.join(root, "dist") + path9.sep]
3651
3933
  };
3652
3934
  }
3653
3935
  } catch {
@@ -3675,7 +3957,7 @@ function internalCall(owner, run) {
3675
3957
  // src/internal/capture/code-reach.ts
3676
3958
  var crypto2 = __toESM(require("crypto"));
3677
3959
  var fs6 = __toESM(require("fs"));
3678
- var path9 = __toESM(require("path"));
3960
+ var path10 = __toESM(require("path"));
3679
3961
  var import_node_child_process = require("child_process");
3680
3962
 
3681
3963
  // ../core/src/source-map.ts
@@ -3938,12 +4220,12 @@ function repositoryRoot(from) {
3938
4220
  }).trim();
3939
4221
  } catch {
3940
4222
  }
3941
- cachedRepoRoot = { from, root: path9.resolve(root) };
4223
+ cachedRepoRoot = { from, root: path10.resolve(root) };
3942
4224
  return cachedRepoRoot.root;
3943
4225
  }
3944
4226
  function codeReachRoots(configDir, configured) {
3945
4227
  const repoRoot = repositoryRoot(configDir);
3946
- const roots = configured?.length ? configured.map((r) => path9.resolve(configDir, r)) : [configDir];
4228
+ const roots = configured?.length ? configured.map((r) => path10.resolve(configDir, r)) : [configDir];
3947
4229
  return { roots: [.../* @__PURE__ */ new Set([...roots, repoRoot])], repoRoot };
3948
4230
  }
3949
4231
  function isFile(file) {
@@ -3956,10 +4238,10 @@ function isFile(file) {
3956
4238
  function resolveSourcePath(source, roots) {
3957
4239
  const normalized = normalizeSourcePath(source);
3958
4240
  if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
3959
- if (path9.isAbsolute(normalized) && isFile(normalized)) return path9.resolve(normalized);
3960
- const relative8 = normalized.replace(/^\/+/, "");
4241
+ if (path10.isAbsolute(normalized) && isFile(normalized)) return path10.resolve(normalized);
4242
+ const relative9 = normalized.replace(/^\/+/, "");
3961
4243
  for (const root of roots) {
3962
- const candidate = path9.resolve(root, relative8);
4244
+ const candidate = path10.resolve(root, relative9);
3963
4245
  if (isFile(candidate)) return candidate;
3964
4246
  }
3965
4247
  return null;
@@ -3975,10 +4257,10 @@ function viteBases(entries) {
3975
4257
  function fileForUrl(url, roots, bases) {
3976
4258
  let pathname;
3977
4259
  try {
3978
- const parsed = new URL(url);
3979
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && parsed.protocol !== "file:") return null;
3980
- pathname = parsed.pathname;
3981
- const base = bases.get(parsed.origin);
4260
+ const parsed2 = new URL(url);
4261
+ if (parsed2.protocol !== "http:" && parsed2.protocol !== "https:" && parsed2.protocol !== "file:") return null;
4262
+ pathname = parsed2.pathname;
4263
+ const base = bases.get(parsed2.origin);
3982
4264
  if (base && pathname.startsWith(base)) pathname = pathname.slice(base.length - 1);
3983
4265
  } catch {
3984
4266
  return null;
@@ -3991,7 +4273,7 @@ function fileForUrl(url, roots, bases) {
3991
4273
  return file;
3992
4274
  }
3993
4275
  function relativeToRepo(file, repoRoot) {
3994
- return path9.relative(repoRoot, file).split(path9.sep).join("/");
4276
+ return path10.relative(repoRoot, file).split(path10.sep).join("/");
3995
4277
  }
3996
4278
  function scriptHash(entry) {
3997
4279
  return crypto2.createHash("sha1").update(entry.source ?? "").digest("hex");
@@ -4111,7 +4393,7 @@ async function stopCodeReach(page) {
4111
4393
 
4112
4394
  // src/internal/capture/resource-ledger.ts
4113
4395
  var fs8 = __toESM(require("fs"));
4114
- var path11 = __toESM(require("path"));
4396
+ var path12 = __toESM(require("path"));
4115
4397
  var import_node_module2 = require("module");
4116
4398
 
4117
4399
  // src/internal/capture/worker-metrics.ts
@@ -4119,7 +4401,7 @@ var import_node_perf_hooks = require("perf_hooks");
4119
4401
 
4120
4402
  // src/internal/support/system-readers.ts
4121
4403
  var fs7 = __toESM(require("fs"));
4122
- var path10 = __toESM(require("path"));
4404
+ var path11 = __toESM(require("path"));
4123
4405
  var CLK_TCK = 100;
4124
4406
  function readText(file) {
4125
4407
  try {
@@ -4205,7 +4487,7 @@ var ProcFs = class {
4205
4487
  }
4206
4488
  /** Whether this looks like a Linux `/proc` at all. */
4207
4489
  available() {
4208
- return fs7.existsSync(path10.join(this.root, "stat"));
4490
+ return fs7.existsSync(path11.join(this.root, "stat"));
4209
4491
  }
4210
4492
  /** Every process, keyed by pid. */
4211
4493
  table() {
@@ -4218,32 +4500,32 @@ var ProcFs = class {
4218
4500
  }
4219
4501
  for (const name of names) {
4220
4502
  if (!/^\d+$/.test(name)) continue;
4221
- const text = readText(path10.join(this.root, name, "stat"));
4503
+ const text = readText(path11.join(this.root, name, "stat"));
4222
4504
  const stat = text ? parseProcStat(Number(name), text) : null;
4223
4505
  if (stat) out.set(stat.pid, stat);
4224
4506
  }
4225
4507
  return out;
4226
4508
  }
4227
4509
  stat(pid) {
4228
- const text = readText(path10.join(this.root, String(pid), "stat"));
4510
+ const text = readText(path11.join(this.root, String(pid), "stat"));
4229
4511
  return text ? parseProcStat(pid, text) : null;
4230
4512
  }
4231
4513
  runWaitMs(pid) {
4232
- const text = readText(path10.join(this.root, String(pid), "schedstat"));
4514
+ const text = readText(path11.join(this.root, String(pid), "schedstat"));
4233
4515
  return text ? parseSchedstat(text) : null;
4234
4516
  }
4235
4517
  cmdline(pid) {
4236
- return (readText(path10.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
4518
+ return (readText(path11.join(this.root, String(pid), "cmdline")) ?? "").split("\0").join(" ").trim();
4237
4519
  }
4238
4520
  /** A field of `/proc/<pid>/status`, in kB: `VmRSS`, `VmHWM`. */
4239
4521
  statusKb(pid, key) {
4240
- const text = readText(path10.join(this.root, String(pid), "status"));
4522
+ const text = readText(path11.join(this.root, String(pid), "status"));
4241
4523
  return text ? parseKbField(text, key) : null;
4242
4524
  }
4243
4525
  /** Proportional set size: shared pages split between the processes sharing them, so a sum counts them once. */
4244
4526
  async pssKb(pid) {
4245
4527
  try {
4246
- return parseKbField(await fs7.promises.readFile(path10.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
4528
+ return parseKbField(await fs7.promises.readFile(path11.join(this.root, String(pid), "smaps_rollup"), "utf8"), "Pss");
4247
4529
  } catch {
4248
4530
  return null;
4249
4531
  }
@@ -4251,29 +4533,29 @@ var ProcFs = class {
4251
4533
  /** Reset a process's peak RSS (`VmHWM`) to its current RSS. */
4252
4534
  resetPeakRss(pid) {
4253
4535
  try {
4254
- fs7.writeFileSync(path10.join(this.root, String(pid), "clear_refs"), "5");
4536
+ fs7.writeFileSync(path11.join(this.root, String(pid), "clear_refs"), "5");
4255
4537
  return true;
4256
4538
  } catch {
4257
4539
  return false;
4258
4540
  }
4259
4541
  }
4260
4542
  cpuTicks() {
4261
- const text = readText(path10.join(this.root, "stat"));
4543
+ const text = readText(path11.join(this.root, "stat"));
4262
4544
  return text ? parseCpuTicks(text) : null;
4263
4545
  }
4264
4546
  /** Total stall time of a resource's pressure file, in µs. */
4265
4547
  pressureUs(resource) {
4266
- const text = readText(path10.join(this.root, "pressure", resource));
4548
+ const text = readText(path11.join(this.root, "pressure", resource));
4267
4549
  return text ? parsePsiTotal(text) : null;
4268
4550
  }
4269
4551
  memAvailableKb() {
4270
- const text = readText(path10.join(this.root, "meminfo"));
4552
+ const text = readText(path11.join(this.root, "meminfo"));
4271
4553
  return text ? parseKbField(text, "MemAvailable") : null;
4272
4554
  }
4273
4555
  /** Open file descriptors of a process. */
4274
4556
  fdCount(pid) {
4275
4557
  try {
4276
- return fs7.readdirSync(path10.join(this.root, String(pid), "fd")).length;
4558
+ return fs7.readdirSync(path11.join(this.root, String(pid), "fd")).length;
4277
4559
  } catch {
4278
4560
  return null;
4279
4561
  }
@@ -4435,10 +4717,10 @@ var PAGE_READ_TIMEOUT_MS = 1e3;
4435
4717
  var HOOK_PHASES = /* @__PURE__ */ new Set(["beforeAll", "afterAll", "beforeEach", "afterEach"]);
4436
4718
  var KIND_ORDER = { browser: 0, context: 1, page: 2, request: 3 };
4437
4719
  function relativePath(file) {
4438
- return path11.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path11.sep).join("/");
4720
+ return path12.relative(process.cwd(), file.replace(/^file:\/\//, "")).split(path12.sep).join("/");
4439
4721
  }
4440
4722
  function isLibraryFile(file) {
4441
- return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path11.sep}node_modules${path11.sep}`) || file.includes("/node_modules/") || file === __filename;
4723
+ return file.startsWith("node:") || file.startsWith("internal/") || file.includes(`${path12.sep}node_modules${path12.sep}`) || file.includes("/node_modules/") || file === __filename;
4442
4724
  }
4443
4725
  function userSite(stack) {
4444
4726
  for (const line of (stack ?? "").split("\n").slice(1)) {
@@ -4486,7 +4768,7 @@ function playwrightTestInfoSource() {
4486
4768
  try {
4487
4769
  const packageJson = resolvePlaywrightPackageJson();
4488
4770
  if (!packageJson) return () => null;
4489
- const globals = (0, import_node_module2.createRequire)(packageJson)(path11.join(path11.dirname(packageJson), "lib", "globals.js"));
4771
+ const globals = (0, import_node_module2.createRequire)(packageJson)(path12.join(path12.dirname(packageJson), "lib", "globals.js"));
4490
4772
  const read = globals.currentTestInfo;
4491
4773
  if (typeof read !== "function") return () => null;
4492
4774
  return () => {
@@ -5026,6 +5308,7 @@ function createSink() {
5026
5308
  flake: null,
5027
5309
  pickOffered: false,
5028
5310
  userPick: null,
5311
+ pause: null,
5029
5312
  locatorPages: new LocatorPageLog(),
5030
5313
  codeReach: null
5031
5314
  };
@@ -5042,11 +5325,11 @@ function configuredCodeReachRoots() {
5042
5325
  const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
5043
5326
  if (!raw) return null;
5044
5327
  try {
5045
- const parsed = JSON.parse(raw);
5046
- if (Array.isArray(parsed)) return parsed.filter((r) => typeof r === "string" && r !== "");
5328
+ const parsed2 = JSON.parse(raw);
5329
+ if (Array.isArray(parsed2)) return parsed2.filter((r) => typeof r === "string" && r !== "");
5047
5330
  } catch {
5048
5331
  }
5049
- return raw.split(path12.delimiter).filter(Boolean);
5332
+ return raw.split(path13.delimiter).filter(Boolean);
5050
5333
  }
5051
5334
  async function stopSinkCodeReach(sink, closing) {
5052
5335
  const session = sink.codeReach;
@@ -5061,7 +5344,7 @@ async function stopSinkCodeReach(sink, closing) {
5061
5344
  if (!entries) return;
5062
5345
  const info = sink.testInfo;
5063
5346
  const configFile = info?.config.configFile;
5064
- const configDir = configFile ? path12.dirname(configFile) : info?.config.rootDir ?? process.cwd();
5347
+ const configDir = configFile ? path13.dirname(configFile) : info?.config.rootDir ?? process.cwd();
5065
5348
  session.files = await resolveCodeReach(
5066
5349
  entries,
5067
5350
  codeReachRoots(configDir, configuredCodeReachRoots()),
@@ -5480,6 +5763,53 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
5480
5763
  } catch {
5481
5764
  }
5482
5765
  }
5766
+ function pausesAt(pause, callerLocation) {
5767
+ return !pause.finished && (pause.next || pause.set.matches(callerLocation));
5768
+ }
5769
+ function locatorSource(target, method, args) {
5770
+ try {
5771
+ const text = String(target);
5772
+ if (text && !text.startsWith("[object")) return text;
5773
+ } catch {
5774
+ }
5775
+ return renderFailing({ method, args });
5776
+ }
5777
+ async function pauseBefore(sink, page, target, callerLocation, action, origin) {
5778
+ const pause = sink.pause;
5779
+ const testInfo = sink.testInfo;
5780
+ if (!pause || !testInfo || !callerLocation) return;
5781
+ pause.next = false;
5782
+ try {
5783
+ const locator = locatorSource(target, origin.method, origin.args);
5784
+ const onPick = async (picked) => {
5785
+ const pick = {
5786
+ failing: { method: origin.method, args: origin.args, rendered: locator, location: callerLocation },
5787
+ ...picked
5788
+ };
5789
+ applyPickToSnapshots(sink.capturedLocators, pick);
5790
+ sink.probedLocations.add(callerLocation);
5791
+ const place = callerLocation.slice(0, callerLocation.lastIndexOf(":"));
5792
+ console.log(`[piwi] Locator picked at ${place}: ${picked.picked.locator}`);
5793
+ await postPickToEditor(
5794
+ process.env[PIWI_LOCAL_DEBUG_ENV.editorSend],
5795
+ pickPayload(picked.picked.locator, callerLocation)
5796
+ );
5797
+ };
5798
+ const choice = await internalCall(
5799
+ page,
5800
+ () => pauseHere(
5801
+ page,
5802
+ testInfo,
5803
+ { location: callerLocation, action, locator, target },
5804
+ { fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
5805
+ onPick
5806
+ )
5807
+ );
5808
+ if (choice === "step") pause.next = true;
5809
+ else if (choice === "finish") pause.finished = true;
5810
+ } catch {
5811
+ }
5812
+ }
5483
5813
  function wrapLocator(page, locator, originMethod, originArgs) {
5484
5814
  return new Proxy(locator, {
5485
5815
  get(target, prop) {
@@ -5501,6 +5831,12 @@ function wrapLocator(page, locator, originMethod, originArgs) {
5501
5831
  const isNot = Boolean(callArgs[1]?.isNot);
5502
5832
  const callerLocation = sink ? captureCallerLocation() : null;
5503
5833
  if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
5834
+ if (sink?.pause && pausesAt(sink.pause, callerLocation)) {
5835
+ await pauseBefore(sink, page, target, callerLocation, assertionLabel(expression, isNot), {
5836
+ method: originMethod,
5837
+ args: originArgs
5838
+ });
5839
+ }
5504
5840
  if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
5505
5841
  return callMethod(target, prop, callArgs);
5506
5842
  }
@@ -5539,6 +5875,9 @@ function wrapLocator(page, locator, originMethod, originArgs) {
5539
5875
  args: originArgs,
5540
5876
  raw: `${originMethod}(${JSON.stringify(originArgs)})`
5541
5877
  };
5878
+ if (sink.pause && pausesAt(sink.pause, callerLocation)) {
5879
+ await pauseBefore(sink, page, target, callerLocation, String(prop), used);
5880
+ }
5542
5881
  const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
5543
5882
  let seq = -1;
5544
5883
  if (!alreadyProbed) {
@@ -5999,6 +6338,12 @@ var piwiFixtures = {
5999
6338
  if (conflict) throw new Error(conflict);
6000
6339
  const sink = createSink();
6001
6340
  sink.testInfo = testInfo;
6341
+ const pauseSet = currentPauseSet();
6342
+ if (pauseSet.size) {
6343
+ const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
6344
+ if (reason) logPauseSkipOnce(reason);
6345
+ else sink.pause = { set: pauseSet, next: false, finished: false };
6346
+ }
6002
6347
  if (isFlakeMode()) {
6003
6348
  const plan = loadFlakePlan();
6004
6349
  const role = flakeRoleForTest(plan, {