@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
@@ -368,13 +368,13 @@ function isBlockedEnvVar(key) {
368
368
  function sanitizeTestVariables(vars) {
369
369
  if (!vars)
370
370
  return {};
371
- const clean2 = {};
371
+ const clean3 = {};
372
372
  for (const [key, value] of Object.entries(vars)) {
373
373
  if (!isBlockedEnvVar(key)) {
374
- clean2[key] = value;
374
+ clean3[key] = value;
375
375
  }
376
376
  }
377
- return clean2;
377
+ return clean3;
378
378
  }
379
379
  function buildSandboxedEnv(processEnv, testVariables) {
380
380
  const env = {};
@@ -1831,9 +1831,40 @@ function semanticTarget(label, platform, base2) {
1831
1831
  }
1832
1832
  };
1833
1833
  }
1834
+ function androidResourceOwners(value) {
1835
+ const owners = [];
1836
+ for (const match of value.matchAll(ANDROID_RESOURCE_ID_SUFFIX_RE)) {
1837
+ let runStart = match.index;
1838
+ while (runStart > 0 && /[a-z0-9_.]/i.test(value[runStart - 1]))
1839
+ runStart--;
1840
+ const owner = ownerAtEndOfRun(value.slice(runStart, match.index));
1841
+ if (owner !== null)
1842
+ owners.push(owner);
1843
+ }
1844
+ return owners;
1845
+ }
1846
+ function ownerAtEndOfRun(run) {
1847
+ const segments = run.split(".");
1848
+ const last = segments.length - 1;
1849
+ const startsWithLetter = (segment) => /^[a-z]/i.test(segment);
1850
+ if (last < 1 || !startsWithLetter(segments[last]))
1851
+ return null;
1852
+ let first = last;
1853
+ while (first > 0 && startsWithLetter(segments[first - 1]))
1854
+ first--;
1855
+ let offset = 0;
1856
+ for (let i = 0; i < first - 1; i++)
1857
+ offset += segments[i].length + 1;
1858
+ if (first > 0) {
1859
+ const letterAt = segments[first - 1].search(/[a-z]/i);
1860
+ if (letterAt >= 0)
1861
+ return run.slice(offset + letterAt);
1862
+ offset += segments[first - 1].length + 1;
1863
+ }
1864
+ return first < last ? run.slice(offset) : null;
1865
+ }
1834
1866
  function containsForeignAppPackage(value, targetAppId) {
1835
- for (const match of value.matchAll(ANDROID_RESOURCE_OWNER_RE)) {
1836
- const owner = match[1];
1867
+ for (const owner of androidResourceOwners(value)) {
1837
1868
  if (targetAppId && owner !== targetAppId && !owner.startsWith(`${targetAppId}.`))
1838
1869
  return true;
1839
1870
  }
@@ -1970,7 +2001,7 @@ function healMobileTestSteps(input) {
1970
2001
  summary: changed ? uniq(summary).slice(0, 12) : []
1971
2002
  };
1972
2003
  }
1973
- var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_OWNER_RE, COMMON_EXTERNAL_PREFIXES;
2004
+ var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_ID_SUFFIX_RE, COMMON_EXTERNAL_PREFIXES;
1974
2005
  var init_mobile_step_healer = __esm({
1975
2006
  "../shared/dist/mobile-step-healer.js"() {
1976
2007
  "use strict";
@@ -1991,7 +2022,7 @@ var init_mobile_step_healer = __esm({
1991
2022
  "assertStateDelta"
1992
2023
  ]);
1993
2024
  FOREIGN_PACKAGE_RE = /\b[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}\b/gi;
1994
- ANDROID_RESOURCE_OWNER_RE = /([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+):id\//gi;
2025
+ ANDROID_RESOURCE_ID_SUFFIX_RE = /:id\//gi;
1995
2026
  COMMON_EXTERNAL_PREFIXES = [
1996
2027
  "com.android.",
1997
2028
  "com.google.",
@@ -3304,7 +3335,7 @@ function unescapeQuotedBody(raw) {
3304
3335
  return raw.replace(/\\(['"`\\])/g, "$1");
3305
3336
  }
3306
3337
  function extractRoleNameValue(line) {
3307
- const m = line.match(new RegExp(`getByRole\\s*\\([^,]+,\\s*\\{[^}]*name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
3338
+ const m = line.match(new RegExp(`getByRole\\s*\\([^,]{1,200},\\s*\\{[^}]{0,200}?name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
3308
3339
  if (!m)
3309
3340
  return null;
3310
3341
  const raw = m[1] ?? m[2] ?? m[3];
@@ -3701,7 +3732,7 @@ var init_assertion_classifier = __esm({
3701
3732
  // toHaveCount(0) stays tier 4 (absence proof) via TIER4_PATTERNS.
3702
3733
  [/\.toHaveCount\s*\(\s*[1-9]\d*\s*\)/, "non-zero toHaveCount asserts a record count that changes between runs"],
3703
3734
  // parenthesised counts like "(3)" or "[12]" in text - but not Playwright method args
3704
- [/(?<!\.\w{3,20}\s*)\(\s*\d+\s*\)(?!\s*[,;]?\s*\))/i, "parenthesised count is session-specific"],
3735
+ [/\((?<!\.\w{3,20}\s*\()\s*\d+\s*\)(?!\s*(?:[,;]\s*)?\))/i, "parenthesised count is session-specific"],
3705
3736
  // bare email addresses in assertion content
3706
3737
  [/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i, "email address is user-specific"],
3707
3738
  // currency/balance amounts (dynamic account data)
@@ -3947,7 +3978,7 @@ var init_assertion_proof = __esm({
3947
3978
  PRESENCE_MATCHER = /\.(?:toBeVisible|toBeAttached|toBeEnabled|toBeInViewport|toBeEditable|toBeFocused)\s*\(|\.toHaveCount\s*\(\s*1\s*\)/;
3948
3979
  TEXT_MATCHER = /\.(?:toHaveText|toContainText)\s*\(/;
3949
3980
  ANY_VALUE_SOURCE = /^(?:\.|\\S|\\w|\\d|\[\^\\s\]|\[0-9\]|\[\\d\]|\[\\w\]|\[\\s\\S\])[+*]?\??$/;
3950
- ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*\)/;
3981
+ ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*[,)]/;
3951
3982
  EMPTY_NOT_VALUE = /\.not\s*\.\s*toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/;
3952
3983
  TEXT_MATCHER_LITERAL = /\.(?:toHaveText|toContainText)\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
3953
3984
  GET_BY_TEXT_LITERAL = /getByText\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
@@ -3964,7 +3995,7 @@ var init_assertion_proof = __esm({
3964
3995
  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;
3965
3996
  TEXT_LOCATOR = /\.getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:/g;
3966
3997
  CSS_LOCATOR = /\.locator\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/g;
3967
- SPECIFIC_CSS = /\[[^\]]*=|#[\w-]|has-text|:text/;
3998
+ SPECIFIC_CSS = /\[[^\][=]*=|#[\w-]|has-text|:text/;
3968
3999
  }
3969
4000
  });
3970
4001
 
@@ -4012,6 +4043,8 @@ function stripCoverageComments(code) {
4012
4043
  let copyFrom = 0;
4013
4044
  let i = 0;
4014
4045
  let quote = null;
4046
+ let prev = "";
4047
+ let noRegexBefore = 0;
4015
4048
  while (i < code.length) {
4016
4049
  const ch = code[i];
4017
4050
  if (quote) {
@@ -4019,12 +4052,24 @@ function stripCoverageComments(code) {
4019
4052
  i += 2;
4020
4053
  continue;
4021
4054
  }
4022
- if (ch === quote || ch === "\n" && quote !== "`")
4055
+ if (ch === quote || ch === "\n" && quote !== "`") {
4023
4056
  quote = null;
4057
+ prev = ch === "\n" ? prev : ch;
4058
+ }
4024
4059
  i++;
4025
4060
  continue;
4026
4061
  }
4027
4062
  const next = code[i + 1];
4063
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
4064
+ const end = regexLiteralEnd3(code, i);
4065
+ if (end > 0) {
4066
+ i = end;
4067
+ prev = "a";
4068
+ continue;
4069
+ }
4070
+ const eol = code.indexOf("\n", i);
4071
+ noRegexBefore = eol === -1 ? code.length : eol;
4072
+ }
4028
4073
  if (ch === "/" && next === "*") {
4029
4074
  const end = code.indexOf("*/", i + 2);
4030
4075
  const stop = end === -1 ? code.length : end + 2;
@@ -4043,11 +4088,39 @@ function stripCoverageComments(code) {
4043
4088
  }
4044
4089
  if (ch === '"' || ch === "'" || ch === "`")
4045
4090
  quote = ch;
4091
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
4092
+ prev = ch;
4046
4093
  i++;
4047
4094
  }
4048
4095
  out.push(code.slice(copyFrom));
4049
4096
  return out.join("");
4050
4097
  }
4098
+ function regexLiteralEnd3(code, start) {
4099
+ let inClass = false;
4100
+ for (let j = start + 1; j < code.length; j++) {
4101
+ const c = code[j];
4102
+ if (c === "\n")
4103
+ return -1;
4104
+ if (c === "\\") {
4105
+ j++;
4106
+ continue;
4107
+ }
4108
+ if (inClass) {
4109
+ if (c === "]")
4110
+ inClass = false;
4111
+ continue;
4112
+ }
4113
+ if (c === "[")
4114
+ inClass = true;
4115
+ else if (c === "/") {
4116
+ let k = j + 1;
4117
+ while (k < code.length && isIdentChar(code[k]))
4118
+ k++;
4119
+ return k;
4120
+ }
4121
+ }
4122
+ return -1;
4123
+ }
4051
4124
  function isIdentChar(ch) {
4052
4125
  if (!ch)
4053
4126
  return false;
@@ -4705,7 +4778,7 @@ function assessTitleCoverage(title, code) {
4705
4778
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
4706
4779
  return { covered: hit >= needed || repeatedHit, missingTerms };
4707
4780
  }
4708
- 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;
4781
+ 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;
4709
4782
  var init_expected_outcome_coverage = __esm({
4710
4783
  "../shared/dist/expected-outcome-coverage.js"() {
4711
4784
  "use strict";
@@ -4742,6 +4815,7 @@ var init_expected_outcome_coverage = __esm({
4742
4815
  ]);
4743
4816
  MAX_ASSERTION_JOIN_CHARS = 8e3;
4744
4817
  MAX_COVERAGE_LINE_CHARS = 2e3;
4818
+ REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
4745
4819
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
4746
4820
  cachedAssertionInput = null;
4747
4821
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -4885,22 +4959,37 @@ function classifyAuthHandoffIntent(text) {
4885
4959
  function withoutIdpUrlAsserts(code) {
4886
4960
  return code.replace(/\.(?:toHaveURL|waitForURL)\s*\((?:[^)(]|\([^)]*\))*\)/g, (match) => /accountchooser|accounts\.google|appleid\.apple|login\.microsoftonline|\/v\d+\/signin|oauth/i.test(match) ? "" : match);
4887
4961
  }
4962
+ function stripLineComment(line) {
4963
+ let from = line.length;
4964
+ while (from > 0) {
4965
+ const c = line.charCodeAt(from - 1);
4966
+ if (c === 10 || c === 13 || c === 8232 || c === 8233)
4967
+ break;
4968
+ from--;
4969
+ }
4970
+ const at = line.indexOf("//", from);
4971
+ return at < 0 ? line : line.slice(0, at);
4972
+ }
4888
4973
  function stripComments(code) {
4889
- return replaceBlockComments(code, () => "").split("\n").map((line) => line.replace(/\/\/.*$/, "")).join("\n");
4974
+ return replaceBlockComments(code, () => "").split("\n").map(stripLineComment).join("\n");
4890
4975
  }
4891
- function balancedCall(code, start) {
4892
- const open = code.indexOf("(", start);
4893
- if (open < 0)
4894
- return null;
4895
- let depth = 0;
4896
- for (let i = open; i < code.length; i++) {
4976
+ function closingParens(code) {
4977
+ const close = new Int32Array(code.length).fill(-1);
4978
+ const open = [];
4979
+ for (let i = 0; i < code.length; i++) {
4897
4980
  const c = code[i];
4898
4981
  if (c === "(")
4899
- depth++;
4900
- else if (c === ")" && --depth === 0)
4901
- return { inner: code.slice(open + 1, i), end: i };
4982
+ open.push(i);
4983
+ else if (c === ")" && open.length > 0)
4984
+ close[open.pop()] = i;
4902
4985
  }
4903
- return null;
4986
+ return close;
4987
+ }
4988
+ function balancedCall(code, close, start) {
4989
+ const open = code.indexOf("(", start);
4990
+ if (open < 0 || close[open] < 0)
4991
+ return null;
4992
+ return { inner: code.slice(open + 1, close[open]), end: close[open] };
4904
4993
  }
4905
4994
  function resolveLocator(code, subject) {
4906
4995
  const id = subject.trim().match(/^([A-Za-z_$][\w$]*)$/);
@@ -4948,8 +5037,10 @@ function isAuthHandoffDismissCode(code) {
4948
5037
  const expects = [];
4949
5038
  const re = new RegExp(EXPECT_CALL_RE.source, "g");
4950
5039
  let match;
5040
+ let close = null;
4951
5041
  while (match = re.exec(body)) {
4952
- const found = balancedCall(body, match.index);
5042
+ close ??= closingParens(body);
5043
+ const found = balancedCall(body, close, match.index);
4953
5044
  if (found)
4954
5045
  expects.push({ inner: found.inner, tail: body.slice(found.end, found.end + 48) });
4955
5046
  }
@@ -4994,12 +5085,209 @@ var init_auth_handoff_coverage = __esm({
4994
5085
  }
4995
5086
  });
4996
5087
 
5088
+ // ../shared/dist/auth-plan.js
5089
+ function hasInlineLoginSteps(playwrightCode) {
5090
+ if (!playwrightCode)
5091
+ return false;
5092
+ return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
5093
+ }
5094
+ function hasGeneratedAuthGuard(playwrightCode) {
5095
+ if (!playwrightCode)
5096
+ return false;
5097
+ return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
5098
+ }
5099
+ function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
5100
+ if (!sharedAuthInjectEnabled)
5101
+ return false;
5102
+ if (tags.includes("@auth"))
5103
+ return false;
5104
+ if (tags.includes("@api"))
5105
+ return true;
5106
+ if (!tags.includes("@needs-auth"))
5107
+ return false;
5108
+ if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
5109
+ return true;
5110
+ const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
5111
+ const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
5112
+ return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
5113
+ }
5114
+ function codeReadsStoredCredentials(code) {
5115
+ if (!code)
5116
+ return false;
5117
+ if (/__vqaLogin\b/.test(code))
5118
+ return true;
5119
+ for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
5120
+ if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
5121
+ return true;
5122
+ }
5123
+ for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
5124
+ if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
5125
+ return true;
5126
+ }
5127
+ return false;
5128
+ }
5129
+ function explicitAuthTypeTags(t) {
5130
+ const found = /* @__PURE__ */ new Set();
5131
+ for (const tag of t) {
5132
+ if (AUTH_TYPE_TAGS.includes(tag))
5133
+ found.add(tag);
5134
+ else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
5135
+ found.add(AUTH_TAG_ALIASES[tag]);
5136
+ }
5137
+ return [...found];
5138
+ }
5139
+ function invalidPlan(configurationError) {
5140
+ return {
5141
+ version: 1,
5142
+ source: "invalid",
5143
+ acquisition: "unsupported",
5144
+ presentation: "none",
5145
+ needsCookieState: false,
5146
+ needsStandingHeaders: false,
5147
+ standingHeadersMode: "none",
5148
+ needsLoginCreds: false,
5149
+ requiredEnvKeys: [],
5150
+ configurationError
5151
+ };
5152
+ }
5153
+ function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
5154
+ return {
5155
+ version: 1,
5156
+ source: "explicit",
5157
+ authTypeTag: authTypeTag2,
5158
+ acquisition,
5159
+ presentation,
5160
+ needsCookieState: opts.cookie,
5161
+ needsStandingHeaders: opts.standingMode === "required",
5162
+ standingHeadersMode: opts.standingMode,
5163
+ needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
5164
+ requiredEnvKeys: opts.requiredEnvKeys
5165
+ };
5166
+ }
5167
+ function deriveAuthPlan(tags, code, opts) {
5168
+ const t = Array.isArray(tags) ? tags : [];
5169
+ const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
5170
+ const isApi = t.includes("@api");
5171
+ const explicit = isApi ? explicitAuthTypeTags(t) : [];
5172
+ if (explicit.length > 0) {
5173
+ if (t.includes("@auth")) {
5174
+ return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
5175
+ }
5176
+ const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
5177
+ if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
5178
+ const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
5179
+ switch (loginCompose[0]) {
5180
+ case AUTH_TAG_COOKIE_STATE:
5181
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
5182
+ case AUTH_TAG_LOGIN_COOKIE:
5183
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
5184
+ case AUTH_TAG_LOGIN_BEARER:
5185
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
5186
+ }
5187
+ }
5188
+ if (explicit.length > 1) {
5189
+ return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
5190
+ }
5191
+ switch (explicit[0]) {
5192
+ case AUTH_TAG_UNSUPPORTED:
5193
+ return {
5194
+ ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
5195
+ };
5196
+ case AUTH_TAG_NONE:
5197
+ return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
5198
+ case AUTH_TAG_STANDING:
5199
+ return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
5200
+ case AUTH_TAG_LOGIN_BEARER:
5201
+ return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
5202
+ case AUTH_TAG_LOGIN_COOKIE:
5203
+ return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
5204
+ case AUTH_TAG_COOKIE_STATE:
5205
+ return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
5206
+ }
5207
+ }
5208
+ const cookie = legacyNeedsCookieState(t, code, sharedFlag);
5209
+ const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
5210
+ const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
5211
+ const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
5212
+ const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
5213
+ return {
5214
+ version: 1,
5215
+ source: "legacy",
5216
+ acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
5217
+ presentation: authedLegacy ? "cookie" : "none",
5218
+ needsCookieState: cookie,
5219
+ needsStandingHeaders: false,
5220
+ // A legacy bare-@api test keeps injecting standing headers IF the project configured them
5221
+ // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
5222
+ standingHeadersMode: isApi ? "legacy-if-configured" : "none",
5223
+ needsLoginCreds,
5224
+ requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
5225
+ };
5226
+ }
5227
+ function authTypeTag(tags) {
5228
+ const t = Array.isArray(tags) ? tags : [];
5229
+ const explicit = explicitAuthTypeTags(t);
5230
+ return explicit.length === 1 ? explicit[0] : void 0;
5231
+ }
5232
+ 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;
5233
+ var init_auth_plan = __esm({
5234
+ "../shared/dist/auth-plan.js"() {
5235
+ "use strict";
5236
+ AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
5237
+ AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
5238
+ AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
5239
+ AUTH_TAG_STANDING = "@auth-standing";
5240
+ AUTH_TAG_NONE = "@auth-none";
5241
+ AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
5242
+ AUTH_TYPE_TAGS = [
5243
+ AUTH_TAG_COOKIE_STATE,
5244
+ AUTH_TAG_LOGIN_COOKIE,
5245
+ AUTH_TAG_LOGIN_BEARER,
5246
+ AUTH_TAG_STANDING,
5247
+ AUTH_TAG_NONE,
5248
+ AUTH_TAG_UNSUPPORTED
5249
+ ];
5250
+ AUTH_TAG_ALIASES = {
5251
+ "@auth-cookie": AUTH_TAG_COOKIE_STATE,
5252
+ "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
5253
+ "@auth-token": AUTH_TAG_LOGIN_BEARER,
5254
+ "@auth-api-key": AUTH_TAG_STANDING,
5255
+ "@auth-standing-header": AUTH_TAG_STANDING
5256
+ };
5257
+ TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
5258
+ API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
5259
+ }
5260
+ });
5261
+
4997
5262
  // ../shared/dist/assertion-strength.js
5263
+ function parseLastActionProofMode(raw) {
5264
+ const v = (raw ?? "").trim().toLowerCase();
5265
+ return v === "on" || v === "shadow" ? v : "off";
5266
+ }
5267
+ function isUrlProof(stmt) {
5268
+ if (URL_PROOF.test(stmt))
5269
+ return true;
5270
+ let calls = 0;
5271
+ for (const open of stmt.matchAll(POLL_OPEN)) {
5272
+ if (++calls > POLL_CALLS_MAX)
5273
+ break;
5274
+ const from = open.index + open[0].length;
5275
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
5276
+ if (args === null)
5277
+ continue;
5278
+ const subject = pollSubject(args);
5279
+ if (!POLL_PAGE_URL.test(subject))
5280
+ continue;
5281
+ if (!POLL_PAGE_URL_ONLY.test(subject) || PAGE_VALUE_MATCHER.test(stmt.slice(from + args.length)))
5282
+ return true;
5283
+ }
5284
+ return false;
5285
+ }
4998
5286
  function liveExpectStatements(uncommented) {
4999
5287
  const masked = maskedOutsideStrings(uncommented).split("\n");
5000
5288
  return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
5001
5289
  }
5002
- function stripLineComment(ln) {
5290
+ function stripLineComment2(ln) {
5003
5291
  let quote = null;
5004
5292
  for (let i = 0; i < ln.length; i++) {
5005
5293
  const c = ln[i];
@@ -5022,7 +5310,7 @@ function splitInteractionStatementsWithLines(lines) {
5022
5310
  let depth = 0;
5023
5311
  let scaffold = false;
5024
5312
  for (let i = 0; i < lines.length; i++) {
5025
- const ln = stripLineComment(lines[i]).trim();
5313
+ const ln = stripLineComment2(lines[i]).trim();
5026
5314
  if (!ln)
5027
5315
  continue;
5028
5316
  const isContinuation = out.length > 0 && (ln.startsWith(".") || depth > 0 && !scaffold);
@@ -5046,7 +5334,7 @@ function testBlockIds(lines) {
5046
5334
  let any = false;
5047
5335
  let block = 0;
5048
5336
  for (let i = 0; i < lines.length; i++) {
5049
- const ln = stripLineComment(lines[i]).trim();
5337
+ const ln = stripLineComment2(lines[i]).trim();
5050
5338
  if (!open) {
5051
5339
  if (!TEST_BLOCK_OPEN.test(ln))
5052
5340
  continue;
@@ -5130,7 +5418,7 @@ function expectSubjectSpan(stmt) {
5130
5418
  }
5131
5419
  return null;
5132
5420
  }
5133
- function resolveSubjectBindings(stmts, bindings, blockOfLine) {
5421
+ function resolveSubjectBindings(stmts, bindings, blockOfLine, boundAt) {
5134
5422
  if (bindings.length === 0)
5135
5423
  return [...stmts];
5136
5424
  return stmts.map((item) => {
@@ -5153,14 +5441,22 @@ function resolveSubjectBindings(stmts, bindings, blockOfLine) {
5153
5441
  subject = `${lead[1]}${found.expr.replace(/;\s*$/, "")}${subject.slice(lead[0].length)}`;
5154
5442
  line = found.line;
5155
5443
  }
5156
- return { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
5444
+ const out = { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
5445
+ if (boundAt !== void 0 && line !== item.startLine)
5446
+ boundAt.set(out, line);
5447
+ return out;
5157
5448
  });
5158
5449
  }
5159
5450
  function stripLoginScaffolding(code) {
5160
- const out = [];
5451
+ const lines = code.split("\n");
5452
+ return keptScaffoldingLines(lines).map((i) => lines[i]).join("\n");
5453
+ }
5454
+ function keptScaffoldingLines(lines) {
5455
+ const kept = [];
5161
5456
  let inAuth = false;
5162
5457
  let inInbox = false;
5163
- for (const ln of code.split("\n")) {
5458
+ for (let i = 0; i < lines.length; i++) {
5459
+ const ln = lines[i];
5164
5460
  if (!inAuth && !inInbox && AUTH_START.test(ln)) {
5165
5461
  inAuth = true;
5166
5462
  continue;
@@ -5181,9 +5477,9 @@ function stripLoginScaffolding(code) {
5181
5477
  }
5182
5478
  if (LOGIN_CALL_LINE.test(ln))
5183
5479
  continue;
5184
- out.push(ln);
5480
+ kept.push(i);
5185
5481
  }
5186
- return out.join("\n");
5482
+ return kept;
5187
5483
  }
5188
5484
  function stripCommentsAndLogs(code) {
5189
5485
  return stripCoverageComments(code).split("\n").filter((ln) => !/^\s*console\./.test(ln)).join("\n");
@@ -5192,7 +5488,7 @@ function detectsErrorBannerAssertion(code) {
5192
5488
  const cleaned = stripCommentsAndLogs(stripLoginScaffolding(code));
5193
5489
  const lines = cleaned.split("\n");
5194
5490
  const candidates = [];
5195
- const positive = (stmt) => !NEGATED_ASSERT.test(stmt) && !ABSENCE_MATCHER.test(stmt);
5491
+ const positive = (stmt) => !claimsAbsence(stmt);
5196
5492
  const statements = liveExpectStatements(cleaned).map((s) => s.stmt);
5197
5493
  for (const stmt of statements) {
5198
5494
  if (positive(stmt))
@@ -5321,6 +5617,12 @@ function detectsObservedFailureAsExpected(code) {
5321
5617
  return FAILURE_STATUS_ASSERT.some((re) => re.test(stmt)) || hasFailedListener && FAILED_LIST_ASSERT.test(stmt);
5322
5618
  });
5323
5619
  }
5620
+ function claimsAbsence(stmt) {
5621
+ const negated = NEGATED_ASSERT_NOT.test(stmt);
5622
+ const bare = negated ? stmt.replace(NOT_BEFORE_MATCHER, ".") : stmt;
5623
+ const absenceMatcher = /\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
5624
+ return negated !== absenceMatcher;
5625
+ }
5324
5626
  function isVacuousErrorAbsence(stmt, presence) {
5325
5627
  if (!ABSENCE_MATCHER.test(stmt))
5326
5628
  return false;
@@ -5329,6 +5631,41 @@ function isVacuousErrorAbsence(stmt, presence) {
5329
5631
  return false;
5330
5632
  return !presence().includes(m[1]);
5331
5633
  }
5634
+ function isVacuousAccountAbsence(stmt, shown, account) {
5635
+ if (!claimsAbsence(stmt))
5636
+ return false;
5637
+ if (!ABSENCE_MATCHER.test(stmt)) {
5638
+ const negated = NEGATED_TEXT.exec(stmt);
5639
+ if (!negated)
5640
+ return false;
5641
+ const read = account.exec(stmt.slice(negated.index));
5642
+ return read !== null && !shown().has(read[0]);
5643
+ }
5644
+ const m = account.exec(stmt);
5645
+ if (!m || shown().has(m[0]))
5646
+ return false;
5647
+ for (let at = endOfEnclosingCall(stmt, m.index + m[0].length); at < stmt.length; at = endOfEnclosingCall(stmt, at)) {
5648
+ SCOPING_STEP.lastIndex = at;
5649
+ if (SCOPING_STEP.test(stmt))
5650
+ return false;
5651
+ }
5652
+ return true;
5653
+ }
5654
+ function endOfEnclosingCall(stmt, from) {
5655
+ let depth = 0;
5656
+ for (let i = from; i < stmt.length; i++) {
5657
+ const c = stmt[i];
5658
+ if (c === "'" || c === '"') {
5659
+ for (i++; i < stmt.length && stmt[i] !== c; i++)
5660
+ if (stmt[i] === "\\")
5661
+ i++;
5662
+ } else if (c === "(")
5663
+ depth++;
5664
+ else if (c === ")" && depth-- === 0)
5665
+ return i + 1;
5666
+ }
5667
+ return stmt.length;
5668
+ }
5332
5669
  function isBareContainerVisibility(stmt) {
5333
5670
  return BARE_CONTAINER_ROLE.test(stmt) && !GET_BY_TEXT.test(stmt) && !HAS_TEXT_FILTER.test(stmt) && !TEXT_CONTENT_MATCHER.test(stmt);
5334
5671
  }
@@ -5336,19 +5673,53 @@ function isChromeTarget(stmt) {
5336
5673
  return CHROME_LOCATOR.test(stmt) && !CONTENT_ROLE.test(stmt) && !/getByText\s*\(/.test(stmt);
5337
5674
  }
5338
5675
  function countsAsContentText(stmt) {
5339
- if (!TEXT_CONTENT_MATCHER.test(stmt))
5676
+ if (!TEXT_CONTENT_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt))
5340
5677
  return false;
5341
5678
  if (isSymbolOnlyArgument(stmt, TEXT_CONTENT_MATCHER))
5342
5679
  return false;
5343
- if (!isChromeTarget(stmt))
5680
+ return onContentTarget(stmt);
5681
+ }
5682
+ function onContentTarget(stmt) {
5683
+ return !isChromeTarget(stmt) || /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
5684
+ }
5685
+ function provesTextRemoved(stmt, presence) {
5686
+ const m = stmt.match(NEGATED_TEXT);
5687
+ if (!m || !onContentTarget(stmt))
5688
+ return false;
5689
+ const text = m[1] ?? m[2];
5690
+ if (text === void 0)
5344
5691
  return true;
5345
- return /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
5692
+ if (!/[\p{L}\p{N}]/u.test(text))
5693
+ return false;
5694
+ return !ERROR_TEXT_WORDS.test(text) || presence().includes(text);
5346
5695
  }
5347
5696
  function isDataContentRole(stmt) {
5348
5697
  return CONTENT_ROLE.test(stmt) && !ANNOUNCE_ROLE.test(stmt);
5349
5698
  }
5699
+ function destructuresRequest(code) {
5700
+ let readTo = -1;
5701
+ for (const m of code.matchAll(ASYNC_DESTRUCTURE_OPEN)) {
5702
+ const start = m.index + m[0].length;
5703
+ if (start <= readTo)
5704
+ continue;
5705
+ let end = start;
5706
+ while (end < code.length && code[end] !== "}" && code[end] !== ")")
5707
+ end++;
5708
+ readTo = end;
5709
+ if (end === code.length)
5710
+ return false;
5711
+ if (code[end] !== "}" || !REQUEST_WORD.test(code.slice(start, end)))
5712
+ continue;
5713
+ let close = end + 1;
5714
+ while (close < code.length && /\s/.test(code[close]))
5715
+ close++;
5716
+ if (code[close] === ")")
5717
+ return true;
5718
+ }
5719
+ return false;
5720
+ }
5350
5721
  function isApiTest(code) {
5351
- const usesRequestFixture = /async\s*\(\s*\{[^})]*\brequest\b[^})]*\}\s*\)/.test(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
5722
+ const usesRequestFixture = destructuresRequest(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
5352
5723
  if (!usesRequestFixture)
5353
5724
  return false;
5354
5725
  const drivesUi = /\bpage\.(getBy\w+|locator|click|fill|goto|waitForURL|reload)\s*\(/.test(code) || /\.(click|fill|check|uncheck|selectOption|pressSequentially)\s*\(/.test(code);
@@ -5376,7 +5747,7 @@ function roleNames(s) {
5376
5747
  function interactedControlNames(lines) {
5377
5748
  const names = /* @__PURE__ */ new Set();
5378
5749
  for (const raw of lines) {
5379
- const ln = stripLineComment(raw);
5750
+ const ln = stripLineComment2(raw);
5380
5751
  if (!INTERACT_VERB.test(ln))
5381
5752
  continue;
5382
5753
  for (const n of roleNames(ln))
@@ -5572,6 +5943,14 @@ function balancedArguments(code, from, limit = code.length) {
5572
5943
  }
5573
5944
  return null;
5574
5945
  }
5946
+ function expectCallTail(stmt) {
5947
+ const at = stmt.search(EXPECT_CALL_RE);
5948
+ if (at < 0)
5949
+ return null;
5950
+ const from = stmt.indexOf("(", at) + 1;
5951
+ const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
5952
+ return args === null ? null : stmt.slice(from + args.length).trimStart();
5953
+ }
5575
5954
  function matcherArgumentsAreConstant(body, from) {
5576
5955
  MATCHER_OPEN.lastIndex = from;
5577
5956
  const opened = MATCHER_OPEN.exec(body);
@@ -5607,32 +5986,70 @@ function hasOnlyConstantAssertions(code) {
5607
5986
  const { constant, real } = constantAssertions(code);
5608
5987
  return constant.length > 0 && real === 0;
5609
5988
  }
5989
+ function envValueKey(name) {
5990
+ return `\0env:${name}`;
5991
+ }
5610
5992
  function enteredValues(stmts) {
5611
5993
  const values = /* @__PURE__ */ new Map();
5612
5994
  const navLines = [];
5995
+ const accountConsts = /* @__PURE__ */ new Set();
5613
5996
  for (const { stmt, startLine } of stmts) {
5614
5997
  if (PAGE_NAV.test(stmt))
5615
5998
  navLines.push(startLine);
5999
+ for (const c of stmt.matchAll(ACCOUNT_CONST))
6000
+ accountConsts.add(c[1].replace(/\$/g, "\\$"));
5616
6001
  let m;
5617
6002
  ENTERED_VALUE.lastIndex = 0;
5618
- while (m = ENTERED_VALUE.exec(stmt)) {
5619
- const value = (m[1] ?? m[2] ?? m[3]).toLowerCase();
5620
- if (!values.has(value))
5621
- values.set(value, startLine);
5622
- }
6003
+ const add = (value) => {
6004
+ const lines = values.get(value);
6005
+ if (!lines)
6006
+ values.set(value, [startLine]);
6007
+ else if (lines[lines.length - 1] !== startLine)
6008
+ lines.push(startLine);
6009
+ };
6010
+ while (m = ENTERED_VALUE.exec(stmt))
6011
+ add((m[1] ?? m[2] ?? m[3]).toLowerCase());
6012
+ ENTERED_ENV.lastIndex = 0;
6013
+ while (m = ENTERED_ENV.exec(stmt))
6014
+ add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
5623
6015
  }
5624
- return { values, navLines };
6016
+ const consts = accountConsts.size > 0 ? `|(?<![\\w$.])(?:${[...accountConsts].join("|")})(?![\\w$])` : "";
6017
+ return { values, navLines, account: new RegExp(`${ACCOUNT_ENV}${consts}`) };
5625
6018
  }
5626
- function isEnteredValueEcho(stmt, entered, startLine) {
6019
+ function countBelow(lines, line) {
6020
+ let lo = 0;
6021
+ let hi = lines.length;
6022
+ while (lo < hi) {
6023
+ const mid = lo + hi >> 1;
6024
+ if (lines[mid] < line)
6025
+ lo = mid + 1;
6026
+ else
6027
+ hi = mid;
6028
+ }
6029
+ return lo;
6030
+ }
6031
+ function enteredLinesOf(stmt, entered) {
5627
6032
  const m = stmt.match(/\.toHaveValue\s*\(\s*(['"`])((?:(?!\1).)+)\1/);
5628
- if (!m)
6033
+ if (m)
6034
+ return entered.values.get(m[2].toLowerCase());
6035
+ const env = stmt.match(HAVE_VALUE_ENV);
6036
+ return env ? entered.values.get(envValueKey(env[1] ?? env[2])) : void 0;
6037
+ }
6038
+ function typedSinceLastNav(stmt, entered, startLine, lines = enteredLinesOf(stmt, entered)) {
6039
+ if (!lines || startLine === void 0)
5629
6040
  return false;
5630
- const enteredAt = entered.values.get(m[2].toLowerCase());
5631
- if (enteredAt === void 0)
6041
+ const before = countBelow(lines, startLine);
6042
+ if (before === 0)
5632
6043
  return false;
5633
- if (startLine === void 0)
6044
+ return countBelow(entered.navLines, startLine) === countBelow(entered.navLines, lines[before - 1] + 1);
6045
+ }
6046
+ function isEnteredValueEcho(stmt, entered, startLine) {
6047
+ const lines = enteredLinesOf(stmt, entered);
6048
+ if (!lines)
6049
+ return false;
6050
+ if (startLine === void 0 || lines[0] >= startLine)
5634
6051
  return true;
5635
- return !entered.navLines.some((n) => n > enteredAt && n < startLine);
6052
+ return typedSinceLastNav(stmt, entered, startLine, lines);
5636
6053
  }
5637
6054
  function isAnyValueMatcher(stmt) {
5638
6055
  if (/\.not\.toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/.test(stmt))
@@ -5722,6 +6139,49 @@ function hasAttributeTransition(stmts) {
5722
6139
  return true;
5723
6140
  return false;
5724
6141
  }
6142
+ function comparesWithItself(subject, tail) {
6143
+ const m = SELF_COMPARE_MATCHER.exec(tail);
6144
+ if (!m)
6145
+ return false;
6146
+ const args = balancedArguments(tail, m[0].length, MATCHER_ARGS_MAX);
6147
+ if (args === null)
6148
+ return false;
6149
+ const squash2 = (x) => x.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
6150
+ const expected = squash2(args);
6151
+ return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
6152
+ }
6153
+ function asPlainAssertion(s) {
6154
+ return plainAssertion(s, false);
6155
+ }
6156
+ function plainAssertion(s, keepSelfCompare) {
6157
+ const { stmt } = s;
6158
+ if (COUNT_AT_LEAST_ZERO.test(stmt))
6159
+ return [];
6160
+ const subject = extractExpectSubject(stmt);
6161
+ if (subject === null)
6162
+ return [s];
6163
+ const plain = subject.replace(/^await\s+/, "").trim();
6164
+ const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
6165
+ const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
6166
+ const as = (rewritten) => [{ ...s, stmt: rewritten }];
6167
+ if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
6168
+ return [];
6169
+ if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
6170
+ return [];
6171
+ if (!keepSelfCompare && comparesWithItself(plain, tail))
6172
+ return [];
6173
+ const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
6174
+ if (visible && IS_VISIBLE_TRUE.test(tail))
6175
+ return as(`await expect(${visible[1]}).toBeVisible();`);
6176
+ if (/^page\s*\.\s*(?:url|title)\s*\(\s*\)$/.test(plain) && PAGE_VALUE_MATCHER.test(tail)) {
6177
+ const matcher = plain.includes("url") ? "toHaveURL" : "toHaveTitle";
6178
+ return as(`await expect(page)${tail.replace(/^\)\s*\.\s*\w+\s*\(/, `.${matcher}(`)}`);
6179
+ }
6180
+ return [s];
6181
+ }
6182
+ function assertionStrengthHoistedReadsOn(value = typeof process !== "undefined" ? process.env?.ASSERTION_STRENGTH_HOISTED_READS : void 0) {
6183
+ return /^(1|true|on)$/i.test((value ?? "").trim());
6184
+ }
5725
6185
  function creditsOutcomeAlways(stmt, entered, startLine) {
5726
6186
  if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
5727
6187
  return false;
@@ -5754,6 +6214,22 @@ function successNoticeQuote(stmt) {
5754
6214
  }
5755
6215
  return null;
5756
6216
  }
6217
+ function namedPagePath(arg) {
6218
+ const text = arg.trim();
6219
+ let path;
6220
+ const quoted = /^(['"`])([^'"`]*)\1$/.exec(text);
6221
+ const regex = quoted ? null : /^\/((?:\\.|[^/\\])+)\/[a-z]*$/.exec(text);
6222
+ if (quoted) {
6223
+ path = quoted[2].replace(/^\$\{[^}]*\}/, "").replace(/^\*\*/, "").replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "").replace(/\*\*$/, "");
6224
+ } else if (regex) {
6225
+ path = regex[1].replace(/^\^/, "").replace(/^\.\*/, "").replace(/\$$/, "").replace(/\\([/.-])/g, "$1");
6226
+ } else {
6227
+ return null;
6228
+ }
6229
+ if (!PLAIN_PAGE_PATH.test(path))
6230
+ return null;
6231
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
6232
+ }
5757
6233
  function provesStoredControlValue(stmt, startLine, persistence) {
5758
6234
  if (!persistence || startLine === void 0)
5759
6235
  return false;
@@ -5780,7 +6256,64 @@ function reopenedAfterSave(startLine, persistence, reopens = persistence.reloadL
5780
6256
  }
5781
6257
  return lo < reopens.length && reopens[lo] < startLine;
5782
6258
  }
5783
- function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence) {
6259
+ function actsBetween(withLines, from, to) {
6260
+ let acts = actLinesOf.get(withLines);
6261
+ if (acts === void 0) {
6262
+ acts = withLines.filter((s) => ACTS.test(s.stmt)).map((s) => s.startLine).sort((a, b) => a - b);
6263
+ actLinesOf.set(withLines, acts);
6264
+ }
6265
+ let lo = 0;
6266
+ let hi = acts.length;
6267
+ while (lo < hi) {
6268
+ const mid = lo + hi >> 1;
6269
+ if (acts[mid] <= from)
6270
+ lo = mid + 1;
6271
+ else
6272
+ hi = mid;
6273
+ }
6274
+ return lo < acts.length && acts[lo] < to;
6275
+ }
6276
+ function signInProofWindow(stmts, withLines, lines, blockIds) {
6277
+ if (blockIds === null)
6278
+ return void 0;
6279
+ const testBodies2 = /* @__PURE__ */ new Set();
6280
+ for (let i = 0; i < lines.length; i++) {
6281
+ const id = blockIds[i];
6282
+ if (id > 0 && (i === 0 || blockIds[i - 1] !== id) && TEST_BODY_OPEN.test(stripLineComment2(lines[i]).trim()))
6283
+ testBodies2.add(id);
6284
+ }
6285
+ const blockOf = (i) => blockIds[withLines[i].startLine - 1];
6286
+ const passwordAt = stmts.findIndex((stmt, i) => testBodies2.has(blockOf(i)) && ENV_PASSWORD_ENTRY.test(stmt));
6287
+ if (passwordAt < 0)
6288
+ return void 0;
6289
+ const body = blockOf(passwordAt);
6290
+ let clickAt = passwordAt + 1;
6291
+ while (clickAt < stmts.length && FORM_INPUT.test(stmts[clickAt]) && !ANY_CLICK.test(stmts[clickAt]))
6292
+ clickAt++;
6293
+ if (clickAt >= stmts.length || blockOf(clickAt) !== body || !ANY_CLICK.test(stmts[clickAt]) || NAV_LOCATOR.test(stmts[clickAt]))
6294
+ return void 0;
6295
+ let end = clickAt + 1;
6296
+ for (; end < stmts.length && blockOf(end) === body; end++) {
6297
+ if (CLOSERS_ONLY.test(stmts[end]))
6298
+ continue;
6299
+ if (!ASSERT_OR_WAIT.test(stmts[end]) || ACTS.test(stmts[end]))
6300
+ return void 0;
6301
+ }
6302
+ return { after: withLines[clickAt].startLine, until: end < stmts.length ? withLines[end].startLine : Number.POSITIVE_INFINITY };
6303
+ }
6304
+ function isAccountControlCheck(stmt) {
6305
+ if (!VISIBILITY_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt) || OPTION_ABSENCE.test(stmt))
6306
+ return false;
6307
+ const role = ACCOUNT_CONTROL_ROLE.exec(stmt);
6308
+ if (!role)
6309
+ return false;
6310
+ const rest = stmt.slice(role.index + role[0].length, role.index + role[0].length + 300);
6311
+ const close = rest.indexOf("}");
6312
+ const options = close < 0 ? rest : rest.slice(0, close);
6313
+ const name = options.match(/(?:^|[\s,])name\s*:\s*/);
6314
+ return name !== null && ACCOUNT_EMAIL_ENV.test(options.slice(name.index + name[0].length));
6315
+ }
6316
+ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence, signedIn) {
5784
6317
  if (mutated && hasAttributeTransition(stmts))
5785
6318
  return { kind: "durable" };
5786
6319
  const notices = [];
@@ -5790,11 +6323,25 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5790
6323
  };
5791
6324
  let presenceText;
5792
6325
  const presence = () => presenceText ??= stmts.filter((o) => !ABSENCE_MATCHER.test(o.stmt) && !NEGATED_ASSERT.test(o.stmt)).map((o) => o.stmt).join("\n");
6326
+ let shownAccounts;
6327
+ const shown = () => {
6328
+ if (shownAccounts)
6329
+ return shownAccounts;
6330
+ const ref = new RegExp(entered.account.source, "g");
6331
+ return shownAccounts = new Set(stmts.filter((o) => !claimsAbsence(o.stmt)).flatMap((o) => Array.from(o.stmt.matchAll(ref), (m) => m[0])));
6332
+ };
5793
6333
  for (const { stmt, startLine } of stmts) {
5794
6334
  if (firstMutLine !== void 0 && startLine !== void 0 && startLine < firstMutLine)
5795
6335
  continue;
5796
6336
  if (isVacuousErrorAbsence(stmt, presence))
5797
6337
  continue;
6338
+ if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
6339
+ continue;
6340
+ if (isVacuousAccountAbsence(stmt, shown, entered.account))
6341
+ continue;
6342
+ if (signedIn && startLine !== void 0 && startLine > signedIn.after && startLine < signedIn.until && isAccountControlCheck(stmt)) {
6343
+ return { kind: "durable" };
6344
+ }
5798
6345
  if (creditsOutcomeAlways(stmt, entered, startLine))
5799
6346
  return { kind: "durable" };
5800
6347
  if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
@@ -5804,12 +6351,15 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
5804
6351
  else
5805
6352
  return { kind: "durable" };
5806
6353
  }
5807
- if (STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
6354
+ const retyped = typedSinceLastNav(stmt, entered, startLine);
6355
+ if (!retyped && STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
5808
6356
  return { kind: "durable" };
5809
- if (provesStoredControlValue(stmt, startLine, persistence))
6357
+ if (!retyped && provesStoredControlValue(stmt, startLine, persistence))
5810
6358
  return { kind: "durable" };
5811
6359
  if (countsAsContentText(stmt))
5812
6360
  return { kind: "durable" };
6361
+ if (provesTextRemoved(stmt, presence))
6362
+ return { kind: "durable" };
5813
6363
  if (mutated && VISIBILITY_MATCHER.test(stmt)) {
5814
6364
  const revealsText = GET_BY_TEXT.test(stmt) || HAS_TEXT_FILTER.test(stmt) || CONTENT_ROLE.test(stmt);
5815
6365
  if (revealsText && !isChromeTarget(stmt)) {
@@ -5873,10 +6423,57 @@ function noticeOnlyExemption(clicked, noticeText, interactionStmts) {
5873
6423
  function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine) {
5874
6424
  return classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine).kind !== "none";
5875
6425
  }
6426
+ function clickLabel(stmt) {
6427
+ let label = null;
6428
+ for (const m of stmt.matchAll(CONTROL_LABEL))
6429
+ label = m[2] ?? m[3] ?? null;
6430
+ return label;
6431
+ }
6432
+ function teardownLines(lines) {
6433
+ const out = new Array(lines.length).fill(false);
6434
+ let open = false;
6435
+ let depth = 0;
6436
+ for (let i = 0; i < lines.length; i++) {
6437
+ const ln = stripLineComment2(lines[i]).trim();
6438
+ let from = 0;
6439
+ if (!open) {
6440
+ const m = TEARDOWN_OPEN.exec(ln);
6441
+ if (!m)
6442
+ continue;
6443
+ open = true;
6444
+ depth = 0;
6445
+ from = m.index;
6446
+ }
6447
+ out[i] = true;
6448
+ let closed = false;
6449
+ let quote = null;
6450
+ for (let k = from; k < ln.length; k++) {
6451
+ const ch = ln[k];
6452
+ if (quote !== null) {
6453
+ if (ch === "\\")
6454
+ k++;
6455
+ else if (ch === quote)
6456
+ quote = null;
6457
+ continue;
6458
+ }
6459
+ if (ch === "'" || ch === '"' || ch === "`")
6460
+ quote = ch;
6461
+ else if (ch === "{")
6462
+ depth++;
6463
+ else if (ch === "}") {
6464
+ depth--;
6465
+ closed = true;
6466
+ }
6467
+ }
6468
+ if (depth <= 0 && closed)
6469
+ open = false;
6470
+ }
6471
+ return out;
6472
+ }
5876
6473
  function validateAssertionStrength(code, opts = {}) {
5877
6474
  const body = stripLoginScaffolding(code);
5878
6475
  const lines = body.split("\n");
5879
- const stmts = liveExpectStatements(stripCoverageComments(body));
6476
+ const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
5880
6477
  const base2 = (extra) => ({
5881
6478
  apiTest: false,
5882
6479
  interacted: false,
@@ -5916,7 +6513,13 @@ function validateAssertionStrength(code, opts = {}) {
5916
6513
  const interactionStmts = inlineLocatorBindings(interactionStmtsRaw);
5917
6514
  const blockIds = testBlockIds(lines);
5918
6515
  const blockOfLine = (line) => blockIds?.[line - 1] ?? 0;
5919
- const checks = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine);
6516
+ const hoistedReads = assertionStrengthHoistedReadsOn();
6517
+ const boundAt = hoistedReads ? /* @__PURE__ */ new Map() : void 0;
6518
+ const resolved = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine, boundAt);
6519
+ const checks = boundAt === void 0 ? resolved : resolved.flatMap((s) => {
6520
+ const from = boundAt.get(s);
6521
+ return plainAssertion(s, from !== void 0 && actsBetween(interactionStmtsWithLines, from, s.startLine));
6522
+ });
5920
6523
  const insideTest = linesInsideTestBlocks(lines);
5921
6524
  const inTestOrder = (i) => insideTest === null || insideTest[interactionStmtsWithLines[i].startLine - 1] === true;
5922
6525
  let mutated = opts.interactionPerformed === true;
@@ -5926,9 +6529,34 @@ function validateAssertionStrength(code, opts = {}) {
5926
6529
  let navClicks = 0;
5927
6530
  const persistence = { mutLines: [], reloadLines: [], reloadCalls: [] };
5928
6531
  const openedPages = /* @__PURE__ */ new Set();
6532
+ let checkedPage = null;
6533
+ const savedOnCheckedPage = /* @__PURE__ */ new Set();
6534
+ const saveAt = (i, at) => {
6535
+ lastMutIdx = i;
6536
+ if (!inTestOrder(i))
6537
+ return;
6538
+ persistence.mutLines.push(at);
6539
+ if (firstMutIdx < 0)
6540
+ firstMutIdx = i;
6541
+ if (checkedPage !== null)
6542
+ savedOnCheckedPage.add(checkedPage);
6543
+ };
6544
+ const lastActionProof = opts.lastActionProof ?? "off";
6545
+ const teardown = lastActionProof === "off" ? null : teardownLines(lines);
6546
+ const acts = { last: null, prev: null, count: 0 };
6547
+ const noteAct = (i, at, kind) => {
6548
+ if (teardown === null || teardown[at - 1] || !inTestOrder(i))
6549
+ return;
6550
+ if (kind !== "enter")
6551
+ acts.count++;
6552
+ acts.prev = acts.last;
6553
+ acts.last = { i, kind };
6554
+ };
5929
6555
  for (let i = 0; i < interactionStmts.length; i++) {
5930
6556
  const ln = interactionStmts[i];
5931
6557
  const at = interactionStmtsWithLines[i].startLine;
6558
+ if (teardown !== null && acts.last?.kind === "fill" && ENTER_PRESS.test(ln))
6559
+ noteAct(i, at, "enter");
5932
6560
  if (RELOAD.test(ln)) {
5933
6561
  persistence.reloadLines.push(at);
5934
6562
  persistence.reloadCalls.push(at);
@@ -5936,32 +6564,31 @@ function validateAssertionStrength(code, opts = {}) {
5936
6564
  const opened = ln.match(/\.goto\s*\(\s*([^,)]+)/);
5937
6565
  if (opened) {
5938
6566
  const page = opened[1].trim();
5939
- if (openedPages.has(page))
6567
+ const path = namedPagePath(page);
6568
+ if (openedPages.has(page) || path !== null && savedOnCheckedPage.has(path))
5940
6569
  persistence.reloadLines.push(at);
5941
- else
5942
- openedPages.add(page);
5943
- }
6570
+ openedPages.add(page);
6571
+ checkedPage = null;
6572
+ }
6573
+ if (/\bpage\s*\.\s*go(?:Back|Forward)\s*\(/.test(ln))
6574
+ checkedPage = null;
6575
+ const checked = URL_CHECK_ARG.exec(ln);
6576
+ if (checked)
6577
+ checkedPage = namedPagePath(checked[1] ?? checked[2]);
5944
6578
  if (FORM_INPUT.test(ln)) {
5945
6579
  mutated = true;
5946
- lastMutIdx = i;
5947
- if (inTestOrder(i)) {
5948
- persistence.mutLines.push(at);
5949
- if (firstMutIdx < 0)
5950
- firstMutIdx = i;
5951
- }
6580
+ saveAt(i, at);
6581
+ noteAct(i, at, "fill");
5952
6582
  continue;
5953
6583
  }
5954
6584
  if (ANY_CLICK.test(ln)) {
5955
6585
  if (NAV_LOCATOR.test(ln)) {
5956
6586
  navClicks++;
6587
+ checkedPage = null;
5957
6588
  } else {
5958
6589
  mutated = true;
5959
- lastMutIdx = i;
5960
- if (inTestOrder(i)) {
5961
- persistence.mutLines.push(at);
5962
- if (firstMutIdx < 0)
5963
- firstMutIdx = i;
5964
- }
6590
+ saveAt(i, at);
6591
+ noteAct(i, at, "click");
5965
6592
  }
5966
6593
  }
5967
6594
  }
@@ -5994,9 +6621,28 @@ function validateAssertionStrength(code, opts = {}) {
5994
6621
  };
5995
6622
  }
5996
6623
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
5997
- const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence);
6624
+ const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
6625
+ const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
5998
6626
  if (proof.kind === "durable") {
5999
6627
  const signals2 = base2({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
6628
+ const { last, prev } = acts;
6629
+ 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])))) {
6630
+ const lastLine = interactionStmtsWithLines[last.i].startLine;
6631
+ const clicked = clickedControlNames(interactionStmts);
6632
+ const after = classifyOutcomeProof(checks, mutated, reloaded, clicked, entered, lastLine, orderedPersistence, signedIn);
6633
+ const unproven = after.kind === "none" || after.kind === "notice" && noticeOnlyExemption(clicked, after.noticeText, interactionStmts) === null;
6634
+ if (unproven && lastActionProof === "on") {
6635
+ const line = (keptScaffoldingLines(code.split("\n"))[lastLine - 1] ?? lastLine - 1) + 1;
6636
+ return {
6637
+ ok: false,
6638
+ severity: "block",
6639
+ reason: `the last save (line ${line}) has no outcome check after it (the earlier check proves only the earlier action)`,
6640
+ signals: { ...signals2, lastActionUnproven: true }
6641
+ };
6642
+ }
6643
+ if (unproven)
6644
+ signals2.lastActionUnproven = true;
6645
+ }
6000
6646
  return { ok: true, severity: "none", reason: "proves an outcome (count / diff / value / revealed content)", signals: signals2 };
6001
6647
  }
6002
6648
  if (mutated && proof.kind === "notice") {
@@ -6045,7 +6691,7 @@ function validateAssertionStrength(code, opts = {}) {
6045
6691
  signals
6046
6692
  };
6047
6693
  }
6048
- const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && URL_PROOF.test(s));
6694
+ const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && isUrlProof(s));
6049
6695
  if (urlProofAfter) {
6050
6696
  return {
6051
6697
  ok: false,
@@ -6082,7 +6728,7 @@ function validateAssertionStrength(code, opts = {}) {
6082
6728
  signals
6083
6729
  };
6084
6730
  }
6085
- 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;
6731
+ 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;
6086
6732
  var init_assertion_strength = __esm({
6087
6733
  "../shared/dist/assertion-strength.js"() {
6088
6734
  "use strict";
@@ -6091,6 +6737,7 @@ var init_assertion_strength = __esm({
6091
6737
  init_assertion_proof();
6092
6738
  init_auth_handoff_coverage();
6093
6739
  init_expect_call();
6740
+ init_auth_plan();
6094
6741
  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;
6095
6742
  OUTCOME_ALWAYS = [
6096
6743
  /\.toHaveCount\s*\(/,
@@ -6107,8 +6754,8 @@ var init_assertion_strength = __esm({
6107
6754
  // form field value
6108
6755
  /\.toMatch\s*\(/,
6109
6756
  // string/regex content match (e.g. textContent() vs /[2-9] selected/)
6110
- /\.toBeDisabled\s*\(/,
6111
- // a control became DISABLED — a gating/validation outcome
6757
+ /\.(?<!\.not\s*\.)\s*toBeDisabled\s*\(/,
6758
+ // a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
6112
6759
  /\.not\.toBeVisible\s*\(/,
6113
6760
  /\.toBeHidden\s*\(/
6114
6761
  // something the action removed/closed (disappearance proof)
@@ -6116,12 +6763,16 @@ var init_assertion_strength = __esm({
6116
6763
  FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
6117
6764
  ANY_CLICK = /\.click\s*\(/;
6118
6765
  NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
6119
- URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(/;
6766
+ 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*\(/;
6767
+ POLL_OPEN = /\bexpect\s*\.\s*poll\s*\(/g;
6768
+ POLL_PAGE_URL = /^page\s*\.\s*url\s*\(\s*\)/;
6769
+ POLL_PAGE_URL_ONLY = /^page\s*\.\s*url\s*\(\s*\)$/;
6770
+ POLL_CALLS_MAX = 8;
6120
6771
  RELOAD = /\.reload\s*\(/;
6121
6772
  EXPECT_STMT = /\bexpect\s*[.(]/;
6122
6773
  BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
6123
6774
  TEST_BLOCK_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow|serial|describe\.serial))?\s*\(|^(?:test\.)?(?:beforeEach|beforeAll)\s*\(/;
6124
- LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(.+?(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\(.+)$/;
6775
+ 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*\(.+)$/;
6125
6776
  EXPECT_OPEN = /\bexpect(?:\.soft)?\s*\(/;
6126
6777
  STATE_MATCHER = /\.(toBeChecked|toBeVisible|toHaveValue|toBeDisabled|toBeEnabled)\s*\(/;
6127
6778
  CHROME_LOCATOR = /getByRole\s*\(\s*['"`](?:heading|navigation|banner|main|contentinfo|complementary|region|search|tablist|tab|button|link|menuitem|textbox|searchbox|combobox|spinbutton)['"`]/i;
@@ -6141,16 +6792,17 @@ var init_assertion_strength = __esm({
6141
6792
  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;
6142
6793
  AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
6143
6794
  AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
6144
- LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
6795
+ LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*(?:;\s*)?$/;
6145
6796
  INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
6146
6797
  INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
6147
6798
  NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
6799
+ NEGATED_ASSERT_NOT = /\.not\s*\.\s*to[A-Z]/;
6148
6800
  CONST_CHAIN_MAX_DEPTH = 5;
6149
6801
  TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
6150
6802
  TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
6151
6803
  FAILURE_STATUS_ASSERT = [
6152
6804
  /\.status\s*\(\s*\)[^;]{0,200}\.(?:toBeGreaterThanOrEqual|toBeGreaterThan)\s*\(\s*(?:399|[45]\d\d)\b/,
6153
- /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\))?\s*>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
6805
+ /\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\)\s*)?>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
6154
6806
  /\bexpect\s*\([^;]{0,200}\.ok\s*\(\s*\)\s*\)\s*\.\s*(?:toBe\s*\(\s*false\s*\)|toBeFalsy\s*\()/
6155
6807
  ];
6156
6808
  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;
@@ -6160,8 +6812,15 @@ var init_assertion_strength = __esm({
6160
6812
  CATCH_ALL_PATTERN = /^(['"`])\*\*(?:\/\*)?\1$|^\/\.\*\/[a-z]*$/;
6161
6813
  GUARD_HANDLER = /\bMUTATING\.has\s*\(|blockedbyclient|Read-only guard/;
6162
6814
  ABSENCE_MATCHER = /\.not\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/;
6815
+ 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*\())/;
6816
+ OPTION_ABSENCE = /\.(?:toBeVisible\s*\(\s*\{[^{}()]{0,200}\bvisible|toBeAttached\s*\(\s*\{[^{}()]{0,200}\battached)\s*:\s*false\b/;
6817
+ NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
6163
6818
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
6164
6819
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
6820
+ SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
6821
+ NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
6822
+ ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
6823
+ REQUEST_WORD = /\brequest\b/;
6165
6824
  ROLE_NAME_RE = /getByRole\s*\(\s*['"`][a-z]+['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
6166
6825
  INTERACT_VERB = /\.(click|fill|check|uncheck|setChecked|selectOption|pressSequentially)\s*\(/;
6167
6826
  ROLE_SIG_RE = /getByRole\s*\(\s*['"`]([a-z]+)['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
@@ -6182,21 +6841,64 @@ var init_assertion_strength = __esm({
6182
6841
  MATCHER_OPEN = /\s*(?:\.\s*(?:not|resolves|rejects)\s*)*\.\s*[A-Za-z_$][\w$]*\s*\(/y;
6183
6842
  ENTERED_VALUE = /\.(?:fill|pressSequentially)\s*\(\s*['"`]([^'"`]+)['"`]|keyboard\.type\s*\(\s*['"`]([^'"`]+)['"`]|\.selectOption\s*\(\s*(?:\[\s*)?(?:\{\s*(?:label|value)\s*:\s*)?['"`]([^'"`]+)['"`]/g;
6184
6843
  PAGE_NAV = /\.(?:goto|reload)\s*\(/;
6844
+ ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
6845
+ ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
6846
+ HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
6847
+ ACCOUNT_KEYS = TEST_USER_ENV_KEYS.join("|");
6848
+ ACCOUNT_ENV = `process\\.env(?:\\??\\.(?:${ACCOUNT_KEYS})\\b|(?:\\?\\.)?\\[\\s*['"\`](?:${ACCOUNT_KEYS})['"\`]\\s*\\])`;
6849
+ ACCOUNT_CONST = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::\\s*string\\s*)?=\\s*${ACCOUNT_ENV}`, "g");
6185
6850
  IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
6851
+ LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
6852
+ LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
6853
+ ALWAYS_TRUE_ON_OBJECT = /^\)\s*\.\s*(?:toBeTruthy|toBeDefined|not\s*\.\s*(?:toBeNull|toBeUndefined|toBeFalsy))\s*\(/;
6854
+ COUNT_AT_LEAST_ZERO = /\.(?:count\s*\(\s*\)|length)[\s)]{0,10}\.(?:toBeGreaterThanOrEqual\s*\(\s*0\s*\)|toBeGreaterThan\s*\(\s*-\s*1\s*\))/;
6855
+ DEFINED_VALUE_READ = /\.\s*(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|title|url|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts)\s*\([^()]*\)$/;
6856
+ ALWAYS_DEFINED = /^\)\s*\.\s*(?:toBeDefined|not\s*\.\s*toBeUndefined)\s*\(/;
6857
+ SELF_COMPARE_MATCHER = /^\)\s*\.\s*(toBe|toEqual|toStrictEqual|toHaveCount)\s*\(/;
6858
+ IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
6859
+ PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
6860
+ COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
6861
+ URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
6862
+ PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
6186
6863
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
6187
6864
  CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
6865
+ ACCOUNT_EMAIL_ENV = /^process\.env(?:(?:\?\.|\.)TEST_USER_EMAIL\b|(?:\?\.)?\[\s*['"]TEST_USER_EMAIL['"]\s*\])/;
6866
+ ENV_PASSWORD_ENTRY = /\.(?:fill|pressSequentially)\s*\(\s*process\.env(?:(?:\?\.|\.)TEST_USER_PASSWORD\b|(?:\?\.)?\[\s*['"]TEST_USER_PASSWORD['"]\s*\])/;
6867
+ ACCOUNT_CONTROL_ROLE = /getByRole\s*\(\s*['"`](?:button|link|menuitem)['"`]\s*,\s*\{/;
6868
+ ASSERT_OR_WAIT = /^(?:await\s+)?(?:expect(?:\.soft|\.poll)?\s*\(|[\w$]+(?:\.[\w$]+)*\.waitFor[A-Za-z]*\s*\()/;
6869
+ 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*\(/;
6870
+ actLinesOf = /* @__PURE__ */ new WeakMap();
6871
+ CLOSERS_ONLY = /^[})\];,\s]+$/;
6872
+ TEST_BODY_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow))?\s*\(/;
6188
6873
  COPY_CONTROL = /\bcopy\b/i;
6189
6874
  COPIED_NOTICE = /\bcopied\b/i;
6190
6875
  SEND_CONTROL = /\b(send|submit|subscribe|sign up for|contact|feedback|join (the )?waitlist)\b/i;
6191
6876
  SEND_NOTICE = /\b(thank|thanks|sent|subscribed|received|in touch)\b/i;
6192
6877
  MARK_READ_CONTROL = /\bmark(?:ed)? as read\b|\bmark all(?: as)? read\b/i;
6193
6878
  APP_DATA_FIELD = /\b(title|name of|amount|price|quantity|sku|status|due|assignee|project|ticket|invoice|order)\b/i;
6879
+ COMMIT_CONTROL = /\b(?:save|submit|update|apply|add|create|post|send|publish)\b|\bconfirm\s+changes\b/i;
6880
+ 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;
6881
+ DESTRUCTIVE_CONTROL = /\b(?:delete|remove|discard)\b/i;
6882
+ ENTER_PRESS = /\.press\s*\(\s*['"`]Enter['"`]/;
6883
+ CONTROL_LABEL = /(?:name\s*:\s*|getBy(?:Text|Label|Title|TestId)\s*\(\s*|has-text\(\s*)(?:(['"`])((?:(?!\1).)*)\1|\/((?:\\.|[^/\\])+)\/)/g;
6884
+ isCommitClick = (stmt) => {
6885
+ const label = clickLabel(stmt);
6886
+ return label !== null && COMMIT_CONTROL.test(label) && !NOT_COMMIT_CONTROL.test(label) && !EXCLUDE_AFFORDANCE.test(label);
6887
+ };
6888
+ isDestructiveClick = (stmt) => {
6889
+ const label = clickLabel(stmt);
6890
+ return label !== null && DESTRUCTIVE_CONTROL.test(label);
6891
+ };
6892
+ TEARDOWN_OPEN = /\bfinally\s*\{|^(?:test\.)?(?:after|before)(?:Each|All)\s*\(/;
6194
6893
  }
6195
6894
  });
6196
6895
 
6197
6896
  // ../shared/dist/run-classification.js
6198
- function isUserCancelledError(error2) {
6199
- return typeof error2 === "string" && error2.trim().startsWith(USER_CANCELLED_RUN_ERROR);
6897
+ function isCancelledReason(error2) {
6898
+ if (typeof error2 !== "string")
6899
+ return false;
6900
+ const text = error2.trim();
6901
+ return CANCELLED_REASON_PREFIXES.some((prefix) => text.startsWith(prefix));
6200
6902
  }
6201
6903
  function isHeldHealRun(run) {
6202
6904
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
@@ -6229,7 +6931,7 @@ function pickLatestHealthRun(runs) {
6229
6931
  for (const run of runs) {
6230
6932
  if (run.mode === "auth-setup")
6231
6933
  continue;
6232
- if (run.status === "ERROR" && isUserCancelledError(run.error))
6934
+ if (run.status === "ERROR" && isCancelledReason(run.error))
6233
6935
  continue;
6234
6936
  if (isHeldHealRun(run))
6235
6937
  continue;
@@ -6247,6 +6949,15 @@ function pickDisplayRun(runs) {
6247
6949
  return latest;
6248
6950
  return pickLatestHealthRun(runs) ?? latest;
6249
6951
  }
6952
+ function featureRowRunStatus(runs) {
6953
+ const latest = runs.find((run) => run.mode !== "auth-setup");
6954
+ if (!latest)
6955
+ return null;
6956
+ const decisive = pickLatestHealthRun(runs.filter((run) => !isEnvironmentalError(run)));
6957
+ if (decisive)
6958
+ return decisive.status;
6959
+ return latest.status === "PENDING" || latest.status === "RUNNING" ? latest.status : null;
6960
+ }
6250
6961
  function isEnvironmentalError(run) {
6251
6962
  if (!run)
6252
6963
  return false;
@@ -6278,7 +6989,7 @@ function classifyErrorMessage(error2) {
6278
6989
  return "config_issue";
6279
6990
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
6280
6991
  return "email_not_received";
6281
- if (isUserCancelledError(error2))
6992
+ if (isCancelledReason(error2))
6282
6993
  return "cancelled";
6283
6994
  if (isRetriedRunInterruption(error2))
6284
6995
  return "interrupted";
@@ -6313,7 +7024,7 @@ function heldHealRunWhere() {
6313
7024
  function excludeCancelledRunsWhere() {
6314
7025
  return {
6315
7026
  NOT: [
6316
- { status: "ERROR", error: { not: null, startsWith: USER_CANCELLED_RUN_ERROR } },
7027
+ { status: "ERROR", OR: CANCELLED_REASON_PREFIXES.map((prefix) => ({ error: { not: null, startsWith: prefix } })) },
6317
7028
  heldHealRunWhere()
6318
7029
  ]
6319
7030
  };
@@ -6391,11 +7102,25 @@ function reportedBugWhere() {
6391
7102
  function bugReviewStatus(status) {
6392
7103
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
6393
7104
  }
6394
- 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;
7105
+ 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;
6395
7106
  var init_run_classification = __esm({
6396
7107
  "../shared/dist/run-classification.js"() {
6397
7108
  "use strict";
6398
7109
  USER_CANCELLED_RUN_ERROR = "Cancelled by user";
7110
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR = "Cancelled by platform support";
7111
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR = "Cancelled by platform admin";
7112
+ ADMIN_FORCE_CANCELLED_RUN_ERROR = "Admin force-cancelled all active runs";
7113
+ CHAIN_CANCELLED_RUN_ERROR = "Cancelled: parent run cancelled";
7114
+ RETRY_HELD_RUN_ERROR = "Cancelled: auto generation paused";
7115
+ CANCELLED_REASON_PREFIXES = [
7116
+ USER_CANCELLED_RUN_ERROR,
7117
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
7118
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
7119
+ ADMIN_FORCE_CANCELLED_RUN_ERROR,
7120
+ CHAIN_CANCELLED_RUN_ERROR,
7121
+ RETRY_HELD_RUN_ERROR
7122
+ ];
7123
+ isUserCancelledError = isCancelledReason;
6399
7124
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
6400
7125
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
6401
7126
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
@@ -6411,6 +7136,7 @@ var init_run_classification = __esm({
6411
7136
  UNEXPECTED_PASS_ERROR_PREFIX = "UNEXPECTED_PASS:";
6412
7137
  BUG_TEST_BROKEN_ERROR_PREFIX = "BUG_TEST_BROKEN:";
6413
7138
  BUG_TEST_BROKEN_REASON = "bug test broken (not a bug verdict)";
7139
+ TEST_DID_NOT_RUN_ERROR = "Test did not run (a dependency failed or the run was interrupted)";
6414
7140
  EMAIL_NOT_RECEIVED_PREFIXES = [
6415
7141
  "VERIFICATION_EMAIL_NOT_RECEIVED:",
6416
7142
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
@@ -6423,9 +7149,10 @@ var init_run_classification = __esm({
6423
7149
  ];
6424
7150
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
6425
7151
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
6426
- // Written by the cancel routes as is, with no prefix wrapper.
6427
- USER_CANCELLED_RUN_ERROR
7152
+ // Written by the cancels as is, with no prefix wrapper.
7153
+ ...CANCELLED_REASON_PREFIXES
6428
7154
  ];
7155
+ FEATURE_ROW_RUN_WINDOW = 10;
6429
7156
  BUG_SURFACE_SUPPRESSING_TAGS = ["@rate-limit-blocked", "@quota-exceeded", "@auth-lost"];
6430
7157
  QUARANTINE_CLOSE_TAGS = [
6431
7158
  "@bug-found",
@@ -8858,180 +9585,6 @@ var init_admin_view_return_to = __esm({
8858
9585
  }
8859
9586
  });
8860
9587
 
8861
- // ../shared/dist/auth-plan.js
8862
- function hasInlineLoginSteps(playwrightCode) {
8863
- if (!playwrightCode)
8864
- return false;
8865
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
8866
- }
8867
- function hasGeneratedAuthGuard(playwrightCode) {
8868
- if (!playwrightCode)
8869
- return false;
8870
- return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
8871
- }
8872
- function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
8873
- if (!sharedAuthInjectEnabled)
8874
- return false;
8875
- if (tags.includes("@auth"))
8876
- return false;
8877
- if (tags.includes("@api"))
8878
- return true;
8879
- if (!tags.includes("@needs-auth"))
8880
- return false;
8881
- if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
8882
- return true;
8883
- const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
8884
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
8885
- return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
8886
- }
8887
- function codeReadsStoredCredentials(code) {
8888
- if (!code)
8889
- return false;
8890
- if (/__vqaLogin\b/.test(code))
8891
- return true;
8892
- for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
8893
- if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
8894
- return true;
8895
- }
8896
- for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
8897
- if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
8898
- return true;
8899
- }
8900
- return false;
8901
- }
8902
- function explicitAuthTypeTags(t) {
8903
- const found = /* @__PURE__ */ new Set();
8904
- for (const tag of t) {
8905
- if (AUTH_TYPE_TAGS.includes(tag))
8906
- found.add(tag);
8907
- else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
8908
- found.add(AUTH_TAG_ALIASES[tag]);
8909
- }
8910
- return [...found];
8911
- }
8912
- function invalidPlan(configurationError) {
8913
- return {
8914
- version: 1,
8915
- source: "invalid",
8916
- acquisition: "unsupported",
8917
- presentation: "none",
8918
- needsCookieState: false,
8919
- needsStandingHeaders: false,
8920
- standingHeadersMode: "none",
8921
- needsLoginCreds: false,
8922
- requiredEnvKeys: [],
8923
- configurationError
8924
- };
8925
- }
8926
- function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
8927
- return {
8928
- version: 1,
8929
- source: "explicit",
8930
- authTypeTag: authTypeTag2,
8931
- acquisition,
8932
- presentation,
8933
- needsCookieState: opts.cookie,
8934
- needsStandingHeaders: opts.standingMode === "required",
8935
- standingHeadersMode: opts.standingMode,
8936
- needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
8937
- requiredEnvKeys: opts.requiredEnvKeys
8938
- };
8939
- }
8940
- function deriveAuthPlan(tags, code, opts) {
8941
- const t = Array.isArray(tags) ? tags : [];
8942
- const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
8943
- const isApi = t.includes("@api");
8944
- const explicit = isApi ? explicitAuthTypeTags(t) : [];
8945
- if (explicit.length > 0) {
8946
- if (t.includes("@auth")) {
8947
- return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
8948
- }
8949
- const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
8950
- if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
8951
- const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
8952
- switch (loginCompose[0]) {
8953
- case AUTH_TAG_COOKIE_STATE:
8954
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
8955
- case AUTH_TAG_LOGIN_COOKIE:
8956
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8957
- case AUTH_TAG_LOGIN_BEARER:
8958
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
8959
- }
8960
- }
8961
- if (explicit.length > 1) {
8962
- return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
8963
- }
8964
- switch (explicit[0]) {
8965
- case AUTH_TAG_UNSUPPORTED:
8966
- return {
8967
- ...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
8968
- };
8969
- case AUTH_TAG_NONE:
8970
- return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
8971
- case AUTH_TAG_STANDING:
8972
- return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
8973
- case AUTH_TAG_LOGIN_BEARER:
8974
- return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8975
- case AUTH_TAG_LOGIN_COOKIE:
8976
- return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8977
- case AUTH_TAG_COOKIE_STATE:
8978
- return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
8979
- }
8980
- }
8981
- const cookie = legacyNeedsCookieState(t, code, sharedFlag);
8982
- const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
8983
- const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
8984
- const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
8985
- const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
8986
- return {
8987
- version: 1,
8988
- source: "legacy",
8989
- acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
8990
- presentation: authedLegacy ? "cookie" : "none",
8991
- needsCookieState: cookie,
8992
- needsStandingHeaders: false,
8993
- // A legacy bare-@api test keeps injecting standing headers IF the project configured them
8994
- // (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
8995
- standingHeadersMode: isApi ? "legacy-if-configured" : "none",
8996
- needsLoginCreds,
8997
- requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
8998
- };
8999
- }
9000
- function authTypeTag(tags) {
9001
- const t = Array.isArray(tags) ? tags : [];
9002
- const explicit = explicitAuthTypeTags(t);
9003
- return explicit.length === 1 ? explicit[0] : void 0;
9004
- }
9005
- 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;
9006
- var init_auth_plan = __esm({
9007
- "../shared/dist/auth-plan.js"() {
9008
- "use strict";
9009
- AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
9010
- AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
9011
- AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
9012
- AUTH_TAG_STANDING = "@auth-standing";
9013
- AUTH_TAG_NONE = "@auth-none";
9014
- AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
9015
- AUTH_TYPE_TAGS = [
9016
- AUTH_TAG_COOKIE_STATE,
9017
- AUTH_TAG_LOGIN_COOKIE,
9018
- AUTH_TAG_LOGIN_BEARER,
9019
- AUTH_TAG_STANDING,
9020
- AUTH_TAG_NONE,
9021
- AUTH_TAG_UNSUPPORTED
9022
- ];
9023
- AUTH_TAG_ALIASES = {
9024
- "@auth-cookie": AUTH_TAG_COOKIE_STATE,
9025
- "@auth-bearer": AUTH_TAG_LOGIN_BEARER,
9026
- "@auth-token": AUTH_TAG_LOGIN_BEARER,
9027
- "@auth-api-key": AUTH_TAG_STANDING,
9028
- "@auth-standing-header": AUTH_TAG_STANDING
9029
- };
9030
- TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
9031
- API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
9032
- }
9033
- });
9034
-
9035
9588
  // ../shared/dist/auth-block-detector.js
9036
9589
  function stripEchoLines(text) {
9037
9590
  return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
@@ -9039,13 +9592,16 @@ function stripEchoLines(text) {
9039
9592
  function matchesAny(text, patterns) {
9040
9593
  return patterns.some((p) => p.test(text));
9041
9594
  }
9595
+ function isIntentionalNegativeAuthTest(input) {
9596
+ const meta = [input.testName, input.description].filter(Boolean).join(" ");
9597
+ return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
9598
+ }
9042
9599
  function detectAuthBlock(input) {
9043
9600
  const tags = input.tags ?? [];
9044
9601
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
9045
9602
  if (!isAuthed)
9046
9603
  return NO_DETECTION;
9047
- const meta = [input.testName, input.description].filter(Boolean).join(" ");
9048
- if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
9604
+ if (isIntentionalNegativeAuthTest(input))
9049
9605
  return NO_DETECTION;
9050
9606
  const sources = [];
9051
9607
  if (input.error)
@@ -9148,7 +9704,24 @@ var init_auth_block_detector = __esm({
9148
9704
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
9149
9705
  ];
9150
9706
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
9151
- 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;
9707
+ 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;
9708
+ }
9709
+ });
9710
+
9711
+ // ../shared/dist/auth-session-stamp.js
9712
+ function parseAuthSessionStamp(value) {
9713
+ if (!value || typeof value !== "object" || Array.isArray(value))
9714
+ return null;
9715
+ const { shared, role, generation } = value;
9716
+ if (shared !== true || role !== "producer" && role !== "follower")
9717
+ return null;
9718
+ return typeof generation === "string" && AUTH_SESSION_GENERATION_RE.test(generation) ? { shared: true, role, generation } : { shared: true, role };
9719
+ }
9720
+ var AUTH_SESSION_GENERATION_RE;
9721
+ var init_auth_session_stamp = __esm({
9722
+ "../shared/dist/auth-session-stamp.js"() {
9723
+ "use strict";
9724
+ AUTH_SESSION_GENERATION_RE = /^[0-9a-z]{1,16}$/;
9152
9725
  }
9153
9726
  });
9154
9727
 
@@ -10072,12 +10645,70 @@ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
10072
10645
  function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
10073
10646
  return value?.trim().toLowerCase() !== "false";
10074
10647
  }
10648
+ function visualBaselinesEnabled(projectId, raw = process.env.VISUAL_BASELINES_PROJECTS) {
10649
+ if (typeof projectId !== "string" || projectId.trim() === "")
10650
+ return false;
10651
+ const listed = (raw ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
10652
+ return listed.includes("*") || listed.includes(projectId);
10653
+ }
10075
10654
  var init_feature_flags = __esm({
10076
10655
  "../shared/dist/feature-flags.js"() {
10077
10656
  "use strict";
10078
10657
  }
10079
10658
  });
10080
10659
 
10660
+ // ../shared/dist/visual-compare-limits.js
10661
+ function isSafeMaskSelector(selector) {
10662
+ if (typeof selector !== "string")
10663
+ return false;
10664
+ if (selector.trim().length === 0)
10665
+ return false;
10666
+ if (selector.length > VISUAL_MASK_SELECTOR_MAX_LENGTH)
10667
+ return false;
10668
+ return !VISUAL_MASK_SELECTOR_FORBIDDEN.test(selector);
10669
+ }
10670
+ var VISUAL_MASK_SELECTORS_MAX, VISUAL_MASK_SELECTOR_MAX_LENGTH, VISUAL_MASK_SELECTOR_FORBIDDEN;
10671
+ var init_visual_compare_limits = __esm({
10672
+ "../shared/dist/visual-compare-limits.js"() {
10673
+ "use strict";
10674
+ VISUAL_MASK_SELECTORS_MAX = 50;
10675
+ VISUAL_MASK_SELECTOR_MAX_LENGTH = 300;
10676
+ VISUAL_MASK_SELECTOR_FORBIDDEN = /[{};@<\r\n]/;
10677
+ }
10678
+ });
10679
+
10680
+ // ../shared/dist/visual-baseline-identity.js
10681
+ function isBrowserVersion(value) {
10682
+ return typeof value === "string" && value.length <= 32 && BROWSER_VERSION.test(value);
10683
+ }
10684
+ function renderIdentity(flavour, version, image) {
10685
+ if (!/^[a-z][a-z-]{0,31}$/.test(flavour) || !/^[a-z][a-z-]{0,31}$/.test(image) || !isBrowserVersion(version))
10686
+ return null;
10687
+ const identity = `${flavour}/${version}/${image}`;
10688
+ return identity.length <= RENDER_IDENTITY_MAX_LENGTH ? identity : null;
10689
+ }
10690
+ function v1RenderIdentity(version) {
10691
+ return renderIdentity(VISUAL_V1_IDENTITY.flavour, version, VISUAL_V1_IDENTITY.image);
10692
+ }
10693
+ function parseRenderIdentity(value) {
10694
+ if (typeof value !== "string" || value.length > RENDER_IDENTITY_MAX_LENGTH)
10695
+ return null;
10696
+ const parts = value.split("/");
10697
+ if (parts.length !== 3)
10698
+ return null;
10699
+ const [flavour, version, image] = parts;
10700
+ return renderIdentity(flavour, version, image) === value ? { flavour, version, image } : null;
10701
+ }
10702
+ var VISUAL_V1_IDENTITY, RENDER_IDENTITY_MAX_LENGTH, BROWSER_VERSION;
10703
+ var init_visual_baseline_identity = __esm({
10704
+ "../shared/dist/visual-baseline-identity.js"() {
10705
+ "use strict";
10706
+ VISUAL_V1_IDENTITY = { platform: "linux", image: "runner-fast", flavour: "headless-shell" };
10707
+ RENDER_IDENTITY_MAX_LENGTH = 64;
10708
+ BROWSER_VERSION = /^\d{1,4}(\.\d{1,6}){0,3}$/;
10709
+ }
10710
+ });
10711
+
10081
10712
  // ../shared/dist/analytics-url.js
10082
10713
  function isSensitiveQueryKey(key) {
10083
10714
  return SENSITIVE_QUERY_KEY_RE.test(key);
@@ -10380,7 +11011,7 @@ var init_agentic_cli_runtime = __esm({
10380
11011
  GROK_CLI_DEFAULT_MODEL = "grok-4.7";
10381
11012
  GROK_CLI_DEFAULT_REASONING_EFFORT = "high";
10382
11013
  CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
10383
- CLAUDE_CLI_DEFAULT_EFFORT = "max";
11014
+ CLAUDE_CLI_DEFAULT_EFFORT = "high";
10384
11015
  MUSE_CLI_DEFAULT_MODEL = "muse-spark-1.3-contributor";
10385
11016
  MUSE_CLI_DEFAULT_EFFORT = "high";
10386
11017
  CODEX_CLI_DEFAULT_MODEL = "gpt-6.1-sol";
@@ -10435,6 +11066,17 @@ function openCodeGoWireModel(id) {
10435
11066
  const trimmed = id?.trim() ?? "";
10436
11067
  return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
10437
11068
  }
11069
+ function isZaiModelId(id) {
11070
+ const trimmed = id?.trim() ?? "";
11071
+ return ZAI_MODEL_PATTERN.test(trimmed) && !trimmed.includes("..");
11072
+ }
11073
+ function hasZaiPrefix(id) {
11074
+ return (id?.trim() ?? "").startsWith(ZAI_PREFIX);
11075
+ }
11076
+ function zaiWireModel(id) {
11077
+ const trimmed = id?.trim() ?? "";
11078
+ return isZaiModelId(trimmed) ? trimmed.slice(ZAI_PREFIX.length) : null;
11079
+ }
10438
11080
  function isEngineId(value) {
10439
11081
  return CLI_ENGINE_MODEL_IDS.includes(value);
10440
11082
  }
@@ -10531,7 +11173,7 @@ function museCliContributorMode(env = process.env) {
10531
11173
  const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
10532
11174
  return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
10533
11175
  }
10534
- 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;
11176
+ 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;
10535
11177
  var init_engine_spec = __esm({
10536
11178
  "../shared/dist/engine-spec.js"() {
10537
11179
  "use strict";
@@ -10591,6 +11233,8 @@ var init_engine_spec = __esm({
10591
11233
  OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
10592
11234
  OPENCODE_GO_PREFIX = "opencode-go:";
10593
11235
  OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
11236
+ ZAI_PREFIX = "zai:";
11237
+ ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
10594
11238
  }
10595
11239
  });
10596
11240
 
@@ -10875,6 +11519,8 @@ var init_api_model_catalog = __esm({
10875
11519
  authCaptureModel: "Login capture",
10876
11520
  plannerModel: "Scenario Planner",
10877
11521
  plannerGateModel: "Planner Gate",
11522
+ pwAgentsHealerModel: "Test Healer (Playwright agents)",
11523
+ pwAgentsReviewModel: "Plan Review (Playwright agents)",
10878
11524
  generationModel: "Code Generation",
10879
11525
  reviewModel: "Code Review",
10880
11526
  apiTestModel: "API Test Generator",
@@ -10899,11 +11545,6 @@ var init_api_model_catalog = __esm({
10899
11545
  });
10900
11546
 
10901
11547
  // ../shared/dist/provider-chains.js
10902
- function isClaudeModel(id) {
10903
- if (/claude|anthropic/i.test(id))
10904
- return true;
10905
- return API_MODEL_CATALOG.some((m) => m.provider === "anthropic" && (m.id === id || m.apiModel === id));
10906
- }
10907
11548
  function chainKey(modelId) {
10908
11549
  const id = canonicalModelId(modelId);
10909
11550
  if (!id)
@@ -10911,35 +11552,334 @@ function chainKey(modelId) {
10911
11552
  const entry = API_MODEL_CATALOG.find((m) => m.id === id || m.apiModel === id);
10912
11553
  return entry?.id ?? id;
10913
11554
  }
11555
+ function has(map2, key) {
11556
+ return Object.prototype.hasOwnProperty.call(map2, key);
11557
+ }
10914
11558
  function providerChainFor(modelId) {
10915
11559
  const key = chainKey(modelId);
10916
- if (!key || isClaudeModel(key))
10917
- return [];
10918
- return Object.prototype.hasOwnProperty.call(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
11560
+ if (key && has(_overrideHops, key))
11561
+ return _overrideHops[key];
11562
+ return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
11563
+ }
11564
+ function providerChainLeadsFor(modelId) {
11565
+ const key = chainKey(modelId);
11566
+ if (key && has(_overrideLeads, key))
11567
+ return _overrideLeads[key];
11568
+ return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
11569
+ }
11570
+ function allChains() {
11571
+ const ordered = (map2, override) => [...Object.entries(map2).filter(([key]) => !has(override, key)), ...Object.entries(override)];
11572
+ return [...ordered(PROVIDER_CHAINS, _overrideHops), ...ordered(PROVIDER_CHAIN_LEADS, _overrideLeads)];
10919
11573
  }
10920
11574
  function isProviderChainHopId(modelId) {
10921
11575
  const id = modelId ?? "";
10922
- return id !== "" && Object.values(PROVIDER_CHAINS).some((hops) => hops.includes(id));
11576
+ return id !== "" && allChains().some(([, hops]) => hops.includes(id));
10923
11577
  }
10924
11578
  function providerChainPrimariesForHop(hopId) {
10925
11579
  const id = hopId ?? "";
10926
11580
  if (!id)
10927
11581
  return [];
10928
- return Object.entries(PROVIDER_CHAINS).filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
11582
+ return allChains().filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
11583
+ }
11584
+ function providerChainHostOf(value, modelValue) {
11585
+ if (value === modelValue)
11586
+ return "own";
11587
+ if (hasZaiPrefix(value))
11588
+ return "zai";
11589
+ if (hasOpenCodeGoPrefix(value))
11590
+ return "opencode-go";
11591
+ if (hasOpenRouterPrefix(value))
11592
+ return "openrouter";
11593
+ return "own";
11594
+ }
11595
+ function providerChainHostProvider(value) {
11596
+ if (typeof value !== "string" || !value)
11597
+ return null;
11598
+ if (hasOpenRouterPrefix(value))
11599
+ return "openrouter";
11600
+ if (hasOpenCodeGoPrefix(value))
11601
+ return "opencode-go";
11602
+ if (hasZaiPrefix(value))
11603
+ return "zai";
11604
+ const key = chainKey(value);
11605
+ return API_MODEL_CATALOG.find((m) => m.id === key)?.provider ?? GEMINI_ASSISTANT_MODELS.find((m) => m.id === key)?.provider ?? null;
11606
+ }
11607
+ function providerHostPlan(modelId) {
11608
+ const key = chainKey(modelId);
11609
+ if (!key)
11610
+ return [];
11611
+ const own = { host: hasOpenRouterPrefix(key) ? "openrouter" : "own", value: key };
11612
+ const steps = [
11613
+ ...providerChainLeadsFor(key).map((value) => ({ host: providerChainHostOf(value, key), value })),
11614
+ own,
11615
+ ...providerChainFor(key).map((value) => ({ host: providerChainHostOf(value, key), value }))
11616
+ ];
11617
+ const notOffered = Object.prototype.hasOwnProperty.call(PROVIDER_HOST_NOT_OFFERED, key) ? PROVIDER_HOST_NOT_OFFERED[key] : {};
11618
+ for (const [host, reason] of Object.entries(notOffered)) {
11619
+ const at = steps.findIndex((step) => step === own) + 1;
11620
+ steps.splice(at, 0, { host, value: null, notOffered: reason });
11621
+ }
11622
+ return steps;
11623
+ }
11624
+ function providerChainModelKey(modelId) {
11625
+ return chainKey(modelId);
11626
+ }
11627
+ function hasProviderChainLeadsByDefault(modelId) {
11628
+ const key = chainKey(modelId);
11629
+ return key !== null && has(PROVIDER_CHAIN_LEADS, key);
11630
+ }
11631
+ function isChainablePrimary(id) {
11632
+ return typeof id === "string" && id !== "" && (has(PROVIDER_CHAINS, id) || has(PROVIDER_CHAIN_LEADS, id));
11633
+ }
11634
+ function defaultProviderChainEntries(primary) {
11635
+ if (!isChainablePrimary(primary))
11636
+ return [];
11637
+ const leads = has(PROVIDER_CHAIN_LEADS, primary) ? PROVIDER_CHAIN_LEADS[primary] : [];
11638
+ const hops = has(PROVIDER_CHAINS, primary) ? PROVIDER_CHAINS[primary] : [];
11639
+ return [...leads, primary, ...hops].map((hop) => ({ hop, enabled: true }));
11640
+ }
11641
+ function allowedProviderChainHosts(primary) {
11642
+ return defaultProviderChainEntries(primary).map((entry) => entry.hop);
11643
+ }
11644
+ function isPlainObject2(value) {
11645
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
11646
+ }
11647
+ function validateProviderChainEntries(primary, entries) {
11648
+ if (!isChainablePrimary(primary))
11649
+ return `${String(primary).slice(0, 80)} has no provider chain to order`;
11650
+ if (!Array.isArray(entries))
11651
+ return `${primary}: the chain must be a list`;
11652
+ if (entries.length > PROVIDER_CHAIN_MAX_ENTRIES)
11653
+ return `${primary}: at most ${PROVIDER_CHAIN_MAX_ENTRIES} hosts`;
11654
+ const allowed = allowedProviderChainHosts(primary);
11655
+ const seen = /* @__PURE__ */ new Set();
11656
+ for (const entry of entries) {
11657
+ if (!isPlainObject2(entry))
11658
+ return `${primary}: each host must be { hop, enabled }`;
11659
+ const keys = Object.keys(entry);
11660
+ if (keys.length !== 2 || !keys.includes("hop") || !keys.includes("enabled"))
11661
+ return `${primary}: each host must be { hop, enabled } and nothing else`;
11662
+ const { hop, enabled } = entry;
11663
+ if (typeof enabled !== "boolean")
11664
+ return `${primary}: enabled must be true or false`;
11665
+ if (typeof hop !== "string" || !hop)
11666
+ return `${primary}: hop must be a model value`;
11667
+ if (!allowed.includes(hop))
11668
+ return `${primary}: ${hop.slice(0, 80)} is not a host of this model`;
11669
+ if (seen.has(hop))
11670
+ return `${primary}: ${hop} is listed twice`;
11671
+ if (hop === primary && !enabled)
11672
+ return `${primary}: the model's own value stays on (pick another model on AI Models instead)`;
11673
+ seen.add(hop);
11674
+ }
11675
+ if (!seen.has(primary))
11676
+ return `${primary}: list the model's own value once, where its own host sits`;
11677
+ return null;
11678
+ }
11679
+ function decodeProviderChains(raw) {
11680
+ let value = raw;
11681
+ if (typeof raw === "string") {
11682
+ if (raw.length > PROVIDER_CHAINS_SETTING_MAX_CHARS)
11683
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
11684
+ try {
11685
+ value = JSON.parse(raw);
11686
+ } catch {
11687
+ return { ok: false, error: "AI_PROVIDER_CHAINS must be valid JSON" };
11688
+ }
11689
+ } else if (JSON.stringify(raw ?? null).length > PROVIDER_CHAINS_SETTING_MAX_CHARS) {
11690
+ return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
11691
+ }
11692
+ if (!isPlainObject2(value))
11693
+ return { ok: false, error: 'AI_PROVIDER_CHAINS must be an object: { "<model>": [ { "hop": "<model value>", "enabled": true } ] }' };
11694
+ if (Object.keys(value).length > PROVIDER_CHAIN_MAX_PRIMARIES)
11695
+ return { ok: false, error: `AI_PROVIDER_CHAINS names at most ${PROVIDER_CHAIN_MAX_PRIMARIES} models` };
11696
+ return { ok: true, value };
11697
+ }
11698
+ function checkedProviderChains(value) {
11699
+ const out = {};
11700
+ for (const [primary, entries] of Object.entries(value)) {
11701
+ const problem = validateProviderChainEntries(primary, entries);
11702
+ if (problem)
11703
+ return { ok: false, error: problem };
11704
+ out[primary] = entries.map(({ hop, enabled }) => ({ hop, enabled }));
11705
+ }
11706
+ return { ok: true, value: out };
11707
+ }
11708
+ function parseProviderChainOverrides(raw) {
11709
+ const decoded = decodeProviderChains(raw);
11710
+ return decoded.ok ? checkedProviderChains(decoded.value) : decoded;
11711
+ }
11712
+ function parseStoredProviderChainOverrides(raw) {
11713
+ const decoded = decodeProviderChains(raw);
11714
+ if (!decoded.ok)
11715
+ return decoded;
11716
+ const dropped = [];
11717
+ const kept = {};
11718
+ for (const [primary, entries] of Object.entries(decoded.value)) {
11719
+ if (!isChainablePrimary(primary) || !Array.isArray(entries)) {
11720
+ kept[primary] = entries;
11721
+ continue;
11722
+ }
11723
+ const allowed = allowedProviderChainHosts(primary);
11724
+ kept[primary] = entries.filter((entry) => {
11725
+ const hop = isPlainObject2(entry) ? entry.hop : void 0;
11726
+ if (typeof hop !== "string" || hop === "" || allowed.includes(hop))
11727
+ return true;
11728
+ dropped.push({ primary, hop: hop.slice(0, 80) });
11729
+ return false;
11730
+ });
11731
+ }
11732
+ const checked = checkedProviderChains(kept);
11733
+ return checked.ok ? { ...checked, dropped } : checked;
11734
+ }
11735
+ function validateProviderChainsSetting(raw) {
11736
+ const parsed = parseProviderChainOverrides(raw);
11737
+ return parsed.ok ? null : parsed.error;
11738
+ }
11739
+ function setProviderChainOverrides(value, onDropped) {
11740
+ const parsed = value === null || value === void 0 || value === "" ? null : parseStoredProviderChainOverrides(value);
11741
+ if (parsed?.ok && parsed.dropped.length > 0)
11742
+ onDropped?.(parsed.dropped);
11743
+ _overrides = parsed?.ok ? parsed.value : null;
11744
+ _overrideLeads = {};
11745
+ _overrideHops = {};
11746
+ for (const [primary, entries] of Object.entries(_overrides ?? {})) {
11747
+ const at = entries.findIndex((entry) => entry.hop === primary);
11748
+ const on = (list) => list.filter((entry) => entry.enabled).map((entry) => entry.hop);
11749
+ _overrideLeads[primary] = on(entries.slice(0, at));
11750
+ _overrideHops[primary] = on(entries.slice(at + 1));
11751
+ }
11752
+ return parsed === null || parsed.ok;
11753
+ }
11754
+ function getProviderChainOverrides() {
11755
+ return _overrides;
10929
11756
  }
10930
- var PROVIDER_CHAINS;
11757
+ 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;
10931
11758
  var init_provider_chains = __esm({
10932
11759
  "../shared/dist/provider-chains.js"() {
10933
11760
  "use strict";
10934
11761
  init_api_model_catalog();
11762
+ init_engine_spec();
10935
11763
  PROVIDER_CHAINS = {
10936
- "minimax-m3": ["openrouter:minimax/minimax-m3", "opencode-go:minimax-m3"],
10937
- "minimax-m3.1-flash": ["openrouter:minimax/minimax-m3.1-flash", "opencode-go:minimax-m3.1-flash"],
11764
+ "minimax-m3": ["opencode-go:minimax-m3", "openrouter:minimax/minimax-m3"],
11765
+ "minimax-m3.1-flash": ["opencode-go:minimax-m3.1-flash", "openrouter:minimax/minimax-m3.1-flash"],
10938
11766
  "muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
10939
- "openrouter:z-ai/glm-5.3-flash": ["opencode-go:glm-5.3-flash"],
10940
- "openrouter:z-ai/glm-5.3": ["opencode-go:glm-5.3"],
10941
- "openrouter:z-ai/glm-5.2": ["opencode-go:glm-5.2"]
11767
+ "claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
11768
+ "gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
11769
+ "deepseek-flash": ["opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
11770
+ "deepseek-v4-pro": ["opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
11771
+ };
11772
+ PROVIDER_CHAIN_LEADS = {
11773
+ "openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
11774
+ "openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go:glm-5.3"],
11775
+ "openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go:glm-5.2"]
11776
+ };
11777
+ PROVIDER_HOST_NOT_OFFERED = {
11778
+ "muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
11779
+ "gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
10942
11780
  };
11781
+ PROVIDER_CHAIN_MAX_ENTRIES = 8;
11782
+ PROVIDER_CHAINS_SETTING_MAX_CHARS = 16e3;
11783
+ PROVIDER_CHAIN_MAX_PRIMARIES = 32;
11784
+ ALLOWED_PROVIDER_CHAIN_HOPS = /* @__PURE__ */ new Set([
11785
+ ...Object.values(PROVIDER_CHAINS).flat(),
11786
+ ...Object.values(PROVIDER_CHAIN_LEADS).flat()
11787
+ ]);
11788
+ _overrides = null;
11789
+ _overrideLeads = {};
11790
+ _overrideHops = {};
11791
+ }
11792
+ });
11793
+
11794
+ // ../shared/dist/provider-accounts.js
11795
+ function modelNames(modelId) {
11796
+ const value = modelId.trim();
11797
+ const names = /* @__PURE__ */ new Set([value]);
11798
+ for (const prefix of HOST_PREFIXES) {
11799
+ if (!value.startsWith(prefix))
11800
+ continue;
11801
+ const bare = value.slice(prefix.length);
11802
+ names.add(bare);
11803
+ const slash = bare.lastIndexOf("/");
11804
+ if (slash >= 0)
11805
+ names.add(bare.slice(slash + 1));
11806
+ }
11807
+ const canonical = canonicalModelId(value);
11808
+ if (canonical) {
11809
+ names.add(canonical);
11810
+ const entry = API_MODEL_CATALOG.find((m) => m.id === canonical || m.apiModel === canonical);
11811
+ if (entry) {
11812
+ names.add(entry.id);
11813
+ if (entry.apiModel)
11814
+ names.add(entry.apiModel);
11815
+ }
11816
+ }
11817
+ return names;
11818
+ }
11819
+ function accountServesModel(models, modelId) {
11820
+ if (!models || models.length === 0)
11821
+ return true;
11822
+ if (!modelId)
11823
+ return false;
11824
+ const names = modelNames(modelId);
11825
+ return models.some((model) => names.has(model.trim()));
11826
+ }
11827
+ function normalizeProviderAccountModels(models) {
11828
+ if (!Array.isArray(models))
11829
+ return null;
11830
+ const out = [];
11831
+ for (const model of models) {
11832
+ if (typeof model !== "string")
11833
+ return null;
11834
+ const trimmed = model.trim();
11835
+ if (!PROVIDER_ACCOUNT_MODEL_PATTERN.test(trimmed) || trimmed.includes(".."))
11836
+ return null;
11837
+ if (!out.includes(trimmed))
11838
+ out.push(trimmed);
11839
+ }
11840
+ return out.length > MAX_PROVIDER_ACCOUNT_MODELS ? null : out;
11841
+ }
11842
+ function parseProviderAccountLists(value, providers) {
11843
+ if (!value || typeof value !== "object" || Array.isArray(value))
11844
+ return null;
11845
+ const parsed = {};
11846
+ for (const provider of providers) {
11847
+ const list = value[provider];
11848
+ if (!Array.isArray(list))
11849
+ continue;
11850
+ const accounts = [];
11851
+ for (const raw of list) {
11852
+ if (!raw || typeof raw !== "object")
11853
+ continue;
11854
+ const { id, label, key, models } = raw;
11855
+ if (typeof id !== "string" || !id.trim() || typeof key !== "string" || !key.trim())
11856
+ continue;
11857
+ const modelList = Array.isArray(models) ? models.filter((m) => typeof m === "string" && m.trim() !== "").map((m) => m.trim()) : [];
11858
+ accounts.push({ id: id.trim(), label: typeof label === "string" && label.trim() ? label.trim() : null, key: key.trim(), models: modelList });
11859
+ }
11860
+ if (accounts.length > 0)
11861
+ parsed[provider] = accounts;
11862
+ }
11863
+ return parsed;
11864
+ }
11865
+ 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;
11866
+ var init_provider_accounts = __esm({
11867
+ "../shared/dist/provider-accounts.js"() {
11868
+ "use strict";
11869
+ init_api_model_catalog();
11870
+ DEFAULT_PROVIDER_ACCOUNT_ID = "primary";
11871
+ PROVIDER_ACCOUNT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
11872
+ MAX_PROVIDER_ACCOUNTS = 10;
11873
+ MAX_PROVIDER_ACCOUNT_MODELS = 50;
11874
+ PROVIDER_ACCOUNT_MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,120}$/;
11875
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS = [
11876
+ "deepseek-v4.1-flash",
11877
+ "deepseek-v4-pro",
11878
+ "glm-5.3-flash",
11879
+ "glm-5.3",
11880
+ "glm-5.2"
11881
+ ];
11882
+ HOST_PREFIXES = ["opencode-go:", "zai:", "openrouter:"];
10943
11883
  }
10944
11884
  });
10945
11885
 
@@ -11058,6 +11998,25 @@ var init_project_engine_overrides = __esm({
11058
11998
  }
11059
11999
  });
11060
12000
 
12001
+ // ../shared/dist/trailing-slashes.js
12002
+ function stripTrailingSlashes(value) {
12003
+ let end = value.length;
12004
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
12005
+ end--;
12006
+ return end === value.length ? value : value.slice(0, end);
12007
+ }
12008
+ function stripTrailingChars(value, isTrailing) {
12009
+ let end = value.length;
12010
+ while (end > 0 && isTrailing.test(value[end - 1]))
12011
+ end--;
12012
+ return end === value.length ? value : value.slice(0, end);
12013
+ }
12014
+ var init_trailing_slashes = __esm({
12015
+ "../shared/dist/trailing-slashes.js"() {
12016
+ "use strict";
12017
+ }
12018
+ });
12019
+
11061
12020
  // ../shared/dist/gemini-visual.js
11062
12021
  function visualProDailyBudget(raw) {
11063
12022
  const parsed = raw && /^\s*\d{1,6}\s*$/.test(raw) ? Number(raw.trim()) : NaN;
@@ -11071,7 +12030,7 @@ function isRetiredGeminiModel(id) {
11071
12030
  }
11072
12031
  function geminiNativeModelsUrl(base2 = process.env.GEMINI_API_BASE) {
11073
12032
  const raw = base2 && base2.trim() || GEMINI_OPENAI_BASE;
11074
- const native = raw.replace(/\/openai\/?$/, "/").replace(/\/+$/, "");
12033
+ const native = stripTrailingSlashes(raw.replace(/\/openai\/?$/, "/"));
11075
12034
  return `${native}/models?pageSize=1000`;
11076
12035
  }
11077
12036
  function asRecord(value) {
@@ -11191,6 +12150,7 @@ var KNOWN_INPUT_USD_PER_M, GEMINI_OPENAI_BASE, DEFAULT_GEMINI_SCREEN_MODEL, DEFA
11191
12150
  var init_gemini_visual = __esm({
11192
12151
  "../shared/dist/gemini-visual.js"() {
11193
12152
  "use strict";
12153
+ init_trailing_slashes();
11194
12154
  KNOWN_INPUT_USD_PER_M = {
11195
12155
  "gemini-2.5-flash-lite": 0.1,
11196
12156
  "gemini-3.1-flash-lite": 0.25,
@@ -12554,12 +13514,13 @@ function truncateAtWord(text, max) {
12554
13514
  const slice = trimmed.slice(0, room + 1);
12555
13515
  const lastSpace = slice.lastIndexOf(" ");
12556
13516
  const cut = lastSpace > room * 0.5 ? slice.slice(0, lastSpace) : trimmed.slice(0, room);
12557
- return `${cut.replace(/[\s,;:.\-–—]+$/u, "")}\u2026`;
13517
+ return `${stripTrailingChars(cut, TRAILING_PUNCTUATION)}\u2026`;
12558
13518
  }
12559
- 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;
13519
+ 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;
12560
13520
  var init_canvas_support = __esm({
12561
13521
  "../shared/dist/canvas-support.js"() {
12562
13522
  "use strict";
13523
+ init_trailing_slashes();
12563
13524
  CANVAS_UNSUPPORTED_CODE = "canvas_unsupported";
12564
13525
  CANVAS_UNSUPPORTED_TITLE = "Games and canvas apps aren't supported yet";
12565
13526
  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.";
@@ -12684,6 +13645,7 @@ var init_canvas_support = __esm({
12684
13645
  TOUCH_NONE_RE = /touch-action\s{0,8}:\s{0,8}none/;
12685
13646
  CODE_TAGS = /* @__PURE__ */ new Set(["script", "link", "flt-glass-pane", "flutter-view", "flt-scene-host"]);
12686
13647
  STATIC_STANDARD_MIN_INTERACTIVE = 3;
13648
+ TRAILING_PUNCTUATION = /[\s,;:.\-–—]/u;
12687
13649
  }
12688
13650
  });
12689
13651
 
@@ -12866,22 +13828,22 @@ function joinChainedLines(code) {
12866
13828
  const trimmed = lines[i].trimStart();
12867
13829
  if (last !== -1 && trimmed.startsWith(".") && !trimmed.startsWith("...")) {
12868
13830
  if (!lastStripped) {
12869
- lines[last] = stripLineComment2(lines[last]).trimEnd();
13831
+ lines[last] = stripLineComment3(lines[last]).trimEnd();
12870
13832
  lastStripped = true;
12871
13833
  }
12872
13834
  if (lines[last].length <= MAX_LINE_CHARS)
12873
- lines[last] = `${lines[last]} ${stripLineComment2(trimmed).trimEnd()}`;
13835
+ lines[last] = `${lines[last]} ${stripLineComment3(trimmed).trimEnd()}`;
12874
13836
  lines[i] = "";
12875
13837
  continue;
12876
13838
  }
12877
- if (stripLineComment2(lines[i]).trim()) {
13839
+ if (stripLineComment3(lines[i]).trim()) {
12878
13840
  last = i;
12879
13841
  lastStripped = false;
12880
13842
  }
12881
13843
  }
12882
13844
  return lines.join("\n");
12883
13845
  }
12884
- function stripLineComment2(rawLine) {
13846
+ function stripLineComment3(rawLine) {
12885
13847
  if (rawLine.trimStart().startsWith("//"))
12886
13848
  return "";
12887
13849
  const at = rawLine.indexOf(" // ");
@@ -12897,7 +13859,7 @@ function stateLiteralsOf(line) {
12897
13859
  const out = [];
12898
13860
  for (const m of line.matchAll(STRING_LITERAL_RE)) {
12899
13861
  const text = m[1] ?? m[2] ?? m[3] ?? "";
12900
- if (text.includes("placeholder="))
13862
+ if (PLACEHOLDER_ATTR_RE.test(text))
12901
13863
  continue;
12902
13864
  if (PLACEHOLDER_CALL_TAIL_RE.test(line.slice(Math.max(0, m.index - 40), m.index)))
12903
13865
  continue;
@@ -12930,7 +13892,7 @@ function analyzeReadinessPolling(rawCode) {
12930
13892
  offset += rawLine.length + 1;
12931
13893
  if (rawLine.length > MAX_LINE_CHARS)
12932
13894
  continue;
12933
- const line = stripLineComment2(rawLine);
13895
+ const line = stripLineComment3(rawLine);
12934
13896
  if (!line.trim())
12935
13897
  continue;
12936
13898
  const literals = literalsOf(line);
@@ -12993,7 +13955,7 @@ function formatReadinessPollAdvisory(findings) {
12993
13955
  const where = findings.length === 1 ? "One place" : `${findings.length} places`;
12994
13956
  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}`;
12995
13957
  }
12996
- 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;
13958
+ 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;
12997
13959
  var init_readiness_poll = __esm({
12998
13960
  "../shared/dist/readiness-poll.js"() {
12999
13961
  "use strict";
@@ -13006,7 +13968,7 @@ var init_readiness_poll = __esm({
13006
13968
  STRING_LITERAL_RE = /'((?:\\.|[^'\\\n]){0,300})'|"((?:\\.|[^"\\\n]){0,300})"|`((?:\\.|[^`\\\n]){0,300})`/g;
13007
13969
  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;
13008
13970
  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;
13009
- 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;
13971
+ 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;
13010
13972
  LOCATOR_LINE_RE = /\.(?:getBy[A-Z]\w{0,30}|locator|toHaveText|toContainText)\s{0,20}\(/;
13011
13973
  CLICK_RE = /\.(?:click|dblclick|tap)\s{0,20}\(/;
13012
13974
  RELOAD_RE = /\bpage\.reload\s{0,20}\(/;
@@ -13020,6 +13982,7 @@ var init_readiness_poll = __esm({
13020
13982
  TEST_END_RE = /^\s{0,20}\}\s{0,20}\)\s{0,20};?\s{0,20}$/;
13021
13983
  TEST_START_RE = /\btest(?:\.\w{1,20})?\s{0,20}\(/;
13022
13984
  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;
13985
+ PLACEHOLDER_ATTR_RE = /placeholder\s{0,4}[*^$~|]?=/;
13023
13986
  PLACEHOLDER_CALL_TAIL_RE = /getByPlaceholder\s{0,20}\(\s{0,20}$/;
13024
13987
  POLL_SHAPE = "await expect(async () => { await page.reload(); await expect(<the finished state>).toBeVisible({ timeout: 1500 }); }).toPass({ timeout: 20000 });";
13025
13988
  }
@@ -13041,7 +14004,7 @@ function blankBlockComments2(code) {
13041
14004
  from = close + 2;
13042
14005
  }
13043
14006
  }
13044
- function stripLineComment3(rawLine) {
14007
+ function stripLineComment4(rawLine) {
13045
14008
  if (rawLine.trimStart().startsWith("//"))
13046
14009
  return "";
13047
14010
  const at = rawLine.indexOf(" // ");
@@ -13052,7 +14015,7 @@ function logicalLines(code) {
13052
14015
  for (const raw of code.split("\n")) {
13053
14016
  if (raw.length > MAX_LINE_CHARS2)
13054
14017
  continue;
13055
- const line = stripLineComment3(raw).trimEnd();
14018
+ const line = stripLineComment4(raw).trimEnd();
13056
14019
  if (!line.trim())
13057
14020
  continue;
13058
14021
  const trimmed = line.trimStart();
@@ -13414,13 +14377,33 @@ function short4(text, max) {
13414
14377
  const flat = text.replace(/\s+/g, " ").trim();
13415
14378
  return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
13416
14379
  }
14380
+ function stripFractionZeros(s) {
14381
+ let end = s.length;
14382
+ while (end > 0 && s[end - 1] === "0")
14383
+ end--;
14384
+ if (end === s.length)
14385
+ return s;
14386
+ const dot = s.lastIndexOf(".");
14387
+ if (dot < 0)
14388
+ return s;
14389
+ return s.slice(0, end === dot + 1 ? dot : end);
14390
+ }
13417
14391
  function digitsOf(token) {
13418
- return token.replace(/[^\d.]/g, "").replace(/\.0+$|(\.\d*?)0+$/, "$1").replace(/^0+(?=\d)/, "");
14392
+ return stripFractionZeros(token.replace(/[^\d.]/g, "")).replace(/^0+(?=\d)/, "");
13419
14393
  }
13420
14394
  function computes(line) {
13421
14395
  const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
13422
14396
  return COMPUTE_RE.test(code) || RELATIONAL_RE.test(line);
13423
14397
  }
14398
+ function identifiersIn(line) {
14399
+ const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX");
14400
+ return [code, ...[...line.matchAll(TEMPLATE_EXPR_RE)].map((m) => m[1])].flatMap((part) => part.match(IDENTIFIER_RE) ?? []);
14401
+ }
14402
+ function addTyped(typed, value) {
14403
+ const d = digitsOf(value);
14404
+ if (/\d/.test(d))
14405
+ typed.add(d);
14406
+ }
13424
14407
  function reasonFor4(kind, label, value) {
13425
14408
  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.`;
13426
14409
  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.`;
@@ -13432,25 +14415,32 @@ function analyzeDerivedValues(rawCode) {
13432
14415
  for (const body of testBodies(rawCode)) {
13433
14416
  if (findings.length >= MAX_FINDINGS4)
13434
14417
  break;
13435
- if (body.some(computes))
13436
- continue;
13437
14418
  const typed = /* @__PURE__ */ new Set();
13438
14419
  const definitions = /* @__PURE__ */ new Map();
14420
+ const computed = /* @__PURE__ */ new Set();
14421
+ const usesComputed = (text) => identifiersIn(text).some((id) => computed.has(id));
13439
14422
  for (const line of body) {
13440
14423
  if (FILL_RE2.test(line) && !EXPECT_RE3.test(line)) {
13441
- for (const m of line.matchAll(/['"`]([^'"`\n]{0,40})['"`]/g)) {
13442
- const d = digitsOf(m[1]);
13443
- if (/\d/.test(d))
13444
- typed.add(d);
13445
- }
14424
+ for (const m of line.matchAll(QUOTED_RE))
14425
+ addTyped(typed, m[1]);
14426
+ const arg = FILL_ARG_RE.exec(line)?.[1];
14427
+ const literal = arg === void 0 ? null : LITERAL_VALUE_RE.exec(definitions.get(arg) ?? "");
14428
+ const value = literal?.slice(1).find((v) => v !== void 0);
14429
+ if (value !== void 0)
14430
+ addTyped(typed, value);
13446
14431
  }
13447
14432
  const def = DEFINITION_RE.exec(line);
13448
- if (def)
14433
+ if (def) {
13449
14434
  definitions.set(def[1], def[2]);
14435
+ if (computes(def[2]) || usesComputed(def[2]))
14436
+ computed.add(def[1]);
14437
+ }
13450
14438
  }
13451
14439
  for (const line of body) {
13452
14440
  if (!EXPECT_RE3.test(line) || !VALUE_MATCHER_RE2.test(line) || /\.not\s{0,20}\./.test(line))
13453
14441
  continue;
14442
+ if (computes(line) || usesComputed(line))
14443
+ continue;
13454
14444
  const subject = EXPECT_SUBJECT_RE.exec(line)?.[1];
13455
14445
  const label = DERIVED_WORD_RE.exec(`${line} ${(subject && definitions.get(subject)) ?? ""}`)?.[0];
13456
14446
  if (!label)
@@ -13480,7 +14470,7 @@ function formatDerivedValueAdvisory(findings) {
13480
14470
  const where = findings.length === 1 ? "One computed value is" : `${findings.length} computed values are`;
13481
14471
  return `${where} checked against a copied number or only its shape (${short4(first.line, 90)}). ${first.reason}`;
13482
14472
  }
13483
- 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;
14473
+ 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;
13484
14474
  var init_derived_value_check = __esm({
13485
14475
  "../shared/dist/derived-value-check.js"() {
13486
14476
  "use strict";
@@ -13500,6 +14490,11 @@ var init_derived_value_check = __esm({
13500
14490
  DEFINITION_RE = /\b(?:const|let)\s{1,10}([\w$]{1,60})\s{0,10}=\s{0,10}([^\n]{0,300})/;
13501
14491
  EXPECT_SUBJECT_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}([\w$]{1,60})/;
13502
14492
  LITERAL_ARITH_RE = /\b\d[\d_.]{0,15}(?:\s{0,5}[*/+-]\s{0,5}\d[\d_.]{0,15}){1,8}\b/g;
14493
+ TEMPLATE_EXPR_RE = /\$\{([^{}\n]{0,200})\}/g;
14494
+ IDENTIFIER_RE = /[A-Za-z_$][\w$]{0,60}/g;
14495
+ QUOTED_RE = /['"`]([^'"`\n]{0,40})['"`]/g;
14496
+ FILL_ARG_RE = /\.(?:fill|type|pressSequentially|selectOption)\s{0,20}\(\s{0,20}([A-Za-z_$][\w$]{0,60})\s{0,20}[,)]/;
14497
+ 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}$/;
13503
14498
  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.";
13504
14499
  }
13505
14500
  });
@@ -13612,7 +14607,7 @@ function findExpectCalls(code) {
13612
14607
  if (!subj)
13613
14608
  continue;
13614
14609
  const rest = code.slice(subj.end + 1, subj.end + 1 + 80);
13615
- const mm = /^\s*((?:\.\s*not)?\s*\.\s*(\w+))\s{0,20}\(/.exec(rest);
14610
+ const mm = /^\s*((?:\.\s*not\s*)?\.\s*(\w+))\s{0,20}\(/.exec(rest);
13616
14611
  if (!mm)
13617
14612
  continue;
13618
14613
  const argStart = subj.end + 1 + mm[0].length;
@@ -15534,6 +16529,133 @@ function formatStaleAfterSubmitAdvisory(rawCode) {
15534
16529
  const more = described.length > 3 ? ` (+${described.length - 3} more)` : "";
15535
16530
  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+$/)`;
15536
16531
  }
16532
+ function regexLiteralEnd4(text, at) {
16533
+ let inClass = false;
16534
+ for (let i = at + 1; i < text.length; i++) {
16535
+ const ch = text[i];
16536
+ if (ch === "\n")
16537
+ return -1;
16538
+ if (ch === "\\") {
16539
+ i++;
16540
+ continue;
16541
+ }
16542
+ if (inClass) {
16543
+ if (ch === "]")
16544
+ inClass = false;
16545
+ continue;
16546
+ }
16547
+ if (ch === "[") {
16548
+ inClass = true;
16549
+ continue;
16550
+ }
16551
+ if (ch === "/")
16552
+ return i + 1;
16553
+ }
16554
+ return -1;
16555
+ }
16556
+ function startsWithRegexLiteral(text) {
16557
+ const t = text.trimStart();
16558
+ return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
16559
+ }
16560
+ function isBareRegexLiteral(text) {
16561
+ const t = text.trimStart();
16562
+ if (t[0] !== "/" || t[1] === "/" || t[1] === "*")
16563
+ return false;
16564
+ const end = regexLiteralEnd4(t, 0);
16565
+ if (end === -1)
16566
+ return false;
16567
+ let i = end;
16568
+ while (i < t.length && /[a-z]/.test(t[i]))
16569
+ i++;
16570
+ while (i < t.length && /\s/.test(t[i]))
16571
+ i++;
16572
+ return i >= t.length || t[i] === ",";
16573
+ }
16574
+ function regexDeclaredNames(code) {
16575
+ const names = /* @__PURE__ */ new Set();
16576
+ 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;
16577
+ let m;
16578
+ while ((m = decl.exec(code)) !== null)
16579
+ names.add(m[1]);
16580
+ return names;
16581
+ }
16582
+ function afterFirstArg(args) {
16583
+ let depth = 0;
16584
+ let quote = null;
16585
+ for (let i = 0; i < args.length; i++) {
16586
+ const ch = args[i];
16587
+ if (quote) {
16588
+ if (ch === "\\") {
16589
+ i++;
16590
+ continue;
16591
+ }
16592
+ if (ch === quote)
16593
+ quote = null;
16594
+ continue;
16595
+ }
16596
+ if (ch === '"' || ch === "'" || ch === "`") {
16597
+ quote = ch;
16598
+ continue;
16599
+ }
16600
+ if (ch === "(" || ch === "[" || ch === "{")
16601
+ depth++;
16602
+ else if (ch === ")" || ch === "]" || ch === "}")
16603
+ depth--;
16604
+ else if (ch === "," && depth === 0)
16605
+ return args.slice(i + 1);
16606
+ }
16607
+ return "";
16608
+ }
16609
+ function analyzeRegexMatchers(rawCode) {
16610
+ if (!rawCode || typeof rawCode !== "string")
16611
+ return [];
16612
+ const code = stripComments2(rawCode.slice(0, MAX_LINT_INPUT));
16613
+ const out = [];
16614
+ const re = /\bexpect(?:\.soft)?\s{0,20}\(/g;
16615
+ let m;
16616
+ let examined = 0;
16617
+ let line = 1;
16618
+ let lineAt2 = 0;
16619
+ let regexNames;
16620
+ while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
16621
+ const subj = readBalanced(code, m.index + m[0].length);
16622
+ if (!subj)
16623
+ continue;
16624
+ 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));
16625
+ if (!mm || !REGEX_EQUALITY_MATCHERS.has(mm[2]))
16626
+ continue;
16627
+ const args = readBalanced(code, subj.end + 1 + mm[0].length);
16628
+ if (!args)
16629
+ continue;
16630
+ const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
16631
+ if (!isBareRegexLiteral(expected))
16632
+ continue;
16633
+ if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain") {
16634
+ const subject = subj.text.trim();
16635
+ if (startsWithRegexLiteral(subject) || /^new\s+RegExp\b/.test(subject))
16636
+ continue;
16637
+ if (/^[A-Za-z_$][\w$]{0,80}$/.test(subject)) {
16638
+ regexNames ??= regexDeclaredNames(code);
16639
+ if (regexNames.has(subject))
16640
+ continue;
16641
+ }
16642
+ }
16643
+ for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
16644
+ line++;
16645
+ lineAt2 = i + 1;
16646
+ }
16647
+ out.push({ line, matcher: mm[2], negated: mm[1] !== void 0 });
16648
+ }
16649
+ return out;
16650
+ }
16651
+ function formatRegexMatcherNote(findings) {
16652
+ const lines = findings.map((f) => {
16653
+ 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";
16654
+ 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"}.`;
16655
+ });
16656
+ return `${lines.join("\n")}
16657
+ 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.`;
16658
+ }
15537
16659
  function lintTestQuality(rawCode, opts = {}) {
15538
16660
  if (!rawCode || typeof rawCode !== "string")
15539
16661
  return [];
@@ -15541,6 +16663,10 @@ function lintTestQuality(rawCode, opts = {}) {
15541
16663
  return lintTestData(rawCode, { pageContacts: opts.pageContacts });
15542
16664
  if (opts.only === "test-data-blocking")
15543
16665
  return analyzeTestData(rawCode, { pageContacts: opts.pageContacts }).blocking;
16666
+ if (opts.only === "regex-matcher") {
16667
+ const found = analyzeRegexMatchers(rawCode);
16668
+ return found.length > 0 ? [formatRegexMatcherNote(found)] : [];
16669
+ }
15544
16670
  const warnings = [];
15545
16671
  if (rawCode.length > MAX_LINT_INPUT) {
15546
16672
  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).`);
@@ -15815,7 +16941,7 @@ function specItemsFromTestPlans(testPlans) {
15815
16941
  }
15816
16942
  return [...new Set(out)].slice(0, 200);
15817
16943
  }
15818
- 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;
16944
+ 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;
15819
16945
  var init_test_authoring_quality = __esm({
15820
16946
  "../shared/dist/test-authoring-quality.js"() {
15821
16947
  "use strict";
@@ -16047,6 +17173,8 @@ var init_test_authoring_quality = __esm({
16047
17173
  };
16048
17174
  LIVE_WRITE_NAME = /\b(?:submit|place|book|reserve|confirm|pay|send|checkout|order|register|sign\s?up|create account)\b/i;
16049
17175
  BUNDLED_CHECKS_BEFORE_WRITE = 5;
17176
+ MAX_REGEX_MATCHER_FINDINGS = 10;
17177
+ REGEX_EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual", "toContain", "toHaveJSProperty"]);
16050
17178
  STOP = /* @__PURE__ */ new Set([
16051
17179
  "a",
16052
17180
  "an",
@@ -16285,7 +17413,7 @@ function normalizeBugPath(route) {
16285
17413
  if (hash >= 0 && path[hash + 1] !== "/")
16286
17414
  path = path.slice(0, hash);
16287
17415
  const segments = path.toLowerCase().split("/").map((segment) => /\d/.test(segment) ? ":id" : segment);
16288
- const joined = segments.join("/").replace(/\/+$/, "");
17416
+ const joined = stripTrailingSlashes(segments.join("/"));
16289
17417
  return joined || (path.startsWith("/") ? "/" : "");
16290
17418
  }
16291
17419
  function isGenericConsoleMessage(message) {
@@ -16298,14 +17426,17 @@ function requestSignatures(text) {
16298
17426
  const statuses = [...text.matchAll(HTTP_STATUS)].map((m) => ({ status: m[1], at: m.index ?? 0, end: (m.index ?? 0) + m[0].length }));
16299
17427
  if (statuses.length === 0)
16300
17428
  return [];
16301
- const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => ({
16302
- method: m[1] ?? null,
16303
- path: normalizeBugPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "")),
16304
- // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
16305
- incidental: isIncidentalAssetPath((m[2] ?? m[3] ?? "").replace(/[.,;:]+$/, "").split(/[?#]/, 1)[0].toLowerCase()),
16306
- at: m.index ?? 0,
16307
- end: (m.index ?? 0) + m[0].length
16308
- })).filter((p) => p.path.length > 1);
17429
+ const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => {
17430
+ const rawPath = stripTrailingChars(m[2] ?? m[3] ?? "", PATH_TRAILING_PUNCTUATION);
17431
+ return {
17432
+ method: m[1] ?? null,
17433
+ path: normalizeBugPath(rawPath),
17434
+ // Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
17435
+ incidental: isIncidentalAssetPath(rawPath.split(/[?#]/, 1)[0].toLowerCase()),
17436
+ at: m.index ?? 0,
17437
+ end: (m.index ?? 0) + m[0].length
17438
+ };
17439
+ }).filter((p) => p.path.length > 1);
16309
17440
  const out = /* @__PURE__ */ new Map();
16310
17441
  for (const { status, at, end } of statuses) {
16311
17442
  const lead = STATUS_TO_PATH.exec(text.slice(end, end + 40));
@@ -16415,10 +17546,12 @@ function dedupeSiteWideSuspectedBugs(drafts) {
16415
17546
  delete plans.suspectedBugs;
16416
17547
  }
16417
17548
  }
16418
- 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;
17549
+ 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;
16419
17550
  var init_survey_bug_signature = __esm({
16420
17551
  "../shared/dist/survey-bug-signature.js"() {
16421
17552
  "use strict";
17553
+ init_trailing_slashes();
17554
+ PATH_TRAILING_PUNCTUATION = /[.,;:]/;
16422
17555
  SIGNATURE_SOURCE_MAX_CHARS = 800;
16423
17556
  UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
16424
17557
  PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
@@ -17699,9 +18832,9 @@ function lastLocatorLabels(chain) {
17699
18832
  if (t)
17700
18833
  labels.push(t[1]);
17701
18834
  }
17702
- const has = optionText(last.args, HAS_TEXT_OPTION_RE);
17703
- if (has !== null)
17704
- labels.push(has);
18835
+ const has2 = optionText(last.args, HAS_TEXT_OPTION_RE);
18836
+ if (has2 !== null)
18837
+ labels.push(has2);
17705
18838
  }
17706
18839
  return labels.map((l) => l.replace(/^[\s^(?:]+/, "").trim());
17707
18840
  }
@@ -17749,7 +18882,7 @@ function pathIdsIn(text) {
17749
18882
  function findCounterIdPins(code) {
17750
18883
  if (code.length > CLOCK_MAX_CODE_CHARS)
17751
18884
  return [];
17752
- const clean2 = stripLineComments(blankBlockComments3(code));
18885
+ const clean3 = stripLineComments(blankBlockComments3(code));
17753
18886
  const fillValues = [];
17754
18887
  const candidates = [];
17755
18888
  let seenWrite = false;
@@ -17757,24 +18890,24 @@ function findCounterIdPins(code) {
17757
18890
  CALL_RE.lastIndex = 0;
17758
18891
  let m;
17759
18892
  let calls = 0;
17760
- while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean2)) !== null) {
18893
+ while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean3)) !== null) {
17761
18894
  const name = m[1];
17762
18895
  const open = m.index + m[0].length - 1;
17763
18896
  if (VALUE_WRITES.has(name)) {
17764
18897
  seenWrite = true;
17765
- const args2 = argsAt(clean2, open);
18898
+ const args2 = argsAt(clean3, open);
17766
18899
  if (args2 !== null)
17767
18900
  fillValues.push(...staticStrings(args2));
17768
18901
  continue;
17769
18902
  }
17770
18903
  if (createIndex === -1) {
17771
- if (name === "click" && seenWrite && isCreateClick(clean2, m.index))
18904
+ if (name === "click" && seenWrite && isCreateClick(clean3, m.index))
17772
18905
  createIndex = m.index;
17773
18906
  continue;
17774
18907
  }
17775
18908
  if (name === "click")
17776
18909
  continue;
17777
- const args = argsAt(clean2, open);
18910
+ const args = argsAt(clean3, open);
17778
18911
  if (args === null)
17779
18912
  continue;
17780
18913
  const isUrl = URL_SINKS.has(name);
@@ -17793,7 +18926,7 @@ function findCounterIdPins(code) {
17793
18926
  }
17794
18927
  if (createIndex === -1)
17795
18928
  return [];
17796
- const before = clean2.slice(0, createIndex);
18929
+ const before = clean3.slice(0, createIndex);
17797
18930
  let pathIdsBefore = null;
17798
18931
  const findings = [];
17799
18932
  const seen = /* @__PURE__ */ new Set();
@@ -17817,7 +18950,7 @@ function findCounterIdPins(code) {
17817
18950
  continue;
17818
18951
  }
17819
18952
  }
17820
- findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean2, c.index) });
18953
+ findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean3, c.index) });
17821
18954
  }
17822
18955
  return findings;
17823
18956
  }
@@ -18112,7 +19245,7 @@ var init_suspicious_expectation = __esm({
18112
19245
  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;
18113
19246
  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;
18114
19247
  STUCK_VETO_RE = new RegExp(`${NOT_STUCK_RE.source}|${LISTS_STUCK_RE.source}`, "i");
18115
- 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;
19248
+ 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;
18116
19249
  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)";
18117
19250
  ELLIPSIS = "(?:\\.{3}|\u2026)";
18118
19251
  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;
@@ -18268,7 +19401,7 @@ function outcomeNamedByAssertion(outcome, code) {
18268
19401
  function namedByStatements(outcome, statements) {
18269
19402
  const text = outcomeText(outcome);
18270
19403
  if (outcome.type === "url") {
18271
- const routes = (text.match(ROUTE_TERM) ?? []).map((r) => r.replace(/[.,;:!?)]+$/, "")).filter((r) => r.length > 1);
19404
+ const routes = (text.match(ROUTE_TERM) ?? []).map((r) => stripTrailingChars(r, ROUTE_TRAILING_PUNCTUATION)).filter((r) => r.length > 1);
18272
19405
  return statements.some((stmt) => {
18273
19406
  if (!/\.(?:waitForURL|toHaveURL)\s{0,20}\(/.test(stmt))
18274
19407
  return false;
@@ -18440,7 +19573,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
18440
19573
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
18441
19574
  };
18442
19575
  }
18443
- 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;
19576
+ 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;
18444
19577
  var init_promotion_quality_gate = __esm({
18445
19578
  "../shared/dist/promotion-quality-gate.js"() {
18446
19579
  "use strict";
@@ -18455,17 +19588,21 @@ var init_promotion_quality_gate = __esm({
18455
19588
  init_run_classification();
18456
19589
  init_expected_outcome_coverage();
18457
19590
  init_expect_call();
19591
+ init_trailing_slashes();
18458
19592
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
18459
19593
  strengthCheck = {
18460
19594
  id: "assertion-strength",
18461
- run({ code, strengthGate, flagContradictedAdditions }) {
18462
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
19595
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
19596
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
18463
19597
  if (verdict.severity === "route-bug")
18464
19598
  return [{ check: "route-bug" }];
18465
- if (verdict.severity !== "block")
19599
+ const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
19600
+ if (verdict.severity !== "block" && !authDowngrade)
18466
19601
  return [];
18467
19602
  const revealed = strengthGate?.severity === "none" && strengthGate.revealEvidence === "revealed" && strengthGate.source !== "heal";
18468
- return revealed ? [] : [{ check: "strength-block" }];
19603
+ if (revealed)
19604
+ return [];
19605
+ return [{ check: authDowngrade ? "strength-downgrade-auth" : "strength-block" }];
18469
19606
  }
18470
19607
  };
18471
19608
  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;
@@ -18474,7 +19611,7 @@ var init_promotion_quality_gate = __esm({
18474
19611
  MAX_STATEMENTS = 500;
18475
19612
  MAX_STATEMENT_CHARS = 400;
18476
19613
  LITERAL_OR_IDENTIFIER = /'(?:[^'\\\n]|\\.){0,500}'|"(?:[^"\\\n]|\\.){0,500}"|`(?:[^`\\]|\\.){0,2000}`|(?<![\w$.])[A-Za-z_$][\w$]{0,60}/g;
18477
- 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}\(/;
19614
+ 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}\(/;
18478
19615
  FEEDBACK_REGION = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/i;
18479
19616
  ONE_SHOT_MESSAGE_REASON = "one-shot success/error banner text";
18480
19617
  FEEDBACK_REGION_ALL = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/gi;
@@ -18533,6 +19670,7 @@ var init_promotion_quality_gate = __esm({
18533
19670
  "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}\)/
18534
19671
  };
18535
19672
  ROUTE_TERM = /\/[A-Za-z0-9_./?&=#-]{1,200}/g;
19673
+ ROUTE_TRAILING_PUNCTUATION = /[.,;:!?)]/;
18536
19674
  QUOTED_LITERAL = /'([^'\\\n]{3,200})'|"([^"\\\n]{3,200})"|`([^`\\$\n]{3,200})`/g;
18537
19675
  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;
18538
19676
  MAX_OUTCOME_STATEMENTS = 300;
@@ -18652,8 +19790,8 @@ ${goalText}`);
18652
19790
  return count3 > 0 ? [{ check: "locator-fragment", detail: String(count3), soft: true }] : [];
18653
19791
  }
18654
19792
  };
18655
- 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;
18656
- NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s*\(/;
19793
+ 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;
19794
+ NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s*\(/;
18657
19795
  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;
18658
19796
  MAX_FILTER_LINES = 2e3;
18659
19797
  MAX_FILTER_LINE_CHARS = 600;
@@ -18768,7 +19906,7 @@ ${goalText}`);
18768
19906
  });
18769
19907
 
18770
19908
  // ../shared/dist/regex-alternation.js
18771
- function closingParens(source) {
19909
+ function closingParens2(source) {
18772
19910
  const close = new Int32Array(source.length).fill(-1);
18773
19911
  const open = [];
18774
19912
  let inClass = false;
@@ -18793,7 +19931,7 @@ function closingParens(source) {
18793
19931
  return close;
18794
19932
  }
18795
19933
  function alternatesAtTop(source) {
18796
- const close = closingParens(source);
19934
+ const close = closingParens2(source);
18797
19935
  let start = source.startsWith("^") ? 1 : 0;
18798
19936
  let end = source.endsWith("$") ? source.length - 1 : source.length;
18799
19937
  while (start < end && source[start] === "(" && close[start] === end - 1) {
@@ -18842,6 +19980,124 @@ var init_page_evidence = __esm({
18842
19980
  }
18843
19981
  });
18844
19982
 
19983
+ // ../shared/dist/stated-limits.js
19984
+ function validDate(v) {
19985
+ const [y, m, d] = v.split("-").map(Number);
19986
+ const t = new Date(Date.UTC(y, m - 1, d));
19987
+ return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
19988
+ }
19989
+ function isoFor(type, v) {
19990
+ const re = DATE_RE[type];
19991
+ if (!re || !re.test(v))
19992
+ return false;
19993
+ if (type === "date")
19994
+ return validDate(v);
19995
+ if (type === "datetime-local")
19996
+ return validDate(v.slice(0, 10)) && Number(v.slice(11, 13)) < 24 && Number(v.slice(14, 16)) < 60;
19997
+ if (type === "time")
19998
+ return Number(v.slice(0, 2)) < 24 && Number(v.slice(3, 5)) < 60;
19999
+ if (type === "month")
20000
+ return Number(v.slice(5, 7)) >= 1 && Number(v.slice(5, 7)) <= 12;
20001
+ return Number(v.slice(6, 8)) >= 1 && Number(v.slice(6, 8)) <= 53;
20002
+ }
20003
+ function clean2(raw) {
20004
+ return typeof raw === "string" ? raw.trim() : "";
20005
+ }
20006
+ function statedLimitsFromAttrs(attrs, inputType) {
20007
+ const type = (inputType ?? "").trim().toLowerCase();
20008
+ const limits = [];
20009
+ const add = (k, v) => {
20010
+ limits.push({ k, v });
20011
+ };
20012
+ if (NUMBER_TYPES.has(type)) {
20013
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
20014
+ const v = clean2(attrs[attr]);
20015
+ if (NUMBER_RE.test(v))
20016
+ add(k, v);
20017
+ }
20018
+ const step = clean2(attrs.step);
20019
+ if (NUMBER_RE.test(step) && Number(step) > 0)
20020
+ add("step", step);
20021
+ } else if (type in DATE_RE) {
20022
+ for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
20023
+ const v = clean2(attrs[attr]);
20024
+ if (isoFor(type, v))
20025
+ add(k, v);
20026
+ }
20027
+ } else if (TEXT_TYPES.has(type)) {
20028
+ for (const [attr, k] of [["minlength", "minLength"], ["maxlength", "maxLength"]]) {
20029
+ const v = clean2(attrs[attr]);
20030
+ if (LENGTH_RE.test(v))
20031
+ add(k, String(Number(v)));
20032
+ }
20033
+ const pattern = cleanPattern(clean2(attrs.pattern).slice(0, STATED_PATTERN_MAX));
20034
+ if (pattern)
20035
+ add("pattern", pattern);
20036
+ }
20037
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
20038
+ }
20039
+ function cleanPattern(text) {
20040
+ return text.replace(/[\u0000-\u001f\u007f]/g, "").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "");
20041
+ }
20042
+ function readStatedLimits(stored) {
20043
+ if (!stored || typeof stored !== "object" || Array.isArray(stored))
20044
+ return [];
20045
+ const rec = stored;
20046
+ if (rec.v !== STATED_LIMITS_VERSION || !Array.isArray(rec.limits))
20047
+ return [];
20048
+ const out = [];
20049
+ const seen = /* @__PURE__ */ new Set();
20050
+ for (const item of rec.limits.slice(0, MAX_LIMITS * 2)) {
20051
+ if (!item || typeof item !== "object")
20052
+ continue;
20053
+ const { k, v } = item;
20054
+ if (typeof k !== "string" || typeof v !== "string" || !KINDS.includes(k) || seen.has(k))
20055
+ continue;
20056
+ if (v.length === 0 || v.length > STATED_PATTERN_MAX)
20057
+ continue;
20058
+ const text = k === "pattern" ? cleanPattern(v) : v;
20059
+ if (text === "")
20060
+ continue;
20061
+ if (k === "minLength" || k === "maxLength") {
20062
+ if (!LENGTH_RE.test(v))
20063
+ continue;
20064
+ } else if (k === "step") {
20065
+ if (!NUMBER_RE.test(v))
20066
+ continue;
20067
+ } else if (k === "min" || k === "max") {
20068
+ if (!NUMBER_RE.test(v) && !Object.values(DATE_RE).some((re) => re.test(v)))
20069
+ continue;
20070
+ }
20071
+ seen.add(k);
20072
+ out.push({ k, v: text });
20073
+ }
20074
+ return out;
20075
+ }
20076
+ function statedLimitsRecord(limits) {
20077
+ return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
20078
+ }
20079
+ var STATED_LIMITS_VERSION, STATED_PATTERN_MAX, KINDS, MAX_LIMITS, TEXT_TYPES, NUMBER_TYPES, DATE_RE, NUMBER_RE, LENGTH_RE;
20080
+ var init_stated_limits = __esm({
20081
+ "../shared/dist/stated-limits.js"() {
20082
+ "use strict";
20083
+ STATED_LIMITS_VERSION = 1;
20084
+ STATED_PATTERN_MAX = 200;
20085
+ KINDS = ["min", "max", "minLength", "maxLength", "step", "pattern"];
20086
+ MAX_LIMITS = KINDS.length;
20087
+ TEXT_TYPES = /* @__PURE__ */ new Set(["text", "search", "email", "url", "tel", "password", "textarea", ""]);
20088
+ NUMBER_TYPES = /* @__PURE__ */ new Set(["number", "range"]);
20089
+ DATE_RE = {
20090
+ date: /^\d{4}-\d{2}-\d{2}$/,
20091
+ "datetime-local": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?$/,
20092
+ time: /^\d{2}:\d{2}(?::\d{2})?$/,
20093
+ month: /^\d{4}-\d{2}$/,
20094
+ week: /^\d{4}-W\d{2}$/
20095
+ };
20096
+ NUMBER_RE = /^-?\d{1,15}(?:\.\d{1,10})?$/;
20097
+ LENGTH_RE = /^\d{1,7}$/;
20098
+ }
20099
+ });
20100
+
18845
20101
  // ../shared/dist/index.js
18846
20102
  var dist_exports = {};
18847
20103
  __export(dist_exports, {
@@ -18849,9 +20105,11 @@ __export(dist_exports, {
18849
20105
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
18850
20106
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
18851
20107
  ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
20108
+ ADMIN_FORCE_CANCELLED_RUN_ERROR: () => ADMIN_FORCE_CANCELLED_RUN_ERROR,
18852
20109
  ADMIN_VIEW_DEFAULT_RETURN_TO: () => ADMIN_VIEW_DEFAULT_RETURN_TO,
18853
20110
  AGENT_AI_PROVIDER_PREFIXES: () => AGENT_AI_PROVIDER_PREFIXES,
18854
20111
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
20112
+ ALLOWED_PROVIDER_CHAIN_HOPS: () => ALLOWED_PROVIDER_CHAIN_HOPS,
18855
20113
  API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
18856
20114
  API_MODEL_CATALOG: () => API_MODEL_CATALOG,
18857
20115
  API_MODEL_FIELDS: () => API_MODEL_FIELDS,
@@ -18872,6 +20130,7 @@ __export(dist_exports, {
18872
20130
  AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
18873
20131
  AUTH_PATH_SIGNATURE: () => AUTH_PATH_SIGNATURE,
18874
20132
  AUTH_PROVIDER_HOST_SIGNATURES: () => AUTH_PROVIDER_HOST_SIGNATURES,
20133
+ AUTH_SESSION_GENERATION_RE: () => AUTH_SESSION_GENERATION_RE,
18875
20134
  AUTH_STATE_EXPIRY_SKEW_MS: () => AUTH_STATE_EXPIRY_SKEW_MS,
18876
20135
  AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
18877
20136
  AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
@@ -18890,12 +20149,14 @@ __export(dist_exports, {
18890
20149
  BUG_TEST_BROKEN_REASON: () => BUG_TEST_BROKEN_REASON,
18891
20150
  BUG_TEST_BROKEN_TAG: () => BUG_TEST_BROKEN_TAG,
18892
20151
  CACHE_PRICE_PER_M: () => CACHE_PRICE_PER_M,
20152
+ CANCELLED_REASON_PREFIXES: () => CANCELLED_REASON_PREFIXES,
18893
20153
  CANVAS_UNSUPPORTED_BODY: () => CANVAS_UNSUPPORTED_BODY,
18894
20154
  CANVAS_UNSUPPORTED_CODE: () => CANVAS_UNSUPPORTED_CODE,
18895
20155
  CANVAS_UNSUPPORTED_NOT_CHARGED: () => CANVAS_UNSUPPORTED_NOT_CHARGED,
18896
20156
  CANVAS_UNSUPPORTED_OVERRIDE_HINT: () => CANVAS_UNSUPPORTED_OVERRIDE_HINT,
18897
20157
  CANVAS_UNSUPPORTED_TITLE: () => CANVAS_UNSUPPORTED_TITLE,
18898
20158
  CAPTURE_JSON_STRUCTURE_LIMITS: () => CAPTURE_JSON_STRUCTURE_LIMITS,
20159
+ CHAIN_CANCELLED_RUN_ERROR: () => CHAIN_CANCELLED_RUN_ERROR,
18899
20160
  CHAT_FAILOVER_KEY: () => CHAT_FAILOVER_KEY,
18900
20161
  CLAUDE_CLI_DEFAULT_EFFORT: () => CLAUDE_CLI_DEFAULT_EFFORT,
18901
20162
  CLAUDE_CLI_DEFAULT_MODEL: () => CLAUDE_CLI_DEFAULT_MODEL,
@@ -18932,6 +20193,7 @@ __export(dist_exports, {
18932
20193
  DEFAULT_GEMINI_SCREEN_MODEL: () => DEFAULT_GEMINI_SCREEN_MODEL,
18933
20194
  DEFAULT_JSON_STRUCTURE_LIMITS: () => DEFAULT_JSON_STRUCTURE_LIMITS,
18934
20195
  DEFAULT_PLATFORM_DB_POOL_SIZE: () => DEFAULT_PLATFORM_DB_POOL_SIZE,
20196
+ DEFAULT_PROVIDER_ACCOUNT_ID: () => DEFAULT_PROVIDER_ACCOUNT_ID,
18935
20197
  DEFAULT_SHARED_DB_POOL_SIZE: () => DEFAULT_SHARED_DB_POOL_SIZE,
18936
20198
  DEFAULT_STT_MODEL: () => DEFAULT_STT_MODEL,
18937
20199
  DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
@@ -18948,6 +20210,7 @@ __export(dist_exports, {
18948
20210
  ENVIRONMENTAL_ERROR_STORED_PREFIXES: () => ENVIRONMENTAL_ERROR_STORED_PREFIXES,
18949
20211
  EVIDENCE_NOTE_PREFIXES: () => EVIDENCE_NOTE_PREFIXES,
18950
20212
  EXPECT_CALL_RE: () => EXPECT_CALL_RE,
20213
+ FEATURE_ROW_RUN_WINDOW: () => FEATURE_ROW_RUN_WINDOW,
18951
20214
  FLOW_PHASE_LABELS: () => FLOW_PHASE_LABELS,
18952
20215
  FLOW_PHASE_TYPES: () => FLOW_PHASE_TYPES,
18953
20216
  FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
@@ -19004,6 +20267,8 @@ __export(dist_exports, {
19004
20267
  LIVE_FRAME_STALE_MS: () => LIVE_FRAME_STALE_MS,
19005
20268
  LIVE_FRAME_UNFOCUSED_INTERVAL_MS: () => LIVE_FRAME_UNFOCUSED_INTERVAL_MS,
19006
20269
  LIVE_THUMBNAIL_MAX_BASE64_BYTES: () => LIVE_THUMBNAIL_MAX_BASE64_BYTES,
20270
+ MAX_BALANCED_SCAN: () => MAX_BALANCED_SCAN,
20271
+ MAX_CALLS: () => MAX_CALLS,
19007
20272
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
19008
20273
  MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
19009
20274
  MAX_EMAIL_ADDRESS_LENGTH: () => MAX_EMAIL_ADDRESS_LENGTH,
@@ -19013,6 +20278,8 @@ __export(dist_exports, {
19013
20278
  MAX_ORG_NAME_LENGTH: () => MAX_ORG_NAME_LENGTH,
19014
20279
  MAX_PASSWORD_BYTES: () => MAX_PASSWORD_BYTES,
19015
20280
  MAX_PLAYWRIGHT_CODE_LENGTH: () => MAX_PLAYWRIGHT_CODE_LENGTH,
20281
+ MAX_PROVIDER_ACCOUNTS: () => MAX_PROVIDER_ACCOUNTS,
20282
+ MAX_PROVIDER_ACCOUNT_MODELS: () => MAX_PROVIDER_ACCOUNT_MODELS,
19016
20283
  MAX_SURVEY_ENTRY_URLS: () => MAX_SURVEY_ENTRY_URLS,
19017
20284
  MAX_SURVEY_ENTRY_URL_LENGTH: () => MAX_SURVEY_ENTRY_URL_LENGTH,
19018
20285
  MCP_ARMY_LIMITS: () => MCP_ARMY_LIMITS,
@@ -19048,6 +20315,7 @@ __export(dist_exports, {
19048
20315
  MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
19049
20316
  MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
19050
20317
  NOREPLY_EMAIL: () => NOREPLY_EMAIL,
20318
+ OPENCODE_GO_DEEPSEEK_GLM_MODELS: () => OPENCODE_GO_DEEPSEEK_GLM_MODELS,
19051
20319
  OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
19052
20320
  OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
19053
20321
  OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
@@ -19067,10 +20335,12 @@ __export(dist_exports, {
19067
20335
  PLAN_FEATURES: () => PLAN_FEATURES,
19068
20336
  PLAN_FEATURE_LOCKED_CODE: () => PLAN_FEATURE_LOCKED_CODE,
19069
20337
  PLAN_ORDER: () => PLAN_ORDER,
20338
+ PLATFORM_ADMIN_CANCELLED_RUN_ERROR: () => PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
19070
20339
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
19071
20340
  PLATFORM_DEFAULT_PROVIDER: () => PLATFORM_DEFAULT_PROVIDER,
19072
20341
  PLATFORM_PLAN_BILLING: () => PLATFORM_PLAN_BILLING,
19073
20342
  PLATFORM_SERVER_APPLICATION_NAME: () => PLATFORM_SERVER_APPLICATION_NAME,
20343
+ PLATFORM_SUPPORT_CANCELLED_RUN_ERROR: () => PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
19074
20344
  POST_SPECIAL_TRIAL_DAYS: () => POST_SPECIAL_TRIAL_DAYS,
19075
20345
  PRESET_DIRECT_FALLBACK: () => PRESET_DIRECT_FALLBACK,
19076
20346
  PRODUCTION_READ_ONLY_GUARD_TEMPLATE: () => PRODUCTION_READ_ONLY_GUARD_TEMPLATE,
@@ -19082,17 +20352,25 @@ __export(dist_exports, {
19082
20352
  PROJECT_OVERRIDE_FIELDS: () => PROJECT_OVERRIDE_FIELDS,
19083
20353
  PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
19084
20354
  PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
20355
+ PROVIDER_ACCOUNT_ID_PATTERN: () => PROVIDER_ACCOUNT_ID_PATTERN,
20356
+ PROVIDER_ACCOUNT_MODEL_PATTERN: () => PROVIDER_ACCOUNT_MODEL_PATTERN,
19085
20357
  PROVIDER_CHAINS: () => PROVIDER_CHAINS,
20358
+ PROVIDER_CHAINS_SETTING_MAX_CHARS: () => PROVIDER_CHAINS_SETTING_MAX_CHARS,
20359
+ PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
20360
+ PROVIDER_CHAIN_MAX_ENTRIES: () => PROVIDER_CHAIN_MAX_ENTRIES,
20361
+ PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
19086
20362
  PUBLIC_PLANS: () => PUBLIC_PLANS,
19087
20363
  QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
19088
20364
  READINESS_POLL_MAX_CODE_CHARS: () => READINESS_POLL_MAX_CODE_CHARS,
19089
20365
  READ_ONLY_TAGS: () => READ_ONLY_TAGS,
19090
20366
  RECHECKED_NOTES: () => RECHECKED_NOTES,
19091
20367
  RECORDING_VALUE_PLACEHOLDERS: () => RECORDING_VALUE_PLACEHOLDERS,
20368
+ RENDER_IDENTITY_MAX_LENGTH: () => RENDER_IDENTITY_MAX_LENGTH,
19092
20369
  REPOSITORY_CLIS: () => REPOSITORY_CLIS,
19093
20370
  RESERVED_PROJECT_KEYS: () => RESERVED_PROJECT_KEYS,
19094
20371
  RESULT_STRUCTURE_TOO_COSTLY_CODE: () => RESULT_STRUCTURE_TOO_COSTLY_CODE,
19095
20372
  RETRIED_ATTEMPTS_KEY: () => RETRIED_ATTEMPTS_KEY,
20373
+ RETRY_HELD_RUN_ERROR: () => RETRY_HELD_RUN_ERROR,
19096
20374
  RUNNER_INTERRUPT_ERROR_PREFIX: () => RUNNER_INTERRUPT_ERROR_PREFIX,
19097
20375
  RUN_INTERRUPTED_ERROR_PREFIXES: () => RUN_INTERRUPTED_ERROR_PREFIXES,
19098
20376
  RUN_INTERRUPTION_NOT_RETRIED_MARKERS: () => RUN_INTERRUPTION_NOT_RETRIED_MARKERS,
@@ -19105,6 +20383,8 @@ __export(dist_exports, {
19105
20383
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
19106
20384
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
19107
20385
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
20386
+ STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
20387
+ STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
19108
20388
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
19109
20389
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
19110
20390
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
@@ -19121,6 +20401,7 @@ __export(dist_exports, {
19121
20401
  SURVEY_NOTES_RENDER_MAX_CHARS: () => SURVEY_NOTES_RENDER_MAX_CHARS,
19122
20402
  SURVEY_NOTE_KEYS: () => SURVEY_NOTE_KEYS,
19123
20403
  SUSPICIOUS_PASS_TAG: () => SUSPICIOUS_PASS_TAG,
20404
+ TEST_DID_NOT_RUN_ERROR: () => TEST_DID_NOT_RUN_ERROR,
19124
20405
  TEST_QUALITY_CONVENTIONS: () => TEST_QUALITY_CONVENTIONS,
19125
20406
  TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
19126
20407
  TEST_VARIABLE_SECRET_KEY_PATTERN: () => TEST_VARIABLE_SECRET_KEY_PATTERN,
@@ -19133,23 +20414,33 @@ __export(dist_exports, {
19133
20414
  UI_VARIANTS: () => UI_VARIANTS,
19134
20415
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
19135
20416
  USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
20417
+ VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
20418
+ VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
20419
+ VISUAL_MASK_SELECTOR_MAX_LENGTH: () => VISUAL_MASK_SELECTOR_MAX_LENGTH,
20420
+ VISUAL_V1_IDENTITY: () => VISUAL_V1_IDENTITY,
19136
20421
  VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
19137
20422
  WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
19138
20423
  WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
20424
+ ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
20425
+ ZAI_PREFIX: () => ZAI_PREFIX,
20426
+ accountServesModel: () => accountServesModel,
19139
20427
  accumulateStatements: () => accumulateStatements,
19140
20428
  adoptBrokerFailover: () => adoptBrokerFailover,
19141
20429
  aiCostToCreditCents: () => aiCostToCreditCents,
20430
+ allowedProviderChainHosts: () => allowedProviderChainHosts,
19142
20431
  alternatesAtTop: () => alternatesAtTop,
19143
20432
  analyzeDerivedValues: () => analyzeDerivedValues,
19144
20433
  analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
19145
20434
  analyzePersistenceReread: () => analyzePersistenceReread,
19146
20435
  analyzeReadinessPolling: () => analyzeReadinessPolling,
20436
+ analyzeRegexMatchers: () => analyzeRegexMatchers,
19147
20437
  analyzeSharedAccountUse: () => analyzeSharedAccountUse,
19148
20438
  analyzeStaleAfterSubmit: () => analyzeStaleAfterSubmit,
19149
20439
  analyzeTestData: () => analyzeTestData,
19150
20440
  analyzeUnreachedUrlWaits: () => analyzeUnreachedUrlWaits,
19151
20441
  analyzeUrlWaits: () => analyzeUrlWaits,
19152
20442
  analyzeWriteResponse: () => analyzeWriteResponse,
20443
+ androidResourceOwners: () => androidResourceOwners,
19153
20444
  apiModelCardsFromConfig: () => apiModelCardsFromConfig,
19154
20445
  apiModelsForProvider: () => apiModelsForProvider,
19155
20446
  apiProviderModelLabel: () => apiProviderModelLabel,
@@ -19160,6 +20451,7 @@ __export(dist_exports, {
19160
20451
  applyLaunchSpecialPlan: () => applyLaunchSpecialPlan,
19161
20452
  asPlainExpect: () => asPlainExpect,
19162
20453
  assertionOnlyCode: () => assertionOnlyCode,
20454
+ assertionStrengthHoistedReadsOn: () => assertionStrengthHoistedReadsOn,
19163
20455
  assertionsReadVisibleContent: () => assertionsReadVisibleContent,
19164
20456
  assessCodeOutcomeCoverage: () => assessCodeOutcomeCoverage,
19165
20457
  assessTitleCoverage: () => assessTitleCoverage,
@@ -19243,6 +20535,7 @@ __export(dist_exports, {
19243
20535
  deepgramSpeak: () => deepgramSpeak,
19244
20536
  deepgramTranscribe: () => deepgramTranscribe,
19245
20537
  defaultConfirmModel: () => defaultConfirmModel,
20538
+ defaultProviderChainEntries: () => defaultProviderChainEntries,
19246
20539
  defaultScreenModel: () => defaultScreenModel,
19247
20540
  defaultVoiceForTtsModel: () => defaultVoiceForTtsModel,
19248
20541
  deriveAuthPlan: () => deriveAuthPlan,
@@ -19285,6 +20578,7 @@ __export(dist_exports, {
19285
20578
  extractOutcomeSearchTerms: () => extractOutcomeSearchTerms,
19286
20579
  featureForAiCallType: () => featureForAiCallType,
19287
20580
  featureForMcpTool: () => featureForMcpTool,
20581
+ featureRowRunStatus: () => featureRowRunStatus,
19288
20582
  findBracedClause: () => findBracedClause,
19289
20583
  findCounterIdPins: () => findCounterIdPins,
19290
20584
  findCredentialTextLeftover: () => findCredentialTextLeftover,
@@ -19307,6 +20601,7 @@ __export(dist_exports, {
19307
20601
  formatPersistenceRereadAdvisory: () => formatPersistenceRereadAdvisory,
19308
20602
  formatPoolBudgetLine: () => formatPoolBudgetLine,
19309
20603
  formatReadinessPollAdvisory: () => formatReadinessPollAdvisory,
20604
+ formatRegexMatcherNote: () => formatRegexMatcherNote,
19310
20605
  formatStaleAfterSubmitAdvisory: () => formatStaleAfterSubmitAdvisory,
19311
20606
  formatSuspiciousExpectation: () => formatSuspiciousExpectation,
19312
20607
  formatUrlWaitAdvisory: () => formatUrlWaitAdvisory,
@@ -19332,6 +20627,7 @@ __export(dist_exports, {
19332
20627
  getPlanChangeType: () => getPlanChangeType,
19333
20628
  getPlanDefinition: () => getPlanDefinition,
19334
20629
  getPlanLabel: () => getPlanLabel,
20630
+ getProviderChainOverrides: () => getProviderChainOverrides,
19335
20631
  getTrialDaysRemaining: () => getTrialDaysRemaining,
19336
20632
  googleBrokerNavigationUrl: () => googleBrokerNavigationUrl,
19337
20633
  googleBrokerUnavailableLine: () => googleBrokerUnavailableLine,
@@ -19348,9 +20644,11 @@ __export(dist_exports, {
19348
20644
  hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
19349
20645
  hasOpenRouterPrefix: () => hasOpenRouterPrefix,
19350
20646
  hasPlatformLocator: () => hasPlatformLocator,
20647
+ hasProviderChainLeadsByDefault: () => hasProviderChainLeadsByDefault,
19351
20648
  hasReadOnlyMarker: () => hasReadOnlyMarker,
19352
20649
  hasSurveyNotes: () => hasSurveyNotes,
19353
20650
  hasTestFailContract: () => hasTestFailContract,
20651
+ hasZaiPrefix: () => hasZaiPrefix,
19354
20652
  healMobileTestSteps: () => healMobileTestSteps,
19355
20653
  healthRunWhere: () => healthRunWhere,
19356
20654
  heldCodeRecord: () => heldCodeRecord,
@@ -19372,8 +20670,11 @@ __export(dist_exports, {
19372
20670
  isAuthTokenName: () => isAuthTokenName,
19373
20671
  isBlockedEnvVar: () => isBlockedEnvVar,
19374
20672
  isBrowserCliEngine: () => isBrowserCliEngine,
20673
+ isBrowserVersion: () => isBrowserVersion,
19375
20674
  isBugTestBrokenError: () => isBugTestBrokenError,
20675
+ isCancelledReason: () => isCancelledReason,
19376
20676
  isCanvasUnsupportedMessage: () => isCanvasUnsupportedMessage,
20677
+ isChainablePrimary: () => isChainablePrimary,
19377
20678
  isCliEngineModelId: () => isCliEngineModelId,
19378
20679
  isCliEngineSpec: () => isCliEngineSpec,
19379
20680
  isCodeFactNote: () => isCodeFactNote,
@@ -19397,6 +20698,7 @@ __export(dist_exports, {
19397
20698
  isIdentityMobileXpath: () => isIdentityMobileXpath,
19398
20699
  isIdentityProviderSurface: () => isIdentityProviderSurface,
19399
20700
  isIncidentalAssetPath: () => isIncidentalAssetPath,
20701
+ isIntentionalNegativeAuthTest: () => isIntentionalNegativeAuthTest,
19400
20702
  isIpOrLocalhost: () => isIpOrLocalhost,
19401
20703
  isKnownPageType: () => isKnownPageType,
19402
20704
  isLaunchSpecialActive: () => isLaunchSpecialActive,
@@ -19424,6 +20726,7 @@ __export(dist_exports, {
19424
20726
  isRunInterruptedError: () => isRunInterruptedError,
19425
20727
  isRunnableStep: () => isRunnableStep,
19426
20728
  isSafeDeepLink: () => isSafeDeepLink,
20729
+ isSafeMaskSelector: () => isSafeMaskSelector,
19427
20730
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
19428
20731
  isSelectableApiModel: () => isSelectableApiModel,
19429
20732
  isSelfServePlan: () => isSelfServePlan,
@@ -19454,6 +20757,7 @@ __export(dist_exports, {
19454
20757
  isValidSttProvider: () => isValidSttProvider,
19455
20758
  isValidTtsModel: () => isValidTtsModel,
19456
20759
  isValidTtsVoiceId: () => isValidTtsVoiceId,
20760
+ isZaiModelId: () => isZaiModelId,
19457
20761
  jsEscapedForms: () => jsEscapedForms,
19458
20762
  jsonStructureRefusalOf: () => jsonStructureRefusalOf,
19459
20763
  jsonStructureVerify: () => jsonStructureVerify,
@@ -19486,6 +20790,7 @@ __export(dist_exports, {
19486
20790
  normalizeFinishReason: () => normalizeFinishReason,
19487
20791
  normalizeNoteText: () => normalizeNoteText,
19488
20792
  normalizePageType: () => normalizePageType,
20793
+ normalizeProviderAccountModels: () => normalizeProviderAccountModels,
19489
20794
  normalizeTtsModel: () => normalizeTtsModel,
19490
20795
  normalizeTtsVoiceId: () => normalizeTtsVoiceId,
19491
20796
  noteChatFailover: () => noteChatFailover,
@@ -19502,6 +20807,7 @@ __export(dist_exports, {
19502
20807
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
19503
20808
  parseAppTimezone: () => parseAppTimezone,
19504
20809
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
20810
+ parseAuthSessionStamp: () => parseAuthSessionStamp,
19505
20811
  parseBoolFlag: () => parseBoolFlag,
19506
20812
  parseDbPoolSize: () => parseDbPoolSize,
19507
20813
  parseDeepgramListenResponse: () => parseDeepgramListenResponse,
@@ -19509,13 +20815,18 @@ __export(dist_exports, {
19509
20815
  parseGeminiModelsPayload: () => parseGeminiModelsPayload,
19510
20816
  parseGoogleAuthorizeUrl: () => parseGoogleAuthorizeUrl,
19511
20817
  parseGoogleGisRequestUrl: () => parseGoogleGisRequestUrl,
20818
+ parseLastActionProofMode: () => parseLastActionProofMode,
19512
20819
  parseLimitParam: () => parseLimitParam,
19513
20820
  parseOffsetParam: () => parseOffsetParam,
19514
20821
  parsePageParam: () => parsePageParam,
19515
20822
  parsePgIntSetting: () => parsePgIntSetting,
19516
20823
  parseProjectEngineOverrides: () => parseProjectEngineOverrides,
19517
20824
  parsePromotionGateMode: () => parsePromotionGateMode,
20825
+ parseProviderAccountLists: () => parseProviderAccountLists,
20826
+ parseProviderChainOverrides: () => parseProviderChainOverrides,
19518
20827
  parseRecheckClearMode: () => parseRecheckClearMode,
20828
+ parseRenderIdentity: () => parseRenderIdentity,
20829
+ parseStoredProviderChainOverrides: () => parseStoredProviderChainOverrides,
19519
20830
  parseSurveyEntryUrls: () => parseSurveyEntryUrls,
19520
20831
  parseSuspiciousPassMode: () => parseSuspiciousPassMode,
19521
20832
  parseTaskModelUpdate: () => parseTaskModelUpdate,
@@ -19545,13 +20856,20 @@ __export(dist_exports, {
19545
20856
  profileKeyRoute: () => profileKeyRoute,
19546
20857
  projectAppHosts: () => projectAppHosts,
19547
20858
  providerChainFor: () => providerChainFor,
20859
+ providerChainHostOf: () => providerChainHostOf,
20860
+ providerChainHostProvider: () => providerChainHostProvider,
20861
+ providerChainLeadsFor: () => providerChainLeadsFor,
20862
+ providerChainModelKey: () => providerChainModelKey,
19548
20863
  providerChainPrimariesForHop: () => providerChainPrimariesForHop,
20864
+ providerHostPlan: () => providerHostPlan,
20865
+ readBalanced: () => readBalanced,
19549
20866
  readChatFailover: () => readChatFailover,
19550
20867
  readChatReasoningTokens: () => readChatReasoningTokens,
19551
20868
  readChatUsage: () => readChatUsage,
19552
20869
  readEnabledEngines: () => readEnabledEngines,
19553
20870
  readProjectEngineOverrides: () => readProjectEngineOverrides,
19554
20871
  readRetriedAttempts: () => readRetriedAttempts,
20872
+ readStatedLimits: () => readStatedLimits,
19555
20873
  readStaticPageCanvasSignals: () => readStaticPageCanvasSignals,
19556
20874
  readSurveyEntryUrls: () => readSurveyEntryUrls,
19557
20875
  readSurveyNotes: () => readSurveyNotes,
@@ -19564,6 +20882,7 @@ __export(dist_exports, {
19564
20882
  regexLiteralEnd: () => regexLiteralEnd2,
19565
20883
  regexOpensAfter: () => regexOpensAfter,
19566
20884
  registrableDomain: () => registrableDomain,
20885
+ renderIdentity: () => renderIdentity,
19567
20886
  renderSurveyNotesForPrompt: () => renderSurveyNotesForPrompt,
19568
20887
  repairPageType: () => repairPageType,
19569
20888
  replaceBlockComments: () => replaceBlockComments,
@@ -19611,11 +20930,14 @@ __export(dist_exports, {
19611
20930
  selectorCandidatesForStep: () => selectorCandidatesForStep,
19612
20931
  selectorForValue: () => selectorForValue,
19613
20932
  setAgenticCliRuntime: () => setAgenticCliRuntime,
20933
+ setProviderChainOverrides: () => setProviderChainOverrides,
19614
20934
  signupPlanFor: () => signupPlanFor,
19615
20935
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
19616
20936
  siteOf: () => siteOf,
19617
20937
  specItemsFromTestPlans: () => specItemsFromTestPlans,
19618
20938
  splitAnswerLines: () => splitAnswerLines,
20939
+ statedLimitsFromAttrs: () => statedLimitsFromAttrs,
20940
+ statedLimitsRecord: () => statedLimitsRecord,
19619
20941
  stepAppliesToPlatform: () => stepAppliesToPlatform,
19620
20942
  stepConsoleLines: () => stepConsoleLines,
19621
20943
  stepIsDivergentForPlatform: () => stepIsDivergentForPlatform,
@@ -19627,7 +20949,11 @@ __export(dist_exports, {
19627
20949
  stripCodeFactPrefix: () => stripCodeFactPrefix,
19628
20950
  stripCoverageComments: () => stripCoverageComments,
19629
20951
  stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
20952
+ stripFractionZeros: () => stripFractionZeros,
20953
+ stripLineComment: () => stripLineComment,
19630
20954
  stripLoginScaffolding: () => stripLoginScaffolding,
20955
+ stripTrailingChars: () => stripTrailingChars,
20956
+ stripTrailingSlashes: () => stripTrailingSlashes,
19631
20957
  strongerNeedsReviewSource: () => strongerNeedsReviewSource,
19632
20958
  sttModelsForProvider: () => sttModelsForProvider,
19633
20959
  summarizeHealth: () => summarizeHealth,
@@ -19644,10 +20970,14 @@ __export(dist_exports, {
19644
20970
  ttsFallbackModel: () => ttsFallbackModel,
19645
20971
  ttsProviderForModel: () => ttsProviderForModel,
19646
20972
  unexpectedPassError: () => unexpectedPassError,
20973
+ v1RenderIdentity: () => v1RenderIdentity,
19647
20974
  validateAppMapDocument: () => validateAppMapDocument,
19648
20975
  validateAssertionStrength: () => validateAssertionStrength,
19649
20976
  validateEngineSpec: () => validateEngineSpec,
20977
+ validateProviderChainEntries: () => validateProviderChainEntries,
20978
+ validateProviderChainsSetting: () => validateProviderChainsSetting,
19650
20979
  validateTaskModelId: () => validateTaskModelId,
20980
+ visualBaselinesEnabled: () => visualBaselinesEnabled,
19651
20981
  visualProDailyBudget: () => visualProDailyBudget,
19652
20982
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
19653
20983
  voicesForTtsModel: () => voicesForTtsModel,
@@ -19655,7 +20985,8 @@ __export(dist_exports, {
19655
20985
  withReviewSource: () => withReviewSource,
19656
20986
  withoutNotChargedClaim: () => withoutNotChargedClaim,
19657
20987
  withoutServerOnlySpendFields: () => withoutServerOnlySpendFields,
19658
- withoutSurveyNotes: () => withoutSurveyNotes
20988
+ withoutSurveyNotes: () => withoutSurveyNotes,
20989
+ zaiWireModel: () => zaiWireModel
19659
20990
  });
19660
20991
  var init_dist = __esm({
19661
20992
  "../shared/dist/index.js"() {
@@ -19697,6 +21028,7 @@ var init_dist = __esm({
19697
21028
  init_admin_view_return_to();
19698
21029
  init_auth_plan();
19699
21030
  init_auth_block_detector();
21031
+ init_auth_session_stamp();
19700
21032
  init_auth_state_freshness();
19701
21033
  init_skip_coverage_detector();
19702
21034
  init_feature_coverage();
@@ -19708,11 +21040,14 @@ var init_dist = __esm({
19708
21040
  init_deepgram_voices();
19709
21041
  init_feature_flags();
19710
21042
  init_env_flags();
21043
+ init_visual_compare_limits();
21044
+ init_visual_baseline_identity();
19711
21045
  init_analytics_url();
19712
21046
  init_agentic_cli_runtime();
19713
21047
  init_api_model_catalog();
19714
21048
  init_engine_spec();
19715
21049
  init_provider_chains();
21050
+ init_provider_accounts();
19716
21051
  init_project_engine_overrides();
19717
21052
  init_ai_profiles();
19718
21053
  init_openrouter_usage();
@@ -19753,6 +21088,8 @@ var init_dist = __esm({
19753
21088
  init_promotion_quality_gate();
19754
21089
  init_regex_alternation();
19755
21090
  init_page_evidence();
21091
+ init_trailing_slashes();
21092
+ init_stated_limits();
19756
21093
  }
19757
21094
  });
19758
21095
 
@@ -19840,7 +21177,7 @@ var require_runs_dir = __commonJS({
19840
21177
  } catch (error2) {
19841
21178
  if (error2.code === "EEXIST" && resolvesFrom(runsDir) === own)
19842
21179
  return;
19843
- throw new Error(`Could not link ${link} to the runner's node_modules (${error2.message}). Set RUNS_DIR to a writable folder outside node_modules that can resolve @playwright/test.`);
21180
+ throw new Error(`Could not link ${link} to the runner's node_modules (${error2.message}). Set RUNS_DIR to a writable folder outside node_modules that can resolve @playwright/test.`, { cause: error2 });
19844
21181
  }
19845
21182
  }
19846
21183
  }
@@ -21443,23 +22780,41 @@ var require_harness_init_script = __commonJS({
21443
22780
  exports2.isHarnessInitConsoleLine = isHarnessInitConsoleLine;
21444
22781
  exports2.stripHarnessConsoleEntries = stripHarnessConsoleEntries;
21445
22782
  var node_crypto_1 = require("crypto");
22783
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
21446
22784
  exports2.HARNESS_INIT_SOURCE_URL = `vqa-harness-init-${(0, node_crypto_1.randomBytes)(4).toString("hex")}.js`;
22785
+ var TRANSITION_LOOP_LIMIT = 8;
22786
+ var TRANSITION_LOOP_WINDOW_MS = 1e3;
21447
22787
  function withHarnessSourceUrl(content) {
21448
22788
  return `${content}
21449
22789
  //# sourceURL=${exports2.HARNESS_INIT_SOURCE_URL}`;
21450
22790
  }
21451
22791
  function buildAnimationDisablingSource(opts) {
22792
+ const finishTransitions = opts.finishTransitions ?? (0, shared_1.parseBoolFlag)(process.env.HARNESS_FINISH_CSS_TRANSITIONS);
22793
+ 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; }";
21452
22794
  return withHarnessSourceUrl(`(() => {
21453
22795
  // Layer 1: CSS transitions and animations - instant, not removed
21454
22796
  // (duration 0s preserves final state; 'none' would prevent keyframes from completing)
22797
+ // The rule goes in a constructed stylesheet on document.adoptedStyleSheets, which adds no node to the DOM:
22798
+ // a <style> element changed what the test sees (locator('style') and 'head > *' counts, document.styleSheets,
22799
+ // an iframe's empty head), so a spec stock Playwright passes failed. The <style> is the fallback for a
22800
+ // browser with no constructable stylesheets.
22801
+ let adopted = false;
21455
22802
  try {
22803
+ if (typeof CSSStyleSheet === 'function' && typeof Document === 'function' && 'adoptedStyleSheets' in Document.prototype) {
22804
+ const sheet = new CSSStyleSheet();
22805
+ sheet.replaceSync(${JSON.stringify(css)});
22806
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
22807
+ adopted = true;
22808
+ }
22809
+ } catch (e) {}
22810
+ if (!adopted) try {
21456
22811
  let applied = false;
21457
22812
  const apply = () => {
21458
22813
  if (applied) return true;
21459
22814
  const root = document.head || document.documentElement;
21460
22815
  if (!root) return false;
21461
22816
  const style = document.createElement('style');
21462
- style.textContent = '*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; transition-delay: 0s !important; animation-delay: 0s !important; }';
22817
+ style.textContent = ${JSON.stringify(css)};
21463
22818
  root.appendChild(style);
21464
22819
  applied = true;
21465
22820
  return true;
@@ -21481,6 +22836,48 @@ ${opts.hideWebdriver ? `
21481
22836
  try {
21482
22837
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
21483
22838
  } catch (e) {}
22839
+ ` : ""}
22840
+ ${finishTransitions ? `
22841
+ // Layer 1b: CSS transitions run, and are finished as they start, so transitionend fires.
22842
+ // - finish() is deferred to a task: called inside the transitionrun dispatch (or a microtask of it) after a
22843
+ // real input event, Chromium finishes the transition but never fires its transitionstart/transitionend.
22844
+ // - setTimeout and performance.now are the ones present when this script runs, before the page's own
22845
+ // scripts: fake timers the page installs later cannot hold a finish back.
22846
+ // - The subtree is walked only for a pseudo-element's transition (its animations hang off the element).
22847
+ // - 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
22848
+ // each transitionend: carousel, pulse) is left to run at its own pace from then on, instead of cycling at
22849
+ // frame rate for the rest of the run.
22850
+ try {
22851
+ const st = setTimeout;
22852
+ const perf = typeof performance === 'object' && performance && typeof performance.now === 'function' ? performance : null;
22853
+ const now = perf ? perf.now.bind(perf) : Date.now;
22854
+ const seen = new WeakMap();
22855
+ const looping = (el, key) => {
22856
+ let byKey = seen.get(el);
22857
+ if (!byKey) { byKey = new Map(); seen.set(el, byKey); }
22858
+ const t = now();
22859
+ let s = byKey.get(key);
22860
+ if (s && s.loop) return true;
22861
+ if (!s || t - s.since > ${TRANSITION_LOOP_WINDOW_MS}) { s = { n: 0, since: t, loop: false }; byKey.set(key, s); }
22862
+ s.n += 1;
22863
+ if (s.n > ${TRANSITION_LOOP_LIMIT}) s.loop = true;
22864
+ return s.loop;
22865
+ };
22866
+ window.addEventListener('transitionrun', (ev) => {
22867
+ try {
22868
+ const el = ev && ev.target;
22869
+ if (!el || typeof el.getAnimations !== 'function' || typeof CSSTransition !== 'function') return;
22870
+ const pseudo = ev.pseudoElement || '';
22871
+ if (looping(el, ev.propertyName + '|' + pseudo)) return;
22872
+ for (const a of (pseudo ? el.getAnimations({ subtree: true }) : el.getAnimations())) {
22873
+ if (a instanceof CSSTransition && a.transitionProperty === ev.propertyName && a.effect
22874
+ && a.effect.target === el && (a.effect.pseudoElement || '') === pseudo) {
22875
+ st(() => { try { a.finish(); } catch (e) {} }, 0);
22876
+ }
22877
+ }
22878
+ } catch (e) {}
22879
+ }, true);
22880
+ } catch (e) {}
21484
22881
  ` : ""}
21485
22882
  // Layer 2: Web Animations API - make instant instead of no-op
21486
22883
  // (preserves animation completion callbacks that app code may depend on)
@@ -22372,6 +23769,7 @@ var require_browser_config = __commonJS({
22372
23769
  exports2.fullChromiumCloudLaunch = fullChromiumCloudLaunch;
22373
23770
  exports2.chooseCloudBrowser = chooseCloudBrowser;
22374
23771
  exports2.appTimezoneConfigLine = appTimezoneConfigLine;
23772
+ exports2.discoveryTimezoneId = discoveryTimezoneId;
22375
23773
  exports2.getMCPBrowserConfig = getMCPBrowserConfig;
22376
23774
  exports2.getPlaywrightLaunchOptions = getPlaywrightLaunchOptions;
22377
23775
  exports2.getLaunchOptions = getLaunchOptions;
@@ -22590,7 +23988,7 @@ var require_browser_config = __commonJS({
22590
23988
  try {
22591
23989
  (0, fs_1.cpSync)(localStateSrc, (0, path_1.join)(tempDir, "Local State"));
22592
23990
  } catch (err) {
22593
- throw new Error(`Cannot copy Chrome profile (${err instanceof Error ? err.message : String(err)}). Quit Google Chrome completely, or use getChromeUserDataDirSource() for seeding.`);
23991
+ throw new Error(`Cannot copy Chrome profile (${err instanceof Error ? err.message : String(err)}). Quit Google Chrome completely, or use getChromeUserDataDirSource() for seeding.`, { cause: err });
22594
23992
  }
22595
23993
  }
22596
23994
  for (const file of essentialFiles) {
@@ -22781,6 +24179,10 @@ var require_browser_config = __commonJS({
22781
24179
  const zone = (0, shared_1.appTimezoneFromVariables)(vars);
22782
24180
  return zone ? `timezoneId: ${JSON.stringify(zone)},` : "";
22783
24181
  }
24182
+ function discoveryTimezoneId(ctx) {
24183
+ const vars = ctx.variableContext;
24184
+ return (0, shared_1.appTimezoneFromVariables)(vars?.publicTestVariables) ?? (0, shared_1.appTimezoneFromVariables)(vars?.allTestVariables);
24185
+ }
22784
24186
  function getMCPBrowserConfig(opts) {
22785
24187
  const userDataDir = opts?.forceIsolated ? null : getTestUserDataDir();
22786
24188
  const timezoneOption = opts?.timezoneId ? { timezoneId: opts.timezoneId } : {};
@@ -22892,11 +24294,13 @@ var require_proc_table = __commonJS({
22892
24294
  "../runner-core/dist/utils/proc-table.js"(exports2) {
22893
24295
  "use strict";
22894
24296
  Object.defineProperty(exports2, "__esModule", { value: true });
24297
+ exports2.PS_MAX_BUFFER = void 0;
22895
24298
  exports2.isMissingBinaryError = isMissingBinaryError;
22896
24299
  exports2.parseProcStat = parseProcStat;
22897
24300
  exports2.readProcRssKb = readProcRssKb;
22898
24301
  exports2.readProcTable = readProcTable;
22899
24302
  var node_fs_1 = require("fs");
24303
+ exports2.PS_MAX_BUFFER = 32 * 1024 * 1024;
22900
24304
  var CLOCK_TICKS_PER_SECOND = 100;
22901
24305
  function isMissingBinaryError(error2) {
22902
24306
  return error2?.code === "ENOENT";
@@ -22982,14 +24386,13 @@ var require_kill_tree = __commonJS({
22982
24386
  var proc_table_js_1 = require_proc_table();
22983
24387
  var execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
22984
24388
  var PS_TIMEOUT_MS = 2e3;
22985
- var PS_BUFFER = 1024 * 1024;
22986
24389
  async function listProcessRows(timeoutMs = PS_TIMEOUT_MS) {
22987
24390
  const controller = new AbortController();
22988
24391
  const timer = setTimeout(() => controller.abort(), timeoutMs);
22989
24392
  timer.unref?.();
22990
24393
  try {
22991
24394
  const { stdout } = await execFileAsync("ps", ["-A", "-o", "pid=,ppid=,command="], {
22992
- maxBuffer: PS_BUFFER,
24395
+ maxBuffer: proc_table_js_1.PS_MAX_BUFFER,
22993
24396
  signal: controller.signal
22994
24397
  });
22995
24398
  const rows = [];
@@ -23026,7 +24429,7 @@ var require_kill_tree = __commonJS({
23026
24429
  timer.unref?.();
23027
24430
  try {
23028
24431
  const { stdout } = await execFileAsync("ps", ["-A", "-o", "pid=,ppid=,pgid=,stat="], {
23029
- maxBuffer: PS_BUFFER,
24432
+ maxBuffer: proc_table_js_1.PS_MAX_BUFFER,
23030
24433
  signal: controller.signal
23031
24434
  });
23032
24435
  const tree = /* @__PURE__ */ new Map();
@@ -23136,15 +24539,15 @@ var require_kill_tree = __commonJS({
23136
24539
  const rows = await listProcessRows(timeoutMs);
23137
24540
  return new Set(rows.filter((row) => row.ppid === parentPid && commandRegex.test(row.command)).map((row) => row.pid));
23138
24541
  } catch {
23139
- return /* @__PURE__ */ new Set();
24542
+ return null;
23140
24543
  }
23141
24544
  }
23142
24545
  var chromiumLaunchTail = Promise.resolve();
23143
24546
  async function launchWithChromiumRootPid(launch, options = {}) {
23144
24547
  const run = async () => {
23145
- const prior = await snapshotMatchingChildren(process.pid, options.commandRegex, options.timeoutMs).catch(() => /* @__PURE__ */ new Set());
24548
+ const prior = await snapshotMatchingChildren(process.pid, options.commandRegex, options.timeoutMs).catch(() => null);
23146
24549
  const value = await launch();
23147
- const rootPid = await findNewChromiumChildPid(process.pid, prior, options).catch(() => null);
24550
+ const rootPid = prior ? await findNewChromiumChildPid(process.pid, prior, options).catch(() => null) : null;
23148
24551
  return { value, rootPid };
23149
24552
  };
23150
24553
  const turn = chromiumLaunchTail.then(run);
@@ -23566,7 +24969,7 @@ var require_survey_door_harvester = __commonJS({
23566
24969
  }
23567
24970
  if (!payload)
23568
24971
  continue;
23569
- const cleaned = payload.replace(/^\s*```(json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
24972
+ const cleaned = payload.replace(/^\s*```(json)?\s*\n?/i, "").replace(/\n?```\s*$/, "").trim();
23570
24973
  if (!cleaned)
23571
24974
  continue;
23572
24975
  try {
@@ -76648,7 +78051,7 @@ var require_he = __commonJS({
76648
78051
  var stringFromCharCode2 = String.fromCharCode;
76649
78052
  var object = {};
76650
78053
  var hasOwnProperty = object.hasOwnProperty;
76651
- var has = function(object2, propertyName) {
78054
+ var has2 = function(object2, propertyName) {
76652
78055
  return hasOwnProperty.call(object2, propertyName);
76653
78056
  };
76654
78057
  var contains = function(array, value) {
@@ -76668,7 +78071,7 @@ var require_he = __commonJS({
76668
78071
  var result = {};
76669
78072
  var key2;
76670
78073
  for (key2 in defaults) {
76671
- result[key2] = has(options, key2) ? options[key2] : defaults[key2];
78074
+ result[key2] = has2(options, key2) ? options[key2] : defaults[key2];
76672
78075
  }
76673
78076
  return result;
76674
78077
  };
@@ -76680,7 +78083,7 @@ var require_he = __commonJS({
76680
78083
  }
76681
78084
  return "\uFFFD";
76682
78085
  }
76683
- if (has(decodeMapNumeric, codePoint)) {
78086
+ if (has2(decodeMapNumeric, codePoint)) {
76684
78087
  if (strict) {
76685
78088
  parseError("disallowed character reference");
76686
78089
  }
@@ -76721,7 +78124,7 @@ var require_he = __commonJS({
76721
78124
  };
76722
78125
  if (encodeEverything) {
76723
78126
  string = string.replace(regexAsciiWhitelist, function(symbol) {
76724
- if (useNamedReferences && has(encodeMap, symbol)) {
78127
+ if (useNamedReferences && has2(encodeMap, symbol)) {
76725
78128
  return "&" + encodeMap[symbol] + ";";
76726
78129
  }
76727
78130
  return escapeBmpSymbol(symbol);
@@ -76846,7 +78249,7 @@ var require_he = __commonJS({
76846
78249
  freeModule.exports = he;
76847
78250
  } else {
76848
78251
  for (var key in he) {
76849
- has(he, key) && (freeExports[key] = he[key]);
78252
+ has2(he, key) && (freeExports[key] = he[key]);
76850
78253
  }
76851
78254
  }
76852
78255
  } else {