@validate.qa/runner 1.0.100 → 1.0.102

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.
Files changed (31) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-MBKGTELT.mjs → chunk-3JM6XTSZ.mjs} +1862 -530
  3. package/dist/{chunk-2XD63NU5.mjs → chunk-4WLP3RWN.mjs} +941 -86
  4. package/dist/{chunk-MHFP3QXU.mjs → chunk-5DFBEL72.mjs} +1 -1
  5. package/dist/{chunk-II7QW7MZ.mjs → chunk-BXLEPT4M.mjs} +31 -18
  6. package/dist/{chunk-MAO6ADAO.mjs → chunk-CBVHPYVJ.mjs} +62 -2
  7. package/dist/{chunk-5P4ANVDY.mjs → chunk-DN3O6EYT.mjs} +43073 -23357
  8. package/dist/{chunk-GIIIVO2W.mjs → chunk-E2RRAE27.mjs} +155 -11
  9. package/dist/chunk-E2VEMBQQ.mjs +291 -0
  10. package/dist/{chunk-HMUC3DKS.mjs → chunk-HJ6FKHMM.mjs} +2281 -2
  11. package/dist/{chunk-WDXLAZ2J.mjs → chunk-PWFWMVZU.mjs} +3 -2
  12. package/dist/{chunk-6NIT2CCH.mjs → chunk-ZZYBRIBT.mjs} +18 -12
  13. package/dist/cli.js +139723 -114754
  14. package/dist/cli.mjs +536 -247
  15. package/dist/credential-mcp-server.js +1761 -358
  16. package/dist/credential-mcp-server.mjs +3 -3
  17. package/dist/discovery-mcp-server.js +1874 -379
  18. package/dist/discovery-mcp-server.mjs +4 -4
  19. package/dist/dist-52M5RDZ6.mjs +17 -0
  20. package/dist/inbox-mcp-server.js +1902 -505
  21. package/dist/inbox-mcp-server.mjs +3 -3
  22. package/dist/route-seed-worker.js +3976 -356
  23. package/dist/route-seed-worker.mjs +14 -7
  24. package/dist/run-write-guard-preload.js +2908 -572
  25. package/dist/run-write-guard-preload.mjs +5 -285
  26. package/dist/run-write-guard.js +2908 -572
  27. package/dist/run-write-guard.mjs +3 -3
  28. package/dist/spec-driver.js +1 -1
  29. package/dist/spec-driver.mjs +1 -1
  30. package/package.json +4 -3
  31. package/dist/dist-DFNLMH6L.mjs +0 -16
@@ -52692,7 +52692,7 @@ var require_he = __commonJS({
52692
52692
  var stringFromCharCode2 = String.fromCharCode;
52693
52693
  var object = {};
52694
52694
  var hasOwnProperty = object.hasOwnProperty;
52695
- var has = function(object2, propertyName) {
52695
+ var has2 = function(object2, propertyName) {
52696
52696
  return hasOwnProperty.call(object2, propertyName);
52697
52697
  };
52698
52698
  var contains = function(array, value) {
@@ -52712,7 +52712,7 @@ var require_he = __commonJS({
52712
52712
  var result = {};
52713
52713
  var key2;
52714
52714
  for (key2 in defaults) {
52715
- result[key2] = has(options, key2) ? options[key2] : defaults[key2];
52715
+ result[key2] = has2(options, key2) ? options[key2] : defaults[key2];
52716
52716
  }
52717
52717
  return result;
52718
52718
  };
@@ -52724,7 +52724,7 @@ var require_he = __commonJS({
52724
52724
  }
52725
52725
  return "\uFFFD";
52726
52726
  }
52727
- if (has(decodeMapNumeric, codePoint)) {
52727
+ if (has2(decodeMapNumeric, codePoint)) {
52728
52728
  if (strict) {
52729
52729
  parseError("disallowed character reference");
52730
52730
  }
@@ -52765,7 +52765,7 @@ var require_he = __commonJS({
52765
52765
  };
52766
52766
  if (encodeEverything) {
52767
52767
  string = string.replace(regexAsciiWhitelist, function(symbol) {
52768
- if (useNamedReferences && has(encodeMap, symbol)) {
52768
+ if (useNamedReferences && has2(encodeMap, symbol)) {
52769
52769
  return "&" + encodeMap[symbol] + ";";
52770
52770
  }
52771
52771
  return escapeBmpSymbol(symbol);
@@ -52890,7 +52890,7 @@ var require_he = __commonJS({
52890
52890
  freeModule.exports = he;
52891
52891
  } else {
52892
52892
  for (var key in he) {
52893
- has(he, key) && (freeExports[key] = he[key]);
52893
+ has2(he, key) && (freeExports[key] = he[key]);
52894
52894
  }
52895
52895
  }
52896
52896
  } else {
@@ -67537,163 +67537,6 @@ var require_verification_inbox = __commonJS({
67537
67537
  }
67538
67538
  });
67539
67539
 
67540
- // ../runner-core/dist/services/harness-init-script.js
67541
- var require_harness_init_script = __commonJS({
67542
- "../runner-core/dist/services/harness-init-script.js"(exports2) {
67543
- "use strict";
67544
- Object.defineProperty(exports2, "__esModule", { value: true });
67545
- exports2.HARNESS_INIT_SOURCE_URL = void 0;
67546
- exports2.withHarnessSourceUrl = withHarnessSourceUrl;
67547
- exports2.buildAnimationDisablingSource = buildAnimationDisablingSource;
67548
- exports2.isHarnessInitLocation = isHarnessInitLocation;
67549
- exports2.isHarnessInitErrorStack = isHarnessInitErrorStack;
67550
- exports2.isHarnessInitConsoleLine = isHarnessInitConsoleLine;
67551
- exports2.stripHarnessConsoleEntries = stripHarnessConsoleEntries;
67552
- var node_crypto_1 = require("crypto");
67553
- exports2.HARNESS_INIT_SOURCE_URL = `vqa-harness-init-${(0, node_crypto_1.randomBytes)(4).toString("hex")}.js`;
67554
- function withHarnessSourceUrl(content) {
67555
- return `${content}
67556
- //# sourceURL=${exports2.HARNESS_INIT_SOURCE_URL}`;
67557
- }
67558
- function buildAnimationDisablingSource(opts) {
67559
- return withHarnessSourceUrl(`(() => {
67560
- // Layer 1: CSS transitions and animations - instant, not removed
67561
- // (duration 0s preserves final state; 'none' would prevent keyframes from completing)
67562
- try {
67563
- let applied = false;
67564
- const apply = () => {
67565
- if (applied) return true;
67566
- const root = document.head || document.documentElement;
67567
- if (!root) return false;
67568
- const style = document.createElement('style');
67569
- style.textContent = '*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; transition-delay: 0s !important; animation-delay: 0s !important; }';
67570
- root.appendChild(style);
67571
- applied = true;
67572
- return true;
67573
- };
67574
- if (!apply()) {
67575
- // The document has no root element yet (iframe, about:blank, empty document): wait for it.
67576
- let observer = null;
67577
- const stop = () => { try { if (observer) observer.disconnect(); } catch (e) {} };
67578
- const retry = () => { try { if (apply()) stop(); } catch (e) { stop(); } };
67579
- try {
67580
- observer = new MutationObserver(retry);
67581
- observer.observe(document, { childList: true });
67582
- } catch (e) {}
67583
- document.addEventListener('readystatechange', retry);
67584
- document.addEventListener('DOMContentLoaded', () => { retry(); stop(); }, { once: true });
67585
- }
67586
- } catch (e) {}
67587
- ${opts.hideWebdriver ? `
67588
- try {
67589
- Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
67590
- } catch (e) {}
67591
- ` : ""}
67592
- // Layer 2: Web Animations API - make instant instead of no-op
67593
- // (preserves animation completion callbacks that app code may depend on)
67594
- try {
67595
- const origAnimate = Element.prototype.animate;
67596
- if (typeof origAnimate === 'function') {
67597
- Element.prototype.animate = function (keyframes, options) {
67598
- if (typeof options === 'object' && options !== null) {
67599
- options = Object.assign({}, options, { duration: 0, delay: 0 });
67600
- } else {
67601
- options = 0; // covers number, undefined, and any other type
67602
- }
67603
- return origAnimate.call(this, keyframes, options);
67604
- };
67605
- }
67606
- } catch (e) {}
67607
- })();`);
67608
- }
67609
- function isHarnessInitLocation(url) {
67610
- return url === exports2.HARNESS_INIT_SOURCE_URL;
67611
- }
67612
- var MAX_MATCHED_LINE_CHARS = 2048;
67613
- var MAX_SCANNED_LINES = 200;
67614
- var STACK_FRAME_RE = /^\s*at\s+(?:[^()\s][^()]*\()?([^()\s]+):\d+:\d+\)?\s*$/;
67615
- function isHarnessInitErrorStack(stack) {
67616
- if (!stack)
67617
- return false;
67618
- const lines = stack.split("\n", MAX_SCANNED_LINES);
67619
- for (const line of lines) {
67620
- if (line.length > MAX_MATCHED_LINE_CHARS)
67621
- continue;
67622
- const match = STACK_FRAME_RE.exec(line);
67623
- if (match)
67624
- return match[1] === exports2.HARNESS_INIT_SOURCE_URL;
67625
- }
67626
- return false;
67627
- }
67628
- var MCP_CONSOLE_LINE_LOCATION_RE = / @ ([^\s@]+):\d+$/;
67629
- function isHarnessInitConsoleLine(line) {
67630
- const trimmed = line.trim();
67631
- if (trimmed.length > MAX_MATCHED_LINE_CHARS)
67632
- return false;
67633
- const match = MCP_CONSOLE_LINE_LOCATION_RE.exec(trimmed);
67634
- return match !== null && match[1] === exports2.HARNESS_INIT_SOURCE_URL;
67635
- }
67636
- var MCP_LEVEL_LINE_RE = /^\s*(?:-\s+)?\[([A-Za-z]+)\]/;
67637
- var MCP_FRAME_LINE_RE = /^\s+at\s/;
67638
- var MCP_TOTALS_RE = /^(\s*Total messages:\s*)(\d+)(\s*\(Errors:\s*)(\d+)(,\s*Warnings:\s*)(\d+)(\).*)$/;
67639
- var MCP_RETURNING_RE = /^(\s*Returning\s+)(\d+)(\s+messages\b.*)$/;
67640
- function stripHarnessConsoleEntries(text) {
67641
- if (!text.includes(exports2.HARNESS_INIT_SOURCE_URL))
67642
- return text;
67643
- const lines = text.split("\n");
67644
- const out = [];
67645
- let removed = 0;
67646
- let removedErrors = 0;
67647
- let removedWarnings = 0;
67648
- let i = 0;
67649
- while (i < lines.length) {
67650
- const line = lines[i];
67651
- const level = MCP_LEVEL_LINE_RE.exec(line);
67652
- if (level && isHarnessInitConsoleLine(line.replace(/^\s*-\s+/, ""))) {
67653
- removed++;
67654
- const kind = level[1].toLowerCase();
67655
- if (kind === "error")
67656
- removedErrors++;
67657
- else if (kind === "warning" || kind === "warn")
67658
- removedWarnings++;
67659
- i++;
67660
- continue;
67661
- }
67662
- if (MCP_FRAME_LINE_RE.test(line)) {
67663
- let end = i;
67664
- while (end < lines.length && MCP_FRAME_LINE_RE.test(lines[end]))
67665
- end++;
67666
- const frames = lines.slice(i, end);
67667
- if (isHarnessInitErrorStack(frames.join("\n"))) {
67668
- const previous = out[out.length - 1];
67669
- if (previous !== void 0 && previous.trim() !== "" && !/^\s*(?:#|Total messages|Returning)/.test(previous) && !MCP_LEVEL_LINE_RE.test(previous))
67670
- out.pop();
67671
- removed++;
67672
- removedErrors++;
67673
- } else {
67674
- out.push(...frames);
67675
- }
67676
- i = end;
67677
- continue;
67678
- }
67679
- out.push(line);
67680
- i++;
67681
- }
67682
- if (removed === 0)
67683
- return text;
67684
- return out.map((line) => {
67685
- const totals = MCP_TOTALS_RE.exec(line);
67686
- if (totals) {
67687
- const [, a, total, b, errors2, c, warnings, d] = totals;
67688
- return `${a}${Math.max(0, +total - removed)}${b}${Math.max(0, +errors2 - removedErrors)}${c}${Math.max(0, +warnings - removedWarnings)}${d}`;
67689
- }
67690
- const returning = MCP_RETURNING_RE.exec(line);
67691
- return returning ? `${returning[1]}${Math.max(0, +returning[2] - removed)}${returning[3]}` : line;
67692
- }).join("\n");
67693
- }
67694
- }
67695
- });
67696
-
67697
67540
  // ../shared/dist/types.js
67698
67541
  function isUIVariant(value) {
67699
67542
  return typeof value === "string" && UI_VARIANTS.includes(value);
@@ -67743,13 +67586,13 @@ function isBlockedEnvVar(key) {
67743
67586
  function sanitizeTestVariables(vars) {
67744
67587
  if (!vars)
67745
67588
  return {};
67746
- const clean2 = {};
67589
+ const clean3 = {};
67747
67590
  for (const [key, value] of Object.entries(vars)) {
67748
67591
  if (!isBlockedEnvVar(key)) {
67749
- clean2[key] = value;
67592
+ clean3[key] = value;
67750
67593
  }
67751
67594
  }
67752
- return clean2;
67595
+ return clean3;
67753
67596
  }
67754
67597
  function buildSandboxedEnv(processEnv, testVariables) {
67755
67598
  const env = {};
@@ -69206,9 +69049,40 @@ function semanticTarget(label, platform, base2) {
69206
69049
  }
69207
69050
  };
69208
69051
  }
69052
+ function androidResourceOwners(value) {
69053
+ const owners = [];
69054
+ for (const match of value.matchAll(ANDROID_RESOURCE_ID_SUFFIX_RE)) {
69055
+ let runStart = match.index;
69056
+ while (runStart > 0 && /[a-z0-9_.]/i.test(value[runStart - 1]))
69057
+ runStart--;
69058
+ const owner = ownerAtEndOfRun(value.slice(runStart, match.index));
69059
+ if (owner !== null)
69060
+ owners.push(owner);
69061
+ }
69062
+ return owners;
69063
+ }
69064
+ function ownerAtEndOfRun(run) {
69065
+ const segments = run.split(".");
69066
+ const last = segments.length - 1;
69067
+ const startsWithLetter = (segment) => /^[a-z]/i.test(segment);
69068
+ if (last < 1 || !startsWithLetter(segments[last]))
69069
+ return null;
69070
+ let first = last;
69071
+ while (first > 0 && startsWithLetter(segments[first - 1]))
69072
+ first--;
69073
+ let offset = 0;
69074
+ for (let i = 0; i < first - 1; i++)
69075
+ offset += segments[i].length + 1;
69076
+ if (first > 0) {
69077
+ const letterAt = segments[first - 1].search(/[a-z]/i);
69078
+ if (letterAt >= 0)
69079
+ return run.slice(offset + letterAt);
69080
+ offset += segments[first - 1].length + 1;
69081
+ }
69082
+ return first < last ? run.slice(offset) : null;
69083
+ }
69209
69084
  function containsForeignAppPackage(value, targetAppId) {
69210
- for (const match of value.matchAll(ANDROID_RESOURCE_OWNER_RE)) {
69211
- const owner = match[1];
69085
+ for (const owner of androidResourceOwners(value)) {
69212
69086
  if (targetAppId && owner !== targetAppId && !owner.startsWith(`${targetAppId}.`))
69213
69087
  return true;
69214
69088
  }
@@ -69345,7 +69219,7 @@ function healMobileTestSteps(input) {
69345
69219
  summary: changed ? uniq(summary).slice(0, 12) : []
69346
69220
  };
69347
69221
  }
69348
- var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_OWNER_RE, COMMON_EXTERNAL_PREFIXES;
69222
+ var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_ID_SUFFIX_RE, COMMON_EXTERNAL_PREFIXES;
69349
69223
  var init_mobile_step_healer = __esm({
69350
69224
  "../shared/dist/mobile-step-healer.js"() {
69351
69225
  "use strict";
@@ -69366,7 +69240,7 @@ var init_mobile_step_healer = __esm({
69366
69240
  "assertStateDelta"
69367
69241
  ]);
69368
69242
  FOREIGN_PACKAGE_RE = /\b[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}\b/gi;
69369
- ANDROID_RESOURCE_OWNER_RE = /([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+):id\//gi;
69243
+ ANDROID_RESOURCE_ID_SUFFIX_RE = /:id\//gi;
69370
69244
  COMMON_EXTERNAL_PREFIXES = [
69371
69245
  "com.android.",
69372
69246
  "com.google.",
@@ -70679,7 +70553,7 @@ function unescapeQuotedBody(raw) {
70679
70553
  return raw.replace(/\\(['"`\\])/g, "$1");
70680
70554
  }
70681
70555
  function extractRoleNameValue(line) {
70682
- const m = line.match(new RegExp(`getByRole\\s*\\([^,]+,\\s*\\{[^}]*name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
70556
+ const m = line.match(new RegExp(`getByRole\\s*\\([^,]{1,200},\\s*\\{[^}]{0,200}?name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
70683
70557
  if (!m)
70684
70558
  return null;
70685
70559
  const raw = m[1] ?? m[2] ?? m[3];
@@ -71076,7 +70950,7 @@ var init_assertion_classifier = __esm({
71076
70950
  // toHaveCount(0) stays tier 4 (absence proof) via TIER4_PATTERNS.
71077
70951
  [/\.toHaveCount\s*\(\s*[1-9]\d*\s*\)/, "non-zero toHaveCount asserts a record count that changes between runs"],
71078
70952
  // parenthesised counts like "(3)" or "[12]" in text - but not Playwright method args
71079
- [/(?<!\.\w{3,20}\s*)\(\s*\d+\s*\)(?!\s*[,;]?\s*\))/i, "parenthesised count is session-specific"],
70953
+ [/\((?<!\.\w{3,20}\s*\()\s*\d+\s*\)(?!\s*(?:[,;]\s*)?\))/i, "parenthesised count is session-specific"],
71080
70954
  // bare email addresses in assertion content
71081
70955
  [/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i, "email address is user-specific"],
71082
70956
  // currency/balance amounts (dynamic account data)
@@ -71322,7 +71196,7 @@ var init_assertion_proof = __esm({
71322
71196
  PRESENCE_MATCHER = /\.(?:toBeVisible|toBeAttached|toBeEnabled|toBeInViewport|toBeEditable|toBeFocused)\s*\(|\.toHaveCount\s*\(\s*1\s*\)/;
71323
71197
  TEXT_MATCHER = /\.(?:toHaveText|toContainText)\s*\(/;
71324
71198
  ANY_VALUE_SOURCE = /^(?:\.|\\S|\\w|\\d|\[\^\\s\]|\[0-9\]|\[\\d\]|\[\\w\]|\[\\s\\S\])[+*]?\??$/;
71325
- ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*\)/;
71199
+ ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*[,)]/;
71326
71200
  EMPTY_NOT_VALUE = /\.not\s*\.\s*toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/;
71327
71201
  TEXT_MATCHER_LITERAL = /\.(?:toHaveText|toContainText)\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
71328
71202
  GET_BY_TEXT_LITERAL = /getByText\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
@@ -71339,7 +71213,7 @@ var init_assertion_proof = __esm({
71339
71213
  DATA_ROLE = /getByRole\s*\(\s*['"`](?:dialog|alertdialog|alert|cell|gridcell|row|rowheader|columnheader|listitem|article|option|status|note|tooltip|paragraph|term|definition|log|marquee|timer|meter)['"`]/gi;
71340
71214
  TEXT_LOCATOR = /\.getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:/g;
71341
71215
  CSS_LOCATOR = /\.locator\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/g;
71342
- SPECIFIC_CSS = /\[[^\]]*=|#[\w-]|has-text|:text/;
71216
+ SPECIFIC_CSS = /\[[^\][=]*=|#[\w-]|has-text|:text/;
71343
71217
  }
71344
71218
  });
71345
71219
 
@@ -71387,6 +71261,8 @@ function stripCoverageComments(code) {
71387
71261
  let copyFrom = 0;
71388
71262
  let i = 0;
71389
71263
  let quote = null;
71264
+ let prev = "";
71265
+ let noRegexBefore = 0;
71390
71266
  while (i < code.length) {
71391
71267
  const ch = code[i];
71392
71268
  if (quote) {
@@ -71394,12 +71270,24 @@ function stripCoverageComments(code) {
71394
71270
  i += 2;
71395
71271
  continue;
71396
71272
  }
71397
- if (ch === quote || ch === "\n" && quote !== "`")
71273
+ if (ch === quote || ch === "\n" && quote !== "`") {
71398
71274
  quote = null;
71275
+ prev = ch === "\n" ? prev : ch;
71276
+ }
71399
71277
  i++;
71400
71278
  continue;
71401
71279
  }
71402
71280
  const next = code[i + 1];
71281
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
71282
+ const end = regexLiteralEnd3(code, i);
71283
+ if (end > 0) {
71284
+ i = end;
71285
+ prev = "a";
71286
+ continue;
71287
+ }
71288
+ const eol = code.indexOf("\n", i);
71289
+ noRegexBefore = eol === -1 ? code.length : eol;
71290
+ }
71403
71291
  if (ch === "/" && next === "*") {
71404
71292
  const end = code.indexOf("*/", i + 2);
71405
71293
  const stop = end === -1 ? code.length : end + 2;
@@ -71418,11 +71306,39 @@ function stripCoverageComments(code) {
71418
71306
  }
71419
71307
  if (ch === '"' || ch === "'" || ch === "`")
71420
71308
  quote = ch;
71309
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
71310
+ prev = ch;
71421
71311
  i++;
71422
71312
  }
71423
71313
  out.push(code.slice(copyFrom));
71424
71314
  return out.join("");
71425
71315
  }
71316
+ function regexLiteralEnd3(code, start) {
71317
+ let inClass = false;
71318
+ for (let j = start + 1; j < code.length; j++) {
71319
+ const c = code[j];
71320
+ if (c === "\n")
71321
+ return -1;
71322
+ if (c === "\\") {
71323
+ j++;
71324
+ continue;
71325
+ }
71326
+ if (inClass) {
71327
+ if (c === "]")
71328
+ inClass = false;
71329
+ continue;
71330
+ }
71331
+ if (c === "[")
71332
+ inClass = true;
71333
+ else if (c === "/") {
71334
+ let k = j + 1;
71335
+ while (k < code.length && isIdentChar(code[k]))
71336
+ k++;
71337
+ return k;
71338
+ }
71339
+ }
71340
+ return -1;
71341
+ }
71426
71342
  function isIdentChar(ch) {
71427
71343
  if (!ch)
71428
71344
  return false;
@@ -72080,7 +71996,7 @@ function assessTitleCoverage(title, code) {
72080
71996
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
72081
71997
  return { covered: hit >= needed || repeatedHit, missingTerms };
72082
71998
  }
72083
- var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
71999
+ var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, REGEX_PRECEDER2, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
72084
72000
  var init_expected_outcome_coverage = __esm({
72085
72001
  "../shared/dist/expected-outcome-coverage.js"() {
72086
72002
  "use strict";
@@ -72117,6 +72033,7 @@ var init_expected_outcome_coverage = __esm({
72117
72033
  ]);
72118
72034
  MAX_ASSERTION_JOIN_CHARS = 8e3;
72119
72035
  MAX_COVERAGE_LINE_CHARS = 2e3;
72036
+ REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
72120
72037
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
72121
72038
  cachedAssertionInput = null;
72122
72039
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -72260,22 +72177,37 @@ function classifyAuthHandoffIntent(text) {
72260
72177
  function withoutIdpUrlAsserts(code) {
72261
72178
  return code.replace(/\.(?:toHaveURL|waitForURL)\s*\((?:[^)(]|\([^)]*\))*\)/g, (match) => /accountchooser|accounts\.google|appleid\.apple|login\.microsoftonline|\/v\d+\/signin|oauth/i.test(match) ? "" : match);
72262
72179
  }
72180
+ function stripLineComment(line) {
72181
+ let from = line.length;
72182
+ while (from > 0) {
72183
+ const c = line.charCodeAt(from - 1);
72184
+ if (c === 10 || c === 13 || c === 8232 || c === 8233)
72185
+ break;
72186
+ from--;
72187
+ }
72188
+ const at = line.indexOf("//", from);
72189
+ return at < 0 ? line : line.slice(0, at);
72190
+ }
72263
72191
  function stripComments(code) {
72264
- return replaceBlockComments(code, () => "").split("\n").map((line) => line.replace(/\/\/.*$/, "")).join("\n");
72192
+ return replaceBlockComments(code, () => "").split("\n").map(stripLineComment).join("\n");
72265
72193
  }
72266
- function balancedCall(code, start) {
72267
- const open = code.indexOf("(", start);
72268
- if (open < 0)
72269
- return null;
72270
- let depth = 0;
72271
- for (let i = open; i < code.length; i++) {
72194
+ function closingParens(code) {
72195
+ const close = new Int32Array(code.length).fill(-1);
72196
+ const open = [];
72197
+ for (let i = 0; i < code.length; i++) {
72272
72198
  const c = code[i];
72273
72199
  if (c === "(")
72274
- depth++;
72275
- else if (c === ")" && --depth === 0)
72276
- return { inner: code.slice(open + 1, i), end: i };
72200
+ open.push(i);
72201
+ else if (c === ")" && open.length > 0)
72202
+ close[open.pop()] = i;
72277
72203
  }
72278
- return null;
72204
+ return close;
72205
+ }
72206
+ function balancedCall(code, close, start) {
72207
+ const open = code.indexOf("(", start);
72208
+ if (open < 0 || close[open] < 0)
72209
+ return null;
72210
+ return { inner: code.slice(open + 1, close[open]), end: close[open] };
72279
72211
  }
72280
72212
  function resolveLocator(code, subject) {
72281
72213
  const id = subject.trim().match(/^([A-Za-z_$][\w$]*)$/);
@@ -72323,8 +72255,10 @@ function isAuthHandoffDismissCode(code) {
72323
72255
  const expects = [];
72324
72256
  const re = new RegExp(EXPECT_CALL_RE.source, "g");
72325
72257
  let match;
72258
+ let close = null;
72326
72259
  while (match = re.exec(body)) {
72327
- const found = balancedCall(body, match.index);
72260
+ close ??= closingParens(body);
72261
+ const found = balancedCall(body, close, match.index);
72328
72262
  if (found)
72329
72263
  expects.push({ inner: found.inner, tail: body.slice(found.end, found.end + 48) });
72330
72264
  }
@@ -72369,12 +72303,209 @@ var init_auth_handoff_coverage = __esm({
72369
72303
  }
72370
72304
  });
72371
72305
 
72306
+ // ../shared/dist/auth-plan.js
72307
+ function hasInlineLoginSteps(playwrightCode) {
72308
+ if (!playwrightCode)
72309
+ return false;
72310
+ return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
72311
+ }
72312
+ function hasGeneratedAuthGuard(playwrightCode) {
72313
+ if (!playwrightCode)
72314
+ return false;
72315
+ return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
72316
+ }
72317
+ function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
72318
+ if (!sharedAuthInjectEnabled)
72319
+ return false;
72320
+ if (tags.includes("@auth"))
72321
+ return false;
72322
+ if (tags.includes("@api"))
72323
+ return true;
72324
+ if (!tags.includes("@needs-auth"))
72325
+ return false;
72326
+ if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
72327
+ return true;
72328
+ const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
72329
+ const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
72330
+ return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
72331
+ }
72332
+ function codeReadsStoredCredentials(code) {
72333
+ if (!code)
72334
+ return false;
72335
+ if (/__vqaLogin\b/.test(code))
72336
+ return true;
72337
+ for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
72338
+ if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
72339
+ return true;
72340
+ }
72341
+ for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
72342
+ if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
72343
+ return true;
72344
+ }
72345
+ return false;
72346
+ }
72347
+ function explicitAuthTypeTags(t) {
72348
+ const found = /* @__PURE__ */ new Set();
72349
+ for (const tag of t) {
72350
+ if (AUTH_TYPE_TAGS.includes(tag))
72351
+ found.add(tag);
72352
+ else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
72353
+ found.add(AUTH_TAG_ALIASES[tag]);
72354
+ }
72355
+ return [...found];
72356
+ }
72357
+ function invalidPlan(configurationError) {
72358
+ return {
72359
+ version: 1,
72360
+ source: "invalid",
72361
+ acquisition: "unsupported",
72362
+ presentation: "none",
72363
+ needsCookieState: false,
72364
+ needsStandingHeaders: false,
72365
+ standingHeadersMode: "none",
72366
+ needsLoginCreds: false,
72367
+ requiredEnvKeys: [],
72368
+ configurationError
72369
+ };
72370
+ }
72371
+ function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
72372
+ return {
72373
+ version: 1,
72374
+ source: "explicit",
72375
+ authTypeTag: authTypeTag2,
72376
+ acquisition,
72377
+ presentation,
72378
+ needsCookieState: opts.cookie,
72379
+ needsStandingHeaders: opts.standingMode === "required",
72380
+ standingHeadersMode: opts.standingMode,
72381
+ needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
72382
+ requiredEnvKeys: opts.requiredEnvKeys
72383
+ };
72384
+ }
72385
+ function deriveAuthPlan(tags, code, opts) {
72386
+ const t = Array.isArray(tags) ? tags : [];
72387
+ const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
72388
+ const isApi = t.includes("@api");
72389
+ const explicit = isApi ? explicitAuthTypeTags(t) : [];
72390
+ if (explicit.length > 0) {
72391
+ if (t.includes("@auth")) {
72392
+ return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
72393
+ }
72394
+ const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
72395
+ if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
72396
+ const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
72397
+ switch (loginCompose[0]) {
72398
+ case AUTH_TAG_COOKIE_STATE:
72399
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
72400
+ case AUTH_TAG_LOGIN_COOKIE:
72401
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
72402
+ case AUTH_TAG_LOGIN_BEARER:
72403
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
72404
+ }
72405
+ }
72406
+ if (explicit.length > 1) {
72407
+ return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
72408
+ }
72409
+ switch (explicit[0]) {
72410
+ case AUTH_TAG_UNSUPPORTED:
72411
+ return {
72412
+ ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
72413
+ };
72414
+ case AUTH_TAG_NONE:
72415
+ return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
72416
+ case AUTH_TAG_STANDING:
72417
+ return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
72418
+ case AUTH_TAG_LOGIN_BEARER:
72419
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
72420
+ case AUTH_TAG_LOGIN_COOKIE:
72421
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
72422
+ case AUTH_TAG_COOKIE_STATE:
72423
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
72424
+ }
72425
+ }
72426
+ const cookie = legacyNeedsCookieState(t, code, sharedFlag);
72427
+ const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
72428
+ const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
72429
+ const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
72430
+ const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
72431
+ return {
72432
+ version: 1,
72433
+ source: "legacy",
72434
+ acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
72435
+ presentation: authedLegacy ? "cookie" : "none",
72436
+ needsCookieState: cookie,
72437
+ needsStandingHeaders: false,
72438
+ // A legacy bare-@api test keeps injecting standing headers IF the project configured them
72439
+ // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
72440
+ standingHeadersMode: isApi ? "legacy-if-configured" : "none",
72441
+ needsLoginCreds,
72442
+ requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
72443
+ };
72444
+ }
72445
+ function authTypeTag(tags) {
72446
+ const t = Array.isArray(tags) ? tags : [];
72447
+ const explicit = explicitAuthTypeTags(t);
72448
+ return explicit.length === 1 ? explicit[0] : void 0;
72449
+ }
72450
+ var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
72451
+ var init_auth_plan = __esm({
72452
+ "../shared/dist/auth-plan.js"() {
72453
+ "use strict";
72454
+ AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
72455
+ AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
72456
+ AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
72457
+ AUTH_TAG_STANDING = "@auth-standing";
72458
+ AUTH_TAG_NONE = "@auth-none";
72459
+ AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
72460
+ AUTH_TYPE_TAGS = [
72461
+ AUTH_TAG_COOKIE_STATE,
72462
+ AUTH_TAG_LOGIN_COOKIE,
72463
+ AUTH_TAG_LOGIN_BEARER,
72464
+ AUTH_TAG_STANDING,
72465
+ AUTH_TAG_NONE,
72466
+ AUTH_TAG_UNSUPPORTED
72467
+ ];
72468
+ AUTH_TAG_ALIASES = {
72469
+ "@auth-cookie": AUTH_TAG_COOKIE_STATE,
72470
+ "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
72471
+ "@auth-token": AUTH_TAG_LOGIN_BEARER,
72472
+ "@auth-api-key": AUTH_TAG_STANDING,
72473
+ "@auth-standing-header": AUTH_TAG_STANDING
72474
+ };
72475
+ TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
72476
+ API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
72477
+ }
72478
+ });
72479
+
72372
72480
  // ../shared/dist/assertion-strength.js
72481
+ function parseLastActionProofMode(raw) {
72482
+ const v = (raw ?? "").trim().toLowerCase();
72483
+ return v === "on" || v === "shadow" ? v : "off";
72484
+ }
72485
+ function isUrlProof(stmt) {
72486
+ if (URL_PROOF.test(stmt))
72487
+ return true;
72488
+ let calls = 0;
72489
+ for (const open of stmt.matchAll(POLL_OPEN)) {
72490
+ if (++calls > POLL_CALLS_MAX)
72491
+ break;
72492
+ const from = open.index + open[0].length;
72493
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
72494
+ if (args === null)
72495
+ continue;
72496
+ const subject = pollSubject(args);
72497
+ if (!POLL_PAGE_URL.test(subject))
72498
+ continue;
72499
+ if (!POLL_PAGE_URL_ONLY.test(subject) || PAGE_VALUE_MATCHER.test(stmt.slice(from + args.length)))
72500
+ return true;
72501
+ }
72502
+ return false;
72503
+ }
72373
72504
  function liveExpectStatements(uncommented) {
72374
72505
  const masked = maskedOutsideStrings(uncommented).split("\n");
72375
72506
  return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
72376
72507
  }
72377
- function stripLineComment(ln) {
72508
+ function stripLineComment2(ln) {
72378
72509
  let quote = null;
72379
72510
  for (let i = 0; i < ln.length; i++) {
72380
72511
  const c = ln[i];
@@ -72397,7 +72528,7 @@ function splitInteractionStatementsWithLines(lines) {
72397
72528
  let depth = 0;
72398
72529
  let scaffold = false;
72399
72530
  for (let i = 0; i < lines.length; i++) {
72400
- const ln = stripLineComment(lines[i]).trim();
72531
+ const ln = stripLineComment2(lines[i]).trim();
72401
72532
  if (!ln)
72402
72533
  continue;
72403
72534
  const isContinuation = out.length > 0 && (ln.startsWith(".") || depth > 0 && !scaffold);
@@ -72421,7 +72552,7 @@ function testBlockIds(lines) {
72421
72552
  let any = false;
72422
72553
  let block = 0;
72423
72554
  for (let i = 0; i < lines.length; i++) {
72424
- const ln = stripLineComment(lines[i]).trim();
72555
+ const ln = stripLineComment2(lines[i]).trim();
72425
72556
  if (!open) {
72426
72557
  if (!TEST_BLOCK_OPEN.test(ln))
72427
72558
  continue;
@@ -72505,7 +72636,7 @@ function expectSubjectSpan(stmt) {
72505
72636
  }
72506
72637
  return null;
72507
72638
  }
72508
- function resolveSubjectBindings(stmts, bindings, blockOfLine) {
72639
+ function resolveSubjectBindings(stmts, bindings, blockOfLine, boundAt) {
72509
72640
  if (bindings.length === 0)
72510
72641
  return [...stmts];
72511
72642
  return stmts.map((item) => {
@@ -72528,14 +72659,22 @@ function resolveSubjectBindings(stmts, bindings, blockOfLine) {
72528
72659
  subject = `${lead[1]}${found.expr.replace(/;\s*$/, "")}${subject.slice(lead[0].length)}`;
72529
72660
  line = found.line;
72530
72661
  }
72531
- return { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
72662
+ const out = { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
72663
+ if (boundAt !== void 0 && line !== item.startLine)
72664
+ boundAt.set(out, line);
72665
+ return out;
72532
72666
  });
72533
72667
  }
72534
72668
  function stripLoginScaffolding(code) {
72535
- const out = [];
72669
+ const lines = code.split("\n");
72670
+ return keptScaffoldingLines(lines).map((i) => lines[i]).join("\n");
72671
+ }
72672
+ function keptScaffoldingLines(lines) {
72673
+ const kept = [];
72536
72674
  let inAuth = false;
72537
72675
  let inInbox = false;
72538
- for (const ln of code.split("\n")) {
72676
+ for (let i = 0; i < lines.length; i++) {
72677
+ const ln = lines[i];
72539
72678
  if (!inAuth && !inInbox && AUTH_START.test(ln)) {
72540
72679
  inAuth = true;
72541
72680
  continue;
@@ -72556,9 +72695,9 @@ function stripLoginScaffolding(code) {
72556
72695
  }
72557
72696
  if (LOGIN_CALL_LINE.test(ln))
72558
72697
  continue;
72559
- out.push(ln);
72698
+ kept.push(i);
72560
72699
  }
72561
- return out.join("\n");
72700
+ return kept;
72562
72701
  }
72563
72702
  function stripCommentsAndLogs(code) {
72564
72703
  return stripCoverageComments(code).split("\n").filter((ln) => !/^\s*console\./.test(ln)).join("\n");
@@ -72567,7 +72706,7 @@ function detectsErrorBannerAssertion(code) {
72567
72706
  const cleaned = stripCommentsAndLogs(stripLoginScaffolding(code));
72568
72707
  const lines = cleaned.split("\n");
72569
72708
  const candidates = [];
72570
- const positive = (stmt) => !NEGATED_ASSERT.test(stmt) && !ABSENCE_MATCHER.test(stmt);
72709
+ const positive = (stmt) => !claimsAbsence(stmt);
72571
72710
  const statements = liveExpectStatements(cleaned).map((s) => s.stmt);
72572
72711
  for (const stmt of statements) {
72573
72712
  if (positive(stmt))
@@ -72696,6 +72835,12 @@ function detectsObservedFailureAsExpected(code) {
72696
72835
  return FAILURE_STATUS_ASSERT.some((re) => re.test(stmt)) || hasFailedListener && FAILED_LIST_ASSERT.test(stmt);
72697
72836
  });
72698
72837
  }
72838
+ function claimsAbsence(stmt) {
72839
+ const negated = NEGATED_ASSERT_NOT.test(stmt);
72840
+ const bare = negated ? stmt.replace(NOT_BEFORE_MATCHER, ".") : stmt;
72841
+ const absenceMatcher = /\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
72842
+ return negated !== absenceMatcher;
72843
+ }
72699
72844
  function isVacuousErrorAbsence(stmt, presence) {
72700
72845
  if (!ABSENCE_MATCHER.test(stmt))
72701
72846
  return false;
@@ -72704,6 +72849,41 @@ function isVacuousErrorAbsence(stmt, presence) {
72704
72849
  return false;
72705
72850
  return !presence().includes(m[1]);
72706
72851
  }
72852
+ function isVacuousAccountAbsence(stmt, shown, account) {
72853
+ if (!claimsAbsence(stmt))
72854
+ return false;
72855
+ if (!ABSENCE_MATCHER.test(stmt)) {
72856
+ const negated = NEGATED_TEXT.exec(stmt);
72857
+ if (!negated)
72858
+ return false;
72859
+ const read = account.exec(stmt.slice(negated.index));
72860
+ return read !== null && !shown().has(read[0]);
72861
+ }
72862
+ const m = account.exec(stmt);
72863
+ if (!m || shown().has(m[0]))
72864
+ return false;
72865
+ for (let at = endOfEnclosingCall(stmt, m.index + m[0].length); at < stmt.length; at = endOfEnclosingCall(stmt, at)) {
72866
+ SCOPING_STEP.lastIndex = at;
72867
+ if (SCOPING_STEP.test(stmt))
72868
+ return false;
72869
+ }
72870
+ return true;
72871
+ }
72872
+ function endOfEnclosingCall(stmt, from) {
72873
+ let depth = 0;
72874
+ for (let i = from; i < stmt.length; i++) {
72875
+ const c = stmt[i];
72876
+ if (c === "'" || c === '"') {
72877
+ for (i++; i < stmt.length && stmt[i] !== c; i++)
72878
+ if (stmt[i] === "\\")
72879
+ i++;
72880
+ } else if (c === "(")
72881
+ depth++;
72882
+ else if (c === ")" && depth-- === 0)
72883
+ return i + 1;
72884
+ }
72885
+ return stmt.length;
72886
+ }
72707
72887
  function isBareContainerVisibility(stmt) {
72708
72888
  return BARE_CONTAINER_ROLE.test(stmt) && !GET_BY_TEXT.test(stmt) && !HAS_TEXT_FILTER.test(stmt) && !TEXT_CONTENT_MATCHER.test(stmt);
72709
72889
  }
@@ -72711,19 +72891,53 @@ function isChromeTarget(stmt) {
72711
72891
  return CHROME_LOCATOR.test(stmt) && !CONTENT_ROLE.test(stmt) && !/getByText\s*\(/.test(stmt);
72712
72892
  }
72713
72893
  function countsAsContentText(stmt) {
72714
- if (!TEXT_CONTENT_MATCHER.test(stmt))
72894
+ if (!TEXT_CONTENT_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt))
72715
72895
  return false;
72716
72896
  if (isSymbolOnlyArgument(stmt, TEXT_CONTENT_MATCHER))
72717
72897
  return false;
72718
- if (!isChromeTarget(stmt))
72898
+ return onContentTarget(stmt);
72899
+ }
72900
+ function onContentTarget(stmt) {
72901
+ return !isChromeTarget(stmt) || /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
72902
+ }
72903
+ function provesTextRemoved(stmt, presence) {
72904
+ const m = stmt.match(NEGATED_TEXT);
72905
+ if (!m || !onContentTarget(stmt))
72906
+ return false;
72907
+ const text = m[1] ?? m[2];
72908
+ if (text === void 0)
72719
72909
  return true;
72720
- return /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
72910
+ if (!/[\p{L}\p{N}]/u.test(text))
72911
+ return false;
72912
+ return !ERROR_TEXT_WORDS.test(text) || presence().includes(text);
72721
72913
  }
72722
72914
  function isDataContentRole(stmt) {
72723
72915
  return CONTENT_ROLE.test(stmt) && !ANNOUNCE_ROLE.test(stmt);
72724
72916
  }
72917
+ function destructuresRequest(code) {
72918
+ let readTo = -1;
72919
+ for (const m of code.matchAll(ASYNC_DESTRUCTURE_OPEN)) {
72920
+ const start = m.index + m[0].length;
72921
+ if (start <= readTo)
72922
+ continue;
72923
+ let end = start;
72924
+ while (end < code.length && code[end] !== "}" && code[end] !== ")")
72925
+ end++;
72926
+ readTo = end;
72927
+ if (end === code.length)
72928
+ return false;
72929
+ if (code[end] !== "}" || !REQUEST_WORD.test(code.slice(start, end)))
72930
+ continue;
72931
+ let close = end + 1;
72932
+ while (close < code.length && /\s/.test(code[close]))
72933
+ close++;
72934
+ if (code[close] === ")")
72935
+ return true;
72936
+ }
72937
+ return false;
72938
+ }
72725
72939
  function isApiTest(code) {
72726
- const usesRequestFixture = /async\s*\(\s*\{[^})]*\brequest\b[^})]*\}\s*\)/.test(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
72940
+ const usesRequestFixture = destructuresRequest(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
72727
72941
  if (!usesRequestFixture)
72728
72942
  return false;
72729
72943
  const drivesUi = /\bpage\.(getBy\w+|locator|click|fill|goto|waitForURL|reload)\s*\(/.test(code) || /\.(click|fill|check|uncheck|selectOption|pressSequentially)\s*\(/.test(code);
@@ -72751,7 +72965,7 @@ function roleNames(s) {
72751
72965
  function interactedControlNames(lines) {
72752
72966
  const names = /* @__PURE__ */ new Set();
72753
72967
  for (const raw of lines) {
72754
- const ln = stripLineComment(raw);
72968
+ const ln = stripLineComment2(raw);
72755
72969
  if (!INTERACT_VERB.test(ln))
72756
72970
  continue;
72757
72971
  for (const n of roleNames(ln))
@@ -72947,6 +73161,14 @@ function balancedArguments(code, from, limit = code.length) {
72947
73161
  }
72948
73162
  return null;
72949
73163
  }
73164
+ function expectCallTail(stmt) {
73165
+ const at = stmt.search(EXPECT_CALL_RE);
73166
+ if (at < 0)
73167
+ return null;
73168
+ const from = stmt.indexOf("(", at) + 1;
73169
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
73170
+ return args === null ? null : stmt.slice(from + args.length).trimStart();
73171
+ }
72950
73172
  function matcherArgumentsAreConstant(body, from) {
72951
73173
  MATCHER_OPEN.lastIndex = from;
72952
73174
  const opened = MATCHER_OPEN.exec(body);
@@ -72982,32 +73204,70 @@ function hasOnlyConstantAssertions(code) {
72982
73204
  const { constant, real } = constantAssertions(code);
72983
73205
  return constant.length > 0 && real === 0;
72984
73206
  }
73207
+ function envValueKey(name) {
73208
+ return `\0env:${name}`;
73209
+ }
72985
73210
  function enteredValues(stmts) {
72986
73211
  const values = /* @__PURE__ */ new Map();
72987
73212
  const navLines = [];
73213
+ const accountConsts = /* @__PURE__ */ new Set();
72988
73214
  for (const { stmt, startLine } of stmts) {
72989
73215
  if (PAGE_NAV.test(stmt))
72990
73216
  navLines.push(startLine);
73217
+ for (const c of stmt.matchAll(ACCOUNT_CONST))
73218
+ accountConsts.add(c[1].replace(/\$/g, "\\$"));
72991
73219
  let m;
72992
73220
  ENTERED_VALUE.lastIndex = 0;
72993
- while (m = ENTERED_VALUE.exec(stmt)) {
72994
- const value = (m[1] ?? m[2] ?? m[3]).toLowerCase();
72995
- if (!values.has(value))
72996
- values.set(value, startLine);
72997
- }
73221
+ const add = (value) => {
73222
+ const lines = values.get(value);
73223
+ if (!lines)
73224
+ values.set(value, [startLine]);
73225
+ else if (lines[lines.length - 1] !== startLine)
73226
+ lines.push(startLine);
73227
+ };
73228
+ while (m = ENTERED_VALUE.exec(stmt))
73229
+ add((m[1] ?? m[2] ?? m[3]).toLowerCase());
73230
+ ENTERED_ENV.lastIndex = 0;
73231
+ while (m = ENTERED_ENV.exec(stmt))
73232
+ add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
72998
73233
  }
72999
- return { values, navLines };
73234
+ const consts = accountConsts.size > 0 ? `|(?<![\\w$.])(?:${[...accountConsts].join("|")})(?![\\w$])` : "";
73235
+ return { values, navLines, account: new RegExp(`${ACCOUNT_ENV}${consts}`) };
73000
73236
  }
73001
- function isEnteredValueEcho(stmt, entered, startLine) {
73237
+ function countBelow(lines, line) {
73238
+ let lo = 0;
73239
+ let hi = lines.length;
73240
+ while (lo < hi) {
73241
+ const mid = lo + hi >> 1;
73242
+ if (lines[mid] < line)
73243
+ lo = mid + 1;
73244
+ else
73245
+ hi = mid;
73246
+ }
73247
+ return lo;
73248
+ }
73249
+ function enteredLinesOf(stmt, entered) {
73002
73250
  const m = stmt.match(/\.toHaveValue\s*\(\s*(['"`])((?:(?!\1).)+)\1/);
73003
- if (!m)
73251
+ if (m)
73252
+ return entered.values.get(m[2].toLowerCase());
73253
+ const env = stmt.match(HAVE_VALUE_ENV);
73254
+ return env ? entered.values.get(envValueKey(env[1] ?? env[2])) : void 0;
73255
+ }
73256
+ function typedSinceLastNav(stmt, entered, startLine, lines = enteredLinesOf(stmt, entered)) {
73257
+ if (!lines || startLine === void 0)
73258
+ return false;
73259
+ const before = countBelow(lines, startLine);
73260
+ if (before === 0)
73004
73261
  return false;
73005
- const enteredAt = entered.values.get(m[2].toLowerCase());
73006
- if (enteredAt === void 0)
73262
+ return countBelow(entered.navLines, startLine) === countBelow(entered.navLines, lines[before - 1] + 1);
73263
+ }
73264
+ function isEnteredValueEcho(stmt, entered, startLine) {
73265
+ const lines = enteredLinesOf(stmt, entered);
73266
+ if (!lines)
73007
73267
  return false;
73008
- if (startLine === void 0)
73268
+ if (startLine === void 0 || lines[0] >= startLine)
73009
73269
  return true;
73010
- return !entered.navLines.some((n) => n > enteredAt && n < startLine);
73270
+ return typedSinceLastNav(stmt, entered, startLine, lines);
73011
73271
  }
73012
73272
  function isAnyValueMatcher(stmt) {
73013
73273
  if (/\.not\.toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/.test(stmt))
@@ -73097,6 +73357,49 @@ function hasAttributeTransition(stmts) {
73097
73357
  return true;
73098
73358
  return false;
73099
73359
  }
73360
+ function comparesWithItself(subject, tail) {
73361
+ const m = SELF_COMPARE_MATCHER.exec(tail);
73362
+ if (!m)
73363
+ return false;
73364
+ const args = balancedArguments(tail, m[0].length, MATCHER_ARGS_MAX);
73365
+ if (args === null)
73366
+ return false;
73367
+ const squash2 = (x) => x.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
73368
+ const expected = squash2(args);
73369
+ return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
73370
+ }
73371
+ function asPlainAssertion(s) {
73372
+ return plainAssertion(s, false);
73373
+ }
73374
+ function plainAssertion(s, keepSelfCompare) {
73375
+ const { stmt } = s;
73376
+ if (COUNT_AT_LEAST_ZERO.test(stmt))
73377
+ return [];
73378
+ const subject = extractExpectSubject(stmt);
73379
+ if (subject === null)
73380
+ return [s];
73381
+ const plain = subject.replace(/^await\s+/, "").trim();
73382
+ const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
73383
+ const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
73384
+ const as = (rewritten) => [{ ...s, stmt: rewritten }];
73385
+ if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
73386
+ return [];
73387
+ if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
73388
+ return [];
73389
+ if (!keepSelfCompare && comparesWithItself(plain, tail))
73390
+ return [];
73391
+ const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
73392
+ if (visible && IS_VISIBLE_TRUE.test(tail))
73393
+ return as(`await expect(${visible[1]}).toBeVisible();`);
73394
+ if (/^page\s*\.\s*(?:url|title)\s*\(\s*\)$/.test(plain) && PAGE_VALUE_MATCHER.test(tail)) {
73395
+ const matcher = plain.includes("url") ? "toHaveURL" : "toHaveTitle";
73396
+ return as(`await expect(page)${tail.replace(/^\)\s*\.\s*\w+\s*\(/, `.${matcher}(`)}`);
73397
+ }
73398
+ return [s];
73399
+ }
73400
+ function assertionStrengthHoistedReadsOn(value = typeof process !== "undefined" ? process.env?.ASSERTION_STRENGTH_HOISTED_READS : void 0) {
73401
+ return /^(1|true|on)$/i.test((value ?? "").trim());
73402
+ }
73100
73403
  function creditsOutcomeAlways(stmt, entered, startLine) {
73101
73404
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
73102
73405
  return false;
@@ -73129,6 +73432,22 @@ function successNoticeQuote(stmt) {
73129
73432
  }
73130
73433
  return null;
73131
73434
  }
73435
+ function namedPagePath(arg) {
73436
+ const text = arg.trim();
73437
+ let path;
73438
+ const quoted = /^(['"`])([^'"`]*)\1$/.exec(text);
73439
+ const regex = quoted ? null : /^\/((?:\\.|[^/\\])+)\/[a-z]*$/.exec(text);
73440
+ if (quoted) {
73441
+ path = quoted[2].replace(/^\$\{[^}]*\}/, "").replace(/^\*\*/, "").replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "").replace(/\*\*$/, "");
73442
+ } else if (regex) {
73443
+ path = regex[1].replace(/^\^/, "").replace(/^\.\*/, "").replace(/\$$/, "").replace(/\\([/.-])/g, "$1");
73444
+ } else {
73445
+ return null;
73446
+ }
73447
+ if (!PLAIN_PAGE_PATH.test(path))
73448
+ return null;
73449
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
73450
+ }
73132
73451
  function provesStoredControlValue(stmt, startLine, persistence) {
73133
73452
  if (!persistence || startLine === void 0)
73134
73453
  return false;
@@ -73155,7 +73474,64 @@ function reopenedAfterSave(startLine, persistence, reopens = persistence.reloadL
73155
73474
  }
73156
73475
  return lo < reopens.length && reopens[lo] < startLine;
73157
73476
  }
73158
- function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence) {
73477
+ function actsBetween(withLines, from, to) {
73478
+ let acts = actLinesOf.get(withLines);
73479
+ if (acts === void 0) {
73480
+ acts = withLines.filter((s) => ACTS.test(s.stmt)).map((s) => s.startLine).sort((a, b) => a - b);
73481
+ actLinesOf.set(withLines, acts);
73482
+ }
73483
+ let lo = 0;
73484
+ let hi = acts.length;
73485
+ while (lo < hi) {
73486
+ const mid = lo + hi >> 1;
73487
+ if (acts[mid] <= from)
73488
+ lo = mid + 1;
73489
+ else
73490
+ hi = mid;
73491
+ }
73492
+ return lo < acts.length && acts[lo] < to;
73493
+ }
73494
+ function signInProofWindow(stmts, withLines, lines, blockIds) {
73495
+ if (blockIds === null)
73496
+ return void 0;
73497
+ const testBodies2 = /* @__PURE__ */ new Set();
73498
+ for (let i = 0; i < lines.length; i++) {
73499
+ const id = blockIds[i];
73500
+ if (id > 0 && (i === 0 || blockIds[i - 1] !== id) && TEST_BODY_OPEN.test(stripLineComment2(lines[i]).trim()))
73501
+ testBodies2.add(id);
73502
+ }
73503
+ const blockOf = (i) => blockIds[withLines[i].startLine - 1];
73504
+ const passwordAt = stmts.findIndex((stmt, i) => testBodies2.has(blockOf(i)) && ENV_PASSWORD_ENTRY.test(stmt));
73505
+ if (passwordAt < 0)
73506
+ return void 0;
73507
+ const body = blockOf(passwordAt);
73508
+ let clickAt = passwordAt + 1;
73509
+ while (clickAt < stmts.length && FORM_INPUT.test(stmts[clickAt]) && !ANY_CLICK.test(stmts[clickAt]))
73510
+ clickAt++;
73511
+ if (clickAt >= stmts.length || blockOf(clickAt) !== body || !ANY_CLICK.test(stmts[clickAt]) || NAV_LOCATOR.test(stmts[clickAt]))
73512
+ return void 0;
73513
+ let end = clickAt + 1;
73514
+ for (; end < stmts.length && blockOf(end) === body; end++) {
73515
+ if (CLOSERS_ONLY.test(stmts[end]))
73516
+ continue;
73517
+ if (!ASSERT_OR_WAIT.test(stmts[end]) || ACTS.test(stmts[end]))
73518
+ return void 0;
73519
+ }
73520
+ return { after: withLines[clickAt].startLine, until: end < stmts.length ? withLines[end].startLine : Number.POSITIVE_INFINITY };
73521
+ }
73522
+ function isAccountControlCheck(stmt) {
73523
+ if (!VISIBILITY_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt) || OPTION_ABSENCE.test(stmt))
73524
+ return false;
73525
+ const role = ACCOUNT_CONTROL_ROLE.exec(stmt);
73526
+ if (!role)
73527
+ return false;
73528
+ const rest = stmt.slice(role.index + role[0].length, role.index + role[0].length + 300);
73529
+ const close = rest.indexOf("}");
73530
+ const options = close < 0 ? rest : rest.slice(0, close);
73531
+ const name = options.match(/(?:^|[\s,])name\s*:\s*/);
73532
+ return name !== null && ACCOUNT_EMAIL_ENV.test(options.slice(name.index + name[0].length));
73533
+ }
73534
+ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence, signedIn) {
73159
73535
  if (mutated && hasAttributeTransition(stmts))
73160
73536
  return { kind: "durable" };
73161
73537
  const notices = [];
@@ -73165,11 +73541,25 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
73165
73541
  };
73166
73542
  let presenceText;
73167
73543
  const presence = () => presenceText ??= stmts.filter((o) => !ABSENCE_MATCHER.test(o.stmt) && !NEGATED_ASSERT.test(o.stmt)).map((o) => o.stmt).join("\n");
73544
+ let shownAccounts;
73545
+ const shown = () => {
73546
+ if (shownAccounts)
73547
+ return shownAccounts;
73548
+ const ref = new RegExp(entered.account.source, "g");
73549
+ return shownAccounts = new Set(stmts.filter((o) => !claimsAbsence(o.stmt)).flatMap((o) => Array.from(o.stmt.matchAll(ref), (m) => m[0])));
73550
+ };
73168
73551
  for (const { stmt, startLine } of stmts) {
73169
73552
  if (firstMutLine !== void 0 && startLine !== void 0 && startLine < firstMutLine)
73170
73553
  continue;
73171
73554
  if (isVacuousErrorAbsence(stmt, presence))
73172
73555
  continue;
73556
+ if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
73557
+ continue;
73558
+ if (isVacuousAccountAbsence(stmt, shown, entered.account))
73559
+ continue;
73560
+ if (signedIn && startLine !== void 0 && startLine > signedIn.after && startLine < signedIn.until && isAccountControlCheck(stmt)) {
73561
+ return { kind: "durable" };
73562
+ }
73173
73563
  if (creditsOutcomeAlways(stmt, entered, startLine))
73174
73564
  return { kind: "durable" };
73175
73565
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -73179,12 +73569,15 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
73179
73569
  else
73180
73570
  return { kind: "durable" };
73181
73571
  }
73182
- if (STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
73572
+ const retyped = typedSinceLastNav(stmt, entered, startLine);
73573
+ if (!retyped && STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
73183
73574
  return { kind: "durable" };
73184
- if (provesStoredControlValue(stmt, startLine, persistence))
73575
+ if (!retyped && provesStoredControlValue(stmt, startLine, persistence))
73185
73576
  return { kind: "durable" };
73186
73577
  if (countsAsContentText(stmt))
73187
73578
  return { kind: "durable" };
73579
+ if (provesTextRemoved(stmt, presence))
73580
+ return { kind: "durable" };
73188
73581
  if (mutated && VISIBILITY_MATCHER.test(stmt)) {
73189
73582
  const revealsText = GET_BY_TEXT.test(stmt) || HAS_TEXT_FILTER.test(stmt) || CONTENT_ROLE.test(stmt);
73190
73583
  if (revealsText && !isChromeTarget(stmt)) {
@@ -73248,10 +73641,57 @@ function noticeOnlyExemption(clicked, noticeText, interactionStmts) {
73248
73641
  function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine) {
73249
73642
  return classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine).kind !== "none";
73250
73643
  }
73644
+ function clickLabel(stmt) {
73645
+ let label = null;
73646
+ for (const m of stmt.matchAll(CONTROL_LABEL))
73647
+ label = m[2] ?? m[3] ?? null;
73648
+ return label;
73649
+ }
73650
+ function teardownLines(lines) {
73651
+ const out = new Array(lines.length).fill(false);
73652
+ let open = false;
73653
+ let depth = 0;
73654
+ for (let i = 0; i < lines.length; i++) {
73655
+ const ln = stripLineComment2(lines[i]).trim();
73656
+ let from = 0;
73657
+ if (!open) {
73658
+ const m = TEARDOWN_OPEN.exec(ln);
73659
+ if (!m)
73660
+ continue;
73661
+ open = true;
73662
+ depth = 0;
73663
+ from = m.index;
73664
+ }
73665
+ out[i] = true;
73666
+ let closed = false;
73667
+ let quote = null;
73668
+ for (let k = from; k < ln.length; k++) {
73669
+ const ch = ln[k];
73670
+ if (quote !== null) {
73671
+ if (ch === "\\")
73672
+ k++;
73673
+ else if (ch === quote)
73674
+ quote = null;
73675
+ continue;
73676
+ }
73677
+ if (ch === "'" || ch === '"' || ch === "`")
73678
+ quote = ch;
73679
+ else if (ch === "{")
73680
+ depth++;
73681
+ else if (ch === "}") {
73682
+ depth--;
73683
+ closed = true;
73684
+ }
73685
+ }
73686
+ if (depth <= 0 && closed)
73687
+ open = false;
73688
+ }
73689
+ return out;
73690
+ }
73251
73691
  function validateAssertionStrength(code, opts = {}) {
73252
73692
  const body = stripLoginScaffolding(code);
73253
73693
  const lines = body.split("\n");
73254
- const stmts = liveExpectStatements(stripCoverageComments(body));
73694
+ const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
73255
73695
  const base2 = (extra) => ({
73256
73696
  apiTest: false,
73257
73697
  interacted: false,
@@ -73291,7 +73731,13 @@ function validateAssertionStrength(code, opts = {}) {
73291
73731
  const interactionStmts = inlineLocatorBindings(interactionStmtsRaw);
73292
73732
  const blockIds = testBlockIds(lines);
73293
73733
  const blockOfLine = (line) => blockIds?.[line - 1] ?? 0;
73294
- const checks = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine);
73734
+ const hoistedReads = assertionStrengthHoistedReadsOn();
73735
+ const boundAt = hoistedReads ? /* @__PURE__ */ new Map() : void 0;
73736
+ const resolved = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine, boundAt);
73737
+ const checks = boundAt === void 0 ? resolved : resolved.flatMap((s) => {
73738
+ const from = boundAt.get(s);
73739
+ return plainAssertion(s, from !== void 0 && actsBetween(interactionStmtsWithLines, from, s.startLine));
73740
+ });
73295
73741
  const insideTest = linesInsideTestBlocks(lines);
73296
73742
  const inTestOrder = (i) => insideTest === null || insideTest[interactionStmtsWithLines[i].startLine - 1] === true;
73297
73743
  let mutated = opts.interactionPerformed === true;
@@ -73301,9 +73747,34 @@ function validateAssertionStrength(code, opts = {}) {
73301
73747
  let navClicks = 0;
73302
73748
  const persistence = { mutLines: [], reloadLines: [], reloadCalls: [] };
73303
73749
  const openedPages = /* @__PURE__ */ new Set();
73750
+ let checkedPage = null;
73751
+ const savedOnCheckedPage = /* @__PURE__ */ new Set();
73752
+ const saveAt = (i, at) => {
73753
+ lastMutIdx = i;
73754
+ if (!inTestOrder(i))
73755
+ return;
73756
+ persistence.mutLines.push(at);
73757
+ if (firstMutIdx < 0)
73758
+ firstMutIdx = i;
73759
+ if (checkedPage !== null)
73760
+ savedOnCheckedPage.add(checkedPage);
73761
+ };
73762
+ const lastActionProof = opts.lastActionProof ?? "off";
73763
+ const teardown = lastActionProof === "off" ? null : teardownLines(lines);
73764
+ const acts = { last: null, prev: null, count: 0 };
73765
+ const noteAct = (i, at, kind) => {
73766
+ if (teardown === null || teardown[at - 1] || !inTestOrder(i))
73767
+ return;
73768
+ if (kind !== "enter")
73769
+ acts.count++;
73770
+ acts.prev = acts.last;
73771
+ acts.last = { i, kind };
73772
+ };
73304
73773
  for (let i = 0; i < interactionStmts.length; i++) {
73305
73774
  const ln = interactionStmts[i];
73306
73775
  const at = interactionStmtsWithLines[i].startLine;
73776
+ if (teardown !== null && acts.last?.kind === "fill" && ENTER_PRESS.test(ln))
73777
+ noteAct(i, at, "enter");
73307
73778
  if (RELOAD.test(ln)) {
73308
73779
  persistence.reloadLines.push(at);
73309
73780
  persistence.reloadCalls.push(at);
@@ -73311,32 +73782,31 @@ function validateAssertionStrength(code, opts = {}) {
73311
73782
  const opened = ln.match(/\.goto\s*\(\s*([^,)]+)/);
73312
73783
  if (opened) {
73313
73784
  const page = opened[1].trim();
73314
- if (openedPages.has(page))
73785
+ const path = namedPagePath(page);
73786
+ if (openedPages.has(page) || path !== null && savedOnCheckedPage.has(path))
73315
73787
  persistence.reloadLines.push(at);
73316
- else
73317
- openedPages.add(page);
73318
- }
73788
+ openedPages.add(page);
73789
+ checkedPage = null;
73790
+ }
73791
+ if (/\bpage\s*\.\s*go(?:Back|Forward)\s*\(/.test(ln))
73792
+ checkedPage = null;
73793
+ const checked = URL_CHECK_ARG.exec(ln);
73794
+ if (checked)
73795
+ checkedPage = namedPagePath(checked[1] ?? checked[2]);
73319
73796
  if (FORM_INPUT.test(ln)) {
73320
73797
  mutated = true;
73321
- lastMutIdx = i;
73322
- if (inTestOrder(i)) {
73323
- persistence.mutLines.push(at);
73324
- if (firstMutIdx < 0)
73325
- firstMutIdx = i;
73326
- }
73798
+ saveAt(i, at);
73799
+ noteAct(i, at, "fill");
73327
73800
  continue;
73328
73801
  }
73329
73802
  if (ANY_CLICK.test(ln)) {
73330
73803
  if (NAV_LOCATOR.test(ln)) {
73331
73804
  navClicks++;
73805
+ checkedPage = null;
73332
73806
  } else {
73333
73807
  mutated = true;
73334
- lastMutIdx = i;
73335
- if (inTestOrder(i)) {
73336
- persistence.mutLines.push(at);
73337
- if (firstMutIdx < 0)
73338
- firstMutIdx = i;
73339
- }
73808
+ saveAt(i, at);
73809
+ noteAct(i, at, "click");
73340
73810
  }
73341
73811
  }
73342
73812
  }
@@ -73369,9 +73839,28 @@ function validateAssertionStrength(code, opts = {}) {
73369
73839
  };
73370
73840
  }
73371
73841
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
73372
- const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence);
73842
+ const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
73843
+ const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
73373
73844
  if (proof.kind === "durable") {
73374
73845
  const signals2 = base2({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
73846
+ const { last, prev } = acts;
73847
+ if (teardown !== null && last !== null && acts.count >= 2 && last.kind !== "fill" && (last.kind === "enter" || isCommitClick(interactionStmts[last.i]) && !(prev?.kind === "click" && isDestructiveClick(interactionStmts[prev.i])))) {
73848
+ const lastLine = interactionStmtsWithLines[last.i].startLine;
73849
+ const clicked = clickedControlNames(interactionStmts);
73850
+ const after = classifyOutcomeProof(checks, mutated, reloaded, clicked, entered, lastLine, orderedPersistence, signedIn);
73851
+ const unproven = after.kind === "none" || after.kind === "notice" && noticeOnlyExemption(clicked, after.noticeText, interactionStmts) === null;
73852
+ if (unproven && lastActionProof === "on") {
73853
+ const line = (keptScaffoldingLines(code.split("\n"))[lastLine - 1] ?? lastLine - 1) + 1;
73854
+ return {
73855
+ ok: false,
73856
+ severity: "block",
73857
+ reason: `the last save (line ${line}) has no outcome check after it (the earlier check proves only the earlier action)`,
73858
+ signals: { ...signals2, lastActionUnproven: true }
73859
+ };
73860
+ }
73861
+ if (unproven)
73862
+ signals2.lastActionUnproven = true;
73863
+ }
73375
73864
  return { ok: true, severity: "none", reason: "proves an outcome (count / diff / value / revealed content)", signals: signals2 };
73376
73865
  }
73377
73866
  if (mutated && proof.kind === "notice") {
@@ -73420,7 +73909,7 @@ function validateAssertionStrength(code, opts = {}) {
73420
73909
  signals
73421
73910
  };
73422
73911
  }
73423
- const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && URL_PROOF.test(s));
73912
+ const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && isUrlProof(s));
73424
73913
  if (urlProofAfter) {
73425
73914
  return {
73426
73915
  ok: false,
@@ -73457,7 +73946,7 @@ function validateAssertionStrength(code, opts = {}) {
73457
73946
  signals
73458
73947
  };
73459
73948
  }
73460
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, IN_FLIGHT_TEXT, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
73949
+ var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, POLL_OPEN, POLL_PAGE_URL, POLL_PAGE_URL_ONLY, POLL_CALLS_MAX, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, SCOPING_STEP, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, ACCOUNT_KEYS, ACCOUNT_ENV, ACCOUNT_CONST, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, URL_CHECK_ARG, PLAIN_PAGE_PATH, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, ACCOUNT_EMAIL_ENV, ENV_PASSWORD_ENTRY, ACCOUNT_CONTROL_ROLE, ASSERT_OR_WAIT, ACTS, actLinesOf, CLOSERS_ONLY, TEST_BODY_OPEN, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD, COMMIT_CONTROL, NOT_COMMIT_CONTROL, DESTRUCTIVE_CONTROL, ENTER_PRESS, CONTROL_LABEL, isCommitClick, isDestructiveClick, TEARDOWN_OPEN;
73461
73950
  var init_assertion_strength = __esm({
73462
73951
  "../shared/dist/assertion-strength.js"() {
73463
73952
  "use strict";
@@ -73466,6 +73955,7 @@ var init_assertion_strength = __esm({
73466
73955
  init_assertion_proof();
73467
73956
  init_auth_handoff_coverage();
73468
73957
  init_expect_call();
73958
+ init_auth_plan();
73469
73959
  ERROR_BANNER = /(client-side exception|application error|something (?:went wrong|broke)|unhandled (?:exception|error|rejection)|runtime error|error boundary|an unexpected error|internal server error|\b5\d\d\s+(?:internal\s+)?server error|stack trace|chunkloaderror|hydration failed|maximum update depth exceeded)/i;
73470
73960
  OUTCOME_ALWAYS = [
73471
73961
  /\.toHaveCount\s*\(/,
@@ -73482,8 +73972,8 @@ var init_assertion_strength = __esm({
73482
73972
  // form field value
73483
73973
  /\.toMatch\s*\(/,
73484
73974
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
73485
- /\.toBeDisabled\s*\(/,
73486
- // a control became DISABLED — a gating/validation outcome
73975
+ /\.(?<!\.not\s*\.)\s*toBeDisabled\s*\(/,
73976
+ // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
73487
73977
  /\.not\.toBeVisible\s*\(/,
73488
73978
  /\.toBeHidden\s*\(/
73489
73979
  // something the action removed/closed (disappearance proof)
@@ -73491,12 +73981,16 @@ var init_assertion_strength = __esm({
73491
73981
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
73492
73982
  ANY_CLICK = /\.click\s*\(/;
73493
73983
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
73494
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(/;
73984
+ URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
73985
+ POLL_OPEN = /\bexpect\s*\.\s*poll\s*\(/g;
73986
+ POLL_PAGE_URL = /^page\s*\.\s*url\s*\(\s*\)/;
73987
+ POLL_PAGE_URL_ONLY = /^page\s*\.\s*url\s*\(\s*\)$/;
73988
+ POLL_CALLS_MAX = 8;
73495
73989
  RELOAD = /\.reload\s*\(/;
73496
73990
  EXPECT_STMT = /\bexpect\s*[.(]/;
73497
73991
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
73498
73992
  TEST_BLOCK_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow|serial|describe\.serial))?\s*\(|^(?:test\.)?(?:beforeEach|beforeAll)\s*\(/;
73499
- LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\(.+)$/;
73993
+ LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=(?:\s*(?!\s)|\s*(?=\s(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\())(.+?(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\(.+)$/;
73500
73994
  EXPECT_OPEN = /\bexpect(?:\.soft)?\s*\(/;
73501
73995
  STATE_MATCHER = /\.(toBeChecked|toBeVisible|toHaveValue|toBeDisabled|toBeEnabled)\s*\(/;
73502
73996
  CHROME_LOCATOR = /getByRole\s*\(\s*['"`](?:heading|navigation|banner|main|contentinfo|complementary|region|search|tablist|tab|button|link|menuitem|textbox|searchbox|combobox|spinbutton)['"`]/i;
@@ -73516,16 +74010,17 @@ var init_assertion_strength = __esm({
73516
74010
  EMPTY_STATE_TEXT = /\bno\b[\s\S]{0,24}?\b(yet|found|results?|items?|favou?rites?|entries|records?|matches|posts?|comments?)\b|nothing (?:here|yet|to show|found)|\bempty\b|\(\s*0\s*\)|\b0\s+(?:favou?rites?|results?|items?|entries|records?|matches|posts?|comments?)\b/i;
73517
74011
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
73518
74012
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
73519
- LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
74013
+ LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*(?:;\s*)?$/;
73520
74014
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
73521
74015
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
73522
74016
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
74017
+ NEGATED_ASSERT_NOT = /\.not\s*\.\s*to[A-Z]/;
73523
74018
  CONST_CHAIN_MAX_DEPTH = 5;
73524
74019
  TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
73525
74020
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
73526
74021
  FAILURE_STATUS_ASSERT = [
73527
74022
  /\.status\s*\(\s*\)[^;]{0,200}\.(?:toBeGreaterThanOrEqual|toBeGreaterThan)\s*\(\s*(?:399|[45]\d\d)\b/,
73528
- /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\))?\s*>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
74023
+ /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\)\s*)?>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
73529
74024
  /\bexpect\s*\([^;]{0,200}\.ok\s*\(\s*\)\s*\)\s*\.\s*(?:toBe\s*\(\s*false\s*\)|toBeFalsy\s*\()/
73530
74025
  ];
73531
74026
  FAILED_LIST_ASSERT = /\bexpect\s*\(\s*[^;]{0,200}(?:fail|abort|error)\w{0,40}(?:\.length)?\s*\)\s*\.\s*(?:toBeGreaterThan\s*\(\s*0|toHaveLength\s*\(\s*[1-9]|not\s*\.\s*toHaveLength\s*\(\s*0|not\s*\.\s*toEqual\s*\(\s*\[\s*\])/i;
@@ -73535,8 +74030,15 @@ var init_assertion_strength = __esm({
73535
74030
  CATCH_ALL_PATTERN = /^(['"`])\*\*(?:\/\*)?\1$|^\/\.\*\/[a-z]*$/;
73536
74031
  GUARD_HANDLER = /\bMUTATING\.has\s*\(|blockedbyclient|Read-only guard/;
73537
74032
  ABSENCE_MATCHER = /\.not\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/;
74033
+ VALUE_ABSENCE = /\.(?:count\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*0\s*\)|toBeLessThan\s*\(\s*1\s*\))|isVisible\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*false\s*\)|toBeFalsy\s*\()|isHidden\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*true\s*\)|toBeTruthy\s*\())/;
74034
+ OPTION_ABSENCE = /\.(?:toBeVisible\s*\(\s*\{[^{}()]{0,200}\bvisible|toBeAttached\s*\(\s*\{[^{}()]{0,200}\battached)\s*:\s*false\b/;
74035
+ NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
73538
74036
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
73539
74037
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
74038
+ SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
74039
+ NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
74040
+ ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
74041
+ REQUEST_WORD = /\brequest\b/;
73540
74042
  ROLE_NAME_RE = /getByRole\s*\(\s*['"`][a-z]+['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
73541
74043
  INTERACT_VERB = /\.(click|fill|check|uncheck|setChecked|selectOption|pressSequentially)\s*\(/;
73542
74044
  ROLE_SIG_RE = /getByRole\s*\(\s*['"`]([a-z]+)['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
@@ -73557,21 +74059,64 @@ var init_assertion_strength = __esm({
73557
74059
  MATCHER_OPEN = /\s*(?:\.\s*(?:not|resolves|rejects)\s*)*\.\s*[A-Za-z_$][\w$]*\s*\(/y;
73558
74060
  ENTERED_VALUE = /\.(?:fill|pressSequentially)\s*\(\s*['"`]([^'"`]+)['"`]|keyboard\.type\s*\(\s*['"`]([^'"`]+)['"`]|\.selectOption\s*\(\s*(?:\[\s*)?(?:\{\s*(?:label|value)\s*:\s*)?['"`]([^'"`]+)['"`]/g;
73559
74061
  PAGE_NAV = /\.(?:goto|reload)\s*\(/;
74062
+ ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
74063
+ ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
74064
+ HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
74065
+ ACCOUNT_KEYS = TEST_USER_ENV_KEYS.join("|");
74066
+ ACCOUNT_ENV = `process\\.env(?:\\??\\.(?:${ACCOUNT_KEYS})\\b|(?:\\?\\.)?\\[\\s*['"\`](?:${ACCOUNT_KEYS})['"\`]\\s*\\])`;
74067
+ ACCOUNT_CONST = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::\\s*string\\s*)?=\\s*${ACCOUNT_ENV}`, "g");
73560
74068
  IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
74069
+ LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
74070
+ LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
74071
+ ALWAYS_TRUE_ON_OBJECT = /^\)\s*\.\s*(?:toBeTruthy|toBeDefined|not\s*\.\s*(?:toBeNull|toBeUndefined|toBeFalsy))\s*\(/;
74072
+ COUNT_AT_LEAST_ZERO = /\.(?:count\s*\(\s*\)|length)[\s)]{0,10}\.(?:toBeGreaterThanOrEqual\s*\(\s*0\s*\)|toBeGreaterThan\s*\(\s*-\s*1\s*\))/;
74073
+ DEFINED_VALUE_READ = /\.\s*(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|title|url|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts)\s*\([^()]*\)$/;
74074
+ ALWAYS_DEFINED = /^\)\s*\.\s*(?:toBeDefined|not\s*\.\s*toBeUndefined)\s*\(/;
74075
+ SELF_COMPARE_MATCHER = /^\)\s*\.\s*(toBe|toEqual|toStrictEqual|toHaveCount)\s*\(/;
74076
+ IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
74077
+ PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
74078
+ COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
74079
+ URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
74080
+ PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
73561
74081
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
73562
74082
  CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
74083
+ ACCOUNT_EMAIL_ENV = /^process\.env(?:(?:\?\.|\.)TEST_USER_EMAIL\b|(?:\?\.)?\[\s*['"]TEST_USER_EMAIL['"]\s*\])/;
74084
+ ENV_PASSWORD_ENTRY = /\.(?:fill|pressSequentially)\s*\(\s*process\.env(?:(?:\?\.|\.)TEST_USER_PASSWORD\b|(?:\?\.)?\[\s*['"]TEST_USER_PASSWORD['"]\s*\])/;
74085
+ ACCOUNT_CONTROL_ROLE = /getByRole\s*\(\s*['"`](?:button|link|menuitem)['"`]\s*,\s*\{/;
74086
+ ASSERT_OR_WAIT = /^(?:await\s+)?(?:expect(?:\.soft|\.poll)?\s*\(|[\w$]+(?:\.[\w$]+)*\.waitFor[A-Za-z]*\s*\()/;
74087
+ ACTS = /\.(?:click|dblclick|tap|press|pressSequentially|fill|clear|type|check|uncheck|setChecked|selectOption|setInputFiles|dispatchEvent|hover|focus|dragTo|goto|reload|goBack|goForward|evaluate|evaluateHandle)\s*\(|\b(?:keyboard|mouse|touchscreen)\s*\.|\.toPass\s*\(/;
74088
+ actLinesOf = /* @__PURE__ */ new WeakMap();
74089
+ CLOSERS_ONLY = /^[})\];,\s]+$/;
74090
+ TEST_BODY_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow))?\s*\(/;
73563
74091
  COPY_CONTROL = /\bcopy\b/i;
73564
74092
  COPIED_NOTICE = /\bcopied\b/i;
73565
74093
  SEND_CONTROL = /\b(send|submit|subscribe|sign up for|contact|feedback|join (the )?waitlist)\b/i;
73566
74094
  SEND_NOTICE = /\b(thank|thanks|sent|subscribed|received|in touch)\b/i;
73567
74095
  MARK_READ_CONTROL = /\bmark(?:ed)? as read\b|\bmark all(?: as)? read\b/i;
73568
74096
  APP_DATA_FIELD = /\b(title|name of|amount|price|quantity|sku|status|due|assignee|project|ticket|invoice|order)\b/i;
74097
+ COMMIT_CONTROL = /\b(?:save|submit|update|apply|add|create|post|send|publish)\b|\bconfirm\s+changes\b/i;
74098
+ NOT_COMMIT_CONTROL = /\b(?:delete|remove|cancel|close|dismiss|discard|back|sign\s*out|log\s*out|logout|done|ok|got\s+it|export|download)\b/i;
74099
+ DESTRUCTIVE_CONTROL = /\b(?:delete|remove|discard)\b/i;
74100
+ ENTER_PRESS = /\.press\s*\(\s*['"`]Enter['"`]/;
74101
+ CONTROL_LABEL = /(?:name\s*:\s*|getBy(?:Text|Label|Title|TestId)\s*\(\s*|has-text\(\s*)(?:(['"`])((?:(?!\1).)*)\1|\/((?:\\.|[^/\\])+)\/)/g;
74102
+ isCommitClick = (stmt) => {
74103
+ const label = clickLabel(stmt);
74104
+ return label !== null && COMMIT_CONTROL.test(label) && !NOT_COMMIT_CONTROL.test(label) && !EXCLUDE_AFFORDANCE.test(label);
74105
+ };
74106
+ isDestructiveClick = (stmt) => {
74107
+ const label = clickLabel(stmt);
74108
+ return label !== null && DESTRUCTIVE_CONTROL.test(label);
74109
+ };
74110
+ TEARDOWN_OPEN = /\bfinally\s*\{|^(?:test\.)?(?:after|before)(?:Each|All)\s*\(/;
73569
74111
  }
73570
74112
  });
73571
74113
 
73572
74114
  // ../shared/dist/run-classification.js
73573
- function isUserCancelledError(error2) {
73574
- return typeof error2 === "string" && error2.trim().startsWith(USER_CANCELLED_RUN_ERROR);
74115
+ function isCancelledReason(error2) {
74116
+ if (typeof error2 !== "string")
74117
+ return false;
74118
+ const text = error2.trim();
74119
+ return CANCELLED_REASON_PREFIXES.some((prefix) => text.startsWith(prefix));
73575
74120
  }
73576
74121
  function isHeldHealRun(run) {
73577
74122
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
@@ -73604,7 +74149,7 @@ function pickLatestHealthRun(runs) {
73604
74149
  for (const run of runs) {
73605
74150
  if (run.mode === "auth-setup")
73606
74151
  continue;
73607
- if (run.status === "ERROR" && isUserCancelledError(run.error))
74152
+ if (run.status === "ERROR" && isCancelledReason(run.error))
73608
74153
  continue;
73609
74154
  if (isHeldHealRun(run))
73610
74155
  continue;
@@ -73622,6 +74167,15 @@ function pickDisplayRun(runs) {
73622
74167
  return latest;
73623
74168
  return pickLatestHealthRun(runs) ?? latest;
73624
74169
  }
74170
+ function featureRowRunStatus(runs) {
74171
+ const latest = runs.find((run) => run.mode !== "auth-setup");
74172
+ if (!latest)
74173
+ return null;
74174
+ const decisive = pickLatestHealthRun(runs.filter((run) => !isEnvironmentalError(run)));
74175
+ if (decisive)
74176
+ return decisive.status;
74177
+ return latest.status === "PENDING" || latest.status === "RUNNING" ? latest.status : null;
74178
+ }
73625
74179
  function isEnvironmentalError(run) {
73626
74180
  if (!run)
73627
74181
  return false;
@@ -73653,7 +74207,7 @@ function classifyErrorMessage(error2) {
73653
74207
  return "config_issue";
73654
74208
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
73655
74209
  return "email_not_received";
73656
- if (isUserCancelledError(error2))
74210
+ if (isCancelledReason(error2))
73657
74211
  return "cancelled";
73658
74212
  if (isRetriedRunInterruption(error2))
73659
74213
  return "interrupted";
@@ -73688,7 +74242,7 @@ function heldHealRunWhere() {
73688
74242
  function excludeCancelledRunsWhere() {
73689
74243
  return {
73690
74244
  NOT: [
73691
- { status: "ERROR", error: { not: null, startsWith: USER_CANCELLED_RUN_ERROR } },
74245
+ { status: "ERROR", OR: CANCELLED_REASON_PREFIXES.map((prefix) => ({ error: { not: null, startsWith: prefix } })) },
73692
74246
  heldHealRunWhere()
73693
74247
  ]
73694
74248
  };
@@ -73766,11 +74320,25 @@ function reportedBugWhere() {
73766
74320
  function bugReviewStatus(status) {
73767
74321
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
73768
74322
  }
73769
- var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
74323
+ var USER_CANCELLED_RUN_ERROR, PLATFORM_SUPPORT_CANCELLED_RUN_ERROR, PLATFORM_ADMIN_CANCELLED_RUN_ERROR, ADMIN_FORCE_CANCELLED_RUN_ERROR, CHAIN_CANCELLED_RUN_ERROR, RETRY_HELD_RUN_ERROR, CANCELLED_REASON_PREFIXES, isUserCancelledError, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, FEATURE_ROW_RUN_WINDOW, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
73770
74324
  var init_run_classification = __esm({
73771
74325
  "../shared/dist/run-classification.js"() {
73772
74326
  "use strict";
73773
74327
  USER_CANCELLED_RUN_ERROR = "Cancelled by user";
74328
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR = "Cancelled by platform support";
74329
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR = "Cancelled by platform admin";
74330
+ ADMIN_FORCE_CANCELLED_RUN_ERROR = "Admin force-cancelled all active runs";
74331
+ CHAIN_CANCELLED_RUN_ERROR = "Cancelled: parent run cancelled";
74332
+ RETRY_HELD_RUN_ERROR = "Cancelled: auto generation paused";
74333
+ CANCELLED_REASON_PREFIXES = [
74334
+ USER_CANCELLED_RUN_ERROR,
74335
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
74336
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
74337
+ ADMIN_FORCE_CANCELLED_RUN_ERROR,
74338
+ CHAIN_CANCELLED_RUN_ERROR,
74339
+ RETRY_HELD_RUN_ERROR
74340
+ ];
74341
+ isUserCancelledError = isCancelledReason;
73774
74342
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
73775
74343
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
73776
74344
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
@@ -73786,6 +74354,7 @@ var init_run_classification = __esm({
73786
74354
  UNEXPECTED_PASS_ERROR_PREFIX = "UNEXPECTED_PASS:";
73787
74355
  BUG_TEST_BROKEN_ERROR_PREFIX = "BUG_TEST_BROKEN:";
73788
74356
  BUG_TEST_BROKEN_REASON = "bug test broken (not a bug verdict)";
74357
+ TEST_DID_NOT_RUN_ERROR = "Test did not run (a dependency failed or the run was interrupted)";
73789
74358
  EMAIL_NOT_RECEIVED_PREFIXES = [
73790
74359
  "VERIFICATION_EMAIL_NOT_RECEIVED:",
73791
74360
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
@@ -73798,9 +74367,10 @@ var init_run_classification = __esm({
73798
74367
  ];
73799
74368
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
73800
74369
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
73801
- // Written by the cancel routes as is, with no prefix wrapper.
73802
- USER_CANCELLED_RUN_ERROR
74370
+ // Written by the cancels as is, with no prefix wrapper.
74371
+ ...CANCELLED_REASON_PREFIXES
73803
74372
  ];
74373
+ FEATURE_ROW_RUN_WINDOW = 10;
73804
74374
  BUG_SURFACE_SUPPRESSING_TAGS = ["@rate-limit-blocked", "@quota-exceeded", "@auth-lost"];
73805
74375
  QUARANTINE_CLOSE_TAGS = [
73806
74376
  "@bug-found",
@@ -76233,180 +76803,6 @@ var init_admin_view_return_to = __esm({
76233
76803
  }
76234
76804
  });
76235
76805
 
76236
- // ../shared/dist/auth-plan.js
76237
- function hasInlineLoginSteps(playwrightCode) {
76238
- if (!playwrightCode)
76239
- return false;
76240
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
76241
- }
76242
- function hasGeneratedAuthGuard(playwrightCode) {
76243
- if (!playwrightCode)
76244
- return false;
76245
- return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
76246
- }
76247
- function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
76248
- if (!sharedAuthInjectEnabled)
76249
- return false;
76250
- if (tags.includes("@auth"))
76251
- return false;
76252
- if (tags.includes("@api"))
76253
- return true;
76254
- if (!tags.includes("@needs-auth"))
76255
- return false;
76256
- if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
76257
- return true;
76258
- const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
76259
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
76260
- return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
76261
- }
76262
- function codeReadsStoredCredentials(code) {
76263
- if (!code)
76264
- return false;
76265
- if (/__vqaLogin\b/.test(code))
76266
- return true;
76267
- for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
76268
- if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
76269
- return true;
76270
- }
76271
- for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
76272
- if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
76273
- return true;
76274
- }
76275
- return false;
76276
- }
76277
- function explicitAuthTypeTags(t) {
76278
- const found = /* @__PURE__ */ new Set();
76279
- for (const tag of t) {
76280
- if (AUTH_TYPE_TAGS.includes(tag))
76281
- found.add(tag);
76282
- else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
76283
- found.add(AUTH_TAG_ALIASES[tag]);
76284
- }
76285
- return [...found];
76286
- }
76287
- function invalidPlan(configurationError) {
76288
- return {
76289
- version: 1,
76290
- source: "invalid",
76291
- acquisition: "unsupported",
76292
- presentation: "none",
76293
- needsCookieState: false,
76294
- needsStandingHeaders: false,
76295
- standingHeadersMode: "none",
76296
- needsLoginCreds: false,
76297
- requiredEnvKeys: [],
76298
- configurationError
76299
- };
76300
- }
76301
- function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
76302
- return {
76303
- version: 1,
76304
- source: "explicit",
76305
- authTypeTag: authTypeTag2,
76306
- acquisition,
76307
- presentation,
76308
- needsCookieState: opts.cookie,
76309
- needsStandingHeaders: opts.standingMode === "required",
76310
- standingHeadersMode: opts.standingMode,
76311
- needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
76312
- requiredEnvKeys: opts.requiredEnvKeys
76313
- };
76314
- }
76315
- function deriveAuthPlan(tags, code, opts) {
76316
- const t = Array.isArray(tags) ? tags : [];
76317
- const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
76318
- const isApi = t.includes("@api");
76319
- const explicit = isApi ? explicitAuthTypeTags(t) : [];
76320
- if (explicit.length > 0) {
76321
- if (t.includes("@auth")) {
76322
- return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
76323
- }
76324
- const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
76325
- if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
76326
- const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
76327
- switch (loginCompose[0]) {
76328
- case AUTH_TAG_COOKIE_STATE:
76329
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
76330
- case AUTH_TAG_LOGIN_COOKIE:
76331
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
76332
- case AUTH_TAG_LOGIN_BEARER:
76333
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
76334
- }
76335
- }
76336
- if (explicit.length > 1) {
76337
- return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
76338
- }
76339
- switch (explicit[0]) {
76340
- case AUTH_TAG_UNSUPPORTED:
76341
- return {
76342
- ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
76343
- };
76344
- case AUTH_TAG_NONE:
76345
- return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
76346
- case AUTH_TAG_STANDING:
76347
- return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
76348
- case AUTH_TAG_LOGIN_BEARER:
76349
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
76350
- case AUTH_TAG_LOGIN_COOKIE:
76351
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
76352
- case AUTH_TAG_COOKIE_STATE:
76353
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
76354
- }
76355
- }
76356
- const cookie = legacyNeedsCookieState(t, code, sharedFlag);
76357
- const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
76358
- const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
76359
- const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
76360
- const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
76361
- return {
76362
- version: 1,
76363
- source: "legacy",
76364
- acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
76365
- presentation: authedLegacy ? "cookie" : "none",
76366
- needsCookieState: cookie,
76367
- needsStandingHeaders: false,
76368
- // A legacy bare-@api test keeps injecting standing headers IF the project configured them
76369
- // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
76370
- standingHeadersMode: isApi ? "legacy-if-configured" : "none",
76371
- needsLoginCreds,
76372
- requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
76373
- };
76374
- }
76375
- function authTypeTag(tags) {
76376
- const t = Array.isArray(tags) ? tags : [];
76377
- const explicit = explicitAuthTypeTags(t);
76378
- return explicit.length === 1 ? explicit[0] : void 0;
76379
- }
76380
- var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
76381
- var init_auth_plan = __esm({
76382
- "../shared/dist/auth-plan.js"() {
76383
- "use strict";
76384
- AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
76385
- AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
76386
- AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
76387
- AUTH_TAG_STANDING = "@auth-standing";
76388
- AUTH_TAG_NONE = "@auth-none";
76389
- AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
76390
- AUTH_TYPE_TAGS = [
76391
- AUTH_TAG_COOKIE_STATE,
76392
- AUTH_TAG_LOGIN_COOKIE,
76393
- AUTH_TAG_LOGIN_BEARER,
76394
- AUTH_TAG_STANDING,
76395
- AUTH_TAG_NONE,
76396
- AUTH_TAG_UNSUPPORTED
76397
- ];
76398
- AUTH_TAG_ALIASES = {
76399
- "@auth-cookie": AUTH_TAG_COOKIE_STATE,
76400
- "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
76401
- "@auth-token": AUTH_TAG_LOGIN_BEARER,
76402
- "@auth-api-key": AUTH_TAG_STANDING,
76403
- "@auth-standing-header": AUTH_TAG_STANDING
76404
- };
76405
- TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
76406
- API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
76407
- }
76408
- });
76409
-
76410
76806
  // ../shared/dist/auth-block-detector.js
76411
76807
  function stripEchoLines(text) {
76412
76808
  return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
@@ -76414,13 +76810,16 @@ function stripEchoLines(text) {
76414
76810
  function matchesAny(text, patterns) {
76415
76811
  return patterns.some((p) => p.test(text));
76416
76812
  }
76813
+ function isIntentionalNegativeAuthTest(input) {
76814
+ const meta = [input.testName, input.description].filter(Boolean).join(" ");
76815
+ return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
76816
+ }
76417
76817
  function detectAuthBlock(input) {
76418
76818
  const tags = input.tags ?? [];
76419
76819
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
76420
76820
  if (!isAuthed)
76421
76821
  return NO_DETECTION;
76422
- const meta = [input.testName, input.description].filter(Boolean).join(" ");
76423
- if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
76822
+ if (isIntentionalNegativeAuthTest(input))
76424
76823
  return NO_DETECTION;
76425
76824
  const sources = [];
76426
76825
  if (input.error)
@@ -76523,7 +76922,24 @@ var init_auth_block_detector = __esm({
76523
76922
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
76524
76923
  ];
76525
76924
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
76526
- UNAUTHORIZED_TEXT_RE = /(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|gives?|sends?|yield(?:s|ed|ing)?|results?\s+in)\s+(?:a\s+|an\s+|the\s+)?(?:401|403)\b|\bstatus(?:\s+code)?\s*:?\s*(?:401|403)\b|\bHTTP\/[\d.]+\s+(?:401|403)\b|(?:401|403)['’]d\b|(?:[→⇒]|->)\s*(?:401|403)\b|(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|threw|throws?)\s+(?:a\s+|an\s+)?(?:unauthorized|forbidden)\b|\bnot\s+authenticated\b/i;
76925
+ UNAUTHORIZED_TEXT_RE = /(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|gives?|sends?|yield(?:s|ed|ing)?|results?\s+in)\s+(?:a\s+|an\s+|the\s+)?(?:401|403)\b|\bstatus(?:\s+code)?\s*(?::\s*)?(?:401|403)\b|\bHTTP\/[\d.]+\s+(?:401|403)\b|(?:401|403)['’]d\b|(?:[→⇒]|->)\s*(?:401|403)\b|(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|threw|throws?)\s+(?:a\s+|an\s+)?(?:unauthorized|forbidden)\b|\bnot\s+authenticated\b/i;
76926
+ }
76927
+ });
76928
+
76929
+ // ../shared/dist/auth-session-stamp.js
76930
+ function parseAuthSessionStamp(value) {
76931
+ if (!value || typeof value !== "object" || Array.isArray(value))
76932
+ return null;
76933
+ const { shared, role, generation } = value;
76934
+ if (shared !== true || role !== "producer" && role !== "follower")
76935
+ return null;
76936
+ return typeof generation === "string" && AUTH_SESSION_GENERATION_RE.test(generation) ? { shared: true, role, generation } : { shared: true, role };
76937
+ }
76938
+ var AUTH_SESSION_GENERATION_RE;
76939
+ var init_auth_session_stamp = __esm({
76940
+ "../shared/dist/auth-session-stamp.js"() {
76941
+ "use strict";
76942
+ AUTH_SESSION_GENERATION_RE = /^[0-9a-z]{1,16}$/;
76527
76943
  }
76528
76944
  });
76529
76945
 
@@ -77447,12 +77863,70 @@ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
77447
77863
  function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
77448
77864
  return value?.trim().toLowerCase() !== "false";
77449
77865
  }
77866
+ function visualBaselinesEnabled(projectId, raw = process.env.VISUAL_BASELINES_PROJECTS) {
77867
+ if (typeof projectId !== "string" || projectId.trim() === "")
77868
+ return false;
77869
+ const listed = (raw ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
77870
+ return listed.includes("*") || listed.includes(projectId);
77871
+ }
77450
77872
  var init_feature_flags = __esm({
77451
77873
  "../shared/dist/feature-flags.js"() {
77452
77874
  "use strict";
77453
77875
  }
77454
77876
  });
77455
77877
 
77878
+ // ../shared/dist/visual-compare-limits.js
77879
+ function isSafeMaskSelector(selector) {
77880
+ if (typeof selector !== "string")
77881
+ return false;
77882
+ if (selector.trim().length === 0)
77883
+ return false;
77884
+ if (selector.length > VISUAL_MASK_SELECTOR_MAX_LENGTH)
77885
+ return false;
77886
+ return !VISUAL_MASK_SELECTOR_FORBIDDEN.test(selector);
77887
+ }
77888
+ var VISUAL_MASK_SELECTORS_MAX, VISUAL_MASK_SELECTOR_MAX_LENGTH, VISUAL_MASK_SELECTOR_FORBIDDEN;
77889
+ var init_visual_compare_limits = __esm({
77890
+ "../shared/dist/visual-compare-limits.js"() {
77891
+ "use strict";
77892
+ VISUAL_MASK_SELECTORS_MAX = 50;
77893
+ VISUAL_MASK_SELECTOR_MAX_LENGTH = 300;
77894
+ VISUAL_MASK_SELECTOR_FORBIDDEN = /[{};@<\r\n]/;
77895
+ }
77896
+ });
77897
+
77898
+ // ../shared/dist/visual-baseline-identity.js
77899
+ function isBrowserVersion(value) {
77900
+ return typeof value === "string" && value.length <= 32 && BROWSER_VERSION.test(value);
77901
+ }
77902
+ function renderIdentity(flavour, version, image) {
77903
+ if (!/^[a-z][a-z-]{0,31}$/.test(flavour) || !/^[a-z][a-z-]{0,31}$/.test(image) || !isBrowserVersion(version))
77904
+ return null;
77905
+ const identity = `${flavour}/${version}/${image}`;
77906
+ return identity.length <= RENDER_IDENTITY_MAX_LENGTH ? identity : null;
77907
+ }
77908
+ function v1RenderIdentity(version) {
77909
+ return renderIdentity(VISUAL_V1_IDENTITY.flavour, version, VISUAL_V1_IDENTITY.image);
77910
+ }
77911
+ function parseRenderIdentity(value) {
77912
+ if (typeof value !== "string" || value.length > RENDER_IDENTITY_MAX_LENGTH)
77913
+ return null;
77914
+ const parts = value.split("/");
77915
+ if (parts.length !== 3)
77916
+ return null;
77917
+ const [flavour, version, image] = parts;
77918
+ return renderIdentity(flavour, version, image) === value ? { flavour, version, image } : null;
77919
+ }
77920
+ var VISUAL_V1_IDENTITY, RENDER_IDENTITY_MAX_LENGTH, BROWSER_VERSION;
77921
+ var init_visual_baseline_identity = __esm({
77922
+ "../shared/dist/visual-baseline-identity.js"() {
77923
+ "use strict";
77924
+ VISUAL_V1_IDENTITY = { platform: "linux", image: "runner-fast", flavour: "headless-shell" };
77925
+ RENDER_IDENTITY_MAX_LENGTH = 64;
77926
+ BROWSER_VERSION = /^\d{1,4}(\.\d{1,6}){0,3}$/;
77927
+ }
77928
+ });
77929
+
77456
77930
  // ../shared/dist/analytics-url.js
77457
77931
  function isSensitiveQueryKey(key) {
77458
77932
  return SENSITIVE_QUERY_KEY_RE.test(key);
@@ -77755,7 +78229,7 @@ var init_agentic_cli_runtime = __esm({
77755
78229
  GROK_CLI_DEFAULT_MODEL = "grok-4.7";
77756
78230
  GROK_CLI_DEFAULT_REASONING_EFFORT = "high";
77757
78231
  CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
77758
- CLAUDE_CLI_DEFAULT_EFFORT = "max";
78232
+ CLAUDE_CLI_DEFAULT_EFFORT = "high";
77759
78233
  MUSE_CLI_DEFAULT_MODEL = "muse-spark-1.3-contributor";
77760
78234
  MUSE_CLI_DEFAULT_EFFORT = "high";
77761
78235
  CODEX_CLI_DEFAULT_MODEL = "gpt-6.1-sol";
@@ -77810,6 +78284,17 @@ function openCodeGoWireModel(id) {
77810
78284
  const trimmed = id?.trim() ?? "";
77811
78285
  return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
77812
78286
  }
78287
+ function isZaiModelId(id) {
78288
+ const trimmed = id?.trim() ?? "";
78289
+ return ZAI_MODEL_PATTERN.test(trimmed) && !trimmed.includes("..");
78290
+ }
78291
+ function hasZaiPrefix(id) {
78292
+ return (id?.trim() ?? "").startsWith(ZAI_PREFIX);
78293
+ }
78294
+ function zaiWireModel(id) {
78295
+ const trimmed = id?.trim() ?? "";
78296
+ return isZaiModelId(trimmed) ? trimmed.slice(ZAI_PREFIX.length) : null;
78297
+ }
77813
78298
  function isEngineId(value) {
77814
78299
  return CLI_ENGINE_MODEL_IDS.includes(value);
77815
78300
  }
@@ -77906,7 +78391,7 @@ function museCliContributorMode(env = process.env) {
77906
78391
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
77907
78392
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
77908
78393
  }
77909
- var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_MODEL_PATTERN;
78394
+ var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
77910
78395
  var init_engine_spec = __esm({
77911
78396
  "../shared/dist/engine-spec.js"() {
77912
78397
  "use strict";
@@ -77966,6 +78451,8 @@ var init_engine_spec = __esm({
77966
78451
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
77967
78452
  OPENCODE_GO_PREFIX = "opencode-go:";
77968
78453
  OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
78454
+ ZAI_PREFIX = "zai:";
78455
+ ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
77969
78456
  }
77970
78457
  });
77971
78458
 
@@ -78250,6 +78737,8 @@ var init_api_model_catalog = __esm({
78250
78737
  authCaptureModel: "Login capture",
78251
78738
  plannerModel: "Scenario Planner",
78252
78739
  plannerGateModel: "Planner Gate",
78740
+ pwAgentsHealerModel: "Test Healer (Playwright agents)",
78741
+ pwAgentsReviewModel: "Plan Review (Playwright agents)",
78253
78742
  generationModel: "Code Generation",
78254
78743
  reviewModel: "Code Review",
78255
78744
  apiTestModel: "API Test Generator",
@@ -78274,11 +78763,6 @@ var init_api_model_catalog = __esm({
78274
78763
  });
78275
78764
 
78276
78765
  // ../shared/dist/provider-chains.js
78277
- function isClaudeModel(id) {
78278
- if (/claude|anthropic/i.test(id))
78279
- return true;
78280
- return API_MODEL_CATALOG.some((m) => m.provider === "anthropic" && (m.id === id || m.apiModel === id));
78281
- }
78282
78766
  function chainKey(modelId) {
78283
78767
  const id = canonicalModelId(modelId);
78284
78768
  if (!id)
@@ -78286,35 +78770,334 @@ function chainKey(modelId) {
78286
78770
  const entry = API_MODEL_CATALOG.find((m) => m.id === id || m.apiModel === id);
78287
78771
  return entry?.id ?? id;
78288
78772
  }
78773
+ function has(map2, key) {
78774
+ return Object.prototype.hasOwnProperty.call(map2, key);
78775
+ }
78289
78776
  function providerChainFor(modelId) {
78290
78777
  const key = chainKey(modelId);
78291
- if (!key || isClaudeModel(key))
78292
- return [];
78293
- return Object.prototype.hasOwnProperty.call(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
78778
+ if (key && has(_overrideHops, key))
78779
+ return _overrideHops[key];
78780
+ return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
78781
+ }
78782
+ function providerChainLeadsFor(modelId) {
78783
+ const key = chainKey(modelId);
78784
+ if (key && has(_overrideLeads, key))
78785
+ return _overrideLeads[key];
78786
+ return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
78787
+ }
78788
+ function allChains() {
78789
+ const ordered = (map2, override) => [...Object.entries(map2).filter(([key]) => !has(override, key)), ...Object.entries(override)];
78790
+ return [...ordered(PROVIDER_CHAINS, _overrideHops), ...ordered(PROVIDER_CHAIN_LEADS, _overrideLeads)];
78294
78791
  }
78295
78792
  function isProviderChainHopId(modelId) {
78296
78793
  const id = modelId ?? "";
78297
- return id !== "" && Object.values(PROVIDER_CHAINS).some((hops) => hops.includes(id));
78794
+ return id !== "" && allChains().some(([, hops]) => hops.includes(id));
78298
78795
  }
78299
78796
  function providerChainPrimariesForHop(hopId) {
78300
78797
  const id = hopId ?? "";
78301
78798
  if (!id)
78302
78799
  return [];
78303
- return Object.entries(PROVIDER_CHAINS).filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
78800
+ return allChains().filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
78801
+ }
78802
+ function providerChainHostOf(value, modelValue) {
78803
+ if (value === modelValue)
78804
+ return "own";
78805
+ if (hasZaiPrefix(value))
78806
+ return "zai";
78807
+ if (hasOpenCodeGoPrefix(value))
78808
+ return "opencode-go";
78809
+ if (hasOpenRouterPrefix(value))
78810
+ return "openrouter";
78811
+ return "own";
78812
+ }
78813
+ function providerChainHostProvider(value) {
78814
+ if (typeof value !== "string" || !value)
78815
+ return null;
78816
+ if (hasOpenRouterPrefix(value))
78817
+ return "openrouter";
78818
+ if (hasOpenCodeGoPrefix(value))
78819
+ return "opencode-go";
78820
+ if (hasZaiPrefix(value))
78821
+ return "zai";
78822
+ const key = chainKey(value);
78823
+ return API_MODEL_CATALOG.find((m) => m.id === key)?.provider ?? GEMINI_ASSISTANT_MODELS.find((m) => m.id === key)?.provider ?? null;
78824
+ }
78825
+ function providerHostPlan(modelId) {
78826
+ const key = chainKey(modelId);
78827
+ if (!key)
78828
+ return [];
78829
+ const own = { host: hasOpenRouterPrefix(key) ? "openrouter" : "own", value: key };
78830
+ const steps = [
78831
+ ...providerChainLeadsFor(key).map((value) => ({ host: providerChainHostOf(value, key), value })),
78832
+ own,
78833
+ ...providerChainFor(key).map((value) => ({ host: providerChainHostOf(value, key), value }))
78834
+ ];
78835
+ const notOffered = Object.prototype.hasOwnProperty.call(PROVIDER_HOST_NOT_OFFERED, key) ? PROVIDER_HOST_NOT_OFFERED[key] : {};
78836
+ for (const [host, reason] of Object.entries(notOffered)) {
78837
+ const at = steps.findIndex((step) => step === own) + 1;
78838
+ steps.splice(at, 0, { host, value: null, notOffered: reason });
78839
+ }
78840
+ return steps;
78841
+ }
78842
+ function providerChainModelKey(modelId) {
78843
+ return chainKey(modelId);
78844
+ }
78845
+ function hasProviderChainLeadsByDefault(modelId) {
78846
+ const key = chainKey(modelId);
78847
+ return key !== null && has(PROVIDER_CHAIN_LEADS, key);
78848
+ }
78849
+ function isChainablePrimary(id) {
78850
+ return typeof id === "string" && id !== "" && (has(PROVIDER_CHAINS, id) || has(PROVIDER_CHAIN_LEADS, id));
78851
+ }
78852
+ function defaultProviderChainEntries(primary) {
78853
+ if (!isChainablePrimary(primary))
78854
+ return [];
78855
+ const leads = has(PROVIDER_CHAIN_LEADS, primary) ? PROVIDER_CHAIN_LEADS[primary] : [];
78856
+ const hops = has(PROVIDER_CHAINS, primary) ? PROVIDER_CHAINS[primary] : [];
78857
+ return [...leads, primary, ...hops].map((hop) => ({ hop, enabled: true }));
78858
+ }
78859
+ function allowedProviderChainHosts(primary) {
78860
+ return defaultProviderChainEntries(primary).map((entry) => entry.hop);
78861
+ }
78862
+ function isPlainObject2(value) {
78863
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
78864
+ }
78865
+ function validateProviderChainEntries(primary, entries) {
78866
+ if (!isChainablePrimary(primary))
78867
+ return `${String(primary).slice(0, 80)} has no provider chain to order`;
78868
+ if (!Array.isArray(entries))
78869
+ return `${primary}: the chain must be a list`;
78870
+ if (entries.length > PROVIDER_CHAIN_MAX_ENTRIES)
78871
+ return `${primary}: at most ${PROVIDER_CHAIN_MAX_ENTRIES} hosts`;
78872
+ const allowed = allowedProviderChainHosts(primary);
78873
+ const seen = /* @__PURE__ */ new Set();
78874
+ for (const entry of entries) {
78875
+ if (!isPlainObject2(entry))
78876
+ return `${primary}: each host must be { hop, enabled }`;
78877
+ const keys = Object.keys(entry);
78878
+ if (keys.length !== 2 || !keys.includes("hop") || !keys.includes("enabled"))
78879
+ return `${primary}: each host must be { hop, enabled } and nothing else`;
78880
+ const { hop, enabled } = entry;
78881
+ if (typeof enabled !== "boolean")
78882
+ return `${primary}: enabled must be true or false`;
78883
+ if (typeof hop !== "string" || !hop)
78884
+ return `${primary}: hop must be a model value`;
78885
+ if (!allowed.includes(hop))
78886
+ return `${primary}: ${hop.slice(0, 80)} is not a host of this model`;
78887
+ if (seen.has(hop))
78888
+ return `${primary}: ${hop} is listed twice`;
78889
+ if (hop === primary && !enabled)
78890
+ return `${primary}: the model's own value stays on (pick another model on AI Models instead)`;
78891
+ seen.add(hop);
78892
+ }
78893
+ if (!seen.has(primary))
78894
+ return `${primary}: list the model's own value once, where its own host sits`;
78895
+ return null;
78896
+ }
78897
+ function decodeProviderChains(raw) {
78898
+ let value = raw;
78899
+ if (typeof raw === "string") {
78900
+ if (raw.length > PROVIDER_CHAINS_SETTING_MAX_CHARS)
78901
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
78902
+ try {
78903
+ value = JSON.parse(raw);
78904
+ } catch {
78905
+ return { ok: false, error: "AI_PROVIDER_CHAINS must be valid JSON" };
78906
+ }
78907
+ } else if (JSON.stringify(raw ?? null).length > PROVIDER_CHAINS_SETTING_MAX_CHARS) {
78908
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
78909
+ }
78910
+ if (!isPlainObject2(value))
78911
+ return { ok: false, error: 'AI_PROVIDER_CHAINS must be an object: { "<model>": [ { "hop": "<model value>", "enabled": true } ] }' };
78912
+ if (Object.keys(value).length > PROVIDER_CHAIN_MAX_PRIMARIES)
78913
+ return { ok: false, error: `AI_PROVIDER_CHAINS names at most ${PROVIDER_CHAIN_MAX_PRIMARIES} models` };
78914
+ return { ok: true, value };
78915
+ }
78916
+ function checkedProviderChains(value) {
78917
+ const out = {};
78918
+ for (const [primary, entries] of Object.entries(value)) {
78919
+ const problem = validateProviderChainEntries(primary, entries);
78920
+ if (problem)
78921
+ return { ok: false, error: problem };
78922
+ out[primary] = entries.map(({ hop, enabled }) => ({ hop, enabled }));
78923
+ }
78924
+ return { ok: true, value: out };
78925
+ }
78926
+ function parseProviderChainOverrides(raw) {
78927
+ const decoded = decodeProviderChains(raw);
78928
+ return decoded.ok ? checkedProviderChains(decoded.value) : decoded;
78929
+ }
78930
+ function parseStoredProviderChainOverrides(raw) {
78931
+ const decoded = decodeProviderChains(raw);
78932
+ if (!decoded.ok)
78933
+ return decoded;
78934
+ const dropped = [];
78935
+ const kept = {};
78936
+ for (const [primary, entries] of Object.entries(decoded.value)) {
78937
+ if (!isChainablePrimary(primary) || !Array.isArray(entries)) {
78938
+ kept[primary] = entries;
78939
+ continue;
78940
+ }
78941
+ const allowed = allowedProviderChainHosts(primary);
78942
+ kept[primary] = entries.filter((entry) => {
78943
+ const hop = isPlainObject2(entry) ? entry.hop : void 0;
78944
+ if (typeof hop !== "string" || hop === "" || allowed.includes(hop))
78945
+ return true;
78946
+ dropped.push({ primary, hop: hop.slice(0, 80) });
78947
+ return false;
78948
+ });
78949
+ }
78950
+ const checked = checkedProviderChains(kept);
78951
+ return checked.ok ? { ...checked, dropped } : checked;
78304
78952
  }
78305
- var PROVIDER_CHAINS;
78953
+ function validateProviderChainsSetting(raw) {
78954
+ const parsed = parseProviderChainOverrides(raw);
78955
+ return parsed.ok ? null : parsed.error;
78956
+ }
78957
+ function setProviderChainOverrides(value, onDropped) {
78958
+ const parsed = value === null || value === void 0 || value === "" ? null : parseStoredProviderChainOverrides(value);
78959
+ if (parsed?.ok && parsed.dropped.length > 0)
78960
+ onDropped?.(parsed.dropped);
78961
+ _overrides = parsed?.ok ? parsed.value : null;
78962
+ _overrideLeads = {};
78963
+ _overrideHops = {};
78964
+ for (const [primary, entries] of Object.entries(_overrides ?? {})) {
78965
+ const at = entries.findIndex((entry) => entry.hop === primary);
78966
+ const on = (list) => list.filter((entry) => entry.enabled).map((entry) => entry.hop);
78967
+ _overrideLeads[primary] = on(entries.slice(0, at));
78968
+ _overrideHops[primary] = on(entries.slice(at + 1));
78969
+ }
78970
+ return parsed === null || parsed.ok;
78971
+ }
78972
+ function getProviderChainOverrides() {
78973
+ return _overrides;
78974
+ }
78975
+ var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED, PROVIDER_CHAIN_MAX_ENTRIES, PROVIDER_CHAINS_SETTING_MAX_CHARS, PROVIDER_CHAIN_MAX_PRIMARIES, ALLOWED_PROVIDER_CHAIN_HOPS, _overrides, _overrideLeads, _overrideHops;
78306
78976
  var init_provider_chains = __esm({
78307
78977
  "../shared/dist/provider-chains.js"() {
78308
78978
  "use strict";
78309
78979
  init_api_model_catalog();
78980
+ init_engine_spec();
78310
78981
  PROVIDER_CHAINS = {
78311
- "minimax-m3": ["openrouter:minimax/minimax-m3", "opencode-go:minimax-m3"],
78312
- "minimax-m3.1-flash": ["openrouter:minimax/minimax-m3.1-flash", "opencode-go:minimax-m3.1-flash"],
78982
+ "minimax-m3": ["opencode-go:minimax-m3", "openrouter:minimax/minimax-m3"],
78983
+ "minimax-m3.1-flash": ["opencode-go:minimax-m3.1-flash", "openrouter:minimax/minimax-m3.1-flash"],
78313
78984
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
78314
- "openrouter:z-ai/glm-5.3-flash": ["opencode-go:glm-5.3-flash"],
78315
- "openrouter:z-ai/glm-5.3": ["opencode-go:glm-5.3"],
78316
- "openrouter:z-ai/glm-5.2": ["opencode-go:glm-5.2"]
78985
+ "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
78986
+ "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
78987
+ "deepseek-flash": ["opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
78988
+ "deepseek-v4-pro": ["opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
78317
78989
  };
78990
+ PROVIDER_CHAIN_LEADS = {
78991
+ "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
78992
+ "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go:glm-5.3"],
78993
+ "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go:glm-5.2"]
78994
+ };
78995
+ PROVIDER_HOST_NOT_OFFERED = {
78996
+ "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
78997
+ "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
78998
+ };
78999
+ PROVIDER_CHAIN_MAX_ENTRIES = 8;
79000
+ PROVIDER_CHAINS_SETTING_MAX_CHARS = 16e3;
79001
+ PROVIDER_CHAIN_MAX_PRIMARIES = 32;
79002
+ ALLOWED_PROVIDER_CHAIN_HOPS = /* @__PURE__ */ new Set([
79003
+ ...Object.values(PROVIDER_CHAINS).flat(),
79004
+ ...Object.values(PROVIDER_CHAIN_LEADS).flat()
79005
+ ]);
79006
+ _overrides = null;
79007
+ _overrideLeads = {};
79008
+ _overrideHops = {};
79009
+ }
79010
+ });
79011
+
79012
+ // ../shared/dist/provider-accounts.js
79013
+ function modelNames(modelId) {
79014
+ const value = modelId.trim();
79015
+ const names = /* @__PURE__ */ new Set([value]);
79016
+ for (const prefix of HOST_PREFIXES) {
79017
+ if (!value.startsWith(prefix))
79018
+ continue;
79019
+ const bare = value.slice(prefix.length);
79020
+ names.add(bare);
79021
+ const slash = bare.lastIndexOf("/");
79022
+ if (slash >= 0)
79023
+ names.add(bare.slice(slash + 1));
79024
+ }
79025
+ const canonical = canonicalModelId(value);
79026
+ if (canonical) {
79027
+ names.add(canonical);
79028
+ const entry = API_MODEL_CATALOG.find((m) => m.id === canonical || m.apiModel === canonical);
79029
+ if (entry) {
79030
+ names.add(entry.id);
79031
+ if (entry.apiModel)
79032
+ names.add(entry.apiModel);
79033
+ }
79034
+ }
79035
+ return names;
79036
+ }
79037
+ function accountServesModel(models, modelId) {
79038
+ if (!models || models.length === 0)
79039
+ return true;
79040
+ if (!modelId)
79041
+ return false;
79042
+ const names = modelNames(modelId);
79043
+ return models.some((model) => names.has(model.trim()));
79044
+ }
79045
+ function normalizeProviderAccountModels(models) {
79046
+ if (!Array.isArray(models))
79047
+ return null;
79048
+ const out = [];
79049
+ for (const model of models) {
79050
+ if (typeof model !== "string")
79051
+ return null;
79052
+ const trimmed = model.trim();
79053
+ if (!PROVIDER_ACCOUNT_MODEL_PATTERN.test(trimmed) || trimmed.includes(".."))
79054
+ return null;
79055
+ if (!out.includes(trimmed))
79056
+ out.push(trimmed);
79057
+ }
79058
+ return out.length > MAX_PROVIDER_ACCOUNT_MODELS ? null : out;
79059
+ }
79060
+ function parseProviderAccountLists(value, providers) {
79061
+ if (!value || typeof value !== "object" || Array.isArray(value))
79062
+ return null;
79063
+ const parsed = {};
79064
+ for (const provider of providers) {
79065
+ const list = value[provider];
79066
+ if (!Array.isArray(list))
79067
+ continue;
79068
+ const accounts = [];
79069
+ for (const raw of list) {
79070
+ if (!raw || typeof raw !== "object")
79071
+ continue;
79072
+ const { id, label, key, models } = raw;
79073
+ if (typeof id !== "string" || !id.trim() || typeof key !== "string" || !key.trim())
79074
+ continue;
79075
+ const modelList = Array.isArray(models) ? models.filter((m) => typeof m === "string" && m.trim() !== "").map((m) => m.trim()) : [];
79076
+ accounts.push({ id: id.trim(), label: typeof label === "string" && label.trim() ? label.trim() : null, key: key.trim(), models: modelList });
79077
+ }
79078
+ if (accounts.length > 0)
79079
+ parsed[provider] = accounts;
79080
+ }
79081
+ return parsed;
79082
+ }
79083
+ var DEFAULT_PROVIDER_ACCOUNT_ID, PROVIDER_ACCOUNT_ID_PATTERN, MAX_PROVIDER_ACCOUNTS, MAX_PROVIDER_ACCOUNT_MODELS, PROVIDER_ACCOUNT_MODEL_PATTERN, OPENCODE_GO_DEEPSEEK_GLM_MODELS, HOST_PREFIXES;
79084
+ var init_provider_accounts = __esm({
79085
+ "../shared/dist/provider-accounts.js"() {
79086
+ "use strict";
79087
+ init_api_model_catalog();
79088
+ DEFAULT_PROVIDER_ACCOUNT_ID = "primary";
79089
+ PROVIDER_ACCOUNT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
79090
+ MAX_PROVIDER_ACCOUNTS = 10;
79091
+ MAX_PROVIDER_ACCOUNT_MODELS = 50;
79092
+ PROVIDER_ACCOUNT_MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,120}$/;
79093
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS = [
79094
+ "deepseek-v4.1-flash",
79095
+ "deepseek-v4-pro",
79096
+ "glm-5.3-flash",
79097
+ "glm-5.3",
79098
+ "glm-5.2"
79099
+ ];
79100
+ HOST_PREFIXES = ["opencode-go:", "zai:", "openrouter:"];
78318
79101
  }
78319
79102
  });
78320
79103
 
@@ -78433,6 +79216,25 @@ var init_project_engine_overrides = __esm({
78433
79216
  }
78434
79217
  });
78435
79218
 
79219
+ // ../shared/dist/trailing-slashes.js
79220
+ function stripTrailingSlashes(value) {
79221
+ let end = value.length;
79222
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
79223
+ end--;
79224
+ return end === value.length ? value : value.slice(0, end);
79225
+ }
79226
+ function stripTrailingChars(value, isTrailing) {
79227
+ let end = value.length;
79228
+ while (end > 0 && isTrailing.test(value[end - 1]))
79229
+ end--;
79230
+ return end === value.length ? value : value.slice(0, end);
79231
+ }
79232
+ var init_trailing_slashes = __esm({
79233
+ "../shared/dist/trailing-slashes.js"() {
79234
+ "use strict";
79235
+ }
79236
+ });
79237
+
78436
79238
  // ../shared/dist/gemini-visual.js
78437
79239
  function visualProDailyBudget(raw) {
78438
79240
  const parsed = raw && /^\s*\d{1,6}\s*$/.test(raw) ? Number(raw.trim()) : NaN;
@@ -78446,7 +79248,7 @@ function isRetiredGeminiModel(id) {
78446
79248
  }
78447
79249
  function geminiNativeModelsUrl(base2 = process.env.GEMINI_API_BASE) {
78448
79250
  const raw = base2 && base2.trim() || GEMINI_OPENAI_BASE;
78449
- const native = raw.replace(/\/openai\/?$/, "/").replace(/\/+$/, "");
79251
+ const native = stripTrailingSlashes(raw.replace(/\/openai\/?$/, "/"));
78450
79252
  return `${native}/models?pageSize=1000`;
78451
79253
  }
78452
79254
  function asRecord(value) {
@@ -78566,6 +79368,7 @@ var KNOWN_INPUT_USD_PER_M, GEMINI_OPENAI_BASE, DEFAULT_GEMINI_SCREEN_MODEL, DEFA
78566
79368
  var init_gemini_visual = __esm({
78567
79369
  "../shared/dist/gemini-visual.js"() {
78568
79370
  "use strict";
79371
+ init_trailing_slashes();
78569
79372
  KNOWN_INPUT_USD_PER_M = {
78570
79373
  "gemini-2.5-flash-lite": 0.1,
78571
79374
  "gemini-3.1-flash-lite": 0.25,
@@ -79929,12 +80732,13 @@ function truncateAtWord(text, max) {
79929
80732
  const slice = trimmed.slice(0, room + 1);
79930
80733
  const lastSpace = slice.lastIndexOf(" ");
79931
80734
  const cut = lastSpace > room * 0.5 ? slice.slice(0, lastSpace) : trimmed.slice(0, room);
79932
- return `${cut.replace(/[\s,;:.\-–—]+$/u, "")}\u2026`;
80735
+ return `${stripTrailingChars(cut, TRAILING_PUNCTUATION)}\u2026`;
79933
80736
  }
79934
- var CANVAS_UNSUPPORTED_CODE, CANVAS_UNSUPPORTED_TITLE, CANVAS_UNSUPPORTED_BODY, CANVAS_UNSUPPORTED_NOT_CHARGED, CANVAS_UNSUPPORTED_OVERRIDE_HINT, GAME_ENGINES, RENDERER_ENGINES, ENGINE_TEXT_SIGNATURES, GAME_LIBRARY_TEXT_PATTERNS, ENGINE_PACKAGES, GAME_HINT_PACKAGES, MAX_TAG_CHARS, MAX_CSS_SELECTOR_CHARS, MAX_CSS_BLOCK_CHARS, TAG_NAME_RE, ATTR_RE, INTERACTIVE_TAGS, INTERACTIVE_ROLES, ROOT_SELECTOR_RE, OVERFLOW_HIDDEN_RE, TOUCH_NONE_RE, CODE_TAGS, STATIC_STANDARD_MIN_INTERACTIVE;
80737
+ var CANVAS_UNSUPPORTED_CODE, CANVAS_UNSUPPORTED_TITLE, CANVAS_UNSUPPORTED_BODY, CANVAS_UNSUPPORTED_NOT_CHARGED, CANVAS_UNSUPPORTED_OVERRIDE_HINT, GAME_ENGINES, RENDERER_ENGINES, ENGINE_TEXT_SIGNATURES, GAME_LIBRARY_TEXT_PATTERNS, ENGINE_PACKAGES, GAME_HINT_PACKAGES, MAX_TAG_CHARS, MAX_CSS_SELECTOR_CHARS, MAX_CSS_BLOCK_CHARS, TAG_NAME_RE, ATTR_RE, INTERACTIVE_TAGS, INTERACTIVE_ROLES, ROOT_SELECTOR_RE, OVERFLOW_HIDDEN_RE, TOUCH_NONE_RE, CODE_TAGS, STATIC_STANDARD_MIN_INTERACTIVE, TRAILING_PUNCTUATION;
79935
80738
  var init_canvas_support = __esm({
79936
80739
  "../shared/dist/canvas-support.js"() {
79937
80740
  "use strict";
80741
+ init_trailing_slashes();
79938
80742
  CANVAS_UNSUPPORTED_CODE = "canvas_unsupported";
79939
80743
  CANVAS_UNSUPPORTED_TITLE = "Games and canvas apps aren't supported yet";
79940
80744
  CANVAS_UNSUPPORTED_BODY = "This app draws its screen on a canvas (like a game), so there are no page elements for Validate.QA to read and test. Support for games and canvas apps is coming.";
@@ -80059,6 +80863,7 @@ var init_canvas_support = __esm({
80059
80863
  TOUCH_NONE_RE = /touch-action\s{0,8}:\s{0,8}none/;
80060
80864
  CODE_TAGS = /* @__PURE__ */ new Set(["script", "link", "flt-glass-pane", "flutter-view", "flt-scene-host"]);
80061
80865
  STATIC_STANDARD_MIN_INTERACTIVE = 3;
80866
+ TRAILING_PUNCTUATION = /[\s,;:.\-–—]/u;
80062
80867
  }
80063
80868
  });
80064
80869
 
@@ -80241,22 +81046,22 @@ function joinChainedLines(code) {
80241
81046
  const trimmed = lines[i].trimStart();
80242
81047
  if (last !== -1 && trimmed.startsWith(".") && !trimmed.startsWith("...")) {
80243
81048
  if (!lastStripped) {
80244
- lines[last] = stripLineComment2(lines[last]).trimEnd();
81049
+ lines[last] = stripLineComment3(lines[last]).trimEnd();
80245
81050
  lastStripped = true;
80246
81051
  }
80247
81052
  if (lines[last].length <= MAX_LINE_CHARS)
80248
- lines[last] = `${lines[last]} ${stripLineComment2(trimmed).trimEnd()}`;
81053
+ lines[last] = `${lines[last]} ${stripLineComment3(trimmed).trimEnd()}`;
80249
81054
  lines[i] = "";
80250
81055
  continue;
80251
81056
  }
80252
- if (stripLineComment2(lines[i]).trim()) {
81057
+ if (stripLineComment3(lines[i]).trim()) {
80253
81058
  last = i;
80254
81059
  lastStripped = false;
80255
81060
  }
80256
81061
  }
80257
81062
  return lines.join("\n");
80258
81063
  }
80259
- function stripLineComment2(rawLine) {
81064
+ function stripLineComment3(rawLine) {
80260
81065
  if (rawLine.trimStart().startsWith("//"))
80261
81066
  return "";
80262
81067
  const at = rawLine.indexOf(" // ");
@@ -80272,7 +81077,7 @@ function stateLiteralsOf(line) {
80272
81077
  const out = [];
80273
81078
  for (const m of line.matchAll(STRING_LITERAL_RE)) {
80274
81079
  const text = m[1] ?? m[2] ?? m[3] ?? "";
80275
- if (text.includes("placeholder="))
81080
+ if (PLACEHOLDER_ATTR_RE.test(text))
80276
81081
  continue;
80277
81082
  if (PLACEHOLDER_CALL_TAIL_RE.test(line.slice(Math.max(0, m.index - 40), m.index)))
80278
81083
  continue;
@@ -80305,7 +81110,7 @@ function analyzeReadinessPolling(rawCode) {
80305
81110
  offset += rawLine.length + 1;
80306
81111
  if (rawLine.length > MAX_LINE_CHARS)
80307
81112
  continue;
80308
- const line = stripLineComment2(rawLine);
81113
+ const line = stripLineComment3(rawLine);
80309
81114
  if (!line.trim())
80310
81115
  continue;
80311
81116
  const literals = literalsOf(line);
@@ -80368,7 +81173,7 @@ function formatReadinessPollAdvisory(findings) {
80368
81173
  const where = findings.length === 1 ? "One place" : `${findings.length} places`;
80369
81174
  return `${where} re-check a background state once and assert the finished state at once (${short(first.line, 90)} then ${short(first.assertion, 90)}). ${first.reason}`;
80370
81175
  }
80371
- var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
81176
+ var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_ATTR_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
80372
81177
  var init_readiness_poll = __esm({
80373
81178
  "../shared/dist/readiness-poll.js"() {
80374
81179
  "use strict";
@@ -80381,7 +81186,7 @@ var init_readiness_poll = __esm({
80381
81186
  STRING_LITERAL_RE = /'((?:\\.|[^'\\\n]){0,300})'|"((?:\\.|[^"\\\n]){0,300})"|`((?:\\.|[^`\\\n]){0,300})`/g;
80382
81187
  RECHECK_NAME_RE = /^\s{0,10}(?:check again|check (?:status|availability|now|for updates|progress)|re-?check(?: status)?|update status|see (?:latest|updates))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
80383
81188
  RETRY_NAME_RE = /^\s{0,10}(?:refresh(?: (?:status|list|page|results|now))?|reload(?: (?:page|results|list))?|try again|retry|load (?:again|latest))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
80384
- PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b[a-z]{3,40}(?<!th)ing(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
81189
+ PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b(?:saving|uploading|downloading|signing|logging|submitting|creating|updating|deleting|importing|exporting|publishing|connecting|redirecting|authenticating|installing|retrieving|fetching|analy[sz]ing|running|rendering|restarting|building|compiling|booting|provisioning|deploying|indexing|migrating|launching|initiali[sz]ing|optimi[sz]ing|converting|processing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}(?:starting|scanning|working|applying|finishing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}[a-z]{3,40}(?<!th)ing\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
80385
81190
  LOCATOR_LINE_RE = /\.(?:getBy[A-Z]\w{0,30}|locator|toHaveText|toContainText)\s{0,20}\(/;
80386
81191
  CLICK_RE = /\.(?:click|dblclick|tap)\s{0,20}\(/;
80387
81192
  RELOAD_RE = /\bpage\.reload\s{0,20}\(/;
@@ -80395,6 +81200,7 @@ var init_readiness_poll = __esm({
80395
81200
  TEST_END_RE = /^\s{0,20}\}\s{0,20}\)\s{0,20};?\s{0,20}$/;
80396
81201
  TEST_START_RE = /\btest(?:\.\w{1,20})?\s{0,20}\(/;
80397
81202
  POLL_OPEN_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}async\b|\bexpect\.poll\s{0,20}\(|\bfor\s{0,20}\(|\bwhile\s{0,20}\(|\bdo\s{0,20}\{/g;
81203
+ PLACEHOLDER_ATTR_RE = /placeholder\s{0,4}[*^$~|]?=/;
80398
81204
  PLACEHOLDER_CALL_TAIL_RE = /getByPlaceholder\s{0,20}\(\s{0,20}$/;
80399
81205
  POLL_SHAPE = "await expect(async () => { await page.reload(); await expect(<the finished state>).toBeVisible({ timeout: 1500 }); }).toPass({ timeout: 20000 });";
80400
81206
  }
@@ -80416,7 +81222,7 @@ function blankBlockComments2(code) {
80416
81222
  from = close + 2;
80417
81223
  }
80418
81224
  }
80419
- function stripLineComment3(rawLine) {
81225
+ function stripLineComment4(rawLine) {
80420
81226
  if (rawLine.trimStart().startsWith("//"))
80421
81227
  return "";
80422
81228
  const at = rawLine.indexOf(" // ");
@@ -80427,7 +81233,7 @@ function logicalLines(code) {
80427
81233
  for (const raw of code.split("\n")) {
80428
81234
  if (raw.length > MAX_LINE_CHARS2)
80429
81235
  continue;
80430
- const line = stripLineComment3(raw).trimEnd();
81236
+ const line = stripLineComment4(raw).trimEnd();
80431
81237
  if (!line.trim())
80432
81238
  continue;
80433
81239
  const trimmed = line.trimStart();
@@ -80789,13 +81595,33 @@ function short4(text, max) {
80789
81595
  const flat = text.replace(/\s+/g, " ").trim();
80790
81596
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
80791
81597
  }
81598
+ function stripFractionZeros(s) {
81599
+ let end = s.length;
81600
+ while (end > 0 && s[end - 1] === "0")
81601
+ end--;
81602
+ if (end === s.length)
81603
+ return s;
81604
+ const dot = s.lastIndexOf(".");
81605
+ if (dot < 0)
81606
+ return s;
81607
+ return s.slice(0, end === dot + 1 ? dot : end);
81608
+ }
80792
81609
  function digitsOf(token) {
80793
- return token.replace(/[^\d.]/g, "").replace(/\.0+$|(\.\d*?)0+$/, "$1").replace(/^0+(?=\d)/, "");
81610
+ return stripFractionZeros(token.replace(/[^\d.]/g, "")).replace(/^0+(?=\d)/, "");
80794
81611
  }
80795
81612
  function computes(line) {
80796
81613
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
80797
81614
  return COMPUTE_RE.test(code) || RELATIONAL_RE.test(line);
80798
81615
  }
81616
+ function identifiersIn(line) {
81617
+ const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX");
81618
+ return [code, ...[...line.matchAll(TEMPLATE_EXPR_RE)].map((m) => m[1])].flatMap((part) => part.match(IDENTIFIER_RE) ?? []);
81619
+ }
81620
+ function addTyped(typed, value) {
81621
+ const d = digitsOf(value);
81622
+ if (/\d/.test(d))
81623
+ typed.add(d);
81624
+ }
80799
81625
  function reasonFor4(kind, label, value) {
80800
81626
  const what = kind === "derived-value-shape-only" ? `The ${label} is checked only for its shape, so any amount passes, including a wrongly computed one.` : `The ${label} is checked against a number written into the test (${value}). That number was read off the page, so when the page computes it wrong the check still passes.`;
80801
81627
  return `${what} Instead, ${COMPUTE_SHAPE} If the number is not computed from other values (a list price, an id, a fixed fee), leave it as it is. Keep the existing checks.`;
@@ -80807,25 +81633,32 @@ function analyzeDerivedValues(rawCode) {
80807
81633
  for (const body of testBodies(rawCode)) {
80808
81634
  if (findings.length >= MAX_FINDINGS4)
80809
81635
  break;
80810
- if (body.some(computes))
80811
- continue;
80812
81636
  const typed = /* @__PURE__ */ new Set();
80813
81637
  const definitions = /* @__PURE__ */ new Map();
81638
+ const computed = /* @__PURE__ */ new Set();
81639
+ const usesComputed = (text) => identifiersIn(text).some((id) => computed.has(id));
80814
81640
  for (const line of body) {
80815
81641
  if (FILL_RE2.test(line) && !EXPECT_RE3.test(line)) {
80816
- for (const m of line.matchAll(/['"`]([^'"`\n]{0,40})['"`]/g)) {
80817
- const d = digitsOf(m[1]);
80818
- if (/\d/.test(d))
80819
- typed.add(d);
80820
- }
81642
+ for (const m of line.matchAll(QUOTED_RE))
81643
+ addTyped(typed, m[1]);
81644
+ const arg = FILL_ARG_RE.exec(line)?.[1];
81645
+ const literal = arg === void 0 ? null : LITERAL_VALUE_RE.exec(definitions.get(arg) ?? "");
81646
+ const value = literal?.slice(1).find((v) => v !== void 0);
81647
+ if (value !== void 0)
81648
+ addTyped(typed, value);
80821
81649
  }
80822
81650
  const def = DEFINITION_RE.exec(line);
80823
- if (def)
81651
+ if (def) {
80824
81652
  definitions.set(def[1], def[2]);
81653
+ if (computes(def[2]) || usesComputed(def[2]))
81654
+ computed.add(def[1]);
81655
+ }
80825
81656
  }
80826
81657
  for (const line of body) {
80827
81658
  if (!EXPECT_RE3.test(line) || !VALUE_MATCHER_RE2.test(line) || /\.not\s{0,20}\./.test(line))
80828
81659
  continue;
81660
+ if (computes(line) || usesComputed(line))
81661
+ continue;
80829
81662
  const subject = EXPECT_SUBJECT_RE.exec(line)?.[1];
80830
81663
  const label = DERIVED_WORD_RE.exec(`${line} ${(subject && definitions.get(subject)) ?? ""}`)?.[0];
80831
81664
  if (!label)
@@ -80855,7 +81688,7 @@ function formatDerivedValueAdvisory(findings) {
80855
81688
  const where = findings.length === 1 ? "One computed value is" : `${findings.length} computed values are`;
80856
81689
  return `${where} checked against a copied number or only its shape (${short4(first.line, 90)}). ${first.reason}`;
80857
81690
  }
80858
- var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, COMPUTE_SHAPE;
81691
+ var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, TEMPLATE_EXPR_RE, IDENTIFIER_RE, QUOTED_RE, FILL_ARG_RE, LITERAL_VALUE_RE, COMPUTE_SHAPE;
80859
81692
  var init_derived_value_check = __esm({
80860
81693
  "../shared/dist/derived-value-check.js"() {
80861
81694
  "use strict";
@@ -80875,6 +81708,11 @@ var init_derived_value_check = __esm({
80875
81708
  DEFINITION_RE = /\b(?:const|let)\s{1,10}([\w$]{1,60})\s{0,10}=\s{0,10}([^\n]{0,300})/;
80876
81709
  EXPECT_SUBJECT_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}([\w$]{1,60})/;
80877
81710
  LITERAL_ARITH_RE = /\b\d[\d_.]{0,15}(?:\s{0,5}[*/+-]\s{0,5}\d[\d_.]{0,15}){1,8}\b/g;
81711
+ TEMPLATE_EXPR_RE = /\$\{([^{}\n]{0,200})\}/g;
81712
+ IDENTIFIER_RE = /[A-Za-z_$][\w$]{0,60}/g;
81713
+ QUOTED_RE = /['"`]([^'"`\n]{0,40})['"`]/g;
81714
+ FILL_ARG_RE = /\.(?:fill|type|pressSequentially|selectOption)\s{0,20}\(\s{0,20}([A-Za-z_$][\w$]{0,60})\s{0,20}[,)]/;
81715
+ LITERAL_VALUE_RE = /^\s{0,10}(?:'([^'\n]{0,40})'|"([^"\n]{0,40})"|`([^`$\n]{0,40})`|(\d[\d_,]{0,15}(?:\.\d{1,4})?))\s{0,10};?\s{0,10}$/;
80878
81716
  COMPUTE_SHAPE = "compute the expected value in the test from its inputs (the values the test typed, or each input read from the page) and compare: const toNumber = (s) => Number(s.replace(/[^\\d.-]/g, '')); const unit = toNumber(await <unit price locator>.innerText()); then expect(toNumber(await <shown value locator>.innerText())).toBeCloseTo(unit * quantity, 2); for several lines, read them all and sum them with reduce. For a comma-decimal price like \u20AC1.234,56, drop the dots and turn the comma into a point before Number, since the replace above would read it as 1.23456.";
80879
81717
  }
80880
81718
  });
@@ -80987,7 +81825,7 @@ function findExpectCalls(code) {
80987
81825
  if (!subj)
80988
81826
  continue;
80989
81827
  const rest = code.slice(subj.end + 1, subj.end + 1 + 80);
80990
- const mm = /^\s*((?:\.\s*not)?\s*\.\s*(\w+))\s{0,20}\(/.exec(rest);
81828
+ const mm = /^\s*((?:\.\s*not\s*)?\.\s*(\w+))\s{0,20}\(/.exec(rest);
80991
81829
  if (!mm)
80992
81830
  continue;
80993
81831
  const argStart = subj.end + 1 + mm[0].length;
@@ -82909,6 +83747,133 @@ function formatStaleAfterSubmitAdvisory(rawCode) {
82909
83747
  const more = described.length > 3 ? ` (+${described.length - 3} more)` : "";
82910
83748
  return `stale-after-submit: ${described.slice(0, 3).join("; ")}${more}; these pass before the request lands and still pass if the server rejects or ignores the save. Wait for the result instead: page.waitForResponse around the click, or assert something the submit creates (the new row, the confirmation message, a path with an end anchor such as /\\/orders\\/\\d+$/)`;
82911
83749
  }
83750
+ function regexLiteralEnd4(text, at) {
83751
+ let inClass = false;
83752
+ for (let i = at + 1; i < text.length; i++) {
83753
+ const ch = text[i];
83754
+ if (ch === "\n")
83755
+ return -1;
83756
+ if (ch === "\\") {
83757
+ i++;
83758
+ continue;
83759
+ }
83760
+ if (inClass) {
83761
+ if (ch === "]")
83762
+ inClass = false;
83763
+ continue;
83764
+ }
83765
+ if (ch === "[") {
83766
+ inClass = true;
83767
+ continue;
83768
+ }
83769
+ if (ch === "/")
83770
+ return i + 1;
83771
+ }
83772
+ return -1;
83773
+ }
83774
+ function startsWithRegexLiteral(text) {
83775
+ const t = text.trimStart();
83776
+ return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
83777
+ }
83778
+ function isBareRegexLiteral(text) {
83779
+ const t = text.trimStart();
83780
+ if (t[0] !== "/" || t[1] === "/" || t[1] === "*")
83781
+ return false;
83782
+ const end = regexLiteralEnd4(t, 0);
83783
+ if (end === -1)
83784
+ return false;
83785
+ let i = end;
83786
+ while (i < t.length && /[a-z]/.test(t[i]))
83787
+ i++;
83788
+ while (i < t.length && /\s/.test(t[i]))
83789
+ i++;
83790
+ return i >= t.length || t[i] === ",";
83791
+ }
83792
+ function regexDeclaredNames(code) {
83793
+ const names = /* @__PURE__ */ new Set();
83794
+ const decl = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,80})\s{0,20}=\s{0,20}(?:\/(?![/*])|new\s{1,20}RegExp\b)/g;
83795
+ let m;
83796
+ while ((m = decl.exec(code)) !== null)
83797
+ names.add(m[1]);
83798
+ return names;
83799
+ }
83800
+ function afterFirstArg(args) {
83801
+ let depth = 0;
83802
+ let quote = null;
83803
+ for (let i = 0; i < args.length; i++) {
83804
+ const ch = args[i];
83805
+ if (quote) {
83806
+ if (ch === "\\") {
83807
+ i++;
83808
+ continue;
83809
+ }
83810
+ if (ch === quote)
83811
+ quote = null;
83812
+ continue;
83813
+ }
83814
+ if (ch === '"' || ch === "'" || ch === "`") {
83815
+ quote = ch;
83816
+ continue;
83817
+ }
83818
+ if (ch === "(" || ch === "[" || ch === "{")
83819
+ depth++;
83820
+ else if (ch === ")" || ch === "]" || ch === "}")
83821
+ depth--;
83822
+ else if (ch === "," && depth === 0)
83823
+ return args.slice(i + 1);
83824
+ }
83825
+ return "";
83826
+ }
83827
+ function analyzeRegexMatchers(rawCode) {
83828
+ if (!rawCode || typeof rawCode !== "string")
83829
+ return [];
83830
+ const code = stripComments2(rawCode.slice(0, MAX_LINT_INPUT));
83831
+ const out = [];
83832
+ const re = /\bexpect(?:\.soft)?\s{0,20}\(/g;
83833
+ let m;
83834
+ let examined = 0;
83835
+ let line = 1;
83836
+ let lineAt2 = 0;
83837
+ let regexNames;
83838
+ while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
83839
+ const subj = readBalanced(code, m.index + m[0].length);
83840
+ if (!subj)
83841
+ continue;
83842
+ const mm = /^\s{0,20}(\.\s{0,20}not)?\s{0,20}\.\s{0,20}(\w{1,40})\s{0,20}\(/.exec(code.slice(subj.end + 1, subj.end + 1 + 80));
83843
+ if (!mm || !REGEX_EQUALITY_MATCHERS.has(mm[2]))
83844
+ continue;
83845
+ const args = readBalanced(code, subj.end + 1 + mm[0].length);
83846
+ if (!args)
83847
+ continue;
83848
+ const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
83849
+ if (!isBareRegexLiteral(expected))
83850
+ continue;
83851
+ if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain") {
83852
+ const subject = subj.text.trim();
83853
+ if (startsWithRegexLiteral(subject) || /^new\s+RegExp\b/.test(subject))
83854
+ continue;
83855
+ if (/^[A-Za-z_$][\w$]{0,80}$/.test(subject)) {
83856
+ regexNames ??= regexDeclaredNames(code);
83857
+ if (regexNames.has(subject))
83858
+ continue;
83859
+ }
83860
+ }
83861
+ for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
83862
+ line++;
83863
+ lineAt2 = i + 1;
83864
+ }
83865
+ out.push({ line, matcher: mm[2], negated: mm[1] !== void 0 });
83866
+ }
83867
+ return out;
83868
+ }
83869
+ function formatRegexMatcherNote(findings) {
83870
+ const lines = findings.map((f) => {
83871
+ const negated = f.matcher === "toContain" ? "throws on a string and otherwise always passes (a regex never equals an item), so it checks nothing" : "always passes (a regex never equals the value), so it checks nothing";
83872
+ return `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? negated : "can never pass: this matcher compares by equality and a regex never equals a value"}.`;
83873
+ });
83874
+ return `${lines.join("\n")}
83875
+ A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For the \`value\` of an input, textarea or select, use await expect(loc).toHaveValue(/re/). For any other DOM property (or element), poll it so the check still retries: await expect.poll(() => loc.evaluate((n) => n.<prop>)).toMatch(/re/). Never read it once with expect(await loc.evaluate(\u2026)), which sees the value before the page updates it. Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
83876
+ }
82912
83877
  function lintTestQuality(rawCode, opts = {}) {
82913
83878
  if (!rawCode || typeof rawCode !== "string")
82914
83879
  return [];
@@ -82916,6 +83881,10 @@ function lintTestQuality(rawCode, opts = {}) {
82916
83881
  return lintTestData(rawCode, { pageContacts: opts.pageContacts });
82917
83882
  if (opts.only === "test-data-blocking")
82918
83883
  return analyzeTestData(rawCode, { pageContacts: opts.pageContacts }).blocking;
83884
+ if (opts.only === "regex-matcher") {
83885
+ const found = analyzeRegexMatchers(rawCode);
83886
+ return found.length > 0 ? [formatRegexMatcherNote(found)] : [];
83887
+ }
82919
83888
  const warnings = [];
82920
83889
  if (rawCode.length > MAX_LINT_INPUT) {
82921
83890
  warnings.push(`The code is ${Math.round(rawCode.length / 1024)} KB; the quality lint checked only the first ${MAX_LINT_INPUT / 1e3} KB. Split it into smaller tests (one behaviour per test).`);
@@ -83190,7 +84159,7 @@ function specItemsFromTestPlans(testPlans) {
83190
84159
  }
83191
84160
  return [...new Set(out)].slice(0, 200);
83192
84161
  }
83193
- var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, STOP, SYNONYMS, AREA_TOKENS;
84162
+ var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, MAX_REGEX_MATCHER_FINDINGS, REGEX_EQUALITY_MATCHERS, STOP, SYNONYMS, AREA_TOKENS;
83194
84163
  var init_test_authoring_quality = __esm({
83195
84164
  "../shared/dist/test-authoring-quality.js"() {
83196
84165
  "use strict";
@@ -83422,6 +84391,8 @@ var init_test_authoring_quality = __esm({
83422
84391
  };
83423
84392
  LIVE_WRITE_NAME = /\b(?:submit|place|book|reserve|confirm|pay|send|checkout|order|register|sign\s?up|create account)\b/i;
83424
84393
  BUNDLED_CHECKS_BEFORE_WRITE = 5;
84394
+ MAX_REGEX_MATCHER_FINDINGS = 10;
84395
+ REGEX_EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual", "toContain", "toHaveJSProperty"]);
83425
84396
  STOP = /* @__PURE__ */ new Set([
83426
84397
  "a",
83427
84398
  "an",
@@ -83660,7 +84631,7 @@ function normalizeBugPath(route) {
83660
84631
  if (hash >= 0 && path[hash + 1] !== "/")
83661
84632
  path = path.slice(0, hash);
83662
84633
  const segments = path.toLowerCase().split("/").map((segment) => /\d/.test(segment) ? ":id" : segment);
83663
- const joined = segments.join("/").replace(/\/+$/, "");
84634
+ const joined = stripTrailingSlashes(segments.join("/"));
83664
84635
  return joined || (path.startsWith("/") ? "/" : "");
83665
84636
  }
83666
84637
  function isGenericConsoleMessage(message) {
@@ -83673,14 +84644,17 @@ function requestSignatures(text) {
83673
84644
  const statuses = [...text.matchAll(HTTP_STATUS)].map((m) => ({ status: m[1], at: m.index ?? 0, end: (m.index ?? 0) + m[0].length }));
83674
84645
  if (statuses.length === 0)
83675
84646
  return [];
83676
- const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => ({
83677
- method: m[1] ?? null,
83678
- path: normalizeBugPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "")),
83679
- // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
83680
- incidental: isIncidentalAssetPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "").split(/[?#]/, 1)[0].toLowerCase()),
83681
- at: m.index ?? 0,
83682
- end: (m.index ?? 0) + m[0].length
83683
- })).filter((p) => p.path.length > 1);
84647
+ const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => {
84648
+ const rawPath = stripTrailingChars(m[2] ?? m[3] ?? "", PATH_TRAILING_PUNCTUATION);
84649
+ return {
84650
+ method: m[1] ?? null,
84651
+ path: normalizeBugPath(rawPath),
84652
+ // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
84653
+ incidental: isIncidentalAssetPath(rawPath.split(/[?#]/, 1)[0].toLowerCase()),
84654
+ at: m.index ?? 0,
84655
+ end: (m.index ?? 0) + m[0].length
84656
+ };
84657
+ }).filter((p) => p.path.length > 1);
83684
84658
  const out = /* @__PURE__ */ new Map();
83685
84659
  for (const { status, at, end } of statuses) {
83686
84660
  const lead = STATUS_TO_PATH.exec(text.slice(end, end + 40));
@@ -83790,10 +84764,12 @@ function dedupeSiteWideSuspectedBugs(drafts) {
83790
84764
  delete plans.suspectedBugs;
83791
84765
  }
83792
84766
  }
83793
- var SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_JS_MESSAGES, GENERIC_JS_PREFIXES, JS_ERROR, MESSAGE_CLAUSE_END, HTTP_STATUS, PATH_IN_TEXT, STATUS_TO_PATH, INCIDENTAL_ASSET;
84767
+ var PATH_TRAILING_PUNCTUATION, SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_JS_MESSAGES, GENERIC_JS_PREFIXES, JS_ERROR, MESSAGE_CLAUSE_END, HTTP_STATUS, PATH_IN_TEXT, STATUS_TO_PATH, INCIDENTAL_ASSET;
83794
84768
  var init_survey_bug_signature = __esm({
83795
84769
  "../shared/dist/survey-bug-signature.js"() {
83796
84770
  "use strict";
84771
+ init_trailing_slashes();
84772
+ PATH_TRAILING_PUNCTUATION = /[.,;:]/;
83797
84773
  SIGNATURE_SOURCE_MAX_CHARS = 800;
83798
84774
  UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
83799
84775
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -85074,9 +86050,9 @@ function lastLocatorLabels(chain) {
85074
86050
  if (t)
85075
86051
  labels.push(t[1]);
85076
86052
  }
85077
- const has = optionText(last.args, HAS_TEXT_OPTION_RE);
85078
- if (has !== null)
85079
- labels.push(has);
86053
+ const has2 = optionText(last.args, HAS_TEXT_OPTION_RE);
86054
+ if (has2 !== null)
86055
+ labels.push(has2);
85080
86056
  }
85081
86057
  return labels.map((l) => l.replace(/^[\s^(?:]+/, "").trim());
85082
86058
  }
@@ -85124,7 +86100,7 @@ function pathIdsIn(text) {
85124
86100
  function findCounterIdPins(code) {
85125
86101
  if (code.length > CLOCK_MAX_CODE_CHARS)
85126
86102
  return [];
85127
- const clean2 = stripLineComments(blankBlockComments3(code));
86103
+ const clean3 = stripLineComments(blankBlockComments3(code));
85128
86104
  const fillValues = [];
85129
86105
  const candidates = [];
85130
86106
  let seenWrite = false;
@@ -85132,24 +86108,24 @@ function findCounterIdPins(code) {
85132
86108
  CALL_RE.lastIndex = 0;
85133
86109
  let m;
85134
86110
  let calls = 0;
85135
- while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean2)) !== null) {
86111
+ while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean3)) !== null) {
85136
86112
  const name = m[1];
85137
86113
  const open = m.index + m[0].length - 1;
85138
86114
  if (VALUE_WRITES.has(name)) {
85139
86115
  seenWrite = true;
85140
- const args2 = argsAt(clean2, open);
86116
+ const args2 = argsAt(clean3, open);
85141
86117
  if (args2 !== null)
85142
86118
  fillValues.push(...staticStrings(args2));
85143
86119
  continue;
85144
86120
  }
85145
86121
  if (createIndex === -1) {
85146
- if (name === "click" && seenWrite && isCreateClick(clean2, m.index))
86122
+ if (name === "click" && seenWrite && isCreateClick(clean3, m.index))
85147
86123
  createIndex = m.index;
85148
86124
  continue;
85149
86125
  }
85150
86126
  if (name === "click")
85151
86127
  continue;
85152
- const args = argsAt(clean2, open);
86128
+ const args = argsAt(clean3, open);
85153
86129
  if (args === null)
85154
86130
  continue;
85155
86131
  const isUrl = URL_SINKS.has(name);
@@ -85168,7 +86144,7 @@ function findCounterIdPins(code) {
85168
86144
  }
85169
86145
  if (createIndex === -1)
85170
86146
  return [];
85171
- const before = clean2.slice(0, createIndex);
86147
+ const before = clean3.slice(0, createIndex);
85172
86148
  let pathIdsBefore = null;
85173
86149
  const findings = [];
85174
86150
  const seen = /* @__PURE__ */ new Set();
@@ -85192,7 +86168,7 @@ function findCounterIdPins(code) {
85192
86168
  continue;
85193
86169
  }
85194
86170
  }
85195
- findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean2, c.index) });
86171
+ findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean3, c.index) });
85196
86172
  }
85197
86173
  return findings;
85198
86174
  }
@@ -85487,7 +86463,7 @@ var init_suspicious_expectation = __esm({
85487
86463
  NOT_STUCK_RE = /\b(?:not|never|no longer|isn't|doesn't|don't|without)\s+(?:\w+\s+){0,2}?(?:stuck|hang|hangs|hung|freeze|freezes|frozen|spin|spins|spinning)\b/i;
85488
86464
  LISTS_STUCK_RE = /\b(?:appears? in (?:the )?[\w ]{0,25}(?:report|list|dashboard|queue)|(?:lists?|monitors?|shows?|reports?) (?:\w+ ){0,2}(?:that|which|whose) (?:are |is )?(?:stuck|hangs?|hung|frozen)|can (?:retry|re-?run) (?:\w+ ){0,2}(?:that (?:are )?)?stuck)\b/i;
85489
86465
  STUCK_VETO_RE = new RegExp(`${NOT_STUCK_RE.source}|${LISTS_STUCK_RE.source}`, "i");
85490
- NOT_SILENT_RE = /\b(?:not|never|\w+n't|instead of|rather than|avoids?|prevents?)\s+(?:\w+\s+){0,1}?(?:fail(?:s|ing|ed)?|ignor\w*)?\s*silent(?:ly)?\b/i;
86466
+ NOT_SILENT_RE = /\b(?:not|never|\w+n't|instead of|rather than|avoids?|prevents?)\s+(?:\w+\s+){0,1}?(?:(?:fail(?:s|ing|ed)?|ignor\w*)\s*)?silent(?:ly)?\b/i;
85491
86467
  PENDING_WORD = "(?:sending|loading|saving|searching|checking(?!\\s+(?:account|balance|out|in)\\b)|syncing|refreshing|fetching|validating|processing|submitting|uploading|posting|publishing|creating|updating|deleting|connecting|verifying|signing in|logging in)";
85492
86468
  ELLIPSIS = "(?:\\.{3}|\u2026)";
85493
86469
  NOT_STAY_RE = /\b(?:not|never|no longer|without|n't|cannot)\s+(?:\w+\s+){0,2}?(?:stay|stays|stayed|remain|remains|remained|leave|leaves|left)\b/i;
@@ -85643,7 +86619,7 @@ function outcomeNamedByAssertion(outcome, code) {
85643
86619
  function namedByStatements(outcome, statements) {
85644
86620
  const text = outcomeText(outcome);
85645
86621
  if (outcome.type === "url") {
85646
- const routes = (text.match(ROUTE_TERM) ?? []).map((r) => r.replace(/[.,;:!?)]+$/, "")).filter((r) => r.length > 1);
86622
+ const routes = (text.match(ROUTE_TERM) ?? []).map((r) => stripTrailingChars(r, ROUTE_TRAILING_PUNCTUATION)).filter((r) => r.length > 1);
85647
86623
  return statements.some((stmt) => {
85648
86624
  if (!/\.(?:waitForURL|toHaveURL)\s{0,20}\(/.test(stmt))
85649
86625
  return false;
@@ -85815,7 +86791,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
85815
86791
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
85816
86792
  };
85817
86793
  }
85818
- var SOURCE_RANK, strengthCheck, STRING_CONST, TYPED_VALUE, TYPED_CONSTANT, MAX_STATEMENTS, MAX_STATEMENT_CHARS, LITERAL_OR_IDENTIFIER, MUTATING_ACTION, FEEDBACK_REGION, ONE_SHOT_MESSAGE_REASON, FEEDBACK_REGION_ALL, dynamicAssertionCheck, OUTCOME_KIND_MATCHER, ROUTE_TERM, QUOTED_LITERAL, GENERIC_LITERAL, MAX_OUTCOME_STATEMENTS, MAX_OUTCOMES, MAX_OUTCOME_TEXT_CHARS, expectedOutcomeCheck, testDataCheck, sharedAccountCheck, clockLiteralCheck, readinessPollCheck, knownBugTestCheck, bugClaimContractCheck, suspiciousExpectationCheck, bugCandidateCheck, conventionOutcomesCheck, pendingHook, locatorFragmentCheck, FILTER_STEP, NEXT_ACTION, REDUCTION_PROOF, MAX_FILTER_LINES, MAX_FILTER_LINE_CHARS, filterPositiveOnlyCheck, PROMOTION_QUALITY_CHECKS, PROMOTION_GATE_MAX_CODE_CHARS, NOT_EVALUATED, SUSPICIOUS_PASS_TAG, NOT_CONTENT_MATCHER, CONTENT_PROOF, NAMED_ELEMENT, NEEDS_REVIEW_TAG, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
86794
+ var SOURCE_RANK, strengthCheck, STRING_CONST, TYPED_VALUE, TYPED_CONSTANT, MAX_STATEMENTS, MAX_STATEMENT_CHARS, LITERAL_OR_IDENTIFIER, MUTATING_ACTION, FEEDBACK_REGION, ONE_SHOT_MESSAGE_REASON, FEEDBACK_REGION_ALL, dynamicAssertionCheck, OUTCOME_KIND_MATCHER, ROUTE_TERM, ROUTE_TRAILING_PUNCTUATION, QUOTED_LITERAL, GENERIC_LITERAL, MAX_OUTCOME_STATEMENTS, MAX_OUTCOMES, MAX_OUTCOME_TEXT_CHARS, expectedOutcomeCheck, testDataCheck, sharedAccountCheck, clockLiteralCheck, readinessPollCheck, knownBugTestCheck, bugClaimContractCheck, suspiciousExpectationCheck, bugCandidateCheck, conventionOutcomesCheck, pendingHook, locatorFragmentCheck, FILTER_STEP, NEXT_ACTION, REDUCTION_PROOF, MAX_FILTER_LINES, MAX_FILTER_LINE_CHARS, filterPositiveOnlyCheck, PROMOTION_QUALITY_CHECKS, PROMOTION_GATE_MAX_CODE_CHARS, NOT_EVALUATED, SUSPICIOUS_PASS_TAG, NOT_CONTENT_MATCHER, CONTENT_PROOF, NAMED_ELEMENT, NEEDS_REVIEW_TAG, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
85819
86795
  var init_promotion_quality_gate = __esm({
85820
86796
  "../shared/dist/promotion-quality-gate.js"() {
85821
86797
  "use strict";
@@ -85830,17 +86806,21 @@ var init_promotion_quality_gate = __esm({
85830
86806
  init_run_classification();
85831
86807
  init_expected_outcome_coverage();
85832
86808
  init_expect_call();
86809
+ init_trailing_slashes();
85833
86810
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
85834
86811
  strengthCheck = {
85835
86812
  id: "assertion-strength",
85836
- run({ code, strengthGate, flagContradictedAdditions }) {
85837
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
86813
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
86814
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
85838
86815
  if (verdict.severity === "route-bug")
85839
86816
  return [{ check: "route-bug" }];
85840
- if (verdict.severity !== "block")
86817
+ const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
86818
+ if (verdict.severity !== "block" && !authDowngrade)
85841
86819
  return [];
85842
86820
  const revealed = strengthGate?.severity === "none" && strengthGate.revealEvidence === "revealed" && strengthGate.source !== "heal";
85843
- return revealed ? [] : [{ check: "strength-block" }];
86821
+ if (revealed)
86822
+ return [];
86823
+ return [{ check: authDowngrade ? "strength-downgrade-auth" : "strength-block" }];
85844
86824
  }
85845
86825
  };
85846
86826
  STRING_CONST = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,60})\s{0,20}=\s{0,20}(?:'([^'\\\n]{0,300})'|"([^"\\\n]{0,300})"|`([^`\\$\n]{0,300})`)\s{0,20}[;\n]/g;
@@ -85849,7 +86829,7 @@ var init_promotion_quality_gate = __esm({
85849
86829
  MAX_STATEMENTS = 500;
85850
86830
  MAX_STATEMENT_CHARS = 400;
85851
86831
  LITERAL_OR_IDENTIFIER = /'(?:[^'\\\n]|\\.){0,500}'|"(?:[^"\\\n]|\\.){0,500}"|`(?:[^`\\]|\\.){0,2000}`|(?<![\w$.])[A-Za-z_$][\w$]{0,60}/g;
85852
- MUTATING_ACTION = /\.(?:click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s{0,20}\(|\brequest\s{0,20}\.\s{0,20}(?:post|put|patch|delete|fetch)\s{0,20}\(/;
86832
+ MUTATING_ACTION = /\.(?:click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s{0,20}\(|\brequest\s{0,20}\.\s{0,20}(?:post|put|patch|delete|fetch)\s{0,20}\(/;
85853
86833
  FEEDBACK_REGION = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/i;
85854
86834
  ONE_SHOT_MESSAGE_REASON = "one-shot success/error banner text";
85855
86835
  FEEDBACK_REGION_ALL = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/gi;
@@ -85908,6 +86888,7 @@ var init_promotion_quality_gate = __esm({
85908
86888
  "element-hidden": /\.(?:toBeHidden|not\s{0,20}\.\s{0,20}toBeVisible|not\s{0,20}\.\s{0,20}toBeAttached|not\s{0,20}\.\s{0,20}toContainText)\s{0,20}\(|\.toHaveCount\s{0,20}\(\s{0,20}0\s{0,20}\)/
85909
86889
  };
85910
86890
  ROUTE_TERM = /\/[A-Za-z0-9_./?&=#-]{1,200}/g;
86891
+ ROUTE_TRAILING_PUNCTUATION = /[.,;:!?)]/;
85911
86892
  QUOTED_LITERAL = /'([^'\\\n]{3,200})'|"([^"\\\n]{3,200})"|`([^`\\$\n]{3,200})`/g;
85912
86893
  GENERIC_LITERAL = /^(?:true|false|button|link|heading|dialog|alertdialog|article|main|region|status|alert|textbox|combobox|checkbox|radio|tab|tabpanel|option|list|listitem|row|cell|img|navigation|banner|complementary|contentinfo|form|search|menu|menuitem|visible|hidden|attached|detached|domcontentloaded|load|networkidle)$/i;
85913
86894
  MAX_OUTCOME_STATEMENTS = 300;
@@ -86027,8 +87008,8 @@ ${goalText}`);
86027
87008
  return count3 > 0 ? [{ check: "locator-fragment", detail: String(count3), soft: true }] : [];
86028
87009
  }
86029
87010
  };
86030
- FILTER_STEP = /\.(?:getByLabel|getByPlaceholder|getByRole|getByTitle)\s*\([^)\n]{0,200}?\b(?:filter|sort|search|status|delete|remove|dismiss|archive|clear)\b[^)\n]{0,200}\)[^\n]{0,200}\.(?:selectOption|fill|type|pressSequentially|click|check|uncheck|press)\s*\(/i;
86031
- NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s*\(/;
87011
+ FILTER_STEP = /\.(?:getByLabel|getByPlaceholder|getByRole|getByTitle)\s*\([^)\n]{0,200}?\b(?:filter|sort|search|status|delete|remove|dismiss|archive|clear)\b[^)\n]{0,200}\)[^\n]{0,200}\.(?:selectOption|fill|type|pressSequentially|click|check|uncheck|setChecked|press)\s*\(/i;
87012
+ NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s*\(/;
86032
87013
  REDUCTION_PROOF = /\.toHaveCount\s*\(|\.not\s*\.\s*(?:toBeVisible|toBeAttached|toContainText|toHaveText)\s*\(|\.toBeHidden\s*\(|\bShowing\b|\bof \d+\b|\b\d+ (?:results?|rows?|items?|candidates?|entries|matches|records)\b|\bNo (?:[\w'-]+ ){0,3}(?:match|matches|results?|found|left|remain|yet)\b|\bNothing (?:to show|found|matches)\b|\bis empty\b|\btoHaveURL\b/i;
86033
87014
  MAX_FILTER_LINES = 2e3;
86034
87015
  MAX_FILTER_LINE_CHARS = 600;
@@ -86143,7 +87124,7 @@ ${goalText}`);
86143
87124
  });
86144
87125
 
86145
87126
  // ../shared/dist/regex-alternation.js
86146
- function closingParens(source) {
87127
+ function closingParens2(source) {
86147
87128
  const close = new Int32Array(source.length).fill(-1);
86148
87129
  const open = [];
86149
87130
  let inClass = false;
@@ -86168,7 +87149,7 @@ function closingParens(source) {
86168
87149
  return close;
86169
87150
  }
86170
87151
  function alternatesAtTop(source) {
86171
- const close = closingParens(source);
87152
+ const close = closingParens2(source);
86172
87153
  let start = source.startsWith("^") ? 1 : 0;
86173
87154
  let end = source.endsWith("$") ? source.length - 1 : source.length;
86174
87155
  while (start < end && source[start] === "(" && close[start] === end - 1) {
@@ -86217,6 +87198,124 @@ var init_page_evidence = __esm({
86217
87198
  }
86218
87199
  });
86219
87200
 
87201
+ // ../shared/dist/stated-limits.js
87202
+ function validDate(v) {
87203
+ const [y, m, d] = v.split("-").map(Number);
87204
+ const t = new Date(Date.UTC(y, m - 1, d));
87205
+ return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
87206
+ }
87207
+ function isoFor(type, v) {
87208
+ const re = DATE_RE[type];
87209
+ if (!re || !re.test(v))
87210
+ return false;
87211
+ if (type === "date")
87212
+ return validDate(v);
87213
+ if (type === "datetime-local")
87214
+ return validDate(v.slice(0, 10)) && Number(v.slice(11, 13)) < 24 && Number(v.slice(14, 16)) < 60;
87215
+ if (type === "time")
87216
+ return Number(v.slice(0, 2)) < 24 && Number(v.slice(3, 5)) < 60;
87217
+ if (type === "month")
87218
+ return Number(v.slice(5, 7)) >= 1 && Number(v.slice(5, 7)) <= 12;
87219
+ return Number(v.slice(6, 8)) >= 1 && Number(v.slice(6, 8)) <= 53;
87220
+ }
87221
+ function clean2(raw) {
87222
+ return typeof raw === "string" ? raw.trim() : "";
87223
+ }
87224
+ function statedLimitsFromAttrs(attrs, inputType) {
87225
+ const type = (inputType ?? "").trim().toLowerCase();
87226
+ const limits = [];
87227
+ const add = (k, v) => {
87228
+ limits.push({ k, v });
87229
+ };
87230
+ if (NUMBER_TYPES.has(type)) {
87231
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
87232
+ const v = clean2(attrs[attr]);
87233
+ if (NUMBER_RE.test(v))
87234
+ add(k, v);
87235
+ }
87236
+ const step = clean2(attrs.step);
87237
+ if (NUMBER_RE.test(step) && Number(step) > 0)
87238
+ add("step", step);
87239
+ } else if (type in DATE_RE) {
87240
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
87241
+ const v = clean2(attrs[attr]);
87242
+ if (isoFor(type, v))
87243
+ add(k, v);
87244
+ }
87245
+ } else if (TEXT_TYPES.has(type)) {
87246
+ for (const [attr, k] of [["minlength", "minLength"], ["maxlength", "maxLength"]]) {
87247
+ const v = clean2(attrs[attr]);
87248
+ if (LENGTH_RE.test(v))
87249
+ add(k, String(Number(v)));
87250
+ }
87251
+ const pattern = cleanPattern(clean2(attrs.pattern).slice(0, STATED_PATTERN_MAX));
87252
+ if (pattern)
87253
+ add("pattern", pattern);
87254
+ }
87255
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
87256
+ }
87257
+ function cleanPattern(text) {
87258
+ return text.replace(/[\u0000-\u001f\u007f]/g, "").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "");
87259
+ }
87260
+ function readStatedLimits(stored) {
87261
+ if (!stored || typeof stored !== "object" || Array.isArray(stored))
87262
+ return [];
87263
+ const rec = stored;
87264
+ if (rec.v !== STATED_LIMITS_VERSION || !Array.isArray(rec.limits))
87265
+ return [];
87266
+ const out = [];
87267
+ const seen = /* @__PURE__ */ new Set();
87268
+ for (const item of rec.limits.slice(0, MAX_LIMITS * 2)) {
87269
+ if (!item || typeof item !== "object")
87270
+ continue;
87271
+ const { k, v } = item;
87272
+ if (typeof k !== "string" || typeof v !== "string" || !KINDS.includes(k) || seen.has(k))
87273
+ continue;
87274
+ if (v.length === 0 || v.length > STATED_PATTERN_MAX)
87275
+ continue;
87276
+ const text = k === "pattern" ? cleanPattern(v) : v;
87277
+ if (text === "")
87278
+ continue;
87279
+ if (k === "minLength" || k === "maxLength") {
87280
+ if (!LENGTH_RE.test(v))
87281
+ continue;
87282
+ } else if (k === "step") {
87283
+ if (!NUMBER_RE.test(v))
87284
+ continue;
87285
+ } else if (k === "min" || k === "max") {
87286
+ if (!NUMBER_RE.test(v) && !Object.values(DATE_RE).some((re) => re.test(v)))
87287
+ continue;
87288
+ }
87289
+ seen.add(k);
87290
+ out.push({ k, v: text });
87291
+ }
87292
+ return out;
87293
+ }
87294
+ function statedLimitsRecord(limits) {
87295
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
87296
+ }
87297
+ var STATED_LIMITS_VERSION, STATED_PATTERN_MAX, KINDS, MAX_LIMITS, TEXT_TYPES, NUMBER_TYPES, DATE_RE, NUMBER_RE, LENGTH_RE;
87298
+ var init_stated_limits = __esm({
87299
+ "../shared/dist/stated-limits.js"() {
87300
+ "use strict";
87301
+ STATED_LIMITS_VERSION = 1;
87302
+ STATED_PATTERN_MAX = 200;
87303
+ KINDS = ["min", "max", "minLength", "maxLength", "step", "pattern"];
87304
+ MAX_LIMITS = KINDS.length;
87305
+ TEXT_TYPES = /* @__PURE__ */ new Set(["text", "search", "email", "url", "tel", "password", "textarea", ""]);
87306
+ NUMBER_TYPES = /* @__PURE__ */ new Set(["number", "range"]);
87307
+ DATE_RE = {
87308
+ date: /^\d{4}-\d{2}-\d{2}$/,
87309
+ "datetime-local": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?$/,
87310
+ time: /^\d{2}:\d{2}(?::\d{2})?$/,
87311
+ month: /^\d{4}-\d{2}$/,
87312
+ week: /^\d{4}-W\d{2}$/
87313
+ };
87314
+ NUMBER_RE = /^-?\d{1,15}(?:\.\d{1,10})?$/;
87315
+ LENGTH_RE = /^\d{1,7}$/;
87316
+ }
87317
+ });
87318
+
86220
87319
  // ../shared/dist/index.js
86221
87320
  var dist_exports = {};
86222
87321
  __export(dist_exports, {
@@ -86224,9 +87323,11 @@ __export(dist_exports, {
86224
87323
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
86225
87324
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
86226
87325
  ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
87326
+ ADMIN_FORCE_CANCELLED_RUN_ERROR: () => ADMIN_FORCE_CANCELLED_RUN_ERROR,
86227
87327
  ADMIN_VIEW_DEFAULT_RETURN_TO: () => ADMIN_VIEW_DEFAULT_RETURN_TO,
86228
87328
  AGENT_AI_PROVIDER_PREFIXES: () => AGENT_AI_PROVIDER_PREFIXES,
86229
87329
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
87330
+ ALLOWED_PROVIDER_CHAIN_HOPS: () => ALLOWED_PROVIDER_CHAIN_HOPS,
86230
87331
  API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
86231
87332
  API_MODEL_CATALOG: () => API_MODEL_CATALOG,
86232
87333
  API_MODEL_FIELDS: () => API_MODEL_FIELDS,
@@ -86247,6 +87348,7 @@ __export(dist_exports, {
86247
87348
  AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
86248
87349
  AUTH_PATH_SIGNATURE: () => AUTH_PATH_SIGNATURE,
86249
87350
  AUTH_PROVIDER_HOST_SIGNATURES: () => AUTH_PROVIDER_HOST_SIGNATURES,
87351
+ AUTH_SESSION_GENERATION_RE: () => AUTH_SESSION_GENERATION_RE,
86250
87352
  AUTH_STATE_EXPIRY_SKEW_MS: () => AUTH_STATE_EXPIRY_SKEW_MS,
86251
87353
  AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
86252
87354
  AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
@@ -86265,12 +87367,14 @@ __export(dist_exports, {
86265
87367
  BUG_TEST_BROKEN_REASON: () => BUG_TEST_BROKEN_REASON,
86266
87368
  BUG_TEST_BROKEN_TAG: () => BUG_TEST_BROKEN_TAG,
86267
87369
  CACHE_PRICE_PER_M: () => CACHE_PRICE_PER_M,
87370
+ CANCELLED_REASON_PREFIXES: () => CANCELLED_REASON_PREFIXES,
86268
87371
  CANVAS_UNSUPPORTED_BODY: () => CANVAS_UNSUPPORTED_BODY,
86269
87372
  CANVAS_UNSUPPORTED_CODE: () => CANVAS_UNSUPPORTED_CODE,
86270
87373
  CANVAS_UNSUPPORTED_NOT_CHARGED: () => CANVAS_UNSUPPORTED_NOT_CHARGED,
86271
87374
  CANVAS_UNSUPPORTED_OVERRIDE_HINT: () => CANVAS_UNSUPPORTED_OVERRIDE_HINT,
86272
87375
  CANVAS_UNSUPPORTED_TITLE: () => CANVAS_UNSUPPORTED_TITLE,
86273
87376
  CAPTURE_JSON_STRUCTURE_LIMITS: () => CAPTURE_JSON_STRUCTURE_LIMITS,
87377
+ CHAIN_CANCELLED_RUN_ERROR: () => CHAIN_CANCELLED_RUN_ERROR,
86274
87378
  CHAT_FAILOVER_KEY: () => CHAT_FAILOVER_KEY,
86275
87379
  CLAUDE_CLI_DEFAULT_EFFORT: () => CLAUDE_CLI_DEFAULT_EFFORT,
86276
87380
  CLAUDE_CLI_DEFAULT_MODEL: () => CLAUDE_CLI_DEFAULT_MODEL,
@@ -86307,6 +87411,7 @@ __export(dist_exports, {
86307
87411
  DEFAULT_GEMINI_SCREEN_MODEL: () => DEFAULT_GEMINI_SCREEN_MODEL,
86308
87412
  DEFAULT_JSON_STRUCTURE_LIMITS: () => DEFAULT_JSON_STRUCTURE_LIMITS,
86309
87413
  DEFAULT_PLATFORM_DB_POOL_SIZE: () => DEFAULT_PLATFORM_DB_POOL_SIZE,
87414
+ DEFAULT_PROVIDER_ACCOUNT_ID: () => DEFAULT_PROVIDER_ACCOUNT_ID,
86310
87415
  DEFAULT_SHARED_DB_POOL_SIZE: () => DEFAULT_SHARED_DB_POOL_SIZE,
86311
87416
  DEFAULT_STT_MODEL: () => DEFAULT_STT_MODEL,
86312
87417
  DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
@@ -86323,6 +87428,7 @@ __export(dist_exports, {
86323
87428
  ENVIRONMENTAL_ERROR_STORED_PREFIXES: () => ENVIRONMENTAL_ERROR_STORED_PREFIXES,
86324
87429
  EVIDENCE_NOTE_PREFIXES: () => EVIDENCE_NOTE_PREFIXES,
86325
87430
  EXPECT_CALL_RE: () => EXPECT_CALL_RE,
87431
+ FEATURE_ROW_RUN_WINDOW: () => FEATURE_ROW_RUN_WINDOW,
86326
87432
  FLOW_PHASE_LABELS: () => FLOW_PHASE_LABELS,
86327
87433
  FLOW_PHASE_TYPES: () => FLOW_PHASE_TYPES,
86328
87434
  FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
@@ -86379,6 +87485,8 @@ __export(dist_exports, {
86379
87485
  LIVE_FRAME_STALE_MS: () => LIVE_FRAME_STALE_MS,
86380
87486
  LIVE_FRAME_UNFOCUSED_INTERVAL_MS: () => LIVE_FRAME_UNFOCUSED_INTERVAL_MS,
86381
87487
  LIVE_THUMBNAIL_MAX_BASE64_BYTES: () => LIVE_THUMBNAIL_MAX_BASE64_BYTES,
87488
+ MAX_BALANCED_SCAN: () => MAX_BALANCED_SCAN,
87489
+ MAX_CALLS: () => MAX_CALLS,
86382
87490
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
86383
87491
  MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
86384
87492
  MAX_EMAIL_ADDRESS_LENGTH: () => MAX_EMAIL_ADDRESS_LENGTH,
@@ -86388,6 +87496,8 @@ __export(dist_exports, {
86388
87496
  MAX_ORG_NAME_LENGTH: () => MAX_ORG_NAME_LENGTH,
86389
87497
  MAX_PASSWORD_BYTES: () => MAX_PASSWORD_BYTES,
86390
87498
  MAX_PLAYWRIGHT_CODE_LENGTH: () => MAX_PLAYWRIGHT_CODE_LENGTH,
87499
+ MAX_PROVIDER_ACCOUNTS: () => MAX_PROVIDER_ACCOUNTS,
87500
+ MAX_PROVIDER_ACCOUNT_MODELS: () => MAX_PROVIDER_ACCOUNT_MODELS,
86391
87501
  MAX_SURVEY_ENTRY_URLS: () => MAX_SURVEY_ENTRY_URLS,
86392
87502
  MAX_SURVEY_ENTRY_URL_LENGTH: () => MAX_SURVEY_ENTRY_URL_LENGTH,
86393
87503
  MCP_ARMY_LIMITS: () => MCP_ARMY_LIMITS,
@@ -86423,6 +87533,7 @@ __export(dist_exports, {
86423
87533
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
86424
87534
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
86425
87535
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
87536
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS: () => OPENCODE_GO_DEEPSEEK_GLM_MODELS,
86426
87537
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
86427
87538
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
86428
87539
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -86442,10 +87553,12 @@ __export(dist_exports, {
86442
87553
  PLAN_FEATURES: () => PLAN_FEATURES,
86443
87554
  PLAN_FEATURE_LOCKED_CODE: () => PLAN_FEATURE_LOCKED_CODE,
86444
87555
  PLAN_ORDER: () => PLAN_ORDER,
87556
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR: () => PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
86445
87557
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
86446
87558
  PLATFORM_DEFAULT_PROVIDER: () => PLATFORM_DEFAULT_PROVIDER,
86447
87559
  PLATFORM_PLAN_BILLING: () => PLATFORM_PLAN_BILLING,
86448
87560
  PLATFORM_SERVER_APPLICATION_NAME: () => PLATFORM_SERVER_APPLICATION_NAME,
87561
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR: () => PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
86449
87562
  POST_SPECIAL_TRIAL_DAYS: () => POST_SPECIAL_TRIAL_DAYS,
86450
87563
  PRESET_DIRECT_FALLBACK: () => PRESET_DIRECT_FALLBACK,
86451
87564
  PRODUCTION_READ_ONLY_GUARD_TEMPLATE: () => PRODUCTION_READ_ONLY_GUARD_TEMPLATE,
@@ -86457,17 +87570,25 @@ __export(dist_exports, {
86457
87570
  PROJECT_OVERRIDE_FIELDS: () => PROJECT_OVERRIDE_FIELDS,
86458
87571
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
86459
87572
  PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
87573
+ PROVIDER_ACCOUNT_ID_PATTERN: () => PROVIDER_ACCOUNT_ID_PATTERN,
87574
+ PROVIDER_ACCOUNT_MODEL_PATTERN: () => PROVIDER_ACCOUNT_MODEL_PATTERN,
86460
87575
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
87576
+ PROVIDER_CHAINS_SETTING_MAX_CHARS: () => PROVIDER_CHAINS_SETTING_MAX_CHARS,
87577
+ PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
87578
+ PROVIDER_CHAIN_MAX_ENTRIES: () => PROVIDER_CHAIN_MAX_ENTRIES,
87579
+ PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
86461
87580
  PUBLIC_PLANS: () => PUBLIC_PLANS,
86462
87581
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
86463
87582
  READINESS_POLL_MAX_CODE_CHARS: () => READINESS_POLL_MAX_CODE_CHARS,
86464
87583
  READ_ONLY_TAGS: () => READ_ONLY_TAGS,
86465
87584
  RECHECKED_NOTES: () => RECHECKED_NOTES,
86466
87585
  RECORDING_VALUE_PLACEHOLDERS: () => RECORDING_VALUE_PLACEHOLDERS,
87586
+ RENDER_IDENTITY_MAX_LENGTH: () => RENDER_IDENTITY_MAX_LENGTH,
86467
87587
  REPOSITORY_CLIS: () => REPOSITORY_CLIS,
86468
87588
  RESERVED_PROJECT_KEYS: () => RESERVED_PROJECT_KEYS,
86469
87589
  RESULT_STRUCTURE_TOO_COSTLY_CODE: () => RESULT_STRUCTURE_TOO_COSTLY_CODE,
86470
87590
  RETRIED_ATTEMPTS_KEY: () => RETRIED_ATTEMPTS_KEY,
87591
+ RETRY_HELD_RUN_ERROR: () => RETRY_HELD_RUN_ERROR,
86471
87592
  RUNNER_INTERRUPT_ERROR_PREFIX: () => RUNNER_INTERRUPT_ERROR_PREFIX,
86472
87593
  RUN_INTERRUPTED_ERROR_PREFIXES: () => RUN_INTERRUPTED_ERROR_PREFIXES,
86473
87594
  RUN_INTERRUPTION_NOT_RETRIED_MARKERS: () => RUN_INTERRUPTION_NOT_RETRIED_MARKERS,
@@ -86480,6 +87601,8 @@ __export(dist_exports, {
86480
87601
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
86481
87602
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
86482
87603
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
87604
+ STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
87605
+ STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
86483
87606
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
86484
87607
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
86485
87608
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
@@ -86496,6 +87619,7 @@ __export(dist_exports, {
86496
87619
  SURVEY_NOTES_RENDER_MAX_CHARS: () => SURVEY_NOTES_RENDER_MAX_CHARS,
86497
87620
  SURVEY_NOTE_KEYS: () => SURVEY_NOTE_KEYS,
86498
87621
  SUSPICIOUS_PASS_TAG: () => SUSPICIOUS_PASS_TAG,
87622
+ TEST_DID_NOT_RUN_ERROR: () => TEST_DID_NOT_RUN_ERROR,
86499
87623
  TEST_QUALITY_CONVENTIONS: () => TEST_QUALITY_CONVENTIONS,
86500
87624
  TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
86501
87625
  TEST_VARIABLE_SECRET_KEY_PATTERN: () => TEST_VARIABLE_SECRET_KEY_PATTERN,
@@ -86508,23 +87632,33 @@ __export(dist_exports, {
86508
87632
  UI_VARIANTS: () => UI_VARIANTS,
86509
87633
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
86510
87634
  USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
87635
+ VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
87636
+ VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
87637
+ VISUAL_MASK_SELECTOR_MAX_LENGTH: () => VISUAL_MASK_SELECTOR_MAX_LENGTH,
87638
+ VISUAL_V1_IDENTITY: () => VISUAL_V1_IDENTITY,
86511
87639
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
86512
87640
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
86513
87641
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
87642
+ ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
87643
+ ZAI_PREFIX: () => ZAI_PREFIX,
87644
+ accountServesModel: () => accountServesModel,
86514
87645
  accumulateStatements: () => accumulateStatements,
86515
87646
  adoptBrokerFailover: () => adoptBrokerFailover,
86516
87647
  aiCostToCreditCents: () => aiCostToCreditCents,
87648
+ allowedProviderChainHosts: () => allowedProviderChainHosts,
86517
87649
  alternatesAtTop: () => alternatesAtTop,
86518
87650
  analyzeDerivedValues: () => analyzeDerivedValues,
86519
87651
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
86520
87652
  analyzePersistenceReread: () => analyzePersistenceReread,
86521
87653
  analyzeReadinessPolling: () => analyzeReadinessPolling,
87654
+ analyzeRegexMatchers: () => analyzeRegexMatchers,
86522
87655
  analyzeSharedAccountUse: () => analyzeSharedAccountUse,
86523
87656
  analyzeStaleAfterSubmit: () => analyzeStaleAfterSubmit,
86524
87657
  analyzeTestData: () => analyzeTestData,
86525
87658
  analyzeUnreachedUrlWaits: () => analyzeUnreachedUrlWaits,
86526
87659
  analyzeUrlWaits: () => analyzeUrlWaits,
86527
87660
  analyzeWriteResponse: () => analyzeWriteResponse,
87661
+ androidResourceOwners: () => androidResourceOwners,
86528
87662
  apiModelCardsFromConfig: () => apiModelCardsFromConfig,
86529
87663
  apiModelsForProvider: () => apiModelsForProvider,
86530
87664
  apiProviderModelLabel: () => apiProviderModelLabel,
@@ -86535,6 +87669,7 @@ __export(dist_exports, {
86535
87669
  applyLaunchSpecialPlan: () => applyLaunchSpecialPlan,
86536
87670
  asPlainExpect: () => asPlainExpect,
86537
87671
  assertionOnlyCode: () => assertionOnlyCode,
87672
+ assertionStrengthHoistedReadsOn: () => assertionStrengthHoistedReadsOn,
86538
87673
  assertionsReadVisibleContent: () => assertionsReadVisibleContent,
86539
87674
  assessCodeOutcomeCoverage: () => assessCodeOutcomeCoverage,
86540
87675
  assessTitleCoverage: () => assessTitleCoverage,
@@ -86618,6 +87753,7 @@ __export(dist_exports, {
86618
87753
  deepgramSpeak: () => deepgramSpeak,
86619
87754
  deepgramTranscribe: () => deepgramTranscribe,
86620
87755
  defaultConfirmModel: () => defaultConfirmModel,
87756
+ defaultProviderChainEntries: () => defaultProviderChainEntries,
86621
87757
  defaultScreenModel: () => defaultScreenModel,
86622
87758
  defaultVoiceForTtsModel: () => defaultVoiceForTtsModel,
86623
87759
  deriveAuthPlan: () => deriveAuthPlan,
@@ -86660,6 +87796,7 @@ __export(dist_exports, {
86660
87796
  extractOutcomeSearchTerms: () => extractOutcomeSearchTerms,
86661
87797
  featureForAiCallType: () => featureForAiCallType,
86662
87798
  featureForMcpTool: () => featureForMcpTool,
87799
+ featureRowRunStatus: () => featureRowRunStatus,
86663
87800
  findBracedClause: () => findBracedClause,
86664
87801
  findCounterIdPins: () => findCounterIdPins,
86665
87802
  findCredentialTextLeftover: () => findCredentialTextLeftover,
@@ -86682,6 +87819,7 @@ __export(dist_exports, {
86682
87819
  formatPersistenceRereadAdvisory: () => formatPersistenceRereadAdvisory,
86683
87820
  formatPoolBudgetLine: () => formatPoolBudgetLine,
86684
87821
  formatReadinessPollAdvisory: () => formatReadinessPollAdvisory,
87822
+ formatRegexMatcherNote: () => formatRegexMatcherNote,
86685
87823
  formatStaleAfterSubmitAdvisory: () => formatStaleAfterSubmitAdvisory,
86686
87824
  formatSuspiciousExpectation: () => formatSuspiciousExpectation,
86687
87825
  formatUrlWaitAdvisory: () => formatUrlWaitAdvisory,
@@ -86707,6 +87845,7 @@ __export(dist_exports, {
86707
87845
  getPlanChangeType: () => getPlanChangeType,
86708
87846
  getPlanDefinition: () => getPlanDefinition,
86709
87847
  getPlanLabel: () => getPlanLabel,
87848
+ getProviderChainOverrides: () => getProviderChainOverrides,
86710
87849
  getTrialDaysRemaining: () => getTrialDaysRemaining,
86711
87850
  googleBrokerNavigationUrl: () => googleBrokerNavigationUrl,
86712
87851
  googleBrokerUnavailableLine: () => googleBrokerUnavailableLine,
@@ -86723,9 +87862,11 @@ __export(dist_exports, {
86723
87862
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
86724
87863
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
86725
87864
  hasPlatformLocator: () => hasPlatformLocator,
87865
+ hasProviderChainLeadsByDefault: () => hasProviderChainLeadsByDefault,
86726
87866
  hasReadOnlyMarker: () => hasReadOnlyMarker,
86727
87867
  hasSurveyNotes: () => hasSurveyNotes,
86728
87868
  hasTestFailContract: () => hasTestFailContract,
87869
+ hasZaiPrefix: () => hasZaiPrefix,
86729
87870
  healMobileTestSteps: () => healMobileTestSteps,
86730
87871
  healthRunWhere: () => healthRunWhere,
86731
87872
  heldCodeRecord: () => heldCodeRecord,
@@ -86747,8 +87888,11 @@ __export(dist_exports, {
86747
87888
  isAuthTokenName: () => isAuthTokenName,
86748
87889
  isBlockedEnvVar: () => isBlockedEnvVar,
86749
87890
  isBrowserCliEngine: () => isBrowserCliEngine,
87891
+ isBrowserVersion: () => isBrowserVersion,
86750
87892
  isBugTestBrokenError: () => isBugTestBrokenError,
87893
+ isCancelledReason: () => isCancelledReason,
86751
87894
  isCanvasUnsupportedMessage: () => isCanvasUnsupportedMessage,
87895
+ isChainablePrimary: () => isChainablePrimary,
86752
87896
  isCliEngineModelId: () => isCliEngineModelId,
86753
87897
  isCliEngineSpec: () => isCliEngineSpec,
86754
87898
  isCodeFactNote: () => isCodeFactNote,
@@ -86772,6 +87916,7 @@ __export(dist_exports, {
86772
87916
  isIdentityMobileXpath: () => isIdentityMobileXpath,
86773
87917
  isIdentityProviderSurface: () => isIdentityProviderSurface,
86774
87918
  isIncidentalAssetPath: () => isIncidentalAssetPath,
87919
+ isIntentionalNegativeAuthTest: () => isIntentionalNegativeAuthTest,
86775
87920
  isIpOrLocalhost: () => isIpOrLocalhost,
86776
87921
  isKnownPageType: () => isKnownPageType,
86777
87922
  isLaunchSpecialActive: () => isLaunchSpecialActive,
@@ -86799,6 +87944,7 @@ __export(dist_exports, {
86799
87944
  isRunInterruptedError: () => isRunInterruptedError,
86800
87945
  isRunnableStep: () => isRunnableStep,
86801
87946
  isSafeDeepLink: () => isSafeDeepLink,
87947
+ isSafeMaskSelector: () => isSafeMaskSelector,
86802
87948
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
86803
87949
  isSelectableApiModel: () => isSelectableApiModel,
86804
87950
  isSelfServePlan: () => isSelfServePlan,
@@ -86829,6 +87975,7 @@ __export(dist_exports, {
86829
87975
  isValidSttProvider: () => isValidSttProvider,
86830
87976
  isValidTtsModel: () => isValidTtsModel,
86831
87977
  isValidTtsVoiceId: () => isValidTtsVoiceId,
87978
+ isZaiModelId: () => isZaiModelId,
86832
87979
  jsEscapedForms: () => jsEscapedForms,
86833
87980
  jsonStructureRefusalOf: () => jsonStructureRefusalOf,
86834
87981
  jsonStructureVerify: () => jsonStructureVerify,
@@ -86861,6 +88008,7 @@ __export(dist_exports, {
86861
88008
  normalizeFinishReason: () => normalizeFinishReason,
86862
88009
  normalizeNoteText: () => normalizeNoteText,
86863
88010
  normalizePageType: () => normalizePageType,
88011
+ normalizeProviderAccountModels: () => normalizeProviderAccountModels,
86864
88012
  normalizeTtsModel: () => normalizeTtsModel,
86865
88013
  normalizeTtsVoiceId: () => normalizeTtsVoiceId,
86866
88014
  noteChatFailover: () => noteChatFailover,
@@ -86877,6 +88025,7 @@ __export(dist_exports, {
86877
88025
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
86878
88026
  parseAppTimezone: () => parseAppTimezone,
86879
88027
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
88028
+ parseAuthSessionStamp: () => parseAuthSessionStamp,
86880
88029
  parseBoolFlag: () => parseBoolFlag,
86881
88030
  parseDbPoolSize: () => parseDbPoolSize,
86882
88031
  parseDeepgramListenResponse: () => parseDeepgramListenResponse,
@@ -86884,13 +88033,18 @@ __export(dist_exports, {
86884
88033
  parseGeminiModelsPayload: () => parseGeminiModelsPayload,
86885
88034
  parseGoogleAuthorizeUrl: () => parseGoogleAuthorizeUrl,
86886
88035
  parseGoogleGisRequestUrl: () => parseGoogleGisRequestUrl,
88036
+ parseLastActionProofMode: () => parseLastActionProofMode,
86887
88037
  parseLimitParam: () => parseLimitParam,
86888
88038
  parseOffsetParam: () => parseOffsetParam,
86889
88039
  parsePageParam: () => parsePageParam,
86890
88040
  parsePgIntSetting: () => parsePgIntSetting,
86891
88041
  parseProjectEngineOverrides: () => parseProjectEngineOverrides,
86892
88042
  parsePromotionGateMode: () => parsePromotionGateMode,
88043
+ parseProviderAccountLists: () => parseProviderAccountLists,
88044
+ parseProviderChainOverrides: () => parseProviderChainOverrides,
86893
88045
  parseRecheckClearMode: () => parseRecheckClearMode,
88046
+ parseRenderIdentity: () => parseRenderIdentity,
88047
+ parseStoredProviderChainOverrides: () => parseStoredProviderChainOverrides,
86894
88048
  parseSurveyEntryUrls: () => parseSurveyEntryUrls,
86895
88049
  parseSuspiciousPassMode: () => parseSuspiciousPassMode,
86896
88050
  parseTaskModelUpdate: () => parseTaskModelUpdate,
@@ -86920,13 +88074,20 @@ __export(dist_exports, {
86920
88074
  profileKeyRoute: () => profileKeyRoute,
86921
88075
  projectAppHosts: () => projectAppHosts,
86922
88076
  providerChainFor: () => providerChainFor,
88077
+ providerChainHostOf: () => providerChainHostOf,
88078
+ providerChainHostProvider: () => providerChainHostProvider,
88079
+ providerChainLeadsFor: () => providerChainLeadsFor,
88080
+ providerChainModelKey: () => providerChainModelKey,
86923
88081
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
88082
+ providerHostPlan: () => providerHostPlan,
88083
+ readBalanced: () => readBalanced,
86924
88084
  readChatFailover: () => readChatFailover,
86925
88085
  readChatReasoningTokens: () => readChatReasoningTokens,
86926
88086
  readChatUsage: () => readChatUsage,
86927
88087
  readEnabledEngines: () => readEnabledEngines,
86928
88088
  readProjectEngineOverrides: () => readProjectEngineOverrides,
86929
88089
  readRetriedAttempts: () => readRetriedAttempts,
88090
+ readStatedLimits: () => readStatedLimits,
86930
88091
  readStaticPageCanvasSignals: () => readStaticPageCanvasSignals,
86931
88092
  readSurveyEntryUrls: () => readSurveyEntryUrls,
86932
88093
  readSurveyNotes: () => readSurveyNotes,
@@ -86939,6 +88100,7 @@ __export(dist_exports, {
86939
88100
  regexLiteralEnd: () => regexLiteralEnd2,
86940
88101
  regexOpensAfter: () => regexOpensAfter,
86941
88102
  registrableDomain: () => registrableDomain,
88103
+ renderIdentity: () => renderIdentity,
86942
88104
  renderSurveyNotesForPrompt: () => renderSurveyNotesForPrompt,
86943
88105
  repairPageType: () => repairPageType,
86944
88106
  replaceBlockComments: () => replaceBlockComments,
@@ -86986,11 +88148,14 @@ __export(dist_exports, {
86986
88148
  selectorCandidatesForStep: () => selectorCandidatesForStep,
86987
88149
  selectorForValue: () => selectorForValue,
86988
88150
  setAgenticCliRuntime: () => setAgenticCliRuntime,
88151
+ setProviderChainOverrides: () => setProviderChainOverrides,
86989
88152
  signupPlanFor: () => signupPlanFor,
86990
88153
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
86991
88154
  siteOf: () => siteOf,
86992
88155
  specItemsFromTestPlans: () => specItemsFromTestPlans,
86993
88156
  splitAnswerLines: () => splitAnswerLines,
88157
+ statedLimitsFromAttrs: () => statedLimitsFromAttrs,
88158
+ statedLimitsRecord: () => statedLimitsRecord,
86994
88159
  stepAppliesToPlatform: () => stepAppliesToPlatform,
86995
88160
  stepConsoleLines: () => stepConsoleLines,
86996
88161
  stepIsDivergentForPlatform: () => stepIsDivergentForPlatform,
@@ -87002,7 +88167,11 @@ __export(dist_exports, {
87002
88167
  stripCodeFactPrefix: () => stripCodeFactPrefix,
87003
88168
  stripCoverageComments: () => stripCoverageComments,
87004
88169
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
88170
+ stripFractionZeros: () => stripFractionZeros,
88171
+ stripLineComment: () => stripLineComment,
87005
88172
  stripLoginScaffolding: () => stripLoginScaffolding,
88173
+ stripTrailingChars: () => stripTrailingChars,
88174
+ stripTrailingSlashes: () => stripTrailingSlashes,
87006
88175
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
87007
88176
  sttModelsForProvider: () => sttModelsForProvider,
87008
88177
  summarizeHealth: () => summarizeHealth,
@@ -87019,10 +88188,14 @@ __export(dist_exports, {
87019
88188
  ttsFallbackModel: () => ttsFallbackModel,
87020
88189
  ttsProviderForModel: () => ttsProviderForModel,
87021
88190
  unexpectedPassError: () => unexpectedPassError,
88191
+ v1RenderIdentity: () => v1RenderIdentity,
87022
88192
  validateAppMapDocument: () => validateAppMapDocument,
87023
88193
  validateAssertionStrength: () => validateAssertionStrength,
87024
88194
  validateEngineSpec: () => validateEngineSpec,
88195
+ validateProviderChainEntries: () => validateProviderChainEntries,
88196
+ validateProviderChainsSetting: () => validateProviderChainsSetting,
87025
88197
  validateTaskModelId: () => validateTaskModelId,
88198
+ visualBaselinesEnabled: () => visualBaselinesEnabled,
87026
88199
  visualProDailyBudget: () => visualProDailyBudget,
87027
88200
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
87028
88201
  voicesForTtsModel: () => voicesForTtsModel,
@@ -87030,7 +88203,8 @@ __export(dist_exports, {
87030
88203
  withReviewSource: () => withReviewSource,
87031
88204
  withoutNotChargedClaim: () => withoutNotChargedClaim,
87032
88205
  withoutServerOnlySpendFields: () => withoutServerOnlySpendFields,
87033
- withoutSurveyNotes: () => withoutSurveyNotes
88206
+ withoutSurveyNotes: () => withoutSurveyNotes,
88207
+ zaiWireModel: () => zaiWireModel
87034
88208
  });
87035
88209
  var init_dist = __esm({
87036
88210
  "../shared/dist/index.js"() {
@@ -87072,6 +88246,7 @@ var init_dist = __esm({
87072
88246
  init_admin_view_return_to();
87073
88247
  init_auth_plan();
87074
88248
  init_auth_block_detector();
88249
+ init_auth_session_stamp();
87075
88250
  init_auth_state_freshness();
87076
88251
  init_skip_coverage_detector();
87077
88252
  init_feature_coverage();
@@ -87083,11 +88258,14 @@ var init_dist = __esm({
87083
88258
  init_deepgram_voices();
87084
88259
  init_feature_flags();
87085
88260
  init_env_flags();
88261
+ init_visual_compare_limits();
88262
+ init_visual_baseline_identity();
87086
88263
  init_analytics_url();
87087
88264
  init_agentic_cli_runtime();
87088
88265
  init_api_model_catalog();
87089
88266
  init_engine_spec();
87090
88267
  init_provider_chains();
88268
+ init_provider_accounts();
87091
88269
  init_project_engine_overrides();
87092
88270
  init_ai_profiles();
87093
88271
  init_openrouter_usage();
@@ -87128,6 +88306,225 @@ var init_dist = __esm({
87128
88306
  init_promotion_quality_gate();
87129
88307
  init_regex_alternation();
87130
88308
  init_page_evidence();
88309
+ init_trailing_slashes();
88310
+ init_stated_limits();
88311
+ }
88312
+ });
88313
+
88314
+ // ../runner-core/dist/services/harness-init-script.js
88315
+ var require_harness_init_script = __commonJS({
88316
+ "../runner-core/dist/services/harness-init-script.js"(exports2) {
88317
+ "use strict";
88318
+ Object.defineProperty(exports2, "__esModule", { value: true });
88319
+ exports2.HARNESS_INIT_SOURCE_URL = void 0;
88320
+ exports2.withHarnessSourceUrl = withHarnessSourceUrl;
88321
+ exports2.buildAnimationDisablingSource = buildAnimationDisablingSource;
88322
+ exports2.isHarnessInitLocation = isHarnessInitLocation;
88323
+ exports2.isHarnessInitErrorStack = isHarnessInitErrorStack;
88324
+ exports2.isHarnessInitConsoleLine = isHarnessInitConsoleLine;
88325
+ exports2.stripHarnessConsoleEntries = stripHarnessConsoleEntries;
88326
+ var node_crypto_1 = require("crypto");
88327
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
88328
+ exports2.HARNESS_INIT_SOURCE_URL = `vqa-harness-init-${(0, node_crypto_1.randomBytes)(4).toString("hex")}.js`;
88329
+ var TRANSITION_LOOP_LIMIT = 8;
88330
+ var TRANSITION_LOOP_WINDOW_MS = 1e3;
88331
+ function withHarnessSourceUrl(content) {
88332
+ return `${content}
88333
+ //# sourceURL=${exports2.HARNESS_INIT_SOURCE_URL}`;
88334
+ }
88335
+ function buildAnimationDisablingSource(opts) {
88336
+ const finishTransitions = opts.finishTransitions ?? (0, shared_1.parseBoolFlag)(process.env.HARNESS_FINISH_CSS_TRANSITIONS);
88337
+ const css = finishTransitions ? "*, *::before, *::after { animation-duration: 0s !important; animation-delay: 0s !important; }" : "*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; transition-delay: 0s !important; animation-delay: 0s !important; }";
88338
+ return withHarnessSourceUrl(`(() => {
88339
+ // Layer 1: CSS transitions and animations - instant, not removed
88340
+ // (duration 0s preserves final state; 'none' would prevent keyframes from completing)
88341
+ // The rule goes in a constructed stylesheet on document.adoptedStyleSheets, which adds no node to the DOM:
88342
+ // a <style> element changed what the test sees (locator('style') and 'head > *' counts, document.styleSheets,
88343
+ // an iframe's empty head), so a spec stock Playwright passes failed. The <style> is the fallback for a
88344
+ // browser with no constructable stylesheets.
88345
+ let adopted = false;
88346
+ try {
88347
+ if (typeof CSSStyleSheet === 'function' && typeof Document === 'function' && 'adoptedStyleSheets' in Document.prototype) {
88348
+ const sheet = new CSSStyleSheet();
88349
+ sheet.replaceSync(${JSON.stringify(css)});
88350
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
88351
+ adopted = true;
88352
+ }
88353
+ } catch (e) {}
88354
+ if (!adopted) try {
88355
+ let applied = false;
88356
+ const apply = () => {
88357
+ if (applied) return true;
88358
+ const root = document.head || document.documentElement;
88359
+ if (!root) return false;
88360
+ const style = document.createElement('style');
88361
+ style.textContent = ${JSON.stringify(css)};
88362
+ root.appendChild(style);
88363
+ applied = true;
88364
+ return true;
88365
+ };
88366
+ if (!apply()) {
88367
+ // The document has no root element yet (iframe, about:blank, empty document): wait for it.
88368
+ let observer = null;
88369
+ const stop = () => { try { if (observer) observer.disconnect(); } catch (e) {} };
88370
+ const retry = () => { try { if (apply()) stop(); } catch (e) { stop(); } };
88371
+ try {
88372
+ observer = new MutationObserver(retry);
88373
+ observer.observe(document, { childList: true });
88374
+ } catch (e) {}
88375
+ document.addEventListener('readystatechange', retry);
88376
+ document.addEventListener('DOMContentLoaded', () => { retry(); stop(); }, { once: true });
88377
+ }
88378
+ } catch (e) {}
88379
+ ${opts.hideWebdriver ? `
88380
+ try {
88381
+ Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
88382
+ } catch (e) {}
88383
+ ` : ""}
88384
+ ${finishTransitions ? `
88385
+ // Layer 1b: CSS transitions run, and are finished as they start, so transitionend fires.
88386
+ // - finish() is deferred to a task: called inside the transitionrun dispatch (or a microtask of it) after a
88387
+ // real input event, Chromium finishes the transition but never fires its transitionstart/transitionend.
88388
+ // - setTimeout and performance.now are the ones present when this script runs, before the page's own
88389
+ // scripts: fake timers the page installs later cannot hold a finish back.
88390
+ // - The subtree is walked only for a pseudo-element's transition (its animations hang off the element).
88391
+ // - An element whose same transition restarts more than ${TRANSITION_LOOP_LIMIT} times within ${TRANSITION_LOOP_WINDOW_MS} ms (an app that starts it again on
88392
+ // each transitionend: carousel, pulse) is left to run at its own pace from then on, instead of cycling at
88393
+ // frame rate for the rest of the run.
88394
+ try {
88395
+ const st = setTimeout;
88396
+ const perf = typeof performance === 'object' && performance && typeof performance.now === 'function' ? performance : null;
88397
+ const now = perf ? perf.now.bind(perf) : Date.now;
88398
+ const seen = new WeakMap();
88399
+ const looping = (el, key) => {
88400
+ let byKey = seen.get(el);
88401
+ if (!byKey) { byKey = new Map(); seen.set(el, byKey); }
88402
+ const t = now();
88403
+ let s = byKey.get(key);
88404
+ if (s && s.loop) return true;
88405
+ if (!s || t - s.since > ${TRANSITION_LOOP_WINDOW_MS}) { s = { n: 0, since: t, loop: false }; byKey.set(key, s); }
88406
+ s.n += 1;
88407
+ if (s.n > ${TRANSITION_LOOP_LIMIT}) s.loop = true;
88408
+ return s.loop;
88409
+ };
88410
+ window.addEventListener('transitionrun', (ev) => {
88411
+ try {
88412
+ const el = ev && ev.target;
88413
+ if (!el || typeof el.getAnimations !== 'function' || typeof CSSTransition !== 'function') return;
88414
+ const pseudo = ev.pseudoElement || '';
88415
+ if (looping(el, ev.propertyName + '|' + pseudo)) return;
88416
+ for (const a of (pseudo ? el.getAnimations({ subtree: true }) : el.getAnimations())) {
88417
+ if (a instanceof CSSTransition && a.transitionProperty === ev.propertyName && a.effect
88418
+ && a.effect.target === el && (a.effect.pseudoElement || '') === pseudo) {
88419
+ st(() => { try { a.finish(); } catch (e) {} }, 0);
88420
+ }
88421
+ }
88422
+ } catch (e) {}
88423
+ }, true);
88424
+ } catch (e) {}
88425
+ ` : ""}
88426
+ // Layer 2: Web Animations API - make instant instead of no-op
88427
+ // (preserves animation completion callbacks that app code may depend on)
88428
+ try {
88429
+ const origAnimate = Element.prototype.animate;
88430
+ if (typeof origAnimate === 'function') {
88431
+ Element.prototype.animate = function (keyframes, options) {
88432
+ if (typeof options === 'object' && options !== null) {
88433
+ options = Object.assign({}, options, { duration: 0, delay: 0 });
88434
+ } else {
88435
+ options = 0; // covers number, undefined, and any other type
88436
+ }
88437
+ return origAnimate.call(this, keyframes, options);
88438
+ };
88439
+ }
88440
+ } catch (e) {}
88441
+ })();`);
88442
+ }
88443
+ function isHarnessInitLocation(url) {
88444
+ return url === exports2.HARNESS_INIT_SOURCE_URL;
88445
+ }
88446
+ var MAX_MATCHED_LINE_CHARS = 2048;
88447
+ var MAX_SCANNED_LINES = 200;
88448
+ var STACK_FRAME_RE = /^\s*at\s+(?:[^()\s][^()]*\()?([^()\s]+):\d+:\d+\)?\s*$/;
88449
+ function isHarnessInitErrorStack(stack) {
88450
+ if (!stack)
88451
+ return false;
88452
+ const lines = stack.split("\n", MAX_SCANNED_LINES);
88453
+ for (const line of lines) {
88454
+ if (line.length > MAX_MATCHED_LINE_CHARS)
88455
+ continue;
88456
+ const match = STACK_FRAME_RE.exec(line);
88457
+ if (match)
88458
+ return match[1] === exports2.HARNESS_INIT_SOURCE_URL;
88459
+ }
88460
+ return false;
88461
+ }
88462
+ var MCP_CONSOLE_LINE_LOCATION_RE = / @ ([^\s@]+):\d+$/;
88463
+ function isHarnessInitConsoleLine(line) {
88464
+ const trimmed = line.trim();
88465
+ if (trimmed.length > MAX_MATCHED_LINE_CHARS)
88466
+ return false;
88467
+ const match = MCP_CONSOLE_LINE_LOCATION_RE.exec(trimmed);
88468
+ return match !== null && match[1] === exports2.HARNESS_INIT_SOURCE_URL;
88469
+ }
88470
+ var MCP_LEVEL_LINE_RE = /^\s*(?:-\s+)?\[([A-Za-z]+)\]/;
88471
+ var MCP_FRAME_LINE_RE = /^\s+at\s/;
88472
+ var MCP_TOTALS_RE = /^(\s*Total messages:\s*)(\d+)(\s*\(Errors:\s*)(\d+)(,\s*Warnings:\s*)(\d+)(\).*)$/;
88473
+ var MCP_RETURNING_RE = /^(\s*Returning\s+)(\d+)(\s+messages\b.*)$/;
88474
+ function stripHarnessConsoleEntries(text) {
88475
+ if (!text.includes(exports2.HARNESS_INIT_SOURCE_URL))
88476
+ return text;
88477
+ const lines = text.split("\n");
88478
+ const out = [];
88479
+ let removed = 0;
88480
+ let removedErrors = 0;
88481
+ let removedWarnings = 0;
88482
+ let i = 0;
88483
+ while (i < lines.length) {
88484
+ const line = lines[i];
88485
+ const level = MCP_LEVEL_LINE_RE.exec(line);
88486
+ if (level && isHarnessInitConsoleLine(line.replace(/^\s*-\s+/, ""))) {
88487
+ removed++;
88488
+ const kind = level[1].toLowerCase();
88489
+ if (kind === "error")
88490
+ removedErrors++;
88491
+ else if (kind === "warning" || kind === "warn")
88492
+ removedWarnings++;
88493
+ i++;
88494
+ continue;
88495
+ }
88496
+ if (MCP_FRAME_LINE_RE.test(line)) {
88497
+ let end = i;
88498
+ while (end < lines.length && MCP_FRAME_LINE_RE.test(lines[end]))
88499
+ end++;
88500
+ const frames = lines.slice(i, end);
88501
+ if (isHarnessInitErrorStack(frames.join("\n"))) {
88502
+ const previous = out[out.length - 1];
88503
+ if (previous !== void 0 && previous.trim() !== "" && !/^\s*(?:#|Total messages|Returning)/.test(previous) && !MCP_LEVEL_LINE_RE.test(previous))
88504
+ out.pop();
88505
+ removed++;
88506
+ removedErrors++;
88507
+ } else {
88508
+ out.push(...frames);
88509
+ }
88510
+ i = end;
88511
+ continue;
88512
+ }
88513
+ out.push(line);
88514
+ i++;
88515
+ }
88516
+ if (removed === 0)
88517
+ return text;
88518
+ return out.map((line) => {
88519
+ const totals = MCP_TOTALS_RE.exec(line);
88520
+ if (totals) {
88521
+ const [, a, total, b, errors2, c, warnings, d] = totals;
88522
+ return `${a}${Math.max(0, +total - removed)}${b}${Math.max(0, +errors2 - removedErrors)}${c}${Math.max(0, +warnings - removedWarnings)}${d}`;
88523
+ }
88524
+ const returning = MCP_RETURNING_RE.exec(line);
88525
+ return returning ? `${returning[1]}${Math.max(0, +returning[2] - removed)}${returning[3]}` : line;
88526
+ }).join("\n");
88527
+ }
87131
88528
  }
87132
88529
  });
87133
88530