@validate.qa/runner 1.0.100 → 1.0.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/{chunk-MBKGTELT.mjs → chunk-3JM6XTSZ.mjs} +1862 -530
- package/dist/{chunk-2XD63NU5.mjs → chunk-4WLP3RWN.mjs} +941 -86
- package/dist/{chunk-MHFP3QXU.mjs → chunk-5DFBEL72.mjs} +1 -1
- package/dist/{chunk-II7QW7MZ.mjs → chunk-BXLEPT4M.mjs} +31 -18
- package/dist/{chunk-MAO6ADAO.mjs → chunk-CBVHPYVJ.mjs} +62 -2
- package/dist/{chunk-5P4ANVDY.mjs → chunk-DN3O6EYT.mjs} +43073 -23357
- package/dist/{chunk-GIIIVO2W.mjs → chunk-E2RRAE27.mjs} +155 -11
- package/dist/chunk-E2VEMBQQ.mjs +291 -0
- package/dist/{chunk-HMUC3DKS.mjs → chunk-HJ6FKHMM.mjs} +2281 -2
- package/dist/{chunk-WDXLAZ2J.mjs → chunk-PWFWMVZU.mjs} +3 -2
- package/dist/{chunk-6NIT2CCH.mjs → chunk-ZZYBRIBT.mjs} +18 -12
- package/dist/cli.js +139723 -114754
- package/dist/cli.mjs +536 -247
- package/dist/credential-mcp-server.js +1761 -358
- package/dist/credential-mcp-server.mjs +3 -3
- package/dist/discovery-mcp-server.js +1874 -379
- package/dist/discovery-mcp-server.mjs +4 -4
- package/dist/dist-52M5RDZ6.mjs +17 -0
- package/dist/inbox-mcp-server.js +1902 -505
- package/dist/inbox-mcp-server.mjs +3 -3
- package/dist/route-seed-worker.js +3976 -356
- package/dist/route-seed-worker.mjs +14 -7
- package/dist/run-write-guard-preload.js +2908 -572
- package/dist/run-write-guard-preload.mjs +5 -285
- package/dist/run-write-guard.js +2908 -572
- package/dist/run-write-guard.mjs +3 -3
- package/dist/spec-driver.js +1 -1
- package/dist/spec-driver.mjs +1 -1
- package/package.json +4 -3
- package/dist/dist-DFNLMH6L.mjs +0 -16
|
@@ -91,13 +91,13 @@ function isBlockedEnvVar(key) {
|
|
|
91
91
|
function sanitizeTestVariables(vars) {
|
|
92
92
|
if (!vars)
|
|
93
93
|
return {};
|
|
94
|
-
const
|
|
94
|
+
const clean3 = {};
|
|
95
95
|
for (const [key, value] of Object.entries(vars)) {
|
|
96
96
|
if (!isBlockedEnvVar(key)) {
|
|
97
|
-
|
|
97
|
+
clean3[key] = value;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
return
|
|
100
|
+
return clean3;
|
|
101
101
|
}
|
|
102
102
|
function buildSandboxedEnv(processEnv, testVariables) {
|
|
103
103
|
const env = {};
|
|
@@ -1554,9 +1554,40 @@ function semanticTarget(label, platform, base) {
|
|
|
1554
1554
|
}
|
|
1555
1555
|
};
|
|
1556
1556
|
}
|
|
1557
|
+
function androidResourceOwners(value) {
|
|
1558
|
+
const owners = [];
|
|
1559
|
+
for (const match of value.matchAll(ANDROID_RESOURCE_ID_SUFFIX_RE)) {
|
|
1560
|
+
let runStart = match.index;
|
|
1561
|
+
while (runStart > 0 && /[a-z0-9_.]/i.test(value[runStart - 1]))
|
|
1562
|
+
runStart--;
|
|
1563
|
+
const owner = ownerAtEndOfRun(value.slice(runStart, match.index));
|
|
1564
|
+
if (owner !== null)
|
|
1565
|
+
owners.push(owner);
|
|
1566
|
+
}
|
|
1567
|
+
return owners;
|
|
1568
|
+
}
|
|
1569
|
+
function ownerAtEndOfRun(run) {
|
|
1570
|
+
const segments = run.split(".");
|
|
1571
|
+
const last = segments.length - 1;
|
|
1572
|
+
const startsWithLetter = (segment) => /^[a-z]/i.test(segment);
|
|
1573
|
+
if (last < 1 || !startsWithLetter(segments[last]))
|
|
1574
|
+
return null;
|
|
1575
|
+
let first = last;
|
|
1576
|
+
while (first > 0 && startsWithLetter(segments[first - 1]))
|
|
1577
|
+
first--;
|
|
1578
|
+
let offset = 0;
|
|
1579
|
+
for (let i = 0; i < first - 1; i++)
|
|
1580
|
+
offset += segments[i].length + 1;
|
|
1581
|
+
if (first > 0) {
|
|
1582
|
+
const letterAt = segments[first - 1].search(/[a-z]/i);
|
|
1583
|
+
if (letterAt >= 0)
|
|
1584
|
+
return run.slice(offset + letterAt);
|
|
1585
|
+
offset += segments[first - 1].length + 1;
|
|
1586
|
+
}
|
|
1587
|
+
return first < last ? run.slice(offset) : null;
|
|
1588
|
+
}
|
|
1557
1589
|
function containsForeignAppPackage(value, targetAppId) {
|
|
1558
|
-
for (const
|
|
1559
|
-
const owner = match[1];
|
|
1590
|
+
for (const owner of androidResourceOwners(value)) {
|
|
1560
1591
|
if (targetAppId && owner !== targetAppId && !owner.startsWith(`${targetAppId}.`))
|
|
1561
1592
|
return true;
|
|
1562
1593
|
}
|
|
@@ -1693,7 +1724,7 @@ function healMobileTestSteps(input) {
|
|
|
1693
1724
|
summary: changed ? uniq(summary).slice(0, 12) : []
|
|
1694
1725
|
};
|
|
1695
1726
|
}
|
|
1696
|
-
var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE,
|
|
1727
|
+
var MOBILE_STEP_ACTIONS, FOREIGN_PACKAGE_RE, ANDROID_RESOURCE_ID_SUFFIX_RE, COMMON_EXTERNAL_PREFIXES;
|
|
1697
1728
|
var init_mobile_step_healer = __esm({
|
|
1698
1729
|
"../shared/dist/mobile-step-healer.js"() {
|
|
1699
1730
|
"use strict";
|
|
@@ -1714,7 +1745,7 @@ var init_mobile_step_healer = __esm({
|
|
|
1714
1745
|
"assertStateDelta"
|
|
1715
1746
|
]);
|
|
1716
1747
|
FOREIGN_PACKAGE_RE = /\b[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}\b/gi;
|
|
1717
|
-
|
|
1748
|
+
ANDROID_RESOURCE_ID_SUFFIX_RE = /:id\//gi;
|
|
1718
1749
|
COMMON_EXTERNAL_PREFIXES = [
|
|
1719
1750
|
"com.android.",
|
|
1720
1751
|
"com.google.",
|
|
@@ -3027,7 +3058,7 @@ function unescapeQuotedBody(raw) {
|
|
|
3027
3058
|
return raw.replace(/\\(['"`\\])/g, "$1");
|
|
3028
3059
|
}
|
|
3029
3060
|
function extractRoleNameValue(line) {
|
|
3030
|
-
const m = line.match(new RegExp(`getByRole\\s*\\([^,]
|
|
3061
|
+
const m = line.match(new RegExp(`getByRole\\s*\\([^,]{1,200},\\s*\\{[^}]{0,200}?name\\s*:\\s*(?:${QUOTED_BODY}|\\/([^/]*?)\\/)`, "i"));
|
|
3031
3062
|
if (!m)
|
|
3032
3063
|
return null;
|
|
3033
3064
|
const raw = m[1] ?? m[2] ?? m[3];
|
|
@@ -3424,7 +3455,7 @@ var init_assertion_classifier = __esm({
|
|
|
3424
3455
|
// toHaveCount(0) stays tier 4 (absence proof) via TIER4_PATTERNS.
|
|
3425
3456
|
[/\.toHaveCount\s*\(\s*[1-9]\d*\s*\)/, "non-zero toHaveCount asserts a record count that changes between runs"],
|
|
3426
3457
|
// parenthesised counts like "(3)" or "[12]" in text - but not Playwright method args
|
|
3427
|
-
[
|
|
3458
|
+
[/\((?<!\.\w{3,20}\s*\()\s*\d+\s*\)(?!\s*(?:[,;]\s*)?\))/i, "parenthesised count is session-specific"],
|
|
3428
3459
|
// bare email addresses in assertion content
|
|
3429
3460
|
[/\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b/i, "email address is user-specific"],
|
|
3430
3461
|
// currency/balance amounts (dynamic account data)
|
|
@@ -3670,7 +3701,7 @@ var init_assertion_proof = __esm({
|
|
|
3670
3701
|
PRESENCE_MATCHER = /\.(?:toBeVisible|toBeAttached|toBeEnabled|toBeInViewport|toBeEditable|toBeFocused)\s*\(|\.toHaveCount\s*\(\s*1\s*\)/;
|
|
3671
3702
|
TEXT_MATCHER = /\.(?:toHaveText|toContainText)\s*\(/;
|
|
3672
3703
|
ANY_VALUE_SOURCE = /^(?:\.|\\S|\\w|\\d|\[\^\\s\]|\[0-9\]|\[\\d\]|\[\\w\]|\[\\s\\S\])[+*]?\??$/;
|
|
3673
|
-
ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s
|
|
3704
|
+
ANY_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(\s*\/((?:\\.|[^/\\]){1,300})\/[a-z]*\s*[,)]/;
|
|
3674
3705
|
EMPTY_NOT_VALUE = /\.not\s*\.\s*toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/;
|
|
3675
3706
|
TEXT_MATCHER_LITERAL = /\.(?:toHaveText|toContainText)\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
|
|
3676
3707
|
GET_BY_TEXT_LITERAL = /getByText\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/;
|
|
@@ -3687,7 +3718,7 @@ var init_assertion_proof = __esm({
|
|
|
3687
3718
|
DATA_ROLE = /getByRole\s*\(\s*['"`](?:dialog|alertdialog|alert|cell|gridcell|row|rowheader|columnheader|listitem|article|option|status|note|tooltip|paragraph|term|definition|log|marquee|timer|meter)['"`]/gi;
|
|
3688
3719
|
TEXT_LOCATOR = /\.getByText\s*\(|\.filter\s*\(\s*\{\s*hasText\s*:/g;
|
|
3689
3720
|
CSS_LOCATOR = /\.locator\s*\(\s*(['"`])((?:(?!\1).){1,300})\1/g;
|
|
3690
|
-
SPECIFIC_CSS = /\[[^\]]*=|#[\w-]|has-text|:text/;
|
|
3721
|
+
SPECIFIC_CSS = /\[[^\][=]*=|#[\w-]|has-text|:text/;
|
|
3691
3722
|
}
|
|
3692
3723
|
});
|
|
3693
3724
|
|
|
@@ -3735,6 +3766,8 @@ function stripCoverageComments(code) {
|
|
|
3735
3766
|
let copyFrom = 0;
|
|
3736
3767
|
let i = 0;
|
|
3737
3768
|
let quote = null;
|
|
3769
|
+
let prev = "";
|
|
3770
|
+
let noRegexBefore = 0;
|
|
3738
3771
|
while (i < code.length) {
|
|
3739
3772
|
const ch = code[i];
|
|
3740
3773
|
if (quote) {
|
|
@@ -3742,12 +3775,24 @@ function stripCoverageComments(code) {
|
|
|
3742
3775
|
i += 2;
|
|
3743
3776
|
continue;
|
|
3744
3777
|
}
|
|
3745
|
-
if (ch === quote || ch === "\n" && quote !== "`")
|
|
3778
|
+
if (ch === quote || ch === "\n" && quote !== "`") {
|
|
3746
3779
|
quote = null;
|
|
3780
|
+
prev = ch === "\n" ? prev : ch;
|
|
3781
|
+
}
|
|
3747
3782
|
i++;
|
|
3748
3783
|
continue;
|
|
3749
3784
|
}
|
|
3750
3785
|
const next = code[i + 1];
|
|
3786
|
+
if (ch === "/" && next !== "*" && next !== "/" && i >= noRegexBefore && (prev === "" || REGEX_PRECEDER2.includes(prev))) {
|
|
3787
|
+
const end = regexLiteralEnd3(code, i);
|
|
3788
|
+
if (end > 0) {
|
|
3789
|
+
i = end;
|
|
3790
|
+
prev = "a";
|
|
3791
|
+
continue;
|
|
3792
|
+
}
|
|
3793
|
+
const eol = code.indexOf("\n", i);
|
|
3794
|
+
noRegexBefore = eol === -1 ? code.length : eol;
|
|
3795
|
+
}
|
|
3751
3796
|
if (ch === "/" && next === "*") {
|
|
3752
3797
|
const end = code.indexOf("*/", i + 2);
|
|
3753
3798
|
const stop = end === -1 ? code.length : end + 2;
|
|
@@ -3766,11 +3811,39 @@ function stripCoverageComments(code) {
|
|
|
3766
3811
|
}
|
|
3767
3812
|
if (ch === '"' || ch === "'" || ch === "`")
|
|
3768
3813
|
quote = ch;
|
|
3814
|
+
if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r")
|
|
3815
|
+
prev = ch;
|
|
3769
3816
|
i++;
|
|
3770
3817
|
}
|
|
3771
3818
|
out.push(code.slice(copyFrom));
|
|
3772
3819
|
return out.join("");
|
|
3773
3820
|
}
|
|
3821
|
+
function regexLiteralEnd3(code, start) {
|
|
3822
|
+
let inClass = false;
|
|
3823
|
+
for (let j = start + 1; j < code.length; j++) {
|
|
3824
|
+
const c = code[j];
|
|
3825
|
+
if (c === "\n")
|
|
3826
|
+
return -1;
|
|
3827
|
+
if (c === "\\") {
|
|
3828
|
+
j++;
|
|
3829
|
+
continue;
|
|
3830
|
+
}
|
|
3831
|
+
if (inClass) {
|
|
3832
|
+
if (c === "]")
|
|
3833
|
+
inClass = false;
|
|
3834
|
+
continue;
|
|
3835
|
+
}
|
|
3836
|
+
if (c === "[")
|
|
3837
|
+
inClass = true;
|
|
3838
|
+
else if (c === "/") {
|
|
3839
|
+
let k = j + 1;
|
|
3840
|
+
while (k < code.length && isIdentChar(code[k]))
|
|
3841
|
+
k++;
|
|
3842
|
+
return k;
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
return -1;
|
|
3846
|
+
}
|
|
3774
3847
|
function isIdentChar(ch) {
|
|
3775
3848
|
if (!ch)
|
|
3776
3849
|
return false;
|
|
@@ -4428,7 +4501,7 @@ function assessTitleCoverage(title, code) {
|
|
|
4428
4501
|
const repeatedHit = [...repeated].some((term) => hay.includes(term));
|
|
4429
4502
|
return { covered: hit >= needed || repeatedHit, missingTerms };
|
|
4430
4503
|
}
|
|
4431
|
-
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;
|
|
4504
|
+
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;
|
|
4432
4505
|
var init_expected_outcome_coverage = __esm({
|
|
4433
4506
|
"../shared/dist/expected-outcome-coverage.js"() {
|
|
4434
4507
|
"use strict";
|
|
@@ -4465,6 +4538,7 @@ var init_expected_outcome_coverage = __esm({
|
|
|
4465
4538
|
]);
|
|
4466
4539
|
MAX_ASSERTION_JOIN_CHARS = 8e3;
|
|
4467
4540
|
MAX_COVERAGE_LINE_CHARS = 2e3;
|
|
4541
|
+
REGEX_PRECEDER2 = "(,=:[!&|?{};+-*%<>~^";
|
|
4468
4542
|
BLOCK_SCAFFOLD = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
|
|
4469
4543
|
cachedAssertionInput = null;
|
|
4470
4544
|
cachedAssertionSource = { raw: "", normalized: "", bindings: /* @__PURE__ */ new Map(), bindingRefs: /* @__PURE__ */ new Map() };
|
|
@@ -4608,22 +4682,37 @@ function classifyAuthHandoffIntent(text) {
|
|
|
4608
4682
|
function withoutIdpUrlAsserts(code) {
|
|
4609
4683
|
return code.replace(/\.(?:toHaveURL|waitForURL)\s*\((?:[^)(]|\([^)]*\))*\)/g, (match) => /accountchooser|accounts\.google|appleid\.apple|login\.microsoftonline|\/v\d+\/signin|oauth/i.test(match) ? "" : match);
|
|
4610
4684
|
}
|
|
4685
|
+
function stripLineComment(line) {
|
|
4686
|
+
let from = line.length;
|
|
4687
|
+
while (from > 0) {
|
|
4688
|
+
const c = line.charCodeAt(from - 1);
|
|
4689
|
+
if (c === 10 || c === 13 || c === 8232 || c === 8233)
|
|
4690
|
+
break;
|
|
4691
|
+
from--;
|
|
4692
|
+
}
|
|
4693
|
+
const at = line.indexOf("//", from);
|
|
4694
|
+
return at < 0 ? line : line.slice(0, at);
|
|
4695
|
+
}
|
|
4611
4696
|
function stripComments(code) {
|
|
4612
|
-
return replaceBlockComments(code, () => "").split("\n").map(
|
|
4697
|
+
return replaceBlockComments(code, () => "").split("\n").map(stripLineComment).join("\n");
|
|
4613
4698
|
}
|
|
4614
|
-
function
|
|
4615
|
-
const
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
let depth = 0;
|
|
4619
|
-
for (let i = open; i < code.length; i++) {
|
|
4699
|
+
function closingParens(code) {
|
|
4700
|
+
const close = new Int32Array(code.length).fill(-1);
|
|
4701
|
+
const open = [];
|
|
4702
|
+
for (let i = 0; i < code.length; i++) {
|
|
4620
4703
|
const c = code[i];
|
|
4621
4704
|
if (c === "(")
|
|
4622
|
-
|
|
4623
|
-
else if (c === ")" &&
|
|
4624
|
-
|
|
4705
|
+
open.push(i);
|
|
4706
|
+
else if (c === ")" && open.length > 0)
|
|
4707
|
+
close[open.pop()] = i;
|
|
4625
4708
|
}
|
|
4626
|
-
return
|
|
4709
|
+
return close;
|
|
4710
|
+
}
|
|
4711
|
+
function balancedCall(code, close, start) {
|
|
4712
|
+
const open = code.indexOf("(", start);
|
|
4713
|
+
if (open < 0 || close[open] < 0)
|
|
4714
|
+
return null;
|
|
4715
|
+
return { inner: code.slice(open + 1, close[open]), end: close[open] };
|
|
4627
4716
|
}
|
|
4628
4717
|
function resolveLocator(code, subject) {
|
|
4629
4718
|
const id = subject.trim().match(/^([A-Za-z_$][\w$]*)$/);
|
|
@@ -4671,8 +4760,10 @@ function isAuthHandoffDismissCode(code) {
|
|
|
4671
4760
|
const expects = [];
|
|
4672
4761
|
const re = new RegExp(EXPECT_CALL_RE.source, "g");
|
|
4673
4762
|
let match;
|
|
4763
|
+
let close = null;
|
|
4674
4764
|
while (match = re.exec(body)) {
|
|
4675
|
-
|
|
4765
|
+
close ??= closingParens(body);
|
|
4766
|
+
const found = balancedCall(body, close, match.index);
|
|
4676
4767
|
if (found)
|
|
4677
4768
|
expects.push({ inner: found.inner, tail: body.slice(found.end, found.end + 48) });
|
|
4678
4769
|
}
|
|
@@ -4717,12 +4808,209 @@ var init_auth_handoff_coverage = __esm({
|
|
|
4717
4808
|
}
|
|
4718
4809
|
});
|
|
4719
4810
|
|
|
4811
|
+
// ../shared/dist/auth-plan.js
|
|
4812
|
+
function hasInlineLoginSteps(playwrightCode) {
|
|
4813
|
+
if (!playwrightCode)
|
|
4814
|
+
return false;
|
|
4815
|
+
return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
|
|
4816
|
+
}
|
|
4817
|
+
function hasGeneratedAuthGuard(playwrightCode) {
|
|
4818
|
+
if (!playwrightCode)
|
|
4819
|
+
return false;
|
|
4820
|
+
return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
|
|
4821
|
+
}
|
|
4822
|
+
function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
|
|
4823
|
+
if (!sharedAuthInjectEnabled)
|
|
4824
|
+
return false;
|
|
4825
|
+
if (tags.includes("@auth"))
|
|
4826
|
+
return false;
|
|
4827
|
+
if (tags.includes("@api"))
|
|
4828
|
+
return true;
|
|
4829
|
+
if (!tags.includes("@needs-auth"))
|
|
4830
|
+
return false;
|
|
4831
|
+
if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
|
|
4832
|
+
return true;
|
|
4833
|
+
const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
|
|
4834
|
+
const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
|
|
4835
|
+
return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
|
|
4836
|
+
}
|
|
4837
|
+
function codeReadsStoredCredentials(code) {
|
|
4838
|
+
if (!code)
|
|
4839
|
+
return false;
|
|
4840
|
+
if (/__vqaLogin\b/.test(code))
|
|
4841
|
+
return true;
|
|
4842
|
+
for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
|
|
4843
|
+
if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
|
|
4844
|
+
return true;
|
|
4845
|
+
}
|
|
4846
|
+
for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
|
|
4847
|
+
if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
|
|
4848
|
+
return true;
|
|
4849
|
+
}
|
|
4850
|
+
return false;
|
|
4851
|
+
}
|
|
4852
|
+
function explicitAuthTypeTags(t) {
|
|
4853
|
+
const found = /* @__PURE__ */ new Set();
|
|
4854
|
+
for (const tag of t) {
|
|
4855
|
+
if (AUTH_TYPE_TAGS.includes(tag))
|
|
4856
|
+
found.add(tag);
|
|
4857
|
+
else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
|
|
4858
|
+
found.add(AUTH_TAG_ALIASES[tag]);
|
|
4859
|
+
}
|
|
4860
|
+
return [...found];
|
|
4861
|
+
}
|
|
4862
|
+
function invalidPlan(configurationError) {
|
|
4863
|
+
return {
|
|
4864
|
+
version: 1,
|
|
4865
|
+
source: "invalid",
|
|
4866
|
+
acquisition: "unsupported",
|
|
4867
|
+
presentation: "none",
|
|
4868
|
+
needsCookieState: false,
|
|
4869
|
+
needsStandingHeaders: false,
|
|
4870
|
+
standingHeadersMode: "none",
|
|
4871
|
+
needsLoginCreds: false,
|
|
4872
|
+
requiredEnvKeys: [],
|
|
4873
|
+
configurationError
|
|
4874
|
+
};
|
|
4875
|
+
}
|
|
4876
|
+
function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
|
|
4877
|
+
return {
|
|
4878
|
+
version: 1,
|
|
4879
|
+
source: "explicit",
|
|
4880
|
+
authTypeTag: authTypeTag2,
|
|
4881
|
+
acquisition,
|
|
4882
|
+
presentation,
|
|
4883
|
+
needsCookieState: opts.cookie,
|
|
4884
|
+
needsStandingHeaders: opts.standingMode === "required",
|
|
4885
|
+
standingHeadersMode: opts.standingMode,
|
|
4886
|
+
needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
|
|
4887
|
+
requiredEnvKeys: opts.requiredEnvKeys
|
|
4888
|
+
};
|
|
4889
|
+
}
|
|
4890
|
+
function deriveAuthPlan(tags, code, opts) {
|
|
4891
|
+
const t = Array.isArray(tags) ? tags : [];
|
|
4892
|
+
const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
|
|
4893
|
+
const isApi = t.includes("@api");
|
|
4894
|
+
const explicit = isApi ? explicitAuthTypeTags(t) : [];
|
|
4895
|
+
if (explicit.length > 0) {
|
|
4896
|
+
if (t.includes("@auth")) {
|
|
4897
|
+
return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
|
|
4898
|
+
}
|
|
4899
|
+
const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
|
|
4900
|
+
if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
|
|
4901
|
+
const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
|
|
4902
|
+
switch (loginCompose[0]) {
|
|
4903
|
+
case AUTH_TAG_COOKIE_STATE:
|
|
4904
|
+
return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
|
|
4905
|
+
case AUTH_TAG_LOGIN_COOKIE:
|
|
4906
|
+
return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
|
|
4907
|
+
case AUTH_TAG_LOGIN_BEARER:
|
|
4908
|
+
return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
if (explicit.length > 1) {
|
|
4912
|
+
return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
|
|
4913
|
+
}
|
|
4914
|
+
switch (explicit[0]) {
|
|
4915
|
+
case AUTH_TAG_UNSUPPORTED:
|
|
4916
|
+
return {
|
|
4917
|
+
...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
|
|
4918
|
+
};
|
|
4919
|
+
case AUTH_TAG_NONE:
|
|
4920
|
+
return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
|
|
4921
|
+
case AUTH_TAG_STANDING:
|
|
4922
|
+
return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
|
|
4923
|
+
case AUTH_TAG_LOGIN_BEARER:
|
|
4924
|
+
return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
4925
|
+
case AUTH_TAG_LOGIN_COOKIE:
|
|
4926
|
+
return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
4927
|
+
case AUTH_TAG_COOKIE_STATE:
|
|
4928
|
+
return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
const cookie = legacyNeedsCookieState(t, code, sharedFlag);
|
|
4932
|
+
const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
|
|
4933
|
+
const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
|
|
4934
|
+
const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
|
|
4935
|
+
const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
|
|
4936
|
+
return {
|
|
4937
|
+
version: 1,
|
|
4938
|
+
source: "legacy",
|
|
4939
|
+
acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
|
|
4940
|
+
presentation: authedLegacy ? "cookie" : "none",
|
|
4941
|
+
needsCookieState: cookie,
|
|
4942
|
+
needsStandingHeaders: false,
|
|
4943
|
+
// A legacy bare-@api test keeps injecting standing headers IF the project configured them
|
|
4944
|
+
// (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
|
|
4945
|
+
standingHeadersMode: isApi ? "legacy-if-configured" : "none",
|
|
4946
|
+
needsLoginCreds,
|
|
4947
|
+
requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
|
|
4948
|
+
};
|
|
4949
|
+
}
|
|
4950
|
+
function authTypeTag(tags) {
|
|
4951
|
+
const t = Array.isArray(tags) ? tags : [];
|
|
4952
|
+
const explicit = explicitAuthTypeTags(t);
|
|
4953
|
+
return explicit.length === 1 ? explicit[0] : void 0;
|
|
4954
|
+
}
|
|
4955
|
+
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;
|
|
4956
|
+
var init_auth_plan = __esm({
|
|
4957
|
+
"../shared/dist/auth-plan.js"() {
|
|
4958
|
+
"use strict";
|
|
4959
|
+
AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
|
|
4960
|
+
AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
|
|
4961
|
+
AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
|
|
4962
|
+
AUTH_TAG_STANDING = "@auth-standing";
|
|
4963
|
+
AUTH_TAG_NONE = "@auth-none";
|
|
4964
|
+
AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
|
|
4965
|
+
AUTH_TYPE_TAGS = [
|
|
4966
|
+
AUTH_TAG_COOKIE_STATE,
|
|
4967
|
+
AUTH_TAG_LOGIN_COOKIE,
|
|
4968
|
+
AUTH_TAG_LOGIN_BEARER,
|
|
4969
|
+
AUTH_TAG_STANDING,
|
|
4970
|
+
AUTH_TAG_NONE,
|
|
4971
|
+
AUTH_TAG_UNSUPPORTED
|
|
4972
|
+
];
|
|
4973
|
+
AUTH_TAG_ALIASES = {
|
|
4974
|
+
"@auth-cookie": AUTH_TAG_COOKIE_STATE,
|
|
4975
|
+
"@auth-bearer": AUTH_TAG_LOGIN_BEARER,
|
|
4976
|
+
"@auth-token": AUTH_TAG_LOGIN_BEARER,
|
|
4977
|
+
"@auth-api-key": AUTH_TAG_STANDING,
|
|
4978
|
+
"@auth-standing-header": AUTH_TAG_STANDING
|
|
4979
|
+
};
|
|
4980
|
+
TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
|
|
4981
|
+
API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
|
|
4982
|
+
}
|
|
4983
|
+
});
|
|
4984
|
+
|
|
4720
4985
|
// ../shared/dist/assertion-strength.js
|
|
4986
|
+
function parseLastActionProofMode(raw) {
|
|
4987
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
4988
|
+
return v === "on" || v === "shadow" ? v : "off";
|
|
4989
|
+
}
|
|
4990
|
+
function isUrlProof(stmt) {
|
|
4991
|
+
if (URL_PROOF.test(stmt))
|
|
4992
|
+
return true;
|
|
4993
|
+
let calls = 0;
|
|
4994
|
+
for (const open of stmt.matchAll(POLL_OPEN)) {
|
|
4995
|
+
if (++calls > POLL_CALLS_MAX)
|
|
4996
|
+
break;
|
|
4997
|
+
const from = open.index + open[0].length;
|
|
4998
|
+
const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
|
|
4999
|
+
if (args === null)
|
|
5000
|
+
continue;
|
|
5001
|
+
const subject = pollSubject(args);
|
|
5002
|
+
if (!POLL_PAGE_URL.test(subject))
|
|
5003
|
+
continue;
|
|
5004
|
+
if (!POLL_PAGE_URL_ONLY.test(subject) || PAGE_VALUE_MATCHER.test(stmt.slice(from + args.length)))
|
|
5005
|
+
return true;
|
|
5006
|
+
}
|
|
5007
|
+
return false;
|
|
5008
|
+
}
|
|
4721
5009
|
function liveExpectStatements(uncommented) {
|
|
4722
5010
|
const masked = maskedOutsideStrings(uncommented).split("\n");
|
|
4723
5011
|
return accumulateStatements(uncommented.split("\n")).filter((s) => EXPECT_STMT.test(s.stmt) && EXPECT_STMT.test(masked[s.startLine - 1] ?? ""));
|
|
4724
5012
|
}
|
|
4725
|
-
function
|
|
5013
|
+
function stripLineComment2(ln) {
|
|
4726
5014
|
let quote = null;
|
|
4727
5015
|
for (let i = 0; i < ln.length; i++) {
|
|
4728
5016
|
const c = ln[i];
|
|
@@ -4745,7 +5033,7 @@ function splitInteractionStatementsWithLines(lines) {
|
|
|
4745
5033
|
let depth = 0;
|
|
4746
5034
|
let scaffold = false;
|
|
4747
5035
|
for (let i = 0; i < lines.length; i++) {
|
|
4748
|
-
const ln =
|
|
5036
|
+
const ln = stripLineComment2(lines[i]).trim();
|
|
4749
5037
|
if (!ln)
|
|
4750
5038
|
continue;
|
|
4751
5039
|
const isContinuation = out.length > 0 && (ln.startsWith(".") || depth > 0 && !scaffold);
|
|
@@ -4769,7 +5057,7 @@ function testBlockIds(lines) {
|
|
|
4769
5057
|
let any = false;
|
|
4770
5058
|
let block = 0;
|
|
4771
5059
|
for (let i = 0; i < lines.length; i++) {
|
|
4772
|
-
const ln =
|
|
5060
|
+
const ln = stripLineComment2(lines[i]).trim();
|
|
4773
5061
|
if (!open) {
|
|
4774
5062
|
if (!TEST_BLOCK_OPEN.test(ln))
|
|
4775
5063
|
continue;
|
|
@@ -4853,7 +5141,7 @@ function expectSubjectSpan(stmt) {
|
|
|
4853
5141
|
}
|
|
4854
5142
|
return null;
|
|
4855
5143
|
}
|
|
4856
|
-
function resolveSubjectBindings(stmts, bindings, blockOfLine) {
|
|
5144
|
+
function resolveSubjectBindings(stmts, bindings, blockOfLine, boundAt) {
|
|
4857
5145
|
if (bindings.length === 0)
|
|
4858
5146
|
return [...stmts];
|
|
4859
5147
|
return stmts.map((item) => {
|
|
@@ -4876,14 +5164,22 @@ function resolveSubjectBindings(stmts, bindings, blockOfLine) {
|
|
|
4876
5164
|
subject = `${lead[1]}${found.expr.replace(/;\s*$/, "")}${subject.slice(lead[0].length)}`;
|
|
4877
5165
|
line = found.line;
|
|
4878
5166
|
}
|
|
4879
|
-
|
|
5167
|
+
const out = { ...item, stmt: `${item.stmt.slice(0, span.from)}${subject}${item.stmt.slice(span.to)}` };
|
|
5168
|
+
if (boundAt !== void 0 && line !== item.startLine)
|
|
5169
|
+
boundAt.set(out, line);
|
|
5170
|
+
return out;
|
|
4880
5171
|
});
|
|
4881
5172
|
}
|
|
4882
5173
|
function stripLoginScaffolding(code) {
|
|
4883
|
-
const
|
|
5174
|
+
const lines = code.split("\n");
|
|
5175
|
+
return keptScaffoldingLines(lines).map((i) => lines[i]).join("\n");
|
|
5176
|
+
}
|
|
5177
|
+
function keptScaffoldingLines(lines) {
|
|
5178
|
+
const kept = [];
|
|
4884
5179
|
let inAuth = false;
|
|
4885
5180
|
let inInbox = false;
|
|
4886
|
-
for (
|
|
5181
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5182
|
+
const ln = lines[i];
|
|
4887
5183
|
if (!inAuth && !inInbox && AUTH_START.test(ln)) {
|
|
4888
5184
|
inAuth = true;
|
|
4889
5185
|
continue;
|
|
@@ -4904,9 +5200,9 @@ function stripLoginScaffolding(code) {
|
|
|
4904
5200
|
}
|
|
4905
5201
|
if (LOGIN_CALL_LINE.test(ln))
|
|
4906
5202
|
continue;
|
|
4907
|
-
|
|
5203
|
+
kept.push(i);
|
|
4908
5204
|
}
|
|
4909
|
-
return
|
|
5205
|
+
return kept;
|
|
4910
5206
|
}
|
|
4911
5207
|
function stripCommentsAndLogs(code) {
|
|
4912
5208
|
return stripCoverageComments(code).split("\n").filter((ln) => !/^\s*console\./.test(ln)).join("\n");
|
|
@@ -4915,7 +5211,7 @@ function detectsErrorBannerAssertion(code) {
|
|
|
4915
5211
|
const cleaned = stripCommentsAndLogs(stripLoginScaffolding(code));
|
|
4916
5212
|
const lines = cleaned.split("\n");
|
|
4917
5213
|
const candidates = [];
|
|
4918
|
-
const positive = (stmt) => !
|
|
5214
|
+
const positive = (stmt) => !claimsAbsence(stmt);
|
|
4919
5215
|
const statements = liveExpectStatements(cleaned).map((s) => s.stmt);
|
|
4920
5216
|
for (const stmt of statements) {
|
|
4921
5217
|
if (positive(stmt))
|
|
@@ -5044,6 +5340,12 @@ function detectsObservedFailureAsExpected(code) {
|
|
|
5044
5340
|
return FAILURE_STATUS_ASSERT.some((re) => re.test(stmt)) || hasFailedListener && FAILED_LIST_ASSERT.test(stmt);
|
|
5045
5341
|
});
|
|
5046
5342
|
}
|
|
5343
|
+
function claimsAbsence(stmt) {
|
|
5344
|
+
const negated = NEGATED_ASSERT_NOT.test(stmt);
|
|
5345
|
+
const bare = negated ? stmt.replace(NOT_BEFORE_MATCHER, ".") : stmt;
|
|
5346
|
+
const absenceMatcher = /\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/.test(bare) || VALUE_ABSENCE.test(bare) || OPTION_ABSENCE.test(bare);
|
|
5347
|
+
return negated !== absenceMatcher;
|
|
5348
|
+
}
|
|
5047
5349
|
function isVacuousErrorAbsence(stmt, presence) {
|
|
5048
5350
|
if (!ABSENCE_MATCHER.test(stmt))
|
|
5049
5351
|
return false;
|
|
@@ -5052,6 +5354,41 @@ function isVacuousErrorAbsence(stmt, presence) {
|
|
|
5052
5354
|
return false;
|
|
5053
5355
|
return !presence().includes(m[1]);
|
|
5054
5356
|
}
|
|
5357
|
+
function isVacuousAccountAbsence(stmt, shown, account) {
|
|
5358
|
+
if (!claimsAbsence(stmt))
|
|
5359
|
+
return false;
|
|
5360
|
+
if (!ABSENCE_MATCHER.test(stmt)) {
|
|
5361
|
+
const negated = NEGATED_TEXT.exec(stmt);
|
|
5362
|
+
if (!negated)
|
|
5363
|
+
return false;
|
|
5364
|
+
const read = account.exec(stmt.slice(negated.index));
|
|
5365
|
+
return read !== null && !shown().has(read[0]);
|
|
5366
|
+
}
|
|
5367
|
+
const m = account.exec(stmt);
|
|
5368
|
+
if (!m || shown().has(m[0]))
|
|
5369
|
+
return false;
|
|
5370
|
+
for (let at = endOfEnclosingCall(stmt, m.index + m[0].length); at < stmt.length; at = endOfEnclosingCall(stmt, at)) {
|
|
5371
|
+
SCOPING_STEP.lastIndex = at;
|
|
5372
|
+
if (SCOPING_STEP.test(stmt))
|
|
5373
|
+
return false;
|
|
5374
|
+
}
|
|
5375
|
+
return true;
|
|
5376
|
+
}
|
|
5377
|
+
function endOfEnclosingCall(stmt, from) {
|
|
5378
|
+
let depth = 0;
|
|
5379
|
+
for (let i = from; i < stmt.length; i++) {
|
|
5380
|
+
const c = stmt[i];
|
|
5381
|
+
if (c === "'" || c === '"') {
|
|
5382
|
+
for (i++; i < stmt.length && stmt[i] !== c; i++)
|
|
5383
|
+
if (stmt[i] === "\\")
|
|
5384
|
+
i++;
|
|
5385
|
+
} else if (c === "(")
|
|
5386
|
+
depth++;
|
|
5387
|
+
else if (c === ")" && depth-- === 0)
|
|
5388
|
+
return i + 1;
|
|
5389
|
+
}
|
|
5390
|
+
return stmt.length;
|
|
5391
|
+
}
|
|
5055
5392
|
function isBareContainerVisibility(stmt) {
|
|
5056
5393
|
return BARE_CONTAINER_ROLE.test(stmt) && !GET_BY_TEXT.test(stmt) && !HAS_TEXT_FILTER.test(stmt) && !TEXT_CONTENT_MATCHER.test(stmt);
|
|
5057
5394
|
}
|
|
@@ -5059,19 +5396,53 @@ function isChromeTarget(stmt) {
|
|
|
5059
5396
|
return CHROME_LOCATOR.test(stmt) && !CONTENT_ROLE.test(stmt) && !/getByText\s*\(/.test(stmt);
|
|
5060
5397
|
}
|
|
5061
5398
|
function countsAsContentText(stmt) {
|
|
5062
|
-
if (!TEXT_CONTENT_MATCHER.test(stmt))
|
|
5399
|
+
if (!TEXT_CONTENT_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt))
|
|
5063
5400
|
return false;
|
|
5064
5401
|
if (isSymbolOnlyArgument(stmt, TEXT_CONTENT_MATCHER))
|
|
5065
5402
|
return false;
|
|
5066
|
-
|
|
5403
|
+
return onContentTarget(stmt);
|
|
5404
|
+
}
|
|
5405
|
+
function onContentTarget(stmt) {
|
|
5406
|
+
return !isChromeTarget(stmt) || /getByRole\s*\(\s*['"`]main['"`]/i.test(stmt);
|
|
5407
|
+
}
|
|
5408
|
+
function provesTextRemoved(stmt, presence) {
|
|
5409
|
+
const m = stmt.match(NEGATED_TEXT);
|
|
5410
|
+
if (!m || !onContentTarget(stmt))
|
|
5411
|
+
return false;
|
|
5412
|
+
const text = m[1] ?? m[2];
|
|
5413
|
+
if (text === void 0)
|
|
5067
5414
|
return true;
|
|
5068
|
-
|
|
5415
|
+
if (!/[\p{L}\p{N}]/u.test(text))
|
|
5416
|
+
return false;
|
|
5417
|
+
return !ERROR_TEXT_WORDS.test(text) || presence().includes(text);
|
|
5069
5418
|
}
|
|
5070
5419
|
function isDataContentRole(stmt) {
|
|
5071
5420
|
return CONTENT_ROLE.test(stmt) && !ANNOUNCE_ROLE.test(stmt);
|
|
5072
5421
|
}
|
|
5422
|
+
function destructuresRequest(code) {
|
|
5423
|
+
let readTo = -1;
|
|
5424
|
+
for (const m of code.matchAll(ASYNC_DESTRUCTURE_OPEN)) {
|
|
5425
|
+
const start = m.index + m[0].length;
|
|
5426
|
+
if (start <= readTo)
|
|
5427
|
+
continue;
|
|
5428
|
+
let end = start;
|
|
5429
|
+
while (end < code.length && code[end] !== "}" && code[end] !== ")")
|
|
5430
|
+
end++;
|
|
5431
|
+
readTo = end;
|
|
5432
|
+
if (end === code.length)
|
|
5433
|
+
return false;
|
|
5434
|
+
if (code[end] !== "}" || !REQUEST_WORD.test(code.slice(start, end)))
|
|
5435
|
+
continue;
|
|
5436
|
+
let close = end + 1;
|
|
5437
|
+
while (close < code.length && /\s/.test(code[close]))
|
|
5438
|
+
close++;
|
|
5439
|
+
if (code[close] === ")")
|
|
5440
|
+
return true;
|
|
5441
|
+
}
|
|
5442
|
+
return false;
|
|
5443
|
+
}
|
|
5073
5444
|
function isApiTest(code) {
|
|
5074
|
-
const usesRequestFixture =
|
|
5445
|
+
const usesRequestFixture = destructuresRequest(code) || /\brequest\.(get|post|put|patch|delete|head|fetch)\s*\(/.test(code);
|
|
5075
5446
|
if (!usesRequestFixture)
|
|
5076
5447
|
return false;
|
|
5077
5448
|
const drivesUi = /\bpage\.(getBy\w+|locator|click|fill|goto|waitForURL|reload)\s*\(/.test(code) || /\.(click|fill|check|uncheck|selectOption|pressSequentially)\s*\(/.test(code);
|
|
@@ -5099,7 +5470,7 @@ function roleNames(s) {
|
|
|
5099
5470
|
function interactedControlNames(lines) {
|
|
5100
5471
|
const names = /* @__PURE__ */ new Set();
|
|
5101
5472
|
for (const raw of lines) {
|
|
5102
|
-
const ln =
|
|
5473
|
+
const ln = stripLineComment2(raw);
|
|
5103
5474
|
if (!INTERACT_VERB.test(ln))
|
|
5104
5475
|
continue;
|
|
5105
5476
|
for (const n of roleNames(ln))
|
|
@@ -5295,6 +5666,14 @@ function balancedArguments(code, from, limit = code.length) {
|
|
|
5295
5666
|
}
|
|
5296
5667
|
return null;
|
|
5297
5668
|
}
|
|
5669
|
+
function expectCallTail(stmt) {
|
|
5670
|
+
const at = stmt.search(EXPECT_CALL_RE);
|
|
5671
|
+
if (at < 0)
|
|
5672
|
+
return null;
|
|
5673
|
+
const from = stmt.indexOf("(", at) + 1;
|
|
5674
|
+
const args = balancedArguments(stmt, from, MATCHER_ARGS_MAX);
|
|
5675
|
+
return args === null ? null : stmt.slice(from + args.length).trimStart();
|
|
5676
|
+
}
|
|
5298
5677
|
function matcherArgumentsAreConstant(body, from) {
|
|
5299
5678
|
MATCHER_OPEN.lastIndex = from;
|
|
5300
5679
|
const opened = MATCHER_OPEN.exec(body);
|
|
@@ -5330,32 +5709,70 @@ function hasOnlyConstantAssertions(code) {
|
|
|
5330
5709
|
const { constant, real } = constantAssertions(code);
|
|
5331
5710
|
return constant.length > 0 && real === 0;
|
|
5332
5711
|
}
|
|
5712
|
+
function envValueKey(name) {
|
|
5713
|
+
return `\0env:${name}`;
|
|
5714
|
+
}
|
|
5333
5715
|
function enteredValues(stmts) {
|
|
5334
5716
|
const values = /* @__PURE__ */ new Map();
|
|
5335
5717
|
const navLines = [];
|
|
5718
|
+
const accountConsts = /* @__PURE__ */ new Set();
|
|
5336
5719
|
for (const { stmt, startLine } of stmts) {
|
|
5337
5720
|
if (PAGE_NAV.test(stmt))
|
|
5338
5721
|
navLines.push(startLine);
|
|
5722
|
+
for (const c of stmt.matchAll(ACCOUNT_CONST))
|
|
5723
|
+
accountConsts.add(c[1].replace(/\$/g, "\\$"));
|
|
5339
5724
|
let m;
|
|
5340
5725
|
ENTERED_VALUE.lastIndex = 0;
|
|
5341
|
-
|
|
5342
|
-
const
|
|
5343
|
-
if (!
|
|
5344
|
-
values.set(value, startLine);
|
|
5345
|
-
|
|
5726
|
+
const add = (value) => {
|
|
5727
|
+
const lines = values.get(value);
|
|
5728
|
+
if (!lines)
|
|
5729
|
+
values.set(value, [startLine]);
|
|
5730
|
+
else if (lines[lines.length - 1] !== startLine)
|
|
5731
|
+
lines.push(startLine);
|
|
5732
|
+
};
|
|
5733
|
+
while (m = ENTERED_VALUE.exec(stmt))
|
|
5734
|
+
add((m[1] ?? m[2] ?? m[3]).toLowerCase());
|
|
5735
|
+
ENTERED_ENV.lastIndex = 0;
|
|
5736
|
+
while (m = ENTERED_ENV.exec(stmt))
|
|
5737
|
+
add(envValueKey(m[1] ?? m[2] ?? m[3] ?? m[4]));
|
|
5346
5738
|
}
|
|
5347
|
-
|
|
5739
|
+
const consts = accountConsts.size > 0 ? `|(?<![\\w$.])(?:${[...accountConsts].join("|")})(?![\\w$])` : "";
|
|
5740
|
+
return { values, navLines, account: new RegExp(`${ACCOUNT_ENV}${consts}`) };
|
|
5348
5741
|
}
|
|
5349
|
-
function
|
|
5742
|
+
function countBelow(lines, line) {
|
|
5743
|
+
let lo = 0;
|
|
5744
|
+
let hi = lines.length;
|
|
5745
|
+
while (lo < hi) {
|
|
5746
|
+
const mid = lo + hi >> 1;
|
|
5747
|
+
if (lines[mid] < line)
|
|
5748
|
+
lo = mid + 1;
|
|
5749
|
+
else
|
|
5750
|
+
hi = mid;
|
|
5751
|
+
}
|
|
5752
|
+
return lo;
|
|
5753
|
+
}
|
|
5754
|
+
function enteredLinesOf(stmt, entered) {
|
|
5350
5755
|
const m = stmt.match(/\.toHaveValue\s*\(\s*(['"`])((?:(?!\1).)+)\1/);
|
|
5351
|
-
if (
|
|
5756
|
+
if (m)
|
|
5757
|
+
return entered.values.get(m[2].toLowerCase());
|
|
5758
|
+
const env = stmt.match(HAVE_VALUE_ENV);
|
|
5759
|
+
return env ? entered.values.get(envValueKey(env[1] ?? env[2])) : void 0;
|
|
5760
|
+
}
|
|
5761
|
+
function typedSinceLastNav(stmt, entered, startLine, lines = enteredLinesOf(stmt, entered)) {
|
|
5762
|
+
if (!lines || startLine === void 0)
|
|
5763
|
+
return false;
|
|
5764
|
+
const before = countBelow(lines, startLine);
|
|
5765
|
+
if (before === 0)
|
|
5352
5766
|
return false;
|
|
5353
|
-
|
|
5354
|
-
|
|
5767
|
+
return countBelow(entered.navLines, startLine) === countBelow(entered.navLines, lines[before - 1] + 1);
|
|
5768
|
+
}
|
|
5769
|
+
function isEnteredValueEcho(stmt, entered, startLine) {
|
|
5770
|
+
const lines = enteredLinesOf(stmt, entered);
|
|
5771
|
+
if (!lines)
|
|
5355
5772
|
return false;
|
|
5356
|
-
if (startLine === void 0)
|
|
5773
|
+
if (startLine === void 0 || lines[0] >= startLine)
|
|
5357
5774
|
return true;
|
|
5358
|
-
return
|
|
5775
|
+
return typedSinceLastNav(stmt, entered, startLine, lines);
|
|
5359
5776
|
}
|
|
5360
5777
|
function isAnyValueMatcher(stmt) {
|
|
5361
5778
|
if (/\.not\.toHaveValue\s*\(\s*(?:['"`]{2}|\/\^?\$?\/)/.test(stmt))
|
|
@@ -5445,6 +5862,49 @@ function hasAttributeTransition(stmts) {
|
|
|
5445
5862
|
return true;
|
|
5446
5863
|
return false;
|
|
5447
5864
|
}
|
|
5865
|
+
function comparesWithItself(subject, tail) {
|
|
5866
|
+
const m = SELF_COMPARE_MATCHER.exec(tail);
|
|
5867
|
+
if (!m)
|
|
5868
|
+
return false;
|
|
5869
|
+
const args = balancedArguments(tail, m[0].length, MATCHER_ARGS_MAX);
|
|
5870
|
+
if (args === null)
|
|
5871
|
+
return false;
|
|
5872
|
+
const squash2 = (x) => x.replace(/^\s*await\s+/, "").replace(/\s+/g, "");
|
|
5873
|
+
const expected = squash2(args);
|
|
5874
|
+
return expected === squash2(m[1] === "toHaveCount" ? `${subject}.count()` : subject);
|
|
5875
|
+
}
|
|
5876
|
+
function asPlainAssertion(s) {
|
|
5877
|
+
return plainAssertion(s, false);
|
|
5878
|
+
}
|
|
5879
|
+
function plainAssertion(s, keepSelfCompare) {
|
|
5880
|
+
const { stmt } = s;
|
|
5881
|
+
if (COUNT_AT_LEAST_ZERO.test(stmt))
|
|
5882
|
+
return [];
|
|
5883
|
+
const subject = extractExpectSubject(stmt);
|
|
5884
|
+
if (subject === null)
|
|
5885
|
+
return [s];
|
|
5886
|
+
const plain = subject.replace(/^await\s+/, "").trim();
|
|
5887
|
+
const subjectAt = stmt.indexOf(subject, stmt.search(EXPECT_CALL_RE));
|
|
5888
|
+
const tail = (stmt.startsWith("expect.poll", stmt.search(EXPECT_CALL_RE)) ? expectCallTail(stmt) : null) ?? stmt.slice(subjectAt + subject.length).trimStart();
|
|
5889
|
+
const as = (rewritten) => [{ ...s, stmt: rewritten }];
|
|
5890
|
+
if (LOCATOR_EXPR.test(plain) && !LOCATOR_VALUE_READ.test(plain) && ALWAYS_TRUE_ON_OBJECT.test(tail))
|
|
5891
|
+
return [];
|
|
5892
|
+
if (DEFINED_VALUE_READ.test(plain) && ALWAYS_DEFINED.test(tail))
|
|
5893
|
+
return [];
|
|
5894
|
+
if (!keepSelfCompare && comparesWithItself(plain, tail))
|
|
5895
|
+
return [];
|
|
5896
|
+
const visible = /^(.+)\.\s*isVisible\s*\(\s*\)$/s.exec(plain);
|
|
5897
|
+
if (visible && IS_VISIBLE_TRUE.test(tail))
|
|
5898
|
+
return as(`await expect(${visible[1]}).toBeVisible();`);
|
|
5899
|
+
if (/^page\s*\.\s*(?:url|title)\s*\(\s*\)$/.test(plain) && PAGE_VALUE_MATCHER.test(tail)) {
|
|
5900
|
+
const matcher = plain.includes("url") ? "toHaveURL" : "toHaveTitle";
|
|
5901
|
+
return as(`await expect(page)${tail.replace(/^\)\s*\.\s*\w+\s*\(/, `.${matcher}(`)}`);
|
|
5902
|
+
}
|
|
5903
|
+
return [s];
|
|
5904
|
+
}
|
|
5905
|
+
function assertionStrengthHoistedReadsOn(value = typeof process !== "undefined" ? process.env?.ASSERTION_STRENGTH_HOISTED_READS : void 0) {
|
|
5906
|
+
return /^(1|true|on)$/i.test((value ?? "").trim());
|
|
5907
|
+
}
|
|
5448
5908
|
function creditsOutcomeAlways(stmt, entered, startLine) {
|
|
5449
5909
|
if (!OUTCOME_ALWAYS.some((re) => re.test(stmt)))
|
|
5450
5910
|
return false;
|
|
@@ -5477,6 +5937,22 @@ function successNoticeQuote(stmt) {
|
|
|
5477
5937
|
}
|
|
5478
5938
|
return null;
|
|
5479
5939
|
}
|
|
5940
|
+
function namedPagePath(arg) {
|
|
5941
|
+
const text = arg.trim();
|
|
5942
|
+
let path;
|
|
5943
|
+
const quoted = /^(['"`])([^'"`]*)\1$/.exec(text);
|
|
5944
|
+
const regex = quoted ? null : /^\/((?:\\.|[^/\\])+)\/[a-z]*$/.exec(text);
|
|
5945
|
+
if (quoted) {
|
|
5946
|
+
path = quoted[2].replace(/^\$\{[^}]*\}/, "").replace(/^\*\*/, "").replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "").replace(/\*\*$/, "");
|
|
5947
|
+
} else if (regex) {
|
|
5948
|
+
path = regex[1].replace(/^\^/, "").replace(/^\.\*/, "").replace(/\$$/, "").replace(/\\([/.-])/g, "$1");
|
|
5949
|
+
} else {
|
|
5950
|
+
return null;
|
|
5951
|
+
}
|
|
5952
|
+
if (!PLAIN_PAGE_PATH.test(path))
|
|
5953
|
+
return null;
|
|
5954
|
+
return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
5955
|
+
}
|
|
5480
5956
|
function provesStoredControlValue(stmt, startLine, persistence) {
|
|
5481
5957
|
if (!persistence || startLine === void 0)
|
|
5482
5958
|
return false;
|
|
@@ -5503,7 +5979,64 @@ function reopenedAfterSave(startLine, persistence, reopens = persistence.reloadL
|
|
|
5503
5979
|
}
|
|
5504
5980
|
return lo < reopens.length && reopens[lo] < startLine;
|
|
5505
5981
|
}
|
|
5506
|
-
function
|
|
5982
|
+
function actsBetween(withLines, from, to) {
|
|
5983
|
+
let acts = actLinesOf.get(withLines);
|
|
5984
|
+
if (acts === void 0) {
|
|
5985
|
+
acts = withLines.filter((s) => ACTS.test(s.stmt)).map((s) => s.startLine).sort((a, b) => a - b);
|
|
5986
|
+
actLinesOf.set(withLines, acts);
|
|
5987
|
+
}
|
|
5988
|
+
let lo = 0;
|
|
5989
|
+
let hi = acts.length;
|
|
5990
|
+
while (lo < hi) {
|
|
5991
|
+
const mid = lo + hi >> 1;
|
|
5992
|
+
if (acts[mid] <= from)
|
|
5993
|
+
lo = mid + 1;
|
|
5994
|
+
else
|
|
5995
|
+
hi = mid;
|
|
5996
|
+
}
|
|
5997
|
+
return lo < acts.length && acts[lo] < to;
|
|
5998
|
+
}
|
|
5999
|
+
function signInProofWindow(stmts, withLines, lines, blockIds) {
|
|
6000
|
+
if (blockIds === null)
|
|
6001
|
+
return void 0;
|
|
6002
|
+
const testBodies2 = /* @__PURE__ */ new Set();
|
|
6003
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6004
|
+
const id = blockIds[i];
|
|
6005
|
+
if (id > 0 && (i === 0 || blockIds[i - 1] !== id) && TEST_BODY_OPEN.test(stripLineComment2(lines[i]).trim()))
|
|
6006
|
+
testBodies2.add(id);
|
|
6007
|
+
}
|
|
6008
|
+
const blockOf = (i) => blockIds[withLines[i].startLine - 1];
|
|
6009
|
+
const passwordAt = stmts.findIndex((stmt, i) => testBodies2.has(blockOf(i)) && ENV_PASSWORD_ENTRY.test(stmt));
|
|
6010
|
+
if (passwordAt < 0)
|
|
6011
|
+
return void 0;
|
|
6012
|
+
const body = blockOf(passwordAt);
|
|
6013
|
+
let clickAt = passwordAt + 1;
|
|
6014
|
+
while (clickAt < stmts.length && FORM_INPUT.test(stmts[clickAt]) && !ANY_CLICK.test(stmts[clickAt]))
|
|
6015
|
+
clickAt++;
|
|
6016
|
+
if (clickAt >= stmts.length || blockOf(clickAt) !== body || !ANY_CLICK.test(stmts[clickAt]) || NAV_LOCATOR.test(stmts[clickAt]))
|
|
6017
|
+
return void 0;
|
|
6018
|
+
let end = clickAt + 1;
|
|
6019
|
+
for (; end < stmts.length && blockOf(end) === body; end++) {
|
|
6020
|
+
if (CLOSERS_ONLY.test(stmts[end]))
|
|
6021
|
+
continue;
|
|
6022
|
+
if (!ASSERT_OR_WAIT.test(stmts[end]) || ACTS.test(stmts[end]))
|
|
6023
|
+
return void 0;
|
|
6024
|
+
}
|
|
6025
|
+
return { after: withLines[clickAt].startLine, until: end < stmts.length ? withLines[end].startLine : Number.POSITIVE_INFINITY };
|
|
6026
|
+
}
|
|
6027
|
+
function isAccountControlCheck(stmt) {
|
|
6028
|
+
if (!VISIBILITY_MATCHER.test(stmt) || NEGATED_ASSERT.test(stmt) || OPTION_ABSENCE.test(stmt))
|
|
6029
|
+
return false;
|
|
6030
|
+
const role = ACCOUNT_CONTROL_ROLE.exec(stmt);
|
|
6031
|
+
if (!role)
|
|
6032
|
+
return false;
|
|
6033
|
+
const rest = stmt.slice(role.index + role[0].length, role.index + role[0].length + 300);
|
|
6034
|
+
const close = rest.indexOf("}");
|
|
6035
|
+
const options = close < 0 ? rest : rest.slice(0, close);
|
|
6036
|
+
const name = options.match(/(?:^|[\s,])name\s*:\s*/);
|
|
6037
|
+
return name !== null && ACCOUNT_EMAIL_ENV.test(options.slice(name.index + name[0].length));
|
|
6038
|
+
}
|
|
6039
|
+
function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine, persistence, signedIn) {
|
|
5507
6040
|
if (mutated && hasAttributeTransition(stmts))
|
|
5508
6041
|
return { kind: "durable" };
|
|
5509
6042
|
const notices = [];
|
|
@@ -5513,11 +6046,25 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
|
|
|
5513
6046
|
};
|
|
5514
6047
|
let presenceText;
|
|
5515
6048
|
const presence = () => presenceText ??= stmts.filter((o) => !ABSENCE_MATCHER.test(o.stmt) && !NEGATED_ASSERT.test(o.stmt)).map((o) => o.stmt).join("\n");
|
|
6049
|
+
let shownAccounts;
|
|
6050
|
+
const shown = () => {
|
|
6051
|
+
if (shownAccounts)
|
|
6052
|
+
return shownAccounts;
|
|
6053
|
+
const ref = new RegExp(entered.account.source, "g");
|
|
6054
|
+
return shownAccounts = new Set(stmts.filter((o) => !claimsAbsence(o.stmt)).flatMap((o) => Array.from(o.stmt.matchAll(ref), (m) => m[0])));
|
|
6055
|
+
};
|
|
5516
6056
|
for (const { stmt, startLine } of stmts) {
|
|
5517
6057
|
if (firstMutLine !== void 0 && startLine !== void 0 && startLine < firstMutLine)
|
|
5518
6058
|
continue;
|
|
5519
6059
|
if (isVacuousErrorAbsence(stmt, presence))
|
|
5520
6060
|
continue;
|
|
6061
|
+
if (COUNT_ONE.test(stmt) && quotedAssertedStrings(stmt).some((t) => clicked.has(t.toLowerCase())))
|
|
6062
|
+
continue;
|
|
6063
|
+
if (isVacuousAccountAbsence(stmt, shown, entered.account))
|
|
6064
|
+
continue;
|
|
6065
|
+
if (signedIn && startLine !== void 0 && startLine > signedIn.after && startLine < signedIn.until && isAccountControlCheck(stmt)) {
|
|
6066
|
+
return { kind: "durable" };
|
|
6067
|
+
}
|
|
5521
6068
|
if (creditsOutcomeAlways(stmt, entered, startLine))
|
|
5522
6069
|
return { kind: "durable" };
|
|
5523
6070
|
if (mutated && !NEGATED_ASSERT.test(stmt) && MESSAGE_SURFACE.test(stmt) && OUTCOME_TEXT.test(assertedText(stmt))) {
|
|
@@ -5527,12 +6074,15 @@ function classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstM
|
|
|
5527
6074
|
else
|
|
5528
6075
|
return { kind: "durable" };
|
|
5529
6076
|
}
|
|
5530
|
-
|
|
6077
|
+
const retyped = typedSinceLastNav(stmt, entered, startLine);
|
|
6078
|
+
if (!retyped && STATE_MATCHER.test(stmt) && (persistence && startLine !== void 0 ? reopenedAfterSave(startLine, persistence, persistence.reloadCalls) : reloaded))
|
|
5531
6079
|
return { kind: "durable" };
|
|
5532
|
-
if (provesStoredControlValue(stmt, startLine, persistence))
|
|
6080
|
+
if (!retyped && provesStoredControlValue(stmt, startLine, persistence))
|
|
5533
6081
|
return { kind: "durable" };
|
|
5534
6082
|
if (countsAsContentText(stmt))
|
|
5535
6083
|
return { kind: "durable" };
|
|
6084
|
+
if (provesTextRemoved(stmt, presence))
|
|
6085
|
+
return { kind: "durable" };
|
|
5536
6086
|
if (mutated && VISIBILITY_MATCHER.test(stmt)) {
|
|
5537
6087
|
const revealsText = GET_BY_TEXT.test(stmt) || HAS_TEXT_FILTER.test(stmt) || CONTENT_ROLE.test(stmt);
|
|
5538
6088
|
if (revealsText && !isChromeTarget(stmt)) {
|
|
@@ -5596,10 +6146,57 @@ function noticeOnlyExemption(clicked, noticeText, interactionStmts) {
|
|
|
5596
6146
|
function detectOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine) {
|
|
5597
6147
|
return classifyOutcomeProof(stmts, mutated, reloaded, clicked, entered, firstMutLine).kind !== "none";
|
|
5598
6148
|
}
|
|
6149
|
+
function clickLabel(stmt) {
|
|
6150
|
+
let label = null;
|
|
6151
|
+
for (const m of stmt.matchAll(CONTROL_LABEL))
|
|
6152
|
+
label = m[2] ?? m[3] ?? null;
|
|
6153
|
+
return label;
|
|
6154
|
+
}
|
|
6155
|
+
function teardownLines(lines) {
|
|
6156
|
+
const out = new Array(lines.length).fill(false);
|
|
6157
|
+
let open = false;
|
|
6158
|
+
let depth = 0;
|
|
6159
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6160
|
+
const ln = stripLineComment2(lines[i]).trim();
|
|
6161
|
+
let from = 0;
|
|
6162
|
+
if (!open) {
|
|
6163
|
+
const m = TEARDOWN_OPEN.exec(ln);
|
|
6164
|
+
if (!m)
|
|
6165
|
+
continue;
|
|
6166
|
+
open = true;
|
|
6167
|
+
depth = 0;
|
|
6168
|
+
from = m.index;
|
|
6169
|
+
}
|
|
6170
|
+
out[i] = true;
|
|
6171
|
+
let closed = false;
|
|
6172
|
+
let quote = null;
|
|
6173
|
+
for (let k = from; k < ln.length; k++) {
|
|
6174
|
+
const ch = ln[k];
|
|
6175
|
+
if (quote !== null) {
|
|
6176
|
+
if (ch === "\\")
|
|
6177
|
+
k++;
|
|
6178
|
+
else if (ch === quote)
|
|
6179
|
+
quote = null;
|
|
6180
|
+
continue;
|
|
6181
|
+
}
|
|
6182
|
+
if (ch === "'" || ch === '"' || ch === "`")
|
|
6183
|
+
quote = ch;
|
|
6184
|
+
else if (ch === "{")
|
|
6185
|
+
depth++;
|
|
6186
|
+
else if (ch === "}") {
|
|
6187
|
+
depth--;
|
|
6188
|
+
closed = true;
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
6191
|
+
if (depth <= 0 && closed)
|
|
6192
|
+
open = false;
|
|
6193
|
+
}
|
|
6194
|
+
return out;
|
|
6195
|
+
}
|
|
5599
6196
|
function validateAssertionStrength(code, opts = {}) {
|
|
5600
6197
|
const body = stripLoginScaffolding(code);
|
|
5601
6198
|
const lines = body.split("\n");
|
|
5602
|
-
const stmts = liveExpectStatements(stripCoverageComments(body));
|
|
6199
|
+
const stmts = liveExpectStatements(stripCoverageComments(body)).flatMap(asPlainAssertion);
|
|
5603
6200
|
const base = (extra) => ({
|
|
5604
6201
|
apiTest: false,
|
|
5605
6202
|
interacted: false,
|
|
@@ -5639,7 +6236,13 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
5639
6236
|
const interactionStmts = inlineLocatorBindings(interactionStmtsRaw);
|
|
5640
6237
|
const blockIds = testBlockIds(lines);
|
|
5641
6238
|
const blockOfLine = (line) => blockIds?.[line - 1] ?? 0;
|
|
5642
|
-
const
|
|
6239
|
+
const hoistedReads = assertionStrengthHoistedReadsOn();
|
|
6240
|
+
const boundAt = hoistedReads ? /* @__PURE__ */ new Map() : void 0;
|
|
6241
|
+
const resolved = resolveSubjectBindings(stmts, scopedLocatorBindings(interactionStmtsWithLines, blockOfLine), blockOfLine, boundAt);
|
|
6242
|
+
const checks = boundAt === void 0 ? resolved : resolved.flatMap((s) => {
|
|
6243
|
+
const from = boundAt.get(s);
|
|
6244
|
+
return plainAssertion(s, from !== void 0 && actsBetween(interactionStmtsWithLines, from, s.startLine));
|
|
6245
|
+
});
|
|
5643
6246
|
const insideTest = linesInsideTestBlocks(lines);
|
|
5644
6247
|
const inTestOrder = (i) => insideTest === null || insideTest[interactionStmtsWithLines[i].startLine - 1] === true;
|
|
5645
6248
|
let mutated = opts.interactionPerformed === true;
|
|
@@ -5649,9 +6252,34 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
5649
6252
|
let navClicks = 0;
|
|
5650
6253
|
const persistence = { mutLines: [], reloadLines: [], reloadCalls: [] };
|
|
5651
6254
|
const openedPages = /* @__PURE__ */ new Set();
|
|
6255
|
+
let checkedPage = null;
|
|
6256
|
+
const savedOnCheckedPage = /* @__PURE__ */ new Set();
|
|
6257
|
+
const saveAt = (i, at) => {
|
|
6258
|
+
lastMutIdx = i;
|
|
6259
|
+
if (!inTestOrder(i))
|
|
6260
|
+
return;
|
|
6261
|
+
persistence.mutLines.push(at);
|
|
6262
|
+
if (firstMutIdx < 0)
|
|
6263
|
+
firstMutIdx = i;
|
|
6264
|
+
if (checkedPage !== null)
|
|
6265
|
+
savedOnCheckedPage.add(checkedPage);
|
|
6266
|
+
};
|
|
6267
|
+
const lastActionProof = opts.lastActionProof ?? "off";
|
|
6268
|
+
const teardown = lastActionProof === "off" ? null : teardownLines(lines);
|
|
6269
|
+
const acts = { last: null, prev: null, count: 0 };
|
|
6270
|
+
const noteAct = (i, at, kind) => {
|
|
6271
|
+
if (teardown === null || teardown[at - 1] || !inTestOrder(i))
|
|
6272
|
+
return;
|
|
6273
|
+
if (kind !== "enter")
|
|
6274
|
+
acts.count++;
|
|
6275
|
+
acts.prev = acts.last;
|
|
6276
|
+
acts.last = { i, kind };
|
|
6277
|
+
};
|
|
5652
6278
|
for (let i = 0; i < interactionStmts.length; i++) {
|
|
5653
6279
|
const ln = interactionStmts[i];
|
|
5654
6280
|
const at = interactionStmtsWithLines[i].startLine;
|
|
6281
|
+
if (teardown !== null && acts.last?.kind === "fill" && ENTER_PRESS.test(ln))
|
|
6282
|
+
noteAct(i, at, "enter");
|
|
5655
6283
|
if (RELOAD.test(ln)) {
|
|
5656
6284
|
persistence.reloadLines.push(at);
|
|
5657
6285
|
persistence.reloadCalls.push(at);
|
|
@@ -5659,32 +6287,31 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
5659
6287
|
const opened = ln.match(/\.goto\s*\(\s*([^,)]+)/);
|
|
5660
6288
|
if (opened) {
|
|
5661
6289
|
const page = opened[1].trim();
|
|
5662
|
-
|
|
6290
|
+
const path = namedPagePath(page);
|
|
6291
|
+
if (openedPages.has(page) || path !== null && savedOnCheckedPage.has(path))
|
|
5663
6292
|
persistence.reloadLines.push(at);
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
}
|
|
6293
|
+
openedPages.add(page);
|
|
6294
|
+
checkedPage = null;
|
|
6295
|
+
}
|
|
6296
|
+
if (/\bpage\s*\.\s*go(?:Back|Forward)\s*\(/.test(ln))
|
|
6297
|
+
checkedPage = null;
|
|
6298
|
+
const checked = URL_CHECK_ARG.exec(ln);
|
|
6299
|
+
if (checked)
|
|
6300
|
+
checkedPage = namedPagePath(checked[1] ?? checked[2]);
|
|
5667
6301
|
if (FORM_INPUT.test(ln)) {
|
|
5668
6302
|
mutated = true;
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
persistence.mutLines.push(at);
|
|
5672
|
-
if (firstMutIdx < 0)
|
|
5673
|
-
firstMutIdx = i;
|
|
5674
|
-
}
|
|
6303
|
+
saveAt(i, at);
|
|
6304
|
+
noteAct(i, at, "fill");
|
|
5675
6305
|
continue;
|
|
5676
6306
|
}
|
|
5677
6307
|
if (ANY_CLICK.test(ln)) {
|
|
5678
6308
|
if (NAV_LOCATOR.test(ln)) {
|
|
5679
6309
|
navClicks++;
|
|
6310
|
+
checkedPage = null;
|
|
5680
6311
|
} else {
|
|
5681
6312
|
mutated = true;
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
persistence.mutLines.push(at);
|
|
5685
|
-
if (firstMutIdx < 0)
|
|
5686
|
-
firstMutIdx = i;
|
|
5687
|
-
}
|
|
6313
|
+
saveAt(i, at);
|
|
6314
|
+
noteAct(i, at, "click");
|
|
5688
6315
|
}
|
|
5689
6316
|
}
|
|
5690
6317
|
}
|
|
@@ -5717,9 +6344,28 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
5717
6344
|
};
|
|
5718
6345
|
}
|
|
5719
6346
|
const orderedPersistence = firstMutLine !== void 0 ? persistence : void 0;
|
|
5720
|
-
const
|
|
6347
|
+
const signedIn = signInProofWindow(interactionStmts, interactionStmtsWithLines, lines, blockIds);
|
|
6348
|
+
const proof = classifyOutcomeProof(checks, mutated, reloaded, clickedControlNames(interactionStmts), entered, firstMutLine, orderedPersistence, signedIn);
|
|
5721
6349
|
if (proof.kind === "durable") {
|
|
5722
6350
|
const signals2 = base({ interacted, mutated, navOnly, hasOutcomeProof: true, terminal });
|
|
6351
|
+
const { last, prev } = acts;
|
|
6352
|
+
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])))) {
|
|
6353
|
+
const lastLine = interactionStmtsWithLines[last.i].startLine;
|
|
6354
|
+
const clicked = clickedControlNames(interactionStmts);
|
|
6355
|
+
const after = classifyOutcomeProof(checks, mutated, reloaded, clicked, entered, lastLine, orderedPersistence, signedIn);
|
|
6356
|
+
const unproven = after.kind === "none" || after.kind === "notice" && noticeOnlyExemption(clicked, after.noticeText, interactionStmts) === null;
|
|
6357
|
+
if (unproven && lastActionProof === "on") {
|
|
6358
|
+
const line = (keptScaffoldingLines(code.split("\n"))[lastLine - 1] ?? lastLine - 1) + 1;
|
|
6359
|
+
return {
|
|
6360
|
+
ok: false,
|
|
6361
|
+
severity: "block",
|
|
6362
|
+
reason: `the last save (line ${line}) has no outcome check after it (the earlier check proves only the earlier action)`,
|
|
6363
|
+
signals: { ...signals2, lastActionUnproven: true }
|
|
6364
|
+
};
|
|
6365
|
+
}
|
|
6366
|
+
if (unproven)
|
|
6367
|
+
signals2.lastActionUnproven = true;
|
|
6368
|
+
}
|
|
5723
6369
|
return { ok: true, severity: "none", reason: "proves an outcome (count / diff / value / revealed content)", signals: signals2 };
|
|
5724
6370
|
}
|
|
5725
6371
|
if (mutated && proof.kind === "notice") {
|
|
@@ -5768,7 +6414,7 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
5768
6414
|
signals
|
|
5769
6415
|
};
|
|
5770
6416
|
}
|
|
5771
|
-
const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx &&
|
|
6417
|
+
const urlProofAfter = interactionStmts.some((s, i) => i > lastMutIdx && isUrlProof(s));
|
|
5772
6418
|
if (urlProofAfter) {
|
|
5773
6419
|
return {
|
|
5774
6420
|
ok: false,
|
|
@@ -5805,7 +6451,7 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
5805
6451
|
signals
|
|
5806
6452
|
};
|
|
5807
6453
|
}
|
|
5808
|
-
var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD2, TEST_BLOCK_OPEN, LOCATOR_BINDING, EXPECT_OPEN, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, BARE_CONTAINER_ROLE, HAS_TEXT_FILTER, OUTCOME_TEXT, SUCCESS_NOTICE_TEXT, NOT_A_SUCCESS_NOTICE, ANNOUNCE_ROLE, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, CONST_CHAIN_MAX_DEPTH, TYPED_INPUT, TITLE_LINE, FAILURE_STATUS_ASSERT, FAILED_LIST_ASSERT, ENDPOINT_DOWN, NAVIGATION_RESPONSE_VAR, DIRECT_REQUEST_VAR, CATCH_ALL_PATTERN, GUARD_HANDLER, ABSENCE_MATCHER, ERROR_TEXT_WORDS, TEXT_REGEX_ARG, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES, POLL_CALLBACK, CONSTANT_WORD, CONSTANT_GLUE_CHARS, WORD_CHAR, MATCHER_ARGS_MAX, MATCHER_OPEN, ENTERED_VALUE, PAGE_NAV, IN_FLIGHT_TEXT, FORM_CONTROL_LOCATOR, CONTROL_VALUE_MATCHER, COPY_CONTROL, COPIED_NOTICE, SEND_CONTROL, SEND_NOTICE, MARK_READ_CONTROL, APP_DATA_FIELD;
|
|
6454
|
+
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;
|
|
5809
6455
|
var init_assertion_strength = __esm({
|
|
5810
6456
|
"../shared/dist/assertion-strength.js"() {
|
|
5811
6457
|
"use strict";
|
|
@@ -5814,6 +6460,7 @@ var init_assertion_strength = __esm({
|
|
|
5814
6460
|
init_assertion_proof();
|
|
5815
6461
|
init_auth_handoff_coverage();
|
|
5816
6462
|
init_expect_call();
|
|
6463
|
+
init_auth_plan();
|
|
5817
6464
|
ERROR_BANNER = /(client-side exception|application error|something (?:went wrong|broke)|unhandled (?:exception|error|rejection)|runtime error|error boundary|an unexpected error|internal server error|\b5\d\d\s+(?:internal\s+)?server error|stack trace|chunkloaderror|hydration failed|maximum update depth exceeded)/i;
|
|
5818
6465
|
OUTCOME_ALWAYS = [
|
|
5819
6466
|
/\.toHaveCount\s*\(/,
|
|
@@ -5830,8 +6477,8 @@ var init_assertion_strength = __esm({
|
|
|
5830
6477
|
// form field value
|
|
5831
6478
|
/\.toMatch\s*\(/,
|
|
5832
6479
|
// string/regex content match (e.g. textContent() vs /[2-9] selected/)
|
|
5833
|
-
/\.toBeDisabled\s*\(/,
|
|
5834
|
-
// a control became DISABLED — a gating/validation outcome
|
|
6480
|
+
/\.(?<!\.not\s*\.)\s*toBeDisabled\s*\(/,
|
|
6481
|
+
// a control became DISABLED — a gating/validation outcome (not.toBeDisabled is the toBeEnabled twin)
|
|
5835
6482
|
/\.not\.toBeVisible\s*\(/,
|
|
5836
6483
|
/\.toBeHidden\s*\(/
|
|
5837
6484
|
// something the action removed/closed (disappearance proof)
|
|
@@ -5839,12 +6486,16 @@ var init_assertion_strength = __esm({
|
|
|
5839
6486
|
FORM_INPUT = /\.(fill|selectOption|setInputFiles|check|uncheck|setChecked|pressSequentially)\s*\(|keyboard\.type\s*\(/;
|
|
5840
6487
|
ANY_CLICK = /\.click\s*\(/;
|
|
5841
6488
|
NAV_LOCATOR = /getByRole\s*\(\s*['"`](?:link|menuitem)['"`]/i;
|
|
5842
|
-
URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(/;
|
|
6489
|
+
URL_PROOF = /\.toHaveURL\s*\(|\.waitForURL\s*\(|\bexpect\s*\(\s*(?:await\s+)?page\s*\.\s*url\s*\(\s*\)\s*\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
|
|
6490
|
+
POLL_OPEN = /\bexpect\s*\.\s*poll\s*\(/g;
|
|
6491
|
+
POLL_PAGE_URL = /^page\s*\.\s*url\s*\(\s*\)/;
|
|
6492
|
+
POLL_PAGE_URL_ONLY = /^page\s*\.\s*url\s*\(\s*\)$/;
|
|
6493
|
+
POLL_CALLS_MAX = 8;
|
|
5843
6494
|
RELOAD = /\.reload\s*\(/;
|
|
5844
6495
|
EXPECT_STMT = /\bexpect\s*[.(]/;
|
|
5845
6496
|
BLOCK_SCAFFOLD2 = /=>|\b(?:test|describe|it|beforeEach|afterEach|beforeAll|afterAll|function)\b/;
|
|
5846
6497
|
TEST_BLOCK_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow|serial|describe\.serial))?\s*\(|^(?:test\.)?(?:beforeEach|beforeAll)\s*\(/;
|
|
5847
|
-
LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s
|
|
6498
|
+
LOCATOR_BINDING = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=(?:\s*(?!\s)|\s*(?=\s(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\())(.+?(?:getByRole|getByText|getByLabel|getByPlaceholder|getByTestId|getByAltText|getByTitle|locator)\s*\(.+)$/;
|
|
5848
6499
|
EXPECT_OPEN = /\bexpect(?:\.soft)?\s*\(/;
|
|
5849
6500
|
STATE_MATCHER = /\.(toBeChecked|toBeVisible|toHaveValue|toBeDisabled|toBeEnabled)\s*\(/;
|
|
5850
6501
|
CHROME_LOCATOR = /getByRole\s*\(\s*['"`](?:heading|navigation|banner|main|contentinfo|complementary|region|search|tablist|tab|button|link|menuitem|textbox|searchbox|combobox|spinbutton)['"`]/i;
|
|
@@ -5864,16 +6515,17 @@ var init_assertion_strength = __esm({
|
|
|
5864
6515
|
EMPTY_STATE_TEXT = /\bno\b[\s\S]{0,24}?\b(yet|found|results?|items?|favou?rites?|entries|records?|matches|posts?|comments?)\b|nothing (?:here|yet|to show|found)|\bempty\b|\(\s*0\s*\)|\b0\s+(?:favou?rites?|results?|items?|entries|records?|matches|posts?|comments?)\b/i;
|
|
5865
6516
|
AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
|
|
5866
6517
|
AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
|
|
5867
|
-
LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s
|
|
6518
|
+
LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*(?:;\s*)?$/;
|
|
5868
6519
|
INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
|
|
5869
6520
|
INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
|
|
5870
6521
|
NEGATED_ASSERT = /\.not\s*\.\s*to[A-Z]\w*|\.toBeHidden\s*\(/;
|
|
6522
|
+
NEGATED_ASSERT_NOT = /\.not\s*\.\s*to[A-Z]/;
|
|
5871
6523
|
CONST_CHAIN_MAX_DEPTH = 5;
|
|
5872
6524
|
TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
|
|
5873
6525
|
TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
|
|
5874
6526
|
FAILURE_STATUS_ASSERT = [
|
|
5875
6527
|
/\.status\s*\(\s*\)[^;]{0,200}\.(?:toBeGreaterThanOrEqual|toBeGreaterThan)\s*\(\s*(?:399|[45]\d\d)\b/,
|
|
5876
|
-
/\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\))
|
|
6528
|
+
/\bexpect\s*\(\s*[^;]{0,200}\bstatus\s*(?:\(\s*\)\s*)?>=?\s*(?:399|[45]\d\d)\b[^;]{0,200}\)\s*\.\s*(?:toBe\s*\(\s*true|toBeTruthy)/,
|
|
5877
6529
|
/\bexpect\s*\([^;]{0,200}\.ok\s*\(\s*\)\s*\)\s*\.\s*(?:toBe\s*\(\s*false\s*\)|toBeFalsy\s*\()/
|
|
5878
6530
|
];
|
|
5879
6531
|
FAILED_LIST_ASSERT = /\bexpect\s*\(\s*[^;]{0,200}(?:fail|abort|error)\w{0,40}(?:\.length)?\s*\)\s*\.\s*(?:toBeGreaterThan\s*\(\s*0|toHaveLength\s*\(\s*[1-9]|not\s*\.\s*toHaveLength\s*\(\s*0|not\s*\.\s*toEqual\s*\(\s*\[\s*\])/i;
|
|
@@ -5883,8 +6535,15 @@ var init_assertion_strength = __esm({
|
|
|
5883
6535
|
CATCH_ALL_PATTERN = /^(['"`])\*\*(?:\/\*)?\1$|^\/\.\*\/[a-z]*$/;
|
|
5884
6536
|
GUARD_HANDLER = /\bMUTATING\.has\s*\(|blockedbyclient|Read-only guard/;
|
|
5885
6537
|
ABSENCE_MATCHER = /\.not\.toBeVisible\s*\(|\.toBeHidden\s*\(|\.toHaveCount\s*\(\s*0\s*\)/;
|
|
6538
|
+
VALUE_ABSENCE = /\.(?:count\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*0\s*\)|toBeLessThan\s*\(\s*1\s*\))|isVisible\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*false\s*\)|toBeFalsy\s*\()|isHidden\s*\(\s*\)[\s)]{0,10}\.(?:(?:toBe|toEqual|toStrictEqual)\s*\(\s*true\s*\)|toBeTruthy\s*\())/;
|
|
6539
|
+
OPTION_ABSENCE = /\.(?:toBeVisible\s*\(\s*\{[^{}()]{0,200}\bvisible|toBeAttached\s*\(\s*\{[^{}()]{0,200}\battached)\s*:\s*false\b/;
|
|
6540
|
+
NOT_BEFORE_MATCHER = /\.not\s*\.\s*(?=to[A-Z])/g;
|
|
5886
6541
|
ERROR_TEXT_WORDS = /error|fail|wrong|try again|unable|could ?n.?t|can.?t|problem|invalid|unavailable/i;
|
|
5887
6542
|
TEXT_REGEX_ARG = /(?:getByText|hasText|getByRole\([^)]{0,200}name)\s*[:(]\s*(\/(?:\\.|[^/\\\n])+\/[a-z]*)/;
|
|
6543
|
+
SCOPING_STEP = /(?:\s*\.\s*(?:first|last|nth)\s*\([^()]*\))*\s*\.\s*(?:getBy[A-Z]\w*|locator|filter)\s*\(/y;
|
|
6544
|
+
NEGATED_TEXT = /\.not\s*\.\s*(?:toHaveText|toContainText)\s*\(\s*(?:['"`]([^'"`]*)['"`]|\/((?:\\.|[^/\\\n])+)\/)?/;
|
|
6545
|
+
ASYNC_DESTRUCTURE_OPEN = /async\s*\(\s*\{/g;
|
|
6546
|
+
REQUEST_WORD = /\brequest\b/;
|
|
5888
6547
|
ROLE_NAME_RE = /getByRole\s*\(\s*['"`][a-z]+['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
|
|
5889
6548
|
INTERACT_VERB = /\.(click|fill|check|uncheck|setChecked|selectOption|pressSequentially)\s*\(/;
|
|
5890
6549
|
ROLE_SIG_RE = /getByRole\s*\(\s*['"`]([a-z]+)['"`]\s*,\s*\{[^}]*?name\s*:\s*(?:['"`]([^'"`]+)['"`]|\/([^/]+)\/)/gi;
|
|
@@ -5905,21 +6564,64 @@ var init_assertion_strength = __esm({
|
|
|
5905
6564
|
MATCHER_OPEN = /\s*(?:\.\s*(?:not|resolves|rejects)\s*)*\.\s*[A-Za-z_$][\w$]*\s*\(/y;
|
|
5906
6565
|
ENTERED_VALUE = /\.(?:fill|pressSequentially)\s*\(\s*['"`]([^'"`]+)['"`]|keyboard\.type\s*\(\s*['"`]([^'"`]+)['"`]|\.selectOption\s*\(\s*(?:\[\s*)?(?:\{\s*(?:label|value)\s*:\s*)?['"`]([^'"`]+)['"`]/g;
|
|
5907
6566
|
PAGE_NAV = /\.(?:goto|reload)\s*\(/;
|
|
6567
|
+
ENV_READ = String.raw`process\.env(?:(?:\?\.|\.)([A-Za-z_]\w*)|(?:\?\.)?\[\s*['"]([A-Za-z_]\w*)['"]\s*\])`;
|
|
6568
|
+
ENTERED_ENV = new RegExp(String.raw`\.(?:fill|pressSequentially)\s*\(\s*${ENV_READ}|keyboard\.type\s*\(\s*${ENV_READ}`, "g");
|
|
6569
|
+
HAVE_VALUE_ENV = new RegExp(String.raw`\.toHaveValue\s*\(\s*${ENV_READ}`);
|
|
6570
|
+
ACCOUNT_KEYS = TEST_USER_ENV_KEYS.join("|");
|
|
6571
|
+
ACCOUNT_ENV = `process\\.env(?:\\??\\.(?:${ACCOUNT_KEYS})\\b|(?:\\?\\.)?\\[\\s*['"\`](?:${ACCOUNT_KEYS})['"\`]\\s*\\])`;
|
|
6572
|
+
ACCOUNT_CONST = new RegExp(`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*(?::\\s*string\\s*)?=\\s*${ACCOUNT_ENV}`, "g");
|
|
5908
6573
|
IN_FLIGHT_TEXT = /^(?:(?:sending|saving|loading|searching|checking|processing|submitting|preparing|updating|please wait|working|one moment)\b[^.]{0,40}\.{2,3}|\.{3}|…)$/i;
|
|
6574
|
+
LOCATOR_VALUE_READ = /\.(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts|evaluate|evaluateAll|boundingBox|all)\s*\(/;
|
|
6575
|
+
LOCATOR_EXPR = /^(?:this\.)?page\s*\.\s*(?:getBy\w+|locator)\s*\(/;
|
|
6576
|
+
ALWAYS_TRUE_ON_OBJECT = /^\)\s*\.\s*(?:toBeTruthy|toBeDefined|not\s*\.\s*(?:toBeNull|toBeUndefined|toBeFalsy))\s*\(/;
|
|
6577
|
+
COUNT_AT_LEAST_ZERO = /\.(?:count\s*\(\s*\)|length)[\s)]{0,10}\.(?:toBeGreaterThanOrEqual\s*\(\s*0\s*\)|toBeGreaterThan\s*\(\s*-\s*1\s*\))/;
|
|
6578
|
+
DEFINED_VALUE_READ = /\.\s*(?:count|textContent|innerText|innerHTML|inputValue|getAttribute|title|url|isVisible|isHidden|isChecked|isEnabled|isDisabled|isEditable|allTextContents|allInnerTexts)\s*\([^()]*\)$/;
|
|
6579
|
+
ALWAYS_DEFINED = /^\)\s*\.\s*(?:toBeDefined|not\s*\.\s*toBeUndefined)\s*\(/;
|
|
6580
|
+
SELF_COMPARE_MATCHER = /^\)\s*\.\s*(toBe|toEqual|toStrictEqual|toHaveCount)\s*\(/;
|
|
6581
|
+
IS_VISIBLE_TRUE = /^\)*\s*\.\s*(?:toBe\s*\(\s*true\s*\)|toBeTruthy\s*\(\s*\))/;
|
|
6582
|
+
PAGE_VALUE_MATCHER = /^\)\s*\.\s*(?:toBe|toEqual|toStrictEqual|toMatch|toContain)\s*\(/;
|
|
6583
|
+
COUNT_ONE = /\.\s*toHaveCount\s*\(\s*1\s*\)/;
|
|
6584
|
+
URL_CHECK_ARG = /\bpage\s*\.\s*waitForURL\s*\(\s*([^,)]+)|\bexpect(?:\s*\.\s*soft)?\s*\(\s*page\s*\)\s*\.\s*toHaveURL\s*\(\s*([^,)]+)/;
|
|
6585
|
+
PLAIN_PAGE_PATH = /^\/[\w\-./~%]*(?:[?#][^\s]*)?$/;
|
|
5909
6586
|
FORM_CONTROL_LOCATOR = /getByRole\s*\(\s*['"`](?:combobox|switch|checkbox|radio|textbox|searchbox|spinbutton|slider)['"`]|getByLabel\s*\(|getByPlaceholder\s*\(/;
|
|
5910
6587
|
CONTROL_VALUE_MATCHER = /\.(?:toHaveValue|toHaveText|toContainText)\s*\(|\.toHaveAttribute\s*\(\s*['"`]aria-checked['"`]/;
|
|
6588
|
+
ACCOUNT_EMAIL_ENV = /^process\.env(?:(?:\?\.|\.)TEST_USER_EMAIL\b|(?:\?\.)?\[\s*['"]TEST_USER_EMAIL['"]\s*\])/;
|
|
6589
|
+
ENV_PASSWORD_ENTRY = /\.(?:fill|pressSequentially)\s*\(\s*process\.env(?:(?:\?\.|\.)TEST_USER_PASSWORD\b|(?:\?\.)?\[\s*['"]TEST_USER_PASSWORD['"]\s*\])/;
|
|
6590
|
+
ACCOUNT_CONTROL_ROLE = /getByRole\s*\(\s*['"`](?:button|link|menuitem)['"`]\s*,\s*\{/;
|
|
6591
|
+
ASSERT_OR_WAIT = /^(?:await\s+)?(?:expect(?:\.soft|\.poll)?\s*\(|[\w$]+(?:\.[\w$]+)*\.waitFor[A-Za-z]*\s*\()/;
|
|
6592
|
+
ACTS = /\.(?:click|dblclick|tap|press|pressSequentially|fill|clear|type|check|uncheck|setChecked|selectOption|setInputFiles|dispatchEvent|hover|focus|dragTo|goto|reload|goBack|goForward|evaluate|evaluateHandle)\s*\(|\b(?:keyboard|mouse|touchscreen)\s*\.|\.toPass\s*\(/;
|
|
6593
|
+
actLinesOf = /* @__PURE__ */ new WeakMap();
|
|
6594
|
+
CLOSERS_ONLY = /^[})\];,\s]+$/;
|
|
6595
|
+
TEST_BODY_OPEN = /^(?:test|it)(?:\.(?:only|skip|fixme|slow))?\s*\(/;
|
|
5911
6596
|
COPY_CONTROL = /\bcopy\b/i;
|
|
5912
6597
|
COPIED_NOTICE = /\bcopied\b/i;
|
|
5913
6598
|
SEND_CONTROL = /\b(send|submit|subscribe|sign up for|contact|feedback|join (the )?waitlist)\b/i;
|
|
5914
6599
|
SEND_NOTICE = /\b(thank|thanks|sent|subscribed|received|in touch)\b/i;
|
|
5915
6600
|
MARK_READ_CONTROL = /\bmark(?:ed)? as read\b|\bmark all(?: as)? read\b/i;
|
|
5916
6601
|
APP_DATA_FIELD = /\b(title|name of|amount|price|quantity|sku|status|due|assignee|project|ticket|invoice|order)\b/i;
|
|
6602
|
+
COMMIT_CONTROL = /\b(?:save|submit|update|apply|add|create|post|send|publish)\b|\bconfirm\s+changes\b/i;
|
|
6603
|
+
NOT_COMMIT_CONTROL = /\b(?:delete|remove|cancel|close|dismiss|discard|back|sign\s*out|log\s*out|logout|done|ok|got\s+it|export|download)\b/i;
|
|
6604
|
+
DESTRUCTIVE_CONTROL = /\b(?:delete|remove|discard)\b/i;
|
|
6605
|
+
ENTER_PRESS = /\.press\s*\(\s*['"`]Enter['"`]/;
|
|
6606
|
+
CONTROL_LABEL = /(?:name\s*:\s*|getBy(?:Text|Label|Title|TestId)\s*\(\s*|has-text\(\s*)(?:(['"`])((?:(?!\1).)*)\1|\/((?:\\.|[^/\\])+)\/)/g;
|
|
6607
|
+
isCommitClick = (stmt) => {
|
|
6608
|
+
const label = clickLabel(stmt);
|
|
6609
|
+
return label !== null && COMMIT_CONTROL.test(label) && !NOT_COMMIT_CONTROL.test(label) && !EXCLUDE_AFFORDANCE.test(label);
|
|
6610
|
+
};
|
|
6611
|
+
isDestructiveClick = (stmt) => {
|
|
6612
|
+
const label = clickLabel(stmt);
|
|
6613
|
+
return label !== null && DESTRUCTIVE_CONTROL.test(label);
|
|
6614
|
+
};
|
|
6615
|
+
TEARDOWN_OPEN = /\bfinally\s*\{|^(?:test\.)?(?:after|before)(?:Each|All)\s*\(/;
|
|
5917
6616
|
}
|
|
5918
6617
|
});
|
|
5919
6618
|
|
|
5920
6619
|
// ../shared/dist/run-classification.js
|
|
5921
|
-
function
|
|
5922
|
-
|
|
6620
|
+
function isCancelledReason(error) {
|
|
6621
|
+
if (typeof error !== "string")
|
|
6622
|
+
return false;
|
|
6623
|
+
const text = error.trim();
|
|
6624
|
+
return CANCELLED_REASON_PREFIXES.some((prefix) => text.startsWith(prefix));
|
|
5923
6625
|
}
|
|
5924
6626
|
function isHeldHealRun(run) {
|
|
5925
6627
|
return run?.status === "PASSED" && typeof run.error === "string" && run.error.startsWith(HEAL_PATCH_HELD_RUN_ERROR_PREFIX);
|
|
@@ -5952,7 +6654,7 @@ function pickLatestHealthRun(runs) {
|
|
|
5952
6654
|
for (const run of runs) {
|
|
5953
6655
|
if (run.mode === "auth-setup")
|
|
5954
6656
|
continue;
|
|
5955
|
-
if (run.status === "ERROR" &&
|
|
6657
|
+
if (run.status === "ERROR" && isCancelledReason(run.error))
|
|
5956
6658
|
continue;
|
|
5957
6659
|
if (isHeldHealRun(run))
|
|
5958
6660
|
continue;
|
|
@@ -5970,6 +6672,15 @@ function pickDisplayRun(runs) {
|
|
|
5970
6672
|
return latest;
|
|
5971
6673
|
return pickLatestHealthRun(runs) ?? latest;
|
|
5972
6674
|
}
|
|
6675
|
+
function featureRowRunStatus(runs) {
|
|
6676
|
+
const latest = runs.find((run) => run.mode !== "auth-setup");
|
|
6677
|
+
if (!latest)
|
|
6678
|
+
return null;
|
|
6679
|
+
const decisive = pickLatestHealthRun(runs.filter((run) => !isEnvironmentalError(run)));
|
|
6680
|
+
if (decisive)
|
|
6681
|
+
return decisive.status;
|
|
6682
|
+
return latest.status === "PENDING" || latest.status === "RUNNING" ? latest.status : null;
|
|
6683
|
+
}
|
|
5973
6684
|
function isEnvironmentalError(run) {
|
|
5974
6685
|
if (!run)
|
|
5975
6686
|
return false;
|
|
@@ -6001,7 +6712,7 @@ function classifyErrorMessage(error) {
|
|
|
6001
6712
|
return "config_issue";
|
|
6002
6713
|
if (EMAIL_NOT_RECEIVED_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
|
|
6003
6714
|
return "email_not_received";
|
|
6004
|
-
if (
|
|
6715
|
+
if (isCancelledReason(error))
|
|
6005
6716
|
return "cancelled";
|
|
6006
6717
|
if (isRetriedRunInterruption(error))
|
|
6007
6718
|
return "interrupted";
|
|
@@ -6036,7 +6747,7 @@ function heldHealRunWhere() {
|
|
|
6036
6747
|
function excludeCancelledRunsWhere() {
|
|
6037
6748
|
return {
|
|
6038
6749
|
NOT: [
|
|
6039
|
-
{ status: "ERROR", error: { not: null, startsWith:
|
|
6750
|
+
{ status: "ERROR", OR: CANCELLED_REASON_PREFIXES.map((prefix) => ({ error: { not: null, startsWith: prefix } })) },
|
|
6040
6751
|
heldHealRunWhere()
|
|
6041
6752
|
]
|
|
6042
6753
|
};
|
|
@@ -6114,11 +6825,25 @@ function reportedBugWhere() {
|
|
|
6114
6825
|
function bugReviewStatus(status) {
|
|
6115
6826
|
return status === "QUARANTINED" ? "confirmed" : "needs-review";
|
|
6116
6827
|
}
|
|
6117
|
-
var USER_CANCELLED_RUN_ERROR, HEAL_PATCH_HELD_RUN_ERROR_PREFIX, RUNNER_INTERRUPT_ERROR_PREFIX, SERVER_INTERRUPT_ERROR_PREFIX, RUN_LOST_ERROR_PREFIX, RUN_INTERRUPTED_ERROR_PREFIXES, RUN_INTERRUPTION_RETRIED_MARKER, RUN_INTERRUPTION_NOT_RETRIED_MARKERS, BUG_ASSERTION_TAG, UNEXPECTED_PASS_ERROR_PREFIX, BUG_TEST_BROKEN_ERROR_PREFIX, BUG_TEST_BROKEN_REASON, EMAIL_NOT_RECEIVED_PREFIXES, ENVIRONMENTAL_ERROR_PREFIXES, ENVIRONMENTAL_ERROR_STORED_PREFIXES, BUG_SURFACE_SUPPRESSING_TAGS, QUARANTINE_CLOSE_TAGS, BUG_CANDIDATE_TAG, BUG_TEST_BROKEN_TAG;
|
|
6828
|
+
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;
|
|
6118
6829
|
var init_run_classification = __esm({
|
|
6119
6830
|
"../shared/dist/run-classification.js"() {
|
|
6120
6831
|
"use strict";
|
|
6121
6832
|
USER_CANCELLED_RUN_ERROR = "Cancelled by user";
|
|
6833
|
+
PLATFORM_SUPPORT_CANCELLED_RUN_ERROR = "Cancelled by platform support";
|
|
6834
|
+
PLATFORM_ADMIN_CANCELLED_RUN_ERROR = "Cancelled by platform admin";
|
|
6835
|
+
ADMIN_FORCE_CANCELLED_RUN_ERROR = "Admin force-cancelled all active runs";
|
|
6836
|
+
CHAIN_CANCELLED_RUN_ERROR = "Cancelled: parent run cancelled";
|
|
6837
|
+
RETRY_HELD_RUN_ERROR = "Cancelled: auto generation paused";
|
|
6838
|
+
CANCELLED_REASON_PREFIXES = [
|
|
6839
|
+
USER_CANCELLED_RUN_ERROR,
|
|
6840
|
+
PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
|
|
6841
|
+
PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
|
|
6842
|
+
ADMIN_FORCE_CANCELLED_RUN_ERROR,
|
|
6843
|
+
CHAIN_CANCELLED_RUN_ERROR,
|
|
6844
|
+
RETRY_HELD_RUN_ERROR
|
|
6845
|
+
];
|
|
6846
|
+
isUserCancelledError = isCancelledReason;
|
|
6122
6847
|
HEAL_PATCH_HELD_RUN_ERROR_PREFIX = "Heal patch held";
|
|
6123
6848
|
RUNNER_INTERRUPT_ERROR_PREFIX = "Runner shutting down";
|
|
6124
6849
|
SERVER_INTERRUPT_ERROR_PREFIX = "Run interrupted by server restart";
|
|
@@ -6134,6 +6859,7 @@ var init_run_classification = __esm({
|
|
|
6134
6859
|
UNEXPECTED_PASS_ERROR_PREFIX = "UNEXPECTED_PASS:";
|
|
6135
6860
|
BUG_TEST_BROKEN_ERROR_PREFIX = "BUG_TEST_BROKEN:";
|
|
6136
6861
|
BUG_TEST_BROKEN_REASON = "bug test broken (not a bug verdict)";
|
|
6862
|
+
TEST_DID_NOT_RUN_ERROR = "Test did not run (a dependency failed or the run was interrupted)";
|
|
6137
6863
|
EMAIL_NOT_RECEIVED_PREFIXES = [
|
|
6138
6864
|
"VERIFICATION_EMAIL_NOT_RECEIVED:",
|
|
6139
6865
|
"VERIFICATION_EMAIL_HAS_NO_CODE:",
|
|
@@ -6146,9 +6872,10 @@ var init_run_classification = __esm({
|
|
|
6146
6872
|
];
|
|
6147
6873
|
ENVIRONMENTAL_ERROR_STORED_PREFIXES = [
|
|
6148
6874
|
...ENVIRONMENTAL_ERROR_PREFIXES.flatMap((prefix) => [prefix, `Error: ${prefix}`]),
|
|
6149
|
-
// Written by the
|
|
6150
|
-
|
|
6875
|
+
// Written by the cancels as is, with no prefix wrapper.
|
|
6876
|
+
...CANCELLED_REASON_PREFIXES
|
|
6151
6877
|
];
|
|
6878
|
+
FEATURE_ROW_RUN_WINDOW = 10;
|
|
6152
6879
|
BUG_SURFACE_SUPPRESSING_TAGS = ["@rate-limit-blocked", "@quota-exceeded", "@auth-lost"];
|
|
6153
6880
|
QUARANTINE_CLOSE_TAGS = [
|
|
6154
6881
|
"@bug-found",
|
|
@@ -8581,180 +9308,6 @@ var init_admin_view_return_to = __esm({
|
|
|
8581
9308
|
}
|
|
8582
9309
|
});
|
|
8583
9310
|
|
|
8584
|
-
// ../shared/dist/auth-plan.js
|
|
8585
|
-
function hasInlineLoginSteps(playwrightCode) {
|
|
8586
|
-
if (!playwrightCode)
|
|
8587
|
-
return false;
|
|
8588
|
-
return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
|
|
8589
|
-
}
|
|
8590
|
-
function hasGeneratedAuthGuard(playwrightCode) {
|
|
8591
|
-
if (!playwrightCode)
|
|
8592
|
-
return false;
|
|
8593
|
-
return /\/\/ >>> VALIDATE_QA_AUTH v(\d+)\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\1\b/.test(playwrightCode);
|
|
8594
|
-
}
|
|
8595
|
-
function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled) {
|
|
8596
|
-
if (!sharedAuthInjectEnabled)
|
|
8597
|
-
return false;
|
|
8598
|
-
if (tags.includes("@auth"))
|
|
8599
|
-
return false;
|
|
8600
|
-
if (tags.includes("@api"))
|
|
8601
|
-
return true;
|
|
8602
|
-
if (!tags.includes("@needs-auth"))
|
|
8603
|
-
return false;
|
|
8604
|
-
if (sharedAuthInjectEnabled && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
|
|
8605
|
-
return true;
|
|
8606
|
-
const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
|
|
8607
|
-
const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
|
|
8608
|
-
return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
|
|
8609
|
-
}
|
|
8610
|
-
function codeReadsStoredCredentials(code) {
|
|
8611
|
-
if (!code)
|
|
8612
|
-
return false;
|
|
8613
|
-
if (/__vqaLogin\b/.test(code))
|
|
8614
|
-
return true;
|
|
8615
|
-
for (const m of code.matchAll(/TEST_[A-Z0-9_]+/g)) {
|
|
8616
|
-
if (/_(?:EMAIL|PASSWORD)/.test(m[0]))
|
|
8617
|
-
return true;
|
|
8618
|
-
}
|
|
8619
|
-
for (const m of code.matchAll(/process\.env\s*\[([^\]]*)\]?/g)) {
|
|
8620
|
-
if (!/^\s*(['"])[^'"`]*\1\s*$/.test(m[1]))
|
|
8621
|
-
return true;
|
|
8622
|
-
}
|
|
8623
|
-
return false;
|
|
8624
|
-
}
|
|
8625
|
-
function explicitAuthTypeTags(t) {
|
|
8626
|
-
const found = /* @__PURE__ */ new Set();
|
|
8627
|
-
for (const tag of t) {
|
|
8628
|
-
if (AUTH_TYPE_TAGS.includes(tag))
|
|
8629
|
-
found.add(tag);
|
|
8630
|
-
else if (Object.hasOwn(AUTH_TAG_ALIASES, tag))
|
|
8631
|
-
found.add(AUTH_TAG_ALIASES[tag]);
|
|
8632
|
-
}
|
|
8633
|
-
return [...found];
|
|
8634
|
-
}
|
|
8635
|
-
function invalidPlan(configurationError) {
|
|
8636
|
-
return {
|
|
8637
|
-
version: 1,
|
|
8638
|
-
source: "invalid",
|
|
8639
|
-
acquisition: "unsupported",
|
|
8640
|
-
presentation: "none",
|
|
8641
|
-
needsCookieState: false,
|
|
8642
|
-
needsStandingHeaders: false,
|
|
8643
|
-
standingHeadersMode: "none",
|
|
8644
|
-
needsLoginCreds: false,
|
|
8645
|
-
requiredEnvKeys: [],
|
|
8646
|
-
configurationError
|
|
8647
|
-
};
|
|
8648
|
-
}
|
|
8649
|
-
function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
|
|
8650
|
-
return {
|
|
8651
|
-
version: 1,
|
|
8652
|
-
source: "explicit",
|
|
8653
|
-
authTypeTag: authTypeTag2,
|
|
8654
|
-
acquisition,
|
|
8655
|
-
presentation,
|
|
8656
|
-
needsCookieState: opts.cookie,
|
|
8657
|
-
needsStandingHeaders: opts.standingMode === "required",
|
|
8658
|
-
standingHeadersMode: opts.standingMode,
|
|
8659
|
-
needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
|
|
8660
|
-
requiredEnvKeys: opts.requiredEnvKeys
|
|
8661
|
-
};
|
|
8662
|
-
}
|
|
8663
|
-
function deriveAuthPlan(tags, code, opts) {
|
|
8664
|
-
const t = Array.isArray(tags) ? tags : [];
|
|
8665
|
-
const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
|
|
8666
|
-
const isApi = t.includes("@api");
|
|
8667
|
-
const explicit = isApi ? explicitAuthTypeTags(t) : [];
|
|
8668
|
-
if (explicit.length > 0) {
|
|
8669
|
-
if (t.includes("@auth")) {
|
|
8670
|
-
return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
|
|
8671
|
-
}
|
|
8672
|
-
const loginCompose = explicit.filter((e) => e === AUTH_TAG_COOKIE_STATE || e === AUTH_TAG_LOGIN_COOKIE || e === AUTH_TAG_LOGIN_BEARER);
|
|
8673
|
-
if (explicit.length === 2 && explicit.includes(AUTH_TAG_STANDING) && loginCompose.length === 1) {
|
|
8674
|
-
const composedKeys = [...TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY];
|
|
8675
|
-
switch (loginCompose[0]) {
|
|
8676
|
-
case AUTH_TAG_COOKIE_STATE:
|
|
8677
|
-
return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "required", requiredEnvKeys: composedKeys });
|
|
8678
|
-
case AUTH_TAG_LOGIN_COOKIE:
|
|
8679
|
-
return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
|
|
8680
|
-
case AUTH_TAG_LOGIN_BEARER:
|
|
8681
|
-
return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "required", requiredEnvKeys: composedKeys });
|
|
8682
|
-
}
|
|
8683
|
-
}
|
|
8684
|
-
if (explicit.length > 1) {
|
|
8685
|
-
return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
|
|
8686
|
-
}
|
|
8687
|
-
switch (explicit[0]) {
|
|
8688
|
-
case AUTH_TAG_UNSUPPORTED:
|
|
8689
|
-
return {
|
|
8690
|
-
...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
|
|
8691
|
-
};
|
|
8692
|
-
case AUTH_TAG_NONE:
|
|
8693
|
-
return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
|
|
8694
|
-
case AUTH_TAG_STANDING:
|
|
8695
|
-
return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
|
|
8696
|
-
case AUTH_TAG_LOGIN_BEARER:
|
|
8697
|
-
return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
8698
|
-
case AUTH_TAG_LOGIN_COOKIE:
|
|
8699
|
-
return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
8700
|
-
case AUTH_TAG_COOKIE_STATE:
|
|
8701
|
-
return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: sharedFlag, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
8702
|
-
}
|
|
8703
|
-
}
|
|
8704
|
-
const cookie = legacyNeedsCookieState(t, code, sharedFlag);
|
|
8705
|
-
const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
|
|
8706
|
-
const selfLoginWithoutStoredCreds = t.includes("@auth") && !t.includes("@needs-auth") && !isApi && !!code?.trim() && !/TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin\b/.test(code);
|
|
8707
|
-
const apiWithoutStoredCreds = isApi && !t.includes("@auth") && !t.includes("@needs-auth") && !!code?.trim() && !codeReadsStoredCredentials(code);
|
|
8708
|
-
const needsLoginCreds = authedLegacy && !selfLoginWithoutStoredCreds && !apiWithoutStoredCreds;
|
|
8709
|
-
return {
|
|
8710
|
-
version: 1,
|
|
8711
|
-
source: "legacy",
|
|
8712
|
-
acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
|
|
8713
|
-
presentation: authedLegacy ? "cookie" : "none",
|
|
8714
|
-
needsCookieState: cookie,
|
|
8715
|
-
needsStandingHeaders: false,
|
|
8716
|
-
// A legacy bare-@api test keeps injecting standing headers IF the project configured them
|
|
8717
|
-
// (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
|
|
8718
|
-
standingHeadersMode: isApi ? "legacy-if-configured" : "none",
|
|
8719
|
-
needsLoginCreds,
|
|
8720
|
-
requiredEnvKeys: needsLoginCreds ? [...TEST_USER_ENV_KEYS] : []
|
|
8721
|
-
};
|
|
8722
|
-
}
|
|
8723
|
-
function authTypeTag(tags) {
|
|
8724
|
-
const t = Array.isArray(tags) ? tags : [];
|
|
8725
|
-
const explicit = explicitAuthTypeTags(t);
|
|
8726
|
-
return explicit.length === 1 ? explicit[0] : void 0;
|
|
8727
|
-
}
|
|
8728
|
-
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;
|
|
8729
|
-
var init_auth_plan = __esm({
|
|
8730
|
-
"../shared/dist/auth-plan.js"() {
|
|
8731
|
-
"use strict";
|
|
8732
|
-
AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
|
|
8733
|
-
AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
|
|
8734
|
-
AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
|
|
8735
|
-
AUTH_TAG_STANDING = "@auth-standing";
|
|
8736
|
-
AUTH_TAG_NONE = "@auth-none";
|
|
8737
|
-
AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
|
|
8738
|
-
AUTH_TYPE_TAGS = [
|
|
8739
|
-
AUTH_TAG_COOKIE_STATE,
|
|
8740
|
-
AUTH_TAG_LOGIN_COOKIE,
|
|
8741
|
-
AUTH_TAG_LOGIN_BEARER,
|
|
8742
|
-
AUTH_TAG_STANDING,
|
|
8743
|
-
AUTH_TAG_NONE,
|
|
8744
|
-
AUTH_TAG_UNSUPPORTED
|
|
8745
|
-
];
|
|
8746
|
-
AUTH_TAG_ALIASES = {
|
|
8747
|
-
"@auth-cookie": AUTH_TAG_COOKIE_STATE,
|
|
8748
|
-
"@auth-bearer": AUTH_TAG_LOGIN_BEARER,
|
|
8749
|
-
"@auth-token": AUTH_TAG_LOGIN_BEARER,
|
|
8750
|
-
"@auth-api-key": AUTH_TAG_STANDING,
|
|
8751
|
-
"@auth-standing-header": AUTH_TAG_STANDING
|
|
8752
|
-
};
|
|
8753
|
-
TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
|
|
8754
|
-
API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
|
|
8755
|
-
}
|
|
8756
|
-
});
|
|
8757
|
-
|
|
8758
9311
|
// ../shared/dist/auth-block-detector.js
|
|
8759
9312
|
function stripEchoLines(text) {
|
|
8760
9313
|
return text.split("\n").filter((line) => !ECHO_LINE_RE.test(line)).join("\n");
|
|
@@ -8762,13 +9315,16 @@ function stripEchoLines(text) {
|
|
|
8762
9315
|
function matchesAny(text, patterns) {
|
|
8763
9316
|
return patterns.some((p) => p.test(text));
|
|
8764
9317
|
}
|
|
9318
|
+
function isIntentionalNegativeAuthTest(input) {
|
|
9319
|
+
const meta = [input.testName, input.description].filter(Boolean).join(" ");
|
|
9320
|
+
return matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH);
|
|
9321
|
+
}
|
|
8765
9322
|
function detectAuthBlock(input) {
|
|
8766
9323
|
const tags = input.tags ?? [];
|
|
8767
9324
|
const isAuthed = tags.some((t) => AUTHED_TAGS.includes(t));
|
|
8768
9325
|
if (!isAuthed)
|
|
8769
9326
|
return NO_DETECTION;
|
|
8770
|
-
|
|
8771
|
-
if (matchesAny(meta, INTENTIONAL_NEGATIVE_AUTH))
|
|
9327
|
+
if (isIntentionalNegativeAuthTest(input))
|
|
8772
9328
|
return NO_DETECTION;
|
|
8773
9329
|
const sources = [];
|
|
8774
9330
|
if (input.error)
|
|
@@ -8871,7 +9427,24 @@ var init_auth_block_detector = __esm({
|
|
|
8871
9427
|
/\b(reject|deny|denied|block(ed)?)\b[^\n]{0,20}\b(login|sign[\s-]?in|auth|credential|password)\b/i
|
|
8872
9428
|
];
|
|
8873
9429
|
ECHO_LINE_RE = /^\s*(?:(?:📋|🎭|🌐|⚙️?)|(?:stderr:\s*|stdout:\s*)?Locator:)/u;
|
|
8874
|
-
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
|
|
9430
|
+
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;
|
|
9431
|
+
}
|
|
9432
|
+
});
|
|
9433
|
+
|
|
9434
|
+
// ../shared/dist/auth-session-stamp.js
|
|
9435
|
+
function parseAuthSessionStamp(value) {
|
|
9436
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
9437
|
+
return null;
|
|
9438
|
+
const { shared, role, generation } = value;
|
|
9439
|
+
if (shared !== true || role !== "producer" && role !== "follower")
|
|
9440
|
+
return null;
|
|
9441
|
+
return typeof generation === "string" && AUTH_SESSION_GENERATION_RE.test(generation) ? { shared: true, role, generation } : { shared: true, role };
|
|
9442
|
+
}
|
|
9443
|
+
var AUTH_SESSION_GENERATION_RE;
|
|
9444
|
+
var init_auth_session_stamp = __esm({
|
|
9445
|
+
"../shared/dist/auth-session-stamp.js"() {
|
|
9446
|
+
"use strict";
|
|
9447
|
+
AUTH_SESSION_GENERATION_RE = /^[0-9a-z]{1,16}$/;
|
|
8875
9448
|
}
|
|
8876
9449
|
});
|
|
8877
9450
|
|
|
@@ -9795,12 +10368,70 @@ function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
|
|
|
9795
10368
|
function isAuthReadinessGuardEnabled(value = process.env.AUTH_READINESS_GUARD) {
|
|
9796
10369
|
return value?.trim().toLowerCase() !== "false";
|
|
9797
10370
|
}
|
|
10371
|
+
function visualBaselinesEnabled(projectId, raw = process.env.VISUAL_BASELINES_PROJECTS) {
|
|
10372
|
+
if (typeof projectId !== "string" || projectId.trim() === "")
|
|
10373
|
+
return false;
|
|
10374
|
+
const listed = (raw ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
10375
|
+
return listed.includes("*") || listed.includes(projectId);
|
|
10376
|
+
}
|
|
9798
10377
|
var init_feature_flags = __esm({
|
|
9799
10378
|
"../shared/dist/feature-flags.js"() {
|
|
9800
10379
|
"use strict";
|
|
9801
10380
|
}
|
|
9802
10381
|
});
|
|
9803
10382
|
|
|
10383
|
+
// ../shared/dist/visual-compare-limits.js
|
|
10384
|
+
function isSafeMaskSelector(selector) {
|
|
10385
|
+
if (typeof selector !== "string")
|
|
10386
|
+
return false;
|
|
10387
|
+
if (selector.trim().length === 0)
|
|
10388
|
+
return false;
|
|
10389
|
+
if (selector.length > VISUAL_MASK_SELECTOR_MAX_LENGTH)
|
|
10390
|
+
return false;
|
|
10391
|
+
return !VISUAL_MASK_SELECTOR_FORBIDDEN.test(selector);
|
|
10392
|
+
}
|
|
10393
|
+
var VISUAL_MASK_SELECTORS_MAX, VISUAL_MASK_SELECTOR_MAX_LENGTH, VISUAL_MASK_SELECTOR_FORBIDDEN;
|
|
10394
|
+
var init_visual_compare_limits = __esm({
|
|
10395
|
+
"../shared/dist/visual-compare-limits.js"() {
|
|
10396
|
+
"use strict";
|
|
10397
|
+
VISUAL_MASK_SELECTORS_MAX = 50;
|
|
10398
|
+
VISUAL_MASK_SELECTOR_MAX_LENGTH = 300;
|
|
10399
|
+
VISUAL_MASK_SELECTOR_FORBIDDEN = /[{};@<\r\n]/;
|
|
10400
|
+
}
|
|
10401
|
+
});
|
|
10402
|
+
|
|
10403
|
+
// ../shared/dist/visual-baseline-identity.js
|
|
10404
|
+
function isBrowserVersion(value) {
|
|
10405
|
+
return typeof value === "string" && value.length <= 32 && BROWSER_VERSION.test(value);
|
|
10406
|
+
}
|
|
10407
|
+
function renderIdentity(flavour, version, image) {
|
|
10408
|
+
if (!/^[a-z][a-z-]{0,31}$/.test(flavour) || !/^[a-z][a-z-]{0,31}$/.test(image) || !isBrowserVersion(version))
|
|
10409
|
+
return null;
|
|
10410
|
+
const identity = `${flavour}/${version}/${image}`;
|
|
10411
|
+
return identity.length <= RENDER_IDENTITY_MAX_LENGTH ? identity : null;
|
|
10412
|
+
}
|
|
10413
|
+
function v1RenderIdentity(version) {
|
|
10414
|
+
return renderIdentity(VISUAL_V1_IDENTITY.flavour, version, VISUAL_V1_IDENTITY.image);
|
|
10415
|
+
}
|
|
10416
|
+
function parseRenderIdentity(value) {
|
|
10417
|
+
if (typeof value !== "string" || value.length > RENDER_IDENTITY_MAX_LENGTH)
|
|
10418
|
+
return null;
|
|
10419
|
+
const parts = value.split("/");
|
|
10420
|
+
if (parts.length !== 3)
|
|
10421
|
+
return null;
|
|
10422
|
+
const [flavour, version, image] = parts;
|
|
10423
|
+
return renderIdentity(flavour, version, image) === value ? { flavour, version, image } : null;
|
|
10424
|
+
}
|
|
10425
|
+
var VISUAL_V1_IDENTITY, RENDER_IDENTITY_MAX_LENGTH, BROWSER_VERSION;
|
|
10426
|
+
var init_visual_baseline_identity = __esm({
|
|
10427
|
+
"../shared/dist/visual-baseline-identity.js"() {
|
|
10428
|
+
"use strict";
|
|
10429
|
+
VISUAL_V1_IDENTITY = { platform: "linux", image: "runner-fast", flavour: "headless-shell" };
|
|
10430
|
+
RENDER_IDENTITY_MAX_LENGTH = 64;
|
|
10431
|
+
BROWSER_VERSION = /^\d{1,4}(\.\d{1,6}){0,3}$/;
|
|
10432
|
+
}
|
|
10433
|
+
});
|
|
10434
|
+
|
|
9804
10435
|
// ../shared/dist/analytics-url.js
|
|
9805
10436
|
function isSensitiveQueryKey(key) {
|
|
9806
10437
|
return SENSITIVE_QUERY_KEY_RE.test(key);
|
|
@@ -10103,7 +10734,7 @@ var init_agentic_cli_runtime = __esm({
|
|
|
10103
10734
|
GROK_CLI_DEFAULT_MODEL = "grok-4.7";
|
|
10104
10735
|
GROK_CLI_DEFAULT_REASONING_EFFORT = "high";
|
|
10105
10736
|
CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
|
|
10106
|
-
CLAUDE_CLI_DEFAULT_EFFORT = "
|
|
10737
|
+
CLAUDE_CLI_DEFAULT_EFFORT = "high";
|
|
10107
10738
|
MUSE_CLI_DEFAULT_MODEL = "muse-spark-1.3-contributor";
|
|
10108
10739
|
MUSE_CLI_DEFAULT_EFFORT = "high";
|
|
10109
10740
|
CODEX_CLI_DEFAULT_MODEL = "gpt-6.1-sol";
|
|
@@ -10158,6 +10789,17 @@ function openCodeGoWireModel(id) {
|
|
|
10158
10789
|
const trimmed = id?.trim() ?? "";
|
|
10159
10790
|
return isOpenCodeGoModelId(trimmed) ? trimmed.slice(OPENCODE_GO_PREFIX.length) : null;
|
|
10160
10791
|
}
|
|
10792
|
+
function isZaiModelId(id) {
|
|
10793
|
+
const trimmed = id?.trim() ?? "";
|
|
10794
|
+
return ZAI_MODEL_PATTERN.test(trimmed) && !trimmed.includes("..");
|
|
10795
|
+
}
|
|
10796
|
+
function hasZaiPrefix(id) {
|
|
10797
|
+
return (id?.trim() ?? "").startsWith(ZAI_PREFIX);
|
|
10798
|
+
}
|
|
10799
|
+
function zaiWireModel(id) {
|
|
10800
|
+
const trimmed = id?.trim() ?? "";
|
|
10801
|
+
return isZaiModelId(trimmed) ? trimmed.slice(ZAI_PREFIX.length) : null;
|
|
10802
|
+
}
|
|
10161
10803
|
function isEngineId(value) {
|
|
10162
10804
|
return CLI_ENGINE_MODEL_IDS.includes(value);
|
|
10163
10805
|
}
|
|
@@ -10254,7 +10896,7 @@ function museCliContributorMode(env = process.env) {
|
|
|
10254
10896
|
const raw = (env.MUSE_CLI_CONTRIBUTOR_MODE ?? "").trim().toLowerCase();
|
|
10255
10897
|
return raw === "off" || raw === "false" || raw === "0" || raw === "no" ? "off" : "on";
|
|
10256
10898
|
}
|
|
10257
|
-
var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_MODEL_PATTERN;
|
|
10899
|
+
var CLI_ENGINE_MODEL_IDS, BROWSER_CLI_ENGINE_IDS, PIPELINE_STEPS, CLI_ENGINE_RULES, ENGINE_NAMES, OPENROUTER_PREFIX, OPENROUTER_MODEL_PATTERN, OPENCODE_GO_PREFIX, OPENCODE_GO_MODEL_PATTERN, ZAI_PREFIX, ZAI_MODEL_PATTERN;
|
|
10258
10900
|
var init_engine_spec = __esm({
|
|
10259
10901
|
"../shared/dist/engine-spec.js"() {
|
|
10260
10902
|
"use strict";
|
|
@@ -10314,6 +10956,8 @@ var init_engine_spec = __esm({
|
|
|
10314
10956
|
OPENROUTER_MODEL_PATTERN = /^openrouter:[a-z0-9][a-z0-9._-]{0,40}\/[A-Za-z0-9._:-]{1,100}$/;
|
|
10315
10957
|
OPENCODE_GO_PREFIX = "opencode-go:";
|
|
10316
10958
|
OPENCODE_GO_MODEL_PATTERN = /^opencode-go:[a-z0-9][a-z0-9._-]{0,100}$/;
|
|
10959
|
+
ZAI_PREFIX = "zai:";
|
|
10960
|
+
ZAI_MODEL_PATTERN = /^zai:glm-[a-z0-9][a-z0-9._-]{0,60}$/;
|
|
10317
10961
|
}
|
|
10318
10962
|
});
|
|
10319
10963
|
|
|
@@ -10598,6 +11242,8 @@ var init_api_model_catalog = __esm({
|
|
|
10598
11242
|
authCaptureModel: "Login capture",
|
|
10599
11243
|
plannerModel: "Scenario Planner",
|
|
10600
11244
|
plannerGateModel: "Planner Gate",
|
|
11245
|
+
pwAgentsHealerModel: "Test Healer (Playwright agents)",
|
|
11246
|
+
pwAgentsReviewModel: "Plan Review (Playwright agents)",
|
|
10601
11247
|
generationModel: "Code Generation",
|
|
10602
11248
|
reviewModel: "Code Review",
|
|
10603
11249
|
apiTestModel: "API Test Generator",
|
|
@@ -10622,11 +11268,6 @@ var init_api_model_catalog = __esm({
|
|
|
10622
11268
|
});
|
|
10623
11269
|
|
|
10624
11270
|
// ../shared/dist/provider-chains.js
|
|
10625
|
-
function isClaudeModel(id) {
|
|
10626
|
-
if (/claude|anthropic/i.test(id))
|
|
10627
|
-
return true;
|
|
10628
|
-
return API_MODEL_CATALOG.some((m) => m.provider === "anthropic" && (m.id === id || m.apiModel === id));
|
|
10629
|
-
}
|
|
10630
11271
|
function chainKey(modelId) {
|
|
10631
11272
|
const id = canonicalModelId(modelId);
|
|
10632
11273
|
if (!id)
|
|
@@ -10634,35 +11275,334 @@ function chainKey(modelId) {
|
|
|
10634
11275
|
const entry = API_MODEL_CATALOG.find((m) => m.id === id || m.apiModel === id);
|
|
10635
11276
|
return entry?.id ?? id;
|
|
10636
11277
|
}
|
|
11278
|
+
function has(map, key) {
|
|
11279
|
+
return Object.prototype.hasOwnProperty.call(map, key);
|
|
11280
|
+
}
|
|
10637
11281
|
function providerChainFor(modelId) {
|
|
10638
11282
|
const key = chainKey(modelId);
|
|
10639
|
-
if (
|
|
11283
|
+
if (key && has(_overrideHops, key))
|
|
11284
|
+
return _overrideHops[key];
|
|
11285
|
+
return key && has(PROVIDER_CHAINS, key) ? PROVIDER_CHAINS[key] : [];
|
|
11286
|
+
}
|
|
11287
|
+
function providerChainLeadsFor(modelId) {
|
|
11288
|
+
const key = chainKey(modelId);
|
|
11289
|
+
if (key && has(_overrideLeads, key))
|
|
11290
|
+
return _overrideLeads[key];
|
|
11291
|
+
return key && has(PROVIDER_CHAIN_LEADS, key) ? PROVIDER_CHAIN_LEADS[key] : [];
|
|
11292
|
+
}
|
|
11293
|
+
function allChains() {
|
|
11294
|
+
const ordered = (map, override) => [...Object.entries(map).filter(([key]) => !has(override, key)), ...Object.entries(override)];
|
|
11295
|
+
return [...ordered(PROVIDER_CHAINS, _overrideHops), ...ordered(PROVIDER_CHAIN_LEADS, _overrideLeads)];
|
|
11296
|
+
}
|
|
11297
|
+
function isProviderChainHopId(modelId) {
|
|
11298
|
+
const id = modelId ?? "";
|
|
11299
|
+
return id !== "" && allChains().some(([, hops]) => hops.includes(id));
|
|
11300
|
+
}
|
|
11301
|
+
function providerChainPrimariesForHop(hopId) {
|
|
11302
|
+
const id = hopId ?? "";
|
|
11303
|
+
if (!id)
|
|
11304
|
+
return [];
|
|
11305
|
+
return allChains().filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
|
|
11306
|
+
}
|
|
11307
|
+
function providerChainHostOf(value, modelValue) {
|
|
11308
|
+
if (value === modelValue)
|
|
11309
|
+
return "own";
|
|
11310
|
+
if (hasZaiPrefix(value))
|
|
11311
|
+
return "zai";
|
|
11312
|
+
if (hasOpenCodeGoPrefix(value))
|
|
11313
|
+
return "opencode-go";
|
|
11314
|
+
if (hasOpenRouterPrefix(value))
|
|
11315
|
+
return "openrouter";
|
|
11316
|
+
return "own";
|
|
11317
|
+
}
|
|
11318
|
+
function providerChainHostProvider(value) {
|
|
11319
|
+
if (typeof value !== "string" || !value)
|
|
11320
|
+
return null;
|
|
11321
|
+
if (hasOpenRouterPrefix(value))
|
|
11322
|
+
return "openrouter";
|
|
11323
|
+
if (hasOpenCodeGoPrefix(value))
|
|
11324
|
+
return "opencode-go";
|
|
11325
|
+
if (hasZaiPrefix(value))
|
|
11326
|
+
return "zai";
|
|
11327
|
+
const key = chainKey(value);
|
|
11328
|
+
return API_MODEL_CATALOG.find((m) => m.id === key)?.provider ?? GEMINI_ASSISTANT_MODELS.find((m) => m.id === key)?.provider ?? null;
|
|
11329
|
+
}
|
|
11330
|
+
function providerHostPlan(modelId) {
|
|
11331
|
+
const key = chainKey(modelId);
|
|
11332
|
+
if (!key)
|
|
11333
|
+
return [];
|
|
11334
|
+
const own = { host: hasOpenRouterPrefix(key) ? "openrouter" : "own", value: key };
|
|
11335
|
+
const steps = [
|
|
11336
|
+
...providerChainLeadsFor(key).map((value) => ({ host: providerChainHostOf(value, key), value })),
|
|
11337
|
+
own,
|
|
11338
|
+
...providerChainFor(key).map((value) => ({ host: providerChainHostOf(value, key), value }))
|
|
11339
|
+
];
|
|
11340
|
+
const notOffered = Object.prototype.hasOwnProperty.call(PROVIDER_HOST_NOT_OFFERED, key) ? PROVIDER_HOST_NOT_OFFERED[key] : {};
|
|
11341
|
+
for (const [host, reason] of Object.entries(notOffered)) {
|
|
11342
|
+
const at = steps.findIndex((step) => step === own) + 1;
|
|
11343
|
+
steps.splice(at, 0, { host, value: null, notOffered: reason });
|
|
11344
|
+
}
|
|
11345
|
+
return steps;
|
|
11346
|
+
}
|
|
11347
|
+
function providerChainModelKey(modelId) {
|
|
11348
|
+
return chainKey(modelId);
|
|
11349
|
+
}
|
|
11350
|
+
function hasProviderChainLeadsByDefault(modelId) {
|
|
11351
|
+
const key = chainKey(modelId);
|
|
11352
|
+
return key !== null && has(PROVIDER_CHAIN_LEADS, key);
|
|
11353
|
+
}
|
|
11354
|
+
function isChainablePrimary(id) {
|
|
11355
|
+
return typeof id === "string" && id !== "" && (has(PROVIDER_CHAINS, id) || has(PROVIDER_CHAIN_LEADS, id));
|
|
11356
|
+
}
|
|
11357
|
+
function defaultProviderChainEntries(primary) {
|
|
11358
|
+
if (!isChainablePrimary(primary))
|
|
10640
11359
|
return [];
|
|
10641
|
-
|
|
11360
|
+
const leads = has(PROVIDER_CHAIN_LEADS, primary) ? PROVIDER_CHAIN_LEADS[primary] : [];
|
|
11361
|
+
const hops = has(PROVIDER_CHAINS, primary) ? PROVIDER_CHAINS[primary] : [];
|
|
11362
|
+
return [...leads, primary, ...hops].map((hop) => ({ hop, enabled: true }));
|
|
11363
|
+
}
|
|
11364
|
+
function allowedProviderChainHosts(primary) {
|
|
11365
|
+
return defaultProviderChainEntries(primary).map((entry) => entry.hop);
|
|
11366
|
+
}
|
|
11367
|
+
function isPlainObject2(value) {
|
|
11368
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
11369
|
+
}
|
|
11370
|
+
function validateProviderChainEntries(primary, entries) {
|
|
11371
|
+
if (!isChainablePrimary(primary))
|
|
11372
|
+
return `${String(primary).slice(0, 80)} has no provider chain to order`;
|
|
11373
|
+
if (!Array.isArray(entries))
|
|
11374
|
+
return `${primary}: the chain must be a list`;
|
|
11375
|
+
if (entries.length > PROVIDER_CHAIN_MAX_ENTRIES)
|
|
11376
|
+
return `${primary}: at most ${PROVIDER_CHAIN_MAX_ENTRIES} hosts`;
|
|
11377
|
+
const allowed = allowedProviderChainHosts(primary);
|
|
11378
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11379
|
+
for (const entry of entries) {
|
|
11380
|
+
if (!isPlainObject2(entry))
|
|
11381
|
+
return `${primary}: each host must be { hop, enabled }`;
|
|
11382
|
+
const keys = Object.keys(entry);
|
|
11383
|
+
if (keys.length !== 2 || !keys.includes("hop") || !keys.includes("enabled"))
|
|
11384
|
+
return `${primary}: each host must be { hop, enabled } and nothing else`;
|
|
11385
|
+
const { hop, enabled } = entry;
|
|
11386
|
+
if (typeof enabled !== "boolean")
|
|
11387
|
+
return `${primary}: enabled must be true or false`;
|
|
11388
|
+
if (typeof hop !== "string" || !hop)
|
|
11389
|
+
return `${primary}: hop must be a model value`;
|
|
11390
|
+
if (!allowed.includes(hop))
|
|
11391
|
+
return `${primary}: ${hop.slice(0, 80)} is not a host of this model`;
|
|
11392
|
+
if (seen.has(hop))
|
|
11393
|
+
return `${primary}: ${hop} is listed twice`;
|
|
11394
|
+
if (hop === primary && !enabled)
|
|
11395
|
+
return `${primary}: the model's own value stays on (pick another model on AI Models instead)`;
|
|
11396
|
+
seen.add(hop);
|
|
11397
|
+
}
|
|
11398
|
+
if (!seen.has(primary))
|
|
11399
|
+
return `${primary}: list the model's own value once, where its own host sits`;
|
|
11400
|
+
return null;
|
|
11401
|
+
}
|
|
11402
|
+
function decodeProviderChains(raw) {
|
|
11403
|
+
let value = raw;
|
|
11404
|
+
if (typeof raw === "string") {
|
|
11405
|
+
if (raw.length > PROVIDER_CHAINS_SETTING_MAX_CHARS)
|
|
11406
|
+
return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
|
|
11407
|
+
try {
|
|
11408
|
+
value = JSON.parse(raw);
|
|
11409
|
+
} catch {
|
|
11410
|
+
return { ok: false, error: "AI_PROVIDER_CHAINS must be valid JSON" };
|
|
11411
|
+
}
|
|
11412
|
+
} else if (JSON.stringify(raw ?? null).length > PROVIDER_CHAINS_SETTING_MAX_CHARS) {
|
|
11413
|
+
return { ok: false, error: `AI_PROVIDER_CHAINS is too long (at most ${PROVIDER_CHAINS_SETTING_MAX_CHARS} characters)` };
|
|
11414
|
+
}
|
|
11415
|
+
if (!isPlainObject2(value))
|
|
11416
|
+
return { ok: false, error: 'AI_PROVIDER_CHAINS must be an object: { "<model>": [ { "hop": "<model value>", "enabled": true } ] }' };
|
|
11417
|
+
if (Object.keys(value).length > PROVIDER_CHAIN_MAX_PRIMARIES)
|
|
11418
|
+
return { ok: false, error: `AI_PROVIDER_CHAINS names at most ${PROVIDER_CHAIN_MAX_PRIMARIES} models` };
|
|
11419
|
+
return { ok: true, value };
|
|
11420
|
+
}
|
|
11421
|
+
function checkedProviderChains(value) {
|
|
11422
|
+
const out = {};
|
|
11423
|
+
for (const [primary, entries] of Object.entries(value)) {
|
|
11424
|
+
const problem = validateProviderChainEntries(primary, entries);
|
|
11425
|
+
if (problem)
|
|
11426
|
+
return { ok: false, error: problem };
|
|
11427
|
+
out[primary] = entries.map(({ hop, enabled }) => ({ hop, enabled }));
|
|
11428
|
+
}
|
|
11429
|
+
return { ok: true, value: out };
|
|
11430
|
+
}
|
|
11431
|
+
function parseProviderChainOverrides(raw) {
|
|
11432
|
+
const decoded = decodeProviderChains(raw);
|
|
11433
|
+
return decoded.ok ? checkedProviderChains(decoded.value) : decoded;
|
|
11434
|
+
}
|
|
11435
|
+
function parseStoredProviderChainOverrides(raw) {
|
|
11436
|
+
const decoded = decodeProviderChains(raw);
|
|
11437
|
+
if (!decoded.ok)
|
|
11438
|
+
return decoded;
|
|
11439
|
+
const dropped = [];
|
|
11440
|
+
const kept = {};
|
|
11441
|
+
for (const [primary, entries] of Object.entries(decoded.value)) {
|
|
11442
|
+
if (!isChainablePrimary(primary) || !Array.isArray(entries)) {
|
|
11443
|
+
kept[primary] = entries;
|
|
11444
|
+
continue;
|
|
11445
|
+
}
|
|
11446
|
+
const allowed = allowedProviderChainHosts(primary);
|
|
11447
|
+
kept[primary] = entries.filter((entry) => {
|
|
11448
|
+
const hop = isPlainObject2(entry) ? entry.hop : void 0;
|
|
11449
|
+
if (typeof hop !== "string" || hop === "" || allowed.includes(hop))
|
|
11450
|
+
return true;
|
|
11451
|
+
dropped.push({ primary, hop: hop.slice(0, 80) });
|
|
11452
|
+
return false;
|
|
11453
|
+
});
|
|
11454
|
+
}
|
|
11455
|
+
const checked = checkedProviderChains(kept);
|
|
11456
|
+
return checked.ok ? { ...checked, dropped } : checked;
|
|
11457
|
+
}
|
|
11458
|
+
function validateProviderChainsSetting(raw) {
|
|
11459
|
+
const parsed = parseProviderChainOverrides(raw);
|
|
11460
|
+
return parsed.ok ? null : parsed.error;
|
|
10642
11461
|
}
|
|
10643
|
-
function
|
|
10644
|
-
const
|
|
10645
|
-
|
|
11462
|
+
function setProviderChainOverrides(value, onDropped) {
|
|
11463
|
+
const parsed = value === null || value === void 0 || value === "" ? null : parseStoredProviderChainOverrides(value);
|
|
11464
|
+
if (parsed?.ok && parsed.dropped.length > 0)
|
|
11465
|
+
onDropped?.(parsed.dropped);
|
|
11466
|
+
_overrides = parsed?.ok ? parsed.value : null;
|
|
11467
|
+
_overrideLeads = {};
|
|
11468
|
+
_overrideHops = {};
|
|
11469
|
+
for (const [primary, entries] of Object.entries(_overrides ?? {})) {
|
|
11470
|
+
const at = entries.findIndex((entry) => entry.hop === primary);
|
|
11471
|
+
const on = (list) => list.filter((entry) => entry.enabled).map((entry) => entry.hop);
|
|
11472
|
+
_overrideLeads[primary] = on(entries.slice(0, at));
|
|
11473
|
+
_overrideHops[primary] = on(entries.slice(at + 1));
|
|
11474
|
+
}
|
|
11475
|
+
return parsed === null || parsed.ok;
|
|
10646
11476
|
}
|
|
10647
|
-
function
|
|
10648
|
-
|
|
10649
|
-
if (!id)
|
|
10650
|
-
return [];
|
|
10651
|
-
return Object.entries(PROVIDER_CHAINS).filter(([, hops]) => hops.includes(id)).map(([primary]) => primary);
|
|
11477
|
+
function getProviderChainOverrides() {
|
|
11478
|
+
return _overrides;
|
|
10652
11479
|
}
|
|
10653
|
-
var PROVIDER_CHAINS;
|
|
11480
|
+
var PROVIDER_CHAINS, PROVIDER_CHAIN_LEADS, PROVIDER_HOST_NOT_OFFERED, PROVIDER_CHAIN_MAX_ENTRIES, PROVIDER_CHAINS_SETTING_MAX_CHARS, PROVIDER_CHAIN_MAX_PRIMARIES, ALLOWED_PROVIDER_CHAIN_HOPS, _overrides, _overrideLeads, _overrideHops;
|
|
10654
11481
|
var init_provider_chains = __esm({
|
|
10655
11482
|
"../shared/dist/provider-chains.js"() {
|
|
10656
11483
|
"use strict";
|
|
10657
11484
|
init_api_model_catalog();
|
|
11485
|
+
init_engine_spec();
|
|
10658
11486
|
PROVIDER_CHAINS = {
|
|
10659
|
-
"minimax-m3": ["
|
|
10660
|
-
"minimax-m3.1-flash": ["
|
|
11487
|
+
"minimax-m3": ["opencode-go:minimax-m3", "openrouter:minimax/minimax-m3"],
|
|
11488
|
+
"minimax-m3.1-flash": ["opencode-go:minimax-m3.1-flash", "openrouter:minimax/minimax-m3.1-flash"],
|
|
10661
11489
|
"muse-spark-1.3-contributor": ["openrouter:meta/muse-spark-1.3-contributor"],
|
|
10662
|
-
"
|
|
10663
|
-
"
|
|
10664
|
-
"
|
|
11490
|
+
"claude-haiku-5-5": ["opencode-go:claude-haiku-5-5", "openrouter:anthropic/claude-haiku-5.5"],
|
|
11491
|
+
"gemini-3.1-pro-preview": ["openrouter:google/gemini-3.1-pro-preview"],
|
|
11492
|
+
"deepseek-flash": ["opencode-go:deepseek-v4.1-flash", "openrouter:deepseek/deepseek-v4.1-flash"],
|
|
11493
|
+
"deepseek-v4-pro": ["opencode-go:deepseek-v4-pro", "openrouter:deepseek/deepseek-v4-pro"]
|
|
10665
11494
|
};
|
|
11495
|
+
PROVIDER_CHAIN_LEADS = {
|
|
11496
|
+
"openrouter:z-ai/glm-5.3-flash": ["zai:glm-5.3-flash", "opencode-go:glm-5.3-flash"],
|
|
11497
|
+
"openrouter:z-ai/glm-5.3": ["zai:glm-5.3", "opencode-go:glm-5.3"],
|
|
11498
|
+
"openrouter:z-ai/glm-5.2": ["zai:glm-5.2", "opencode-go:glm-5.2"]
|
|
11499
|
+
};
|
|
11500
|
+
PROVIDER_HOST_NOT_OFFERED = {
|
|
11501
|
+
"muse-spark-1.3-contributor": { "opencode-go": "OpenCode Go serves Muse only on its Responses API" },
|
|
11502
|
+
"gemini-3.1-pro-preview": { "opencode-go": "OpenCode Go does not serve Gemini" }
|
|
11503
|
+
};
|
|
11504
|
+
PROVIDER_CHAIN_MAX_ENTRIES = 8;
|
|
11505
|
+
PROVIDER_CHAINS_SETTING_MAX_CHARS = 16e3;
|
|
11506
|
+
PROVIDER_CHAIN_MAX_PRIMARIES = 32;
|
|
11507
|
+
ALLOWED_PROVIDER_CHAIN_HOPS = /* @__PURE__ */ new Set([
|
|
11508
|
+
...Object.values(PROVIDER_CHAINS).flat(),
|
|
11509
|
+
...Object.values(PROVIDER_CHAIN_LEADS).flat()
|
|
11510
|
+
]);
|
|
11511
|
+
_overrides = null;
|
|
11512
|
+
_overrideLeads = {};
|
|
11513
|
+
_overrideHops = {};
|
|
11514
|
+
}
|
|
11515
|
+
});
|
|
11516
|
+
|
|
11517
|
+
// ../shared/dist/provider-accounts.js
|
|
11518
|
+
function modelNames(modelId) {
|
|
11519
|
+
const value = modelId.trim();
|
|
11520
|
+
const names = /* @__PURE__ */ new Set([value]);
|
|
11521
|
+
for (const prefix of HOST_PREFIXES) {
|
|
11522
|
+
if (!value.startsWith(prefix))
|
|
11523
|
+
continue;
|
|
11524
|
+
const bare = value.slice(prefix.length);
|
|
11525
|
+
names.add(bare);
|
|
11526
|
+
const slash = bare.lastIndexOf("/");
|
|
11527
|
+
if (slash >= 0)
|
|
11528
|
+
names.add(bare.slice(slash + 1));
|
|
11529
|
+
}
|
|
11530
|
+
const canonical = canonicalModelId(value);
|
|
11531
|
+
if (canonical) {
|
|
11532
|
+
names.add(canonical);
|
|
11533
|
+
const entry = API_MODEL_CATALOG.find((m) => m.id === canonical || m.apiModel === canonical);
|
|
11534
|
+
if (entry) {
|
|
11535
|
+
names.add(entry.id);
|
|
11536
|
+
if (entry.apiModel)
|
|
11537
|
+
names.add(entry.apiModel);
|
|
11538
|
+
}
|
|
11539
|
+
}
|
|
11540
|
+
return names;
|
|
11541
|
+
}
|
|
11542
|
+
function accountServesModel(models, modelId) {
|
|
11543
|
+
if (!models || models.length === 0)
|
|
11544
|
+
return true;
|
|
11545
|
+
if (!modelId)
|
|
11546
|
+
return false;
|
|
11547
|
+
const names = modelNames(modelId);
|
|
11548
|
+
return models.some((model) => names.has(model.trim()));
|
|
11549
|
+
}
|
|
11550
|
+
function normalizeProviderAccountModels(models) {
|
|
11551
|
+
if (!Array.isArray(models))
|
|
11552
|
+
return null;
|
|
11553
|
+
const out = [];
|
|
11554
|
+
for (const model of models) {
|
|
11555
|
+
if (typeof model !== "string")
|
|
11556
|
+
return null;
|
|
11557
|
+
const trimmed = model.trim();
|
|
11558
|
+
if (!PROVIDER_ACCOUNT_MODEL_PATTERN.test(trimmed) || trimmed.includes(".."))
|
|
11559
|
+
return null;
|
|
11560
|
+
if (!out.includes(trimmed))
|
|
11561
|
+
out.push(trimmed);
|
|
11562
|
+
}
|
|
11563
|
+
return out.length > MAX_PROVIDER_ACCOUNT_MODELS ? null : out;
|
|
11564
|
+
}
|
|
11565
|
+
function parseProviderAccountLists(value, providers) {
|
|
11566
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
11567
|
+
return null;
|
|
11568
|
+
const parsed = {};
|
|
11569
|
+
for (const provider of providers) {
|
|
11570
|
+
const list = value[provider];
|
|
11571
|
+
if (!Array.isArray(list))
|
|
11572
|
+
continue;
|
|
11573
|
+
const accounts = [];
|
|
11574
|
+
for (const raw of list) {
|
|
11575
|
+
if (!raw || typeof raw !== "object")
|
|
11576
|
+
continue;
|
|
11577
|
+
const { id, label, key, models } = raw;
|
|
11578
|
+
if (typeof id !== "string" || !id.trim() || typeof key !== "string" || !key.trim())
|
|
11579
|
+
continue;
|
|
11580
|
+
const modelList = Array.isArray(models) ? models.filter((m) => typeof m === "string" && m.trim() !== "").map((m) => m.trim()) : [];
|
|
11581
|
+
accounts.push({ id: id.trim(), label: typeof label === "string" && label.trim() ? label.trim() : null, key: key.trim(), models: modelList });
|
|
11582
|
+
}
|
|
11583
|
+
if (accounts.length > 0)
|
|
11584
|
+
parsed[provider] = accounts;
|
|
11585
|
+
}
|
|
11586
|
+
return parsed;
|
|
11587
|
+
}
|
|
11588
|
+
var DEFAULT_PROVIDER_ACCOUNT_ID, PROVIDER_ACCOUNT_ID_PATTERN, MAX_PROVIDER_ACCOUNTS, MAX_PROVIDER_ACCOUNT_MODELS, PROVIDER_ACCOUNT_MODEL_PATTERN, OPENCODE_GO_DEEPSEEK_GLM_MODELS, HOST_PREFIXES;
|
|
11589
|
+
var init_provider_accounts = __esm({
|
|
11590
|
+
"../shared/dist/provider-accounts.js"() {
|
|
11591
|
+
"use strict";
|
|
11592
|
+
init_api_model_catalog();
|
|
11593
|
+
DEFAULT_PROVIDER_ACCOUNT_ID = "primary";
|
|
11594
|
+
PROVIDER_ACCOUNT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
11595
|
+
MAX_PROVIDER_ACCOUNTS = 10;
|
|
11596
|
+
MAX_PROVIDER_ACCOUNT_MODELS = 50;
|
|
11597
|
+
PROVIDER_ACCOUNT_MODEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,120}$/;
|
|
11598
|
+
OPENCODE_GO_DEEPSEEK_GLM_MODELS = [
|
|
11599
|
+
"deepseek-v4.1-flash",
|
|
11600
|
+
"deepseek-v4-pro",
|
|
11601
|
+
"glm-5.3-flash",
|
|
11602
|
+
"glm-5.3",
|
|
11603
|
+
"glm-5.2"
|
|
11604
|
+
];
|
|
11605
|
+
HOST_PREFIXES = ["opencode-go:", "zai:", "openrouter:"];
|
|
10666
11606
|
}
|
|
10667
11607
|
});
|
|
10668
11608
|
|
|
@@ -10781,6 +11721,25 @@ var init_project_engine_overrides = __esm({
|
|
|
10781
11721
|
}
|
|
10782
11722
|
});
|
|
10783
11723
|
|
|
11724
|
+
// ../shared/dist/trailing-slashes.js
|
|
11725
|
+
function stripTrailingSlashes(value) {
|
|
11726
|
+
let end = value.length;
|
|
11727
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47)
|
|
11728
|
+
end--;
|
|
11729
|
+
return end === value.length ? value : value.slice(0, end);
|
|
11730
|
+
}
|
|
11731
|
+
function stripTrailingChars(value, isTrailing) {
|
|
11732
|
+
let end = value.length;
|
|
11733
|
+
while (end > 0 && isTrailing.test(value[end - 1]))
|
|
11734
|
+
end--;
|
|
11735
|
+
return end === value.length ? value : value.slice(0, end);
|
|
11736
|
+
}
|
|
11737
|
+
var init_trailing_slashes = __esm({
|
|
11738
|
+
"../shared/dist/trailing-slashes.js"() {
|
|
11739
|
+
"use strict";
|
|
11740
|
+
}
|
|
11741
|
+
});
|
|
11742
|
+
|
|
10784
11743
|
// ../shared/dist/gemini-visual.js
|
|
10785
11744
|
function visualProDailyBudget(raw) {
|
|
10786
11745
|
const parsed = raw && /^\s*\d{1,6}\s*$/.test(raw) ? Number(raw.trim()) : NaN;
|
|
@@ -10794,7 +11753,7 @@ function isRetiredGeminiModel(id) {
|
|
|
10794
11753
|
}
|
|
10795
11754
|
function geminiNativeModelsUrl(base = process.env.GEMINI_API_BASE) {
|
|
10796
11755
|
const raw = base && base.trim() || GEMINI_OPENAI_BASE;
|
|
10797
|
-
const native = raw.replace(/\/openai\/?$/, "/")
|
|
11756
|
+
const native = stripTrailingSlashes(raw.replace(/\/openai\/?$/, "/"));
|
|
10798
11757
|
return `${native}/models?pageSize=1000`;
|
|
10799
11758
|
}
|
|
10800
11759
|
function asRecord(value) {
|
|
@@ -10914,6 +11873,7 @@ var KNOWN_INPUT_USD_PER_M, GEMINI_OPENAI_BASE, DEFAULT_GEMINI_SCREEN_MODEL, DEFA
|
|
|
10914
11873
|
var init_gemini_visual = __esm({
|
|
10915
11874
|
"../shared/dist/gemini-visual.js"() {
|
|
10916
11875
|
"use strict";
|
|
11876
|
+
init_trailing_slashes();
|
|
10917
11877
|
KNOWN_INPUT_USD_PER_M = {
|
|
10918
11878
|
"gemini-2.5-flash-lite": 0.1,
|
|
10919
11879
|
"gemini-3.1-flash-lite": 0.25,
|
|
@@ -12277,12 +13237,13 @@ function truncateAtWord(text, max) {
|
|
|
12277
13237
|
const slice = trimmed.slice(0, room + 1);
|
|
12278
13238
|
const lastSpace = slice.lastIndexOf(" ");
|
|
12279
13239
|
const cut = lastSpace > room * 0.5 ? slice.slice(0, lastSpace) : trimmed.slice(0, room);
|
|
12280
|
-
return `${cut
|
|
13240
|
+
return `${stripTrailingChars(cut, TRAILING_PUNCTUATION)}\u2026`;
|
|
12281
13241
|
}
|
|
12282
|
-
var CANVAS_UNSUPPORTED_CODE, CANVAS_UNSUPPORTED_TITLE, CANVAS_UNSUPPORTED_BODY, CANVAS_UNSUPPORTED_NOT_CHARGED, CANVAS_UNSUPPORTED_OVERRIDE_HINT, GAME_ENGINES, RENDERER_ENGINES, ENGINE_TEXT_SIGNATURES, GAME_LIBRARY_TEXT_PATTERNS, ENGINE_PACKAGES, GAME_HINT_PACKAGES, MAX_TAG_CHARS, MAX_CSS_SELECTOR_CHARS, MAX_CSS_BLOCK_CHARS, TAG_NAME_RE, ATTR_RE, INTERACTIVE_TAGS, INTERACTIVE_ROLES, ROOT_SELECTOR_RE, OVERFLOW_HIDDEN_RE, TOUCH_NONE_RE, CODE_TAGS, STATIC_STANDARD_MIN_INTERACTIVE;
|
|
13242
|
+
var CANVAS_UNSUPPORTED_CODE, CANVAS_UNSUPPORTED_TITLE, CANVAS_UNSUPPORTED_BODY, CANVAS_UNSUPPORTED_NOT_CHARGED, CANVAS_UNSUPPORTED_OVERRIDE_HINT, GAME_ENGINES, RENDERER_ENGINES, ENGINE_TEXT_SIGNATURES, GAME_LIBRARY_TEXT_PATTERNS, ENGINE_PACKAGES, GAME_HINT_PACKAGES, MAX_TAG_CHARS, MAX_CSS_SELECTOR_CHARS, MAX_CSS_BLOCK_CHARS, TAG_NAME_RE, ATTR_RE, INTERACTIVE_TAGS, INTERACTIVE_ROLES, ROOT_SELECTOR_RE, OVERFLOW_HIDDEN_RE, TOUCH_NONE_RE, CODE_TAGS, STATIC_STANDARD_MIN_INTERACTIVE, TRAILING_PUNCTUATION;
|
|
12283
13243
|
var init_canvas_support = __esm({
|
|
12284
13244
|
"../shared/dist/canvas-support.js"() {
|
|
12285
13245
|
"use strict";
|
|
13246
|
+
init_trailing_slashes();
|
|
12286
13247
|
CANVAS_UNSUPPORTED_CODE = "canvas_unsupported";
|
|
12287
13248
|
CANVAS_UNSUPPORTED_TITLE = "Games and canvas apps aren't supported yet";
|
|
12288
13249
|
CANVAS_UNSUPPORTED_BODY = "This app draws its screen on a canvas (like a game), so there are no page elements for Validate.QA to read and test. Support for games and canvas apps is coming.";
|
|
@@ -12407,6 +13368,7 @@ var init_canvas_support = __esm({
|
|
|
12407
13368
|
TOUCH_NONE_RE = /touch-action\s{0,8}:\s{0,8}none/;
|
|
12408
13369
|
CODE_TAGS = /* @__PURE__ */ new Set(["script", "link", "flt-glass-pane", "flutter-view", "flt-scene-host"]);
|
|
12409
13370
|
STATIC_STANDARD_MIN_INTERACTIVE = 3;
|
|
13371
|
+
TRAILING_PUNCTUATION = /[\s,;:.\-–—]/u;
|
|
12410
13372
|
}
|
|
12411
13373
|
});
|
|
12412
13374
|
|
|
@@ -12589,22 +13551,22 @@ function joinChainedLines(code) {
|
|
|
12589
13551
|
const trimmed = lines[i].trimStart();
|
|
12590
13552
|
if (last !== -1 && trimmed.startsWith(".") && !trimmed.startsWith("...")) {
|
|
12591
13553
|
if (!lastStripped) {
|
|
12592
|
-
lines[last] =
|
|
13554
|
+
lines[last] = stripLineComment3(lines[last]).trimEnd();
|
|
12593
13555
|
lastStripped = true;
|
|
12594
13556
|
}
|
|
12595
13557
|
if (lines[last].length <= MAX_LINE_CHARS)
|
|
12596
|
-
lines[last] = `${lines[last]} ${
|
|
13558
|
+
lines[last] = `${lines[last]} ${stripLineComment3(trimmed).trimEnd()}`;
|
|
12597
13559
|
lines[i] = "";
|
|
12598
13560
|
continue;
|
|
12599
13561
|
}
|
|
12600
|
-
if (
|
|
13562
|
+
if (stripLineComment3(lines[i]).trim()) {
|
|
12601
13563
|
last = i;
|
|
12602
13564
|
lastStripped = false;
|
|
12603
13565
|
}
|
|
12604
13566
|
}
|
|
12605
13567
|
return lines.join("\n");
|
|
12606
13568
|
}
|
|
12607
|
-
function
|
|
13569
|
+
function stripLineComment3(rawLine) {
|
|
12608
13570
|
if (rawLine.trimStart().startsWith("//"))
|
|
12609
13571
|
return "";
|
|
12610
13572
|
const at = rawLine.indexOf(" // ");
|
|
@@ -12620,7 +13582,7 @@ function stateLiteralsOf(line) {
|
|
|
12620
13582
|
const out = [];
|
|
12621
13583
|
for (const m of line.matchAll(STRING_LITERAL_RE)) {
|
|
12622
13584
|
const text = m[1] ?? m[2] ?? m[3] ?? "";
|
|
12623
|
-
if (
|
|
13585
|
+
if (PLACEHOLDER_ATTR_RE.test(text))
|
|
12624
13586
|
continue;
|
|
12625
13587
|
if (PLACEHOLDER_CALL_TAIL_RE.test(line.slice(Math.max(0, m.index - 40), m.index)))
|
|
12626
13588
|
continue;
|
|
@@ -12653,7 +13615,7 @@ function analyzeReadinessPolling(rawCode) {
|
|
|
12653
13615
|
offset += rawLine.length + 1;
|
|
12654
13616
|
if (rawLine.length > MAX_LINE_CHARS)
|
|
12655
13617
|
continue;
|
|
12656
|
-
const line =
|
|
13618
|
+
const line = stripLineComment3(rawLine);
|
|
12657
13619
|
if (!line.trim())
|
|
12658
13620
|
continue;
|
|
12659
13621
|
const literals = literalsOf(line);
|
|
@@ -12716,7 +13678,7 @@ function formatReadinessPollAdvisory(findings) {
|
|
|
12716
13678
|
const where = findings.length === 1 ? "One place" : `${findings.length} places`;
|
|
12717
13679
|
return `${where} re-check a background state once and assert the finished state at once (${short(first.line, 90)} then ${short(first.assertion, 90)}). ${first.reason}`;
|
|
12718
13680
|
}
|
|
12719
|
-
var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
|
|
13681
|
+
var READINESS_POLL_MAX_CODE_CHARS, MAX_LINE_CHARS, MAX_FINDINGS, ASSERTION_WINDOW_LINES, MAX_BLOCK_CHARS, MAX_HEAD_CHARS, STRING_LITERAL_RE, RECHECK_NAME_RE, RETRY_NAME_RE, PENDING_STATE_RE, LOCATOR_LINE_RE, CLICK_RE, RELOAD_RE, GOTO_RE, ACTION_RE, EXPECT_RE, NEGATED_RE, PENDING_ENDED_RE, POSITIVE_MATCHER_RE, ZERO_COUNT_RE, TEST_END_RE, TEST_START_RE, POLL_OPEN_RE, PLACEHOLDER_ATTR_RE, PLACEHOLDER_CALL_TAIL_RE, POLL_SHAPE;
|
|
12720
13682
|
var init_readiness_poll = __esm({
|
|
12721
13683
|
"../shared/dist/readiness-poll.js"() {
|
|
12722
13684
|
"use strict";
|
|
@@ -12729,7 +13691,7 @@ var init_readiness_poll = __esm({
|
|
|
12729
13691
|
STRING_LITERAL_RE = /'((?:\\.|[^'\\\n]){0,300})'|"((?:\\.|[^"\\\n]){0,300})"|`((?:\\.|[^`\\\n]){0,300})`/g;
|
|
12730
13692
|
RECHECK_NAME_RE = /^\s{0,10}(?:check again|check (?:status|availability|now|for updates|progress)|re-?check(?: status)?|update status|see (?:latest|updates))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
|
|
12731
13693
|
RETRY_NAME_RE = /^\s{0,10}(?:refresh(?: (?:status|list|page|results|now))?|reload(?: (?:page|results|list))?|try again|retry|load (?:again|latest))\s{0,10}(?:\.\.\.|…)?\s{0,10}$/i;
|
|
12732
|
-
PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b[a-z]{3,
|
|
13694
|
+
PENDING_STATE_RE = /\b(?:searching|checking|sending|pending|processing|queued|in progress|loading|generating|calculating|preparing|verifying|syncing|refreshing|not ready|please wait|waiting)\b|\b(?:saving|uploading|downloading|signing|logging|submitting|creating|updating|deleting|importing|exporting|publishing|connecting|redirecting|authenticating|installing|retrieving|fetching|analy[sz]ing|running|rendering|restarting|building|compiling|booting|provisioning|deploying|indexing|migrating|launching|initiali[sz]ing|optimi[sz]ing|converting|processing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}(?:starting|scanning|working|applying|finishing)(?:\s{1,3}[a-z]{1,20}){0,2}\s{0,3}(?:\.\.\.|…)\s{0,10}$|^\s{0,10}[a-z]{3,40}(?<!th)ing\s{0,3}(?:\.\.\.|…)\s{0,10}$/i;
|
|
12733
13695
|
LOCATOR_LINE_RE = /\.(?:getBy[A-Z]\w{0,30}|locator|toHaveText|toContainText)\s{0,20}\(/;
|
|
12734
13696
|
CLICK_RE = /\.(?:click|dblclick|tap)\s{0,20}\(/;
|
|
12735
13697
|
RELOAD_RE = /\bpage\.reload\s{0,20}\(/;
|
|
@@ -12743,6 +13705,7 @@ var init_readiness_poll = __esm({
|
|
|
12743
13705
|
TEST_END_RE = /^\s{0,20}\}\s{0,20}\)\s{0,20};?\s{0,20}$/;
|
|
12744
13706
|
TEST_START_RE = /\btest(?:\.\w{1,20})?\s{0,20}\(/;
|
|
12745
13707
|
POLL_OPEN_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}async\b|\bexpect\.poll\s{0,20}\(|\bfor\s{0,20}\(|\bwhile\s{0,20}\(|\bdo\s{0,20}\{/g;
|
|
13708
|
+
PLACEHOLDER_ATTR_RE = /placeholder\s{0,4}[*^$~|]?=/;
|
|
12746
13709
|
PLACEHOLDER_CALL_TAIL_RE = /getByPlaceholder\s{0,20}\(\s{0,20}$/;
|
|
12747
13710
|
POLL_SHAPE = "await expect(async () => { await page.reload(); await expect(<the finished state>).toBeVisible({ timeout: 1500 }); }).toPass({ timeout: 20000 });";
|
|
12748
13711
|
}
|
|
@@ -12764,7 +13727,7 @@ function blankBlockComments2(code) {
|
|
|
12764
13727
|
from = close + 2;
|
|
12765
13728
|
}
|
|
12766
13729
|
}
|
|
12767
|
-
function
|
|
13730
|
+
function stripLineComment4(rawLine) {
|
|
12768
13731
|
if (rawLine.trimStart().startsWith("//"))
|
|
12769
13732
|
return "";
|
|
12770
13733
|
const at = rawLine.indexOf(" // ");
|
|
@@ -12775,7 +13738,7 @@ function logicalLines(code) {
|
|
|
12775
13738
|
for (const raw of code.split("\n")) {
|
|
12776
13739
|
if (raw.length > MAX_LINE_CHARS2)
|
|
12777
13740
|
continue;
|
|
12778
|
-
const line =
|
|
13741
|
+
const line = stripLineComment4(raw).trimEnd();
|
|
12779
13742
|
if (!line.trim())
|
|
12780
13743
|
continue;
|
|
12781
13744
|
const trimmed = line.trimStart();
|
|
@@ -13137,13 +14100,33 @@ function short4(text, max) {
|
|
|
13137
14100
|
const flat = text.replace(/\s+/g, " ").trim();
|
|
13138
14101
|
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
13139
14102
|
}
|
|
14103
|
+
function stripFractionZeros(s) {
|
|
14104
|
+
let end = s.length;
|
|
14105
|
+
while (end > 0 && s[end - 1] === "0")
|
|
14106
|
+
end--;
|
|
14107
|
+
if (end === s.length)
|
|
14108
|
+
return s;
|
|
14109
|
+
const dot = s.lastIndexOf(".");
|
|
14110
|
+
if (dot < 0)
|
|
14111
|
+
return s;
|
|
14112
|
+
return s.slice(0, end === dot + 1 ? dot : end);
|
|
14113
|
+
}
|
|
13140
14114
|
function digitsOf(token) {
|
|
13141
|
-
return token.replace(/[^\d.]/g, "")
|
|
14115
|
+
return stripFractionZeros(token.replace(/[^\d.]/g, "")).replace(/^0+(?=\d)/, "");
|
|
13142
14116
|
}
|
|
13143
14117
|
function computes(line) {
|
|
13144
14118
|
const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX").replace(LITERAL_ARITH_RE, "N");
|
|
13145
14119
|
return COMPUTE_RE.test(code) || RELATIONAL_RE.test(line);
|
|
13146
14120
|
}
|
|
14121
|
+
function identifiersIn(line) {
|
|
14122
|
+
const code = line.replace(STRING_RE, '""').replace(REGEX_LITERAL_RE, "$1RX");
|
|
14123
|
+
return [code, ...[...line.matchAll(TEMPLATE_EXPR_RE)].map((m) => m[1])].flatMap((part) => part.match(IDENTIFIER_RE) ?? []);
|
|
14124
|
+
}
|
|
14125
|
+
function addTyped(typed, value) {
|
|
14126
|
+
const d = digitsOf(value);
|
|
14127
|
+
if (/\d/.test(d))
|
|
14128
|
+
typed.add(d);
|
|
14129
|
+
}
|
|
13147
14130
|
function reasonFor4(kind, label, value) {
|
|
13148
14131
|
const what = kind === "derived-value-shape-only" ? `The ${label} is checked only for its shape, so any amount passes, including a wrongly computed one.` : `The ${label} is checked against a number written into the test (${value}). That number was read off the page, so when the page computes it wrong the check still passes.`;
|
|
13149
14132
|
return `${what} Instead, ${COMPUTE_SHAPE} If the number is not computed from other values (a list price, an id, a fixed fee), leave it as it is. Keep the existing checks.`;
|
|
@@ -13155,25 +14138,32 @@ function analyzeDerivedValues(rawCode) {
|
|
|
13155
14138
|
for (const body of testBodies(rawCode)) {
|
|
13156
14139
|
if (findings.length >= MAX_FINDINGS4)
|
|
13157
14140
|
break;
|
|
13158
|
-
if (body.some(computes))
|
|
13159
|
-
continue;
|
|
13160
14141
|
const typed = /* @__PURE__ */ new Set();
|
|
13161
14142
|
const definitions = /* @__PURE__ */ new Map();
|
|
14143
|
+
const computed = /* @__PURE__ */ new Set();
|
|
14144
|
+
const usesComputed = (text) => identifiersIn(text).some((id) => computed.has(id));
|
|
13162
14145
|
for (const line of body) {
|
|
13163
14146
|
if (FILL_RE2.test(line) && !EXPECT_RE3.test(line)) {
|
|
13164
|
-
for (const m of line.matchAll(
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
14147
|
+
for (const m of line.matchAll(QUOTED_RE))
|
|
14148
|
+
addTyped(typed, m[1]);
|
|
14149
|
+
const arg = FILL_ARG_RE.exec(line)?.[1];
|
|
14150
|
+
const literal = arg === void 0 ? null : LITERAL_VALUE_RE.exec(definitions.get(arg) ?? "");
|
|
14151
|
+
const value = literal?.slice(1).find((v) => v !== void 0);
|
|
14152
|
+
if (value !== void 0)
|
|
14153
|
+
addTyped(typed, value);
|
|
13169
14154
|
}
|
|
13170
14155
|
const def = DEFINITION_RE.exec(line);
|
|
13171
|
-
if (def)
|
|
14156
|
+
if (def) {
|
|
13172
14157
|
definitions.set(def[1], def[2]);
|
|
14158
|
+
if (computes(def[2]) || usesComputed(def[2]))
|
|
14159
|
+
computed.add(def[1]);
|
|
14160
|
+
}
|
|
13173
14161
|
}
|
|
13174
14162
|
for (const line of body) {
|
|
13175
14163
|
if (!EXPECT_RE3.test(line) || !VALUE_MATCHER_RE2.test(line) || /\.not\s{0,20}\./.test(line))
|
|
13176
14164
|
continue;
|
|
14165
|
+
if (computes(line) || usesComputed(line))
|
|
14166
|
+
continue;
|
|
13177
14167
|
const subject = EXPECT_SUBJECT_RE.exec(line)?.[1];
|
|
13178
14168
|
const label = DERIVED_WORD_RE.exec(`${line} ${(subject && definitions.get(subject)) ?? ""}`)?.[0];
|
|
13179
14169
|
if (!label)
|
|
@@ -13203,7 +14193,7 @@ function formatDerivedValueAdvisory(findings) {
|
|
|
13203
14193
|
const where = findings.length === 1 ? "One computed value is" : `${findings.length} computed values are`;
|
|
13204
14194
|
return `${where} checked against a copied number or only its shape (${short4(first.line, 90)}). ${first.reason}`;
|
|
13205
14195
|
}
|
|
13206
|
-
var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, COMPUTE_SHAPE;
|
|
14196
|
+
var MAX_FINDINGS4, EXPECT_RE3, DERIVED_WORD_RE, VALUE_MATCHER_RE2, MONEY_TOKEN_RE, SHAPE_ARG_RE, FILL_RE2, EXPECT_MESSAGE_RE, STRING_RE, REGEX_LITERAL_RE, COMPUTE_RE, RELATIONAL_RE, DEFINITION_RE, EXPECT_SUBJECT_RE, LITERAL_ARITH_RE, TEMPLATE_EXPR_RE, IDENTIFIER_RE, QUOTED_RE, FILL_ARG_RE, LITERAL_VALUE_RE, COMPUTE_SHAPE;
|
|
13207
14197
|
var init_derived_value_check = __esm({
|
|
13208
14198
|
"../shared/dist/derived-value-check.js"() {
|
|
13209
14199
|
"use strict";
|
|
@@ -13223,6 +14213,11 @@ var init_derived_value_check = __esm({
|
|
|
13223
14213
|
DEFINITION_RE = /\b(?:const|let)\s{1,10}([\w$]{1,60})\s{0,10}=\s{0,10}([^\n]{0,300})/;
|
|
13224
14214
|
EXPECT_SUBJECT_RE = /\bexpect(?:\.soft)?\s{0,20}\(\s{0,20}([\w$]{1,60})/;
|
|
13225
14215
|
LITERAL_ARITH_RE = /\b\d[\d_.]{0,15}(?:\s{0,5}[*/+-]\s{0,5}\d[\d_.]{0,15}){1,8}\b/g;
|
|
14216
|
+
TEMPLATE_EXPR_RE = /\$\{([^{}\n]{0,200})\}/g;
|
|
14217
|
+
IDENTIFIER_RE = /[A-Za-z_$][\w$]{0,60}/g;
|
|
14218
|
+
QUOTED_RE = /['"`]([^'"`\n]{0,40})['"`]/g;
|
|
14219
|
+
FILL_ARG_RE = /\.(?:fill|type|pressSequentially|selectOption)\s{0,20}\(\s{0,20}([A-Za-z_$][\w$]{0,60})\s{0,20}[,)]/;
|
|
14220
|
+
LITERAL_VALUE_RE = /^\s{0,10}(?:'([^'\n]{0,40})'|"([^"\n]{0,40})"|`([^`$\n]{0,40})`|(\d[\d_,]{0,15}(?:\.\d{1,4})?))\s{0,10};?\s{0,10}$/;
|
|
13226
14221
|
COMPUTE_SHAPE = "compute the expected value in the test from its inputs (the values the test typed, or each input read from the page) and compare: const toNumber = (s) => Number(s.replace(/[^\\d.-]/g, '')); const unit = toNumber(await <unit price locator>.innerText()); then expect(toNumber(await <shown value locator>.innerText())).toBeCloseTo(unit * quantity, 2); for several lines, read them all and sum them with reduce. For a comma-decimal price like \u20AC1.234,56, drop the dots and turn the comma into a point before Number, since the replace above would read it as 1.23456.";
|
|
13227
14222
|
}
|
|
13228
14223
|
});
|
|
@@ -13335,7 +14330,7 @@ function findExpectCalls(code) {
|
|
|
13335
14330
|
if (!subj)
|
|
13336
14331
|
continue;
|
|
13337
14332
|
const rest = code.slice(subj.end + 1, subj.end + 1 + 80);
|
|
13338
|
-
const mm = /^\s*((?:\.\s*not)
|
|
14333
|
+
const mm = /^\s*((?:\.\s*not\s*)?\.\s*(\w+))\s{0,20}\(/.exec(rest);
|
|
13339
14334
|
if (!mm)
|
|
13340
14335
|
continue;
|
|
13341
14336
|
const argStart = subj.end + 1 + mm[0].length;
|
|
@@ -15257,6 +16252,133 @@ function formatStaleAfterSubmitAdvisory(rawCode) {
|
|
|
15257
16252
|
const more = described.length > 3 ? ` (+${described.length - 3} more)` : "";
|
|
15258
16253
|
return `stale-after-submit: ${described.slice(0, 3).join("; ")}${more}; these pass before the request lands and still pass if the server rejects or ignores the save. Wait for the result instead: page.waitForResponse around the click, or assert something the submit creates (the new row, the confirmation message, a path with an end anchor such as /\\/orders\\/\\d+$/)`;
|
|
15259
16254
|
}
|
|
16255
|
+
function regexLiteralEnd4(text, at) {
|
|
16256
|
+
let inClass = false;
|
|
16257
|
+
for (let i = at + 1; i < text.length; i++) {
|
|
16258
|
+
const ch = text[i];
|
|
16259
|
+
if (ch === "\n")
|
|
16260
|
+
return -1;
|
|
16261
|
+
if (ch === "\\") {
|
|
16262
|
+
i++;
|
|
16263
|
+
continue;
|
|
16264
|
+
}
|
|
16265
|
+
if (inClass) {
|
|
16266
|
+
if (ch === "]")
|
|
16267
|
+
inClass = false;
|
|
16268
|
+
continue;
|
|
16269
|
+
}
|
|
16270
|
+
if (ch === "[") {
|
|
16271
|
+
inClass = true;
|
|
16272
|
+
continue;
|
|
16273
|
+
}
|
|
16274
|
+
if (ch === "/")
|
|
16275
|
+
return i + 1;
|
|
16276
|
+
}
|
|
16277
|
+
return -1;
|
|
16278
|
+
}
|
|
16279
|
+
function startsWithRegexLiteral(text) {
|
|
16280
|
+
const t = text.trimStart();
|
|
16281
|
+
return t[0] === "/" && t[1] !== "/" && t[1] !== "*" && regexLiteralEnd4(t, 0) !== -1;
|
|
16282
|
+
}
|
|
16283
|
+
function isBareRegexLiteral(text) {
|
|
16284
|
+
const t = text.trimStart();
|
|
16285
|
+
if (t[0] !== "/" || t[1] === "/" || t[1] === "*")
|
|
16286
|
+
return false;
|
|
16287
|
+
const end = regexLiteralEnd4(t, 0);
|
|
16288
|
+
if (end === -1)
|
|
16289
|
+
return false;
|
|
16290
|
+
let i = end;
|
|
16291
|
+
while (i < t.length && /[a-z]/.test(t[i]))
|
|
16292
|
+
i++;
|
|
16293
|
+
while (i < t.length && /\s/.test(t[i]))
|
|
16294
|
+
i++;
|
|
16295
|
+
return i >= t.length || t[i] === ",";
|
|
16296
|
+
}
|
|
16297
|
+
function regexDeclaredNames(code) {
|
|
16298
|
+
const names = /* @__PURE__ */ new Set();
|
|
16299
|
+
const decl = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,80})\s{0,20}=\s{0,20}(?:\/(?![/*])|new\s{1,20}RegExp\b)/g;
|
|
16300
|
+
let m;
|
|
16301
|
+
while ((m = decl.exec(code)) !== null)
|
|
16302
|
+
names.add(m[1]);
|
|
16303
|
+
return names;
|
|
16304
|
+
}
|
|
16305
|
+
function afterFirstArg(args) {
|
|
16306
|
+
let depth = 0;
|
|
16307
|
+
let quote = null;
|
|
16308
|
+
for (let i = 0; i < args.length; i++) {
|
|
16309
|
+
const ch = args[i];
|
|
16310
|
+
if (quote) {
|
|
16311
|
+
if (ch === "\\") {
|
|
16312
|
+
i++;
|
|
16313
|
+
continue;
|
|
16314
|
+
}
|
|
16315
|
+
if (ch === quote)
|
|
16316
|
+
quote = null;
|
|
16317
|
+
continue;
|
|
16318
|
+
}
|
|
16319
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
16320
|
+
quote = ch;
|
|
16321
|
+
continue;
|
|
16322
|
+
}
|
|
16323
|
+
if (ch === "(" || ch === "[" || ch === "{")
|
|
16324
|
+
depth++;
|
|
16325
|
+
else if (ch === ")" || ch === "]" || ch === "}")
|
|
16326
|
+
depth--;
|
|
16327
|
+
else if (ch === "," && depth === 0)
|
|
16328
|
+
return args.slice(i + 1);
|
|
16329
|
+
}
|
|
16330
|
+
return "";
|
|
16331
|
+
}
|
|
16332
|
+
function analyzeRegexMatchers(rawCode) {
|
|
16333
|
+
if (!rawCode || typeof rawCode !== "string")
|
|
16334
|
+
return [];
|
|
16335
|
+
const code = stripComments2(rawCode.slice(0, MAX_LINT_INPUT));
|
|
16336
|
+
const out = [];
|
|
16337
|
+
const re = /\bexpect(?:\.soft)?\s{0,20}\(/g;
|
|
16338
|
+
let m;
|
|
16339
|
+
let examined = 0;
|
|
16340
|
+
let line = 1;
|
|
16341
|
+
let lineAt2 = 0;
|
|
16342
|
+
let regexNames;
|
|
16343
|
+
while ((m = re.exec(code)) !== null && examined++ < MAX_CALLS && out.length < MAX_REGEX_MATCHER_FINDINGS) {
|
|
16344
|
+
const subj = readBalanced(code, m.index + m[0].length);
|
|
16345
|
+
if (!subj)
|
|
16346
|
+
continue;
|
|
16347
|
+
const mm = /^\s{0,20}(\.\s{0,20}not)?\s{0,20}\.\s{0,20}(\w{1,40})\s{0,20}\(/.exec(code.slice(subj.end + 1, subj.end + 1 + 80));
|
|
16348
|
+
if (!mm || !REGEX_EQUALITY_MATCHERS.has(mm[2]))
|
|
16349
|
+
continue;
|
|
16350
|
+
const args = readBalanced(code, subj.end + 1 + mm[0].length);
|
|
16351
|
+
if (!args)
|
|
16352
|
+
continue;
|
|
16353
|
+
const expected = mm[2] === "toHaveJSProperty" ? afterFirstArg(args.text) : args.text;
|
|
16354
|
+
if (!isBareRegexLiteral(expected))
|
|
16355
|
+
continue;
|
|
16356
|
+
if (mm[2] !== "toHaveJSProperty" && mm[2] !== "toContain") {
|
|
16357
|
+
const subject = subj.text.trim();
|
|
16358
|
+
if (startsWithRegexLiteral(subject) || /^new\s+RegExp\b/.test(subject))
|
|
16359
|
+
continue;
|
|
16360
|
+
if (/^[A-Za-z_$][\w$]{0,80}$/.test(subject)) {
|
|
16361
|
+
regexNames ??= regexDeclaredNames(code);
|
|
16362
|
+
if (regexNames.has(subject))
|
|
16363
|
+
continue;
|
|
16364
|
+
}
|
|
16365
|
+
}
|
|
16366
|
+
for (let i = code.indexOf("\n", lineAt2); i !== -1 && i < m.index; i = code.indexOf("\n", i + 1)) {
|
|
16367
|
+
line++;
|
|
16368
|
+
lineAt2 = i + 1;
|
|
16369
|
+
}
|
|
16370
|
+
out.push({ line, matcher: mm[2], negated: mm[1] !== void 0 });
|
|
16371
|
+
}
|
|
16372
|
+
return out;
|
|
16373
|
+
}
|
|
16374
|
+
function formatRegexMatcherNote(findings) {
|
|
16375
|
+
const lines = findings.map((f) => {
|
|
16376
|
+
const negated = f.matcher === "toContain" ? "throws on a string and otherwise always passes (a regex never equals an item), so it checks nothing" : "always passes (a regex never equals the value), so it checks nothing";
|
|
16377
|
+
return `Line ${f.line}: .${f.negated ? "not." : ""}${f.matcher}(\u2026 /regex/ \u2026) ${f.negated ? negated : "can never pass: this matcher compares by equality and a regex never equals a value"}.`;
|
|
16378
|
+
});
|
|
16379
|
+
return `${lines.join("\n")}
|
|
16380
|
+
A regex only works in toMatch, toHaveText, toContainText, toHaveValue and similar text matchers. For the \`value\` of an input, textarea or select, use await expect(loc).toHaveValue(/re/). For any other DOM property (or element), poll it so the check still retries: await expect.poll(() => loc.evaluate((n) => n.<prop>)).toMatch(/re/). Never read it once with expect(await loc.evaluate(\u2026)), which sees the value before the page updates it. Or give toHaveJSProperty the exact string (or boolean) the page shows. Keep every other line as-is.`;
|
|
16381
|
+
}
|
|
15260
16382
|
function lintTestQuality(rawCode, opts = {}) {
|
|
15261
16383
|
if (!rawCode || typeof rawCode !== "string")
|
|
15262
16384
|
return [];
|
|
@@ -15264,6 +16386,10 @@ function lintTestQuality(rawCode, opts = {}) {
|
|
|
15264
16386
|
return lintTestData(rawCode, { pageContacts: opts.pageContacts });
|
|
15265
16387
|
if (opts.only === "test-data-blocking")
|
|
15266
16388
|
return analyzeTestData(rawCode, { pageContacts: opts.pageContacts }).blocking;
|
|
16389
|
+
if (opts.only === "regex-matcher") {
|
|
16390
|
+
const found = analyzeRegexMatchers(rawCode);
|
|
16391
|
+
return found.length > 0 ? [formatRegexMatcherNote(found)] : [];
|
|
16392
|
+
}
|
|
15267
16393
|
const warnings = [];
|
|
15268
16394
|
if (rawCode.length > MAX_LINT_INPUT) {
|
|
15269
16395
|
warnings.push(`The code is ${Math.round(rawCode.length / 1024)} KB; the quality lint checked only the first ${MAX_LINT_INPUT / 1e3} KB. Split it into smaller tests (one behaviour per test).`);
|
|
@@ -15538,7 +16664,7 @@ function specItemsFromTestPlans(testPlans) {
|
|
|
15538
16664
|
}
|
|
15539
16665
|
return [...new Set(out)].slice(0, 200);
|
|
15540
16666
|
}
|
|
15541
|
-
var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, STOP, SYNONYMS, AREA_TOKENS;
|
|
16667
|
+
var READ_ONLY_TAGS, PRODUCTION_READ_ONLY_GUARD_TEMPLATE, HOST_AGNOSTIC_URL_TEMPLATE, TEST_QUALITY_CONVENTIONS, NON_PROD_HOST_LABEL, NON_PROD_HOST_TOKEN, TRANSIENT_TEXT, STRING_LITERAL, FIRST_LITERAL, GET_BY_TEXT_LITERAL2, ROLE_NAME_LITERAL2, MAX_LINT_INPUT, MAX_BALANCED_SCAN, MAX_CALLS, MAX_SEEN_SCAN, ACTION_CALL, BY_NAME_LOCATOR, ACTION_AFTER, MAX_CONTACTS, MAX_SNAPSHOT_SCAN, PHONE_IN_TEXT, EMAIL_IN_TEXT, TEL_HREF, MAILTO_HREF, FIELD_LINE, INBOX_EMAIL_READ, MAIL_READ_CALL, RESERVED_EMAIL_TLD, PHONE_LABEL, NON_PHONE_NUMBER_LABEL, IDENTITY_LABEL, LOOKUP_LABEL, NON_NAME_SUFFIX_LABEL, DATE_LIKE, SIGNUP_ACTION_NAME, SIGNUP_SUBMIT_NAME, LOGIN_ACTION_NAME, SIGNUP_GOTO, LOGIN_GOTO, MINTED_INBOX_ALIAS, STAMP_EVAL, RECORDING_VALUE_PLACEHOLDERS, NEGATIVE_TEST_TITLE, NEGATIVE_SIGNUP_ASSERT, STORED_LOGIN_RE, CONFIRM_CLICK_RE, SAVE_UPDATE_RE, NOT_A_CONFIRM_RE, ENTITY_WORDS, CREATE_ENTITY_CLICK_RE, RESTORE_CLICK_RE, MAX_ACCOUNT_ACTIONS, MAX_TRY_BLOCKS, MAX_STATEMENT_LOOKBACK, MAX_FINDINGS5, MAX_TRY_BODY, AUTH_REGION_START, AUTH_REGION_END, INBOX_REGION_START, INBOX_REGION_END, ACCOUNT_ROUTE_RE, ENTITY_WORD_RES, PERSIST_CALL_RE, FILL_IDENT_ARG_RE, PERSIST_ARGS_RE, HOST_IN_URL_ARG, URL_WAIT_TOKEN, GLOB_ESCAPED, URL_WAIT_MAX_PATH, URL_WAIT_MAX_GLOB_STARS, UNREACHED_MAX_PATHS, UNREACHED_MATCH_BUDGET, STALE_TOKEN, RESULT_WAIT, WRITE_CALL_PRESENT, SUBMIT_CALL_PRESENT, TEST_CALL, VALUE_WRITE_CALLS, SUBMIT_CALLS, DISTINCT_PAGE_CALLS, OWN_CONTROL_MATCHERS, OUTCOME_CONTENT_MATCHERS, OUTCOME_STATE_MATCHERS, ANY_CONTENT_ARG, ROOT_SUBJECT, LITERAL_SUBJECT, MATCHER_AFTER_EXPECT, STALE_MAX_EVENTS, CHECKED, UNCHECKED, squash, NOT_JUDGED, STALE_REASON_TEXT, LIVE_WRITE_NAME, BUNDLED_CHECKS_BEFORE_WRITE, MAX_REGEX_MATCHER_FINDINGS, REGEX_EQUALITY_MATCHERS, STOP, SYNONYMS, AREA_TOKENS;
|
|
15542
16668
|
var init_test_authoring_quality = __esm({
|
|
15543
16669
|
"../shared/dist/test-authoring-quality.js"() {
|
|
15544
16670
|
"use strict";
|
|
@@ -15770,6 +16896,8 @@ var init_test_authoring_quality = __esm({
|
|
|
15770
16896
|
};
|
|
15771
16897
|
LIVE_WRITE_NAME = /\b(?:submit|place|book|reserve|confirm|pay|send|checkout|order|register|sign\s?up|create account)\b/i;
|
|
15772
16898
|
BUNDLED_CHECKS_BEFORE_WRITE = 5;
|
|
16899
|
+
MAX_REGEX_MATCHER_FINDINGS = 10;
|
|
16900
|
+
REGEX_EQUALITY_MATCHERS = /* @__PURE__ */ new Set(["toBe", "toEqual", "toStrictEqual", "toContain", "toHaveJSProperty"]);
|
|
15773
16901
|
STOP = /* @__PURE__ */ new Set([
|
|
15774
16902
|
"a",
|
|
15775
16903
|
"an",
|
|
@@ -16008,7 +17136,7 @@ function normalizeBugPath(route) {
|
|
|
16008
17136
|
if (hash >= 0 && path[hash + 1] !== "/")
|
|
16009
17137
|
path = path.slice(0, hash);
|
|
16010
17138
|
const segments = path.toLowerCase().split("/").map((segment) => /\d/.test(segment) ? ":id" : segment);
|
|
16011
|
-
const joined = segments.join("/")
|
|
17139
|
+
const joined = stripTrailingSlashes(segments.join("/"));
|
|
16012
17140
|
return joined || (path.startsWith("/") ? "/" : "");
|
|
16013
17141
|
}
|
|
16014
17142
|
function isGenericConsoleMessage(message) {
|
|
@@ -16021,14 +17149,17 @@ function requestSignatures(text) {
|
|
|
16021
17149
|
const statuses = [...text.matchAll(HTTP_STATUS)].map((m) => ({ status: m[1], at: m.index ?? 0, end: (m.index ?? 0) + m[0].length }));
|
|
16022
17150
|
if (statuses.length === 0)
|
|
16023
17151
|
return [];
|
|
16024
|
-
const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) =>
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
17152
|
+
const paths = [...text.matchAll(PATH_IN_TEXT)].map((m) => {
|
|
17153
|
+
const rawPath = stripTrailingChars(m[2] ?? m[3] ?? "", PATH_TRAILING_PUNCTUATION);
|
|
17154
|
+
return {
|
|
17155
|
+
method: m[1] ?? null,
|
|
17156
|
+
path: normalizeBugPath(rawPath),
|
|
17157
|
+
// Before normalisation, which turns a segment with a digit (favicon-32x32.png) into :id.
|
|
17158
|
+
incidental: isIncidentalAssetPath(rawPath.split(/[?#]/, 1)[0].toLowerCase()),
|
|
17159
|
+
at: m.index ?? 0,
|
|
17160
|
+
end: (m.index ?? 0) + m[0].length
|
|
17161
|
+
};
|
|
17162
|
+
}).filter((p) => p.path.length > 1);
|
|
16032
17163
|
const out = /* @__PURE__ */ new Map();
|
|
16033
17164
|
for (const { status, at, end } of statuses) {
|
|
16034
17165
|
const lead = STATUS_TO_PATH.exec(text.slice(end, end + 40));
|
|
@@ -16138,10 +17269,12 @@ function dedupeSiteWideSuspectedBugs(drafts) {
|
|
|
16138
17269
|
delete plans.suspectedBugs;
|
|
16139
17270
|
}
|
|
16140
17271
|
}
|
|
16141
|
-
var SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_JS_MESSAGES, GENERIC_JS_PREFIXES, JS_ERROR, MESSAGE_CLAUSE_END, HTTP_STATUS, PATH_IN_TEXT, STATUS_TO_PATH, INCIDENTAL_ASSET;
|
|
17272
|
+
var PATH_TRAILING_PUNCTUATION, SIGNATURE_SOURCE_MAX_CHARS, UUID, PREFIXED_ID, HEX_RUN, NUMBER_RUN, GENERIC_JS_MESSAGES, GENERIC_JS_PREFIXES, JS_ERROR, MESSAGE_CLAUSE_END, HTTP_STATUS, PATH_IN_TEXT, STATUS_TO_PATH, INCIDENTAL_ASSET;
|
|
16142
17273
|
var init_survey_bug_signature = __esm({
|
|
16143
17274
|
"../shared/dist/survey-bug-signature.js"() {
|
|
16144
17275
|
"use strict";
|
|
17276
|
+
init_trailing_slashes();
|
|
17277
|
+
PATH_TRAILING_PUNCTUATION = /[.,;:]/;
|
|
16145
17278
|
SIGNATURE_SOURCE_MAX_CHARS = 800;
|
|
16146
17279
|
UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g;
|
|
16147
17280
|
PREFIXED_ID = /\b[a-z]{1,8}[-_]\d+\b/g;
|
|
@@ -17422,9 +18555,9 @@ function lastLocatorLabels(chain) {
|
|
|
17422
18555
|
if (t)
|
|
17423
18556
|
labels.push(t[1]);
|
|
17424
18557
|
}
|
|
17425
|
-
const
|
|
17426
|
-
if (
|
|
17427
|
-
labels.push(
|
|
18558
|
+
const has2 = optionText(last.args, HAS_TEXT_OPTION_RE);
|
|
18559
|
+
if (has2 !== null)
|
|
18560
|
+
labels.push(has2);
|
|
17428
18561
|
}
|
|
17429
18562
|
return labels.map((l) => l.replace(/^[\s^(?:]+/, "").trim());
|
|
17430
18563
|
}
|
|
@@ -17472,7 +18605,7 @@ function pathIdsIn(text) {
|
|
|
17472
18605
|
function findCounterIdPins(code) {
|
|
17473
18606
|
if (code.length > CLOCK_MAX_CODE_CHARS)
|
|
17474
18607
|
return [];
|
|
17475
|
-
const
|
|
18608
|
+
const clean3 = stripLineComments(blankBlockComments3(code));
|
|
17476
18609
|
const fillValues = [];
|
|
17477
18610
|
const candidates = [];
|
|
17478
18611
|
let seenWrite = false;
|
|
@@ -17480,24 +18613,24 @@ function findCounterIdPins(code) {
|
|
|
17480
18613
|
CALL_RE.lastIndex = 0;
|
|
17481
18614
|
let m;
|
|
17482
18615
|
let calls = 0;
|
|
17483
|
-
while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(
|
|
18616
|
+
while (calls++ < MAX_CALLS2 && (m = CALL_RE.exec(clean3)) !== null) {
|
|
17484
18617
|
const name = m[1];
|
|
17485
18618
|
const open = m.index + m[0].length - 1;
|
|
17486
18619
|
if (VALUE_WRITES.has(name)) {
|
|
17487
18620
|
seenWrite = true;
|
|
17488
|
-
const args2 = argsAt(
|
|
18621
|
+
const args2 = argsAt(clean3, open);
|
|
17489
18622
|
if (args2 !== null)
|
|
17490
18623
|
fillValues.push(...staticStrings(args2));
|
|
17491
18624
|
continue;
|
|
17492
18625
|
}
|
|
17493
18626
|
if (createIndex === -1) {
|
|
17494
|
-
if (name === "click" && seenWrite && isCreateClick(
|
|
18627
|
+
if (name === "click" && seenWrite && isCreateClick(clean3, m.index))
|
|
17495
18628
|
createIndex = m.index;
|
|
17496
18629
|
continue;
|
|
17497
18630
|
}
|
|
17498
18631
|
if (name === "click")
|
|
17499
18632
|
continue;
|
|
17500
|
-
const args = argsAt(
|
|
18633
|
+
const args = argsAt(clean3, open);
|
|
17501
18634
|
if (args === null)
|
|
17502
18635
|
continue;
|
|
17503
18636
|
const isUrl = URL_SINKS.has(name);
|
|
@@ -17516,7 +18649,7 @@ function findCounterIdPins(code) {
|
|
|
17516
18649
|
}
|
|
17517
18650
|
if (createIndex === -1)
|
|
17518
18651
|
return [];
|
|
17519
|
-
const before =
|
|
18652
|
+
const before = clean3.slice(0, createIndex);
|
|
17520
18653
|
let pathIdsBefore = null;
|
|
17521
18654
|
const findings = [];
|
|
17522
18655
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -17540,7 +18673,7 @@ function findCounterIdPins(code) {
|
|
|
17540
18673
|
continue;
|
|
17541
18674
|
}
|
|
17542
18675
|
}
|
|
17543
|
-
findings.push({ kind: c.kind, literal: c.literal, line: lineAt(
|
|
18676
|
+
findings.push({ kind: c.kind, literal: c.literal, line: lineAt(clean3, c.index) });
|
|
17544
18677
|
}
|
|
17545
18678
|
return findings;
|
|
17546
18679
|
}
|
|
@@ -17835,7 +18968,7 @@ var init_suspicious_expectation = __esm({
|
|
|
17835
18968
|
NOT_STUCK_RE = /\b(?:not|never|no longer|isn't|doesn't|don't|without)\s+(?:\w+\s+){0,2}?(?:stuck|hang|hangs|hung|freeze|freezes|frozen|spin|spins|spinning)\b/i;
|
|
17836
18969
|
LISTS_STUCK_RE = /\b(?:appears? in (?:the )?[\w ]{0,25}(?:report|list|dashboard|queue)|(?:lists?|monitors?|shows?|reports?) (?:\w+ ){0,2}(?:that|which|whose) (?:are |is )?(?:stuck|hangs?|hung|frozen)|can (?:retry|re-?run) (?:\w+ ){0,2}(?:that (?:are )?)?stuck)\b/i;
|
|
17837
18970
|
STUCK_VETO_RE = new RegExp(`${NOT_STUCK_RE.source}|${LISTS_STUCK_RE.source}`, "i");
|
|
17838
|
-
NOT_SILENT_RE = /\b(?:not|never|\w+n't|instead of|rather than|avoids?|prevents?)\s+(?:\w+\s+){0,1}?(?:fail(?:s|ing|ed)?|ignor\w*)
|
|
18971
|
+
NOT_SILENT_RE = /\b(?:not|never|\w+n't|instead of|rather than|avoids?|prevents?)\s+(?:\w+\s+){0,1}?(?:(?:fail(?:s|ing|ed)?|ignor\w*)\s*)?silent(?:ly)?\b/i;
|
|
17839
18972
|
PENDING_WORD = "(?:sending|loading|saving|searching|checking(?!\\s+(?:account|balance|out|in)\\b)|syncing|refreshing|fetching|validating|processing|submitting|uploading|posting|publishing|creating|updating|deleting|connecting|verifying|signing in|logging in)";
|
|
17840
18973
|
ELLIPSIS = "(?:\\.{3}|\u2026)";
|
|
17841
18974
|
NOT_STAY_RE = /\b(?:not|never|no longer|without|n't|cannot)\s+(?:\w+\s+){0,2}?(?:stay|stays|stayed|remain|remains|remained|leave|leaves|left)\b/i;
|
|
@@ -17991,7 +19124,7 @@ function outcomeNamedByAssertion(outcome, code) {
|
|
|
17991
19124
|
function namedByStatements(outcome, statements) {
|
|
17992
19125
|
const text = outcomeText(outcome);
|
|
17993
19126
|
if (outcome.type === "url") {
|
|
17994
|
-
const routes = (text.match(ROUTE_TERM) ?? []).map((r) => r
|
|
19127
|
+
const routes = (text.match(ROUTE_TERM) ?? []).map((r) => stripTrailingChars(r, ROUTE_TRAILING_PUNCTUATION)).filter((r) => r.length > 1);
|
|
17995
19128
|
return statements.some((stmt) => {
|
|
17996
19129
|
if (!/\.(?:waitForURL|toHaveURL)\s{0,20}\(/.test(stmt))
|
|
17997
19130
|
return false;
|
|
@@ -18163,7 +19296,7 @@ function evaluatePromotionGate(input, checks = PROMOTION_QUALITY_CHECKS) {
|
|
|
18163
19296
|
...suspiciousTagged ? { suspiciousPass: { mode: suspicious, covered: suspiciousCovered, wouldHold: suspiciousHold } } : {}
|
|
18164
19297
|
};
|
|
18165
19298
|
}
|
|
18166
|
-
var SOURCE_RANK, strengthCheck, STRING_CONST, TYPED_VALUE, TYPED_CONSTANT, MAX_STATEMENTS, MAX_STATEMENT_CHARS, LITERAL_OR_IDENTIFIER, MUTATING_ACTION, FEEDBACK_REGION, ONE_SHOT_MESSAGE_REASON, FEEDBACK_REGION_ALL, dynamicAssertionCheck, OUTCOME_KIND_MATCHER, ROUTE_TERM, QUOTED_LITERAL, GENERIC_LITERAL, MAX_OUTCOME_STATEMENTS, MAX_OUTCOMES, MAX_OUTCOME_TEXT_CHARS, expectedOutcomeCheck, testDataCheck, sharedAccountCheck, clockLiteralCheck, readinessPollCheck, knownBugTestCheck, bugClaimContractCheck, suspiciousExpectationCheck, bugCandidateCheck, conventionOutcomesCheck, pendingHook, locatorFragmentCheck, FILTER_STEP, NEXT_ACTION, REDUCTION_PROOF, MAX_FILTER_LINES, MAX_FILTER_LINE_CHARS, filterPositiveOnlyCheck, PROMOTION_QUALITY_CHECKS, PROMOTION_GATE_MAX_CODE_CHARS, NOT_EVALUATED, SUSPICIOUS_PASS_TAG, NOT_CONTENT_MATCHER, CONTENT_PROOF, NAMED_ELEMENT, NEEDS_REVIEW_TAG, count2, RECHECKED_NOTES, EVIDENCE_NOTE_PREFIXES, PENDING_RECHECK_NOTE_PREFIXES, STRENGTH_RANK;
|
|
19299
|
+
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;
|
|
18167
19300
|
var init_promotion_quality_gate = __esm({
|
|
18168
19301
|
"../shared/dist/promotion-quality-gate.js"() {
|
|
18169
19302
|
"use strict";
|
|
@@ -18178,17 +19311,21 @@ var init_promotion_quality_gate = __esm({
|
|
|
18178
19311
|
init_run_classification();
|
|
18179
19312
|
init_expected_outcome_coverage();
|
|
18180
19313
|
init_expect_call();
|
|
19314
|
+
init_trailing_slashes();
|
|
18181
19315
|
SOURCE_RANK = { generator: 0, classifier: 0, healer: 1, human: 2 };
|
|
18182
19316
|
strengthCheck = {
|
|
18183
19317
|
id: "assertion-strength",
|
|
18184
|
-
run({ code, strengthGate, flagContradictedAdditions }) {
|
|
18185
|
-
const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true });
|
|
19318
|
+
run({ code, tags, strengthGate, flagContradictedAdditions, lastActionProof }) {
|
|
19319
|
+
const verdict = validateAssertionStrength(code, { flagContradictedAdditions: flagContradictedAdditions === true, lastActionProof });
|
|
18186
19320
|
if (verdict.severity === "route-bug")
|
|
18187
19321
|
return [{ check: "route-bug" }];
|
|
18188
|
-
|
|
19322
|
+
const authDowngrade = verdict.severity === "downgrade" && tags.some((t) => t.toLowerCase() === "@auth");
|
|
19323
|
+
if (verdict.severity !== "block" && !authDowngrade)
|
|
18189
19324
|
return [];
|
|
18190
19325
|
const revealed = strengthGate?.severity === "none" && strengthGate.revealEvidence === "revealed" && strengthGate.source !== "heal";
|
|
18191
|
-
|
|
19326
|
+
if (revealed)
|
|
19327
|
+
return [];
|
|
19328
|
+
return [{ check: authDowngrade ? "strength-downgrade-auth" : "strength-block" }];
|
|
18192
19329
|
}
|
|
18193
19330
|
};
|
|
18194
19331
|
STRING_CONST = /\b(?:const|let|var)\s{1,20}([A-Za-z_$][\w$]{0,60})\s{0,20}=\s{0,20}(?:'([^'\\\n]{0,300})'|"([^"\\\n]{0,300})"|`([^`\\$\n]{0,300})`)\s{0,20}[;\n]/g;
|
|
@@ -18197,7 +19334,7 @@ var init_promotion_quality_gate = __esm({
|
|
|
18197
19334
|
MAX_STATEMENTS = 500;
|
|
18198
19335
|
MAX_STATEMENT_CHARS = 400;
|
|
18199
19336
|
LITERAL_OR_IDENTIFIER = /'(?:[^'\\\n]|\\.){0,500}'|"(?:[^"\\\n]|\\.){0,500}"|`(?:[^`\\]|\\.){0,2000}`|(?<![\w$.])[A-Za-z_$][\w$]{0,60}/g;
|
|
18200
|
-
MUTATING_ACTION = /\.(?:click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s{0,20}\(|\brequest\s{0,20}\.\s{0,20}(?:post|put|patch|delete|fetch)\s{0,20}\(/;
|
|
19337
|
+
MUTATING_ACTION = /\.(?:click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s{0,20}\(|\brequest\s{0,20}\.\s{0,20}(?:post|put|patch|delete|fetch)\s{0,20}\(/;
|
|
18201
19338
|
FEEDBACK_REGION = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/i;
|
|
18202
19339
|
ONE_SHOT_MESSAGE_REASON = "one-shot success/error banner text";
|
|
18203
19340
|
FEEDBACK_REGION_ALL = /getByRole\s{0,20}\(\s{0,20}['"`](?:alert|status)['"`]|\baria-live\b|\btoast|\bsnackbar\b|\bflash.?message\b/gi;
|
|
@@ -18256,6 +19393,7 @@ var init_promotion_quality_gate = __esm({
|
|
|
18256
19393
|
"element-hidden": /\.(?:toBeHidden|not\s{0,20}\.\s{0,20}toBeVisible|not\s{0,20}\.\s{0,20}toBeAttached|not\s{0,20}\.\s{0,20}toContainText)\s{0,20}\(|\.toHaveCount\s{0,20}\(\s{0,20}0\s{0,20}\)/
|
|
18257
19394
|
};
|
|
18258
19395
|
ROUTE_TERM = /\/[A-Za-z0-9_./?&=#-]{1,200}/g;
|
|
19396
|
+
ROUTE_TRAILING_PUNCTUATION = /[.,;:!?)]/;
|
|
18259
19397
|
QUOTED_LITERAL = /'([^'\\\n]{3,200})'|"([^"\\\n]{3,200})"|`([^`\\$\n]{3,200})`/g;
|
|
18260
19398
|
GENERIC_LITERAL = /^(?:true|false|button|link|heading|dialog|alertdialog|article|main|region|status|alert|textbox|combobox|checkbox|radio|tab|tabpanel|option|list|listitem|row|cell|img|navigation|banner|complementary|contentinfo|form|search|menu|menuitem|visible|hidden|attached|detached|domcontentloaded|load|networkidle)$/i;
|
|
18261
19399
|
MAX_OUTCOME_STATEMENTS = 300;
|
|
@@ -18375,8 +19513,8 @@ ${goalText}`);
|
|
|
18375
19513
|
return count3 > 0 ? [{ check: "locator-fragment", detail: String(count3), soft: true }] : [];
|
|
18376
19514
|
}
|
|
18377
19515
|
};
|
|
18378
|
-
FILTER_STEP = /\.(?:getByLabel|getByPlaceholder|getByRole|getByTitle)\s*\([^)\n]{0,200}?\b(?:filter|sort|search|status|delete|remove|dismiss|archive|clear)\b[^)\n]{0,200}\)[^\n]{0,200}\.(?:selectOption|fill|type|pressSequentially|click|check|uncheck|press)\s*\(/i;
|
|
18379
|
-
NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|selectOption|setInputFiles|dragTo|clear)\s*\(/;
|
|
19516
|
+
FILTER_STEP = /\.(?:getByLabel|getByPlaceholder|getByRole|getByTitle)\s*\([^)\n]{0,200}?\b(?:filter|sort|search|status|delete|remove|dismiss|archive|clear)\b[^)\n]{0,200}\)[^\n]{0,200}\.(?:selectOption|fill|type|pressSequentially|click|check|uncheck|setChecked|press)\s*\(/i;
|
|
19517
|
+
NEXT_ACTION = /\.(?:goto|reload|click|dblclick|tap|fill|type|pressSequentially|press|check|uncheck|setChecked|selectOption|setInputFiles|dragTo|dragAndDrop|clear)\s*\(/;
|
|
18380
19518
|
REDUCTION_PROOF = /\.toHaveCount\s*\(|\.not\s*\.\s*(?:toBeVisible|toBeAttached|toContainText|toHaveText)\s*\(|\.toBeHidden\s*\(|\bShowing\b|\bof \d+\b|\b\d+ (?:results?|rows?|items?|candidates?|entries|matches|records)\b|\bNo (?:[\w'-]+ ){0,3}(?:match|matches|results?|found|left|remain|yet)\b|\bNothing (?:to show|found|matches)\b|\bis empty\b|\btoHaveURL\b/i;
|
|
18381
19519
|
MAX_FILTER_LINES = 2e3;
|
|
18382
19520
|
MAX_FILTER_LINE_CHARS = 600;
|
|
@@ -18491,7 +19629,7 @@ ${goalText}`);
|
|
|
18491
19629
|
});
|
|
18492
19630
|
|
|
18493
19631
|
// ../shared/dist/regex-alternation.js
|
|
18494
|
-
function
|
|
19632
|
+
function closingParens2(source) {
|
|
18495
19633
|
const close = new Int32Array(source.length).fill(-1);
|
|
18496
19634
|
const open = [];
|
|
18497
19635
|
let inClass = false;
|
|
@@ -18516,7 +19654,7 @@ function closingParens(source) {
|
|
|
18516
19654
|
return close;
|
|
18517
19655
|
}
|
|
18518
19656
|
function alternatesAtTop(source) {
|
|
18519
|
-
const close =
|
|
19657
|
+
const close = closingParens2(source);
|
|
18520
19658
|
let start = source.startsWith("^") ? 1 : 0;
|
|
18521
19659
|
let end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
18522
19660
|
while (start < end && source[start] === "(" && close[start] === end - 1) {
|
|
@@ -18565,6 +19703,124 @@ var init_page_evidence = __esm({
|
|
|
18565
19703
|
}
|
|
18566
19704
|
});
|
|
18567
19705
|
|
|
19706
|
+
// ../shared/dist/stated-limits.js
|
|
19707
|
+
function validDate(v) {
|
|
19708
|
+
const [y, m, d] = v.split("-").map(Number);
|
|
19709
|
+
const t = new Date(Date.UTC(y, m - 1, d));
|
|
19710
|
+
return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
|
|
19711
|
+
}
|
|
19712
|
+
function isoFor(type, v) {
|
|
19713
|
+
const re = DATE_RE[type];
|
|
19714
|
+
if (!re || !re.test(v))
|
|
19715
|
+
return false;
|
|
19716
|
+
if (type === "date")
|
|
19717
|
+
return validDate(v);
|
|
19718
|
+
if (type === "datetime-local")
|
|
19719
|
+
return validDate(v.slice(0, 10)) && Number(v.slice(11, 13)) < 24 && Number(v.slice(14, 16)) < 60;
|
|
19720
|
+
if (type === "time")
|
|
19721
|
+
return Number(v.slice(0, 2)) < 24 && Number(v.slice(3, 5)) < 60;
|
|
19722
|
+
if (type === "month")
|
|
19723
|
+
return Number(v.slice(5, 7)) >= 1 && Number(v.slice(5, 7)) <= 12;
|
|
19724
|
+
return Number(v.slice(6, 8)) >= 1 && Number(v.slice(6, 8)) <= 53;
|
|
19725
|
+
}
|
|
19726
|
+
function clean2(raw) {
|
|
19727
|
+
return typeof raw === "string" ? raw.trim() : "";
|
|
19728
|
+
}
|
|
19729
|
+
function statedLimitsFromAttrs(attrs, inputType) {
|
|
19730
|
+
const type = (inputType ?? "").trim().toLowerCase();
|
|
19731
|
+
const limits = [];
|
|
19732
|
+
const add = (k, v) => {
|
|
19733
|
+
limits.push({ k, v });
|
|
19734
|
+
};
|
|
19735
|
+
if (NUMBER_TYPES.has(type)) {
|
|
19736
|
+
for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
|
|
19737
|
+
const v = clean2(attrs[attr]);
|
|
19738
|
+
if (NUMBER_RE.test(v))
|
|
19739
|
+
add(k, v);
|
|
19740
|
+
}
|
|
19741
|
+
const step = clean2(attrs.step);
|
|
19742
|
+
if (NUMBER_RE.test(step) && Number(step) > 0)
|
|
19743
|
+
add("step", step);
|
|
19744
|
+
} else if (type in DATE_RE) {
|
|
19745
|
+
for (const [attr, k] of [["min", "min"], ["max", "max"]]) {
|
|
19746
|
+
const v = clean2(attrs[attr]);
|
|
19747
|
+
if (isoFor(type, v))
|
|
19748
|
+
add(k, v);
|
|
19749
|
+
}
|
|
19750
|
+
} else if (TEXT_TYPES.has(type)) {
|
|
19751
|
+
for (const [attr, k] of [["minlength", "minLength"], ["maxlength", "maxLength"]]) {
|
|
19752
|
+
const v = clean2(attrs[attr]);
|
|
19753
|
+
if (LENGTH_RE.test(v))
|
|
19754
|
+
add(k, String(Number(v)));
|
|
19755
|
+
}
|
|
19756
|
+
const pattern = cleanPattern(clean2(attrs.pattern).slice(0, STATED_PATTERN_MAX));
|
|
19757
|
+
if (pattern)
|
|
19758
|
+
add("pattern", pattern);
|
|
19759
|
+
}
|
|
19760
|
+
return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
|
|
19761
|
+
}
|
|
19762
|
+
function cleanPattern(text) {
|
|
19763
|
+
return text.replace(/[\u0000-\u001f\u007f]/g, "").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "");
|
|
19764
|
+
}
|
|
19765
|
+
function readStatedLimits(stored) {
|
|
19766
|
+
if (!stored || typeof stored !== "object" || Array.isArray(stored))
|
|
19767
|
+
return [];
|
|
19768
|
+
const rec = stored;
|
|
19769
|
+
if (rec.v !== STATED_LIMITS_VERSION || !Array.isArray(rec.limits))
|
|
19770
|
+
return [];
|
|
19771
|
+
const out = [];
|
|
19772
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19773
|
+
for (const item of rec.limits.slice(0, MAX_LIMITS * 2)) {
|
|
19774
|
+
if (!item || typeof item !== "object")
|
|
19775
|
+
continue;
|
|
19776
|
+
const { k, v } = item;
|
|
19777
|
+
if (typeof k !== "string" || typeof v !== "string" || !KINDS.includes(k) || seen.has(k))
|
|
19778
|
+
continue;
|
|
19779
|
+
if (v.length === 0 || v.length > STATED_PATTERN_MAX)
|
|
19780
|
+
continue;
|
|
19781
|
+
const text = k === "pattern" ? cleanPattern(v) : v;
|
|
19782
|
+
if (text === "")
|
|
19783
|
+
continue;
|
|
19784
|
+
if (k === "minLength" || k === "maxLength") {
|
|
19785
|
+
if (!LENGTH_RE.test(v))
|
|
19786
|
+
continue;
|
|
19787
|
+
} else if (k === "step") {
|
|
19788
|
+
if (!NUMBER_RE.test(v))
|
|
19789
|
+
continue;
|
|
19790
|
+
} else if (k === "min" || k === "max") {
|
|
19791
|
+
if (!NUMBER_RE.test(v) && !Object.values(DATE_RE).some((re) => re.test(v)))
|
|
19792
|
+
continue;
|
|
19793
|
+
}
|
|
19794
|
+
seen.add(k);
|
|
19795
|
+
out.push({ k, v: text });
|
|
19796
|
+
}
|
|
19797
|
+
return out;
|
|
19798
|
+
}
|
|
19799
|
+
function statedLimitsRecord(limits) {
|
|
19800
|
+
return limits.length > 0 ? { v: STATED_LIMITS_VERSION, limits } : null;
|
|
19801
|
+
}
|
|
19802
|
+
var STATED_LIMITS_VERSION, STATED_PATTERN_MAX, KINDS, MAX_LIMITS, TEXT_TYPES, NUMBER_TYPES, DATE_RE, NUMBER_RE, LENGTH_RE;
|
|
19803
|
+
var init_stated_limits = __esm({
|
|
19804
|
+
"../shared/dist/stated-limits.js"() {
|
|
19805
|
+
"use strict";
|
|
19806
|
+
STATED_LIMITS_VERSION = 1;
|
|
19807
|
+
STATED_PATTERN_MAX = 200;
|
|
19808
|
+
KINDS = ["min", "max", "minLength", "maxLength", "step", "pattern"];
|
|
19809
|
+
MAX_LIMITS = KINDS.length;
|
|
19810
|
+
TEXT_TYPES = /* @__PURE__ */ new Set(["text", "search", "email", "url", "tel", "password", "textarea", ""]);
|
|
19811
|
+
NUMBER_TYPES = /* @__PURE__ */ new Set(["number", "range"]);
|
|
19812
|
+
DATE_RE = {
|
|
19813
|
+
date: /^\d{4}-\d{2}-\d{2}$/,
|
|
19814
|
+
"datetime-local": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?$/,
|
|
19815
|
+
time: /^\d{2}:\d{2}(?::\d{2})?$/,
|
|
19816
|
+
month: /^\d{4}-\d{2}$/,
|
|
19817
|
+
week: /^\d{4}-W\d{2}$/
|
|
19818
|
+
};
|
|
19819
|
+
NUMBER_RE = /^-?\d{1,15}(?:\.\d{1,10})?$/;
|
|
19820
|
+
LENGTH_RE = /^\d{1,7}$/;
|
|
19821
|
+
}
|
|
19822
|
+
});
|
|
19823
|
+
|
|
18568
19824
|
// ../shared/dist/index.js
|
|
18569
19825
|
var dist_exports = {};
|
|
18570
19826
|
__export(dist_exports, {
|
|
@@ -18572,9 +19828,11 @@ __export(dist_exports, {
|
|
|
18572
19828
|
ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
|
|
18573
19829
|
ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
|
|
18574
19830
|
ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
|
|
19831
|
+
ADMIN_FORCE_CANCELLED_RUN_ERROR: () => ADMIN_FORCE_CANCELLED_RUN_ERROR,
|
|
18575
19832
|
ADMIN_VIEW_DEFAULT_RETURN_TO: () => ADMIN_VIEW_DEFAULT_RETURN_TO,
|
|
18576
19833
|
AGENT_AI_PROVIDER_PREFIXES: () => AGENT_AI_PROVIDER_PREFIXES,
|
|
18577
19834
|
AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
|
|
19835
|
+
ALLOWED_PROVIDER_CHAIN_HOPS: () => ALLOWED_PROVIDER_CHAIN_HOPS,
|
|
18578
19836
|
API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
|
|
18579
19837
|
API_MODEL_CATALOG: () => API_MODEL_CATALOG,
|
|
18580
19838
|
API_MODEL_FIELDS: () => API_MODEL_FIELDS,
|
|
@@ -18595,6 +19853,7 @@ __export(dist_exports, {
|
|
|
18595
19853
|
AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
|
|
18596
19854
|
AUTH_PATH_SIGNATURE: () => AUTH_PATH_SIGNATURE,
|
|
18597
19855
|
AUTH_PROVIDER_HOST_SIGNATURES: () => AUTH_PROVIDER_HOST_SIGNATURES,
|
|
19856
|
+
AUTH_SESSION_GENERATION_RE: () => AUTH_SESSION_GENERATION_RE,
|
|
18598
19857
|
AUTH_STATE_EXPIRY_SKEW_MS: () => AUTH_STATE_EXPIRY_SKEW_MS,
|
|
18599
19858
|
AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
|
|
18600
19859
|
AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
|
|
@@ -18613,12 +19872,14 @@ __export(dist_exports, {
|
|
|
18613
19872
|
BUG_TEST_BROKEN_REASON: () => BUG_TEST_BROKEN_REASON,
|
|
18614
19873
|
BUG_TEST_BROKEN_TAG: () => BUG_TEST_BROKEN_TAG,
|
|
18615
19874
|
CACHE_PRICE_PER_M: () => CACHE_PRICE_PER_M,
|
|
19875
|
+
CANCELLED_REASON_PREFIXES: () => CANCELLED_REASON_PREFIXES,
|
|
18616
19876
|
CANVAS_UNSUPPORTED_BODY: () => CANVAS_UNSUPPORTED_BODY,
|
|
18617
19877
|
CANVAS_UNSUPPORTED_CODE: () => CANVAS_UNSUPPORTED_CODE,
|
|
18618
19878
|
CANVAS_UNSUPPORTED_NOT_CHARGED: () => CANVAS_UNSUPPORTED_NOT_CHARGED,
|
|
18619
19879
|
CANVAS_UNSUPPORTED_OVERRIDE_HINT: () => CANVAS_UNSUPPORTED_OVERRIDE_HINT,
|
|
18620
19880
|
CANVAS_UNSUPPORTED_TITLE: () => CANVAS_UNSUPPORTED_TITLE,
|
|
18621
19881
|
CAPTURE_JSON_STRUCTURE_LIMITS: () => CAPTURE_JSON_STRUCTURE_LIMITS,
|
|
19882
|
+
CHAIN_CANCELLED_RUN_ERROR: () => CHAIN_CANCELLED_RUN_ERROR,
|
|
18622
19883
|
CHAT_FAILOVER_KEY: () => CHAT_FAILOVER_KEY,
|
|
18623
19884
|
CLAUDE_CLI_DEFAULT_EFFORT: () => CLAUDE_CLI_DEFAULT_EFFORT,
|
|
18624
19885
|
CLAUDE_CLI_DEFAULT_MODEL: () => CLAUDE_CLI_DEFAULT_MODEL,
|
|
@@ -18655,6 +19916,7 @@ __export(dist_exports, {
|
|
|
18655
19916
|
DEFAULT_GEMINI_SCREEN_MODEL: () => DEFAULT_GEMINI_SCREEN_MODEL,
|
|
18656
19917
|
DEFAULT_JSON_STRUCTURE_LIMITS: () => DEFAULT_JSON_STRUCTURE_LIMITS,
|
|
18657
19918
|
DEFAULT_PLATFORM_DB_POOL_SIZE: () => DEFAULT_PLATFORM_DB_POOL_SIZE,
|
|
19919
|
+
DEFAULT_PROVIDER_ACCOUNT_ID: () => DEFAULT_PROVIDER_ACCOUNT_ID,
|
|
18658
19920
|
DEFAULT_SHARED_DB_POOL_SIZE: () => DEFAULT_SHARED_DB_POOL_SIZE,
|
|
18659
19921
|
DEFAULT_STT_MODEL: () => DEFAULT_STT_MODEL,
|
|
18660
19922
|
DEFAULT_STT_MODEL_BY_PROVIDER: () => DEFAULT_STT_MODEL_BY_PROVIDER,
|
|
@@ -18671,6 +19933,7 @@ __export(dist_exports, {
|
|
|
18671
19933
|
ENVIRONMENTAL_ERROR_STORED_PREFIXES: () => ENVIRONMENTAL_ERROR_STORED_PREFIXES,
|
|
18672
19934
|
EVIDENCE_NOTE_PREFIXES: () => EVIDENCE_NOTE_PREFIXES,
|
|
18673
19935
|
EXPECT_CALL_RE: () => EXPECT_CALL_RE,
|
|
19936
|
+
FEATURE_ROW_RUN_WINDOW: () => FEATURE_ROW_RUN_WINDOW,
|
|
18674
19937
|
FLOW_PHASE_LABELS: () => FLOW_PHASE_LABELS,
|
|
18675
19938
|
FLOW_PHASE_TYPES: () => FLOW_PHASE_TYPES,
|
|
18676
19939
|
FLOW_SERVER_TYPES: () => FLOW_SERVER_TYPES,
|
|
@@ -18727,6 +19990,8 @@ __export(dist_exports, {
|
|
|
18727
19990
|
LIVE_FRAME_STALE_MS: () => LIVE_FRAME_STALE_MS,
|
|
18728
19991
|
LIVE_FRAME_UNFOCUSED_INTERVAL_MS: () => LIVE_FRAME_UNFOCUSED_INTERVAL_MS,
|
|
18729
19992
|
LIVE_THUMBNAIL_MAX_BASE64_BYTES: () => LIVE_THUMBNAIL_MAX_BASE64_BYTES,
|
|
19993
|
+
MAX_BALANCED_SCAN: () => MAX_BALANCED_SCAN,
|
|
19994
|
+
MAX_CALLS: () => MAX_CALLS,
|
|
18730
19995
|
MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
|
|
18731
19996
|
MAX_DEEPLINK_LENGTH: () => MAX_DEEPLINK_LENGTH,
|
|
18732
19997
|
MAX_EMAIL_ADDRESS_LENGTH: () => MAX_EMAIL_ADDRESS_LENGTH,
|
|
@@ -18736,6 +20001,8 @@ __export(dist_exports, {
|
|
|
18736
20001
|
MAX_ORG_NAME_LENGTH: () => MAX_ORG_NAME_LENGTH,
|
|
18737
20002
|
MAX_PASSWORD_BYTES: () => MAX_PASSWORD_BYTES,
|
|
18738
20003
|
MAX_PLAYWRIGHT_CODE_LENGTH: () => MAX_PLAYWRIGHT_CODE_LENGTH,
|
|
20004
|
+
MAX_PROVIDER_ACCOUNTS: () => MAX_PROVIDER_ACCOUNTS,
|
|
20005
|
+
MAX_PROVIDER_ACCOUNT_MODELS: () => MAX_PROVIDER_ACCOUNT_MODELS,
|
|
18739
20006
|
MAX_SURVEY_ENTRY_URLS: () => MAX_SURVEY_ENTRY_URLS,
|
|
18740
20007
|
MAX_SURVEY_ENTRY_URL_LENGTH: () => MAX_SURVEY_ENTRY_URL_LENGTH,
|
|
18741
20008
|
MCP_ARMY_LIMITS: () => MCP_ARMY_LIMITS,
|
|
@@ -18771,6 +20038,7 @@ __export(dist_exports, {
|
|
|
18771
20038
|
MUSE_ENGINE_DEFAULT_MODEL: () => MUSE_ENGINE_DEFAULT_MODEL,
|
|
18772
20039
|
MUSE_ENGINE_MODELS: () => MUSE_ENGINE_MODELS,
|
|
18773
20040
|
NOREPLY_EMAIL: () => NOREPLY_EMAIL,
|
|
20041
|
+
OPENCODE_GO_DEEPSEEK_GLM_MODELS: () => OPENCODE_GO_DEEPSEEK_GLM_MODELS,
|
|
18774
20042
|
OPENCODE_GO_MODEL_PATTERN: () => OPENCODE_GO_MODEL_PATTERN,
|
|
18775
20043
|
OPENCODE_GO_PREFIX: () => OPENCODE_GO_PREFIX,
|
|
18776
20044
|
OPENROUTER_MAX_CALL_COST_USD: () => OPENROUTER_MAX_CALL_COST_USD,
|
|
@@ -18790,10 +20058,12 @@ __export(dist_exports, {
|
|
|
18790
20058
|
PLAN_FEATURES: () => PLAN_FEATURES,
|
|
18791
20059
|
PLAN_FEATURE_LOCKED_CODE: () => PLAN_FEATURE_LOCKED_CODE,
|
|
18792
20060
|
PLAN_ORDER: () => PLAN_ORDER,
|
|
20061
|
+
PLATFORM_ADMIN_CANCELLED_RUN_ERROR: () => PLATFORM_ADMIN_CANCELLED_RUN_ERROR,
|
|
18793
20062
|
PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
|
|
18794
20063
|
PLATFORM_DEFAULT_PROVIDER: () => PLATFORM_DEFAULT_PROVIDER,
|
|
18795
20064
|
PLATFORM_PLAN_BILLING: () => PLATFORM_PLAN_BILLING,
|
|
18796
20065
|
PLATFORM_SERVER_APPLICATION_NAME: () => PLATFORM_SERVER_APPLICATION_NAME,
|
|
20066
|
+
PLATFORM_SUPPORT_CANCELLED_RUN_ERROR: () => PLATFORM_SUPPORT_CANCELLED_RUN_ERROR,
|
|
18797
20067
|
POST_SPECIAL_TRIAL_DAYS: () => POST_SPECIAL_TRIAL_DAYS,
|
|
18798
20068
|
PRESET_DIRECT_FALLBACK: () => PRESET_DIRECT_FALLBACK,
|
|
18799
20069
|
PRODUCTION_READ_ONLY_GUARD_TEMPLATE: () => PRODUCTION_READ_ONLY_GUARD_TEMPLATE,
|
|
@@ -18805,17 +20075,25 @@ __export(dist_exports, {
|
|
|
18805
20075
|
PROJECT_OVERRIDE_FIELDS: () => PROJECT_OVERRIDE_FIELDS,
|
|
18806
20076
|
PROMOTION_GATE_MAX_CODE_CHARS: () => PROMOTION_GATE_MAX_CODE_CHARS,
|
|
18807
20077
|
PROMOTION_QUALITY_CHECKS: () => PROMOTION_QUALITY_CHECKS,
|
|
20078
|
+
PROVIDER_ACCOUNT_ID_PATTERN: () => PROVIDER_ACCOUNT_ID_PATTERN,
|
|
20079
|
+
PROVIDER_ACCOUNT_MODEL_PATTERN: () => PROVIDER_ACCOUNT_MODEL_PATTERN,
|
|
18808
20080
|
PROVIDER_CHAINS: () => PROVIDER_CHAINS,
|
|
20081
|
+
PROVIDER_CHAINS_SETTING_MAX_CHARS: () => PROVIDER_CHAINS_SETTING_MAX_CHARS,
|
|
20082
|
+
PROVIDER_CHAIN_LEADS: () => PROVIDER_CHAIN_LEADS,
|
|
20083
|
+
PROVIDER_CHAIN_MAX_ENTRIES: () => PROVIDER_CHAIN_MAX_ENTRIES,
|
|
20084
|
+
PROVIDER_HOST_NOT_OFFERED: () => PROVIDER_HOST_NOT_OFFERED,
|
|
18809
20085
|
PUBLIC_PLANS: () => PUBLIC_PLANS,
|
|
18810
20086
|
QUARANTINE_CLOSE_TAGS: () => QUARANTINE_CLOSE_TAGS,
|
|
18811
20087
|
READINESS_POLL_MAX_CODE_CHARS: () => READINESS_POLL_MAX_CODE_CHARS,
|
|
18812
20088
|
READ_ONLY_TAGS: () => READ_ONLY_TAGS,
|
|
18813
20089
|
RECHECKED_NOTES: () => RECHECKED_NOTES,
|
|
18814
20090
|
RECORDING_VALUE_PLACEHOLDERS: () => RECORDING_VALUE_PLACEHOLDERS,
|
|
20091
|
+
RENDER_IDENTITY_MAX_LENGTH: () => RENDER_IDENTITY_MAX_LENGTH,
|
|
18815
20092
|
REPOSITORY_CLIS: () => REPOSITORY_CLIS,
|
|
18816
20093
|
RESERVED_PROJECT_KEYS: () => RESERVED_PROJECT_KEYS,
|
|
18817
20094
|
RESULT_STRUCTURE_TOO_COSTLY_CODE: () => RESULT_STRUCTURE_TOO_COSTLY_CODE,
|
|
18818
20095
|
RETRIED_ATTEMPTS_KEY: () => RETRIED_ATTEMPTS_KEY,
|
|
20096
|
+
RETRY_HELD_RUN_ERROR: () => RETRY_HELD_RUN_ERROR,
|
|
18819
20097
|
RUNNER_INTERRUPT_ERROR_PREFIX: () => RUNNER_INTERRUPT_ERROR_PREFIX,
|
|
18820
20098
|
RUN_INTERRUPTED_ERROR_PREFIXES: () => RUN_INTERRUPTED_ERROR_PREFIXES,
|
|
18821
20099
|
RUN_INTERRUPTION_NOT_RETRIED_MARKERS: () => RUN_INTERRUPTION_NOT_RETRIED_MARKERS,
|
|
@@ -18828,6 +20106,8 @@ __export(dist_exports, {
|
|
|
18828
20106
|
SHARED_SERVER_APPLICATION_NAME: () => SHARED_SERVER_APPLICATION_NAME,
|
|
18829
20107
|
SPEND_SOURCE_KEY: () => SPEND_SOURCE_KEY,
|
|
18830
20108
|
SPEND_SOURCE_SERVER: () => SPEND_SOURCE_SERVER,
|
|
20109
|
+
STATED_LIMITS_VERSION: () => STATED_LIMITS_VERSION,
|
|
20110
|
+
STATED_PATTERN_MAX: () => STATED_PATTERN_MAX,
|
|
18831
20111
|
STATIC_STANDARD_MIN_INTERACTIVE: () => STATIC_STANDARD_MIN_INTERACTIVE,
|
|
18832
20112
|
STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
|
|
18833
20113
|
SUCCESS_NOTICE_TEXT: () => SUCCESS_NOTICE_TEXT,
|
|
@@ -18844,6 +20124,7 @@ __export(dist_exports, {
|
|
|
18844
20124
|
SURVEY_NOTES_RENDER_MAX_CHARS: () => SURVEY_NOTES_RENDER_MAX_CHARS,
|
|
18845
20125
|
SURVEY_NOTE_KEYS: () => SURVEY_NOTE_KEYS,
|
|
18846
20126
|
SUSPICIOUS_PASS_TAG: () => SUSPICIOUS_PASS_TAG,
|
|
20127
|
+
TEST_DID_NOT_RUN_ERROR: () => TEST_DID_NOT_RUN_ERROR,
|
|
18847
20128
|
TEST_QUALITY_CONVENTIONS: () => TEST_QUALITY_CONVENTIONS,
|
|
18848
20129
|
TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
|
|
18849
20130
|
TEST_VARIABLE_SECRET_KEY_PATTERN: () => TEST_VARIABLE_SECRET_KEY_PATTERN,
|
|
@@ -18856,23 +20137,33 @@ __export(dist_exports, {
|
|
|
18856
20137
|
UI_VARIANTS: () => UI_VARIANTS,
|
|
18857
20138
|
UNEXPECTED_PASS_ERROR_PREFIX: () => UNEXPECTED_PASS_ERROR_PREFIX,
|
|
18858
20139
|
USER_CANCELLED_RUN_ERROR: () => USER_CANCELLED_RUN_ERROR,
|
|
20140
|
+
VISUAL_MASK_SELECTORS_MAX: () => VISUAL_MASK_SELECTORS_MAX,
|
|
20141
|
+
VISUAL_MASK_SELECTOR_FORBIDDEN: () => VISUAL_MASK_SELECTOR_FORBIDDEN,
|
|
20142
|
+
VISUAL_MASK_SELECTOR_MAX_LENGTH: () => VISUAL_MASK_SELECTOR_MAX_LENGTH,
|
|
20143
|
+
VISUAL_V1_IDENTITY: () => VISUAL_V1_IDENTITY,
|
|
18859
20144
|
VOICE_PREVIEW_SAMPLE: () => VOICE_PREVIEW_SAMPLE,
|
|
18860
20145
|
WEB_PROXY_CHAIN_HEADER: () => WEB_PROXY_CHAIN_HEADER,
|
|
18861
20146
|
WITHHELD_CREDENTIAL_TEXT: () => WITHHELD_CREDENTIAL_TEXT,
|
|
20147
|
+
ZAI_MODEL_PATTERN: () => ZAI_MODEL_PATTERN,
|
|
20148
|
+
ZAI_PREFIX: () => ZAI_PREFIX,
|
|
20149
|
+
accountServesModel: () => accountServesModel,
|
|
18862
20150
|
accumulateStatements: () => accumulateStatements,
|
|
18863
20151
|
adoptBrokerFailover: () => adoptBrokerFailover,
|
|
18864
20152
|
aiCostToCreditCents: () => aiCostToCreditCents,
|
|
20153
|
+
allowedProviderChainHosts: () => allowedProviderChainHosts,
|
|
18865
20154
|
alternatesAtTop: () => alternatesAtTop,
|
|
18866
20155
|
analyzeDerivedValues: () => analyzeDerivedValues,
|
|
18867
20156
|
analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
|
|
18868
20157
|
analyzePersistenceReread: () => analyzePersistenceReread,
|
|
18869
20158
|
analyzeReadinessPolling: () => analyzeReadinessPolling,
|
|
20159
|
+
analyzeRegexMatchers: () => analyzeRegexMatchers,
|
|
18870
20160
|
analyzeSharedAccountUse: () => analyzeSharedAccountUse,
|
|
18871
20161
|
analyzeStaleAfterSubmit: () => analyzeStaleAfterSubmit,
|
|
18872
20162
|
analyzeTestData: () => analyzeTestData,
|
|
18873
20163
|
analyzeUnreachedUrlWaits: () => analyzeUnreachedUrlWaits,
|
|
18874
20164
|
analyzeUrlWaits: () => analyzeUrlWaits,
|
|
18875
20165
|
analyzeWriteResponse: () => analyzeWriteResponse,
|
|
20166
|
+
androidResourceOwners: () => androidResourceOwners,
|
|
18876
20167
|
apiModelCardsFromConfig: () => apiModelCardsFromConfig,
|
|
18877
20168
|
apiModelsForProvider: () => apiModelsForProvider,
|
|
18878
20169
|
apiProviderModelLabel: () => apiProviderModelLabel,
|
|
@@ -18883,6 +20174,7 @@ __export(dist_exports, {
|
|
|
18883
20174
|
applyLaunchSpecialPlan: () => applyLaunchSpecialPlan,
|
|
18884
20175
|
asPlainExpect: () => asPlainExpect,
|
|
18885
20176
|
assertionOnlyCode: () => assertionOnlyCode,
|
|
20177
|
+
assertionStrengthHoistedReadsOn: () => assertionStrengthHoistedReadsOn,
|
|
18886
20178
|
assertionsReadVisibleContent: () => assertionsReadVisibleContent,
|
|
18887
20179
|
assessCodeOutcomeCoverage: () => assessCodeOutcomeCoverage,
|
|
18888
20180
|
assessTitleCoverage: () => assessTitleCoverage,
|
|
@@ -18966,6 +20258,7 @@ __export(dist_exports, {
|
|
|
18966
20258
|
deepgramSpeak: () => deepgramSpeak,
|
|
18967
20259
|
deepgramTranscribe: () => deepgramTranscribe,
|
|
18968
20260
|
defaultConfirmModel: () => defaultConfirmModel,
|
|
20261
|
+
defaultProviderChainEntries: () => defaultProviderChainEntries,
|
|
18969
20262
|
defaultScreenModel: () => defaultScreenModel,
|
|
18970
20263
|
defaultVoiceForTtsModel: () => defaultVoiceForTtsModel,
|
|
18971
20264
|
deriveAuthPlan: () => deriveAuthPlan,
|
|
@@ -19008,6 +20301,7 @@ __export(dist_exports, {
|
|
|
19008
20301
|
extractOutcomeSearchTerms: () => extractOutcomeSearchTerms,
|
|
19009
20302
|
featureForAiCallType: () => featureForAiCallType,
|
|
19010
20303
|
featureForMcpTool: () => featureForMcpTool,
|
|
20304
|
+
featureRowRunStatus: () => featureRowRunStatus,
|
|
19011
20305
|
findBracedClause: () => findBracedClause,
|
|
19012
20306
|
findCounterIdPins: () => findCounterIdPins,
|
|
19013
20307
|
findCredentialTextLeftover: () => findCredentialTextLeftover,
|
|
@@ -19030,6 +20324,7 @@ __export(dist_exports, {
|
|
|
19030
20324
|
formatPersistenceRereadAdvisory: () => formatPersistenceRereadAdvisory,
|
|
19031
20325
|
formatPoolBudgetLine: () => formatPoolBudgetLine,
|
|
19032
20326
|
formatReadinessPollAdvisory: () => formatReadinessPollAdvisory,
|
|
20327
|
+
formatRegexMatcherNote: () => formatRegexMatcherNote,
|
|
19033
20328
|
formatStaleAfterSubmitAdvisory: () => formatStaleAfterSubmitAdvisory,
|
|
19034
20329
|
formatSuspiciousExpectation: () => formatSuspiciousExpectation,
|
|
19035
20330
|
formatUrlWaitAdvisory: () => formatUrlWaitAdvisory,
|
|
@@ -19055,6 +20350,7 @@ __export(dist_exports, {
|
|
|
19055
20350
|
getPlanChangeType: () => getPlanChangeType,
|
|
19056
20351
|
getPlanDefinition: () => getPlanDefinition,
|
|
19057
20352
|
getPlanLabel: () => getPlanLabel,
|
|
20353
|
+
getProviderChainOverrides: () => getProviderChainOverrides,
|
|
19058
20354
|
getTrialDaysRemaining: () => getTrialDaysRemaining,
|
|
19059
20355
|
googleBrokerNavigationUrl: () => googleBrokerNavigationUrl,
|
|
19060
20356
|
googleBrokerUnavailableLine: () => googleBrokerUnavailableLine,
|
|
@@ -19071,9 +20367,11 @@ __export(dist_exports, {
|
|
|
19071
20367
|
hasOpenCodeGoPrefix: () => hasOpenCodeGoPrefix,
|
|
19072
20368
|
hasOpenRouterPrefix: () => hasOpenRouterPrefix,
|
|
19073
20369
|
hasPlatformLocator: () => hasPlatformLocator,
|
|
20370
|
+
hasProviderChainLeadsByDefault: () => hasProviderChainLeadsByDefault,
|
|
19074
20371
|
hasReadOnlyMarker: () => hasReadOnlyMarker,
|
|
19075
20372
|
hasSurveyNotes: () => hasSurveyNotes,
|
|
19076
20373
|
hasTestFailContract: () => hasTestFailContract,
|
|
20374
|
+
hasZaiPrefix: () => hasZaiPrefix,
|
|
19077
20375
|
healMobileTestSteps: () => healMobileTestSteps,
|
|
19078
20376
|
healthRunWhere: () => healthRunWhere,
|
|
19079
20377
|
heldCodeRecord: () => heldCodeRecord,
|
|
@@ -19095,8 +20393,11 @@ __export(dist_exports, {
|
|
|
19095
20393
|
isAuthTokenName: () => isAuthTokenName,
|
|
19096
20394
|
isBlockedEnvVar: () => isBlockedEnvVar,
|
|
19097
20395
|
isBrowserCliEngine: () => isBrowserCliEngine,
|
|
20396
|
+
isBrowserVersion: () => isBrowserVersion,
|
|
19098
20397
|
isBugTestBrokenError: () => isBugTestBrokenError,
|
|
20398
|
+
isCancelledReason: () => isCancelledReason,
|
|
19099
20399
|
isCanvasUnsupportedMessage: () => isCanvasUnsupportedMessage,
|
|
20400
|
+
isChainablePrimary: () => isChainablePrimary,
|
|
19100
20401
|
isCliEngineModelId: () => isCliEngineModelId,
|
|
19101
20402
|
isCliEngineSpec: () => isCliEngineSpec,
|
|
19102
20403
|
isCodeFactNote: () => isCodeFactNote,
|
|
@@ -19120,6 +20421,7 @@ __export(dist_exports, {
|
|
|
19120
20421
|
isIdentityMobileXpath: () => isIdentityMobileXpath,
|
|
19121
20422
|
isIdentityProviderSurface: () => isIdentityProviderSurface,
|
|
19122
20423
|
isIncidentalAssetPath: () => isIncidentalAssetPath,
|
|
20424
|
+
isIntentionalNegativeAuthTest: () => isIntentionalNegativeAuthTest,
|
|
19123
20425
|
isIpOrLocalhost: () => isIpOrLocalhost,
|
|
19124
20426
|
isKnownPageType: () => isKnownPageType,
|
|
19125
20427
|
isLaunchSpecialActive: () => isLaunchSpecialActive,
|
|
@@ -19147,6 +20449,7 @@ __export(dist_exports, {
|
|
|
19147
20449
|
isRunInterruptedError: () => isRunInterruptedError,
|
|
19148
20450
|
isRunnableStep: () => isRunnableStep,
|
|
19149
20451
|
isSafeDeepLink: () => isSafeDeepLink,
|
|
20452
|
+
isSafeMaskSelector: () => isSafeMaskSelector,
|
|
19150
20453
|
isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
|
|
19151
20454
|
isSelectableApiModel: () => isSelectableApiModel,
|
|
19152
20455
|
isSelfServePlan: () => isSelfServePlan,
|
|
@@ -19177,6 +20480,7 @@ __export(dist_exports, {
|
|
|
19177
20480
|
isValidSttProvider: () => isValidSttProvider,
|
|
19178
20481
|
isValidTtsModel: () => isValidTtsModel,
|
|
19179
20482
|
isValidTtsVoiceId: () => isValidTtsVoiceId,
|
|
20483
|
+
isZaiModelId: () => isZaiModelId,
|
|
19180
20484
|
jsEscapedForms: () => jsEscapedForms,
|
|
19181
20485
|
jsonStructureRefusalOf: () => jsonStructureRefusalOf,
|
|
19182
20486
|
jsonStructureVerify: () => jsonStructureVerify,
|
|
@@ -19209,6 +20513,7 @@ __export(dist_exports, {
|
|
|
19209
20513
|
normalizeFinishReason: () => normalizeFinishReason,
|
|
19210
20514
|
normalizeNoteText: () => normalizeNoteText,
|
|
19211
20515
|
normalizePageType: () => normalizePageType,
|
|
20516
|
+
normalizeProviderAccountModels: () => normalizeProviderAccountModels,
|
|
19212
20517
|
normalizeTtsModel: () => normalizeTtsModel,
|
|
19213
20518
|
normalizeTtsVoiceId: () => normalizeTtsVoiceId,
|
|
19214
20519
|
noteChatFailover: () => noteChatFailover,
|
|
@@ -19225,6 +20530,7 @@ __export(dist_exports, {
|
|
|
19225
20530
|
parseApiCardModelUpdate: () => parseApiCardModelUpdate,
|
|
19226
20531
|
parseAppTimezone: () => parseAppTimezone,
|
|
19227
20532
|
parseAuditGroupScopeId: () => parseAuditGroupScopeId,
|
|
20533
|
+
parseAuthSessionStamp: () => parseAuthSessionStamp,
|
|
19228
20534
|
parseBoolFlag: () => parseBoolFlag,
|
|
19229
20535
|
parseDbPoolSize: () => parseDbPoolSize,
|
|
19230
20536
|
parseDeepgramListenResponse: () => parseDeepgramListenResponse,
|
|
@@ -19232,13 +20538,18 @@ __export(dist_exports, {
|
|
|
19232
20538
|
parseGeminiModelsPayload: () => parseGeminiModelsPayload,
|
|
19233
20539
|
parseGoogleAuthorizeUrl: () => parseGoogleAuthorizeUrl,
|
|
19234
20540
|
parseGoogleGisRequestUrl: () => parseGoogleGisRequestUrl,
|
|
20541
|
+
parseLastActionProofMode: () => parseLastActionProofMode,
|
|
19235
20542
|
parseLimitParam: () => parseLimitParam,
|
|
19236
20543
|
parseOffsetParam: () => parseOffsetParam,
|
|
19237
20544
|
parsePageParam: () => parsePageParam,
|
|
19238
20545
|
parsePgIntSetting: () => parsePgIntSetting,
|
|
19239
20546
|
parseProjectEngineOverrides: () => parseProjectEngineOverrides,
|
|
19240
20547
|
parsePromotionGateMode: () => parsePromotionGateMode,
|
|
20548
|
+
parseProviderAccountLists: () => parseProviderAccountLists,
|
|
20549
|
+
parseProviderChainOverrides: () => parseProviderChainOverrides,
|
|
19241
20550
|
parseRecheckClearMode: () => parseRecheckClearMode,
|
|
20551
|
+
parseRenderIdentity: () => parseRenderIdentity,
|
|
20552
|
+
parseStoredProviderChainOverrides: () => parseStoredProviderChainOverrides,
|
|
19242
20553
|
parseSurveyEntryUrls: () => parseSurveyEntryUrls,
|
|
19243
20554
|
parseSuspiciousPassMode: () => parseSuspiciousPassMode,
|
|
19244
20555
|
parseTaskModelUpdate: () => parseTaskModelUpdate,
|
|
@@ -19268,13 +20579,20 @@ __export(dist_exports, {
|
|
|
19268
20579
|
profileKeyRoute: () => profileKeyRoute,
|
|
19269
20580
|
projectAppHosts: () => projectAppHosts,
|
|
19270
20581
|
providerChainFor: () => providerChainFor,
|
|
20582
|
+
providerChainHostOf: () => providerChainHostOf,
|
|
20583
|
+
providerChainHostProvider: () => providerChainHostProvider,
|
|
20584
|
+
providerChainLeadsFor: () => providerChainLeadsFor,
|
|
20585
|
+
providerChainModelKey: () => providerChainModelKey,
|
|
19271
20586
|
providerChainPrimariesForHop: () => providerChainPrimariesForHop,
|
|
20587
|
+
providerHostPlan: () => providerHostPlan,
|
|
20588
|
+
readBalanced: () => readBalanced,
|
|
19272
20589
|
readChatFailover: () => readChatFailover,
|
|
19273
20590
|
readChatReasoningTokens: () => readChatReasoningTokens,
|
|
19274
20591
|
readChatUsage: () => readChatUsage,
|
|
19275
20592
|
readEnabledEngines: () => readEnabledEngines,
|
|
19276
20593
|
readProjectEngineOverrides: () => readProjectEngineOverrides,
|
|
19277
20594
|
readRetriedAttempts: () => readRetriedAttempts,
|
|
20595
|
+
readStatedLimits: () => readStatedLimits,
|
|
19278
20596
|
readStaticPageCanvasSignals: () => readStaticPageCanvasSignals,
|
|
19279
20597
|
readSurveyEntryUrls: () => readSurveyEntryUrls,
|
|
19280
20598
|
readSurveyNotes: () => readSurveyNotes,
|
|
@@ -19287,6 +20605,7 @@ __export(dist_exports, {
|
|
|
19287
20605
|
regexLiteralEnd: () => regexLiteralEnd2,
|
|
19288
20606
|
regexOpensAfter: () => regexOpensAfter,
|
|
19289
20607
|
registrableDomain: () => registrableDomain,
|
|
20608
|
+
renderIdentity: () => renderIdentity,
|
|
19290
20609
|
renderSurveyNotesForPrompt: () => renderSurveyNotesForPrompt,
|
|
19291
20610
|
repairPageType: () => repairPageType,
|
|
19292
20611
|
replaceBlockComments: () => replaceBlockComments,
|
|
@@ -19334,11 +20653,14 @@ __export(dist_exports, {
|
|
|
19334
20653
|
selectorCandidatesForStep: () => selectorCandidatesForStep,
|
|
19335
20654
|
selectorForValue: () => selectorForValue,
|
|
19336
20655
|
setAgenticCliRuntime: () => setAgenticCliRuntime,
|
|
20656
|
+
setProviderChainOverrides: () => setProviderChainOverrides,
|
|
19337
20657
|
signupPlanFor: () => signupPlanFor,
|
|
19338
20658
|
simplifyMobileWanderingInPlace: () => simplifyMobileWanderingInPlace,
|
|
19339
20659
|
siteOf: () => siteOf,
|
|
19340
20660
|
specItemsFromTestPlans: () => specItemsFromTestPlans,
|
|
19341
20661
|
splitAnswerLines: () => splitAnswerLines,
|
|
20662
|
+
statedLimitsFromAttrs: () => statedLimitsFromAttrs,
|
|
20663
|
+
statedLimitsRecord: () => statedLimitsRecord,
|
|
19342
20664
|
stepAppliesToPlatform: () => stepAppliesToPlatform,
|
|
19343
20665
|
stepConsoleLines: () => stepConsoleLines,
|
|
19344
20666
|
stepIsDivergentForPlatform: () => stepIsDivergentForPlatform,
|
|
@@ -19350,7 +20672,11 @@ __export(dist_exports, {
|
|
|
19350
20672
|
stripCodeFactPrefix: () => stripCodeFactPrefix,
|
|
19351
20673
|
stripCoverageComments: () => stripCoverageComments,
|
|
19352
20674
|
stripEnvironmentalPrefix: () => stripEnvironmentalPrefix,
|
|
20675
|
+
stripFractionZeros: () => stripFractionZeros,
|
|
20676
|
+
stripLineComment: () => stripLineComment,
|
|
19353
20677
|
stripLoginScaffolding: () => stripLoginScaffolding,
|
|
20678
|
+
stripTrailingChars: () => stripTrailingChars,
|
|
20679
|
+
stripTrailingSlashes: () => stripTrailingSlashes,
|
|
19354
20680
|
strongerNeedsReviewSource: () => strongerNeedsReviewSource,
|
|
19355
20681
|
sttModelsForProvider: () => sttModelsForProvider,
|
|
19356
20682
|
summarizeHealth: () => summarizeHealth,
|
|
@@ -19367,10 +20693,14 @@ __export(dist_exports, {
|
|
|
19367
20693
|
ttsFallbackModel: () => ttsFallbackModel,
|
|
19368
20694
|
ttsProviderForModel: () => ttsProviderForModel,
|
|
19369
20695
|
unexpectedPassError: () => unexpectedPassError,
|
|
20696
|
+
v1RenderIdentity: () => v1RenderIdentity,
|
|
19370
20697
|
validateAppMapDocument: () => validateAppMapDocument,
|
|
19371
20698
|
validateAssertionStrength: () => validateAssertionStrength,
|
|
19372
20699
|
validateEngineSpec: () => validateEngineSpec,
|
|
20700
|
+
validateProviderChainEntries: () => validateProviderChainEntries,
|
|
20701
|
+
validateProviderChainsSetting: () => validateProviderChainsSetting,
|
|
19373
20702
|
validateTaskModelId: () => validateTaskModelId,
|
|
20703
|
+
visualBaselinesEnabled: () => visualBaselinesEnabled,
|
|
19374
20704
|
visualProDailyBudget: () => visualProDailyBudget,
|
|
19375
20705
|
voiceMatchesTtsModel: () => voiceMatchesTtsModel,
|
|
19376
20706
|
voicesForTtsModel: () => voicesForTtsModel,
|
|
@@ -19378,7 +20708,8 @@ __export(dist_exports, {
|
|
|
19378
20708
|
withReviewSource: () => withReviewSource,
|
|
19379
20709
|
withoutNotChargedClaim: () => withoutNotChargedClaim,
|
|
19380
20710
|
withoutServerOnlySpendFields: () => withoutServerOnlySpendFields,
|
|
19381
|
-
withoutSurveyNotes: () => withoutSurveyNotes
|
|
20711
|
+
withoutSurveyNotes: () => withoutSurveyNotes,
|
|
20712
|
+
zaiWireModel: () => zaiWireModel
|
|
19382
20713
|
});
|
|
19383
20714
|
var init_dist = __esm({
|
|
19384
20715
|
"../shared/dist/index.js"() {
|
|
@@ -19420,6 +20751,7 @@ var init_dist = __esm({
|
|
|
19420
20751
|
init_admin_view_return_to();
|
|
19421
20752
|
init_auth_plan();
|
|
19422
20753
|
init_auth_block_detector();
|
|
20754
|
+
init_auth_session_stamp();
|
|
19423
20755
|
init_auth_state_freshness();
|
|
19424
20756
|
init_skip_coverage_detector();
|
|
19425
20757
|
init_feature_coverage();
|
|
@@ -19431,11 +20763,14 @@ var init_dist = __esm({
|
|
|
19431
20763
|
init_deepgram_voices();
|
|
19432
20764
|
init_feature_flags();
|
|
19433
20765
|
init_env_flags();
|
|
20766
|
+
init_visual_compare_limits();
|
|
20767
|
+
init_visual_baseline_identity();
|
|
19434
20768
|
init_analytics_url();
|
|
19435
20769
|
init_agentic_cli_runtime();
|
|
19436
20770
|
init_api_model_catalog();
|
|
19437
20771
|
init_engine_spec();
|
|
19438
20772
|
init_provider_chains();
|
|
20773
|
+
init_provider_accounts();
|
|
19439
20774
|
init_project_engine_overrides();
|
|
19440
20775
|
init_ai_profiles();
|
|
19441
20776
|
init_openrouter_usage();
|
|
@@ -19476,6 +20811,8 @@ var init_dist = __esm({
|
|
|
19476
20811
|
init_promotion_quality_gate();
|
|
19477
20812
|
init_regex_alternation();
|
|
19478
20813
|
init_page_evidence();
|
|
20814
|
+
init_trailing_slashes();
|
|
20815
|
+
init_stated_limits();
|
|
19479
20816
|
}
|
|
19480
20817
|
});
|
|
19481
20818
|
|
|
@@ -19614,7 +20951,7 @@ var require_repo_brief = __commonJS({
|
|
|
19614
20951
|
const signupJobs = brief.jobs.filter((job) => /sign.?up|register|create account/i.test(`${job.name} ${job.entry}`));
|
|
19615
20952
|
return signupJobs.some((job) => job.validationRules.some(rejectsPlus)) || (brief.applicationFacts ?? []).some((fact) => /sign.?up|register|create account/i.test(fact.area) && rejectsPlus(`${fact.behavior} ${fact.testImplication}`));
|
|
19616
20953
|
}
|
|
19617
|
-
var
|
|
20954
|
+
var KINDS2 = /* @__PURE__ */ new Set(["happy_path", "validation", "permission", "edge_case", "bug_risk"]);
|
|
19618
20955
|
var SESSIONS = /* @__PURE__ */ new Set(["none", "optional", "required"]);
|
|
19619
20956
|
var FACT_KINDS = /* @__PURE__ */ new Set(["workflow", "business_rule", "state_transition", "permission", "integration", "data_model"]);
|
|
19620
20957
|
var MISSING_REASON = "cited by the reader without a reason";
|
|
@@ -19727,7 +21064,7 @@ var require_repo_brief = __commonJS({
|
|
|
19727
21064
|
const scenarioRefs = refs(scenario.sourceRefs);
|
|
19728
21065
|
if (typeof scenarioName !== "string" || typeof expected !== "string" || scenarioRefs.length === 0)
|
|
19729
21066
|
continue;
|
|
19730
|
-
if (typeof kind !== "string" || !
|
|
21067
|
+
if (typeof kind !== "string" || !KINDS2.has(kind))
|
|
19731
21068
|
continue;
|
|
19732
21069
|
scenarios.push({
|
|
19733
21070
|
name: scenarioName.trim().slice(0, 160),
|
|
@@ -19911,6 +21248,7 @@ var require_route_manifest = __commonJS({
|
|
|
19911
21248
|
exports2.renderRouteManifestChecklist = renderRouteManifestChecklist;
|
|
19912
21249
|
exports2.renderRepoReadsChecklist = renderRepoReadsChecklist;
|
|
19913
21250
|
exports2.buildRepoReadsRouteAllowlist = buildRepoReadsRouteAllowlist;
|
|
21251
|
+
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
19914
21252
|
var promises_1 = require("fs/promises");
|
|
19915
21253
|
var node_path_1 = require("path");
|
|
19916
21254
|
var untrusted_knowledge_js_1 = require_untrusted_knowledge();
|
|
@@ -19926,7 +21264,7 @@ var require_route_manifest = __commonJS({
|
|
|
19926
21264
|
return `routes-${n}.json`;
|
|
19927
21265
|
}
|
|
19928
21266
|
var AUTH_HINTS = /* @__PURE__ */ new Set(["public", "auth", "unknown"]);
|
|
19929
|
-
var
|
|
21267
|
+
var KINDS2 = /* @__PURE__ */ new Set(["page", "api", "redirect"]);
|
|
19930
21268
|
var COMPLETENESS = /* @__PURE__ */ new Set(["complete", "partial", "unsupported"]);
|
|
19931
21269
|
var PROVENANCES = /* @__PURE__ */ new Set(["cli", "file-convention", "route-config"]);
|
|
19932
21270
|
function str(value, max) {
|
|
@@ -19965,7 +21303,7 @@ var require_route_manifest = __commonJS({
|
|
|
19965
21303
|
}
|
|
19966
21304
|
function collapsePath(path) {
|
|
19967
21305
|
const collapsed = path.replace(/\/{2,}/g, "/");
|
|
19968
|
-
return collapsed.length > 1 ?
|
|
21306
|
+
return collapsed.length > 1 ? (0, shared_1.stripTrailingSlashes)(collapsed) : collapsed;
|
|
19969
21307
|
}
|
|
19970
21308
|
function normalizeManifestPath(raw) {
|
|
19971
21309
|
const text = str(raw, 500);
|
|
@@ -20033,7 +21371,7 @@ var require_route_manifest = __commonJS({
|
|
|
20033
21371
|
if (seenFamilies.has(family)) {
|
|
20034
21372
|
const kept = routes.find((r2) => manifestRouteFamilyKey(r2.concretePath ?? r2.pattern ?? "") === family);
|
|
20035
21373
|
const more = strList(rr.methods, 8, 10).map((m) => m.toUpperCase()).filter((m) => /^[A-Z]{3,10}$/.test(m));
|
|
20036
|
-
const rowKind =
|
|
21374
|
+
const rowKind = KINDS2.has(rr.kind) ? rr.kind : normalized.startsWith("/api/") ? "api" : "page";
|
|
20037
21375
|
if (kept?.kind === "api" && rowKind === "api" && more.length) {
|
|
20038
21376
|
kept.methods = [.../* @__PURE__ */ new Set([...kept.methods ?? [], ...more])].slice(0, 8);
|
|
20039
21377
|
} else {
|
|
@@ -20047,7 +21385,7 @@ var require_route_manifest = __commonJS({
|
|
|
20047
21385
|
}
|
|
20048
21386
|
seenFamilies.add(family);
|
|
20049
21387
|
index += 1;
|
|
20050
|
-
const kind =
|
|
21388
|
+
const kind = KINDS2.has(rr.kind) ? rr.kind : normalized.startsWith("/api/") ? "api" : "page";
|
|
20051
21389
|
const authHint = AUTH_HINTS.has(rr.authHint) ? rr.authHint : "unknown";
|
|
20052
21390
|
const methods = strList(rr.methods, 8, 10).map((m) => m.toUpperCase()).filter((m) => /^[A-Z]{3,10}$/.test(m));
|
|
20053
21391
|
const sourceCheck = rr.sourceCheck === "matched" || rr.sourceCheck === "unmatched" ? rr.sourceCheck : void 0;
|
|
@@ -20303,6 +21641,7 @@ var require_exploration_parser = __commonJS({
|
|
|
20303
21641
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
20304
21642
|
exports2.extractCodeLines = extractCodeLines;
|
|
20305
21643
|
exports2.extractProvenLocator = extractProvenLocator;
|
|
21644
|
+
exports2.maskTypedValues = maskTypedValues;
|
|
20306
21645
|
exports2.sanitizePromptCodeLine = sanitizePromptCodeLine;
|
|
20307
21646
|
exports2.extractPageUrl = extractPageUrl;
|
|
20308
21647
|
exports2.extractPageTitle = extractPageTitle;
|
|
@@ -20314,9 +21653,10 @@ var require_exploration_parser = __commonJS({
|
|
|
20314
21653
|
exports2.normalizeRouteForFocusScope = normalizeRouteForFocusScope;
|
|
20315
21654
|
exports2.routeIsWithinFocusAreas = routeIsWithinFocusAreas;
|
|
20316
21655
|
exports2.detectRouteFromResponse = detectRouteFromResponse;
|
|
21656
|
+
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
20317
21657
|
function extractCodeLines(toolResultContent) {
|
|
20318
21658
|
const isPlaywrightLine = (l) => l.startsWith("await page.") || l.startsWith("await expect(");
|
|
20319
|
-
const codeSection = toolResultContent.match(/###\s*Ran Playwright code\s*\n([\s\S]*?)(?=\n###|\n$|$)/);
|
|
21659
|
+
const codeSection = toolResultContent.match(/###\s*Ran Playwright code\s*\n(?![^\S\n]*\n)([\s\S]*?)(?=\n###|\n$|$)/);
|
|
20320
21660
|
if (codeSection) {
|
|
20321
21661
|
return codeSection[1].split("\n").map((l) => l.trim()).filter(isPlaywrightLine).map((l) => l.replace(/;$/, ""));
|
|
20322
21662
|
}
|
|
@@ -20360,7 +21700,7 @@ var require_exploration_parser = __commonJS({
|
|
|
20360
21700
|
cut = idx;
|
|
20361
21701
|
}
|
|
20362
21702
|
let locator = cut > start ? codeLine.slice(start, cut) : codeLine.slice(start);
|
|
20363
|
-
locator = locator.trim().replace(
|
|
21703
|
+
locator = locator.trim().replace(/(?<!;);+\s*$/, "").trim();
|
|
20364
21704
|
if (!locator.includes("("))
|
|
20365
21705
|
return null;
|
|
20366
21706
|
if (!/^(getBy\w+|locator)\(/.test(locator))
|
|
@@ -20440,13 +21780,16 @@ var require_exploration_parser = __commonJS({
|
|
|
20440
21780
|
return false;
|
|
20441
21781
|
return RECORD_TITLE_WITH_COMPANY.test(name);
|
|
20442
21782
|
}
|
|
21783
|
+
var TYPED_VALUE_CALL_RE = /\.(fill|type|pressSequentially|insertText)\(\s*(['"])(?:\\.|(?!\2)[^\\])*\2(\s*,\s*\{[^{}]*\})?\s*\)/g;
|
|
21784
|
+
function maskTypedValues(codeLine, mask) {
|
|
21785
|
+
return codeLine.replace(TYPED_VALUE_CALL_RE, (_all, method, _quote, options) => `.${method}(${mask}${options ?? ""})`);
|
|
21786
|
+
}
|
|
20443
21787
|
function sanitizePromptCodeLine(codeLine) {
|
|
20444
21788
|
let sanitized = codeLine;
|
|
20445
21789
|
sanitized = sanitized.replace(/getByRole\('(textbox|searchbox|combobox|spinbutton)',\s*\{\s*name:\s*['"]([^'"]*)['"]\s*\}\)/g, (match, role, name, _offset, line) => looksDynamicFieldLabel(name, line) ? `getByRole('${role}')` : match);
|
|
20446
21790
|
sanitized = sanitized.replace(/getByLabel\(\s*(['"])([^'"]*)\1\s*\)(?=\)?\.(?:fill|type|toHaveValue|inputValue|press)\()/g, (match, _quote, label, _offset, line) => looksDynamicFieldLabel(label, line) ? "getByRole('textbox')" : match);
|
|
20447
21791
|
sanitized = sanitized.replace(/getByLabel\(\s*(['"])([^'"]*)\1\s*\)(?=\)?\.selectOption\()/g, (match, _quote, label, _offset, line) => looksDynamicFieldLabel(label, line) ? "getByRole('combobox')" : match);
|
|
20448
|
-
sanitized = sanitized
|
|
20449
|
-
sanitized = sanitized.replace(/\.type\(\s*(['"]).*?\1\s*\)/g, ".type('<test-value>')");
|
|
21792
|
+
sanitized = maskTypedValues(sanitized, "'<test-value>'");
|
|
20450
21793
|
sanitized = sanitized.replace(/\.toHaveValue\(\s*(['"]).*?\1\s*\)/g, ".toHaveValue(/./)");
|
|
20451
21794
|
sanitized = sanitized.replace(/getByRole\('heading',\s*\{\s*name:\s*'([^']*)'\s*\}\)/g, (match, name) => looksDynamicRecordTitle(name) ? "getByRole('heading')" : match);
|
|
20452
21795
|
sanitized = sanitized.replace(/getByRole\('heading',\s*\{\s*name:\s*"([^"]*)"\s*\}\)/g, (match, name) => looksDynamicRecordTitle(name) ? "getByRole('heading')" : match);
|
|
@@ -20470,7 +21813,7 @@ var require_exploration_parser = __commonJS({
|
|
|
20470
21813
|
return match ? match[1].trim() : null;
|
|
20471
21814
|
}
|
|
20472
21815
|
function extractSnapshotSection(content) {
|
|
20473
|
-
const match = content.match(/###\s*Snapshot\s*\n([\s\S]*?)(?=\n###|$)/);
|
|
21816
|
+
const match = content.match(/###\s*Snapshot\s*\n(?![^\S\n]*\n)([\s\S]*?)(?=\n###|$)/);
|
|
20474
21817
|
if (!match)
|
|
20475
21818
|
return null;
|
|
20476
21819
|
if (/^\s*- \[Snapshot\]\([^)\n]*\)\s*$/.test(match[1]))
|
|
@@ -20538,7 +21881,7 @@ var require_exploration_parser = __commonJS({
|
|
|
20538
21881
|
return hints;
|
|
20539
21882
|
}
|
|
20540
21883
|
function extractFirstAlert(snapshotYaml) {
|
|
20541
|
-
const match = snapshotYaml.match(
|
|
21884
|
+
const match = snapshotYaml.match(/^[^\S\n\r\u2028\u2029]*-\s+(?:alert|status)\s+"([^"]+)"/m);
|
|
20542
21885
|
return match ? match[1] : null;
|
|
20543
21886
|
}
|
|
20544
21887
|
function buildInteractionLogEntry(toolName, toolArgs, codeLines, pageUrl, pageTitle, snapshotSection) {
|
|
@@ -20579,6 +21922,12 @@ var require_exploration_parser = __commonJS({
|
|
|
20579
21922
|
return /^\d{10,}$/.test(value);
|
|
20580
21923
|
return false;
|
|
20581
21924
|
}
|
|
21925
|
+
function hashRouterPathWithoutTrailingSlash(hash) {
|
|
21926
|
+
const q = hash.indexOf("?");
|
|
21927
|
+
const path = q < 0 ? hash : hash.slice(0, q);
|
|
21928
|
+
const stripped = (0, shared_1.stripTrailingSlashes)(path);
|
|
21929
|
+
return stripped === "#" || stripped === "" ? hash : stripped + (q < 0 ? "" : hash.slice(q));
|
|
21930
|
+
}
|
|
20582
21931
|
function normalizeRoute(url) {
|
|
20583
21932
|
let route = url.pathname;
|
|
20584
21933
|
const encodeQueryPart = (part) => part.replace(/[%&=#+?]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
|
|
@@ -20597,7 +21946,7 @@ var require_exploration_parser = __commonJS({
|
|
|
20597
21946
|
}
|
|
20598
21947
|
}
|
|
20599
21948
|
if (url.hash && url.hash !== "#" && url.hash !== "#/") {
|
|
20600
|
-
route += url.hash;
|
|
21949
|
+
route += url.hash.startsWith("#/") ? hashRouterPathWithoutTrailingSlash(url.hash) : url.hash;
|
|
20601
21950
|
}
|
|
20602
21951
|
return route;
|
|
20603
21952
|
}
|
|
@@ -20607,7 +21956,7 @@ var require_exploration_parser = __commonJS({
|
|
|
20607
21956
|
const decoded = raw.replace(/&/g, "&").replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(Number(code)));
|
|
20608
21957
|
try {
|
|
20609
21958
|
const url = new URL(decoded, "http://canonical.invalid");
|
|
20610
|
-
url.pathname = url.pathname
|
|
21959
|
+
url.pathname = (0, shared_1.stripTrailingSlashes)(url.pathname) || "/";
|
|
20611
21960
|
return normalizeRoute(url);
|
|
20612
21961
|
} catch {
|
|
20613
21962
|
return decoded;
|
|
@@ -20639,9 +21988,9 @@ var require_exploration_parser = __commonJS({
|
|
|
20639
21988
|
const text = String(route ?? "").trim();
|
|
20640
21989
|
const at = text.indexOf("#/");
|
|
20641
21990
|
if (at < 0)
|
|
20642
|
-
return text.startsWith("#") ? text.split("?")[0].toLowerCase()
|
|
21991
|
+
return text.startsWith("#") ? (0, shared_1.stripTrailingSlashes)(text.split("?")[0].toLowerCase()) : null;
|
|
20643
21992
|
const hashPath = text.slice(at).split("?")[0].toLowerCase();
|
|
20644
|
-
return hashPath.length > 2 ?
|
|
21993
|
+
return hashPath.length > 2 ? (0, shared_1.stripTrailingSlashes)(hashPath) || "#/" : hashPath;
|
|
20645
21994
|
}
|
|
20646
21995
|
function routeIsWithinFocusAreas(route, focusAreas, otherRoutes) {
|
|
20647
21996
|
if (!focusAreas || focusAreas.length === 0)
|
|
@@ -20898,8 +22247,12 @@ var require_survey_network_guard = __commonJS({
|
|
|
20898
22247
|
exports2.maskedPath = maskedPath;
|
|
20899
22248
|
exports2.bodyKeysOf = bodyKeysOf;
|
|
20900
22249
|
exports2.graphqlDocumentIsQueryOnly = graphqlDocumentIsQueryOnly;
|
|
22250
|
+
exports2.graphqlOperationRoots = graphqlOperationRoots;
|
|
20901
22251
|
exports2.isTokenRefreshWrite = isTokenRefreshWrite;
|
|
20902
22252
|
exports2.isSignInPathWrite = isSignInPathWrite;
|
|
22253
|
+
exports2.graphqlUrlCarriesQuery = graphqlUrlCarriesQuery;
|
|
22254
|
+
exports2.graphqlUrlQueriesAreReads = graphqlUrlQueriesAreReads;
|
|
22255
|
+
exports2.graphqlGetMayWrite = graphqlGetMayWrite;
|
|
20903
22256
|
exports2.writeShapedGet = writeShapedGet;
|
|
20904
22257
|
exports2.decideSurveyRequest = decideSurveyRequest;
|
|
20905
22258
|
exports2.installSurveyNetworkGuard = installSurveyNetworkGuard;
|
|
@@ -21176,6 +22529,100 @@ var require_survey_network_guard = __commonJS({
|
|
|
21176
22529
|
}
|
|
21177
22530
|
return depth === 0 && parens === 0 && objects === 0 && sawOperation;
|
|
21178
22531
|
}
|
|
22532
|
+
function graphqlOperationRoots(text) {
|
|
22533
|
+
const tokens2 = lexGraphql(text);
|
|
22534
|
+
if (!tokens2)
|
|
22535
|
+
return null;
|
|
22536
|
+
const ops = [];
|
|
22537
|
+
let depth = 0;
|
|
22538
|
+
let parens = 0;
|
|
22539
|
+
let objects = 0;
|
|
22540
|
+
let defStart = true;
|
|
22541
|
+
let kind = null;
|
|
22542
|
+
let current = null;
|
|
22543
|
+
let prev = "";
|
|
22544
|
+
for (const tok of tokens2) {
|
|
22545
|
+
const last = prev;
|
|
22546
|
+
prev = tok;
|
|
22547
|
+
if (tok === "(") {
|
|
22548
|
+
parens++;
|
|
22549
|
+
continue;
|
|
22550
|
+
}
|
|
22551
|
+
if (tok === ")") {
|
|
22552
|
+
parens--;
|
|
22553
|
+
if (parens < 0 || objects !== 0)
|
|
22554
|
+
return null;
|
|
22555
|
+
continue;
|
|
22556
|
+
}
|
|
22557
|
+
if (parens > 0) {
|
|
22558
|
+
if (tok === "{")
|
|
22559
|
+
objects++;
|
|
22560
|
+
else if (tok === "}") {
|
|
22561
|
+
objects--;
|
|
22562
|
+
if (objects < 0)
|
|
22563
|
+
return null;
|
|
22564
|
+
}
|
|
22565
|
+
continue;
|
|
22566
|
+
}
|
|
22567
|
+
if (tok === "{") {
|
|
22568
|
+
if (depth === 0) {
|
|
22569
|
+
if (defStart)
|
|
22570
|
+
kind = "query";
|
|
22571
|
+
else if (kind === null)
|
|
22572
|
+
return null;
|
|
22573
|
+
current = kind === "fragment" ? null : { kind, roots: [] };
|
|
22574
|
+
if (current)
|
|
22575
|
+
ops.push(current);
|
|
22576
|
+
defStart = false;
|
|
22577
|
+
}
|
|
22578
|
+
depth++;
|
|
22579
|
+
continue;
|
|
22580
|
+
}
|
|
22581
|
+
if (tok === "}") {
|
|
22582
|
+
depth--;
|
|
22583
|
+
if (depth < 0)
|
|
22584
|
+
return null;
|
|
22585
|
+
if (depth === 0) {
|
|
22586
|
+
defStart = true;
|
|
22587
|
+
kind = null;
|
|
22588
|
+
current = null;
|
|
22589
|
+
}
|
|
22590
|
+
continue;
|
|
22591
|
+
}
|
|
22592
|
+
if (depth === 0) {
|
|
22593
|
+
if (!/^[_A-Za-z]/.test(tok)) {
|
|
22594
|
+
if (tok === "@")
|
|
22595
|
+
continue;
|
|
22596
|
+
return null;
|
|
22597
|
+
}
|
|
22598
|
+
if (defStart) {
|
|
22599
|
+
if (tok !== "query" && tok !== "mutation" && tok !== "fragment")
|
|
22600
|
+
return null;
|
|
22601
|
+
kind = tok;
|
|
22602
|
+
defStart = false;
|
|
22603
|
+
}
|
|
22604
|
+
continue;
|
|
22605
|
+
}
|
|
22606
|
+
if (depth > 1 || !current)
|
|
22607
|
+
continue;
|
|
22608
|
+
if (/^[_A-Za-z]/.test(tok)) {
|
|
22609
|
+
if (last === "@")
|
|
22610
|
+
continue;
|
|
22611
|
+
if (last === ":") {
|
|
22612
|
+
if (current.roots.length === 0)
|
|
22613
|
+
return null;
|
|
22614
|
+
current.roots[current.roots.length - 1] = tok;
|
|
22615
|
+
continue;
|
|
22616
|
+
}
|
|
22617
|
+
current.roots.push(tok);
|
|
22618
|
+
continue;
|
|
22619
|
+
}
|
|
22620
|
+
if (tok === ":" || tok === "@")
|
|
22621
|
+
continue;
|
|
22622
|
+
return null;
|
|
22623
|
+
}
|
|
22624
|
+
return depth === 0 && parens === 0 && objects === 0 ? ops : null;
|
|
22625
|
+
}
|
|
21179
22626
|
function isGraphqlQueryOnly(postData) {
|
|
21180
22627
|
if (!postData || postData.length > MAX_BODY_CHARS)
|
|
21181
22628
|
return false;
|
|
@@ -21213,6 +22660,34 @@ var require_survey_network_guard = __commonJS({
|
|
|
21213
22660
|
"remove"
|
|
21214
22661
|
]);
|
|
21215
22662
|
var NOT_A_SESSION_END = (segs) => !segs.some((seg) => SESSION_END_SEGMENTS.has(seg));
|
|
22663
|
+
var DESTRUCTIVE_WORDS = /* @__PURE__ */ new Set([
|
|
22664
|
+
"delete",
|
|
22665
|
+
"remove",
|
|
22666
|
+
"destroy",
|
|
22667
|
+
"deactivate",
|
|
22668
|
+
"terminate",
|
|
22669
|
+
"purge",
|
|
22670
|
+
"wipe",
|
|
22671
|
+
"erase",
|
|
22672
|
+
"cancel",
|
|
22673
|
+
"close",
|
|
22674
|
+
"revoke",
|
|
22675
|
+
"unlink",
|
|
22676
|
+
"disconnect"
|
|
22677
|
+
]);
|
|
22678
|
+
var DESTRUCTIVE_PREFIXES = ["delete", "remove", "destroy", "deactivate", "terminate", "purge"];
|
|
22679
|
+
function hasDestructiveSegment(rawSegs) {
|
|
22680
|
+
return rawSegs.some((raw) => {
|
|
22681
|
+
let seg = raw;
|
|
22682
|
+
try {
|
|
22683
|
+
seg = decodeURIComponent(raw);
|
|
22684
|
+
} catch {
|
|
22685
|
+
}
|
|
22686
|
+
const words = seg.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/);
|
|
22687
|
+
return words.some((word) => DESTRUCTIVE_WORDS.has(word) || DESTRUCTIVE_PREFIXES.some((p) => word.startsWith(p)));
|
|
22688
|
+
});
|
|
22689
|
+
}
|
|
22690
|
+
var rawPathSegments = (pathname) => pathname.split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
|
|
21216
22691
|
var pathSegments = (pathname) => pathname.toLowerCase().split("/").map((seg) => seg.split(";")[0]).filter(Boolean);
|
|
21217
22692
|
function isTokenRefreshWrite(method, pathname, recentAction = false) {
|
|
21218
22693
|
if (method.toUpperCase() !== "POST")
|
|
@@ -21229,6 +22704,8 @@ var require_survey_network_guard = __commonJS({
|
|
|
21229
22704
|
var AUTH_PAGE_PATH_RE = /(?:^|\/)(?:log-?in|sign-?in|sign_in|auth|oauth2?|sso|callback)(?:\/|$)/;
|
|
21230
22705
|
var SIGN_IN_SEGMENT_RE = /^(?:log-?in|sign-?in|sign_in)$/;
|
|
21231
22706
|
var CALLBACK_ROOT_SEGMENTS = /* @__PURE__ */ new Set(["auth", "oauth", "oauth2", "sso"]);
|
|
22707
|
+
var CALLBACK_PROTOCOL_SEGMENTS = /* @__PURE__ */ new Set(["oidc", "openid", "openid-connect", "openidconnect", "saml", "saml2", "oauth", "oauth2"]);
|
|
22708
|
+
var API_VERSION_SEGMENT_RE = /^v[0-9]{1,3}$/;
|
|
21232
22709
|
var PROVIDER_SEGMENT_RE = /^[a-z][a-z0-9_-]{0,39}$/;
|
|
21233
22710
|
var NOT_A_PROVIDER_SEGMENTS = /* @__PURE__ */ new Set([
|
|
21234
22711
|
"link",
|
|
@@ -21267,14 +22744,21 @@ var require_survey_network_guard = __commonJS({
|
|
|
21267
22744
|
if (!NOT_A_SESSION_END(segs))
|
|
21268
22745
|
return false;
|
|
21269
22746
|
if (AUTH_END_PATH_RE.test(`/${segs.join("/")}`))
|
|
21270
|
-
return
|
|
22747
|
+
return !hasDestructiveSegment(rawPathSegments(pathname).slice(0, -1));
|
|
22748
|
+
let top = segs[0] === "api" ? 1 : 0;
|
|
22749
|
+
if (top < segs.length && API_VERSION_SEGMENT_RE.test(segs[top]))
|
|
22750
|
+
top += 1;
|
|
22751
|
+
const below = segs.length - top;
|
|
22752
|
+
const isProvider = (seg) => PROVIDER_SEGMENT_RE.test(seg) && !NOT_A_PROVIDER_SEGMENTS.has(seg);
|
|
21271
22753
|
const provider = segs[segs.length - 1];
|
|
21272
|
-
const authRootAtTop =
|
|
21273
|
-
if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) &&
|
|
22754
|
+
const authRootAtTop = below === 3 || segs[segs.length - 4] === "api" && segs[segs.length - 3] === "auth";
|
|
22755
|
+
if (segs.length >= 3 && authRootAtTop && CALLBACK_ROOT_SEGMENTS.has(segs[segs.length - 3]) && isProvider(provider)) {
|
|
21274
22756
|
const step = segs[segs.length - 2];
|
|
21275
22757
|
if (step === "callback" || step === "signin")
|
|
21276
22758
|
return true;
|
|
21277
22759
|
}
|
|
22760
|
+
if (below === 4 && CALLBACK_ROOT_SEGMENTS.has(segs[top]) && CALLBACK_PROTOCOL_SEGMENTS.has(segs[top + 1]) && isProvider(segs[top + 2]) && segs[top + 3] === "callback")
|
|
22761
|
+
return true;
|
|
21278
22762
|
if (segs.length < 2 || segs[segs.length - 1] !== "callback")
|
|
21279
22763
|
return false;
|
|
21280
22764
|
const parent = segs[segs.length - 2];
|
|
@@ -21301,7 +22785,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
21301
22785
|
try {
|
|
21302
22786
|
const a = new URL(url);
|
|
21303
22787
|
const b = new URL(appBaseUrl);
|
|
21304
|
-
const path = (u) => u.pathname
|
|
22788
|
+
const path = (u) => (0, shared_1.stripTrailingSlashes)(u.pathname);
|
|
21305
22789
|
return a.origin === b.origin && path(a) === path(b);
|
|
21306
22790
|
} catch {
|
|
21307
22791
|
return false;
|
|
@@ -21323,9 +22807,18 @@ var require_survey_network_guard = __commonJS({
|
|
|
21323
22807
|
const bodyHash = (0, node_crypto_1.createHash)("sha256").update(req.postData ?? "").digest("hex").slice(0, 16);
|
|
21324
22808
|
return `${record.method} ${url.hostname.toLowerCase()}${url.pathname} ${bodyHash}`;
|
|
21325
22809
|
}
|
|
21326
|
-
function
|
|
21327
|
-
|
|
21328
|
-
|
|
22810
|
+
function graphqlUrlCarriesQuery(params) {
|
|
22811
|
+
return params.has("query");
|
|
22812
|
+
}
|
|
22813
|
+
function graphqlUrlQueriesAreReads(params) {
|
|
22814
|
+
return params.getAll("query").every((doc) => doc.trim() !== "" && graphqlDocumentIsQueryOnly(doc));
|
|
22815
|
+
}
|
|
22816
|
+
function graphqlGetMayWrite(url, body) {
|
|
22817
|
+
const docs = url.searchParams.getAll("query");
|
|
22818
|
+
return docs.length > 0 && (typeof body === "string" && body.length > 0 || docs.some((doc) => !graphqlDocumentIsQueryOnly(doc)));
|
|
22819
|
+
}
|
|
22820
|
+
function isGraphqlGetWrite(url, s, body) {
|
|
22821
|
+
return isGraphqlEndpoint(url, s) && graphqlGetMayWrite(url, body);
|
|
21329
22822
|
}
|
|
21330
22823
|
exports2.WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
21331
22824
|
"cancel",
|
|
@@ -21529,7 +23022,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
21529
23022
|
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
21530
23023
|
return { action: "allow", rule: readMethod ? "read" : "non-http" };
|
|
21531
23024
|
const appSite = isAppSite(url.hostname, s.appBaseUrl);
|
|
21532
|
-
const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s);
|
|
23025
|
+
const graphqlGet = method === "GET" && isGraphqlGetWrite(url, s, req.postData);
|
|
21533
23026
|
const inActionWindow = s.lastActionAt > 0 && s.now - s.lastActionAt <= ACTION_NAVIGATION_WINDOW_MS;
|
|
21534
23027
|
const typedLoad = s.agentLoad === true && req.resourceType === "document";
|
|
21535
23028
|
const writeShaped = method === "GET" && appSite && !graphqlGet && (inActionWindow || typedLoad) ? writeShapedGet(req.url, {
|
|
@@ -21563,7 +23056,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
21563
23056
|
return { action: "allow", rule: "extra-pattern", reason: extra.reason, record };
|
|
21564
23057
|
if (graphqlGet)
|
|
21565
23058
|
return { action: "block", rule: "app-write", record };
|
|
21566
|
-
if (method === "POST" && isGraphqlEndpoint(url, s) && isGraphqlQueryOnly(req.postData))
|
|
23059
|
+
if (method === "POST" && isGraphqlEndpoint(url, s) && graphqlUrlQueriesAreReads(url.searchParams) && isGraphqlQueryOnly(req.postData))
|
|
21567
23060
|
return { action: "allow", rule: "graphql-query", record };
|
|
21568
23061
|
const passive = s.lastNavigationAt > 0 && (s.lastActionAt === 0 || s.lastNavigationAt >= s.lastActionAt) && s.now - s.lastNavigationAt <= s.passiveWindowMs;
|
|
21569
23062
|
const signature = learnableSignature(req, url, record, s);
|
|
@@ -21595,7 +23088,7 @@ var require_survey_network_guard = __commonJS({
|
|
|
21595
23088
|
const re = new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
|
|
21596
23089
|
return { reason, test: (hostPath) => re.test(hostPath) };
|
|
21597
23090
|
}
|
|
21598
|
-
const p = pattern.trim().toLowerCase()
|
|
23091
|
+
const p = (0, shared_1.stripTrailingSlashes)(pattern.trim().toLowerCase());
|
|
21599
23092
|
if (p.includes("/"))
|
|
21600
23093
|
return { reason, test: (hostPath) => hostPath.toLowerCase() === p || hostPath.toLowerCase().startsWith(`${p}/`) };
|
|
21601
23094
|
return { reason, test: (hostPath) => {
|
|
@@ -22143,6 +23636,7 @@ var require_survey_repo_route_frontier = __commonJS({
|
|
|
22143
23636
|
exports2.buildRepoRouteCandidates = buildRepoRouteCandidates;
|
|
22144
23637
|
exports2.routeIdsHiddenFromChecklist = routeIdsHiddenFromChecklist;
|
|
22145
23638
|
exports2.parseRepoRouteCoverage = parseRepoRouteCoverage;
|
|
23639
|
+
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
22146
23640
|
var route_manifest_js_1 = require_route_manifest();
|
|
22147
23641
|
var survey_scope_js_1 = require_survey_scope();
|
|
22148
23642
|
var repo_route_safety_js_1 = require_repo_route_safety();
|
|
@@ -22204,7 +23698,7 @@ var require_survey_repo_route_frontier = __commonJS({
|
|
|
22204
23698
|
return route;
|
|
22205
23699
|
try {
|
|
22206
23700
|
const base = new URL(baseUrl);
|
|
22207
|
-
return `${base.pathname
|
|
23701
|
+
return `${(0, shared_1.stripTrailingSlashes)(base.pathname) || "/"}${route}`;
|
|
22208
23702
|
} catch {
|
|
22209
23703
|
return `/${route}`;
|
|
22210
23704
|
}
|
|
@@ -22435,7 +23929,7 @@ var require_survey_repo_route_frontier = __commonJS({
|
|
|
22435
23929
|
try {
|
|
22436
23930
|
const base = new URL(input.baseUrl);
|
|
22437
23931
|
origin = base.origin;
|
|
22438
|
-
basePathname = base.pathname
|
|
23932
|
+
basePathname = (0, shared_1.stripTrailingSlashes)(base.pathname) || "/";
|
|
22439
23933
|
} catch {
|
|
22440
23934
|
origin = null;
|
|
22441
23935
|
}
|
|
@@ -22468,7 +23962,7 @@ var require_survey_repo_route_frontier = __commonJS({
|
|
|
22468
23962
|
return null;
|
|
22469
23963
|
if (parsed.origin !== this.appOrigin)
|
|
22470
23964
|
return null;
|
|
22471
|
-
const pathname = parsed.pathname
|
|
23965
|
+
const pathname = (0, shared_1.stripTrailingSlashes)(parsed.pathname) || "/";
|
|
22472
23966
|
const key = manifestRouteKey(`${parsed.pathname}${parsed.search}${parsed.hash}`);
|
|
22473
23967
|
if (!key)
|
|
22474
23968
|
return null;
|
|
@@ -22933,6 +24427,7 @@ var require_repo_feature_verification = __commonJS({
|
|
|
22933
24427
|
exports2.parseBrowserVerificationRequirement = parseBrowserVerificationRequirement;
|
|
22934
24428
|
exports2.parseBrowserVerificationResult = parseBrowserVerificationResult;
|
|
22935
24429
|
exports2.renderBrowserVerificationForPrompt = renderBrowserVerificationForPrompt;
|
|
24430
|
+
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
22936
24431
|
var survey_repo_route_frontier_js_1 = require_survey_repo_route_frontier();
|
|
22937
24432
|
var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
22938
24433
|
function originOf(url) {
|
|
@@ -22989,7 +24484,7 @@ var require_repo_feature_verification = __commonJS({
|
|
|
22989
24484
|
before = before.split("?")[0];
|
|
22990
24485
|
if (!before.startsWith("/"))
|
|
22991
24486
|
return null;
|
|
22992
|
-
return
|
|
24487
|
+
return (0, shared_1.stripTrailingSlashes)(before);
|
|
22993
24488
|
}
|
|
22994
24489
|
function sameHashMount(route, entry) {
|
|
22995
24490
|
const entryMount = hashMount(entry);
|
|
@@ -23045,7 +24540,7 @@ var require_repo_feature_verification = __commonJS({
|
|
|
23045
24540
|
function compileEntryPattern(pattern) {
|
|
23046
24541
|
const trimmed = String(pattern ?? "").trim();
|
|
23047
24542
|
const hashAt = trimmed.indexOf("#/");
|
|
23048
|
-
const raw = (hashAt >= 0 || trimmed.startsWith("#") ? trimmed.slice(Math.max(hashAt, 0)).split("?")[0] : trimmed.split(/[?#]/)[0])
|
|
24543
|
+
const raw = (0, shared_1.stripTrailingSlashes)(hashAt >= 0 || trimmed.startsWith("#") ? trimmed.slice(Math.max(hashAt, 0)).split("?")[0] : trimmed.split(/[?#]/)[0]);
|
|
23049
24544
|
if (!isEntrySpelling(raw) || raw.length > 500)
|
|
23050
24545
|
return null;
|
|
23051
24546
|
let out = "";
|