@validate.qa/runner 1.0.102 → 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-E2VEMBQQ.mjs → chunk-6N4QCCEH.mjs} +2 -2
  3. package/dist/{chunk-ZZYBRIBT.mjs → chunk-EBWZ7NGU.mjs} +11 -8
  4. package/dist/{chunk-3JM6XTSZ.mjs → chunk-EKY6CN4Y.mjs} +660 -123
  5. package/dist/{chunk-DN3O6EYT.mjs → chunk-MEE4DXRX.mjs} +19834 -3064
  6. package/dist/{chunk-CBVHPYVJ.mjs → chunk-OXDU6ARE.mjs} +86 -7
  7. package/dist/{chunk-AAUHXGDS.mjs → chunk-PUMRAFJS.mjs} +37 -13
  8. package/dist/{chunk-4WLP3RWN.mjs → chunk-TGVKABFN.mjs} +45 -3
  9. package/dist/{chunk-5DFBEL72.mjs → chunk-TS4OUJVT.mjs} +1 -1
  10. package/dist/{chunk-BXLEPT4M.mjs → chunk-UR7KDOTP.mjs} +32 -8
  11. package/dist/{chunk-E2RRAE27.mjs → chunk-UZ67UBXH.mjs} +14 -3
  12. package/dist/{chunk-HJ6FKHMM.mjs → chunk-VWE3SQ2Z.mjs} +1 -1
  13. package/dist/{chunk-PWFWMVZU.mjs → chunk-XXZZMG6Q.mjs} +15 -3
  14. package/dist/cli.js +35519 -17101
  15. package/dist/cli.mjs +1171 -251
  16. package/dist/credential-mcp-server.js +750 -133
  17. package/dist/credential-mcp-server.mjs +3 -3
  18. package/dist/discovery-mcp-server.js +714 -132
  19. package/dist/discovery-mcp-server.mjs +4 -4
  20. package/dist/dist-BXHLIG5H.mjs +17 -0
  21. package/dist/inbox-mcp-server.js +743 -129
  22. package/dist/inbox-mcp-server.mjs +3 -3
  23. package/dist/route-seed-worker.js +672 -125
  24. package/dist/route-seed-worker.mjs +3 -3
  25. package/dist/run-write-guard-preload.js +714 -126
  26. package/dist/run-write-guard-preload.mjs +4 -4
  27. package/dist/run-write-guard.js +714 -126
  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-52M5RDZ6.mjs +0 -17
@@ -128,7 +128,13 @@ function buildSandboxedEnv(processEnv, testVariables) {
128
128
  return env;
129
129
  }
130
130
  function escapeEnvValue(value) {
131
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
131
+ if (!value.includes("\r")) {
132
+ for (const quote of ["'", "`"]) {
133
+ if (!value.includes(quote))
134
+ return `${quote}${value}${quote}`;
135
+ }
136
+ }
137
+ const escaped = value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
132
138
  return `"${escaped}"`;
133
139
  }
134
140
  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;
@@ -1628,6 +1634,12 @@ function isCancelledReason(error) {
1628
1634
  function isHeldHealRun(run) {
1629
1635
  return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
1630
1636
  }
1637
+ function mayBeUnverifiedHealPass(run, tags) {
1638
+ return !!run && run.status === "PASSED" && run.mode === "heal" && !isHeldHealRun(run) && (tags ?? []).includes(NEEDS_REVIEW_TAG);
1639
+ }
1640
+ function isUnverifiedHealPass(run, tags, healConfidence) {
1641
+ return mayBeUnverifiedHealPass(run, tags) && typeof healConfidence === "string" && UNVERIFIED_HEAL_CONFIDENCES.has(healConfidence);
1642
+ }
1631
1643
  function isRunInterruptedError(error) {
1632
1644
  return typeof error === "string" && RUN_INTERRUPTED_ERROR_PREFIXES.some((prefix) => error.startsWith(prefix));
1633
1645
  }
@@ -1714,12 +1726,25 @@ function classifyErrorMessage(error) {
1714
1726
  return "config_issue";
1715
1727
  if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
1716
1728
  return "email_not_received";
1729
+ if (SMS_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
1730
+ return "email_not_received";
1717
1731
  if (isCancelledReason(error))
1718
1732
  return "cancelled";
1719
1733
  if (isRetriedRunInterruption(error))
1720
1734
  return "interrupted";
1721
1735
  return null;
1722
1736
  }
1737
+ function isSmsNotReceivedError(error) {
1738
+ if (typeof error !== "string")
1739
+ return false;
1740
+ const normalized = error.replace(/^\s+/, "").replace(/^Error:\s*/i, "");
1741
+ return SMS_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix));
1742
+ }
1743
+ function blockedRunLogLine(error, durationMs) {
1744
+ const reason = classifyErrorMessage(error);
1745
+ 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";
1746
+ return `BLOCKED (${durationMs}ms) \u2014 ${tail}`;
1747
+ }
1723
1748
  function stripEnvironmentalPrefix(error) {
1724
1749
  for (const prefix of ENVIRONMENTAL_ERROR_PREFIXES) {
1725
1750
  if (error.startsWith(prefix)) {
@@ -1782,8 +1807,19 @@ function computePassRate(passed, tested) {
1782
1807
  }
1783
1808
  function summarizeHealth(items) {
1784
1809
  const c = { passed: 0, failed: 0, errored: 0, environmental: 0, never_run: 0 };
1810
+ let rateLimited = 0;
1811
+ let configIssue = 0;
1785
1812
  for (const item of items) {
1786
- c[classifyTestHealth(item.run)] += item.weight ?? 1;
1813
+ const weight = item.weight ?? 1;
1814
+ const bucket = classifyTestHealth(item.run);
1815
+ c[bucket] += weight;
1816
+ if (bucket === "environmental") {
1817
+ const reason = classifyErrorMessage(item.run?.error);
1818
+ if (reason === "rate_limit")
1819
+ rateLimited += weight;
1820
+ else if (isConfigLikeReason(reason))
1821
+ configIssue += weight;
1822
+ }
1787
1823
  }
1788
1824
  const tested = c.passed + c.failed + c.errored;
1789
1825
  return {
@@ -1791,6 +1827,8 @@ function summarizeHealth(items) {
1791
1827
  failed: c.failed,
1792
1828
  errored: c.errored,
1793
1829
  environmental: c.environmental,
1830
+ rateLimited,
1831
+ configIssue,
1794
1832
  neverRun: c.never_run,
1795
1833
  tested,
1796
1834
  total: tested + c.environmental + c.never_run,
@@ -1827,7 +1865,7 @@ function reportedBugWhere() {
1827
1865
  function bugReviewStatus(status) {
1828
1866
  return status === "QUARANTINED" ? "confirmed" : "needs-review";
1829
1867
  }
1830
- 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;
1868
+ 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;
1831
1869
  var init_run_classification = __esm({
1832
1870
  "../shared/dist/run-classification.js"() {
1833
1871
  "use strict";
@@ -1847,6 +1885,8 @@ var init_run_classification = __esm({
1847
1885
  ];
1848
1886
  isUserCancelledError = isCancelledReason;
1849
1887
  HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
1888
+ UNVERIFIED_HEAL_CONFIDENCES = /* @__PURE__ */ new Set(["low", "medium"]);
1889
+ NEEDS_REVIEW_TAG = "@needs-review";
1850
1890
  RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
1851
1891
  SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
1852
1892
  RUN_LOST_ERROR_PREFIX = "Runner lost this run";
@@ -1867,10 +1907,12 @@ var init_run_classification = __esm({
1867
1907
  "VERIFICATION_EMAIL_HAS_NO_CODE:",
1868
1908
  "VERIFICATION_EMAIL_HAS_NO_LINK:"
1869
1909
  ];
1910
+ SMS_NOT_RECEIVED_PREFIXES = ["VERIFICATION_SMS_NOT_RECEIVED:"];
1870
1911
  ENVIRONMENTAL_ERROR_PREFIXES = [
1871
1912
  "RATE_LIMIT_BLOCKED:",
1872
1913
  "CONFIG_ISSUE:",
1873
- ...EMAIL_NOT_RECEIVED_PREFIXES
1914
+ ...EMAIL_NOT_RECEIVED_PREFIXES,
1915
+ ...SMS_NOT_RECEIVED_PREFIXES
1874
1916
  ];
1875
1917
  ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
1876
1918
  ...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
@@ -4366,6 +4408,65 @@ var init_audit_grouping = __esm({
4366
4408
  }
4367
4409
  });
4368
4410
 
4411
+ // ../shared/dist/regex-literal.js
4412
+ function opensRegexLiteral(code, i) {
4413
+ let j = i - 1;
4414
+ while (j >= 0 && (code[j] === " " || code[j] === " "))
4415
+ j--;
4416
+ if (j < 0 || code[j] === "\n" || code[j] === "\r")
4417
+ return true;
4418
+ return regexOpensAfter(code, j);
4419
+ }
4420
+ function regexOpensAfter(code, j) {
4421
+ const prev = code[j];
4422
+ if (REGEX_OPENERS.includes(prev))
4423
+ return !((prev === "+" || prev === "-") && code[j - 1] === prev);
4424
+ if (!/[a-z]/.test(prev))
4425
+ return false;
4426
+ let start = j;
4427
+ while (start > 0 && /[A-Za-z0-9_$]/.test(code[start - 1]))
4428
+ start--;
4429
+ if (start > 0 && code[start - 1] === ".")
4430
+ return false;
4431
+ return REGEX_KEYWORDS.has(code.slice(start, j + 1));
4432
+ }
4433
+ function regexLiteralEnd(code, i) {
4434
+ let k = i + 1;
4435
+ let inClass = false;
4436
+ while (k < code.length) {
4437
+ const ch = code[k];
4438
+ if (ch === "\n" || ch === "\r")
4439
+ return k;
4440
+ if (ch === "\\") {
4441
+ if (code[k + 1] === "\n" || code[k + 1] === "\r")
4442
+ return k + 1;
4443
+ k += 2;
4444
+ continue;
4445
+ }
4446
+ if (inClass) {
4447
+ if (ch === "]")
4448
+ inClass = false;
4449
+ } else if (ch === "[") {
4450
+ inClass = true;
4451
+ } else if (ch === "/") {
4452
+ k++;
4453
+ while (k < code.length && /[a-z]/i.test(code[k]))
4454
+ k++;
4455
+ return k;
4456
+ }
4457
+ k++;
4458
+ }
4459
+ return code.length;
4460
+ }
4461
+ var REGEX_OPENERS, REGEX_KEYWORDS;
4462
+ var init_regex_literal = __esm({
4463
+ "../shared/dist/regex-literal.js"() {
4464
+ "use strict";
4465
+ REGEX_OPENERS = "(,=:[!&|?{};+-*%~^<>";
4466
+ REGEX_KEYWORDS = /* @__PURE__ */ new Set(["return", "typeof", "case", "do", "else", "in", "of", "new", "delete", "void", "throw", "await", "yield", "instanceof"]);
4467
+ }
4468
+ });
4469
+
4369
4470
  // ../shared/dist/assertion-classifier.js
4370
4471
  function isButtonOrLinkLocator(line) {
4371
4472
  return /getByRole\s*\(\s*['"`](?:button|link)['"`]/i.test(line);
@@ -4674,7 +4775,7 @@ function classifyMobileAssertValue(value, context = {}) {
4674
4775
  return { tier: 2, reason: t2[1] };
4675
4776
  return null;
4676
4777
  }
4677
- function regexLiteralEnd(text, start) {
4778
+ function regexLiteralEnd2(text, start) {
4678
4779
  let inClass = false;
4679
4780
  for (let i = start + 1; i < text.length; i++) {
4680
4781
  const ch = text[i];
@@ -4700,7 +4801,7 @@ function parenDepthDelta(text) {
4700
4801
  let depth = 0;
4701
4802
  let quote = null;
4702
4803
  let escaped = false;
4703
- let prev = "";
4804
+ let prevAt = -1;
4704
4805
  let noRegexUntil = -1;
4705
4806
  for (let i = 0; i < text.length; i++) {
4706
4807
  const ch = text[i];
@@ -4715,7 +4816,7 @@ function parenDepthDelta(text) {
4715
4816
  if (quote) {
4716
4817
  if (ch === quote) {
4717
4818
  quote = null;
4718
- prev = ch;
4819
+ prevAt = i;
4719
4820
  }
4720
4821
  continue;
4721
4822
  }
@@ -4734,11 +4835,11 @@ function parenDepthDelta(text) {
4734
4835
  i = close + 1;
4735
4836
  continue;
4736
4837
  }
4737
- if (i > noRegexUntil && (prev === "" || REGEX_PRECEDER.has(prev))) {
4738
- const end = regexLiteralEnd(text, i);
4838
+ if (i > noRegexUntil && (prevAt < 0 || regexOpensAfter(text, prevAt))) {
4839
+ const end = regexLiteralEnd2(text, i);
4739
4840
  if (end > 0) {
4740
4841
  i = end - 1;
4741
- prev = "a";
4842
+ prevAt = end - 1;
4742
4843
  continue;
4743
4844
  }
4744
4845
  const nl = text.indexOf("\n", i);
@@ -4750,14 +4851,15 @@ function parenDepthDelta(text) {
4750
4851
  else if (ch === ")")
4751
4852
  depth--;
4752
4853
  if (ch !== " " && ch !== " " && ch !== "\n")
4753
- prev = ch;
4854
+ prevAt = i;
4754
4855
  }
4755
4856
  return depth;
4756
4857
  }
4757
- 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;
4858
+ 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;
4758
4859
  var init_assertion_classifier = __esm({
4759
4860
  "../shared/dist/assertion-classifier.js"() {
4760
4861
  "use strict";
4862
+ init_regex_literal();
4761
4863
  PENDING_STATE_TEXT_REASON = 'pending-state text ("Sending...") is transient';
4762
4864
  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}|…)/;
4763
4865
  TIER1_PATTERNS = [
@@ -4834,7 +4936,6 @@ var init_assertion_classifier = __esm({
4834
4936
  new RegExp(`\\.filter\\s*\\(\\s*\\{[^}]{0,200}?\\bhasText\\s*:\\s*${LOCATOR_ARG_VALUE}`, "gi")
4835
4937
  ];
4836
4938
  EXPECT_TOKEN = /\bexpect\s*[.(]/;
4837
- REGEX_PRECEDER = /* @__PURE__ */ new Set(["(", ",", "=", ":", "[", "!", "&", "|", "?", "{", "}", ";", "+", "-", "*", "%", "<", ">", "~", "^"]);
4838
4939
  }
4839
4940
  });
4840
4941
 
@@ -4842,14 +4943,80 @@ var init_assertion_classifier = __esm({
4842
4943
  function isExpectCall(code) {
4843
4944
  return EXPECT_CALL_RE.test(code);
4844
4945
  }
4946
+ function specCallsExpect(code) {
4947
+ if (EXPECT_CALL_RE.test(code) || SPACED_EXPECT_CALL_RE.test(code) || CONFIGURE_IN_PLACE_RE.test(code))
4948
+ return true;
4949
+ const names = /* @__PURE__ */ new Set();
4950
+ for (const clause of code.matchAll(IMPORT_CLAUSE_RE)) {
4951
+ for (const alias of clause[1].matchAll(EXPECT_ALIAS_RE))
4952
+ names.add(alias[1]);
4953
+ }
4954
+ for (const decl of code.matchAll(DERIVED_EXPECT_DECL_RE)) {
4955
+ if (names.size >= MAX_DERIVED_EXPECT_NAMES)
4956
+ break;
4957
+ const base = decl[2];
4958
+ if (base === void 0 || base === "expect" || names.has(base))
4959
+ names.add(decl[1]);
4960
+ }
4961
+ names.delete("expect");
4962
+ if (names.size === 0)
4963
+ return false;
4964
+ const alts = [...names].map((n) => n.replace(/\$/g, "\\$")).join("|");
4965
+ return new RegExp(String.raw`(?<![\w$.])(?:${alts})${EXPECT_MODIFIERS}`).test(code);
4966
+ }
4845
4967
  function asPlainExpect(code) {
4846
4968
  return code.replace(/\bexpect\.soft(\s*\()/g, "expect$1");
4847
4969
  }
4848
- var EXPECT_CALL_RE;
4970
+ function expectAliasNames(code) {
4971
+ if (!MAY_ALIAS.test(code))
4972
+ return [];
4973
+ const imported = [];
4974
+ for (const m of code.matchAll(PW_IMPORT)) {
4975
+ for (const a of m[1].matchAll(IMPORT_AS_EXPECT))
4976
+ imported.push(a[1]);
4977
+ }
4978
+ const known = /* @__PURE__ */ new Set(["expect", ...imported]);
4979
+ const derived = [];
4980
+ for (const m of code.matchAll(DECLARATION)) {
4981
+ const rhs = DERIVED.exec(code.slice(m.index + m[0].length, m.index + m[0].length + 200));
4982
+ if (!rhs)
4983
+ continue;
4984
+ const base = rhs[1];
4985
+ const fromKnown = base === void 0 || (rhs[2] ? base === "test" : known.has(base));
4986
+ if (!fromKnown || m[1] === "expect")
4987
+ continue;
4988
+ known.add(m[1]);
4989
+ derived.push(m[1]);
4990
+ }
4991
+ const fromImports = LOCAL_EXPECT.test(code) ? [] : imported;
4992
+ return [.../* @__PURE__ */ new Set([...fromImports, ...derived])].filter((n) => n !== "expect");
4993
+ }
4994
+ function withExpectAliasesAsExpect(code) {
4995
+ let out = code;
4996
+ for (const name of expectAliasNames(code)) {
4997
+ const escaped = name.replace(/\$/g, "\\$");
4998
+ out = out.replace(new RegExp(`(?<![\\w$.])${escaped}(?=\\s{0,20}(?:\\.\\s{0,20}(?:soft|poll)\\s{0,20})?\\()`, "g"), "expect");
4999
+ }
5000
+ return out;
5001
+ }
5002
+ 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;
4849
5003
  var init_expect_call = __esm({
4850
5004
  "../shared/dist/expect-call.js"() {
4851
5005
  "use strict";
4852
5006
  EXPECT_CALL_RE = /\bexpect(?:\.(?:soft|poll))?\s*\(/;
5007
+ EXPECT_MODIFIERS = String.raw`\s*(?:\.\s*soft\s*)?(?:\.\s*poll\s*)?\(`;
5008
+ SPACED_EXPECT_CALL_RE = new RegExp(String.raw`\bexpect${EXPECT_MODIFIERS}`);
5009
+ CONFIGURE_IN_PLACE_RE = new RegExp(String.raw`\bexpect\s*\.\s*configure\s*\(\s*(?:\{[^{}()]{0,500}\}\s*)?\)${EXPECT_MODIFIERS}`);
5010
+ IMPORT_CLAUSE_RE = /\bimport\s+(?:type\s+)?\{([^{}]{0,2000})\}/g;
5011
+ EXPECT_ALIAS_RE = /\bexpect\s+as\s+([A-Za-z_$][\w$]*)/g;
5012
+ 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;
5013
+ MAX_DERIVED_EXPECT_NAMES = 20;
5014
+ PW_IMPORT = /\bimport\s{0,20}\{([^}]{0,4000})\}\s{0,20}from\s{0,20}['"]@playwright\/test['"]/g;
5015
+ IMPORT_AS_EXPECT = /(?:^|,)\s{0,20}expect\s{1,20}as\s{1,20}([A-Za-z_$][\w$]{0,99})\s{0,20}(?=,|$)/g;
5016
+ DECLARATION = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,99})\s{0,20}(?::[^=;\n]{0,200})?=\s{0,20}/g;
5017
+ 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}[.(]))/;
5018
+ LOCAL_EXPECT = /\b(?:const|let|var)\s{1,20}expect\b/;
5019
+ 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/;
4853
5020
  }
4854
5021
  });
4855
5022
 
@@ -4879,65 +5046,6 @@ var init_braced_clause = __esm({
4879
5046
  }
4880
5047
  });
4881
5048
 
4882
- // ../shared/dist/regex-literal.js
4883
- function opensRegexLiteral(code, i) {
4884
- let j = i - 1;
4885
- while (j >= 0 && (code[j] === " " || code[j] === " "))
4886
- j--;
4887
- if (j < 0 || code[j] === "\n" || code[j] === "\r")
4888
- return true;
4889
- return regexOpensAfter(code, j);
4890
- }
4891
- function regexOpensAfter(code, j) {
4892
- const prev = code[j];
4893
- if (REGEX_OPENERS.includes(prev))
4894
- return !((prev === "+" || prev === "-") && code[j - 1] === prev);
4895
- if (!/[a-z]/.test(prev))
4896
- return false;
4897
- let start = j;
4898
- while (start > 0 && /[A-Za-z0-9_$]/.test(code[start - 1]))
4899
- start--;
4900
- if (start > 0 && code[start - 1] === ".")
4901
- return false;
4902
- return REGEX_KEYWORDS.has(code.slice(start, j + 1));
4903
- }
4904
- function regexLiteralEnd2(code, i) {
4905
- let k = i + 1;
4906
- let inClass = false;
4907
- while (k < code.length) {
4908
- const ch = code[k];
4909
- if (ch === "\n" || ch === "\r")
4910
- return k;
4911
- if (ch === "\\") {
4912
- if (code[k + 1] === "\n" || code[k + 1] === "\r")
4913
- return k + 1;
4914
- k += 2;
4915
- continue;
4916
- }
4917
- if (inClass) {
4918
- if (ch === "]")
4919
- inClass = false;
4920
- } else if (ch === "[") {
4921
- inClass = true;
4922
- } else if (ch === "/") {
4923
- k++;
4924
- while (k < code.length && /[a-z]/i.test(code[k]))
4925
- k++;
4926
- return k;
4927
- }
4928
- k++;
4929
- }
4930
- return code.length;
4931
- }
4932
- var REGEX_OPENERS, REGEX_KEYWORDS;
4933
- var init_regex_literal = __esm({
4934
- "../shared/dist/regex-literal.js"() {
4935
- "use strict";
4936
- REGEX_OPENERS = "(,=:[!&|?{};+-*%~^<>";
4937
- REGEX_KEYWORDS = /* @__PURE__ */ new Set(["return", "typeof", "case", "do", "else", "in", "of", "new", "delete", "void", "throw", "await", "yield", "instanceof"]);
4938
- }
4939
- });
4940
-
4941
5049
  // ../shared/dist/assertion-proof.js
4942
5050
  function fragmentKindOf(literal, fromText) {
4943
5051
  const text = literal.trim();
@@ -5096,7 +5204,7 @@ function stripCoverageComments(code) {
5096
5204
  let copyFrom = 0;
5097
5205
  let i = 0;
5098
5206
  let quote = null;
5099
- let prev = "";
5207
+ let prevAt = -1;
5100
5208
  let noRegexBefore = 0;
5101
5209
  while (i < code.length) {
5102
5210
  const ch = code[i];
@@ -5107,17 +5215,18 @@ function stripCoverageComments(code) {
5107
5215
  }
5108
5216
  if (ch === quote || ch === "\n" && quote !== "`") {
5109
5217
  quote = null;
5110
- prev = ch === "\n" ? prev : ch;
5218
+ if (ch !== "\n")
5219
+ prevAt = i;
5111
5220
  }
5112
5221
  i++;
5113
5222
  continue;
5114
5223
  }
5115
5224
  const next = code[i + 1];
5116
- if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
5225
+ if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prevAt < 0 || regexOpensAfter(code, prevAt))) {
5117
5226
  const end = regexLiteralEnd3(code, i);
5118
5227
  if (end > 0) {
5119
5228
  i = end;
5120
- prev = "a";
5229
+ prevAt = end - 1;
5121
5230
  continue;
5122
5231
  }
5123
5232
  const eol = code.indexOf("\n", i);
@@ -5142,7 +5251,7 @@ function stripCoverageComments(code) {
5142
5251
  if (ch === '"' || ch === "'" || ch === "`")
5143
5252
  quote = ch;
5144
5253
  if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
5145
- prev = ch;
5254
+ prevAt = i;
5146
5255
  i++;
5147
5256
  }
5148
5257
  out.push(code.slice(copyFrom));
@@ -5831,11 +5940,12 @@ function assessTitleCoverage(title, code) {
5831
5940
  const repeatedHit = [...repeated].some((term) => hay.includes(term));
5832
5941
  return { covered: hit >= needed || repeatedHit, missingTerms };
5833
5942
  }
5834
- 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;
5943
+ 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;
5835
5944
  var init_expected_outcome_coverage = __esm({
5836
5945
  "../shared/dist/expected-outcome-coverage.js"() {
5837
5946
  "use strict";
5838
5947
  init_assertion_classifier();
5948
+ init_regex_literal();
5839
5949
  COVERAGE_STOP_WORDS = /* @__PURE__ */ new Set([
5840
5950
  "after",
5841
5951
  "before",
@@ -5868,7 +5978,6 @@ var init_expected_outcome_coverage = __esm({
5868
5978
  ]);
5869
5979
  MAX_ASSERTION_JOIN_CHARS = 8e3;
5870
5980
  MAX_COVERAGE_LINE_CHARS = 2e3;
5871
- REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
5872
5981
  BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
5873
5982
  cachedAssertionInput = null;
5874
5983
  cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
@@ -5964,7 +6073,7 @@ function replaceBlockComments(code, replacer) {
5964
6073
  continue;
5965
6074
  const next = code[i + 1];
5966
6075
  if (next !== "/" && next !== "*" && opensRegexLiteral(code, i)) {
5967
- i = regexLiteralEnd2(code, i) - 1;
6076
+ i = regexLiteralEnd(code, i) - 1;
5968
6077
  continue;
5969
6078
  }
5970
6079
  if (next === "/" && !(code[i - 1] === ":" && /[A-Za-z]/.test(code[i - 2] ?? ""))) {
@@ -6139,10 +6248,41 @@ var init_auth_handoff_coverage = __esm({
6139
6248
  });
6140
6249
 
6141
6250
  // ../shared/dist/auth-plan.js
6142
- function hasInlineLoginSteps(playwrightCode) {
6251
+ function stripStampedHelperRegions(code) {
6252
+ let out = code;
6253
+ for (const [open, close] of STAMPED_HELPER_REGIONS) {
6254
+ if (out.indexOf(open) === -1)
6255
+ continue;
6256
+ let kept = "";
6257
+ let i = 0;
6258
+ while (i < out.length) {
6259
+ const start = out.indexOf(open, i);
6260
+ if (start === -1) {
6261
+ kept += out.slice(i);
6262
+ break;
6263
+ }
6264
+ const end = out.indexOf(close, start + open.length);
6265
+ if (end === -1) {
6266
+ kept += out.slice(i);
6267
+ break;
6268
+ }
6269
+ const lineEnd = out.indexOf("\n", end);
6270
+ kept += out.slice(i, start);
6271
+ i = lineEnd === -1 ? out.length : lineEnd + 1;
6272
+ }
6273
+ out = kept;
6274
+ }
6275
+ return out;
6276
+ }
6277
+ function signsInInline(playwrightCode) {
6143
6278
  if (!playwrightCode)
6144
6279
  return false;
6145
- return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
6280
+ if (!/process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode))
6281
+ return false;
6282
+ if (/process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode))
6283
+ return true;
6284
+ const body = stripStampedHelperRegions(playwrightCode);
6285
+ return /process\.env\.TEST_USER_EMAIL/.test(body) && /__vqaWaitForEmail\s*\(/.test(body);
6146
6286
  }
6147
6287
  function hasGeneratedAuthGuard(playwrightCode) {
6148
6288
  if (!playwrightCode)
@@ -6161,7 +6301,7 @@ function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
6161
6301
  if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
6162
6302
  return true;
6163
6303
  const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
6164
- const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
6304
+ const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && signsInInline(code);
6165
6305
  return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
6166
6306
  }
6167
6307
  function codeReadsStoredCredentials(code) {
@@ -6282,7 +6422,7 @@ function authTypeTag(tags) {
6282
6422
  const explicit = explicitAuthTypeTags(t);
6283
6423
  return explicit.length === 1 ? explicit[0] : void 0;
6284
6424
  }
6285
- 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;
6425
+ 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;
6286
6426
  var init_auth_plan = __esm({
6287
6427
  "../shared/dist/auth-plan.js"() {
6288
6428
  "use strict";
@@ -6309,6 +6449,10 @@ var init_auth_plan = __esm({
6309
6449
  };
6310
6450
  TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
6311
6451
  API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
6452
+ STAMPED_HELPER_REGIONS = [
6453
+ ["// >>> VALIDATE_QA_INBOX", "// <<< VALIDATE_QA_INBOX"],
6454
+ ["// >>> VALIDATE_QA_SMS", "// <<< VALIDATE_QA_SMS"]
6455
+ ];
6312
6456
  }
6313
6457
  });
6314
6458
 
@@ -6317,6 +6461,9 @@ function parseLastActionProofMode(raw) {
6317
6461
  const v = (raw ?? "").trim().toLowerCase();
6318
6462
  return v === "on" || v === "shadow" ? v : "off";
6319
6463
  }
6464
+ function parseAbsenceProofMode(raw) {
6465
+ return parseLastActionProofMode(raw);
6466
+ }
6320
6467
  function isUrlProof(stmt) {
6321
6468
  if (URL_PROOF.test(stmt))
6322
6469
  return true;
@@ -6704,6 +6851,65 @@ function isVacuousAccountAbsence(stmt, shown, account) {
6704
6851
  }
6705
6852
  return true;
6706
6853
  }
6854
+ function isAbsenceCheck(stmt) {
6855
+ if (!claimsAbsence(stmt))
6856
+ return false;
6857
+ const bare = stmt.replace(NOT_BEFORE_MATCHER, ".");
6858
+ return ABSENCE_READ.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
6859
+ }
6860
+ function comparableSubject(subject) {
6861
+ let s = subject.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
6862
+ for (let prev = ""; prev !== s; ) {
6863
+ prev = s;
6864
+ s = s.replace(/\.(?:count|isVisible|isHidden|first|last)\(\)$/, "").replace(/\.nth\(\d+\)$/, "");
6865
+ }
6866
+ return s;
6867
+ }
6868
+ function subjectsThatExisted(checks, interactionStmts) {
6869
+ const out = [];
6870
+ const bound = locatorBindings(interactionStmts);
6871
+ const resolve = (expr) => {
6872
+ for (let depth = 0; depth < 4; depth++) {
6873
+ const lead = expr.match(/^\s*(?:await\s+)?([A-Za-z_$][\w$]*)(?![\w$(])/);
6874
+ const to = lead ? bound.get(lead[1]) : void 0;
6875
+ if (!lead || to === void 0)
6876
+ break;
6877
+ expr = `${to.replace(/;\s*$/, "")}${expr.slice(lead[0].length)}`;
6878
+ }
6879
+ return expr;
6880
+ };
6881
+ for (const { stmt } of checks) {
6882
+ if (isAbsenceCheck(stmt))
6883
+ continue;
6884
+ const subject = extractExpectSubject(stmt);
6885
+ if (subject)
6886
+ out.push(comparableSubject(subject));
6887
+ }
6888
+ for (const stmt of interactionStmts) {
6889
+ if (EXPECT_CALL_RE.test(stmt))
6890
+ continue;
6891
+ for (const m of stmt.matchAll(LOCATOR_USE)) {
6892
+ if (m[0].includes("waitFor") && /state\s*:\s*['"`](?:hidden|detached)/.test(stmt.slice(m.index)))
6893
+ continue;
6894
+ const before = stmt.slice(0, m.index).replace(/^\s*(?:(?:const|let|var)\s+[\w$]+\s*=|return\b)\s*/, "");
6895
+ if (before.trim())
6896
+ out.push(comparableSubject(resolve(before)));
6897
+ }
6898
+ }
6899
+ return out;
6900
+ }
6901
+ function isUnshownAbsence(stmt, existed, entered) {
6902
+ if (!isAbsenceCheck(stmt))
6903
+ return false;
6904
+ const raw = extractExpectSubject(stmt);
6905
+ if (!raw || !/\b(?:locator|getBy[A-Z]\w*|frameLocator)\s*\(/.test(raw))
6906
+ return false;
6907
+ const subject = comparableSubject(raw);
6908
+ if (existed.some((e) => e === subject || e.startsWith(`${subject}.`)))
6909
+ return false;
6910
+ const named = raw.match(/['"`][^'"`]+['"`]/g) ?? [];
6911
+ return !named.some((q) => entered.values.has(q.slice(1, -1).toLowerCase()));
6912
+ }
6707
6913
  function endOfEnclosingCall(stmt, from) {
6708
6914
  let depth = 0;
6709
6915
  for (let i = from; i < stmt.length; i++) {
@@ -7203,10 +7409,31 @@ function comparesWithItself(subject, tail) {
7203
7409
  const expected = squash2(args);
7204
7410
  return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
7205
7411
  }
7412
+ function ariaSnapshotTexts(template) {
7413
+ const out = [];
7414
+ for (const raw of template.split(ARIA_ENTRY_SPLIT)) {
7415
+ const m = ARIA_ENTRY.exec(raw.trim());
7416
+ if (!m || !ARIA_CONTENT_ROLES.has(m[1]))
7417
+ continue;
7418
+ for (const text of [m[2], m[3]]) {
7419
+ const t = text?.replace(/\\(.)/g, "$1").replace(/^["']|["']$/g, "").trim();
7420
+ if (!t || t.startsWith("/") || !/[\p{L}\p{N}]/u.test(t) || /["'`\\]/.test(t))
7421
+ continue;
7422
+ out.push(t);
7423
+ }
7424
+ }
7425
+ return out;
7426
+ }
7427
+ function presenceTwin(stmt) {
7428
+ return stmt.replace(NOT_HIDDEN, ".toBeVisible(").replace(NOT_COUNT_ZERO, ".toBeVisible(");
7429
+ }
7206
7430
  function asPlainAssertion(s) {
7207
7431
  return plainAssertion(s, false);
7208
7432
  }
7209
7433
  function plainAssertion(s, keepSelfCompare) {
7434
+ const twin = presenceTwin(s.stmt);
7435
+ if (twin !== s.stmt)
7436
+ return plainAssertion({ ...s, stmt: twin }, keepSelfCompare);
7210
7437
  const { stmt } = s;
7211
7438
  if (COUNT_AT_LEAST_ZERO.test(stmt))
7212
7439
  return [];
@@ -7217,6 +7444,12 @@ function plainAssertion(s, keepSelfCompare) {
7217
7444
  const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
7218
7445
  const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
7219
7446
  const as = (rewritten) => [{ ...s, stmt: rewritten }];
7447
+ const aria = ARIA_TEMPLATE_CALL.exec(tail);
7448
+ if (aria && !NEGATED_ASSERT.test(stmt)) {
7449
+ const texts = ariaSnapshotTexts(aria[2]);
7450
+ if (texts.length > 0)
7451
+ return texts.map((t) => ({ ...s, stmt: `await expect(${plain}).toContainText(${JSON.stringify(t)});` }));
7452
+ }
7220
7453
  if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
7221
7454
  return [];
7222
7455
  if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
@@ -7273,7 +7506,7 @@ function namedPagePath(arg) {
7273
7506
  const quoted = /^(['"`])([^'"`]*)\1$/.exec(text);
7274
7507
  const regex = quoted ? null : /^\/((?:\\.|[^/\\])+)\/[a-z]*$/.exec(text);
7275
7508
  if (quoted) {
7276
- path = quoted[2].replace(/^\$\{[^}]*\}/, "").replace(/^\*\*/, "").replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "").replace(/\*\*$/, "");
7509
+ path = quoted[2].replace(/^\$\{[^}]*\}/, "").replace(/^\*\*/, "").replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "").replace(/\/\*\*$/, "/");
7277
7510
  } else if (regex) {
7278
7511
  path = regex[1].replace(/^\^/, "").replace(/^\.\*/, "").replace(/\$$/, "").replace(/\\([/.-])/g, "$1");
7279
7512
  } else {
@@ -7523,7 +7756,8 @@ function teardownLines(lines) {
7523
7756
  }
7524
7757
  return out;
7525
7758
  }
7526
- function validateAssertionStrength(code, opts = {}) {
7759
+ function validateAssertionStrength(source, opts = {}) {
7760
+ const code = withExpectAliasesAsExpect(source);
7527
7761
  const body = stripLoginScaffolding(code);
7528
7762
  const lines = body.split("\n");
7529
7763
  const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
@@ -7676,8 +7910,23 @@ function validateAssertionStrength(code, opts = {}) {
7676
7910
  const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
7677
7911
  const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
7678
7912
  const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
7913
+ const absenceProof = opts.absenceProof ?? "off";
7914
+ let absenceOnly = false;
7915
+ if (absenceProof !== "off" && proof.kind === "durable") {
7916
+ const existed = subjectsThatExisted(checks, interactionStmtsRaw);
7917
+ const kept = checks.filter(({ stmt }) => !isUnshownAbsence(stmt, existed, entered));
7918
+ absenceOnly = kept.length < checks.length && classifyOutcomeProof(kept, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn).kind !== "durable";
7919
+ if (absenceOnly && absenceProof === "on") {
7920
+ return {
7921
+ ok: false,
7922
+ severity: mutated ? "block" : "downgrade",
7923
+ 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",
7924
+ signals: base({ interacted, mutated, navOnly, hasOutcomeProof: false, terminal, absenceOnly: true })
7925
+ };
7926
+ }
7927
+ }
7679
7928
  if (proof.kind === "durable") {
7680
- const signals2 = base({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
7929
+ const signals2 = base({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal, ...absenceOnly ? { absenceOnly } : {} });
7681
7930
  const { last, prev } = acts;
7682
7931
  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])))) {
7683
7932
  const lastLine = interactionStmtsWithLines[last.i].startLine;
@@ -7781,7 +8030,7 @@ function validateAssertionStrength(code, opts = {}) {
7781
8030
  signals
7782
8031
  };
7783
8032
  }
7784
- 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;
8033
+ 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;
7785
8034
  var init_assertion_strength = __esm({
7786
8035
  "../shared/dist/assertion-strength.js"() {
7787
8036
  "use strict";
@@ -7870,6 +8119,8 @@ var init_assertion_strength = __esm({
7870
8119
  NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
7871
8120
  ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
7872
8121
  TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
8122
+ ABSENCE_READ = /\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)|\.toBeAttached\s*\(|\.toHaveText\s*\(|\.toContainText\s*\(/;
8123
+ LOCATOR_USE = /\.\s*(?:click|dblclick|tap|hover|focus|fill|clear|press|pressSequentially|type|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|selectText|waitFor)\s*\(/g;
7873
8124
  SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
7874
8125
  NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
7875
8126
  ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
@@ -7911,6 +8162,29 @@ var init_assertion_strength = __esm({
7911
8162
  IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
7912
8163
  PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
7913
8164
  COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
8165
+ ARIA_TEMPLATE_CALL = /^\)\s*\.\s*toMatchAriaSnapshot\s*\(\s*(['"`])((?:\\[\s\S]|(?!\1)[^\\])*)\1/;
8166
+ ARIA_ENTRY_SPLIT = /(?:^|\s)-\s+(?=\/?[a-z][a-z-]*(?:\s|:|\[|$))/;
8167
+ ARIA_CONTENT_ROLES = /* @__PURE__ */ new Set([
8168
+ "listitem",
8169
+ "row",
8170
+ "cell",
8171
+ "gridcell",
8172
+ "article",
8173
+ "treeitem",
8174
+ "paragraph",
8175
+ "text",
8176
+ "strong",
8177
+ "emphasis",
8178
+ "code",
8179
+ "blockquote",
8180
+ "term",
8181
+ "definition",
8182
+ "alert",
8183
+ "status"
8184
+ ]);
8185
+ ARIA_ENTRY = /^([a-z][a-z-]*)(?:\s+"((?:\\.|[^"\\])*)")?(?:\s*\[[^\]]*\])*\s*(?::\s*(\S.*)?)?$/s;
8186
+ NOT_HIDDEN = /\.not\s*\.\s*toBeHidden\s*\(/g;
8187
+ NOT_COUNT_ZERO = /\.not\s*\.\s*toHaveCount\s*\(\s*0\s*(?:,\s*|(?=\)))/g;
7914
8188
  URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
7915
8189
  PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
7916
8190
  FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
@@ -9235,6 +9509,19 @@ function googleBrokerUnavailableLine(lines, testError) {
9235
9509
  }
9236
9510
  return last;
9237
9511
  }
9512
+ function googleSignInRefusedLine(lines) {
9513
+ let last = null;
9514
+ for (const raw of lines ?? []) {
9515
+ for (const line of String(raw).split("\n")) {
9516
+ const text = line.trim();
9517
+ if (GOOGLE_SIGN_IN_REFUSED.test(text))
9518
+ last = text;
9519
+ else if (GOOGLE_SIGNED_IN.test(text))
9520
+ last = null;
9521
+ }
9522
+ }
9523
+ return last;
9524
+ }
9238
9525
  function stepConsoleLines(result) {
9239
9526
  const steps = Array.isArray(result) ? result : result && typeof result === "object" && Array.isArray(result.stepResults) ? result.stepResults : [];
9240
9527
  return steps.flatMap((step) => {
@@ -9242,7 +9529,23 @@ function stepConsoleLines(result) {
9242
9529
  return Array.isArray(logs) ? logs.filter((line) => typeof line === "string") : [];
9243
9530
  });
9244
9531
  }
9245
- var GOOGLE_BROKER_UNAVAILABLE, GOOGLE_BROKER_ASKED, GOOGLE_SIGNED_IN, TIMEOUT_ERROR;
9532
+ function googleSignInRefusedReason(lines) {
9533
+ let reason = null;
9534
+ for (const raw of lines ?? []) {
9535
+ for (const line of String(raw).split("\n")) {
9536
+ const text = line.trim();
9537
+ if (GOOGLE_SIGNED_IN.test(text)) {
9538
+ reason = null;
9539
+ continue;
9540
+ }
9541
+ const m = GOOGLE_SIGN_IN_ANSWER.exec(text);
9542
+ if (m)
9543
+ reason = m[1];
9544
+ }
9545
+ }
9546
+ return reason;
9547
+ }
9548
+ var GOOGLE_BROKER_UNAVAILABLE, GOOGLE_BROKER_ASKED, GOOGLE_SIGNED_IN, TIMEOUT_ERROR, GOOGLE_SIGN_IN_REFUSED, GOOGLE_SIGN_IN_ANSWER;
9246
9549
  var init_google_broker_lines = __esm({
9247
9550
  "../shared/dist/google-broker-lines.js"() {
9248
9551
  "use strict";
@@ -9250,6 +9553,8 @@ var init_google_broker_lines = __esm({
9250
9553
  GOOGLE_BROKER_ASKED = /^\[google-sign-in\] the app started Google sign-in; asking the broker/;
9251
9554
  GOOGLE_SIGNED_IN = /^\[google-sign-in\] returned to the app with Google sign-in/;
9252
9555
  TIMEOUT_ERROR = /Test timeout of \d+ms exceeded|TimeoutError|Timeout:? \d+ ?ms/i;
9556
+ 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): /;
9557
+ GOOGLE_SIGN_IN_ANSWER = /^\[google-sign-in\] ([a-z][a-z0-9_]*): /;
9253
9558
  }
9254
9559
  });
9255
9560
 
@@ -9301,22 +9606,87 @@ function isIntentionalNegativeAuthTest(input) {
9301
9606
  const meta = [input.testName, input.description].filter(Boolean).join(" ");
9302
9607
  return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
9303
9608
  }
9609
+ function signInStepPassedBeforeFailure(steps) {
9610
+ const list = steps ?? [];
9611
+ const failedAt = list.findIndex((step) => typeof step.status === "string" && step.status !== "passed" && step.status !== "skipped");
9612
+ if (failedAt <= 0)
9613
+ return false;
9614
+ if (SIGN_IN_STEP_TITLE.test(list[failedAt].title ?? ""))
9615
+ return false;
9616
+ return list.slice(0, failedAt).some((step) => step.status === "passed" && SIGN_IN_STEP_TITLE.test(step.title ?? ""));
9617
+ }
9618
+ function loginFormAlertTexts(body) {
9619
+ const text = body.length > ERROR_CONTEXT_CAP ? body.slice(0, ERROR_CONTEXT_CAP) : body;
9620
+ const blocks = [];
9621
+ let open = null;
9622
+ for (const line of text.split("\n")) {
9623
+ const fence = /^```(\w*)$/.exec(line.trim());
9624
+ if (fence) {
9625
+ if (open) {
9626
+ blocks.push(open);
9627
+ open = null;
9628
+ } else if (fence[1].toLowerCase() === "yaml")
9629
+ open = [];
9630
+ continue;
9631
+ }
9632
+ if (open)
9633
+ open.push(line);
9634
+ }
9635
+ const alerts = [];
9636
+ for (const lines of blocks) {
9637
+ if (!lines.some((l) => SNAPSHOT_PASSWORD_FIELD.test(l)))
9638
+ continue;
9639
+ for (let i = 0; i < lines.length && alerts.length < MAX_ALERTS; i++) {
9640
+ const m = SNAPSHOT_ALERT.exec(lines[i]);
9641
+ if (!m)
9642
+ continue;
9643
+ const indent = m[1].length;
9644
+ const parts = [m[2]];
9645
+ let j = i + 1;
9646
+ for (; j < lines.length; j++) {
9647
+ const lead = /^\s*/.exec(lines[j])?.[0].length ?? 0;
9648
+ if (lines[j].trim() === "" || lead <= indent)
9649
+ break;
9650
+ parts.push(lines[j].trim());
9651
+ }
9652
+ alerts.push(parts.join(" ").slice(0, MAX_ALERT_CHARS));
9653
+ i = j - 1;
9654
+ }
9655
+ }
9656
+ return alerts;
9657
+ }
9658
+ function stepLoginFormAlerts(steps) {
9659
+ const out = [];
9660
+ for (const sr of steps ?? []) {
9661
+ for (const a of sr.attachments ?? []) {
9662
+ if (a?.name === "error-context" && typeof a.body === "string")
9663
+ out.push(...loginFormAlertTexts(a.body));
9664
+ }
9665
+ }
9666
+ return out;
9667
+ }
9304
9668
  function detectAuthBlock(input) {
9305
9669
  const tags = input.tags ?? [];
9306
9670
  const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
9307
- if (!isAuthed)
9671
+ const signsInInline2 = !isAuthed && codeReadsStoredCredentials(input.code);
9672
+ if (!isAuthed && !signsInInline2)
9308
9673
  return NO_DETECTION;
9309
9674
  if (isIntentionalNegativeAuthTest(input))
9310
9675
  return NO_DETECTION;
9676
+ if (signInStepPassedBeforeFailure(input.stepResults))
9677
+ return NO_DETECTION;
9311
9678
  const sources = [];
9312
- if (input.error)
9313
- sources.push(stripEchoLines(input.error));
9314
- if (input.logs)
9315
- sources.push(stripEchoLines(input.logs));
9316
- for (const sr of input.stepResults ?? []) {
9317
- if (sr.error)
9318
- sources.push(stripEchoLines(sr.error));
9679
+ if (isAuthed) {
9680
+ if (input.error)
9681
+ sources.push(stripEchoLines(input.error));
9682
+ if (input.logs)
9683
+ sources.push(stripEchoLines(input.logs));
9684
+ for (const sr of input.stepResults ?? []) {
9685
+ if (sr.error)
9686
+ sources.push(stripEchoLines(sr.error));
9687
+ }
9319
9688
  }
9689
+ sources.push(...stepLoginFormAlerts(input.stepResults));
9320
9690
  for (const text of sources) {
9321
9691
  if (!text)
9322
9692
  continue;
@@ -9365,10 +9735,11 @@ function detectApiColdSessionBlock(input) {
9365
9735
  function coldSessionConfigMessage(context, reason, evidence) {
9366
9736
  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("; ")}`;
9367
9737
  }
9368
- var NO_DETECTION, AUTHED_TAGS, LOGIN_CTX, AUTH_BLOCK_SIGNATURES, INTENTIONAL_NEGATIVE_AUTH, ECHO_LINE_RE, UNAUTHORIZED_TEXT_RE;
9738
+ 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;
9369
9739
  var init_auth_block_detector = __esm({
9370
9740
  "../shared/dist/auth-block-detector.js"() {
9371
9741
  "use strict";
9742
+ init_auth_plan();
9372
9743
  NO_DETECTION = { detected: false, reason: "", evidence: [] };
9373
9744
  AUTHED_TAGS = ["@needs-auth", "@inline-login"];
9374
9745
  LOGIN_CTX = "\\b(?:log\\s*in|login|logging\\s+in|sign[\\s-]?in|signing\\s+in|continue)\\b";
@@ -9378,6 +9749,10 @@ var init_auth_block_detector = __esm({
9378
9749
  { reason: "invalid credentials", re: /\b(invalid|incorrect|wrong|bad)\s+(login\s+)?credentials\b/i },
9379
9750
  { reason: "credentials do not match", re: /\bcredentials\b[^\n]{0,40}\b(do(?:es)?\s*n['’]?t|do\s+not)\s+match\b/i },
9380
9751
  { reason: "incorrect email or password", re: /\b(incorrect|invalid|wrong)\s+(email|username)\s+or\s+password\b/i },
9752
+ { 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 },
9753
+ // A locked account is a blocker of the same kind: the configured user can't sign in until it is unlocked. The
9754
+ // rate-limit detector keeps the timed lockouts ("temporarily locked", "too many attempts").
9755
+ { reason: "account locked out", re: /\b(?:user|account)\b[^\n]{0,30}\b(?:has\s+been|is)\s+locked\s+out\b/i },
9381
9756
  // Require an EXPLICIT negative (codex #21): the old `(not\s+)?(confirmed|verified)` made the negation
9382
9757
  // optional, so a benign "Email verified" success message / feature assertion matched and hid a real
9383
9758
  // feature failure as a CONFIG_ISSUE. Only "email not (yet) confirmed/verified" or "unconfirmed/unverified".
@@ -9409,6 +9784,12 @@ var init_auth_block_detector = __esm({
9409
9784
  /\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
9410
9785
  ];
9411
9786
  ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
9787
+ SIGN_IN_STEP_TITLE = /\b(?:log(?:ging)?\s*in(?:to)?|sign(?:ing)?[\s-]?in(?:to)?|authenticat(?:e|es|ed|ing|ion))\b/i;
9788
+ ERROR_CONTEXT_CAP = 64 * 1024;
9789
+ MAX_ALERTS = 20;
9790
+ MAX_ALERT_CHARS = 1e3;
9791
+ SNAPSHOT_PASSWORD_FIELD = /^\s*- textbox "[^"\n]{0,80}\bpass(?:word|code)?\b/i;
9792
+ SNAPSHOT_ALERT = /^(\s*)- alert\b(.*)$/;
9412
9793
  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;
9413
9794
  }
9414
9795
  });
@@ -9843,6 +10224,77 @@ var init_redact_needles = __esm({
9843
10224
  }
9844
10225
  });
9845
10226
 
10227
+ // ../shared/dist/credential-header-scrub.js
10228
+ function scrubCredentialHeaders(text) {
10229
+ if (text.length < 6)
10230
+ return text;
10231
+ let out = text.replace(HEADER_LINE, (match, keyQuote, name, sep, value) => {
10232
+ if (value === "" || ALREADY_REDACTED.test(value))
10233
+ return match;
10234
+ if (COOKIE_NAME.test(name) && !value.includes("="))
10235
+ return match;
10236
+ if (AUTHORIZATION_NAME.test(name) && WORDS_ONLY.test(value))
10237
+ return match;
10238
+ const q = value[0] === '"' || value[0] === "'" ? value[0] : "";
10239
+ return `${keyQuote}${name}${keyQuote}${sep}${q}${REDACTED2}${q}`;
10240
+ });
10241
+ out = out.replace(SET_COOKIE_PAIR, `$1${REDACTED2}`);
10242
+ return out;
10243
+ }
10244
+ function isCredentialHeaderName(name) {
10245
+ return name.length <= 100 && HEADER_NAME_ONLY.test(name);
10246
+ }
10247
+ function scrubCredentialHeaderValue(value) {
10248
+ if (typeof value === "string")
10249
+ return value === REDACTED2 || value === "" ? value : REDACTED2;
10250
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
10251
+ const next = value.map((item) => item === REDACTED2 || item === "" ? item : REDACTED2);
10252
+ return next.every((item, i) => item === value[i]) ? value : next;
10253
+ }
10254
+ return value;
10255
+ }
10256
+ function scrubCredentialHeadersDeep(value) {
10257
+ if (typeof value === "string")
10258
+ return scrubCredentialHeaders(value);
10259
+ if (Array.isArray(value)) {
10260
+ let changed = false;
10261
+ const next = value.map((item) => {
10262
+ const scrubbed = scrubCredentialHeadersDeep(item);
10263
+ if (scrubbed !== item)
10264
+ changed = true;
10265
+ return scrubbed;
10266
+ });
10267
+ return changed ? next : value;
10268
+ }
10269
+ if (value !== null && typeof value === "object") {
10270
+ let changed = false;
10271
+ const next = {};
10272
+ for (const [k, v] of Object.entries(value)) {
10273
+ const scrubbed = isCredentialHeaderName(k) ? scrubCredentialHeaderValue(v) : scrubCredentialHeadersDeep(v);
10274
+ if (scrubbed !== v)
10275
+ changed = true;
10276
+ next[k] = scrubbed;
10277
+ }
10278
+ return changed ? next : value;
10279
+ }
10280
+ return value;
10281
+ }
10282
+ var REDACTED2, HEADER_NAMES, COOKIE_NAME, AUTHORIZATION_NAME, WORDS_ONLY, HEADER_NAME_ONLY, HEADER_LINE, ALREADY_REDACTED, SET_COOKIE_PAIR;
10283
+ var init_credential_header_scrub = __esm({
10284
+ "../shared/dist/credential-header-scrub.js"() {
10285
+ "use strict";
10286
+ REDACTED2 = "[REDACTED]";
10287
+ 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}";
10288
+ COOKIE_NAME = /^(?:set-)?cookie2?$/i;
10289
+ AUTHORIZATION_NAME = /^(?:proxy-)?authorization$/i;
10290
+ WORDS_ONLY = /^[A-Za-z]+(?: [A-Za-z]+){0,3}$/;
10291
+ HEADER_NAME_ONLY = new RegExp(`^(?:${HEADER_NAMES})$`, "i");
10292
+ HEADER_LINE = new RegExp(`(?<![A-Za-z0-9_-])(["']?)(${HEADER_NAMES})\\1(\\s*:\\s*)("(?:[^"\\\\\\r\\n]|\\\\.)*"|'(?:[^'\\\\\\r\\n]|\\\\.)*'|[^\\r\\n\\x1b]*)`, "gi");
10293
+ ALREADY_REDACTED = /^["']?(?:[A-Za-z][\w-]*\s+)?\[REDACTED\]["']?[,;]?\s*$/;
10294
+ 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;
10295
+ }
10296
+ });
10297
+
9846
10298
  // ../shared/dist/deepgram-voices.js
9847
10299
  function voice(family, name, gender, accent, description) {
9848
10300
  const label = name.charAt(0).toUpperCase() + name.slice(1);
@@ -10419,13 +10871,13 @@ function isSensitiveQueryKey(key) {
10419
10871
  return SENSITIVE_QUERY_KEY_RE.test(key);
10420
10872
  }
10421
10873
  function redactSecretValues(value) {
10422
- return value.replace(MCP_SECRET_VALUE_RE, REDACTED2);
10874
+ return value.replace(MCP_SECRET_VALUE_RE, REDACTED3);
10423
10875
  }
10424
10876
  function scrubReturnUrlValue(value, depth) {
10425
10877
  if (!/[?%]/.test(value))
10426
10878
  return value;
10427
10879
  if (depth >= MAX_NESTED_URL_DEPTH)
10428
- return REDACTED2;
10880
+ return REDACTED3;
10429
10881
  const q = value.indexOf("?");
10430
10882
  if (q !== -1) {
10431
10883
  const hashIdx = value.indexOf("#", q);
@@ -10447,7 +10899,7 @@ function scrubReturnUrlValue(value, depth) {
10447
10899
  try {
10448
10900
  return encodeURIComponent(scrubbed);
10449
10901
  } catch {
10450
- return REDACTED2;
10902
+ return REDACTED3;
10451
10903
  }
10452
10904
  }
10453
10905
  function scrubQuery(search, depth) {
@@ -10463,7 +10915,7 @@ function scrubQuery(search, depth) {
10463
10915
  params.forEach((value, key) => {
10464
10916
  let scrubbed = value;
10465
10917
  if (isSensitiveQueryKey(key)) {
10466
- scrubbed = REDACTED2;
10918
+ scrubbed = REDACTED3;
10467
10919
  } else {
10468
10920
  if (RETURN_URL_QUERY_KEY_RE.test(key))
10469
10921
  scrubbed = scrubReturnUrlValue(scrubbed, depth);
@@ -10575,12 +11027,12 @@ function scrubUrlPropertyBag(props) {
10575
11027
  } catch {
10576
11028
  }
10577
11029
  }
10578
- var SENSITIVE_QUERY_KEY_RE, REDACTED2, MCP_SECRET_VALUE_RE, RETURN_URL_QUERY_KEY_RE, MAX_NESTED_URL_DEPTH, MAX_PROPERTY_SWEEP_DEPTH;
11030
+ var SENSITIVE_QUERY_KEY_RE, REDACTED3, MCP_SECRET_VALUE_RE, RETURN_URL_QUERY_KEY_RE, MAX_NESTED_URL_DEPTH, MAX_PROPERTY_SWEEP_DEPTH;
10579
11031
  var init_analytics_url = __esm({
10580
11032
  "../shared/dist/analytics-url.js"() {
10581
11033
  "use strict";
10582
11034
  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;
10583
- REDACTED2 = "[redacted]";
11035
+ REDACTED3 = "[redacted]";
10584
11036
  MCP_SECRET_VALUE_RE = /mcp_(consent|code|at|rt)_[A-Za-z0-9]+/g;
10585
11037
  RETURN_URL_QUERY_KEY_RE = /^(next|redirect|return|continue)([_-]?(to|url|uri|path))?$/i;
10586
11038
  MAX_NESTED_URL_DEPTH = 5;
@@ -13389,8 +13841,19 @@ function successContradicted(text) {
13389
13841
  }
13390
13842
  return SUCCESS_NO_STATUS_RE.test(text) || OFF_BY_RE.test(text);
13391
13843
  }
13844
+ function negatedSafeguard(text) {
13845
+ for (const match of matches(NEGATED_SAFEGUARD_RE, text)) {
13846
+ if (!NOT_KEPT_RE.test(match[1]))
13847
+ return true;
13848
+ const head = text.slice(Math.max(0, match.index - SAFEGUARD_LOOKBEHIND), match.index);
13849
+ const sentence = head.slice(Math.max(head.lastIndexOf("."), head.lastIndexOf(";"), head.lastIndexOf("\n")) + 1);
13850
+ if (!REFUSED_INPUT_RE.test(sentence))
13851
+ return true;
13852
+ }
13853
+ return false;
13854
+ }
13392
13855
  function claimsDefect(text) {
13393
- if (BUG_LABEL_RE.test(text) || SILENT_RE.test(text) || CONCESSION_RE.test(text) || successContradicted(text) || NEGATED_SAFEGUARD_RE.test(text)) {
13856
+ if (BUG_LABEL_RE.test(text) || SILENT_RE.test(text) || CONCESSION_RE.test(text) || successContradicted(text) || negatedSafeguard(text)) {
13394
13857
  return true;
13395
13858
  }
13396
13859
  return BAD_VALUE_RE.test(text) && !REFUSAL_RE.test(text);
@@ -13398,7 +13861,7 @@ function claimsDefect(text) {
13398
13861
  function hasTestFailContract(code) {
13399
13862
  return TEST_FAIL_CONTRACT_RE.test(code);
13400
13863
  }
13401
- 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;
13864
+ 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;
13402
13865
  var init_claims_defect = __esm({
13403
13866
  "../shared/dist/claims-defect.js"() {
13404
13867
  "use strict";
@@ -13442,7 +13905,10 @@ var init_claims_defect = __esm({
13442
13905
  NOT_A_CLEARING_RE = /\b(?:cancel|refresh|reload|preview|open|view|fail|hover|scroll|sort|filter|search)\w*/i;
13443
13906
  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;
13444
13907
  OFF_BY_RE = /\boff[- ]by[- ](?:one|two|\d{1,3})\b/i;
13445
- NEGATED_SAFEGUARD_RE = /\bnot (?:rejected|validated|enforced|applied|persisted|saved)\b/i;
13908
+ NEGATED_SAFEGUARD_RE = /\bnot (rejected|validated|enforced|applied|persisted|saved)\b/gi;
13909
+ NOT_KEPT_RE = /^(?:applied|persisted|saved)$/i;
13910
+ 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;
13911
+ SAFEGUARD_LOOKBEHIND = 80;
13446
13912
  BAD_VALUE_RE = /\b(?:incorrect|wrong|stale|negative|epoch|unix) (?:total|amount|value|date|count|price|label|plan|balance)\b/i;
13447
13913
  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;
13448
13914
  TEST_FAIL_CONTRACT_RE = /\btest\.fail\s{0,20}\(/;
@@ -14301,7 +14767,7 @@ function findExpectCalls(code) {
14301
14767
  return out;
14302
14768
  }
14303
14769
  function countExpectCalls(code) {
14304
- return findExpectCalls(stripComments2(code)).length;
14770
+ return findExpectCalls(stripComments2(withExpectAliasesAsExpect(code))).length;
14305
14771
  }
14306
14772
  function countActionCalls(code) {
14307
14773
  return [...stripComments2(code).matchAll(ACTION_CALL)].length;
@@ -16630,6 +17096,7 @@ var init_test_authoring_quality = __esm({
16630
17096
  "../shared/dist/test-authoring-quality.js"() {
16631
17097
  "use strict";
16632
17098
  init_claims_defect();
17099
+ init_expect_call();
16633
17100
  init_readiness_poll();
16634
17101
  init_persistence_reread();
16635
17102
  init_write_response_check();
@@ -16756,7 +17223,7 @@ var init_test_authoring_quality = __esm({
16756
17223
  MAX_BALANCED_SCAN = 2048;
16757
17224
  MAX_CALLS = 500;
16758
17225
  MAX_SEEN_SCAN = 200;
16759
- ACTION_CALL = /\.(?:click|dblclick|fill|press|pressSequentially|type|check|uncheck|selectOption|setInputFiles|hover|tap|dragTo|goto)\s*\(/g;
17226
+ 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;
16760
17227
  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\(/;
16761
17228
  ACTION_AFTER = /^\s{0,20}\.\s{0,20}(?:click|fill|check|uncheck|press|type|hover|focus|selectOption|setInputFiles|dblclick|tap)\s{0,20}\(/;
16762
17229
  MAX_CONTACTS = 200;
@@ -18265,7 +18732,7 @@ function credentialEntries(maps) {
18265
18732
  }
18266
18733
  function replacePattern(value, form) {
18267
18734
  const escaped = form.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18268
- return new RegExp(value.length < WHOLE_WORD_BELOW ? `(?<![A-Za-z0-9_])${escaped}(?![A-Za-z0-9_])` : escaped, "gi");
18735
+ return value.length < WHOLE_WORD_BELOW ? `(?<![A-Za-z0-9_])${escaped}(?![A-Za-z0-9_])` : escaped;
18269
18736
  }
18270
18737
  function findCredentialTextLeftover(text, maps) {
18271
18738
  const bare = text.split(WITHHELD_CREDENTIAL_TEXT).join(" ").replace(REFERENCE, " ").toLowerCase();
@@ -18279,11 +18746,17 @@ function scrubCredentialText(text, maps) {
18279
18746
  const entries = credentialEntries(maps);
18280
18747
  if (entries.length === 0)
18281
18748
  return text;
18282
- let out = text;
18749
+ const alternatives = [];
18283
18750
  for (const [key, value] of entries) {
18284
18751
  for (const form of credentialValueForms(value))
18285
- out = out.replace(replacePattern(value, form), `{{secret:${key}}}`);
18752
+ alternatives.push({ pattern: replacePattern(value, form), key, length: form.length });
18286
18753
  }
18754
+ alternatives.sort((a, b) => b.length - a.length);
18755
+ const matcher = new RegExp([REFERENCE.source, ...alternatives.map((alt) => `(${alt.pattern})`)].join("|"), "gi");
18756
+ const out = text.replace(matcher, (...args) => {
18757
+ const index = args.slice(1, 1 + alternatives.length).findIndex((group) => group !== void 0);
18758
+ return index < 0 ? String(args[0]) : `{{secret:${alternatives[index].key}}}`;
18759
+ });
18287
18760
  return findCredentialTextLeftover(out, maps) ? WITHHELD_CREDENTIAL_TEXT : out;
18288
18761
  }
18289
18762
  function scrubCredentialStringsIn(value, maps) {
@@ -18777,10 +19250,24 @@ function onlyUrlAfter(code, writeAt) {
18777
19250
  i = at + 6;
18778
19251
  }
18779
19252
  }
19253
+ function readsOrderAfter(code, writeAt) {
19254
+ const rest = code.slice(writeAt + 1);
19255
+ if (ORDER_READ_MARKERS.some((m) => rest.includes(m)) && nextExpect(code, writeAt + 1) >= 0)
19256
+ return true;
19257
+ for (let i = writeAt + 1; ; ) {
19258
+ const at = nextExpect(code, i);
19259
+ if (at < 0)
19260
+ return false;
19261
+ if (ORDERED_ARRAY_EXPECT.test(expectStatement(code, at)))
19262
+ return true;
19263
+ i = at + 6;
19264
+ }
19265
+ }
18780
19266
  function assessTitlePromiseShape(claimText, code) {
18781
19267
  const persistence = PERSISTENCE_RE.test(claimText);
18782
19268
  const refusal = REFUSAL_RE2.test(claimText);
18783
- if (!persistence && !refusal)
19269
+ const order = ORDER_RE.test(claimText) || ORDER_RE_CASED.test(claimText);
19270
+ if (!persistence && !refusal && !order)
18784
19271
  return [];
18785
19272
  const blank = blankCommentsAndStringLiterals(code);
18786
19273
  const writeAt = proofAnchor(blank);
@@ -18793,9 +19280,12 @@ function assessTitlePromiseShape(claimText, code) {
18793
19280
  if (refusal && onlyUrlAfter(blank, writeAt)) {
18794
19281
  findings.push({ rule: "rejection-only-url", reason: REFUSAL_REASON });
18795
19282
  }
19283
+ if (order && !readsOrderAfter(blank, writeAt)) {
19284
+ findings.push({ rule: "sort-without-order", reason: ORDER_REASON });
19285
+ }
18796
19286
  return findings;
18797
19287
  }
18798
- var PERSIST_REASON, REFUSAL_REASON, PERSISTENCE_RE, REFUSAL_RE2, WRITE_MARKERS, VALUE_WRITE_MARKERS, REREAD_MARKERS;
19288
+ 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;
18799
19289
  var init_title_promise_shape = __esm({
18800
19290
  "../shared/dist/title-promise-shape.js"() {
18801
19291
  "use strict";
@@ -18804,6 +19294,21 @@ var init_title_promise_shape = __esm({
18804
19294
  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.";
18805
19295
  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;
18806
19296
  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;
19297
+ 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).";
19298
+ 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;
19299
+ ORDER_RE_CASED = /\b[AZ]\s{0,3}(?:to|-|→)\s{0,3}[AZ]\b/;
19300
+ ORDER_READ_MARKERS = [
19301
+ ".allTextContents(",
19302
+ ".allInnerTexts(",
19303
+ ".evaluateAll(",
19304
+ ".toMatchAriaSnapshot(",
19305
+ ".sort(",
19306
+ ".toSorted(",
19307
+ ".first()",
19308
+ ".last()",
19309
+ ".nth("
19310
+ ];
19311
+ ORDERED_ARRAY_EXPECT = /\.(?:toHaveText|toContainText|toEqual|toStrictEqual)\s*\(\s*\[/;
18807
19312
  WRITE_MARKERS = [
18808
19313
  ".fill(",
18809
19314
  ".click(",
@@ -19135,7 +19640,7 @@ function assertionsReadVisibleContent(code) {
19135
19640
  });
19136
19641
  }
19137
19642
  function isLegacyReviewFlag(tags, activity) {
19138
- const flagged = tags.includes(NEEDS_REVIEW_TAG) || activity?.needsReview === true;
19643
+ const flagged = tags.includes(NEEDS_REVIEW_TAG2) || activity?.needsReview === true;
19139
19644
  const source = activity?.needsReviewSource;
19140
19645
  return flagged && !(typeof source === "string" && source in SOURCE_RANK);
19141
19646
  }
@@ -19197,7 +19702,7 @@ function recheckGeneratorNotes(notes, input) {
19197
19702
  }
19198
19703
  function reviewHolds(tags, activity, cleared = /* @__PURE__ */ new Set()) {
19199
19704
  const out = [];
19200
- const tagged = tags.includes(NEEDS_REVIEW_TAG);
19705
+ const tagged = tags.includes(NEEDS_REVIEW_TAG2);
19201
19706
  const generatorAsked = activity?.needsReview === true;
19202
19707
  if (tagged) {
19203
19708
  if (activity?.needsReviewSource === "human")
@@ -19257,7 +19762,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
19257
19762
  ...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
19258
19763
  };
19259
19764
  }
19260
- 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;
19765
+ 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;
19261
19766
  var init_promotion_quality_gate = __esm({
19262
19767
  "../shared/dist/promotion-quality-gate.js"() {
19263
19768
  "use strict";
@@ -19276,8 +19781,8 @@ var init_promotion_quality_gate = __esm({
19276
19781
  SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
19277
19782
  strengthCheck = {
19278
19783
  id: "assertion-strength",
19279
- run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
19280
- const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
19784
+ run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof, absenceProof }) {
19785
+ const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof, absenceProof });
19281
19786
  if (verdict.severity === "route-bug")
19282
19787
  return [{ check: "route-bug" }];
19283
19788
  const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
@@ -19535,7 +20040,7 @@ ${goalText}`);
19535
20040
  NOT_CONTENT_MATCHER = /\.(?:toHaveURL|toHaveTitle|toBeOK)\s*\(|\bwaitForURL\s*\(|\.(?:status|ok)\s*\(\s*\)/;
19536
20041
  CONTENT_PROOF = /* @__PURE__ */ new Set(["controlled", "revealed", "value"]);
19537
20042
  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/;
19538
- NEEDS_REVIEW_TAG = "@needs-review";
20043
+ NEEDS_REVIEW_TAG2 = "@needs-review";
19539
20044
  count2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
19540
20045
  RECHECKED_NOTES = [
19541
20046
  {
@@ -19656,11 +20161,18 @@ var init_regex_alternation = __esm({
19656
20161
  function isPageEvidenceEntry(entry) {
19657
20162
  return typeof entry === "object" && entry !== null && entry.outcomeType === PAGE_LOADING_OUTCOME_TYPE;
19658
20163
  }
19659
- var PAGE_LOADING_OUTCOME_TYPE;
20164
+ function pageScreenshotKey(projectId, safeName, stillLoading = false) {
20165
+ return `discovery/${projectId}/screenshots/${stillLoading ? `${STILL_LOADING_SCREENSHOT_DIR}/` : ""}${safeName}.png`;
20166
+ }
20167
+ function isStillLoadingScreenshotKey(key) {
20168
+ return typeof key === "string" && key.includes(`/screenshots/${STILL_LOADING_SCREENSHOT_DIR}/`);
20169
+ }
20170
+ var PAGE_LOADING_OUTCOME_TYPE, STILL_LOADING_SCREENSHOT_DIR;
19660
20171
  var init_page_evidence = __esm({
19661
20172
  "../shared/dist/page-evidence.js"() {
19662
20173
  "use strict";
19663
20174
  PAGE_LOADING_OUTCOME_TYPE = "LOADING";
20175
+ STILL_LOADING_SCREENSHOT_DIR = "still-loading";
19664
20176
  }
19665
20177
  });
19666
20178
 
@@ -20065,11 +20577,13 @@ __export(dist_exports, {
20065
20577
  SERVER_INTERRUPT_ERROR_PREFIX: () => SERVER_INTERRUPT_ERROR_PREFIX,
20066
20578
  SHARED_PRICING_PLAN_MAP: () => SHARED_PRICING_PLAN_MAP,
20067
20579
  SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
20580
+ SMS_NOT_RECEIVED_PREFIXES: () => SMS_NOT_RECEIVED_PREFIXES,
20068
20581
  SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
20069
20582
  SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
20070
20583
  STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
20071
20584
  STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
20072
20585
  STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
20586
+ STILL_LOADING_SCREENSHOT_DIR: () => STILL_LOADING_SCREENSHOT_DIR,
20073
20587
  STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
20074
20588
  SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
20075
20589
  SUPPORT_EMAIL: () => SUPPORT_EMAIL,
@@ -20097,6 +20611,7 @@ __export(dist_exports, {
20097
20611
  TTS_VOICE_OPTIONS: () => TTS_VOICE_OPTIONS,
20098
20612
  UI_VARIANTS: () => UI_VARIANTS,
20099
20613
  UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
20614
+ UNVERIFIED_HEAL_CONFIDENCES: () => UNVERIFIED_HEAL_CONFIDENCES,
20100
20615
  USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
20101
20616
  VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
20102
20617
  VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
@@ -20150,6 +20665,7 @@ __export(dist_exports, {
20150
20665
  blankBlockComments: () => blankBlockComments3,
20151
20666
  blankCommentsAndStringLiterals: () => blankCommentsAndStringLiterals,
20152
20667
  blankKeepingLines: () => blankKeepingLines,
20668
+ blockedRunLogLine: () => blockedRunLogLine,
20153
20669
  boundedCallCostUsd: () => boundedCallCostUsd,
20154
20670
  brokerResponseBody: () => brokerResponseBody,
20155
20671
  bugFoundQuarantineWhere: () => bugFoundQuarantineWhere,
@@ -20255,6 +20771,7 @@ __export(dist_exports, {
20255
20771
  evaluatePromotionGate: () => evaluatePromotionGate,
20256
20772
  excludeCancelledRunsAnd: () => excludeCancelledRunsAnd,
20257
20773
  excludeCancelledRunsWhere: () => excludeCancelledRunsWhere,
20774
+ expectAliasNames: () => expectAliasNames,
20258
20775
  explicitAgenticCliOverrideError: () => explicitAgenticCliOverrideError,
20259
20776
  extractAssertedControlSignatures: () => extractAssertedControlSignatures,
20260
20777
  extractMuseAssistantJson: () => extractMuseAssistantJson,
@@ -20321,6 +20838,8 @@ __export(dist_exports, {
20321
20838
  googleGisRequestUrl: () => googleGisRequestUrl,
20322
20839
  googleSignInLimitLines: () => googleSignInLimitLines,
20323
20840
  googleSignInPolicyProblems: () => googleSignInPolicyProblems,
20841
+ googleSignInRefusedLine: () => googleSignInRefusedLine,
20842
+ googleSignInRefusedReason: () => googleSignInRefusedReason,
20324
20843
  hasCrossPlatformIdentity: () => hasCrossPlatformIdentity,
20325
20844
  hasEncodedRequestBody: () => hasEncodedRequestBody,
20326
20845
  hasEngineSpecParts: () => hasEngineSpecParts,
@@ -20364,6 +20883,7 @@ __export(dist_exports, {
20364
20883
  isCodeFactNote: () => isCodeFactNote,
20365
20884
  isConfigLikeReason: () => isConfigLikeReason,
20366
20885
  isCountedFailure: () => isCountedFailure,
20886
+ isCredentialHeaderName: () => isCredentialHeaderName,
20367
20887
  isCreditPackAmountCents: () => isCreditPackAmountCents,
20368
20888
  isDbPricingPlan: () => isDbPricingPlan,
20369
20889
  isDeniedGoogleBrokerRedirectHost: () => isDeniedGoogleBrokerRedirectHost,
@@ -20419,8 +20939,10 @@ __export(dist_exports, {
20419
20939
  isSharedAuthTenantHost: () => isSharedAuthTenantHost,
20420
20940
  isSharedSuffix: () => isSharedSuffix,
20421
20941
  isSingleEmailAddress: () => isSingleEmailAddress,
20942
+ isSmsNotReceivedError: () => isSmsNotReceivedError,
20422
20943
  isSoftOutcome: () => isSoftOutcome,
20423
20944
  isStateValueMobileClassName: () => isStateValueMobileClassName,
20945
+ isStillLoadingScreenshotKey: () => isStillLoadingScreenshotKey,
20424
20946
  isSttModelForProvider: () => isSttModelForProvider,
20425
20947
  isTestReadyForPlatform: () => isTestReadyForPlatform,
20426
20948
  isTestVariableSecretKey: () => isTestVariableSecretKey,
@@ -20428,6 +20950,7 @@ __export(dist_exports, {
20428
20950
  isUIVariant: () => isUIVariant,
20429
20951
  isUnexpectedPassError: () => isUnexpectedPassError,
20430
20952
  isUnsafePrecondition: () => isUnsafePrecondition,
20953
+ isUnverifiedHealPass: () => isUnverifiedHealPass,
20431
20954
  isUsableDeepgramKey: () => isUsableDeepgramKey,
20432
20955
  isUserCancelledError: () => isUserCancelledError,
20433
20956
  isValidAgenticCliModel: () => isValidAgenticCliModel,
@@ -20452,8 +20975,10 @@ __export(dist_exports, {
20452
20975
  lintTestData: () => lintTestData,
20453
20976
  lintTestQuality: () => lintTestQuality,
20454
20977
  literalUrlRegex: () => literalUrlRegex,
20978
+ loginFormAlertTexts: () => loginFormAlertTexts,
20455
20979
  longCredentialValues: () => longCredentialValues,
20456
20980
  maskedOutsideStrings: () => maskedOutsideStrings,
20981
+ mayBeUnverifiedHealPass: () => mayBeUnverifiedHealPass,
20457
20982
  mediumForMobilePlatform: () => mediumForMobilePlatform,
20458
20983
  mergePageType: () => mergePageType,
20459
20984
  mergeSuspectedBugsAcrossSurveys: () => mergeSuspectedBugsAcrossSurveys,
@@ -20487,7 +21012,9 @@ __export(dist_exports, {
20487
21012
  opensRegexLiteral: () => opensRegexLiteral,
20488
21013
  otherMobilePlatform: () => otherMobilePlatform,
20489
21014
  outcomeNamedByAssertion: () => outcomeNamedByAssertion,
21015
+ pageScreenshotKey: () => pageScreenshotKey,
20490
21016
  parenDepthDelta: () => parenDepthDelta,
21017
+ parseAbsenceProofMode: () => parseAbsenceProofMode,
20491
21018
  parseApiCardModelUpdate: () => parseApiCardModelUpdate,
20492
21019
  parseAppTimezone: () => parseAppTimezone,
20493
21020
  parseAuditGroupScopeId: () => parseAuditGroupScopeId,
@@ -20563,7 +21090,7 @@ __export(dist_exports, {
20563
21090
  redactNeedlesText: () => redactNeedlesText,
20564
21091
  redirectOnAppSite: () => redirectOnAppSite,
20565
21092
  refuseEncodedRequestBodies: () => refuseEncodedRequestBodies,
20566
- regexLiteralEnd: () => regexLiteralEnd2,
21093
+ regexLiteralEnd: () => regexLiteralEnd,
20567
21094
  regexOpensAfter: () => regexOpensAfter,
20568
21095
  registrableDomain: () => registrableDomain,
20569
21096
  renderIdentity: () => renderIdentity,
@@ -20605,6 +21132,9 @@ __export(dist_exports, {
20605
21132
  saveOpensConfirm: () => saveOpensConfirm,
20606
21133
  scanClockDependentLiterals: () => scanClockDependentLiterals,
20607
21134
  scanHtmlTags: () => scanHtmlTags,
21135
+ scrubCredentialHeaderValue: () => scrubCredentialHeaderValue,
21136
+ scrubCredentialHeaders: () => scrubCredentialHeaders,
21137
+ scrubCredentialHeadersDeep: () => scrubCredentialHeadersDeep,
20608
21138
  scrubCredentialStringsIn: () => scrubCredentialStringsIn,
20609
21139
  scrubCredentialText: () => scrubCredentialText,
20610
21140
  scrubSearchParams: () => scrubSearchParams,
@@ -20615,9 +21145,12 @@ __export(dist_exports, {
20615
21145
  selectorForValue: () => selectorForValue,
20616
21146
  setAgenticCliRuntime: () => setAgenticCliRuntime,
20617
21147
  setProviderChainOverrides: () => setProviderChainOverrides,
21148
+ signInStepPassedBeforeFailure: () => signInStepPassedBeforeFailure,
21149
+ signsInInline: () => signsInInline,
20618
21150
  signupPlanFor: () => signupPlanFor,
20619
21151
  simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
20620
21152
  siteOf: () => siteOf,
21153
+ specCallsExpect: () => specCallsExpect,
20621
21154
  specItemsFromTestPlans: () => specItemsFromTestPlans,
20622
21155
  splitAnswerLines: () => splitAnswerLines,
20623
21156
  statedLimitsFromAttrs: () => statedLimitsFromAttrs,
@@ -20665,6 +21198,7 @@ __export(dist_exports, {
20665
21198
  visualProDailyBudget: () => visualProDailyBudget,
20666
21199
  voiceMatchesTtsModel: () => voiceMatchesTtsModel,
20667
21200
  voicesForTtsModel: () => voicesForTtsModel,
21201
+ withExpectAliasesAsExpect: () => withExpectAliasesAsExpect,
20668
21202
  withGeneratorReviewSource: () => withGeneratorReviewSource,
20669
21203
  withReviewSource: () => withReviewSource,
20670
21204
  withoutNotChargedClaim: () => withoutNotChargedClaim,
@@ -20719,6 +21253,7 @@ var init_dist = __esm({
20719
21253
  init_encoded_forms();
20720
21254
  init_block_comments();
20721
21255
  init_redact_needles();
21256
+ init_credential_header_scrub();
20722
21257
  init_voice_config();
20723
21258
  init_deepgram_speech();
20724
21259
  init_deepgram_voices();
@@ -20805,6 +21340,8 @@ export {
20805
21340
  deriveAuthPlan,
20806
21341
  isConfigLikeReason,
20807
21342
  classifyErrorMessage,
21343
+ blockedRunLogLine,
21344
+ registrableDomain,
20808
21345
  isAppOriginResolutionEnabled,
20809
21346
  resolveCrossOriginConfigOverride,
20810
21347
  isSharedAuthInjectEnabled,