@validate.qa/runner 1.0.103 → 1.0.104

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 (32) hide show
  1. package/README.md +7 -2
  2. package/dist/{chunk-YI3ZC2XI.mjs → chunk-6N4QCCEH.mjs} +2 -2
  3. package/dist/{chunk-CMM4DOXC.mjs → chunk-EBWZ7NGU.mjs} +11 -8
  4. package/dist/{chunk-OTKJZIOD.mjs → chunk-EKY6CN4Y.mjs} +659 -122
  5. package/dist/{chunk-CG75I3ZZ.mjs → chunk-MEE4DXRX.mjs} +13437 -1386
  6. package/dist/{chunk-WVLBQDFP.mjs → chunk-OXDU6ARE.mjs} +8 -2
  7. package/dist/{chunk-AAUHXGDS.mjs → chunk-PUMRAFJS.mjs} +37 -13
  8. package/dist/{chunk-BECCGMU4.mjs → chunk-TGVKABFN.mjs} +45 -3
  9. package/dist/{chunk-YIM746WO.mjs → chunk-TS4OUJVT.mjs} +1 -1
  10. package/dist/{chunk-NNJYKR3O.mjs → chunk-UR7KDOTP.mjs} +29 -6
  11. package/dist/{chunk-36AWDKPL.mjs → chunk-UZ67UBXH.mjs} +14 -3
  12. package/dist/{chunk-7SH3COMQ.mjs → chunk-VWE3SQ2Z.mjs} +1 -1
  13. package/dist/{chunk-TK5KEGNW.mjs → chunk-XXZZMG6Q.mjs} +15 -3
  14. package/dist/cli.js +20962 -7343
  15. package/dist/cli.mjs +1159 -245
  16. package/dist/credential-mcp-server.js +671 -127
  17. package/dist/credential-mcp-server.mjs +3 -3
  18. package/dist/discovery-mcp-server.js +710 -129
  19. package/dist/discovery-mcp-server.mjs +4 -4
  20. package/dist/dist-BXHLIG5H.mjs +17 -0
  21. package/dist/inbox-mcp-server.js +664 -123
  22. package/dist/inbox-mcp-server.mjs +3 -3
  23. package/dist/route-seed-worker.js +671 -124
  24. package/dist/route-seed-worker.mjs +3 -3
  25. package/dist/run-write-guard-preload.js +713 -125
  26. package/dist/run-write-guard-preload.mjs +4 -4
  27. package/dist/run-write-guard.js +713 -125
  28. package/dist/run-write-guard.mjs +3 -3
  29. package/dist/tar-extract-child.js +38 -14
  30. package/dist/tar-extract-child.mjs +2 -2
  31. package/package.json +3 -2
  32. package/dist/dist-2G7YTNH5.mjs +0 -17
@@ -67609,7 +67609,13 @@ function buildSandboxedEnv(processEnv, testVariables) {
67609
67609
  return env;
67610
67610
  }
67611
67611
  function escapeEnvValue(value) {
67612
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
67612
+ if (!value.includes("\r")) {
67613
+ for (const quote of ["'", "`"]) {
67614
+ if (!value.includes(quote))
67615
+ return `${quote}${value}${quote}`;
67616
+ }
67617
+ }
67618
+ const escaped = value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
67613
67619
  return `"${escaped}"`;
67614
67620
  }
67615
67621
  var API_PREFIX, SUPPORT_EMAIL, PLATFORM_ADMIN_EMAIL, NOREPLY_EMAIL, EMAIL_FROM_IDENTITIES, ADMIN_EMAIL_LIMITS, AUDIO_CHUNK_DURATION_MS, ACTION_BATCH_INTERVAL_MS, ACTION_BATCH_MAX_SIZE, MOBILE_SCREENSHOT_INTERVAL_MS, MOBILE_ACTION_BATCH_MAX_SIZE, MOBILE_BRIDGE_PORT, MOBILE_SNAPSHOT_MAX_SIZE_BYTES, MOBILE_APP_ID_MAX_LENGTH, MOBILE_APP_ID_PATTERN, MOBILE_FOCUS_SETTLE_MS, MOBILE_POST_ACTION_SETTLE_MS, MOBILE_COLD_BOOT_SETTLE_MS, MOBILE_DEFAULT_SWIPE_DISTANCE, MOBILE_MAX_SWIPE_DISTANCE, SELECTOR_PRIORITIES, MOBILE_STEP_TYPES, BLOCKED_ENV_VARS, SPAWNED_ENV_ALLOWLIST, MAX_PLAYWRIGHT_CODE_LENGTH;
@@ -70531,6 +70537,65 @@ var init_discovery_har_decision = __esm({
70531
70537
  }
70532
70538
  });
70533
70539
 
70540
+ // ../shared/dist/regex-literal.js
70541
+ function opensRegexLiteral(code, i) {
70542
+ let j = i - 1;
70543
+ while (j >= 0 && (code[j] === " " || code[j] === " "))
70544
+ j--;
70545
+ if (j < 0 || code[j] === "\n" || code[j] === "\r")
70546
+ return true;
70547
+ return regexOpensAfter(code, j);
70548
+ }
70549
+ function regexOpensAfter(code, j) {
70550
+ const prev = code[j];
70551
+ if (REGEX_OPENERS.includes(prev))
70552
+ return !((prev === "+" || prev === "-") && code[j - 1] === prev);
70553
+ if (!/[a-z]/.test(prev))
70554
+ return false;
70555
+ let start = j;
70556
+ while (start > 0 && /[A-Za-z0-9_$]/.test(code[start - 1]))
70557
+ start--;
70558
+ if (start > 0 && code[start - 1] === ".")
70559
+ return false;
70560
+ return REGEX_KEYWORDS.has(code.slice(start, j + 1));
70561
+ }
70562
+ function regexLiteralEnd(code, i) {
70563
+ let k = i + 1;
70564
+ let inClass = false;
70565
+ while (k < code.length) {
70566
+ const ch = code[k];
70567
+ if (ch === "\n" || ch === "\r")
70568
+ return k;
70569
+ if (ch === "\\") {
70570
+ if (code[k + 1] === "\n" || code[k + 1] === "\r")
70571
+ return k + 1;
70572
+ k += 2;
70573
+ continue;
70574
+ }
70575
+ if (inClass) {
70576
+ if (ch === "]")
70577
+ inClass = false;
70578
+ } else if (ch === "[") {
70579
+ inClass = true;
70580
+ } else if (ch === "/") {
70581
+ k++;
70582
+ while (k < code.length && /[a-z]/i.test(code[k]))
70583
+ k++;
70584
+ return k;
70585
+ }
70586
+ k++;
70587
+ }
70588
+ return code.length;
70589
+ }
70590
+ var REGEX_OPENERS, REGEX_KEYWORDS;
70591
+ var init_regex_literal = __esm({
70592
+ "../shared/dist/regex-literal.js"() {
70593
+ "use strict";
70594
+ REGEX_OPENERS = "(,=:[!&|?{};+-*%~^<>";
70595
+ REGEX_KEYWORDS = /* @__PURE__ */ new Set(["return", "typeof", "case", "do", "else", "in", "of", "new", "delete", "void", "throw", "await", "yield", "instanceof"]);
70596
+ }
70597
+ });
70598
+
70534
70599
  // ../shared/dist/assertion-classifier.js
70535
70600
  function isButtonOrLinkLocator(line) {
70536
70601
  return /getByRole\s*\(\s*['"`](?:button|link)['"`]/i.test(line);
@@ -70839,7 +70904,7 @@ function classifyMobileAssertValue(value, context = {}) {
70839
70904
  return { tier: 2, reason: t2[1] };
70840
70905
  return null;
70841
70906
  }
70842
- function regexLiteralEnd(text, start) {
70907
+ function regexLiteralEnd2(text, start) {
70843
70908
  let inClass = false;
70844
70909
  for (let i = start + 1; i < text.length; i++) {
70845
70910
  const ch = text[i];
@@ -70865,7 +70930,7 @@ function parenDepthDelta(text) {
70865
70930
  let depth = 0;
70866
70931
  let quote = null;
70867
70932
  let escaped = false;
70868
- let prev = "";
70933
+ let prevAt = -1;
70869
70934
  let noRegexUntil = -1;
70870
70935
  for (let i = 0; i < text.length; i++) {
70871
70936
  const ch = text[i];
@@ -70880,7 +70945,7 @@ function parenDepthDelta(text) {
70880
70945
  if (quote) {
70881
70946
  if (ch === quote) {
70882
70947
  quote = null;
70883
- prev = ch;
70948
+ prevAt = i;
70884
70949
  }
70885
70950
  continue;
70886
70951
  }
@@ -70899,11 +70964,11 @@ function parenDepthDelta(text) {
70899
70964
  i = close + 1;
70900
70965
  continue;
70901
70966
  }
70902
- if (i > noRegexUntil && (prev === "" || REGEX_PRECEDER.has(prev))) {
70903
- const end = regexLiteralEnd(text, i);
70967
+ if (i > noRegexUntil && (prevAt < 0 || regexOpensAfter(text, prevAt))) {
70968
+ const end = regexLiteralEnd2(text, i);
70904
70969
  if (end > 0) {
70905
70970
  i = end - 1;
70906
- prev = "a";
70971
+ prevAt = end - 1;
70907
70972
  continue;
70908
70973
  }
70909
70974
  const nl = text.indexOf("\n", i);
@@ -70915,14 +70980,15 @@ function parenDepthDelta(text) {
70915
70980
  else if (ch === ")")
70916
70981
  depth--;
70917
70982
  if (ch !== " " && ch !== " " && ch !== "\n")
70918
- prev = ch;
70983
+ prevAt = i;
70919
70984
  }
70920
70985
  return depth;
70921
70986
  }
70922
- var PENDING_STATE_TEXT_REASON, PENDING_STATE_TEXT, TIER1_PATTERNS, TIER2_PATTERNS, TIER4_PATTERNS, SINGLE_QUOTED, DOUBLE_QUOTED, BACKTICK_QUOTED, QUOTED_BODY, LOCATOR_ARG_VALUE, LOCATOR_TEXT_ARG_PATTERNS, EXPECT_TOKEN, REGEX_PRECEDER;
70987
+ var PENDING_STATE_TEXT_REASON, PENDING_STATE_TEXT, TIER1_PATTERNS, TIER2_PATTERNS, TIER4_PATTERNS, SINGLE_QUOTED, DOUBLE_QUOTED, BACKTICK_QUOTED, QUOTED_BODY, LOCATOR_ARG_VALUE, LOCATOR_TEXT_ARG_PATTERNS, EXPECT_TOKEN;
70923
70988
  var init_assertion_classifier = __esm({
70924
70989
  "../shared/dist/assertion-classifier.js"() {
70925
70990
  "use strict";
70991
+ init_regex_literal();
70926
70992
  PENDING_STATE_TEXT_REASON = 'pending-state text ("Sending...") is transient';
70927
70993
  PENDING_STATE_TEXT = /\b(?!(?:Billing|Shipping|Pricing|Listing|Meeting|Morning|Evening|Warning|Building|Training|Marketing|Parking|Housing|Reading|Rating|Heading|Ceiling|Wedding|Clothing|Painting|Setting)\b)[A-Z][a-z]{2,}ing(?:\.{3}|…)/;
70928
70994
  TIER1_PATTERNS = [
@@ -70999,7 +71065,6 @@ var init_assertion_classifier = __esm({
70999
71065
  new RegExp(`\\.filter\\s*\\(\\s*\\{[^}]{0,200}?\\bhasText\\s*:\\s*${LOCATOR_ARG_VALUE}`, "gi")
71000
71066
  ];
71001
71067
  EXPECT_TOKEN = /\bexpect\s*[.(]/;
71002
- REGEX_PRECEDER = /* @__PURE__ */ new Set(["(", ",", "=", ":", "[", "!", "&", "|", "?", "{", "}", ";", "+", "-", "*", "%", "<", ">", "~", "^"]);
71003
71068
  }
71004
71069
  });
71005
71070
 
@@ -71007,14 +71072,80 @@ var init_assertion_classifier = __esm({
71007
71072
  function isExpectCall(code) {
71008
71073
  return EXPECT_CALL_RE.test(code);
71009
71074
  }
71075
+ function specCallsExpect(code) {
71076
+ if (EXPECT_CALL_RE.test(code) || SPACED_EXPECT_CALL_RE.test(code) || CONFIGURE_IN_PLACE_RE.test(code))
71077
+ return true;
71078
+ const names = /* @__PURE__ */ new Set();
71079
+ for (const clause of code.matchAll(IMPORT_CLAUSE_RE)) {
71080
+ for (const alias of clause[1].matchAll(EXPECT_ALIAS_RE))
71081
+ names.add(alias[1]);
71082
+ }
71083
+ for (const decl of code.matchAll(DERIVED_EXPECT_DECL_RE)) {
71084
+ if (names.size >= MAX_DERIVED_EXPECT_NAMES)
71085
+ break;
71086
+ const base2 = decl[2];
71087
+ if (base2 === void 0 || base2 === "expect" || names.has(base2))
71088
+ names.add(decl[1]);
71089
+ }
71090
+ names.delete("expect");
71091
+ if (names.size === 0)
71092
+ return false;
71093
+ const alts = [...names].map((n) => n.replace(/\$/g, "\\$")).join("|");
71094
+ return new RegExp(String.raw`(?<![\w$.])(?:${alts})${EXPECT_MODIFIERS}`).test(code);
71095
+ }
71010
71096
  function asPlainExpect(code) {
71011
71097
  return code.replace(/\bexpect\.soft(\s*\()/g, "expect$1");
71012
71098
  }
71013
- var EXPECT_CALL_RE;
71099
+ function expectAliasNames(code) {
71100
+ if (!MAY_ALIAS.test(code))
71101
+ return [];
71102
+ const imported = [];
71103
+ for (const m of code.matchAll(PW_IMPORT)) {
71104
+ for (const a of m[1].matchAll(IMPORT_AS_EXPECT))
71105
+ imported.push(a[1]);
71106
+ }
71107
+ const known = /* @__PURE__ */ new Set(["expect", ...imported]);
71108
+ const derived = [];
71109
+ for (const m of code.matchAll(DECLARATION)) {
71110
+ const rhs = DERIVED.exec(code.slice(m.index + m[0].length, m.index + m[0].length + 200));
71111
+ if (!rhs)
71112
+ continue;
71113
+ const base2 = rhs[1];
71114
+ const fromKnown = base2 === void 0 || (rhs[2] ? base2 === "test" : known.has(base2));
71115
+ if (!fromKnown || m[1] === "expect")
71116
+ continue;
71117
+ known.add(m[1]);
71118
+ derived.push(m[1]);
71119
+ }
71120
+ const fromImports = LOCAL_EXPECT.test(code) ? [] : imported;
71121
+ return [.../* @__PURE__ */ new Set([...fromImports, ...derived])].filter((n) => n !== "expect");
71122
+ }
71123
+ function withExpectAliasesAsExpect(code) {
71124
+ let out = code;
71125
+ for (const name of expectAliasNames(code)) {
71126
+ const escaped = name.replace(/\$/g, "\\$");
71127
+ out = out.replace(new RegExp(`(?<![\\w$.])${escaped}(?=\\s{0,20}(?:\\.\\s{0,20}(?:soft|poll)\\s{0,20})?\\()`, "g"), "expect");
71128
+ }
71129
+ return out;
71130
+ }
71131
+ var EXPECT_CALL_RE, EXPECT_MODIFIERS, SPACED_EXPECT_CALL_RE, CONFIGURE_IN_PLACE_RE, IMPORT_CLAUSE_RE, EXPECT_ALIAS_RE, DERIVED_EXPECT_DECL_RE, MAX_DERIVED_EXPECT_NAMES, PW_IMPORT, IMPORT_AS_EXPECT, DECLARATION, DERIVED, LOCAL_EXPECT, MAY_ALIAS;
71014
71132
  var init_expect_call = __esm({
71015
71133
  "../shared/dist/expect-call.js"() {
71016
71134
  "use strict";
71017
71135
  EXPECT_CALL_RE = /\bexpect(?:\.(?:soft|poll))?\s*\(/;
71136
+ EXPECT_MODIFIERS = String.raw`\s*(?:\.\s*soft\s*)?(?:\.\s*poll\s*)?\(`;
71137
+ SPACED_EXPECT_CALL_RE = new RegExp(String.raw`\bexpect${EXPECT_MODIFIERS}`);
71138
+ CONFIGURE_IN_PLACE_RE = new RegExp(String.raw`\bexpect\s*\.\s*configure\s*\(\s*(?:\{[^{}()]{0,500}\}\s*)?\)${EXPECT_MODIFIERS}`);
71139
+ IMPORT_CLAUSE_RE = /\bimport\s+(?:type\s+)?\{([^{}]{0,2000})\}/g;
71140
+ EXPECT_ALIAS_RE = /\bexpect\s+as\s+([A-Za-z_$][\w$]*)/g;
71141
+ DERIVED_EXPECT_DECL_RE = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:([A-Za-z_$][\w$]*)\s*\.\s*(?:configure|extend)|mergeExpects)\s*\(/g;
71142
+ MAX_DERIVED_EXPECT_NAMES = 20;
71143
+ PW_IMPORT = /\bimport\s{0,20}\{([^}]{0,4000})\}\s{0,20}from\s{0,20}['"]@playwright\/test['"]/g;
71144
+ IMPORT_AS_EXPECT = /(?:^|,)\s{0,20}expect\s{1,20}as\s{1,20}([A-Za-z_$][\w$]{0,99})\s{0,20}(?=,|$)/g;
71145
+ DECLARATION = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,99})\s{0,20}(?::[^=;\n]{0,200})?=\s{0,20}/g;
71146
+ DERIVED = /^(?:mergeExpects\s{0,20}\(|([A-Za-z_$][\w$]{0,99})(\s{0,20}\.\s{0,20}expect)?\s{0,20}\.\s{0,20}(?:configure|extend)\s{0,20}\(|test\s{0,20}\.\s{0,20}expect\b(?!\s{0,20}[.(]))/;
71147
+ LOCAL_EXPECT = /\b(?:const|let|var)\s{1,20}expect\b/;
71148
+ MAY_ALIAS = /\bexpect\s{1,20}as\b|\.\s{0,20}(?:configure|extend)\s{0,20}\(|\bmergeExpects\b|\btest\s{0,20}\.\s{0,20}expect\b/;
71018
71149
  }
71019
71150
  });
71020
71151
 
@@ -71044,65 +71175,6 @@ var init_braced_clause = __esm({
71044
71175
  }
71045
71176
  });
71046
71177
 
71047
- // ../shared/dist/regex-literal.js
71048
- function opensRegexLiteral(code, i) {
71049
- let j = i - 1;
71050
- while (j >= 0 && (code[j] === " " || code[j] === " "))
71051
- j--;
71052
- if (j < 0 || code[j] === "\n" || code[j] === "\r")
71053
- return true;
71054
- return regexOpensAfter(code, j);
71055
- }
71056
- function regexOpensAfter(code, j) {
71057
- const prev = code[j];
71058
- if (REGEX_OPENERS.includes(prev))
71059
- return !((prev === "+" || prev === "-") && code[j - 1] === prev);
71060
- if (!/[a-z]/.test(prev))
71061
- return false;
71062
- let start = j;
71063
- while (start > 0 && /[A-Za-z0-9_$]/.test(code[start - 1]))
71064
- start--;
71065
- if (start > 0 && code[start - 1] === ".")
71066
- return false;
71067
- return REGEX_KEYWORDS.has(code.slice(start, j + 1));
71068
- }
71069
- function regexLiteralEnd2(code, i) {
71070
- let k = i + 1;
71071
- let inClass = false;
71072
- while (k < code.length) {
71073
- const ch = code[k];
71074
- if (ch === "\n" || ch === "\r")
71075
- return k;
71076
- if (ch === "\\") {
71077
- if (code[k + 1] === "\n" || code[k + 1] === "\r")
71078
- return k + 1;
71079
- k += 2;
71080
- continue;
71081
- }
71082
- if (inClass) {
71083
- if (ch === "]")
71084
- inClass = false;
71085
- } else if (ch === "[") {
71086
- inClass = true;
71087
- } else if (ch === "/") {
71088
- k++;
71089
- while (k < code.length && /[a-z]/i.test(code[k]))
71090
- k++;
71091
- return k;
71092
- }
71093
- k++;
71094
- }
71095
- return code.length;
71096
- }
71097
- var REGEX_OPENERS, REGEX_KEYWORDS;
71098
- var init_regex_literal = __esm({
71099
- "../shared/dist/regex-literal.js"() {
71100
- "use strict";
71101
- REGEX_OPENERS = "(,=:[!&|?{};+-*%~^<>";
71102
- REGEX_KEYWORDS = /* @__PURE__ */ new Set(["return", "typeof", "case", "do", "else", "in", "of", "new", "delete", "void", "throw", "await", "yield", "instanceof"]);
71103
- }
71104
- });
71105
-
71106
71178
  // ../shared/dist/assertion-proof.js
71107
71179
  function fragmentKindOf(literal, fromText) {
71108
71180
  const text = literal.trim();
@@ -71261,7 +71333,7 @@ function stripCoverageComments(code) {
71261
71333
  let copyFrom = 0;
71262
71334
  let i = 0;
71263
71335
  let quote = null;
71264
- let prev = "";
71336
+ let prevAt = -1;
71265
71337
  let noRegexBefore = 0;
71266
71338
  while (i < code.length) {
71267
71339
  const ch = code[i];
@@ -71272,17 +71344,18 @@ function stripCoverageComments(code) {
71272
71344
  }
71273
71345
  if (ch === quote || ch === "\n" && quote !== "`") {
71274
71346
  quote = null;
71275
- prev = ch === "\n" ? prev : ch;
71347
+ if (ch !== "\n")
71348
+ prevAt = i;
71276
71349
  }
71277
71350
  i++;
71278
71351
  continue;
71279
71352
  }
71280
71353
  const next = code[i + 1];
71281
- if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
71354
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prevAt < 0 || regexOpensAfter(code, prevAt))) {
71282
71355
  const end = regexLiteralEnd3(code, i);
71283
71356
  if (end > 0) {
71284
71357
  i = end;
71285
- prev = "a";
71358
+ prevAt = end - 1;
71286
71359
  continue;
71287
71360
  }
71288
71361
  const eol = code.indexOf("\n", i);
@@ -71307,7 +71380,7 @@ function stripCoverageComments(code) {
71307
71380
  if (ch === '"' || ch === "'" || ch === "`")
71308
71381
  quote = ch;
71309
71382
  if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
71310
- prev = ch;
71383
+ prevAt = i;
71311
71384
  i++;
71312
71385
  }
71313
71386
  out.push(code.slice(copyFrom));
@@ -71996,11 +72069,12 @@ function assessTitleCoverage(title, code) {
71996
72069
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
71997
72070
  return { covered: hit >= needed || repeatedHit, missingTerms };
71998
72071
  }
71999
- var COVERAGE_STOP_WORDS, MAX_ASSERTION_JOIN_CHARS, MAX_COVERAGE_LINE_CHARS, REGEX_PRECEDER2, BLOCK_SCAFFOLD, cachedAssertionInput, cachedAssertionSource, LOCAL_BINDING, ASSERTED_STATUS_RE, MAX_RESOLVED_NAMES_PER_LINE, WORD_CHAR_RE, NOT_CONTAIN_TEXT_RE, PLACE_PHRASE_RE, PLACE_WORD_MIN_CHARS, PLACE_FUNCTION_WORDS, HIDDEN_STATE_TERMS, HIDDEN_ELEMENT_LINE_RE, HIDDEN_LINE_RE, MAX_SHOWN_EARLIER_LINES, SHOWN_EARLIER_LITERAL_RE, SHARED_PREFIX_MIN_CHARS, MAX_SHOWN_EARLIER_LITERALS, VALUE_SHAPE_RE, VALUE_SHAPE_ALL_RE, TITLE_TERM_MIN;
72072
+ 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;
72000
72073
  var init_expected_outcome_coverage = __esm({
72001
72074
  "../shared/dist/expected-outcome-coverage.js"() {
72002
72075
  "use strict";
72003
72076
  init_assertion_classifier();
72077
+ init_regex_literal();
72004
72078
  COVERAGE_STOP_WORDS = /* @__PURE__ */ new Set([
72005
72079
  "after",
72006
72080
  "before",
@@ -72033,7 +72107,6 @@ var init_expected_outcome_coverage = __esm({
72033
72107
  ]);
72034
72108
  MAX_ASSERTION_JOIN_CHARS = 8e3;
72035
72109
  MAX_COVERAGE_LINE_CHARS = 2e3;
72036
- REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
72037
72110
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
72038
72111
  cachedAssertionInput = null;
72039
72112
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -72129,7 +72202,7 @@ function replaceBlockComments(code, replacer) {
72129
72202
  continue;
72130
72203
  const next = code[i + 1];
72131
72204
  if (next !== "/" && next !== "*" && opensRegexLiteral(code, i)) {
72132
- i = regexLiteralEnd2(code, i) - 1;
72205
+ i = regexLiteralEnd(code, i) - 1;
72133
72206
  continue;
72134
72207
  }
72135
72208
  if (next === "/" && !(code[i - 1] === ":" && /[A-Za-z]/.test(code[i - 2] ?? ""))) {
@@ -72304,10 +72377,41 @@ var init_auth_handoff_coverage = __esm({
72304
72377
  });
72305
72378
 
72306
72379
  // ../shared/dist/auth-plan.js
72307
- function hasInlineLoginSteps(playwrightCode) {
72380
+ function stripStampedHelperRegions(code) {
72381
+ let out = code;
72382
+ for (const [open, close] of STAMPED_HELPER_REGIONS) {
72383
+ if (out.indexOf(open) === -1)
72384
+ continue;
72385
+ let kept = "";
72386
+ let i = 0;
72387
+ while (i < out.length) {
72388
+ const start = out.indexOf(open, i);
72389
+ if (start === -1) {
72390
+ kept += out.slice(i);
72391
+ break;
72392
+ }
72393
+ const end = out.indexOf(close, start + open.length);
72394
+ if (end === -1) {
72395
+ kept += out.slice(i);
72396
+ break;
72397
+ }
72398
+ const lineEnd = out.indexOf("\n", end);
72399
+ kept += out.slice(i, start);
72400
+ i = lineEnd === -1 ? out.length : lineEnd + 1;
72401
+ }
72402
+ out = kept;
72403
+ }
72404
+ return out;
72405
+ }
72406
+ function signsInInline(playwrightCode) {
72308
72407
  if (!playwrightCode)
72309
72408
  return false;
72310
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
72409
+ if (!/process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode))
72410
+ return false;
72411
+ if (/process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode))
72412
+ return true;
72413
+ const body = stripStampedHelperRegions(playwrightCode);
72414
+ return /process\.env\.TEST_USER_EMAIL/.test(body) && /__vqaWaitForEmail\s*\(/.test(body);
72311
72415
  }
72312
72416
  function hasGeneratedAuthGuard(playwrightCode) {
72313
72417
  if (!playwrightCode)
@@ -72326,7 +72430,7 @@ function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
72326
72430
  if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
72327
72431
  return true;
72328
72432
  const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
72329
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
72433
+ const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && signsInInline(code);
72330
72434
  return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
72331
72435
  }
72332
72436
  function codeReadsStoredCredentials(code) {
@@ -72447,7 +72551,7 @@ function authTypeTag(tags) {
72447
72551
  const explicit = explicitAuthTypeTags(t);
72448
72552
  return explicit.length === 1 ? explicit[0] : void 0;
72449
72553
  }
72450
- var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
72554
+ 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, STAMPED_HELPER_REGIONS;
72451
72555
  var init_auth_plan = __esm({
72452
72556
  "../shared/dist/auth-plan.js"() {
72453
72557
  "use strict";
@@ -72474,6 +72578,10 @@ var init_auth_plan = __esm({
72474
72578
  };
72475
72579
  TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
72476
72580
  API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
72581
+ STAMPED_HELPER_REGIONS = [
72582
+ ["// >>> VALIDATE_QA_INBOX", "// <<< VALIDATE_QA_INBOX"],
72583
+ ["// >>> VALIDATE_QA_SMS", "// <<< VALIDATE_QA_SMS"]
72584
+ ];
72477
72585
  }
72478
72586
  });
72479
72587
 
@@ -72482,6 +72590,9 @@ function parseLastActionProofMode(raw) {
72482
72590
  const v = (raw ?? "").trim().toLowerCase();
72483
72591
  return v === "on" || v === "shadow" ? v : "off";
72484
72592
  }
72593
+ function parseAbsenceProofMode(raw) {
72594
+ return parseLastActionProofMode(raw);
72595
+ }
72485
72596
  function isUrlProof(stmt) {
72486
72597
  if (URL_PROOF.test(stmt))
72487
72598
  return true;
@@ -72869,6 +72980,65 @@ function isVacuousAccountAbsence(stmt, shown, account) {
72869
72980
  }
72870
72981
  return true;
72871
72982
  }
72983
+ function isAbsenceCheck(stmt) {
72984
+ if (!claimsAbsence(stmt))
72985
+ return false;
72986
+ const bare = stmt.replace(NOT_BEFORE_MATCHER, ".");
72987
+ return ABSENCE_READ.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
72988
+ }
72989
+ function comparableSubject(subject) {
72990
+ let s = subject.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
72991
+ for (let prev = ""; prev !== s; ) {
72992
+ prev = s;
72993
+ s = s.replace(/\.(?:count|isVisible|isHidden|first|last)\(\)$/, "").replace(/\.nth\(\d+\)$/, "");
72994
+ }
72995
+ return s;
72996
+ }
72997
+ function subjectsThatExisted(checks, interactionStmts) {
72998
+ const out = [];
72999
+ const bound = locatorBindings(interactionStmts);
73000
+ const resolve = (expr) => {
73001
+ for (let depth = 0; depth < 4; depth++) {
73002
+ const lead = expr.match(/^\s*(?:await\s+)?([A-Za-z_$][\w$]*)(?![\w$(])/);
73003
+ const to = lead ? bound.get(lead[1]) : void 0;
73004
+ if (!lead || to === void 0)
73005
+ break;
73006
+ expr = `${to.replace(/;\s*$/, "")}${expr.slice(lead[0].length)}`;
73007
+ }
73008
+ return expr;
73009
+ };
73010
+ for (const { stmt } of checks) {
73011
+ if (isAbsenceCheck(stmt))
73012
+ continue;
73013
+ const subject = extractExpectSubject(stmt);
73014
+ if (subject)
73015
+ out.push(comparableSubject(subject));
73016
+ }
73017
+ for (const stmt of interactionStmts) {
73018
+ if (EXPECT_CALL_RE.test(stmt))
73019
+ continue;
73020
+ for (const m of stmt.matchAll(LOCATOR_USE)) {
73021
+ if (m[0].includes("waitFor") && /state\s*:\s*['"`](?:hidden|detached)/.test(stmt.slice(m.index)))
73022
+ continue;
73023
+ const before = stmt.slice(0, m.index).replace(/^\s*(?:(?:const|let|var)\s+[\w$]+\s*=|return\b)\s*/, "");
73024
+ if (before.trim())
73025
+ out.push(comparableSubject(resolve(before)));
73026
+ }
73027
+ }
73028
+ return out;
73029
+ }
73030
+ function isUnshownAbsence(stmt, existed, entered) {
73031
+ if (!isAbsenceCheck(stmt))
73032
+ return false;
73033
+ const raw = extractExpectSubject(stmt);
73034
+ if (!raw || !/\b(?:locator|getBy[A-Z]\w*|frameLocator)\s*\(/.test(raw))
73035
+ return false;
73036
+ const subject = comparableSubject(raw);
73037
+ if (existed.some((e) => e === subject || e.startsWith(`${subject}.`)))
73038
+ return false;
73039
+ const named = raw.match(/['"`][^'"`]+['"`]/g) ?? [];
73040
+ return !named.some((q) => entered.values.has(q.slice(1, -1).toLowerCase()));
73041
+ }
72872
73042
  function endOfEnclosingCall(stmt, from) {
72873
73043
  let depth = 0;
72874
73044
  for (let i = from; i < stmt.length; i++) {
@@ -73368,10 +73538,31 @@ function comparesWithItself(subject, tail) {
73368
73538
  const expected = squash2(args);
73369
73539
  return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
73370
73540
  }
73541
+ function ariaSnapshotTexts(template) {
73542
+ const out = [];
73543
+ for (const raw of template.split(ARIA_ENTRY_SPLIT)) {
73544
+ const m = ARIA_ENTRY.exec(raw.trim());
73545
+ if (!m || !ARIA_CONTENT_ROLES.has(m[1]))
73546
+ continue;
73547
+ for (const text of [m[2], m[3]]) {
73548
+ const t = text?.replace(/\\(.)/g, "$1").replace(/^["']|["']$/g, "").trim();
73549
+ if (!t || t.startsWith("/") || !/[\p{L}\p{N}]/u.test(t) || /["'`\\]/.test(t))
73550
+ continue;
73551
+ out.push(t);
73552
+ }
73553
+ }
73554
+ return out;
73555
+ }
73556
+ function presenceTwin(stmt) {
73557
+ return stmt.replace(NOT_HIDDEN, ".toBeVisible(").replace(NOT_COUNT_ZERO, ".toBeVisible(");
73558
+ }
73371
73559
  function asPlainAssertion(s) {
73372
73560
  return plainAssertion(s, false);
73373
73561
  }
73374
73562
  function plainAssertion(s, keepSelfCompare) {
73563
+ const twin = presenceTwin(s.stmt);
73564
+ if (twin !== s.stmt)
73565
+ return plainAssertion({ ...s, stmt: twin }, keepSelfCompare);
73375
73566
  const { stmt } = s;
73376
73567
  if (COUNT_AT_LEAST_ZERO.test(stmt))
73377
73568
  return [];
@@ -73382,6 +73573,12 @@ function plainAssertion(s, keepSelfCompare) {
73382
73573
  const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
73383
73574
  const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
73384
73575
  const as = (rewritten) => [{ ...s, stmt: rewritten }];
73576
+ const aria = ARIA_TEMPLATE_CALL.exec(tail);
73577
+ if (aria && !NEGATED_ASSERT.test(stmt)) {
73578
+ const texts = ariaSnapshotTexts(aria[2]);
73579
+ if (texts.length > 0)
73580
+ return texts.map((t) => ({ ...s, stmt: `await expect(${plain}).toContainText(${JSON.stringify(t)});` }));
73581
+ }
73385
73582
  if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
73386
73583
  return [];
73387
73584
  if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
@@ -73688,7 +73885,8 @@ function teardownLines(lines) {
73688
73885
  }
73689
73886
  return out;
73690
73887
  }
73691
- function validateAssertionStrength(code, opts = {}) {
73888
+ function validateAssertionStrength(source, opts = {}) {
73889
+ const code = withExpectAliasesAsExpect(source);
73692
73890
  const body = stripLoginScaffolding(code);
73693
73891
  const lines = body.split("\n");
73694
73892
  const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
@@ -73841,8 +74039,23 @@ function validateAssertionStrength(code, opts = {}) {
73841
74039
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
73842
74040
  const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
73843
74041
  const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
74042
+ const absenceProof = opts.absenceProof ?? "off";
74043
+ let absenceOnly = false;
74044
+ if (absenceProof !== "off" && proof.kind === "durable") {
74045
+ const existed = subjectsThatExisted(checks, interactionStmtsRaw);
74046
+ const kept = checks.filter(({ stmt }) => !isUnshownAbsence(stmt, existed, entered));
74047
+ absenceOnly = kept.length < checks.length && classifyOutcomeProof(kept, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn).kind !== "durable";
74048
+ if (absenceOnly && absenceProof === "on") {
74049
+ return {
74050
+ ok: false,
74051
+ severity: mutated ? "block" : "downgrade",
74052
+ reason: "the only outcome checks are absence checks (not visible / hidden / count 0) of something the test never showed, touched or typed: they pass whether or not the action worked \u2014 assert what the action produced (the new text, value, count or page), or assert the element present before it goes",
74053
+ signals: base2({ interacted, mutated, navOnly, hasOutcomeProof: false, terminal, absenceOnly: true })
74054
+ };
74055
+ }
74056
+ }
73844
74057
  if (proof.kind === "durable") {
73845
- const signals2 = base2({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
74058
+ const signals2 = base2({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal, ...absenceOnly ? { absenceOnly } : {} });
73846
74059
  const { last, prev } = acts;
73847
74060
  if (teardown !== null && last !== null && acts.count >= 2 && last.kind !== "fill" && (last.kind === "enter" || isCommitClick(interactionStmts[last.i]) && !(prev?.kind === "click" && isDestructiveClick(interactionStmts[prev.i])))) {
73848
74061
  const lastLine = interactionStmtsWithLines[last.i].startLine;
@@ -73946,7 +74159,7 @@ function validateAssertionStrength(code, opts = {}) {
73946
74159
  signals
73947
74160
  };
73948
74161
  }
73949
- var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, POLL_OPEN, POLL_PAGE_URL, POLL_PAGE_URL_ONLY, POLL_CALLS_MAX, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, NEGATED_ASSERT_NOT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, VALUE_ABSENCE, OPTION_ABSENCE, NOT_BEFORE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, SCOPING_STEP, NEGATED_TEXT, ASYNC_DESTRUCTURE_OPEN, REQUEST_WORD, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, ENV_READ, ENTERED_ENV, HAVE_VALUE_ENV, ACCOUNT_KEYS, ACCOUNT_ENV, ACCOUNT_CONST, IN_FLIGHT_TEXT, LOCATOR_VALUE_READ, LOCATOR_EXPR, ALWAYS_TRUE_ON_OBJECT, COUNT_AT_LEAST_ZERO, DEFINED_VALUE_READ, ALWAYS_DEFINED, SELF_COMPARE_MATCHER, IS_VISIBLE_TRUE, PAGE_VALUE_MATCHER, COUNT_ONE, URL_CHECK_ARG, PLAIN_PAGE_PATH, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, ACCOUNT_EMAIL_ENV, ENV_PASSWORD_ENTRY, ACCOUNT_CONTROL_ROLE, ASSERT_OR_WAIT, ACTS, actLinesOf, CLOSERS_ONLY, TEST_BODY_OPEN, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD, COMMIT_CONTROL, NOT_COMMIT_CONTROL, DESTRUCTIVE_CONTROL, ENTER_PRESS, CONTROL_LABEL, isCommitClick, isDestructiveClick, TEARDOWN_OPEN;
74162
+ 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, ABSENCE_READ, LOCATOR_USE, 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, ARIA_TEMPLATE_CALL, ARIA_ENTRY_SPLIT, ARIA_CONTENT_ROLES, ARIA_ENTRY, NOT_HIDDEN, NOT_COUNT_ZERO, 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;
73950
74163
  var init_assertion_strength = __esm({
73951
74164
  "../shared/dist/assertion-strength.js"() {
73952
74165
  "use strict";
@@ -74035,6 +74248,8 @@ var init_assertion_strength = __esm({
74035
74248
  NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
74036
74249
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
74037
74250
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
74251
+ ABSENCE_READ = /\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)|\.toBeAttached\s*\(|\.toHaveText\s*\(|\.toContainText\s*\(/;
74252
+ LOCATOR_USE = /\.\s*(?:click|dblclick|tap|hover|focus|fill|clear|press|pressSequentially|type|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|selectText|waitFor)\s*\(/g;
74038
74253
  SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
74039
74254
  NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
74040
74255
  ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
@@ -74076,6 +74291,29 @@ var init_assertion_strength = __esm({
74076
74291
  IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
74077
74292
  PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
74078
74293
  COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
74294
+ ARIA_TEMPLATE_CALL = /^\)\s*\.\s*toMatchAriaSnapshot\s*\(\s*(['"`])((?:\\[\s\S]|(?!\1)[^\\])*)\1/;
74295
+ ARIA_ENTRY_SPLIT = /(?:^|\s)-\s+(?=\/?[a-z][a-z-]*(?:\s|:|\[|$))/;
74296
+ ARIA_CONTENT_ROLES = /* @__PURE__ */ new Set([
74297
+ "listitem",
74298
+ "row",
74299
+ "cell",
74300
+ "gridcell",
74301
+ "article",
74302
+ "treeitem",
74303
+ "paragraph",
74304
+ "text",
74305
+ "strong",
74306
+ "emphasis",
74307
+ "code",
74308
+ "blockquote",
74309
+ "term",
74310
+ "definition",
74311
+ "alert",
74312
+ "status"
74313
+ ]);
74314
+ ARIA_ENTRY = /^([a-z][a-z-]*)(?:\s+"((?:\\.|[^"\\])*)")?(?:\s*\[[^\]]*\])*\s*(?::\s*(\S.*)?)?$/s;
74315
+ NOT_HIDDEN = /\.not\s*\.\s*toBeHidden\s*\(/g;
74316
+ NOT_COUNT_ZERO = /\.not\s*\.\s*toHaveCount\s*\(\s*0\s*(?:,\s*|(?=\)))/g;
74079
74317
  URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
74080
74318
  PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
74081
74319
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
@@ -74121,6 +74359,12 @@ function isCancelledReason(error2) {
74121
74359
  function isHeldHealRun(run) {
74122
74360
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
74123
74361
  }
74362
+ function mayBeUnverifiedHealPass(run, tags) {
74363
+ return !!run && run.status === "PASSED" && run.mode === "heal" && !isHeldHealRun(run) && (tags ?? []).includes(NEEDS_REVIEW_TAG);
74364
+ }
74365
+ function isUnverifiedHealPass(run, tags, healConfidence) {
74366
+ return mayBeUnverifiedHealPass(run, tags) && typeof healConfidence === "string" && UNVERIFIED_HEAL_CONFIDENCES.has(healConfidence);
74367
+ }
74124
74368
  function isRunInterruptedError(error2) {
74125
74369
  return typeof error2 === "string" && RUN_INTERRUPTED_ERROR_PREFIXES.some((prefix) => error2.startsWith(prefix));
74126
74370
  }
@@ -74207,12 +74451,25 @@ function classifyErrorMessage(error2) {
74207
74451
  return "config_issue";
74208
74452
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
74209
74453
  return "email_not_received";
74454
+ if (SMS_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
74455
+ return "email_not_received";
74210
74456
  if (isCancelledReason(error2))
74211
74457
  return "cancelled";
74212
74458
  if (isRetriedRunInterruption(error2))
74213
74459
  return "interrupted";
74214
74460
  return null;
74215
74461
  }
74462
+ function isSmsNotReceivedError(error2) {
74463
+ if (typeof error2 !== "string")
74464
+ return false;
74465
+ const normalized = error2.replace(/^\s+/, "").replace(/^Error:\s*/i, "");
74466
+ return SMS_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix));
74467
+ }
74468
+ function blockedRunLogLine(error2, durationMs) {
74469
+ const reason = classifyErrorMessage(error2);
74470
+ const tail = reason === "rate_limit" ? "rate-limit detected, not counted as failure" : reason === "config_issue" ? "config issue (needs setup or sign-in), not counted as failure" : reason === "email_not_received" ? "verification email not received, not counted as failure" : reason === "cancelled" ? "cancelled, not counted as failure" : reason === "interrupted" ? "run interrupted, not counted as failure" : "run error";
74471
+ return `BLOCKED (${durationMs}ms) \u2014 ${tail}`;
74472
+ }
74216
74473
  function stripEnvironmentalPrefix(error2) {
74217
74474
  for (const prefix of ENVIRONMENTAL_ERROR_PREFIXES) {
74218
74475
  if (error2.startsWith(prefix)) {
@@ -74275,8 +74532,19 @@ function computePassRate(passed, tested) {
74275
74532
  }
74276
74533
  function summarizeHealth(items) {
74277
74534
  const c = { passed: 0, failed: 0, errored: 0, environmental: 0, never_run: 0 };
74535
+ let rateLimited = 0;
74536
+ let configIssue = 0;
74278
74537
  for (const item of items) {
74279
- c[classifyTestHealth(item.run)] += item.weight ?? 1;
74538
+ const weight = item.weight ?? 1;
74539
+ const bucket = classifyTestHealth(item.run);
74540
+ c[bucket] += weight;
74541
+ if (bucket === "environmental") {
74542
+ const reason = classifyErrorMessage(item.run?.error);
74543
+ if (reason === "rate_limit")
74544
+ rateLimited += weight;
74545
+ else if (isConfigLikeReason(reason))
74546
+ configIssue += weight;
74547
+ }
74280
74548
  }
74281
74549
  const tested = c.passed + c.failed + c.errored;
74282
74550
  return {
@@ -74284,6 +74552,8 @@ function summarizeHealth(items) {
74284
74552
  failed: c.failed,
74285
74553
  errored: c.errored,
74286
74554
  environmental: c.environmental,
74555
+ rateLimited,
74556
+ configIssue,
74287
74557
  neverRun: c.never_run,
74288
74558
  tested,
74289
74559
  total: tested + c.environmental + c.never_run,
@@ -74320,7 +74590,7 @@ function reportedBugWhere() {
74320
74590
  function bugReviewStatus(status) {
74321
74591
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
74322
74592
  }
74323
- var USER_CANCELLED_RUN_ERROR, PLATFORM_SUPPORT_CANCELLED_RUN_ERROR, PLATFORM_ADMIN_CANCELLED_RUN_ERROR, ADMIN_FORCE_CANCELLED_RUN_ERROR, CHAIN_CANCELLED_RUN_ERROR, RETRY_HELD_RUN_ERROR, CANCELLED_REASON_PREFIXES, isUserCancelledError, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, TEST_DID_NOT_RUN_ERROR, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, FEATURE_ROW_RUN_WINDOW, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
74593
+ 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, UNVERIFIED_HEAL_CONFIDENCES, NEEDS_REVIEW_TAG, 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, SMS_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;
74324
74594
  var init_run_classification = __esm({
74325
74595
  "../shared/dist/run-classification.js"() {
74326
74596
  "use strict";
@@ -74340,6 +74610,8 @@ var init_run_classification = __esm({
74340
74610
  ];
74341
74611
  isUserCancelledError = isCancelledReason;
74342
74612
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
74613
+ UNVERIFIED_HEAL_CONFIDENCES = /* @__PURE__ */ new Set(["low", "medium"]);
74614
+ NEEDS_REVIEW_TAG = "@needs-review";
74343
74615
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
74344
74616
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
74345
74617
  RUN_LOST_ERROR_PREFIX = "Runner lost this run";
@@ -74360,10 +74632,12 @@ var init_run_classification = __esm({
74360
74632
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
74361
74633
  "VERIFICATION_EMAIL_HAS_NO_LINK:"
74362
74634
  ];
74635
+ SMS_NOT_RECEIVED_PREFIXES = ["VERIFICATION_SMS_NOT_RECEIVED:"];
74363
74636
  ENVIRONMENTAL_ERROR_PREFIXES = [
74364
74637
  "RATE_LIMIT_BLOCKED:",
74365
74638
  "CONFIG_ISSUE:",
74366
- ...EMAIL_NOT_RECEIVED_PREFIXES
74639
+ ...EMAIL_NOT_RECEIVED_PREFIXES,
74640
+ ...SMS_NOT_RECEIVED_PREFIXES
74367
74641
  ];
74368
74642
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
74369
74643
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
@@ -76748,6 +77022,19 @@ function googleBrokerUnavailableLine(lines, testError) {
76748
77022
  }
76749
77023
  return last;
76750
77024
  }
77025
+ function googleSignInRefusedLine(lines) {
77026
+ let last = null;
77027
+ for (const raw of lines ?? []) {
77028
+ for (const line of String(raw).split("\n")) {
77029
+ const text = line.trim();
77030
+ if (GOOGLE_SIGN_IN_REFUSED.test(text))
77031
+ last = text;
77032
+ else if (GOOGLE_SIGNED_IN.test(text))
77033
+ last = null;
77034
+ }
77035
+ }
77036
+ return last;
77037
+ }
76751
77038
  function stepConsoleLines(result) {
76752
77039
  const steps = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.stepResults) ? result.stepResults : [];
76753
77040
  return steps.flatMap((step) => {
@@ -76755,7 +77042,23 @@ function stepConsoleLines(result) {
76755
77042
  return Array.isArray(logs) ? logs.filter((line) => typeof line === "string") : [];
76756
77043
  });
76757
77044
  }
76758
- var GOOGLE_BROKER_UNAVAILABLE, GOOGLE_BROKER_ASKED, GOOGLE_SIGNED_IN, TIMEOUT_ERROR;
77045
+ function googleSignInRefusedReason(lines) {
77046
+ let reason = null;
77047
+ for (const raw of lines ?? []) {
77048
+ for (const line of String(raw).split("\n")) {
77049
+ const text = line.trim();
77050
+ if (GOOGLE_SIGNED_IN.test(text)) {
77051
+ reason = null;
77052
+ continue;
77053
+ }
77054
+ const m = GOOGLE_SIGN_IN_ANSWER.exec(text);
77055
+ if (m)
77056
+ reason = m[1];
77057
+ }
77058
+ }
77059
+ return reason;
77060
+ }
77061
+ var GOOGLE_BROKER_UNAVAILABLE, GOOGLE_BROKER_ASKED, GOOGLE_SIGNED_IN, TIMEOUT_ERROR, GOOGLE_SIGN_IN_REFUSED, GOOGLE_SIGN_IN_ANSWER;
76759
77062
  var init_google_broker_lines = __esm({
76760
77063
  "../shared/dist/google-broker-lines.js"() {
76761
77064
  "use strict";
@@ -76763,6 +77066,8 @@ var init_google_broker_lines = __esm({
76763
77066
  GOOGLE_BROKER_ASKED = /^\[google-sign-in\] the app started Google sign-in; asking the broker/;
76764
77067
  GOOGLE_SIGNED_IN = /^\[google-sign-in\] returned to the app with Google sign-in/;
76765
77068
  TIMEOUT_ERROR = /Test timeout of \d+ms exceeded|TimeoutError|Timeout:? \d+ ?ms/i;
77069
+ GOOGLE_SIGN_IN_REFUSED = /^\[google-sign-in\] (?:not_seeded|not_enabled|expired|needs_reseed|device_approval|needs_verification|policy_rejected|unsupported_flow|unsupported_one_tap): /;
77070
+ GOOGLE_SIGN_IN_ANSWER = /^\[google-sign-in\] ([a-z][a-z0-9_]*): /;
76766
77071
  }
76767
77072
  });
76768
77073
 
@@ -76814,22 +77119,87 @@ function isIntentionalNegativeAuthTest(input) {
76814
77119
  const meta = [input.testName, input.description].filter(Boolean).join(" ");
76815
77120
  return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
76816
77121
  }
77122
+ function signInStepPassedBeforeFailure(steps) {
77123
+ const list = steps ?? [];
77124
+ const failedAt = list.findIndex((step) => typeof step.status === "string" && step.status !== "passed" && step.status !== "skipped");
77125
+ if (failedAt <= 0)
77126
+ return false;
77127
+ if (SIGN_IN_STEP_TITLE.test(list[failedAt].title ?? ""))
77128
+ return false;
77129
+ return list.slice(0, failedAt).some((step) => step.status === "passed" && SIGN_IN_STEP_TITLE.test(step.title ?? ""));
77130
+ }
77131
+ function loginFormAlertTexts(body) {
77132
+ const text = body.length > ERROR_CONTEXT_CAP ? body.slice(0, ERROR_CONTEXT_CAP) : body;
77133
+ const blocks = [];
77134
+ let open = null;
77135
+ for (const line of text.split("\n")) {
77136
+ const fence = /^```(\w*)$/.exec(line.trim());
77137
+ if (fence) {
77138
+ if (open) {
77139
+ blocks.push(open);
77140
+ open = null;
77141
+ } else if (fence[1].toLowerCase() === "yaml")
77142
+ open = [];
77143
+ continue;
77144
+ }
77145
+ if (open)
77146
+ open.push(line);
77147
+ }
77148
+ const alerts = [];
77149
+ for (const lines of blocks) {
77150
+ if (!lines.some((l) => SNAPSHOT_PASSWORD_FIELD.test(l)))
77151
+ continue;
77152
+ for (let i = 0; i < lines.length && alerts.length < MAX_ALERTS; i++) {
77153
+ const m = SNAPSHOT_ALERT.exec(lines[i]);
77154
+ if (!m)
77155
+ continue;
77156
+ const indent = m[1].length;
77157
+ const parts = [m[2]];
77158
+ let j = i + 1;
77159
+ for (; j < lines.length; j++) {
77160
+ const lead = /^\s*/.exec(lines[j])?.[0].length ?? 0;
77161
+ if (lines[j].trim() === "" || lead <= indent)
77162
+ break;
77163
+ parts.push(lines[j].trim());
77164
+ }
77165
+ alerts.push(parts.join(" ").slice(0, MAX_ALERT_CHARS));
77166
+ i = j - 1;
77167
+ }
77168
+ }
77169
+ return alerts;
77170
+ }
77171
+ function stepLoginFormAlerts(steps) {
77172
+ const out = [];
77173
+ for (const sr of steps ?? []) {
77174
+ for (const a of sr.attachments ?? []) {
77175
+ if (a?.name === "error-context" && typeof a.body === "string")
77176
+ out.push(...loginFormAlertTexts(a.body));
77177
+ }
77178
+ }
77179
+ return out;
77180
+ }
76817
77181
  function detectAuthBlock(input) {
76818
77182
  const tags = input.tags ?? [];
76819
77183
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
76820
- if (!isAuthed)
77184
+ const signsInInline2 = !isAuthed && codeReadsStoredCredentials(input.code);
77185
+ if (!isAuthed && !signsInInline2)
76821
77186
  return NO_DETECTION;
76822
77187
  if (isIntentionalNegativeAuthTest(input))
76823
77188
  return NO_DETECTION;
77189
+ if (signInStepPassedBeforeFailure(input.stepResults))
77190
+ return NO_DETECTION;
76824
77191
  const sources = [];
76825
- if (input.error)
76826
- sources.push(stripEchoLines(input.error));
76827
- if (input.logs)
76828
- sources.push(stripEchoLines(input.logs));
76829
- for (const sr of input.stepResults ?? []) {
76830
- if (sr.error)
76831
- sources.push(stripEchoLines(sr.error));
77192
+ if (isAuthed) {
77193
+ if (input.error)
77194
+ sources.push(stripEchoLines(input.error));
77195
+ if (input.logs)
77196
+ sources.push(stripEchoLines(input.logs));
77197
+ for (const sr of input.stepResults ?? []) {
77198
+ if (sr.error)
77199
+ sources.push(stripEchoLines(sr.error));
77200
+ }
76832
77201
  }
77202
+ sources.push(...stepLoginFormAlerts(input.stepResults));
76833
77203
  for (const text of sources) {
76834
77204
  if (!text)
76835
77205
  continue;
@@ -76878,10 +77248,11 @@ function detectApiColdSessionBlock(input) {
76878
77248
  function coldSessionConfigMessage(context, reason, evidence) {
76879
77249
  return `CONFIG_ISSUE: ${context} without valid injected auth (${reason}) \u2014 no session cookie was injected this run. Ensure the project login (auth-setup recipe / prewarm) is configured so the shared session jar warms (a project with no login: set TEST_USER_EMAIL and TEST_USER_PASSWORD in Test Data \u2192 Test Variables so a session can be captured); for endpoints that authenticate with a standing API key or bearer header instead, set them in the project's API_AUTH_HEADERS variable (injected as request headers). Evidence: ${evidence.join("; ")}`;
76880
77250
  }
76881
- var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, UNAUTHORIZED_TEXT_RE;
77251
+ var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, SIGN_IN_STEP_TITLE, ERROR_CONTEXT_CAP, MAX_ALERTS, MAX_ALERT_CHARS, SNAPSHOT_PASSWORD_FIELD, SNAPSHOT_ALERT, UNAUTHORIZED_TEXT_RE;
76882
77252
  var init_auth_block_detector = __esm({
76883
77253
  "../shared/dist/auth-block-detector.js"() {
76884
77254
  "use strict";
77255
+ init_auth_plan();
76885
77256
  NO_DETECTION = { detected: false, reason: "", evidence: [] };
76886
77257
  AUTHED_TAGS = ["@needs-auth", "@inline-login"];
76887
77258
  LOGIN_CTX = "\\b(?:log\\s*in|login|logging\\s+in|sign[\\s-]?in|signing\\s+in|continue)\\b";
@@ -76891,6 +77262,10 @@ var init_auth_block_detector = __esm({
76891
77262
  { reason: "invalid credentials", re: /\b(invalid|incorrect|wrong|bad)\s+(login\s+)?credentials\b/i },
76892
77263
  { reason: "credentials do not match", re: /\bcredentials\b[^\n]{0,40}\b(do(?:es)?\s*n['’]?t|do\s+not)\s+match\b/i },
76893
77264
  { reason: "incorrect email or password", re: /\b(incorrect|invalid|wrong)\s+(email|username)\s+or\s+password\b/i },
77265
+ { reason: "username and password do not match", re: /\b(?:user\s*name|e-?mail)\s+and\s+password\s+(?:do(?:es)?\s*n['’]?t|do(?:es)?\s+not)\s+match\b/i },
77266
+ // A locked account is a blocker of the same kind: the configured user can't sign in until it is unlocked. The
77267
+ // rate-limit detector keeps the timed lockouts ("temporarily locked", "too many attempts").
77268
+ { reason: "account locked out", re: /\b(?:user|account)\b[^\n]{0,30}\b(?:has\s+been|is)\s+locked\s+out\b/i },
76894
77269
  // Require an EXPLICIT negative (codex #21): the old `(not\s+)?(confirmed|verified)` made the negation
76895
77270
  // optional, so a benign "Email verified" success message / feature assertion matched and hid a real
76896
77271
  // feature failure as a CONFIG_ISSUE. Only "email not (yet) confirmed/verified" or "unconfirmed/unverified".
@@ -76922,6 +77297,12 @@ var init_auth_block_detector = __esm({
76922
77297
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
76923
77298
  ];
76924
77299
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
77300
+ SIGN_IN_STEP_TITLE = /\b(?:log(?:ging)?\s*in(?:to)?|sign(?:ing)?[\s-]?in(?:to)?|authenticat(?:e|es|ed|ing|ion))\b/i;
77301
+ ERROR_CONTEXT_CAP = 64 * 1024;
77302
+ MAX_ALERTS = 20;
77303
+ MAX_ALERT_CHARS = 1e3;
77304
+ SNAPSHOT_PASSWORD_FIELD = /^\s*- textbox "[^"\n]{0,80}\bpass(?:word|code)?\b/i;
77305
+ SNAPSHOT_ALERT = /^(\s*)- alert\b(.*)$/;
76925
77306
  UNAUTHORIZED_TEXT_RE = /(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|gives?|sends?|yield(?:s|ed|ing)?|results?\s+in)\s+(?:a\s+|an\s+|the\s+)?(?:401|403)\b|\bstatus(?:\s+code)?\s*(?::\s*)?(?:401|403)\b|\bHTTP\/[\d.]+\s+(?:401|403)\b|(?:401|403)['’]d\b|(?:[→⇒]|->)\s*(?:401|403)\b|(?:return(?:s|ed|ing)?|respond(?:s|ed|ing)?|got|receiv(?:e|ed|ing)|threw|throws?)\s+(?:a\s+|an\s+)?(?:unauthorized|forbidden)\b|\bnot\s+authenticated\b/i;
76926
77307
  }
76927
77308
  });
@@ -77356,6 +77737,77 @@ var init_redact_needles = __esm({
77356
77737
  }
77357
77738
  });
77358
77739
 
77740
+ // ../shared/dist/credential-header-scrub.js
77741
+ function scrubCredentialHeaders(text) {
77742
+ if (text.length < 6)
77743
+ return text;
77744
+ let out = text.replace(HEADER_LINE, (match, keyQuote, name, sep, value) => {
77745
+ if (value === "" || ALREADY_REDACTED.test(value))
77746
+ return match;
77747
+ if (COOKIE_NAME.test(name) && !value.includes("="))
77748
+ return match;
77749
+ if (AUTHORIZATION_NAME.test(name) && WORDS_ONLY.test(value))
77750
+ return match;
77751
+ const q = value[0] === '"' || value[0] === "'" ? value[0] : "";
77752
+ return `${keyQuote}${name}${keyQuote}${sep}${q}${REDACTED2}${q}`;
77753
+ });
77754
+ out = out.replace(SET_COOKIE_PAIR, `$1${REDACTED2}`);
77755
+ return out;
77756
+ }
77757
+ function isCredentialHeaderName(name) {
77758
+ return name.length <= 100 && HEADER_NAME_ONLY.test(name);
77759
+ }
77760
+ function scrubCredentialHeaderValue(value) {
77761
+ if (typeof value === "string")
77762
+ return value === REDACTED2 || value === "" ? value : REDACTED2;
77763
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
77764
+ const next = value.map((item) => item === REDACTED2 || item === "" ? item : REDACTED2);
77765
+ return next.every((item, i) => item === value[i]) ? value : next;
77766
+ }
77767
+ return value;
77768
+ }
77769
+ function scrubCredentialHeadersDeep(value) {
77770
+ if (typeof value === "string")
77771
+ return scrubCredentialHeaders(value);
77772
+ if (Array.isArray(value)) {
77773
+ let changed = false;
77774
+ const next = value.map((item) => {
77775
+ const scrubbed = scrubCredentialHeadersDeep(item);
77776
+ if (scrubbed !== item)
77777
+ changed = true;
77778
+ return scrubbed;
77779
+ });
77780
+ return changed ? next : value;
77781
+ }
77782
+ if (value !== null && typeof value === "object") {
77783
+ let changed = false;
77784
+ const next = {};
77785
+ for (const [k, v] of Object.entries(value)) {
77786
+ const scrubbed = isCredentialHeaderName(k) ? scrubCredentialHeaderValue(v) : scrubCredentialHeadersDeep(v);
77787
+ if (scrubbed !== v)
77788
+ changed = true;
77789
+ next[k] = scrubbed;
77790
+ }
77791
+ return changed ? next : value;
77792
+ }
77793
+ return value;
77794
+ }
77795
+ var REDACTED2, HEADER_NAMES, COOKIE_NAME, AUTHORIZATION_NAME, WORDS_ONLY, HEADER_NAME_ONLY, HEADER_LINE, ALREADY_REDACTED, SET_COOKIE_PAIR;
77796
+ var init_credential_header_scrub = __esm({
77797
+ "../shared/dist/credential-header-scrub.js"() {
77798
+ "use strict";
77799
+ REDACTED2 = "[REDACTED]";
77800
+ HEADER_NAMES = "cookie2?|set-cookie2?|proxy-authorization|authorization|api-?key|(?:x-)?(?:csrf|xsrf)[a-z0-9-]{0,30}|x-[a-z0-9-]{0,40}?(?:token|session|secret|key|auth)[a-z0-9-]{0,40}";
77801
+ COOKIE_NAME = /^(?:set-)?cookie2?$/i;
77802
+ AUTHORIZATION_NAME = /^(?:proxy-)?authorization$/i;
77803
+ WORDS_ONLY = /^[A-Za-z]+(?: [A-Za-z]+){0,3}$/;
77804
+ HEADER_NAME_ONLY = new RegExp(`^(?:${HEADER_NAMES})$`, "i");
77805
+ HEADER_LINE = new RegExp(`(?<![A-Za-z0-9_-])(["']?)(${HEADER_NAMES})\\1(\\s*:\\s*)("(?:[^"\\\\\\r\\n]|\\\\.)*"|'(?:[^'\\\\\\r\\n]|\\\\.)*'|[^\\r\\n\\x1b]*)`, "gi");
77806
+ ALREADY_REDACTED = /^["']?(?:[A-Za-z][\w-]*\s+)?\[REDACTED\]["']?[,;]?\s*$/;
77807
+ SET_COOKIE_PAIR = /(?<![A-Za-z0-9_-])([A-Za-z0-9_.%-]{1,64}=)(?!\[REDACTED\])([^\s;,"'<>]{6,2048})(?=;\s*(?:Path|Domain|Expires|Max-Age|HttpOnly|Secure|SameSite|Partitioned)\b)/gi;
77808
+ }
77809
+ });
77810
+
77359
77811
  // ../shared/dist/deepgram-voices.js
77360
77812
  function voice(family, name, gender, accent, description) {
77361
77813
  const label = name.charAt(0).toUpperCase() + name.slice(1);
@@ -77932,13 +78384,13 @@ function isSensitiveQueryKey(key) {
77932
78384
  return SENSITIVE_QUERY_KEY_RE.test(key);
77933
78385
  }
77934
78386
  function redactSecretValues(value) {
77935
- return value.replace(MCP_SECRET_VALUE_RE, REDACTED2);
78387
+ return value.replace(MCP_SECRET_VALUE_RE, REDACTED3);
77936
78388
  }
77937
78389
  function scrubReturnUrlValue(value, depth) {
77938
78390
  if (!/[?%]/.test(value))
77939
78391
  return value;
77940
78392
  if (depth >= MAX_NESTED_URL_DEPTH)
77941
- return REDACTED2;
78393
+ return REDACTED3;
77942
78394
  const q = value.indexOf("?");
77943
78395
  if (q !== -1) {
77944
78396
  const hashIdx = value.indexOf("#", q);
@@ -77960,7 +78412,7 @@ function scrubReturnUrlValue(value, depth) {
77960
78412
  try {
77961
78413
  return encodeURIComponent(scrubbed);
77962
78414
  } catch {
77963
- return REDACTED2;
78415
+ return REDACTED3;
77964
78416
  }
77965
78417
  }
77966
78418
  function scrubQuery(search, depth) {
@@ -77976,7 +78428,7 @@ function scrubQuery(search, depth) {
77976
78428
  params.forEach((value, key) => {
77977
78429
  let scrubbed = value;
77978
78430
  if (isSensitiveQueryKey(key)) {
77979
- scrubbed = REDACTED2;
78431
+ scrubbed = REDACTED3;
77980
78432
  } else {
77981
78433
  if (RETURN_URL_QUERY_KEY_RE.test(key))
77982
78434
  scrubbed = scrubReturnUrlValue(scrubbed, depth);
@@ -78088,12 +78540,12 @@ function scrubUrlPropertyBag(props) {
78088
78540
  } catch {
78089
78541
  }
78090
78542
  }
78091
- var SENSITIVE_QUERY_KEY_RE, REDACTED2, MCP_SECRET_VALUE_RE, RETURN_URL_QUERY_KEY_RE, MAX_NESTED_URL_DEPTH, MAX_PROPERTY_SWEEP_DEPTH;
78543
+ var SENSITIVE_QUERY_KEY_RE, REDACTED3, MCP_SECRET_VALUE_RE, RETURN_URL_QUERY_KEY_RE, MAX_NESTED_URL_DEPTH, MAX_PROPERTY_SWEEP_DEPTH;
78092
78544
  var init_analytics_url = __esm({
78093
78545
  "../shared/dist/analytics-url.js"() {
78094
78546
  "use strict";
78095
78547
  SENSITIVE_QUERY_KEY_RE = /^(access_token|refresh_token|id_token|token|code|state|password|secret|api[_-]?key|auth|authorization|session|jwt|otp|email|phone|magic[_-]?link)$|token|secret|password|credential|email|phone|(^|[_-])(otp|verification|auth)[_-]?code($|[_-])|(^|[_-])(state|session|grant)$/i;
78096
- REDACTED2 = "[redacted]";
78548
+ REDACTED3 = "[redacted]";
78097
78549
  MCP_SECRET_VALUE_RE = /mcp_(consent|code|at|rt)_[A-Za-z0-9]+/g;
78098
78550
  RETURN_URL_QUERY_KEY_RE = /^(next|redirect|return|continue)([_-]?(to|url|uri|path))?$/i;
78099
78551
  MAX_NESTED_URL_DEPTH = 5;
@@ -80923,8 +81375,19 @@ function successContradicted(text) {
80923
81375
  }
80924
81376
  return SUCCESS_NO_STATUS_RE.test(text) || OFF_BY_RE.test(text);
80925
81377
  }
81378
+ function negatedSafeguard(text) {
81379
+ for (const match of matches(NEGATED_SAFEGUARD_RE, text)) {
81380
+ if (!NOT_KEPT_RE.test(match[1]))
81381
+ return true;
81382
+ const head = text.slice(Math.max(0, match.index - SAFEGUARD_LOOKBEHIND), match.index);
81383
+ const sentence = head.slice(Math.max(head.lastIndexOf("."), head.lastIndexOf(";"), head.lastIndexOf("\n")) + 1);
81384
+ if (!REFUSED_INPUT_RE.test(sentence))
81385
+ return true;
81386
+ }
81387
+ return false;
81388
+ }
80926
81389
  function claimsDefect(text) {
80927
- if (BUG_LABEL_RE.test(text) || SILENT_RE.test(text) || CONCESSION_RE.test(text) || successContradicted(text) || NEGATED_SAFEGUARD_RE.test(text)) {
81390
+ if (BUG_LABEL_RE.test(text) || SILENT_RE.test(text) || CONCESSION_RE.test(text) || successContradicted(text) || negatedSafeguard(text)) {
80928
81391
  return true;
80929
81392
  }
80930
81393
  return BAD_VALUE_RE.test(text) && !REFUSAL_RE.test(text);
@@ -80932,7 +81395,7 @@ function claimsDefect(text) {
80932
81395
  function hasTestFailContract(code) {
80933
81396
  return TEST_FAIL_CONTRACT_RE.test(code);
80934
81397
  }
80935
- var BUG_LABEL_RE, SILENT_RE, CONCESSION_RE, SUCCESS_REPORT, SUCCESS_CONTRADICTED_RE, CONTRADICTION_VETO_RE, NOTHING_PROVIDED_RE, UNTOUCHED_SUBSETS, ACTION_LOOKBEHIND, TRAILING_ACTION_RE, ADJECTIVE_BEFORE_NOUN_RE, SUCCESS_SAME_STATE_RE, GENERIC_REPORT_RE, SAME_STATE, OTHER_ITEM_RE, SUCCESS_STATUS_RE, STATUS_ACTION_RE, SUCCESS_WITHOUT_EFFECT_RE, COUNT_UNCHANGED_RE, CLEARING_ACTION_RE, NOT_A_CLEARING_RE, SUCCESS_NO_STATUS_RE, OFF_BY_RE, NEGATED_SAFEGUARD_RE, BAD_VALUE_RE, REFUSAL_RE, TEST_FAIL_CONTRACT_RE;
81398
+ var BUG_LABEL_RE, SILENT_RE, CONCESSION_RE, SUCCESS_REPORT, SUCCESS_CONTRADICTED_RE, CONTRADICTION_VETO_RE, NOTHING_PROVIDED_RE, UNTOUCHED_SUBSETS, ACTION_LOOKBEHIND, TRAILING_ACTION_RE, ADJECTIVE_BEFORE_NOUN_RE, SUCCESS_SAME_STATE_RE, GENERIC_REPORT_RE, SAME_STATE, OTHER_ITEM_RE, SUCCESS_STATUS_RE, STATUS_ACTION_RE, SUCCESS_WITHOUT_EFFECT_RE, COUNT_UNCHANGED_RE, CLEARING_ACTION_RE, NOT_A_CLEARING_RE, SUCCESS_NO_STATUS_RE, OFF_BY_RE, NEGATED_SAFEGUARD_RE, NOT_KEPT_RE, REFUSED_INPUT_RE, SAFEGUARD_LOOKBEHIND, BAD_VALUE_RE, REFUSAL_RE, TEST_FAIL_CONTRACT_RE;
80936
81399
  var init_claims_defect = __esm({
80937
81400
  "../shared/dist/claims-defect.js"() {
80938
81401
  "use strict";
@@ -80976,7 +81439,10 @@ var init_claims_defect = __esm({
80976
81439
  NOT_A_CLEARING_RE = /\b(?:cancel|refresh|reload|preview|open|view|fail|hover|scroll|sort|filter|search)\w*/i;
80977
81440
  SUCCESS_NO_STATUS_RE = /\bsuccess\w*\b[^.;\n]{0,30}?\b(?:(?:do|does|did) not|don't|doesn't|didn't) (?:change|update|move)\b[^.;\n]{0,20}?\bstatus\b/i;
80978
81441
  OFF_BY_RE = /\boff[- ]by[- ](?:one|two|\d{1,3})\b/i;
80979
- NEGATED_SAFEGUARD_RE = /\bnot (?:rejected|validated|enforced|applied|persisted|saved)\b/i;
81442
+ NEGATED_SAFEGUARD_RE = /\bnot (rejected|validated|enforced|applied|persisted|saved)\b/gi;
81443
+ NOT_KEPT_RE = /^(?:applied|persisted|saved)$/i;
81444
+ REFUSED_INPUT_RE = /\b(?:blank|empty|whitespace|invalid|malformed|expired|duplicate[sd]?|too (?:long|short)|cancel(?:s|led|ed|ling|ing)?|discard\w*|escape)\b/i;
81445
+ SAFEGUARD_LOOKBEHIND = 80;
80980
81446
  BAD_VALUE_RE = /\b(?:incorrect|wrong|stale|negative|epoch|unix) (?:total|amount|value|date|count|price|label|plan|balance)\b/i;
80981
81447
  REFUSAL_RE = /\b(?<!not )(?:rejected|refused|denied|blocked|disallowed|prevented)\b|\breturns? (?:4\d\d|an? (?:error|validation))\b|\bshows (?:an? )?(?:error|validation)\b/i;
80982
81448
  TEST_FAIL_CONTRACT_RE = /\btest\.fail\s{0,20}\(/;
@@ -81835,7 +82301,7 @@ function findExpectCalls(code) {
81835
82301
  return out;
81836
82302
  }
81837
82303
  function countExpectCalls(code) {
81838
- return findExpectCalls(stripComments2(code)).length;
82304
+ return findExpectCalls(stripComments2(withExpectAliasesAsExpect(code))).length;
81839
82305
  }
81840
82306
  function countActionCalls(code) {
81841
82307
  return [...stripComments2(code).matchAll(ACTION_CALL)].length;
@@ -84164,6 +84630,7 @@ var init_test_authoring_quality = __esm({
84164
84630
  "../shared/dist/test-authoring-quality.js"() {
84165
84631
  "use strict";
84166
84632
  init_claims_defect();
84633
+ init_expect_call();
84167
84634
  init_readiness_poll();
84168
84635
  init_persistence_reread();
84169
84636
  init_write_response_check();
@@ -84290,7 +84757,7 @@ var init_test_authoring_quality = __esm({
84290
84757
  MAX_BALANCED_SCAN = 2048;
84291
84758
  MAX_CALLS = 500;
84292
84759
  MAX_SEEN_SCAN = 200;
84293
- ACTION_CALL = /\.(?:click|dblclick|fill|press|pressSequentially|type|check|uncheck|selectOption|setInputFiles|hover|tap|dragTo|goto)\s*\(/g;
84760
+ ACTION_CALL = /\.(?:(?:click|dblclick|fill|clear|press|pressSequentially|type|check|uncheck|setChecked|selectOption|setInputFiles|hover|tap|dragTo|dragAndDrop|dispatchEvent|goto)|(?:keyboard|mouse)\s*\.\s*(?:down|up|insertText|move|wheel))\s*\(/g;
84294
84761
  BY_NAME_LOCATOR = /\.getByRole\(\s{0,20}['"][^'"\n]{1,100}['"]\s{0,20},\s{0,20}\{[^}\n]{0,200}\bname\s{0,20}:|\.getByText\(|\.getByLabel\(|\.getByTitle\(|\.getByAltText\(|\.getByPlaceholder\(/;
84295
84762
  ACTION_AFTER = /^\s{0,20}\.\s{0,20}(?:click|fill|check|uncheck|press|type|hover|focus|selectOption|setInputFiles|dblclick|tap)\s{0,20}\(/;
84296
84763
  MAX_CONTACTS = 200;
@@ -85799,7 +86266,7 @@ function credentialEntries(maps) {
85799
86266
  }
85800
86267
  function replacePattern(value, form) {
85801
86268
  const escaped = form.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
85802
- return new RegExp(value.length < WHOLE_WORD_BELOW ? `(?<![A-Za-z0-9_])${escaped}(?![A-Za-z0-9_])` : escaped, "gi");
86269
+ return value.length < WHOLE_WORD_BELOW ? `(?<![A-Za-z0-9_])${escaped}(?![A-Za-z0-9_])` : escaped;
85803
86270
  }
85804
86271
  function findCredentialTextLeftover(text, maps) {
85805
86272
  const bare = text.split(WITHHELD_CREDENTIAL_TEXT).join(" ").replace(REFERENCE, " ").toLowerCase();
@@ -85813,11 +86280,17 @@ function scrubCredentialText(text, maps) {
85813
86280
  const entries = credentialEntries(maps);
85814
86281
  if (entries.length === 0)
85815
86282
  return text;
85816
- let out = text;
86283
+ const alternatives = [];
85817
86284
  for (const [key, value] of entries) {
85818
86285
  for (const form of credentialValueForms(value))
85819
- out = out.replace(replacePattern(value, form), `{{secret:${key}}}`);
86286
+ alternatives.push({ pattern: replacePattern(value, form), key, length: form.length });
85820
86287
  }
86288
+ alternatives.sort((a, b) => b.length - a.length);
86289
+ const matcher = new RegExp([REFERENCE.source, ...alternatives.map((alt) => `(${alt.pattern})`)].join("|"), "gi");
86290
+ const out = text.replace(matcher, (...args) => {
86291
+ const index = args.slice(1, 1 + alternatives.length).findIndex((group) => group !== void 0);
86292
+ return index < 0 ? String(args[0]) : `{{secret:${alternatives[index].key}}}`;
86293
+ });
85821
86294
  return findCredentialTextLeftover(out, maps) ? WITHHELD_CREDENTIAL_TEXT : out;
85822
86295
  }
85823
86296
  function scrubCredentialStringsIn(value, maps) {
@@ -86311,10 +86784,24 @@ function onlyUrlAfter(code, writeAt) {
86311
86784
  i = at + 6;
86312
86785
  }
86313
86786
  }
86787
+ function readsOrderAfter(code, writeAt) {
86788
+ const rest = code.slice(writeAt + 1);
86789
+ if (ORDER_READ_MARKERS.some((m) => rest.includes(m)) && nextExpect(code, writeAt + 1) >= 0)
86790
+ return true;
86791
+ for (let i = writeAt + 1; ; ) {
86792
+ const at = nextExpect(code, i);
86793
+ if (at < 0)
86794
+ return false;
86795
+ if (ORDERED_ARRAY_EXPECT.test(expectStatement(code, at)))
86796
+ return true;
86797
+ i = at + 6;
86798
+ }
86799
+ }
86314
86800
  function assessTitlePromiseShape(claimText, code) {
86315
86801
  const persistence = PERSISTENCE_RE.test(claimText);
86316
86802
  const refusal = REFUSAL_RE2.test(claimText);
86317
- if (!persistence && !refusal)
86803
+ const order = ORDER_RE.test(claimText) || ORDER_RE_CASED.test(claimText);
86804
+ if (!persistence && !refusal && !order)
86318
86805
  return [];
86319
86806
  const blank = blankCommentsAndStringLiterals(code);
86320
86807
  const writeAt = proofAnchor(blank);
@@ -86327,9 +86814,12 @@ function assessTitlePromiseShape(claimText, code) {
86327
86814
  if (refusal && onlyUrlAfter(blank, writeAt)) {
86328
86815
  findings.push({ rule: "rejection-only-url", reason: REFUSAL_REASON });
86329
86816
  }
86817
+ if (order && !readsOrderAfter(blank, writeAt)) {
86818
+ findings.push({ rule: "sort-without-order", reason: ORDER_REASON });
86819
+ }
86330
86820
  return findings;
86331
86821
  }
86332
- var PERSIST_REASON, REFUSAL_REASON, PERSISTENCE_RE, REFUSAL_RE2, WRITE_MARKERS, VALUE_WRITE_MARKERS, REREAD_MARKERS;
86822
+ var PERSIST_REASON, REFUSAL_REASON, PERSISTENCE_RE, REFUSAL_RE2, ORDER_REASON, ORDER_RE, ORDER_RE_CASED, ORDER_READ_MARKERS, ORDERED_ARRAY_EXPECT, WRITE_MARKERS, VALUE_WRITE_MARKERS, REREAD_MARKERS;
86333
86823
  var init_title_promise_shape = __esm({
86334
86824
  "../shared/dist/title-promise-shape.js"() {
86335
86825
  "use strict";
@@ -86338,6 +86828,21 @@ var init_title_promise_shape = __esm({
86338
86828
  REFUSAL_REASON = "The title says the app refuses the input, but the only check after submitting is the URL: assert the error message, the field's invalid state or the response status.";
86339
86829
  PERSISTENCE_RE = /\b(?:is|are|gets|stays)\s{1,20}saved\b|\b(?:is|are)\s{1,20}persisted\b|\bpersists\s{1,20}(?:after|across|through)\s{1,20}(?:a\s{1,20})?(?:reloads?|refresh(?:es)?|sessions?)\b|\bis\s{1,20}(?:stored|kept|remembered)\b|\bafter\s{1,20}(?:a\s{1,20})?reload\b|\bafter\s{1,20}refresh\b|\bsurvives\s{1,20}(?:a\s{1,20})?reload\b/i;
86340
86830
  REFUSAL_RE2 = /\b(?:rejects|rejected|refuses|refused|prevents|prevented|disallows)\b|\b(?:is|are|gets)\s{1,20}blocked\b|\bis\s{1,20}denied\b|\bshows\s{1,20}an\s{1,20}error\b|\bshows\s{1,20}a\s{1,20}validation\b|\bshows\s{1,20}validation\b|\breturns\s{1,20}4\d\d\b/i;
86831
+ ORDER_REASON = "The title says the list is sorted, but no check after the sort reads the order: assert the items in order (toHaveText([...]) on the list, the first and last item, or allTextContents() compared with a sorted copy).";
86832
+ ORDER_RE = /\bsort(?:s|ed|ing)?\b[^\n.]{0,60}?\bby\b|\b(?:ascending|descending)\b|\b(?:low|lowest)\s{1,20}to\s{1,20}(?:high|highest)\b|\b(?:high|highest)\s{1,20}to\s{1,20}(?:low|lowest)\b|\balphabetical(?:ly)?\b|\b(?:newest|oldest)\s{1,20}first\b/i;
86833
+ ORDER_RE_CASED = /\b[AZ]\s{0,3}(?:to|-|→)\s{0,3}[AZ]\b/;
86834
+ ORDER_READ_MARKERS = [
86835
+ ".allTextContents(",
86836
+ ".allInnerTexts(",
86837
+ ".evaluateAll(",
86838
+ ".toMatchAriaSnapshot(",
86839
+ ".sort(",
86840
+ ".toSorted(",
86841
+ ".first()",
86842
+ ".last()",
86843
+ ".nth("
86844
+ ];
86845
+ ORDERED_ARRAY_EXPECT = /\.(?:toHaveText|toContainText|toEqual|toStrictEqual)\s*\(\s*\[/;
86341
86846
  WRITE_MARKERS = [
86342
86847
  ".fill(",
86343
86848
  ".click(",
@@ -86669,7 +87174,7 @@ function assertionsReadVisibleContent(code) {
86669
87174
  });
86670
87175
  }
86671
87176
  function isLegacyReviewFlag(tags, activity) {
86672
- const flagged = tags.includes(NEEDS_REVIEW_TAG) || activity?.needsReview === true;
87177
+ const flagged = tags.includes(NEEDS_REVIEW_TAG2) || activity?.needsReview === true;
86673
87178
  const source = activity?.needsReviewSource;
86674
87179
  return flagged && !(typeof source === "string" && source in SOURCE_RANK);
86675
87180
  }
@@ -86731,7 +87236,7 @@ function recheckGeneratorNotes(notes, input) {
86731
87236
  }
86732
87237
  function reviewHolds(tags, activity, cleared = /* @__PURE__ */ new Set()) {
86733
87238
  const out = [];
86734
- const tagged = tags.includes(NEEDS_REVIEW_TAG);
87239
+ const tagged = tags.includes(NEEDS_REVIEW_TAG2);
86735
87240
  const generatorAsked = activity?.needsReview === true;
86736
87241
  if (tagged) {
86737
87242
  if (activity?.needsReviewSource === "human")
@@ -86791,7 +87296,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
86791
87296
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
86792
87297
  };
86793
87298
  }
86794
- var SOURCE_RANK, strengthCheck, STRING_CONST, TYPED_VALUE, TYPED_CONSTANT, MAX_STATEMENTS, MAX_STATEMENT_CHARS, LITERAL_OR_IDENTIFIER, MUTATING_ACTION, FEEDBACK_REGION, ONE_SHOT_MESSAGE_REASON, FEEDBACK_REGION_ALL, dynamicAssertionCheck, OUTCOME_KIND_MATCHER, ROUTE_TERM, ROUTE_TRAILING_PUNCTUATION, QUOTED_LITERAL, GENERIC_LITERAL, MAX_OUTCOME_STATEMENTS, MAX_OUTCOMES, MAX_OUTCOME_TEXT_CHARS, expectedOutcomeCheck, testDataCheck, sharedAccountCheck, clockLiteralCheck, readinessPollCheck, knownBugTestCheck, bugClaimContractCheck, suspiciousExpectationCheck, bugCandidateCheck, conventionOutcomesCheck, pendingHook, locatorFragmentCheck, FILTER_STEP, NEXT_ACTION, REDUCTION_PROOF, MAX_FILTER_LINES, MAX_FILTER_LINE_CHARS, filterPositiveOnlyCheck, PROMOTION_QUALITY_CHECKS, PROMOTION_GATE_MAX_CODE_CHARS, NOT_EVALUATED, SUSPICIOUS_PASS_TAG, NOT_CONTENT_MATCHER, CONTENT_PROOF, NAMED_ELEMENT, NEEDS_REVIEW_TAG, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
87299
+ 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_TAG2, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
86795
87300
  var init_promotion_quality_gate = __esm({
86796
87301
  "../shared/dist/promotion-quality-gate.js"() {
86797
87302
  "use strict";
@@ -86810,8 +87315,8 @@ var init_promotion_quality_gate = __esm({
86810
87315
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
86811
87316
  strengthCheck = {
86812
87317
  id: "assertion-strength",
86813
- run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
86814
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
87318
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof, absenceProof }) {
87319
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof, absenceProof });
86815
87320
  if (verdict.severity === "route-bug")
86816
87321
  return [{ check: "route-bug" }];
86817
87322
  const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
@@ -87069,7 +87574,7 @@ ${goalText}`);
87069
87574
  NOT_CONTENT_MATCHER = /\.(?:toHaveURL|toHaveTitle|toBeOK)\s*\(|\bwaitForURL\s*\(|\.(?:status|ok)\s*\(\s*\)/;
87070
87575
  CONTENT_PROOF = /* @__PURE__ */ new Set(["controlled", "revealed", "value"]);
87071
87576
  NAMED_ELEMENT = /\bgetByRole\s*\(\s*['"`](?!(?:navigation|banner|main|contentinfo|complementary|region|search)['"`])\w{1,30}['"`]\s*,\s*\{[^}]{0,200}?\bname\s*:\s*(['"`])[^'"`$\n]{1,200}\1|\bgetByLabel\s*\(\s*(['"`])[^'"`$\n]{1,200}\2/;
87072
- NEEDS_REVIEW_TAG = "@needs-review";
87577
+ NEEDS_REVIEW_TAG2 = "@needs-review";
87073
87578
  count2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
87074
87579
  RECHECKED_NOTES = [
87075
87580
  {
@@ -87190,11 +87695,18 @@ var init_regex_alternation = __esm({
87190
87695
  function isPageEvidenceEntry(entry) {
87191
87696
  return typeof entry === "object" && entry !== null && entry.outcomeType === PAGE_LOADING_OUTCOME_TYPE;
87192
87697
  }
87193
- var PAGE_LOADING_OUTCOME_TYPE;
87698
+ function pageScreenshotKey(projectId, safeName, stillLoading = false) {
87699
+ return `discovery/${projectId}/screenshots/${stillLoading ? `${STILL_LOADING_SCREENSHOT_DIR}/` : ""}${safeName}.png`;
87700
+ }
87701
+ function isStillLoadingScreenshotKey(key) {
87702
+ return typeof key === "string" && key.includes(`/screenshots/${STILL_LOADING_SCREENSHOT_DIR}/`);
87703
+ }
87704
+ var PAGE_LOADING_OUTCOME_TYPE, STILL_LOADING_SCREENSHOT_DIR;
87194
87705
  var init_page_evidence = __esm({
87195
87706
  "../shared/dist/page-evidence.js"() {
87196
87707
  "use strict";
87197
87708
  PAGE_LOADING_OUTCOME_TYPE = "LOADING";
87709
+ STILL_LOADING_SCREENSHOT_DIR = "still-loading";
87198
87710
  }
87199
87711
  });
87200
87712
 
@@ -87599,11 +88111,13 @@ __export(dist_exports, {
87599
88111
  SERVER_INTERRUPT_ERROR_PREFIX: () => SERVER_INTERRUPT_ERROR_PREFIX,
87600
88112
  SHARED_PRICING_PLAN_MAP: () => SHARED_PRICING_PLAN_MAP,
87601
88113
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
88114
+ SMS_NOT_RECEIVED_PREFIXES: () => SMS_NOT_RECEIVED_PREFIXES,
87602
88115
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
87603
88116
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
87604
88117
  STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
87605
88118
  STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
87606
88119
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
88120
+ STILL_LOADING_SCREENSHOT_DIR: () => STILL_LOADING_SCREENSHOT_DIR,
87607
88121
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
87608
88122
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
87609
88123
  SUPPORT_EMAIL: () => SUPPORT_EMAIL,
@@ -87631,6 +88145,7 @@ __export(dist_exports, {
87631
88145
  TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
87632
88146
  UI_VARIANTS: () => UI_VARIANTS,
87633
88147
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
88148
+ UNVERIFIED_HEAL_CONFIDENCES: () => UNVERIFIED_HEAL_CONFIDENCES,
87634
88149
  USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
87635
88150
  VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
87636
88151
  VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
@@ -87684,6 +88199,7 @@ __export(dist_exports, {
87684
88199
  blankBlockComments: () => blankBlockComments3,
87685
88200
  blankCommentsAndStringLiterals: () => blankCommentsAndStringLiterals,
87686
88201
  blankKeepingLines: () => blankKeepingLines,
88202
+ blockedRunLogLine: () => blockedRunLogLine,
87687
88203
  boundedCallCostUsd: () => boundedCallCostUsd,
87688
88204
  brokerResponseBody: () => brokerResponseBody,
87689
88205
  bugFoundQuarantineWhere: () => bugFoundQuarantineWhere,
@@ -87789,6 +88305,7 @@ __export(dist_exports, {
87789
88305
  evaluatePromotionGate: () => evaluatePromotionGate,
87790
88306
  excludeCancelledRunsAnd: () => excludeCancelledRunsAnd,
87791
88307
  excludeCancelledRunsWhere: () => excludeCancelledRunsWhere,
88308
+ expectAliasNames: () => expectAliasNames,
87792
88309
  explicitAgenticCliOverrideError: () => explicitAgenticCliOverrideError,
87793
88310
  extractAssertedControlSignatures: () => extractAssertedControlSignatures,
87794
88311
  extractMuseAssistantJson: () => extractMuseAssistantJson,
@@ -87855,6 +88372,8 @@ __export(dist_exports, {
87855
88372
  googleGisRequestUrl: () => googleGisRequestUrl,
87856
88373
  googleSignInLimitLines: () => googleSignInLimitLines,
87857
88374
  googleSignInPolicyProblems: () => googleSignInPolicyProblems,
88375
+ googleSignInRefusedLine: () => googleSignInRefusedLine,
88376
+ googleSignInRefusedReason: () => googleSignInRefusedReason,
87858
88377
  hasCrossPlatformIdentity: () => hasCrossPlatformIdentity,
87859
88378
  hasEncodedRequestBody: () => hasEncodedRequestBody,
87860
88379
  hasEngineSpecParts: () => hasEngineSpecParts,
@@ -87898,6 +88417,7 @@ __export(dist_exports, {
87898
88417
  isCodeFactNote: () => isCodeFactNote,
87899
88418
  isConfigLikeReason: () => isConfigLikeReason,
87900
88419
  isCountedFailure: () => isCountedFailure,
88420
+ isCredentialHeaderName: () => isCredentialHeaderName,
87901
88421
  isCreditPackAmountCents: () => isCreditPackAmountCents,
87902
88422
  isDbPricingPlan: () => isDbPricingPlan,
87903
88423
  isDeniedGoogleBrokerRedirectHost: () => isDeniedGoogleBrokerRedirectHost,
@@ -87953,8 +88473,10 @@ __export(dist_exports, {
87953
88473
  isSharedAuthTenantHost: () => isSharedAuthTenantHost,
87954
88474
  isSharedSuffix: () => isSharedSuffix,
87955
88475
  isSingleEmailAddress: () => isSingleEmailAddress,
88476
+ isSmsNotReceivedError: () => isSmsNotReceivedError,
87956
88477
  isSoftOutcome: () => isSoftOutcome,
87957
88478
  isStateValueMobileClassName: () => isStateValueMobileClassName,
88479
+ isStillLoadingScreenshotKey: () => isStillLoadingScreenshotKey,
87958
88480
  isSttModelForProvider: () => isSttModelForProvider,
87959
88481
  isTestReadyForPlatform: () => isTestReadyForPlatform,
87960
88482
  isTestVariableSecretKey: () => isTestVariableSecretKey,
@@ -87962,6 +88484,7 @@ __export(dist_exports, {
87962
88484
  isUIVariant: () => isUIVariant,
87963
88485
  isUnexpectedPassError: () => isUnexpectedPassError,
87964
88486
  isUnsafePrecondition: () => isUnsafePrecondition,
88487
+ isUnverifiedHealPass: () => isUnverifiedHealPass,
87965
88488
  isUsableDeepgramKey: () => isUsableDeepgramKey,
87966
88489
  isUserCancelledError: () => isUserCancelledError,
87967
88490
  isValidAgenticCliModel: () => isValidAgenticCliModel,
@@ -87986,8 +88509,10 @@ __export(dist_exports, {
87986
88509
  lintTestData: () => lintTestData,
87987
88510
  lintTestQuality: () => lintTestQuality,
87988
88511
  literalUrlRegex: () => literalUrlRegex,
88512
+ loginFormAlertTexts: () => loginFormAlertTexts,
87989
88513
  longCredentialValues: () => longCredentialValues,
87990
88514
  maskedOutsideStrings: () => maskedOutsideStrings,
88515
+ mayBeUnverifiedHealPass: () => mayBeUnverifiedHealPass,
87991
88516
  mediumForMobilePlatform: () => mediumForMobilePlatform,
87992
88517
  mergePageType: () => mergePageType,
87993
88518
  mergeSuspectedBugsAcrossSurveys: () => mergeSuspectedBugsAcrossSurveys,
@@ -88021,7 +88546,9 @@ __export(dist_exports, {
88021
88546
  opensRegexLiteral: () => opensRegexLiteral,
88022
88547
  otherMobilePlatform: () => otherMobilePlatform,
88023
88548
  outcomeNamedByAssertion: () => outcomeNamedByAssertion,
88549
+ pageScreenshotKey: () => pageScreenshotKey,
88024
88550
  parenDepthDelta: () => parenDepthDelta,
88551
+ parseAbsenceProofMode: () => parseAbsenceProofMode,
88025
88552
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
88026
88553
  parseAppTimezone: () => parseAppTimezone,
88027
88554
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
@@ -88097,7 +88624,7 @@ __export(dist_exports, {
88097
88624
  redactNeedlesText: () => redactNeedlesText,
88098
88625
  redirectOnAppSite: () => redirectOnAppSite,
88099
88626
  refuseEncodedRequestBodies: () => refuseEncodedRequestBodies,
88100
- regexLiteralEnd: () => regexLiteralEnd2,
88627
+ regexLiteralEnd: () => regexLiteralEnd,
88101
88628
  regexOpensAfter: () => regexOpensAfter,
88102
88629
  registrableDomain: () => registrableDomain,
88103
88630
  renderIdentity: () => renderIdentity,
@@ -88139,6 +88666,9 @@ __export(dist_exports, {
88139
88666
  saveOpensConfirm: () => saveOpensConfirm,
88140
88667
  scanClockDependentLiterals: () => scanClockDependentLiterals,
88141
88668
  scanHtmlTags: () => scanHtmlTags,
88669
+ scrubCredentialHeaderValue: () => scrubCredentialHeaderValue,
88670
+ scrubCredentialHeaders: () => scrubCredentialHeaders,
88671
+ scrubCredentialHeadersDeep: () => scrubCredentialHeadersDeep,
88142
88672
  scrubCredentialStringsIn: () => scrubCredentialStringsIn,
88143
88673
  scrubCredentialText: () => scrubCredentialText,
88144
88674
  scrubSearchParams: () => scrubSearchParams,
@@ -88149,9 +88679,12 @@ __export(dist_exports, {
88149
88679
  selectorForValue: () => selectorForValue,
88150
88680
  setAgenticCliRuntime: () => setAgenticCliRuntime,
88151
88681
  setProviderChainOverrides: () => setProviderChainOverrides,
88682
+ signInStepPassedBeforeFailure: () => signInStepPassedBeforeFailure,
88683
+ signsInInline: () => signsInInline,
88152
88684
  signupPlanFor: () => signupPlanFor,
88153
88685
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
88154
88686
  siteOf: () => siteOf,
88687
+ specCallsExpect: () => specCallsExpect,
88155
88688
  specItemsFromTestPlans: () => specItemsFromTestPlans,
88156
88689
  splitAnswerLines: () => splitAnswerLines,
88157
88690
  statedLimitsFromAttrs: () => statedLimitsFromAttrs,
@@ -88199,6 +88732,7 @@ __export(dist_exports, {
88199
88732
  visualProDailyBudget: () => visualProDailyBudget,
88200
88733
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
88201
88734
  voicesForTtsModel: () => voicesForTtsModel,
88735
+ withExpectAliasesAsExpect: () => withExpectAliasesAsExpect,
88202
88736
  withGeneratorReviewSource: () => withGeneratorReviewSource,
88203
88737
  withReviewSource: () => withReviewSource,
88204
88738
  withoutNotChargedClaim: () => withoutNotChargedClaim,
@@ -88253,6 +88787,7 @@ var init_dist = __esm({
88253
88787
  init_encoded_forms();
88254
88788
  init_block_comments();
88255
88789
  init_redact_needles();
88790
+ init_credential_header_scrub();
88256
88791
  init_voice_config();
88257
88792
  init_deepgram_speech();
88258
88793
  init_deepgram_voices();
@@ -88946,7 +89481,13 @@ await expect(page).not.toHaveURL(/accounts\\.google\\.com/, { timeout: 60_000 })
88946
89481
  \`\`\`
88947
89482
  Never emit code that clicks, fills or asserts anything on accounts.google.com.`;
88948
89483
  }
88949
- function buildGoogleSignInTestGenSection(_opts) {
89484
+ function buildGoogleSignInTestGenSection(_opts, ctx = {}) {
89485
+ if (ctx.appLoginGiven) {
89486
+ return `
89487
+ ## SIGN-IN: USE THE TEST ACCOUNT
89488
+ This project gave a test account (the Browser Credentials / \`process.env.TEST_USER_*\` values). Sign in with it on the app's own email (or username) and password form, even when the app also offers Continue with Google. Do not click Continue with Google / Sign in with Google / Sign up with Google to sign in.
89489
+ Use Continue with Google only when this scenario's goal is the Google sign-in itself, or the app has no email/username and password sign-in at all. Then click that control on the app and wait until the browser is back on the app: \`await expect(page).not.toHaveURL(/accounts\\.google\\.com/, { timeout: 60_000 })\` (the runner completes Google's step). Never click, fill or assert anything on accounts.google.com, never type a password or code there.`;
89490
+ }
88950
89491
  return `
88951
89492
  ## GOOGLE SIGN-IN (the runner completes Google's step)
88952
89493
  When this scenario uses Continue with Google / Sign in with Google / Sign up with Google: