@piwitests/reporter 0.48.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.
package/dist/index.js CHANGED
@@ -56,9 +56,9 @@ function defaultDesktopConfigPath() {
56
56
  }
57
57
  function readDesktopConfig(filePath) {
58
58
  try {
59
- const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
60
- const url = parsed?.url;
61
- const token = parsed?.token;
59
+ const parsed2 = JSON.parse(fs.readFileSync(filePath, "utf8"));
60
+ const url = parsed2?.url;
61
+ const token = parsed2?.token;
62
62
  if (typeof url !== "string" || !url) return null;
63
63
  if (typeof token !== "string" || !token) return null;
64
64
  return { url, token };
@@ -145,6 +145,10 @@ var PIWI_ENV_KEYS = {
145
145
  aiResponseWaitTimeout: "PIWI_AI_RESPONSE_WAIT_TIMEOUT",
146
146
  aiScreenshotFallback: "PIWI_AI_SCREENSHOT_FALLBACK"
147
147
  };
148
+ var PIWI_LOCAL_DEBUG_ENV = {
149
+ pauseAt: "PIWI_PAUSE_AT",
150
+ editorSend: "PIWI_EDITOR_SEND"
151
+ };
148
152
  var PIWI_DESKTOP_CONFIG_ENV = "PIWI_DESKTOP_CONFIG";
149
153
  var PIWI_DEFAULTED_CAPTURE_ENV = "PIWI_DEFAULTED_CAPTURE";
150
154
  var PIWI_SELECTION_ENV = {
@@ -320,8 +324,8 @@ var Logger = class {
320
324
  // src/internal/transport/http-client.ts
321
325
  function parseErrorMessage(text) {
322
326
  try {
323
- const parsed = JSON.parse(text);
324
- const message = parsed?.message;
327
+ const parsed2 = JSON.parse(text);
328
+ const message = parsed2?.message;
325
329
  return typeof message === "string" ? message : void 0;
326
330
  } catch {
327
331
  return void 0;
@@ -3780,9 +3784,9 @@ function loadAriaSampleSet(projectName) {
3780
3784
  let set = null;
3781
3785
  try {
3782
3786
  const raw = fs11.readFileSync(getAriaSampleFilePath(projectName), "utf8");
3783
- const parsed = JSON.parse(raw);
3784
- if (parsed.projectName === projectName && Array.isArray(parsed.identities)) {
3785
- set = new Set(parsed.identities.filter((x) => typeof x === "string"));
3787
+ const parsed2 = JSON.parse(raw);
3788
+ if (parsed2.projectName === projectName && Array.isArray(parsed2.identities)) {
3789
+ set = new Set(parsed2.identities.filter((x) => typeof x === "string"));
3786
3790
  }
3787
3791
  } catch {
3788
3792
  set = null;
@@ -3933,14 +3937,14 @@ var COMMITTED_MANIFEST_FILE = "piwi.manifest.json";
3933
3937
  var INSTRUMENTATION_MANIFEST_PATH = "/__piwi/manifest";
3934
3938
  var INSTRUMENTATION_HEADERS = ["x-piwi-logs", "x-piwi-trace"];
3935
3939
  function parseManifestJson(text) {
3936
- let parsed;
3940
+ let parsed2;
3937
3941
  try {
3938
- parsed = JSON.parse(text);
3942
+ parsed2 = JSON.parse(text);
3939
3943
  } catch {
3940
3944
  return null;
3941
3945
  }
3942
- if (!parsed || typeof parsed !== "object") return null;
3943
- const obj = parsed;
3946
+ if (!parsed2 || typeof parsed2 !== "object") return null;
3947
+ const obj = parsed2;
3944
3948
  const routes = Array.isArray(obj.routes) ? obj.routes.filter((r) => !!r && typeof r === "object").filter((r) => typeof r.method === "string" && typeof r.pattern === "string").map((r) => ({
3945
3949
  method: r.method,
3946
3950
  pattern: r.pattern,
@@ -4144,8 +4148,8 @@ function parseRouteKey(routeKey2) {
4144
4148
  return { method: routeKey2.slice(0, space).toUpperCase(), pattern: routeKey2.slice(space + 1) };
4145
4149
  }
4146
4150
  function patternToRegex(pattern) {
4147
- const path31 = pattern.split("?")[0] ?? pattern;
4148
- const escaped = path31.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
4151
+ const path32 = pattern.split("?")[0] ?? pattern;
4152
+ const escaped = path32.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[A-Za-z0-9_]+/g, "[^/]+");
4149
4153
  return new RegExp(`^${escaped}$`);
4150
4154
  }
4151
4155
  function suitePathEqual(a, b) {
@@ -4168,13 +4172,13 @@ function matchProbeItem(plan, test) {
4168
4172
  function requestMatchesRoute(method, url, routeKey2) {
4169
4173
  const { method: rm, pattern } = parseRouteKey(routeKey2);
4170
4174
  if (rm && rm !== method.toUpperCase()) return false;
4171
- let path31;
4175
+ let path32;
4172
4176
  try {
4173
- path31 = new URL(url).pathname;
4177
+ path32 = new URL(url).pathname;
4174
4178
  } catch {
4175
- path31 = url.split("?")[0] ?? url;
4179
+ path32 = url.split("?")[0] ?? url;
4176
4180
  }
4177
- return patternToRegex(pattern).test(path31);
4181
+ return patternToRegex(pattern).test(path32);
4178
4182
  }
4179
4183
  function createProbeState() {
4180
4184
  return { navigated: false, matches: 0 };
@@ -4196,10 +4200,10 @@ function isProbeMode() {
4196
4200
  function loadPlan() {
4197
4201
  if (planCache !== void 0) return planCache;
4198
4202
  planCache = null;
4199
- const path31 = process.env[PIWI_PROBE_ENV.plan];
4200
- if (path31) {
4203
+ const path32 = process.env[PIWI_PROBE_ENV.plan];
4204
+ if (path32) {
4201
4205
  try {
4202
- planCache = JSON.parse(fs14.readFileSync(path31, "utf8"));
4206
+ planCache = JSON.parse(fs14.readFileSync(path32, "utf8"));
4203
4207
  } catch {
4204
4208
  planCache = null;
4205
4209
  }
@@ -4221,10 +4225,10 @@ function outcomeFromStatus(status, applied) {
4221
4225
  return status === "failed" || status === "timedOut" || status === "interrupted" ? "noticed" : "not-noticed";
4222
4226
  }
4223
4227
  function recordProbeOutcome(line) {
4224
- const path31 = process.env[PIWI_PROBE_ENV.results];
4225
- if (!path31) return;
4228
+ const path32 = process.env[PIWI_PROBE_ENV.results];
4229
+ if (!path32) return;
4226
4230
  try {
4227
- fs14.appendFileSync(path31, `${JSON.stringify(line)}
4231
+ fs14.appendFileSync(path32, `${JSON.stringify(line)}
4228
4232
  `);
4229
4233
  } catch {
4230
4234
  }
@@ -4447,8 +4451,8 @@ var FLAKE_LAB_RUN_METADATA_KEY = "piwiFlakeLab";
4447
4451
  // ../core/src/page-key.ts
4448
4452
  function normalizeRoute(url) {
4449
4453
  try {
4450
- const parsed = new URL(url);
4451
- let pathname = parsed.pathname;
4454
+ const parsed2 = new URL(url);
4455
+ let pathname = parsed2.pathname;
4452
4456
  pathname = pathname.replace(/\/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(?=\/|$)/g, "/:jwt");
4453
4457
  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");
4454
4458
  pathname = pathname.replace(/\/[0-9ABCDEFGHJKMNPQRSTVWXYZ]{26}(?=\/|$)/gi, "/:ulid");
@@ -4456,8 +4460,8 @@ function normalizeRoute(url) {
4456
4460
  pathname = pathname.replace(/\/[0-9a-f]{32,}(?=\/|$)/gi, "/:token");
4457
4461
  pathname = pathname.replace(/\/\d+(?=\/|$)/g, "/:id");
4458
4462
  let queryString = "";
4459
- if (parsed.search) {
4460
- const params = new URLSearchParams(parsed.search);
4463
+ if (parsed2.search) {
4464
+ const params = new URLSearchParams(parsed2.search);
4461
4465
  const redacted = new URLSearchParams();
4462
4466
  for (const [key] of params) {
4463
4467
  redacted.set(key, "<redacted>");
@@ -4509,24 +4513,24 @@ function faultRouteAction(fault) {
4509
4513
  return fault === "slow" ? { kind: "delay", ms: SLOW_FAULT_DELAY_MS } : null;
4510
4514
  }
4511
4515
  function dropOneField(body) {
4512
- let parsed;
4516
+ let parsed2;
4513
4517
  try {
4514
- parsed = JSON.parse(body);
4518
+ parsed2 = JSON.parse(body);
4515
4519
  } catch {
4516
4520
  return body;
4517
4521
  }
4518
- if (Array.isArray(parsed)) {
4522
+ if (Array.isArray(parsed2)) {
4519
4523
  let key;
4520
- for (const el of parsed) {
4524
+ for (const el of parsed2) {
4521
4525
  if (el && typeof el === "object" && !Array.isArray(el)) {
4522
4526
  key ??= Object.keys(el)[0];
4523
4527
  if (key) delete el[key];
4524
4528
  }
4525
4529
  }
4526
- return JSON.stringify(parsed);
4530
+ return JSON.stringify(parsed2);
4527
4531
  }
4528
- if (parsed && typeof parsed === "object") {
4529
- const obj = parsed;
4532
+ if (parsed2 && typeof parsed2 === "object") {
4533
+ const obj = parsed2;
4530
4534
  const key = Object.keys(obj)[0];
4531
4535
  if (key) delete obj[key];
4532
4536
  return JSON.stringify(obj);
@@ -5334,8 +5338,8 @@ function ciBuildUrlFromMetadata(metadata) {
5334
5338
 
5335
5339
  // src/internal/submit/run-submitter.ts
5336
5340
  var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
5337
- function markSubmitFallback(run, path31, reason) {
5338
- run.metadata.ingestHealth = { submitFallback: { path: path31, reason } };
5341
+ function markSubmitFallback(run, path32, reason) {
5342
+ run.metadata.ingestHealth = { submitFallback: { path: path32, reason } };
5339
5343
  }
5340
5344
  var RunSubmitter = class {
5341
5345
  /**
@@ -7895,7 +7899,7 @@ function resourceTimeline(samples, censuses) {
7895
7899
  let startedAt = hasSamples ? samples.startedAt : Infinity;
7896
7900
  for (const { events } of workers) startedAt = Math.min(startedAt, events[0][0]);
7897
7901
  const shift = hasSamples ? samples.startedAt - startedAt : 0;
7898
- const relative13 = (points, by) => points.map(([at, value]) => [Math.round(at + by), value]);
7902
+ const relative14 = (points, by) => points.map(([at, value]) => [Math.round(at + by), value]);
7899
7903
  const trees = hasSamples ? workerTreesByIndex(samples, censuses) : [];
7900
7904
  const perWorker = Math.max(
7901
7905
  60,
@@ -7903,17 +7907,17 @@ function resourceTimeline(samples, censuses) {
7903
7907
  );
7904
7908
  return {
7905
7909
  startedAt,
7906
- cpuPct: hasSamples ? downsamplePoints(relative13(samples.cpuPct, shift), TIMELINE_SERIES_CAP, "mean") : [],
7907
- memoryBytes: hasSamples ? downsamplePoints(relative13(samples.memoryBytes, shift), TIMELINE_SERIES_CAP, "max") : [],
7910
+ cpuPct: hasSamples ? downsamplePoints(relative14(samples.cpuPct, shift), TIMELINE_SERIES_CAP, "mean") : [],
7911
+ memoryBytes: hasSamples ? downsamplePoints(relative14(samples.memoryBytes, shift), TIMELINE_SERIES_CAP, "max") : [],
7908
7912
  pages: workers.map(({ worker, events }) => {
7909
- const points = relative13(events, -startedAt);
7913
+ const points = relative14(events, -startedAt);
7910
7914
  const last = points.pop();
7911
7915
  return { worker, points: [...downsamplePoints(points, TIMELINE_WORKER_POINTS_CAP - 1, "max"), last] };
7912
7916
  }),
7913
7917
  workers: trees.map((tree) => ({
7914
7918
  worker: tree.worker,
7915
- cpuCores: downsamplePoints(relative13(tree.cpuCores, shift), perWorker, "mean"),
7916
- memoryBytes: downsamplePoints(relative13(tree.memoryBytes, shift), perWorker, "max")
7919
+ cpuCores: downsamplePoints(relative14(tree.cpuCores, shift), perWorker, "mean"),
7920
+ memoryBytes: downsamplePoints(relative14(tree.memoryBytes, shift), perWorker, "max")
7917
7921
  }))
7918
7922
  };
7919
7923
  }
@@ -8076,8 +8080,8 @@ function formatTimeout(ms) {
8076
8080
  }
8077
8081
  function routeOf(url) {
8078
8082
  try {
8079
- const parsed = new URL(url);
8080
- return `${parsed.pathname}${parsed.search}` || "/";
8083
+ const parsed2 = new URL(url);
8084
+ return `${parsed2.pathname}${parsed2.search}` || "/";
8081
8085
  } catch {
8082
8086
  return url;
8083
8087
  }
@@ -8097,14 +8101,14 @@ function textOfGetByText(locator) {
8097
8101
  const m = /^getByText\(\s*(['"`])((?:\\.|(?!\1)[^\\\n\r\u2028\u2029])*)\1\s*(?:,\s*\{[^}]*\})?\s*\)$/.exec(locator);
8098
8102
  return m ? m[2] : null;
8099
8103
  }
8100
- function receivedDisplay(parsed) {
8101
- const received = parsed.received ?? "";
8102
- if (parsed.assertion !== "toHaveURL") return received;
8104
+ function receivedDisplay(parsed2) {
8105
+ const received = parsed2.received ?? "";
8106
+ if (parsed2.assertion !== "toHaveURL") return received;
8103
8107
  const m = /^"?(https?:\/\/[^"\s]+)"?$/.exec(received);
8104
8108
  return m ? `"${routeOf(m[1])}"` : received;
8105
8109
  }
8106
- function firstLineFallback(parsed) {
8107
- const line = (parsed.messageHead.split("\n")[0] ?? "").replace(/^Error:\s*/, "").replace(MASK_TOKEN_RE, "\u2026");
8110
+ function firstLineFallback(parsed2) {
8111
+ const line = (parsed2.messageHead.split("\n")[0] ?? "").replace(/^Error:\s*/, "").replace(MASK_TOKEN_RE, "\u2026");
8108
8112
  return truncateValue(line || "Unknown error", HEADLINE_MAX_CHARS);
8109
8113
  }
8110
8114
  var Line = class {
@@ -8141,148 +8145,148 @@ function timeoutSuffix(timeoutMs) {
8141
8145
  function timeoutParen(timeoutMs) {
8142
8146
  return timeoutMs !== null ? ` (${formatTimeout(timeoutMs)})` : "";
8143
8147
  }
8144
- function buildActionTimeout(parsed, opts) {
8148
+ function buildActionTimeout(parsed2, opts) {
8145
8149
  const line = new Line();
8146
- const verb = ACTION_VERBS[parsed.action ?? ""] ?? parsed.action ?? "action";
8147
- const state = STATE_PHRASES[parsed.lastState];
8148
- if (parsed.locator && state) {
8149
- return subject(line, parsed.locator, opts).text(` ${state} \u2014 ${verb} timed out`).text(timeoutSuffix(parsed.timeoutMs));
8150
+ const verb = ACTION_VERBS[parsed2.action ?? ""] ?? parsed2.action ?? "action";
8151
+ const state = STATE_PHRASES[parsed2.lastState];
8152
+ if (parsed2.locator && state) {
8153
+ return subject(line, parsed2.locator, opts).text(` ${state} \u2014 ${verb} timed out`).text(timeoutSuffix(parsed2.timeoutMs));
8150
8154
  }
8151
- if (parsed.locator && parsed.lastState === "resolved-count" && parsed.resolvedCount !== null) {
8152
- const count = parsed.resolvedCount;
8153
- return subject(line, parsed.locator, opts).text(` matched ${count === 0 ? "no" : count} ${countNoun(parsed.locator, count)} \u2014 ${verb} timed out`).text(timeoutSuffix(parsed.timeoutMs));
8155
+ if (parsed2.locator && parsed2.lastState === "resolved-count" && parsed2.resolvedCount !== null) {
8156
+ const count = parsed2.resolvedCount;
8157
+ return subject(line, parsed2.locator, opts).text(` matched ${count === 0 ? "no" : count} ${countNoun(parsed2.locator, count)} \u2014 ${verb} timed out`).text(timeoutSuffix(parsed2.timeoutMs));
8154
8158
  }
8155
- if (parsed.locator) {
8156
- return line.text(`${verb} on `).locator(opts.locator ?? parsed.locator).text(" timed out").text(timeoutSuffix(parsed.timeoutMs));
8159
+ if (parsed2.locator) {
8160
+ return line.text(`${verb} on `).locator(opts.locator ?? parsed2.locator).text(" timed out").text(timeoutSuffix(parsed2.timeoutMs));
8157
8161
  }
8158
- const call = parsed.subject && parsed.action ? `${parsed.subject}.${parsed.action}` : verb;
8159
- return line.text(`${call} timed out`).text(timeoutSuffix(parsed.timeoutMs));
8162
+ const call = parsed2.subject && parsed2.action ? `${parsed2.subject}.${parsed2.action}` : verb;
8163
+ return line.text(`${call} timed out`).text(timeoutSuffix(parsed2.timeoutMs));
8160
8164
  }
8161
- function buildAssertion(parsed, opts) {
8165
+ function buildAssertion(parsed2, opts) {
8162
8166
  const line = new Line();
8163
- const matcher = parsed.assertion ?? "expect";
8164
- const notFound = parsed.lastState === "not-found" || parsed.lastState === "resolved-count" && parsed.resolvedCount === 0 || /element\(s\) not found/.test(parsed.received ?? "");
8165
- const expected = parsed.expected ? truncateValue(parsed.expected, opts.valueMax) : null;
8166
- const received = parsed.received ? truncateValue(receivedDisplay(parsed), opts.valueMax) : null;
8167
- if (matcher === "toHaveCount" && parsed.locator) {
8168
- const want = Number(parsed.expected);
8169
- const got = notFound ? 0 : Number(parsed.received);
8167
+ const matcher = parsed2.assertion ?? "expect";
8168
+ const notFound = parsed2.lastState === "not-found" || parsed2.lastState === "resolved-count" && parsed2.resolvedCount === 0 || /element\(s\) not found/.test(parsed2.received ?? "");
8169
+ const expected = parsed2.expected ? truncateValue(parsed2.expected, opts.valueMax) : null;
8170
+ const received = parsed2.received ? truncateValue(receivedDisplay(parsed2), opts.valueMax) : null;
8171
+ if (matcher === "toHaveCount" && parsed2.locator) {
8172
+ const want = Number(parsed2.expected);
8173
+ const got = notFound ? 0 : Number(parsed2.received);
8170
8174
  const wantText = Number.isFinite(want) ? String(want) : expected ?? "?";
8171
- const noun2 = countNoun(parsed.locator, Number.isFinite(want) ? want : 2);
8175
+ const noun2 = countNoun(parsed2.locator, Number.isFinite(want) ? want : 2);
8172
8176
  const gotText = Number.isFinite(got) ? got === 0 ? "none" : String(got) : received ?? "none";
8173
- return line.text(`Expected ${wantText} ${noun2}, found ${gotText} \u2014 `).locator(opts.locator ?? parsed.locator).text(" toHaveCount");
8177
+ return line.text(`Expected ${wantText} ${noun2}, found ${gotText} \u2014 `).locator(opts.locator ?? parsed2.locator).text(" toHaveCount");
8174
8178
  }
8175
8179
  const state = STATE_MATCHERS[matcher];
8176
8180
  if (state) {
8177
- const unmet = parsed.negated ? `stayed ${state.met}` : state.unmet;
8178
- if (!parsed.locator) return line.text(`Expected ${state.met}, page ${unmet}`).text(timeoutParen(parsed.timeoutMs));
8181
+ const unmet = parsed2.negated ? `stayed ${state.met}` : state.unmet;
8182
+ if (!parsed2.locator) return line.text(`Expected ${state.met}, page ${unmet}`).text(timeoutParen(parsed2.timeoutMs));
8179
8183
  if (notFound && matcher !== "toBeVisible" && matcher !== "toBeAttached") {
8180
- return subject(line, parsed.locator, opts).text(` was not found on the page \u2014 expected ${state.met}`).text(timeoutParen(parsed.timeoutMs));
8184
+ return subject(line, parsed2.locator, opts).text(` was not found on the page \u2014 expected ${state.met}`).text(timeoutParen(parsed2.timeoutMs));
8181
8185
  }
8182
- return subject(line, parsed.locator, opts).text(` ${unmet}`).text(timeoutParen(parsed.timeoutMs));
8186
+ return subject(line, parsed2.locator, opts).text(` ${unmet}`).text(timeoutParen(parsed2.timeoutMs));
8183
8187
  }
8184
8188
  const noun = VALUE_MATCHERS[matcher];
8185
8189
  if (noun) {
8186
- if (parsed.locator && notFound) {
8187
- return subject(line, parsed.locator, opts).text(" was not found on the page \u2014 expected ").text(`${noun} `).value(expected ?? "").text(timeoutParen(parsed.timeoutMs));
8190
+ if (parsed2.locator && notFound) {
8191
+ return subject(line, parsed2.locator, opts).text(" was not found on the page \u2014 expected ").text(`${noun} `).value(expected ?? "").text(timeoutParen(parsed2.timeoutMs));
8188
8192
  }
8189
8193
  line.text(`Expected ${noun} `);
8190
8194
  if (expected) line.value(expected);
8191
8195
  else line.text("to match");
8192
8196
  if (received) line.text(", got ").value(received);
8193
- if (parsed.locator)
8194
- line.text(" \u2014 ").locator(opts.locator ?? parsed.locator).text(` ${matcher}`);
8197
+ if (parsed2.locator)
8198
+ line.text(" \u2014 ").locator(opts.locator ?? parsed2.locator).text(` ${matcher}`);
8195
8199
  else if (matcher === "toHaveURL" || matcher === "toHaveTitle") line.text(` \u2014 page ${matcher}`);
8196
8200
  return line;
8197
8201
  }
8198
- if (matcher === "toPass") return line.text("expect.toPass never passed").text(timeoutParen(parsed.timeoutMs));
8202
+ if (matcher === "toPass") return line.text("expect.toPass never passed").text(timeoutParen(parsed2.timeoutMs));
8199
8203
  if (expected || received) {
8200
8204
  line.text("Expected ");
8201
8205
  if (expected) line.value(expected);
8202
8206
  else line.text("a different value");
8203
8207
  if (received) line.text(", got ").value(received);
8204
8208
  line.text(` \u2014 ${matcher}`);
8205
- if (parsed.locator) line.text(" on ").locator(opts.locator ?? parsed.locator);
8209
+ if (parsed2.locator) line.text(" on ").locator(opts.locator ?? parsed2.locator);
8206
8210
  return line;
8207
8211
  }
8208
- if (parsed.locator) {
8209
- return subject(line, parsed.locator, opts).text(` failed ${matcher}`).text(timeoutParen(parsed.timeoutMs));
8212
+ if (parsed2.locator) {
8213
+ return subject(line, parsed2.locator, opts).text(` failed ${matcher}`).text(timeoutParen(parsed2.timeoutMs));
8210
8214
  }
8211
8215
  return line.text(`${matcher} assertion failed`);
8212
8216
  }
8213
- function buildStrictMode(parsed, opts) {
8217
+ function buildStrictMode(parsed2, opts) {
8214
8218
  const line = new Line();
8215
- const count = parsed.resolvedCount ?? 0;
8216
- if (!parsed.locator) return line.text(`Locator matched ${count} elements \u2014 strict mode`);
8217
- return line.locator(opts.locator ?? parsed.locator).text(` matched ${count} ${countNoun(parsed.locator, count)} \u2014 strict mode`);
8219
+ const count = parsed2.resolvedCount ?? 0;
8220
+ if (!parsed2.locator) return line.text(`Locator matched ${count} elements \u2014 strict mode`);
8221
+ return line.locator(opts.locator ?? parsed2.locator).text(` matched ${count} ${countNoun(parsed2.locator, count)} \u2014 strict mode`);
8218
8222
  }
8219
- function buildNavigation(parsed, opts) {
8223
+ function buildNavigation(parsed2, opts) {
8220
8224
  const line = new Line();
8221
- const code = parsed.networkErrorCode?.replace(/^net::/, "") ?? null;
8225
+ const code = parsed2.networkErrorCode?.replace(/^net::/, "") ?? null;
8222
8226
  if (code) {
8223
8227
  const phrase = NETWORK_ERRORS[code] ?? `${code.replace(/^(?:ERR|NS_ERROR)_/, "").replace(/_/g, " ").toLowerCase().replace(/^\w/, (c) => c.toUpperCase())} loading`;
8224
8228
  line.text(phrase);
8225
- if (parsed.url) line.text(" ").value(truncateValue(parsed.url, opts.valueMax * 2));
8229
+ if (parsed2.url) line.text(" ").value(truncateValue(parsed2.url, opts.valueMax * 2));
8226
8230
  return line;
8227
8231
  }
8228
- const route = parsed.url ? routeOf(parsed.url) : null;
8229
- const timedOut = parsed.timeoutMs !== null || parsed.errorName === "TimeoutError";
8230
- if (parsed.action === "waitForURL" || parsed.action === "waitForNavigation") {
8232
+ const route = parsed2.url ? routeOf(parsed2.url) : null;
8233
+ const timedOut = parsed2.timeoutMs !== null || parsed2.errorName === "TimeoutError";
8234
+ if (parsed2.action === "waitForURL" || parsed2.action === "waitForNavigation") {
8231
8235
  line.text("Never navigated to ");
8232
8236
  if (route) line.value(truncateValue(route, opts.valueMax));
8233
8237
  else line.text("the expected URL");
8234
- return line.text(` \u2014 ${parsed.action} timed out`).text(timeoutSuffix(parsed.timeoutMs));
8238
+ return line.text(` \u2014 ${parsed2.action} timed out`).text(timeoutSuffix(parsed2.timeoutMs));
8235
8239
  }
8236
8240
  line.text("Navigation");
8237
8241
  if (route) line.text(" to ").value(truncateValue(route, opts.valueMax));
8238
- if (timedOut) return line.text(" timed out").text(timeoutSuffix(parsed.timeoutMs));
8242
+ if (timedOut) return line.text(" timed out").text(timeoutSuffix(parsed2.timeoutMs));
8239
8243
  return line.text(" failed");
8240
8244
  }
8241
- function buildTestTimeout(parsed, opts, ctx) {
8242
- const line = new Line().text("Test timed out").text(timeoutSuffix(parsed.timeoutMs));
8243
- if (parsed.timeoutPhase) {
8244
- const phase = parsed.timeoutPhase;
8245
+ function buildTestTimeout(parsed2, opts, ctx) {
8246
+ const line = new Line().text("Test timed out").text(timeoutSuffix(parsed2.timeoutMs));
8247
+ if (parsed2.timeoutPhase) {
8248
+ const phase = parsed2.timeoutPhase;
8245
8249
  const isHook = /^(?:before|after)(?:Each|All)$/.test(phase);
8246
8250
  return line.text(isHook ? ` in the "${phase}" hook` : ` while tearing down "${phase}"`);
8247
8251
  }
8248
- if (parsed.isNavigationFailure && parsed.url) {
8249
- return line.text(" while navigating to ").value(truncateValue(routeOf(parsed.url), opts.valueMax));
8252
+ if (parsed2.isNavigationFailure && parsed2.url) {
8253
+ return line.text(" while navigating to ").value(truncateValue(routeOf(parsed2.url), opts.valueMax));
8250
8254
  }
8251
- if (parsed.assertion && parsed.locator) {
8252
- const state = STATE_MATCHERS[parsed.assertion];
8255
+ if (parsed2.assertion && parsed2.locator) {
8256
+ const state = STATE_MATCHERS[parsed2.assertion];
8253
8257
  line.text(state ? ` while waiting for ` : " while expecting ");
8254
- subject(line, parsed.locator, opts);
8255
- return line.text(state ? ` to be ${state.met}` : ` ${parsed.assertion}`);
8258
+ subject(line, parsed2.locator, opts);
8259
+ return line.text(state ? ` to be ${state.met}` : ` ${parsed2.assertion}`);
8256
8260
  }
8257
- if (parsed.action && parsed.locator) {
8258
- const gerund = ACTION_GERUNDS[parsed.action] ?? `${ACTION_VERBS[parsed.action] ?? parsed.action} on`;
8261
+ if (parsed2.action && parsed2.locator) {
8262
+ const gerund = ACTION_GERUNDS[parsed2.action] ?? `${ACTION_VERBS[parsed2.action] ?? parsed2.action} on`;
8259
8263
  line.text(` while ${gerund} `);
8260
- return subject(line, parsed.locator, opts);
8264
+ return subject(line, parsed2.locator, opts);
8261
8265
  }
8262
- if (parsed.subject && parsed.action) return line.text(` during ${parsed.subject}.${parsed.action}`);
8266
+ if (parsed2.subject && parsed2.action) return line.text(` during ${parsed2.subject}.${parsed2.action}`);
8263
8267
  const step = ctx?.lastStepTitle?.trim();
8264
8268
  if (step) return line.text(" while ").value(`"${truncateValue(step, opts.valueMax * 1.5)}"`);
8265
8269
  return line;
8266
8270
  }
8267
- function buildCrash(parsed, opts) {
8271
+ function buildCrash(parsed2, opts) {
8268
8272
  const line = new Line();
8269
- const what = /Page crashed/i.test(parsed.messageHead) ? "Page crashed" : "Page or browser closed";
8270
- if (parsed.action) {
8271
- const verb = ACTION_VERBS[parsed.action] ?? parsed.action;
8273
+ const what = /Page crashed/i.test(parsed2.messageHead) ? "Page crashed" : "Page or browser closed";
8274
+ if (parsed2.action) {
8275
+ const verb = ACTION_VERBS[parsed2.action] ?? parsed2.action;
8272
8276
  line.text(`${what} during ${verb}`);
8273
- if (parsed.locator) line.text(" on ").locator(opts.locator ?? parsed.locator);
8277
+ if (parsed2.locator) line.text(" on ").locator(opts.locator ?? parsed2.locator);
8274
8278
  return line;
8275
8279
  }
8276
- if (parsed.assertion && parsed.locator) {
8277
- return line.text(`${what} while expecting `).locator(opts.locator ?? parsed.locator).text(` ${parsed.assertion}`);
8280
+ if (parsed2.assertion && parsed2.locator) {
8281
+ return line.text(`${what} while expecting `).locator(opts.locator ?? parsed2.locator).text(` ${parsed2.assertion}`);
8278
8282
  }
8279
8283
  return line.text(what);
8280
8284
  }
8281
8285
  var CUSTOM_MESSAGE_MAX_CHARS = 60;
8282
8286
  var SENTENCE_CASE_WORD_RE = /^[A-Z][a-z]*(?![A-Za-z])/;
8283
- function withLeads(line, parsed, ctx) {
8284
- const hook = ctx?.failedIn && !parsed.timeoutPhase ? ctx.failedIn.hook : null;
8285
- const message = parsed.customMessage ? truncateValue(parsed.customMessage, CUSTOM_MESSAGE_MAX_CHARS) : null;
8287
+ function withLeads(line, parsed2, ctx) {
8288
+ const hook = ctx?.failedIn && !parsed2.timeoutPhase ? ctx.failedIn.hook : null;
8289
+ const message = parsed2.customMessage ? truncateValue(parsed2.customMessage, CUSTOM_MESSAGE_MAX_CHARS) : null;
8286
8290
  if (!hook && !message) return line;
8287
8291
  const led = new Line();
8288
8292
  if (hook) led.text(`In ${hook}${message ? ", " : ": "}`);
@@ -8295,49 +8299,49 @@ function withLeads(line, parsed, ctx) {
8295
8299
  });
8296
8300
  return led;
8297
8301
  }
8298
- function build(parsed, opts, ctx) {
8299
- return withLeads(buildLine(parsed, opts, ctx), parsed, ctx);
8302
+ function build(parsed2, opts, ctx) {
8303
+ return withLeads(buildLine(parsed2, opts, ctx), parsed2, ctx);
8300
8304
  }
8301
- function buildLine(parsed, opts, ctx) {
8302
- switch (parsed.kind) {
8305
+ function buildLine(parsed2, opts, ctx) {
8306
+ switch (parsed2.kind) {
8303
8307
  case "action-timeout":
8304
- return buildActionTimeout(parsed, opts);
8308
+ return buildActionTimeout(parsed2, opts);
8305
8309
  case "assertion":
8306
8310
  case "assertion-timeout":
8307
- return buildAssertion(parsed, opts);
8311
+ return buildAssertion(parsed2, opts);
8308
8312
  case "strict-mode":
8309
- return buildStrictMode(parsed, opts);
8313
+ return buildStrictMode(parsed2, opts);
8310
8314
  case "navigation":
8311
- return buildNavigation(parsed, opts);
8315
+ return buildNavigation(parsed2, opts);
8312
8316
  case "test-timeout":
8313
- return buildTestTimeout(parsed, opts, ctx);
8317
+ return buildTestTimeout(parsed2, opts, ctx);
8314
8318
  case "crash":
8315
- return buildCrash(parsed, opts);
8319
+ return buildCrash(parsed2, opts);
8316
8320
  default:
8317
- return new Line().text(firstLineFallback(parsed));
8321
+ return new Line().text(firstLineFallback(parsed2));
8318
8322
  }
8319
8323
  }
8320
- function detailOf(parsed, headline) {
8321
- switch (parsed.kind) {
8324
+ function detailOf(parsed2, headline) {
8325
+ switch (parsed2.kind) {
8322
8326
  case "test-timeout":
8323
8327
  case "action-timeout": {
8324
- const state = parsed.lastStateLine;
8325
- if (!state || parsed.lastState === "not-found" || headline.includes(state)) return null;
8328
+ const state = parsed2.lastStateLine;
8329
+ if (!state || parsed2.lastState === "not-found" || headline.includes(state)) return null;
8326
8330
  return truncateValue(state, HEADLINE_MAX_CHARS);
8327
8331
  }
8328
8332
  case "assertion":
8329
8333
  case "assertion-timeout": {
8330
- const received = parsed.received;
8334
+ const received = parsed2.received;
8331
8335
  if (received && !/element\(s\) not found/.test(received)) {
8332
- const shown = truncateValue(receivedDisplay(parsed), SHORT_VALUE_MAX_CHARS).slice(0, 8);
8336
+ const shown = truncateValue(receivedDisplay(parsed2), SHORT_VALUE_MAX_CHARS).slice(0, 8);
8333
8337
  if (!headline.includes(shown)) return truncateValue(`Received: ${received}`, HEADLINE_MAX_CHARS);
8334
8338
  }
8335
- const state = parsed.lastStateLine;
8339
+ const state = parsed2.lastStateLine;
8336
8340
  if (!state || /^(?:waiting for |unexpected value )/.test(state)) return null;
8337
8341
  return truncateValue(state, HEADLINE_MAX_CHARS);
8338
8342
  }
8339
8343
  case "navigation":
8340
- return parsed.networkErrorCode ?? parsed.lastStateLine;
8344
+ return parsed2.networkErrorCode ?? parsed2.lastStateLine;
8341
8345
  case "strict-mode":
8342
8346
  case "crash":
8343
8347
  return null;
@@ -8345,16 +8349,16 @@ function detailOf(parsed, headline) {
8345
8349
  return null;
8346
8350
  }
8347
8351
  }
8348
- function describeFailure(parsed, ctx) {
8352
+ function describeFailure(parsed2, ctx) {
8349
8353
  const attempts = [
8350
- { locator: parsed.locator, valueMax: VALUE_MAX_CHARS },
8351
- { locator: parsed.leafLocator ?? parsed.locator, valueMax: VALUE_MAX_CHARS },
8352
- { locator: parsed.leafLocator ?? parsed.locator, valueMax: SHORT_VALUE_MAX_CHARS }
8354
+ { locator: parsed2.locator, valueMax: VALUE_MAX_CHARS },
8355
+ { locator: parsed2.leafLocator ?? parsed2.locator, valueMax: VALUE_MAX_CHARS },
8356
+ { locator: parsed2.leafLocator ?? parsed2.locator, valueMax: SHORT_VALUE_MAX_CHARS }
8353
8357
  ];
8354
- let line = build(parsed, attempts[0], ctx);
8358
+ let line = build(parsed2, attempts[0], ctx);
8355
8359
  for (const opts of attempts.slice(1)) {
8356
8360
  if (line.toString().length <= HEADLINE_MAX_CHARS) break;
8357
- line = build(parsed, opts, ctx);
8361
+ line = build(parsed2, opts, ctx);
8358
8362
  }
8359
8363
  let parts = line.parts.map((p) => ({ ...p, text: p.text.replace(MASK_TOKEN_RE, "\u2026") }));
8360
8364
  let headline = parts.map((p) => p.text).join("");
@@ -8364,10 +8368,10 @@ function describeFailure(parsed, ctx) {
8364
8368
  parts.push({ kind: "text", text: "\u2026" });
8365
8369
  }
8366
8370
  if (!headline.trim()) {
8367
- headline = firstLineFallback(parsed);
8371
+ headline = firstLineFallback(parsed2);
8368
8372
  parts = [{ kind: "text", text: headline }];
8369
8373
  }
8370
- return { headline, detail: detailOf(parsed, headline), parts };
8374
+ return { headline, detail: detailOf(parsed2, headline), parts };
8371
8375
  }
8372
8376
  function clipParts(parts, max) {
8373
8377
  const out = [];
@@ -9077,7 +9081,7 @@ async function resolveSelection(options = {}) {
9077
9081
  }
9078
9082
 
9079
9083
  // src/internal/capture/capture-fixtures.ts
9080
- var path27 = __toESM(require("path"));
9084
+ var path28 = __toESM(require("path"));
9081
9085
  var import_node_zlib2 = require("zlib");
9082
9086
 
9083
9087
  // ../picker-dom/src/probe.ts
@@ -10140,6 +10144,83 @@ function showPickerChoices(arg) {
10140
10144
  doc.body.appendChild(wrap);
10141
10145
  }
10142
10146
 
10147
+ // ../picker-dom/src/overlay-pause.ts
10148
+ function showPauseBar(arg) {
10149
+ const g = globalThis;
10150
+ const doc = g.document;
10151
+ delete g.__piwiPauseState;
10152
+ if (!doc || !doc.body) {
10153
+ g.__piwiPauseState = "resume";
10154
+ return;
10155
+ }
10156
+ if (typeof g.__piwiPauseCleanup === "function") g.__piwiPauseCleanup();
10157
+ const Z = 2147483600;
10158
+ const bar = doc.createElement("div");
10159
+ bar.id = "__piwi-pause-bar";
10160
+ bar.setAttribute("role", "toolbar");
10161
+ bar.setAttribute("aria-label", "Piwi: test paused");
10162
+ 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;`;
10163
+ const where = doc.createElement("div");
10164
+ where.style.cssText = "font-weight:600;white-space:nowrap;";
10165
+ where.textContent = `Paused at ${arg.place}${arg.attempt ? ` \xB7 attempt ${arg.attempt}` : ""}`;
10166
+ const what = doc.createElement("code");
10167
+ 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;";
10168
+ what.textContent = arg.locator ? `${arg.action} \xB7 ${arg.locator}` : arg.action;
10169
+ what.title = what.textContent;
10170
+ bar.appendChild(where);
10171
+ bar.appendChild(what);
10172
+ const done = (choice) => {
10173
+ cleanup();
10174
+ g.__piwiPauseState = choice;
10175
+ };
10176
+ const onKey = (e) => {
10177
+ if (e.key !== "Escape") return;
10178
+ e.preventDefault();
10179
+ e.stopImmediatePropagation();
10180
+ done("resume");
10181
+ };
10182
+ const cleanup = () => {
10183
+ doc.removeEventListener("keydown", onKey, true);
10184
+ bar.remove();
10185
+ delete g.__piwiPauseCleanup;
10186
+ };
10187
+ const buttons = [
10188
+ ["Resume", "resume", "Run on to the next breakpoint (Esc)", true],
10189
+ ["Step", "step", "Pause again at the next action", false],
10190
+ ["Pick a locator", "pick", "Pick an element on the page and confirm a locator for it", false],
10191
+ ["Finish", "finish", "Run on and pause no more in this test", false]
10192
+ ];
10193
+ for (const [label, choice, hint, primary] of buttons) {
10194
+ const btn = doc.createElement("button");
10195
+ btn.type = "button";
10196
+ btn.textContent = label;
10197
+ btn.title = hint;
10198
+ 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;");
10199
+ btn.addEventListener("click", (e) => {
10200
+ e.preventDefault();
10201
+ e.stopImmediatePropagation();
10202
+ done(choice);
10203
+ });
10204
+ bar.appendChild(btn);
10205
+ }
10206
+ g.__piwiPauseCleanup = cleanup;
10207
+ doc.addEventListener("keydown", onKey, true);
10208
+ doc.body.appendChild(bar);
10209
+ }
10210
+ function pauseAnswered() {
10211
+ const g = globalThis;
10212
+ if (g.__piwiPauseState !== void 0) return true;
10213
+ return !g.document?.getElementById("__piwi-pause-bar");
10214
+ }
10215
+ function takePauseState() {
10216
+ const g = globalThis;
10217
+ const state = g.__piwiPauseState;
10218
+ if (typeof g.__piwiPauseCleanup === "function") g.__piwiPauseCleanup();
10219
+ delete g.__piwiPauseCleanup;
10220
+ delete g.__piwiPauseState;
10221
+ return typeof state === "string" ? state : null;
10222
+ }
10223
+
10143
10224
  // ../core/src/locator-generation.ts
10144
10225
  var TAG_TO_ROLE = {
10145
10226
  a: "link",
@@ -11176,11 +11257,11 @@ function deriveFailedLocator(testInfo) {
11176
11257
  ${err.stack ?? ""}`.replace(ANSI_RE2, "");
11177
11258
  const line = /^\s*Locator:\s*(.+)$/m.exec(text);
11178
11259
  if (!line) continue;
11179
- const parsed = parseLeafLocatorExpression(line[1].trim());
11180
- if (!parsed) continue;
11260
+ const parsed2 = parseLeafLocatorExpression(line[1].trim());
11261
+ if (!parsed2) continue;
11181
11262
  const loc = err.location;
11182
11263
  const location = loc ? `${path25.relative(process.cwd(), loc.file).split(path25.sep).join("/")}:${loc.line}:${loc.column}` : null;
11183
- return { method: parsed.method, args: parsed.args, location };
11264
+ return { method: parsed2.method, args: parsed2.args, location };
11184
11265
  }
11185
11266
  return null;
11186
11267
  }
@@ -11202,25 +11283,16 @@ async function cleanupPicker(page) {
11202
11283
  } catch {
11203
11284
  }
11204
11285
  }
11205
- async function runLocatorPicker(page, testInfo, failed, probe) {
11286
+ async function pickLocator(page, probe, failing) {
11206
11287
  try {
11207
- testInfo.setTimeout(0);
11208
- const rendered = failed ? renderFailing(failed) : null;
11209
- console.log(
11210
- `
11211
- [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).`
11212
- );
11213
- const overlayArg = { transport: "global", failing: rendered };
11288
+ const overlayArg = { transport: "global", failing };
11214
11289
  await page.evaluate(installPickerOverlay, overlayArg);
11215
11290
  await page.waitForFunction(() => globalThis.__piwiPickState !== void 0, void 0, {
11216
11291
  timeout: 0,
11217
11292
  polling: 250
11218
11293
  });
11219
11294
  const state = await page.evaluate(() => globalThis.__piwiPickState);
11220
- if (state !== "picked") {
11221
- await cleanupPicker(page);
11222
- return null;
11223
- }
11295
+ if (state !== "picked") return null;
11224
11296
  const handle = await page.evaluateHandle(() => globalThis.__piwiPickedElement);
11225
11297
  const attrs = await handle.evaluate(probe.fn, probe.arg);
11226
11298
  await handle.dispose();
@@ -11258,11 +11330,10 @@ async function runLocatorPicker(page, testInfo, failed, probe) {
11258
11330
  );
11259
11331
  if (ranked.length === 0) {
11260
11332
  console.log("[piwi] No stable locator could be generated for the picked element \u2014 nothing recorded.");
11261
- await cleanupPicker(page);
11262
11333
  return null;
11263
11334
  }
11264
11335
  await page.evaluate(showPickerChoices, {
11265
- failing: rendered,
11336
+ failing,
11266
11337
  choices: ranked.map((r) => ({ locator: r.locator, score: r.score }))
11267
11338
  });
11268
11339
  await page.waitForFunction(() => globalThis.__piwiPickChoice !== void 0, void 0, {
@@ -11270,31 +11341,45 @@ async function runLocatorPicker(page, testInfo, failed, probe) {
11270
11341
  polling: 250
11271
11342
  });
11272
11343
  const choice = await page.evaluate(() => globalThis.__piwiPickChoice);
11273
- await cleanupPicker(page);
11274
11344
  if (typeof choice !== "number" || choice < 0 || choice >= ranked.length) return null;
11275
11345
  const picked = { ...ranked[choice], pickedByUser: true };
11276
- const alternatives = [picked, ...ranked.filter((_, i) => i !== choice)];
11277
- const element = {
11278
- tagName: attrs.tagName,
11279
- attributes: attrs.attributes,
11280
- textContent: attrs.textContent,
11281
- accessibleName,
11282
- center: attrs.center,
11283
- ...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
11284
- ...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
11346
+ return {
11347
+ picked,
11348
+ alternatives: [picked, ...ranked.filter((_, i) => i !== choice)],
11349
+ element: {
11350
+ tagName: attrs.tagName,
11351
+ attributes: attrs.attributes,
11352
+ textContent: attrs.textContent,
11353
+ accessibleName,
11354
+ center: attrs.center,
11355
+ ...attrs.rolePosition ? { rolePosition: attrs.rolePosition } : {},
11356
+ ...attrs.ancestors && attrs.ancestors.length > 0 ? { ancestors: attrs.ancestors } : {}
11357
+ },
11358
+ ...anchors.length > 0 ? { anchors } : {}
11285
11359
  };
11360
+ } finally {
11361
+ await cleanupPicker(page);
11362
+ }
11363
+ }
11364
+ async function runLocatorPicker(page, testInfo, failed, probe) {
11365
+ try {
11366
+ testInfo.setTimeout(0);
11367
+ const rendered = failed ? renderFailing(failed) : null;
11368
+ console.log(
11369
+ `
11370
+ [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).`
11371
+ );
11372
+ const pick = await pickLocator(page, probe, rendered);
11373
+ if (!pick) return null;
11286
11374
  const location = failed?.location ?? null;
11287
11375
  console.log(
11288
11376
  failed ? `[piwi] Replacement picked for ${rendered}${location ? ` at ${location}` : ""}:
11289
- [piwi] ${picked.locator}` : `[piwi] Locator picked while inspecting:
11290
- [piwi] ${picked.locator}`
11377
+ [piwi] ${pick.picked.locator}` : `[piwi] Locator picked while inspecting:
11378
+ [piwi] ${pick.picked.locator}`
11291
11379
  );
11292
11380
  return {
11293
11381
  failing: failed ? { method: failed.method, args: failed.args, rendered, location } : null,
11294
- picked,
11295
- alternatives,
11296
- element,
11297
- ...anchors.length > 0 ? { anchors } : {}
11382
+ ...pick
11298
11383
  };
11299
11384
  } catch {
11300
11385
  return null;
@@ -11327,10 +11412,207 @@ function applyPickToSnapshots(snapshots, pick) {
11327
11412
  return true;
11328
11413
  }
11329
11414
 
11415
+ // src/internal/capture/pause-at.ts
11416
+ var path26 = __toESM(require("path"));
11417
+
11418
+ // ../core/src/editor-send.ts
11419
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
11420
+ function parsePairing(text) {
11421
+ const trimmed = text.trim();
11422
+ const hash = trimmed.indexOf("#");
11423
+ if (hash < 0) return null;
11424
+ const url = trimmed.slice(0, hash);
11425
+ const token = trimmed.slice(hash + 1);
11426
+ if (!/^[A-Za-z0-9_-]{16,}$/.test(token)) return null;
11427
+ let parsed2;
11428
+ try {
11429
+ parsed2 = new URL(url);
11430
+ } catch {
11431
+ return null;
11432
+ }
11433
+ if (parsed2.protocol !== "http:" || !LOOPBACK_HOSTS.has(parsed2.hostname) || !parsed2.port) return null;
11434
+ return { url: `${parsed2.origin}${parsed2.pathname}`, token };
11435
+ }
11436
+ function isInsidePath(file) {
11437
+ if (!file || file.length > 1e3 || file.includes("\0")) return false;
11438
+ if (file.startsWith("/") || file.startsWith("\\") || /^[A-Za-z]:/.test(file)) return false;
11439
+ return file.split(/[\\/]/).every((part) => part !== "..");
11440
+ }
11441
+
11442
+ // src/internal/capture/pause-at.ts
11443
+ var PauseSet = class {
11444
+ constructor(lines) {
11445
+ this.lines = lines;
11446
+ }
11447
+ get size() {
11448
+ return this.lines.size;
11449
+ }
11450
+ /** Whether a call site (`file:line:col`, as `captureCallerLocation` writes it) is on one of the lines. */
11451
+ matches(callerLocation) {
11452
+ if (!callerLocation) return false;
11453
+ const col = callerLocation.lastIndexOf(":");
11454
+ return col > 0 && this.lines.has(callerLocation.slice(0, col));
11455
+ }
11456
+ };
11457
+ var EMPTY = new PauseSet(/* @__PURE__ */ new Set());
11458
+ function callSiteFile(file, cwd) {
11459
+ const slashed = file.replace(/\\/g, "/");
11460
+ const absolute = path26.isAbsolute(file) || path26.isAbsolute(slashed) || /^[A-Za-z]:\//.test(slashed);
11461
+ let rel = absolute ? path26.relative(cwd, file) : path26.normalize(slashed);
11462
+ rel = rel.split(path26.sep).join("/").replace(/\\/g, "/");
11463
+ if (rel.startsWith("./")) rel = rel.slice(2);
11464
+ return rel;
11465
+ }
11466
+ var ignoredLogged = /* @__PURE__ */ new Set();
11467
+ function parsePauseAt(value, cwd = process.cwd()) {
11468
+ if (!value?.trim()) return EMPTY;
11469
+ const lines = /* @__PURE__ */ new Set();
11470
+ for (const raw of value.split(";")) {
11471
+ const entry = raw.trim();
11472
+ if (!entry) continue;
11473
+ const m = /^(.+):(\d+)$/.exec(entry);
11474
+ const line = m ? Number(m[2]) : 0;
11475
+ if (!m?.[1] || line < 1) {
11476
+ if (!ignoredLogged.has(entry)) {
11477
+ ignoredLogged.add(entry);
11478
+ console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt}: ignoring "${entry}": expected file:line`);
11479
+ }
11480
+ continue;
11481
+ }
11482
+ lines.add(`${callSiteFile(m[1], cwd)}:${line}`);
11483
+ }
11484
+ return lines.size ? new PauseSet(lines) : EMPTY;
11485
+ }
11486
+ var parsed = null;
11487
+ function currentPauseSet() {
11488
+ const value = process.env[PIWI_LOCAL_DEBUG_ENV.pauseAt];
11489
+ if (!parsed || parsed.value !== value) parsed = { value, set: parsePauseAt(value) };
11490
+ return parsed.set;
11491
+ }
11492
+ function pauseGateFromTestInfo(testInfo) {
11493
+ const use = testInfo.project?.use ?? {};
11494
+ return { ci: process.env.CI, headless: use.headless };
11495
+ }
11496
+ function pauseSkipReason(gate) {
11497
+ if (isCi(gate.ci)) return "running under CI \u2014 breakpoints pause local runs only";
11498
+ if (gate.headless !== false)
11499
+ return "the browser is headless \u2014 re-run with --headed (or set use: { headless: false })";
11500
+ return null;
11501
+ }
11502
+ function rearmedTimeout(before, pausedMs) {
11503
+ return before > 0 ? before + Math.max(0, Math.round(pausedMs)) : 0;
11504
+ }
11505
+ function pausePlace(callerLocation) {
11506
+ const col = callerLocation.lastIndexOf(":");
11507
+ const fileLine = col > 0 ? callerLocation.slice(0, col) : callerLocation;
11508
+ return fileLine.slice(fileLine.lastIndexOf("/") + 1);
11509
+ }
11510
+ function assertionLabel(expression, isNot) {
11511
+ const [first = "", ...rest] = expression.split(".").filter(Boolean);
11512
+ const name = first + rest.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
11513
+ return `${isNot ? "not." : ""}${name || "expect"}`;
11514
+ }
11515
+ function pickPlace(callerLocation) {
11516
+ const m = /^(.+):(\d+):\d+$/.exec(callerLocation);
11517
+ if (!m) return null;
11518
+ const file = m[1];
11519
+ if (!isInsidePath(file)) return null;
11520
+ return { file, line: Number(m[2]) };
11521
+ }
11522
+ function pickPayload(locator, callerLocation) {
11523
+ const at = pickPlace(callerLocation);
11524
+ return at ? { kind: "locator", text: locator, at } : { kind: "locator", text: locator };
11525
+ }
11526
+ var postFailureLogged = false;
11527
+ async function postPickToEditor(pairingAddress, payload, fetchImpl = fetch) {
11528
+ if (!pairingAddress) return false;
11529
+ const logOnce = (why) => {
11530
+ if (postFailureLogged) return;
11531
+ postFailureLogged = true;
11532
+ console.log(`[piwi] Could not send the picked locator to the editor: ${why}.`);
11533
+ };
11534
+ const pairing = parsePairing(pairingAddress);
11535
+ if (!pairing) {
11536
+ logOnce(`${PIWI_LOCAL_DEBUG_ENV.editorSend} is not a pairing address`);
11537
+ return false;
11538
+ }
11539
+ try {
11540
+ const response = await fetchImpl(pairing.url, {
11541
+ method: "POST",
11542
+ headers: { Authorization: `Bearer ${pairing.token}`, "Content-Type": "application/json" },
11543
+ body: JSON.stringify(payload),
11544
+ signal: AbortSignal.timeout(5e3)
11545
+ });
11546
+ if (response.ok) return true;
11547
+ logOnce(`the editor answered ${response.status}`);
11548
+ } catch (error) {
11549
+ logOnce(error instanceof Error ? error.message : String(error));
11550
+ }
11551
+ return false;
11552
+ }
11553
+ var skipLogged = false;
11554
+ function logPauseSkipOnce(reason) {
11555
+ if (skipLogged) return;
11556
+ skipLogged = true;
11557
+ console.log(`[piwi] ${PIWI_LOCAL_DEBUG_ENV.pauseAt} is set but breakpoints are skipped: ${reason}`);
11558
+ }
11559
+ var HIGHLIGHT_MS = 2e3;
11560
+ async function pauseHere(page, testInfo, point, probe, onPick) {
11561
+ const before = testInfo.timeout;
11562
+ const started = Date.now();
11563
+ let shown = null;
11564
+ try {
11565
+ testInfo.setTimeout(0);
11566
+ const highlight = point.target?.highlight;
11567
+ if (typeof highlight === "function") {
11568
+ let timer;
11569
+ shown = await Promise.race([
11570
+ Promise.resolve().then(() => highlight.call(point.target)).catch(() => null),
11571
+ new Promise((resolve7) => {
11572
+ timer = setTimeout(() => resolve7(null), HIGHLIGHT_MS);
11573
+ })
11574
+ ]);
11575
+ clearTimeout(timer);
11576
+ }
11577
+ const arg = {
11578
+ place: pausePlace(point.location),
11579
+ attempt: testInfo.retry > 0 ? testInfo.retry + 1 : null,
11580
+ action: point.action,
11581
+ locator: point.locator
11582
+ };
11583
+ console.log(
11584
+ `[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).`
11585
+ );
11586
+ for (; ; ) {
11587
+ await page.evaluate(showPauseBar, arg);
11588
+ await page.waitForFunction(pauseAnswered, void 0, { timeout: 0, polling: 250 });
11589
+ const choice = await page.evaluate(takePauseState);
11590
+ if (choice === "resume" || choice === "step" || choice === "finish") return choice;
11591
+ if (choice !== "pick") continue;
11592
+ const picked = await pickLocator(page, probe, null).catch(() => null);
11593
+ if (picked) await onPick(picked);
11594
+ }
11595
+ } catch {
11596
+ return "resume";
11597
+ } finally {
11598
+ try {
11599
+ const dispose = shown?.dispose;
11600
+ const hide = page.hideHighlight;
11601
+ if (typeof dispose === "function") await dispose.call(shown);
11602
+ else if (typeof hide === "function") await hide.call(page);
11603
+ } catch {
11604
+ }
11605
+ try {
11606
+ testInfo.setTimeout(rearmedTimeout(before, Date.now() - started));
11607
+ } catch {
11608
+ }
11609
+ }
11610
+ }
11611
+
11330
11612
  // src/internal/capture/code-reach.ts
11331
11613
  var crypto3 = __toESM(require("crypto"));
11332
11614
  var fs22 = __toESM(require("fs"));
11333
- var path26 = __toESM(require("path"));
11615
+ var path27 = __toESM(require("path"));
11334
11616
  var import_node_child_process3 = require("child_process");
11335
11617
 
11336
11618
  // ../core/src/source-map.ts
@@ -11593,12 +11875,12 @@ function repositoryRoot(from) {
11593
11875
  }).trim();
11594
11876
  } catch {
11595
11877
  }
11596
- cachedRepoRoot = { from, root: path26.resolve(root) };
11878
+ cachedRepoRoot = { from, root: path27.resolve(root) };
11597
11879
  return cachedRepoRoot.root;
11598
11880
  }
11599
11881
  function codeReachRoots(configDir, configured) {
11600
11882
  const repoRoot = repositoryRoot(configDir);
11601
- const roots = configured?.length ? configured.map((r) => path26.resolve(configDir, r)) : [configDir];
11883
+ const roots = configured?.length ? configured.map((r) => path27.resolve(configDir, r)) : [configDir];
11602
11884
  return { roots: [.../* @__PURE__ */ new Set([...roots, repoRoot])], repoRoot };
11603
11885
  }
11604
11886
  function isFile(file) {
@@ -11611,10 +11893,10 @@ function isFile(file) {
11611
11893
  function resolveSourcePath(source, roots) {
11612
11894
  const normalized = normalizeSourcePath(source);
11613
11895
  if (!normalized || /(?:^|\/)node_modules\//.test(normalized)) return null;
11614
- if (path26.isAbsolute(normalized) && isFile(normalized)) return path26.resolve(normalized);
11615
- const relative13 = normalized.replace(/^\/+/, "");
11896
+ if (path27.isAbsolute(normalized) && isFile(normalized)) return path27.resolve(normalized);
11897
+ const relative14 = normalized.replace(/^\/+/, "");
11616
11898
  for (const root of roots) {
11617
- const candidate = path26.resolve(root, relative13);
11899
+ const candidate = path27.resolve(root, relative14);
11618
11900
  if (isFile(candidate)) return candidate;
11619
11901
  }
11620
11902
  return null;
@@ -11630,10 +11912,10 @@ function viteBases(entries) {
11630
11912
  function fileForUrl(url, roots, bases) {
11631
11913
  let pathname;
11632
11914
  try {
11633
- const parsed = new URL(url);
11634
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && parsed.protocol !== "file:") return null;
11635
- pathname = parsed.pathname;
11636
- const base = bases.get(parsed.origin);
11915
+ const parsed2 = new URL(url);
11916
+ if (parsed2.protocol !== "http:" && parsed2.protocol !== "https:" && parsed2.protocol !== "file:") return null;
11917
+ pathname = parsed2.pathname;
11918
+ const base = bases.get(parsed2.origin);
11637
11919
  if (base && pathname.startsWith(base)) pathname = pathname.slice(base.length - 1);
11638
11920
  } catch {
11639
11921
  return null;
@@ -11646,7 +11928,7 @@ function fileForUrl(url, roots, bases) {
11646
11928
  return file;
11647
11929
  }
11648
11930
  function relativeToRepo(file, repoRoot) {
11649
- return path26.relative(repoRoot, file).split(path26.sep).join("/");
11931
+ return path27.relative(repoRoot, file).split(path27.sep).join("/");
11650
11932
  }
11651
11933
  function scriptHash(entry) {
11652
11934
  return crypto3.createHash("sha1").update(entry.source ?? "").digest("hex");
@@ -11816,6 +12098,7 @@ function createSink() {
11816
12098
  flake: null,
11817
12099
  pickOffered: false,
11818
12100
  userPick: null,
12101
+ pause: null,
11819
12102
  locatorPages: new LocatorPageLog(),
11820
12103
  codeReach: null
11821
12104
  };
@@ -11832,11 +12115,11 @@ function configuredCodeReachRoots() {
11832
12115
  const raw = process.env.PIWI_CODE_REACH_ROOTS?.trim();
11833
12116
  if (!raw) return null;
11834
12117
  try {
11835
- const parsed = JSON.parse(raw);
11836
- if (Array.isArray(parsed)) return parsed.filter((r) => typeof r === "string" && r !== "");
12118
+ const parsed2 = JSON.parse(raw);
12119
+ if (Array.isArray(parsed2)) return parsed2.filter((r) => typeof r === "string" && r !== "");
11837
12120
  } catch {
11838
12121
  }
11839
- return raw.split(path27.delimiter).filter(Boolean);
12122
+ return raw.split(path28.delimiter).filter(Boolean);
11840
12123
  }
11841
12124
  async function stopSinkCodeReach(sink, closing) {
11842
12125
  const session = sink.codeReach;
@@ -11851,7 +12134,7 @@ async function stopSinkCodeReach(sink, closing) {
11851
12134
  if (!entries) return;
11852
12135
  const info = sink.testInfo;
11853
12136
  const configFile = info?.config.configFile;
11854
- const configDir = configFile ? path27.dirname(configFile) : info?.config.rootDir ?? process.cwd();
12137
+ const configDir = configFile ? path28.dirname(configFile) : info?.config.rootDir ?? process.cwd();
11855
12138
  session.files = await resolveCodeReach(
11856
12139
  entries,
11857
12140
  codeReachRoots(configDir, configuredCodeReachRoots()),
@@ -12270,6 +12553,53 @@ function recordLocatorPage(sink, page, locator, location, arrival) {
12270
12553
  } catch {
12271
12554
  }
12272
12555
  }
12556
+ function pausesAt(pause, callerLocation) {
12557
+ return !pause.finished && (pause.next || pause.set.matches(callerLocation));
12558
+ }
12559
+ function locatorSource(target, method, args) {
12560
+ try {
12561
+ const text = String(target);
12562
+ if (text && !text.startsWith("[object")) return text;
12563
+ } catch {
12564
+ }
12565
+ return renderFailing({ method, args });
12566
+ }
12567
+ async function pauseBefore(sink, page, target, callerLocation, action, origin) {
12568
+ const pause = sink.pause;
12569
+ const testInfo = sink.testInfo;
12570
+ if (!pause || !testInfo || !callerLocation) return;
12571
+ pause.next = false;
12572
+ try {
12573
+ const locator = locatorSource(target, origin.method, origin.args);
12574
+ const onPick = async (picked) => {
12575
+ const pick = {
12576
+ failing: { method: origin.method, args: origin.args, rendered: locator, location: callerLocation },
12577
+ ...picked
12578
+ };
12579
+ applyPickToSnapshots(sink.capturedLocators, pick);
12580
+ sink.probedLocations.add(callerLocation);
12581
+ const place = callerLocation.slice(0, callerLocation.lastIndexOf(":"));
12582
+ console.log(`[piwi] Locator picked at ${place}: ${picked.picked.locator}`);
12583
+ await postPickToEditor(
12584
+ process.env[PIWI_LOCAL_DEBUG_ENV.editorSend],
12585
+ pickPayload(picked.picked.locator, callerLocation)
12586
+ );
12587
+ };
12588
+ const choice = await internalCall(
12589
+ page,
12590
+ () => pauseHere(
12591
+ page,
12592
+ testInfo,
12593
+ { location: callerLocation, action, locator, target },
12594
+ { fn: probeElementAttrs, arg: CAPTURED_ATTRS_ARG },
12595
+ onPick
12596
+ )
12597
+ );
12598
+ if (choice === "step") pause.next = true;
12599
+ else if (choice === "finish") pause.finished = true;
12600
+ } catch {
12601
+ }
12602
+ }
12273
12603
  function wrapLocator(page, locator, originMethod, originArgs) {
12274
12604
  return new Proxy(locator, {
12275
12605
  get(target, prop) {
@@ -12291,6 +12621,12 @@ function wrapLocator(page, locator, originMethod, originArgs) {
12291
12621
  const isNot = Boolean(callArgs[1]?.isNot);
12292
12622
  const callerLocation = sink ? captureCallerLocation() : null;
12293
12623
  if (sink) recordLocatorPage(sink, page, target, callerLocation, noteLocatorCall(page, EXPECT_METHOD, true));
12624
+ if (sink?.pause && pausesAt(sink.pause, callerLocation)) {
12625
+ await pauseBefore(sink, page, target, callerLocation, assertionLabel(expression, isNot), {
12626
+ method: originMethod,
12627
+ args: originArgs
12628
+ });
12629
+ }
12294
12630
  if (!sink || isNot || !EXPECT_CAPTURE_EXPRESSIONS.has(expression)) {
12295
12631
  return callMethod(target, prop, callArgs);
12296
12632
  }
@@ -12329,6 +12665,9 @@ function wrapLocator(page, locator, originMethod, originArgs) {
12329
12665
  args: originArgs,
12330
12666
  raw: `${originMethod}(${JSON.stringify(originArgs)})`
12331
12667
  };
12668
+ if (sink.pause && pausesAt(sink.pause, callerLocation)) {
12669
+ await pauseBefore(sink, page, target, callerLocation, String(prop), used);
12670
+ }
12332
12671
  const alreadyProbed = callerLocation !== null && sink.probedLocations.has(callerLocation);
12333
12672
  let seq = -1;
12334
12673
  if (!alreadyProbed) {
@@ -12789,6 +13128,12 @@ var piwiFixtures = {
12789
13128
  if (conflict) throw new Error(conflict);
12790
13129
  const sink = createSink();
12791
13130
  sink.testInfo = testInfo;
13131
+ const pauseSet = currentPauseSet();
13132
+ if (pauseSet.size) {
13133
+ const reason = pauseSkipReason(pauseGateFromTestInfo(testInfo));
13134
+ if (reason) logPauseSkipOnce(reason);
13135
+ else sink.pause = { set: pauseSet, next: false, finished: false };
13136
+ }
12792
13137
  if (isFlakeMode()) {
12793
13138
  const plan = loadFlakePlan();
12794
13139
  const role = flakeRoleForTest(plan, {
@@ -12868,12 +13213,12 @@ function extendPiwiFixtures(test) {
12868
13213
 
12869
13214
  // src/internal/ai/ai-fixtures.ts
12870
13215
  var fs24 = __toESM(require("fs"));
12871
- var path30 = __toESM(require("path"));
13216
+ var path31 = __toESM(require("path"));
12872
13217
  var import_test = require("@playwright/test");
12873
13218
 
12874
13219
  // src/internal/ai/artifact.ts
12875
13220
  var fs23 = __toESM(require("fs"));
12876
- var path28 = __toESM(require("path"));
13221
+ var path29 = __toESM(require("path"));
12877
13222
  var ARTIFACT_VERSION = 1;
12878
13223
  var LOCATOR_METHOD_SET2 = new Set(LOCATOR_METHODS);
12879
13224
  var ACTION_METHOD_SET2 = new Set(ACTION_METHODS);
@@ -12977,7 +13322,7 @@ function writeEntry(file, entry) {
12977
13322
  const canonical = serializeEntry(entry);
12978
13323
  return withEntryLock(file, () => {
12979
13324
  if (fs23.existsSync(file) && fs23.readFileSync(file, "utf8") === canonical) return { written: false };
12980
- fs23.mkdirSync(path28.dirname(file), { recursive: true });
13325
+ fs23.mkdirSync(path29.dirname(file), { recursive: true });
12981
13326
  const tmp = `${file}.${process.pid}.tmp`;
12982
13327
  fs23.writeFileSync(tmp, canonical);
12983
13328
  fs23.renameSync(tmp, file);
@@ -12986,7 +13331,7 @@ function writeEntry(file, entry) {
12986
13331
  }
12987
13332
  function withEntryLock(file, fn) {
12988
13333
  const lock = `${file}.lock`;
12989
- fs23.mkdirSync(path28.dirname(file), { recursive: true });
13334
+ fs23.mkdirSync(path29.dirname(file), { recursive: true });
12990
13335
  const deadline = Date.now() + 5e3;
12991
13336
  for (; ; ) {
12992
13337
  try {
@@ -13015,7 +13360,7 @@ function withEntryLock(file, fn) {
13015
13360
 
13016
13361
  // src/internal/ai/keys.ts
13017
13362
  var crypto4 = __toESM(require("crypto"));
13018
- var path29 = __toESM(require("path"));
13363
+ var path30 = __toESM(require("path"));
13019
13364
  var DEFAULT_AI_DIR = "__piwi__";
13020
13365
  function normalizeTemplate(template) {
13021
13366
  return template.trim().replace(/\s+/g, " ").toLowerCase();
@@ -13029,13 +13374,13 @@ function slug(text, maxLength = 40) {
13029
13374
  return (base || "x").slice(0, maxLength).replace(/-+$/g, "") || "x";
13030
13375
  }
13031
13376
  function entryDir(specFile, dir = DEFAULT_AI_DIR) {
13032
- return path29.join(path29.dirname(specFile), dir, path29.basename(specFile));
13377
+ return path30.join(path30.dirname(specFile), dir, path30.basename(specFile));
13033
13378
  }
13034
13379
  function entryPath(params) {
13035
13380
  const basis = `${normalizeTemplate(params.testTitle)}::${normalizeTemplate(params.template)}`;
13036
13381
  const hash = hashTemplate(basis, params.ordinal ?? 0);
13037
13382
  const name = `${slug(params.testTitle)}.${slug(params.template)}.${hash}.json`;
13038
- return path29.join(entryDir(params.specFile, params.dir), name);
13383
+ return path30.join(entryDir(params.specFile, params.dir), name);
13039
13384
  }
13040
13385
  function parseLocation2(location) {
13041
13386
  const match = /:(\d+):(\d+)$/.exec(location);
@@ -13215,9 +13560,9 @@ function renderSourceArg(arg) {
13215
13560
  if (Array.isArray(arg)) return `[${arg.map(renderSourceArg).join(", ")}]`;
13216
13561
  return `{ ${Object.entries(arg).map(([key, value]) => `${key}: ${renderSourceArg(value)}`).join(", ")} }`;
13217
13562
  }
13218
- function locatorSource(loc) {
13563
+ function locatorSource2(loc) {
13219
13564
  const head = `${loc.method}(${loc.args.map(renderSourceArg).join(", ")})`;
13220
- const tail = (loc.chain ?? []).map(locatorSource).join(".");
13565
+ const tail = (loc.chain ?? []).map(locatorSource2).join(".");
13221
13566
  return tail ? `${head}.${tail}` : head;
13222
13567
  }
13223
13568
  function describePostcondition(post) {
@@ -13549,18 +13894,18 @@ function testIdentity(testInfo) {
13549
13894
  return titles.length > 0 ? titles.join(" \u203A ") : testInfo.title;
13550
13895
  }
13551
13896
  function relativeToCwd(file) {
13552
- return path30.relative(process.cwd(), file).split(path30.sep).join("/");
13897
+ return path31.relative(process.cwd(), file).split(path31.sep).join("/");
13553
13898
  }
13554
13899
  function recordIntents(intents, entry) {
13555
13900
  const add = (locator, kind) => {
13556
13901
  intents.set(`${entry.template}\0${locator}`, { template: entry.template, locator, kind });
13557
13902
  };
13558
13903
  if (entry.kind === "locator") {
13559
- add(locatorSource(entry.locator), "locator");
13904
+ add(locatorSource2(entry.locator), "locator");
13560
13905
  return;
13561
13906
  }
13562
- for (const step of entry.steps) add(locatorSource(step.locator), "run");
13563
- if (entry.postcondition.locator) add(locatorSource(entry.postcondition.locator), "run");
13907
+ for (const step of entry.steps) add(locatorSource2(step.locator), "run");
13908
+ if (entry.postcondition.locator) add(locatorSource2(entry.postcondition.locator), "run");
13564
13909
  }
13565
13910
  function missMessage(template, testTitle, relPath) {
13566
13911
  return [