@validate.qa/runner 1.0.60 → 1.0.62
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/dist/cli.js +409 -22
- package/dist/cli.mjs +409 -22
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2236,6 +2236,16 @@ function selectorForValue(value) {
|
|
|
2236
2236
|
function isIdentityMobileXpath(value) {
|
|
2237
2237
|
return /@[A-Za-z_:][-A-Za-z0-9_:.]*\s*=/.test(value) && !/(?:contains|starts-with)\s*\(/i.test(value);
|
|
2238
2238
|
}
|
|
2239
|
+
function mobileLocatorBundleHasIdentity(bundle) {
|
|
2240
|
+
if (!bundle)
|
|
2241
|
+
return false;
|
|
2242
|
+
if (nonEmpty(bundle.accessibilityId) || nonEmpty(bundle.resourceId))
|
|
2243
|
+
return true;
|
|
2244
|
+
if (nonEmpty(bundle.text) || nonEmpty(bundle.contentDesc))
|
|
2245
|
+
return true;
|
|
2246
|
+
const xpath = nonEmpty(bundle.xpath);
|
|
2247
|
+
return !!xpath && isIdentityMobileXpath(xpath);
|
|
2248
|
+
}
|
|
2239
2249
|
function selectorCandidatesForStep(step) {
|
|
2240
2250
|
const target = step.target;
|
|
2241
2251
|
if (!target)
|
|
@@ -2571,7 +2581,7 @@ function generateMobileTestFromSession(input) {
|
|
|
2571
2581
|
const warnings = [];
|
|
2572
2582
|
const unrunnable = interactionSteps.length - runnableInteractions.length;
|
|
2573
2583
|
if (unrunnable > 0) {
|
|
2574
|
-
warnings.push(`${unrunnable} recorded step(s) could not be turned into a runnable action (missing locator
|
|
2584
|
+
warnings.push(`${unrunnable} recorded step(s) could not be turned into a runnable action (missing a replayable identity locator or coordinate) and were kept best-effort.`);
|
|
2575
2585
|
}
|
|
2576
2586
|
const locatorCoverage = interactionSteps.filter((step) => step.target?.primary).length;
|
|
2577
2587
|
const assertionCoverage = steps.filter((step) => step.action === "assertVisible" || step.action === "assertText").length;
|
|
@@ -2600,10 +2610,29 @@ function generateMobileTestFromSession(input) {
|
|
|
2600
2610
|
warnings
|
|
2601
2611
|
};
|
|
2602
2612
|
}
|
|
2613
|
+
function isGateStepResolvable(step) {
|
|
2614
|
+
const metadata = step.metadata;
|
|
2615
|
+
if (metadata?.appContext === "WEBVIEW") {
|
|
2616
|
+
return selectorCandidatesForStep(step).length > 0;
|
|
2617
|
+
}
|
|
2618
|
+
const target = step.target;
|
|
2619
|
+
if (!target)
|
|
2620
|
+
return false;
|
|
2621
|
+
if (mobileLocatorBundleHasIdentity(target.locatorBundle))
|
|
2622
|
+
return true;
|
|
2623
|
+
return [target.primary, ...target.fallbacks ?? []].some((value) => {
|
|
2624
|
+
const trimmed = nonEmpty(value);
|
|
2625
|
+
if (!trimmed || trimmed.startsWith("css="))
|
|
2626
|
+
return false;
|
|
2627
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("(//"))
|
|
2628
|
+
return isIdentityMobileXpath(trimmed);
|
|
2629
|
+
return true;
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2603
2632
|
function isRunnableStep(step) {
|
|
2604
2633
|
const bounds = step.metadata?.bounds;
|
|
2605
2634
|
const hasCoord = !!bounds && [bounds.x, bounds.y].every((n) => typeof n === "number" && Number.isFinite(n));
|
|
2606
|
-
const
|
|
2635
|
+
const hasExecutableLocator = selectorCandidatesForStep(step).length > 0;
|
|
2607
2636
|
switch (step.action) {
|
|
2608
2637
|
case "launchApp":
|
|
2609
2638
|
case "back":
|
|
@@ -2613,18 +2642,18 @@ function isRunnableStep(step) {
|
|
|
2613
2642
|
return true;
|
|
2614
2643
|
case "tap":
|
|
2615
2644
|
case "clear":
|
|
2616
|
-
return
|
|
2645
|
+
return hasExecutableLocator || hasCoord;
|
|
2617
2646
|
case "type": {
|
|
2618
2647
|
const keyboardSubmitOnly = typeof step.value === "string" && /^[\r\n]+$/.test(step.value);
|
|
2619
2648
|
if (keyboardSubmitOnly)
|
|
2620
2649
|
return true;
|
|
2621
|
-
return (
|
|
2650
|
+
return (hasExecutableLocator || hasCoord) && !!nonEmpty(step.value ?? void 0);
|
|
2622
2651
|
}
|
|
2623
2652
|
case "waitForElement":
|
|
2624
2653
|
case "assertVisible":
|
|
2625
|
-
return
|
|
2654
|
+
return isGateStepResolvable(step);
|
|
2626
2655
|
case "assertText":
|
|
2627
|
-
return
|
|
2656
|
+
return isGateStepResolvable(step) && !!nonEmpty(step.assertion ?? void 0);
|
|
2628
2657
|
default:
|
|
2629
2658
|
return false;
|
|
2630
2659
|
}
|
|
@@ -5897,6 +5926,7 @@ __export(dist_exports, {
|
|
|
5897
5926
|
jwtExpiryMs: () => jwtExpiryMs,
|
|
5898
5927
|
mediumForMobilePlatform: () => mediumForMobilePlatform,
|
|
5899
5928
|
mobileCredentialPlaceholder: () => mobileCredentialPlaceholder,
|
|
5929
|
+
mobileLocatorBundleHasIdentity: () => mobileLocatorBundleHasIdentity,
|
|
5900
5930
|
mobilePlatformFromMedium: () => mobilePlatformFromMedium,
|
|
5901
5931
|
normalizeControlName: () => normalizeControlName,
|
|
5902
5932
|
normalizeTtsVoiceId: () => normalizeTtsVoiceId,
|
|
@@ -78263,6 +78293,12 @@ ${creds.join(", ")}`);
|
|
|
78263
78293
|
A verified login recipe EXISTS for this app \u2014 the harness automatically signs in (and asserts the signed-in state) before every authenticated test. This SUPERSEDES the "all tests start from the landing page and sign in" rule for the non-auth-flow scenarios described here.
|
|
78264
78294
|
For a scenario that REQUIRES an authenticated session but is NOT itself exercising the authentication / session lifecycle, you MAY assume the test begins ALREADY SIGNED IN at ${startRoute}. Do NOT author sign-in navigation, sign-in clicks, or credential-entry steps for it \u2014 begin at the first real feature action. BUT if the feature is not already on ${startRoute}, DO still include the in-app navigation from the signed-in landing to the feature page.
|
|
78265
78295
|
Plan these LOGGED-OUT instead (keep their own establishment steps; do NOT assume a session): any scenario that exercises the auth / session / account-access lifecycle itself \u2014 signing in, signing up / registration, password reset, email / OTP / MFA / 2FA verification, SSO or magic-link sign-in, re-authentication, and SIGNING OUT (which starts signed-in and ends signed-out). Also plan logged-out any journey whose first steps must run without a session (e.g. "register a new account, then use it" \u2014 keep the registration steps).`);
|
|
78296
|
+
}
|
|
78297
|
+
if (context.emailVerifyArmed) {
|
|
78298
|
+
sections.push(`## Verification Inbox Available (runtime email capability)
|
|
78299
|
+
This runner CAN read THIS test's verification email at runtime \u2014 a one-time code AND a verification link \u2014 for a fresh throwaway account the harness registers per run. That lets a SIGN-UP email-verification flow RUN TO COMPLETION instead of stopping at the "enter the code" / "check your email" screen (a survey has no inbox, so exploration could only reach that screen). Plan it as a HAPPY_PATH:
|
|
78300
|
+
- Sign-up + email verification: register a brand-new account, RETRIEVE the emailed code or link, and submit/open it to complete verification.
|
|
78301
|
+
Write "retrieve the emailed code / open the emailed link" as an ordinary plain-language step; the generator supplies the runtime mechanism. This is an exception to Rule 1 ONLY for the ACTION of completing the email step \u2014 it does NOT license inventing an outcome. For the durable assertion, Rule 12 still governs: assert only the post-verification result the evidence supports \u2014 a specific observed control, a confirmation message the explorer actually saw, or a STRUCTURAL signal (the verification screen is replaced, the URL leaves the verify route, the login form is no longer shown). If no concrete signed-in signal was observed, assert the observed transition and NAME the missing signal as a coverage gap in reasoning \u2014 do NOT fabricate a Logout/account control exploration never saw (this app may land on a login page or a confirmation page after verifying, not a signed-in dashboard). Keep it LOGGED-OUT with its own registration steps, and still plan the sign-up / verification negative cases the EVIDENCE supports (e.g. an already-registered email, or an invalid / mismatched verification code) as separate scenarios.`);
|
|
78266
78302
|
}
|
|
78267
78303
|
sections.push(`## Constraints
|
|
78268
78304
|
- Test budget: ${context.testBudget} scenarios maximum`);
|
|
@@ -94874,6 +94910,7 @@ var require_discover_generator = __commonJS({
|
|
|
94874
94910
|
"use strict";
|
|
94875
94911
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
94876
94912
|
exports2.MIN_BUG_DESCRIPTION_LENGTH = exports2.VALID_PLAYWRIGHT_GETBY_METHODS = exports2.BLOCKED_MCP_TOOLS_SCENARIO_GEN = void 0;
|
|
94913
|
+
exports2.scenarioLooksLikeRegistration = scenarioLooksLikeRegistration;
|
|
94877
94914
|
exports2.filterMcpToolsForScenarioGen = filterMcpToolsForScenarioGen;
|
|
94878
94915
|
exports2.detectScenarioSignals = detectScenarioSignals;
|
|
94879
94916
|
exports2.scenarioStepsCoverAuth = scenarioStepsCoverAuth;
|
|
@@ -94886,6 +94923,7 @@ var require_discover_generator = __commonJS({
|
|
|
94886
94923
|
exports2.stripImportDeclarations = stripImportDeclarations;
|
|
94887
94924
|
exports2.stripStringsAndComments = stripStringsAndComments;
|
|
94888
94925
|
exports2.gotoBasePathFromBaseUrl = gotoBasePathFromBaseUrl;
|
|
94926
|
+
exports2.repairInboxHelperScalarDestructure = repairInboxHelperScalarDestructure;
|
|
94889
94927
|
exports2.sanitizeCode = sanitizeCode;
|
|
94890
94928
|
exports2.validateTests = validateTests;
|
|
94891
94929
|
exports2.formatEvidenceForLiveGenerate = formatEvidenceForLiveGenerate;
|
|
@@ -94932,7 +94970,8 @@ var require_discover_generator = __commonJS({
|
|
|
94932
94970
|
var test_inbox_broker_js_1 = require_test_inbox_broker();
|
|
94933
94971
|
var REGISTER_SCENARIO_RE = /\b(register|registration|sign[\s-]?up|create[\s-]?(a[n]?[\s-]?)?(new[\s-]?)?account|verify[\s-]?(your[\s-]?)?email|email[\s-]?verification|confirm[\s-]?(your[\s-]?)?(email|account)|activate[\s-]?(your[\s-]?)?account|new[\s-]?account)\b/i;
|
|
94934
94972
|
function scenarioLooksLikeRegistration(scenario) {
|
|
94935
|
-
const
|
|
94973
|
+
const stepText = (scenario.steps ?? []).map((s) => `${s?.action ?? ""} ${s?.target ?? ""}`).join(" ");
|
|
94974
|
+
const hay = `${scenario.name ?? ""} ${scenario.goal ?? ""} ${scenario.description ?? ""} ${scenario.entryRoute ?? ""} ${(scenario.targetPages ?? []).join(" ")} ${stepText}`;
|
|
94936
94975
|
return REGISTER_SCENARIO_RE.test(hay);
|
|
94937
94976
|
}
|
|
94938
94977
|
function buildEmailVerifyGenBlock(genEmail, genPassword) {
|
|
@@ -94955,6 +94994,7 @@ EMIT (the code you put in finish_generate) \u2014 do NOT hardcode the throwaway
|
|
|
94955
94994
|
- code flow: \`const code = await __vqaWaitForEmail('code');\` then type \`code\` into the verification field.
|
|
94956
94995
|
- link flow: \`const link = await __vqaWaitForEmail('link');\` then \`await page.goto(link);\`.
|
|
94957
94996
|
- if unsure: \`const email = await __vqaWaitForEmail();\` returns \`{ code, link, subject, from }\`.
|
|
94997
|
+
- IMPORTANT: \`__vqaWaitForEmail('code')\` returns the code STRING (and \`('link')\` the link string) \u2014 assign it directly with \`const code = \u2026\`. Do NOT destructure the scalar form (\`const { code } = await __vqaWaitForEmail('code')\` binds \`undefined\`, so \`code.split(...)\` then crashes). ONLY the no-arg \`__vqaWaitForEmail()\` returns an object you can destructure.
|
|
94958
94998
|
- Tag the test \`@email-verify\`. Emit EXACTLY ONE test() in this file (one registration per run).
|
|
94959
94999
|
- End with a DURABLE assertion of the post-verification signed-in state (e.g. a Logout control, an account/dashboard landmark) in your OWN test body \u2014 the helper call is not itself a proof.`;
|
|
94960
95000
|
}
|
|
@@ -96054,6 +96094,200 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
96054
96094
|
return void 0;
|
|
96055
96095
|
}
|
|
96056
96096
|
}
|
|
96097
|
+
function literalPositionMask(code) {
|
|
96098
|
+
const n = code.length;
|
|
96099
|
+
const mask = new Array(n).fill(false);
|
|
96100
|
+
const REGEX_PRECEDING = /[(,=;{}\[!&|?:+\-*%~^<>]$/;
|
|
96101
|
+
const REGEX_PRECEDING_KEYWORD = /\b(?:return|typeof|void|delete|in|of|new|instanceof|throw|await|yield|case)\s*$/;
|
|
96102
|
+
let i = 0;
|
|
96103
|
+
let codeSoFar = "";
|
|
96104
|
+
const isRegexContext = () => {
|
|
96105
|
+
const trimmed = codeSoFar.replace(/\s+$/, "");
|
|
96106
|
+
if (trimmed.length === 0)
|
|
96107
|
+
return true;
|
|
96108
|
+
if (REGEX_PRECEDING.test(trimmed))
|
|
96109
|
+
return true;
|
|
96110
|
+
if (REGEX_PRECEDING_KEYWORD.test(trimmed))
|
|
96111
|
+
return true;
|
|
96112
|
+
if (trimmed.endsWith("=>"))
|
|
96113
|
+
return true;
|
|
96114
|
+
return false;
|
|
96115
|
+
};
|
|
96116
|
+
const scanRegex = () => {
|
|
96117
|
+
i += 1;
|
|
96118
|
+
let inClass = false;
|
|
96119
|
+
while (i < n) {
|
|
96120
|
+
if (code[i] === "\\") {
|
|
96121
|
+
mask[i] = true;
|
|
96122
|
+
if (i + 1 < n)
|
|
96123
|
+
mask[i + 1] = true;
|
|
96124
|
+
i += 2;
|
|
96125
|
+
continue;
|
|
96126
|
+
}
|
|
96127
|
+
if (code[i] === "[") {
|
|
96128
|
+
inClass = true;
|
|
96129
|
+
mask[i] = true;
|
|
96130
|
+
i += 1;
|
|
96131
|
+
continue;
|
|
96132
|
+
}
|
|
96133
|
+
if (code[i] === "]") {
|
|
96134
|
+
inClass = false;
|
|
96135
|
+
mask[i] = true;
|
|
96136
|
+
i += 1;
|
|
96137
|
+
continue;
|
|
96138
|
+
}
|
|
96139
|
+
if (code[i] === "/" && !inClass) {
|
|
96140
|
+
i += 1;
|
|
96141
|
+
break;
|
|
96142
|
+
}
|
|
96143
|
+
if (code[i] === "\n")
|
|
96144
|
+
break;
|
|
96145
|
+
mask[i] = true;
|
|
96146
|
+
i += 1;
|
|
96147
|
+
}
|
|
96148
|
+
while (i < n && /[gimsuydv]/.test(code[i]))
|
|
96149
|
+
i += 1;
|
|
96150
|
+
};
|
|
96151
|
+
const scanQuoted = (quote) => {
|
|
96152
|
+
i += 1;
|
|
96153
|
+
while (i < n) {
|
|
96154
|
+
if (code[i] === "\\") {
|
|
96155
|
+
if (code[i + 1] === "\r" && code[i + 2] === "\n") {
|
|
96156
|
+
mask[i] = true;
|
|
96157
|
+
mask[i + 1] = true;
|
|
96158
|
+
mask[i + 2] = true;
|
|
96159
|
+
i += 3;
|
|
96160
|
+
continue;
|
|
96161
|
+
}
|
|
96162
|
+
mask[i] = true;
|
|
96163
|
+
if (i + 1 < n)
|
|
96164
|
+
mask[i + 1] = true;
|
|
96165
|
+
i += 2;
|
|
96166
|
+
continue;
|
|
96167
|
+
}
|
|
96168
|
+
if (code[i] === quote) {
|
|
96169
|
+
i += 1;
|
|
96170
|
+
break;
|
|
96171
|
+
}
|
|
96172
|
+
if (code[i] === "\n")
|
|
96173
|
+
break;
|
|
96174
|
+
mask[i] = true;
|
|
96175
|
+
i += 1;
|
|
96176
|
+
}
|
|
96177
|
+
};
|
|
96178
|
+
const scanTemplate = () => {
|
|
96179
|
+
while (i < n) {
|
|
96180
|
+
if (code[i] === "\\") {
|
|
96181
|
+
mask[i] = true;
|
|
96182
|
+
if (i + 1 < n)
|
|
96183
|
+
mask[i + 1] = true;
|
|
96184
|
+
i += 2;
|
|
96185
|
+
continue;
|
|
96186
|
+
}
|
|
96187
|
+
if (code[i] === "`") {
|
|
96188
|
+
i += 1;
|
|
96189
|
+
break;
|
|
96190
|
+
}
|
|
96191
|
+
if (code[i] === "$" && code[i + 1] === "{") {
|
|
96192
|
+
i += 2;
|
|
96193
|
+
const savedCodeSoFar = codeSoFar;
|
|
96194
|
+
codeSoFar = "";
|
|
96195
|
+
scanCode(true);
|
|
96196
|
+
codeSoFar = savedCodeSoFar;
|
|
96197
|
+
continue;
|
|
96198
|
+
}
|
|
96199
|
+
mask[i] = true;
|
|
96200
|
+
i += 1;
|
|
96201
|
+
}
|
|
96202
|
+
};
|
|
96203
|
+
function scanCode(stopAtCloseBrace) {
|
|
96204
|
+
let depth = 0;
|
|
96205
|
+
while (i < n) {
|
|
96206
|
+
const c = code[i];
|
|
96207
|
+
const next = i + 1 < n ? code[i + 1] : "";
|
|
96208
|
+
if (stopAtCloseBrace) {
|
|
96209
|
+
if (c === "}" && depth === 0) {
|
|
96210
|
+
i += 1;
|
|
96211
|
+
return;
|
|
96212
|
+
}
|
|
96213
|
+
if (c === "{") {
|
|
96214
|
+
depth += 1;
|
|
96215
|
+
codeSoFar += c;
|
|
96216
|
+
i += 1;
|
|
96217
|
+
continue;
|
|
96218
|
+
}
|
|
96219
|
+
if (c === "}") {
|
|
96220
|
+
depth -= 1;
|
|
96221
|
+
codeSoFar += c;
|
|
96222
|
+
i += 1;
|
|
96223
|
+
continue;
|
|
96224
|
+
}
|
|
96225
|
+
}
|
|
96226
|
+
if (c === "/" && next === "*") {
|
|
96227
|
+
const end = code.indexOf("*/", i + 2);
|
|
96228
|
+
const stop = end < 0 ? n : end + 2;
|
|
96229
|
+
for (let j = i; j < stop; j++)
|
|
96230
|
+
mask[j] = true;
|
|
96231
|
+
i = stop;
|
|
96232
|
+
continue;
|
|
96233
|
+
}
|
|
96234
|
+
if (c === "/" && next === "/") {
|
|
96235
|
+
const end = code.indexOf("\n", i + 2);
|
|
96236
|
+
const stop = end < 0 ? n : end;
|
|
96237
|
+
for (let j = i; j < stop; j++)
|
|
96238
|
+
mask[j] = true;
|
|
96239
|
+
i = stop;
|
|
96240
|
+
continue;
|
|
96241
|
+
}
|
|
96242
|
+
if (c === "/" && isRegexContext()) {
|
|
96243
|
+
scanRegex();
|
|
96244
|
+
codeSoFar += "x";
|
|
96245
|
+
continue;
|
|
96246
|
+
}
|
|
96247
|
+
if (c === "`") {
|
|
96248
|
+
i += 1;
|
|
96249
|
+
scanTemplate();
|
|
96250
|
+
codeSoFar += "x";
|
|
96251
|
+
continue;
|
|
96252
|
+
}
|
|
96253
|
+
if (c === '"' || c === "'") {
|
|
96254
|
+
scanQuoted(c);
|
|
96255
|
+
codeSoFar += "x";
|
|
96256
|
+
continue;
|
|
96257
|
+
}
|
|
96258
|
+
codeSoFar += c;
|
|
96259
|
+
i += 1;
|
|
96260
|
+
}
|
|
96261
|
+
}
|
|
96262
|
+
scanCode(false);
|
|
96263
|
+
return mask;
|
|
96264
|
+
}
|
|
96265
|
+
function atLineStart(code, start) {
|
|
96266
|
+
let j = start - 1;
|
|
96267
|
+
while (j >= 0 && (code[j] === " " || code[j] === " "))
|
|
96268
|
+
j--;
|
|
96269
|
+
return j < 0 || code[j] === "\n" || code[j] === "\r";
|
|
96270
|
+
}
|
|
96271
|
+
function repairInboxHelperScalarDestructure(code) {
|
|
96272
|
+
if (!code || code.indexOf("__vqaWaitForEmail") === -1)
|
|
96273
|
+
return code;
|
|
96274
|
+
const inLiteral = literalPositionMask(code);
|
|
96275
|
+
const re = /\b(const|let|var)\s*\{\s*([A-Za-z_$][\w$]*)\s*(?::\s*([A-Za-z_$][\w$]*)\s*)?\}\s*=\s*await\s+__vqaWaitForEmail\s*\(\s*(['"`])(code|link)\4\s*\)/g;
|
|
96276
|
+
let out = "";
|
|
96277
|
+
let last = 0;
|
|
96278
|
+
let m;
|
|
96279
|
+
while ((m = re.exec(code)) !== null) {
|
|
96280
|
+
const [full, decl, prop, alias, quote, kind] = m;
|
|
96281
|
+
const start = m.index;
|
|
96282
|
+
if (inLiteral[start] || !atLineStart(code, start) || prop !== kind)
|
|
96283
|
+
continue;
|
|
96284
|
+
out += code.slice(last, start);
|
|
96285
|
+
out += `${decl} ${alias || prop} = await __vqaWaitForEmail(${quote}${kind}${quote})`;
|
|
96286
|
+
last = start + full.length;
|
|
96287
|
+
}
|
|
96288
|
+
out += code.slice(last);
|
|
96289
|
+
return out;
|
|
96290
|
+
}
|
|
96057
96291
|
function sanitizeCode(code, options) {
|
|
96058
96292
|
let result = code;
|
|
96059
96293
|
const isNetworkIdleWaitLine = (line) => /^\s*await page\.waitForLoadState\((['"])networkidle\1\);\s*$/.test(line);
|
|
@@ -96144,6 +96378,7 @@ ${testOpen}`;
|
|
|
96144
96378
|
if (options?.authStamp) {
|
|
96145
96379
|
result = (0, auth_recipe_js_1.upsertAuthBlock)(result, options.authStamp);
|
|
96146
96380
|
}
|
|
96381
|
+
result = repairInboxHelperScalarDestructure(result);
|
|
96147
96382
|
try {
|
|
96148
96383
|
result = (0, inbox_stamp_js_1.upsertInboxBlock)(result);
|
|
96149
96384
|
} catch {
|
|
@@ -319988,6 +320223,9 @@ var require_survey_link_frontier = __commonJS({
|
|
|
319988
320223
|
exports2.markLinkNavigationAttempt = markLinkNavigationAttempt;
|
|
319989
320224
|
exports2.getFinishBlockingFrontier = getFinishBlockingFrontier;
|
|
319990
320225
|
exports2.describeBlockingRoutes = describeBlockingRoutes;
|
|
320226
|
+
exports2.isTransientLanding = isTransientLanding;
|
|
320227
|
+
exports2.getAdaptiveDebtRoutes = getAdaptiveDebtRoutes;
|
|
320228
|
+
exports2.listTransientDebtSuppressions = listTransientDebtSuppressions;
|
|
319991
320229
|
var exploration_state_js_1 = require_exploration_state();
|
|
319992
320230
|
var exploration_parser_js_1 = require_exploration_parser();
|
|
319993
320231
|
exports2.MAX_FINISH_REDIRECTS = 2;
|
|
@@ -320347,6 +320585,132 @@ var require_survey_link_frontier = __commonJS({
|
|
|
320347
320585
|
function describeBlockingRoutes(items, max = 3) {
|
|
320348
320586
|
return items.slice(0, max).map((i) => i.label ? `${i.label} (${i.route})` : i.route).join(", ");
|
|
320349
320587
|
}
|
|
320588
|
+
function isOpaqueToken(v) {
|
|
320589
|
+
if (!v)
|
|
320590
|
+
return false;
|
|
320591
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v))
|
|
320592
|
+
return true;
|
|
320593
|
+
if (v.length >= 20 && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(v))
|
|
320594
|
+
return true;
|
|
320595
|
+
if (v.length >= 20 && !/[\s_-]/.test(v) && /[A-Za-z]/.test(v) && /\d/.test(v))
|
|
320596
|
+
return true;
|
|
320597
|
+
if (v.length >= 24 && /^[A-Za-z0-9+/=]+$/.test(v) && /\d/.test(v))
|
|
320598
|
+
return true;
|
|
320599
|
+
return false;
|
|
320600
|
+
}
|
|
320601
|
+
var FLASH_QUERY_KEYS = /* @__PURE__ */ new Set(["toast", "flash", "snackbar"]);
|
|
320602
|
+
var SESSION_TOKEN_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
320603
|
+
"sid",
|
|
320604
|
+
"sess",
|
|
320605
|
+
"sessid",
|
|
320606
|
+
"session",
|
|
320607
|
+
"sessionid",
|
|
320608
|
+
"jsessionid",
|
|
320609
|
+
"phpsessid"
|
|
320610
|
+
]);
|
|
320611
|
+
var ONE_TIME_TOKEN_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
320612
|
+
"token",
|
|
320613
|
+
"oobcode",
|
|
320614
|
+
"confirmation_token",
|
|
320615
|
+
"reset_token",
|
|
320616
|
+
"reset_password_token",
|
|
320617
|
+
"verification_token",
|
|
320618
|
+
"activation_token",
|
|
320619
|
+
"invitation_token",
|
|
320620
|
+
"unsubscribe_token"
|
|
320621
|
+
]);
|
|
320622
|
+
var ERROR_PATH_RE = /^\/(?:404|500|503|403|401|error|not[-_]?found|maintenance|oops|unauthorized|forbidden)(?:\/|$)/i;
|
|
320623
|
+
var OAUTH_CALLBACK_PATH_RE = /(?:^|\/)(?:oauth2?\/callback|auth\/callback|sso\/callback|oidc\/callback|signin[-_]oidc|auth\/response)(?:\/|$)/i;
|
|
320624
|
+
var ONE_TIME_PATH_PREFIX_RE = /^\/(?:verify|verify[-_]email|confirm|confirmation|activate|activation|reset[-_]password|password[-_]reset|magic[-_]?link|invitation|invite|unsubscribe|email[-_]confirm)\/([^/?#]+)/i;
|
|
320625
|
+
var INFRA_PATH_RE = /^\/(?:cdn-cgi|__\/auth|\.well-known|_next\/(?:static|data|image)|_nuxt|_vercel)(?:\/|$)/i;
|
|
320626
|
+
var DOWNLOAD_PATH_RE = /(?:^|\/)(?:download|export)\/([^/?#]+)$/i;
|
|
320627
|
+
function isTransientLanding(route2, depth = 0) {
|
|
320628
|
+
if (!route2 || route2 === "/" || depth > 4)
|
|
320629
|
+
return false;
|
|
320630
|
+
let url;
|
|
320631
|
+
try {
|
|
320632
|
+
url = new URL(route2, "http://transient.invalid");
|
|
320633
|
+
} catch {
|
|
320634
|
+
return false;
|
|
320635
|
+
}
|
|
320636
|
+
const path = url.pathname;
|
|
320637
|
+
const params = url.searchParams;
|
|
320638
|
+
if (url.hash.startsWith("#/") && isTransientLanding(url.hash.slice(1), depth + 1))
|
|
320639
|
+
return true;
|
|
320640
|
+
if (ERROR_PATH_RE.test(path))
|
|
320641
|
+
return true;
|
|
320642
|
+
if (INFRA_PATH_RE.test(path))
|
|
320643
|
+
return true;
|
|
320644
|
+
if (OAUTH_CALLBACK_PATH_RE.test(path))
|
|
320645
|
+
return true;
|
|
320646
|
+
if (isOpaqueToken(params.get("code")) && isOpaqueToken(params.get("state")))
|
|
320647
|
+
return true;
|
|
320648
|
+
if (params.has("error") && isOpaqueToken(params.get("state")))
|
|
320649
|
+
return true;
|
|
320650
|
+
if (/[#&?](?:access_token|id_token)=/.test(url.hash) || params.has("access_token") || params.has("id_token")) {
|
|
320651
|
+
return true;
|
|
320652
|
+
}
|
|
320653
|
+
for (const key of params.keys()) {
|
|
320654
|
+
if (FLASH_QUERY_KEYS.has(key.toLowerCase()))
|
|
320655
|
+
return true;
|
|
320656
|
+
}
|
|
320657
|
+
const otPath = ONE_TIME_PATH_PREFIX_RE.exec(path);
|
|
320658
|
+
if (otPath) {
|
|
320659
|
+
let seg = otPath[1];
|
|
320660
|
+
try {
|
|
320661
|
+
seg = decodeURIComponent(seg);
|
|
320662
|
+
} catch {
|
|
320663
|
+
}
|
|
320664
|
+
if (isOpaqueToken(seg))
|
|
320665
|
+
return true;
|
|
320666
|
+
}
|
|
320667
|
+
for (const [key, value] of params) {
|
|
320668
|
+
if (ONE_TIME_TOKEN_QUERY_KEYS.has(key.toLowerCase()) && isOpaqueToken(value))
|
|
320669
|
+
return true;
|
|
320670
|
+
}
|
|
320671
|
+
const dl = DOWNLOAD_PATH_RE.exec(path);
|
|
320672
|
+
if (dl && (/\.[a-z0-9]{2,5}$/i.test(dl[1]) || /\d/.test(dl[1])))
|
|
320673
|
+
return true;
|
|
320674
|
+
for (const [key, value] of params) {
|
|
320675
|
+
if (SESSION_TOKEN_QUERY_KEYS.has(key.toLowerCase()) && isOpaqueToken(value))
|
|
320676
|
+
return true;
|
|
320677
|
+
}
|
|
320678
|
+
return false;
|
|
320679
|
+
}
|
|
320680
|
+
function getAdaptiveDebtRoutes(state2) {
|
|
320681
|
+
const excluded = (r) => {
|
|
320682
|
+
if (r === "/")
|
|
320683
|
+
return true;
|
|
320684
|
+
const item = state2.frontier.get(r);
|
|
320685
|
+
if (item?.source === "link-seen" && item.attempted === true)
|
|
320686
|
+
return true;
|
|
320687
|
+
if (visitedSatisfies(state2, r))
|
|
320688
|
+
return true;
|
|
320689
|
+
if (item?.source !== "link-seen" && isTransientLanding(r))
|
|
320690
|
+
return true;
|
|
320691
|
+
return false;
|
|
320692
|
+
};
|
|
320693
|
+
const ranked = (0, exploration_state_js_1.getRankedFrontier)(state2, { preferShallow: true }).filter((r) => !excluded(r));
|
|
320694
|
+
const rankedSet = new Set(ranked);
|
|
320695
|
+
const navigatedUncaptured = [];
|
|
320696
|
+
for (const r of state2.frontier.keys()) {
|
|
320697
|
+
if (!rankedSet.has(r) && !excluded(r))
|
|
320698
|
+
navigatedUncaptured.push(r);
|
|
320699
|
+
}
|
|
320700
|
+
return ranked.concat(navigatedUncaptured);
|
|
320701
|
+
}
|
|
320702
|
+
function listTransientDebtSuppressions(state2) {
|
|
320703
|
+
const out = [];
|
|
320704
|
+
for (const [r, item] of state2.frontier) {
|
|
320705
|
+
if (r === "/" || item.source === "link-seen")
|
|
320706
|
+
continue;
|
|
320707
|
+
if (visitedSatisfies(state2, r))
|
|
320708
|
+
continue;
|
|
320709
|
+
if (isTransientLanding(r))
|
|
320710
|
+
out.push(r);
|
|
320711
|
+
}
|
|
320712
|
+
return out;
|
|
320713
|
+
}
|
|
320350
320714
|
}
|
|
320351
320715
|
});
|
|
320352
320716
|
|
|
@@ -324424,21 +324788,16 @@ ${inboxPromptBlock}`;
|
|
|
324424
324788
|
let discoveryCreditedThrough = 0;
|
|
324425
324789
|
let adaptiveLastDiscoveryIteration = Number.NEGATIVE_INFINITY;
|
|
324426
324790
|
let adaptiveDiscoveryLeaseUsed = false;
|
|
324791
|
+
let loggedTransientDebtSuppression = false;
|
|
324427
324792
|
const adaptiveDebtRoutes = () => {
|
|
324428
|
-
|
|
324429
|
-
const
|
|
324430
|
-
|
|
324431
|
-
|
|
324432
|
-
|
|
324433
|
-
const ranked = (0, exploration_state_js_1.getRankedFrontier)(state2, { preferShallow: true }).filter((r) => r !== "/" && !isAttemptedLinkEntry(r) && !isSatisfied(r));
|
|
324434
|
-
const rankedSet = new Set(ranked);
|
|
324435
|
-
const navigatedUncaptured = [];
|
|
324436
|
-
for (const r of state2.frontier.keys()) {
|
|
324437
|
-
if (r !== "/" && !rankedSet.has(r) && !isAttemptedLinkEntry(r) && !isSatisfied(r)) {
|
|
324438
|
-
navigatedUncaptured.push(r);
|
|
324793
|
+
if (!loggedTransientDebtSuppression) {
|
|
324794
|
+
const suppressed = (0, survey_link_frontier_js_1.listTransientDebtSuppressions)(state2);
|
|
324795
|
+
if (suppressed.length > 0) {
|
|
324796
|
+
loggedTransientDebtSuppression = true;
|
|
324797
|
+
logs2.push(` [debt-hygiene] excluded ${suppressed.length} transient landing(s) from adaptive debt: ${suppressed.slice(0, 6).join(", ")}${suppressed.length > 6 ? ` \u2026+${suppressed.length - 6}` : ""}`);
|
|
324439
324798
|
}
|
|
324440
324799
|
}
|
|
324441
|
-
return
|
|
324800
|
+
return (0, survey_link_frontier_js_1.getAdaptiveDebtRoutes)(state2);
|
|
324442
324801
|
};
|
|
324443
324802
|
const describeRankedRoutes = (routes, max) => routes.slice(0, max).map((r) => {
|
|
324444
324803
|
const label = state2.frontier.get(r)?.label;
|
|
@@ -334605,6 +334964,8 @@ var require_discover_agent = __commonJS({
|
|
|
334605
334964
|
var discover_planner_js_1 = require_discover_planner();
|
|
334606
334965
|
var discover_generator_js_1 = require_discover_generator();
|
|
334607
334966
|
var discover_ai_planner_js_1 = require_discover_ai_planner();
|
|
334967
|
+
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
334968
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
334608
334969
|
var sitemap_builder_js_1 = require_sitemap_builder();
|
|
334609
334970
|
Object.defineProperty(exports2, "buildInMemorySiteMap", { enumerable: true, get: function() {
|
|
334610
334971
|
return sitemap_builder_js_1.buildInMemorySiteMap;
|
|
@@ -334617,6 +334978,13 @@ var require_discover_agent = __commonJS({
|
|
|
334617
334978
|
var agentic_cli_constants_js_1 = require_agentic_cli_constants();
|
|
334618
334979
|
var canvas_detector_js_1 = require_canvas_detector();
|
|
334619
334980
|
var auth_recipe_js_1 = require_auth_recipe();
|
|
334981
|
+
function plannerEmailVerifyArmed(ctx) {
|
|
334982
|
+
if (!(0, discovery_lint_flags_js_1.isTestgenEmailVerifyEnabled)())
|
|
334983
|
+
return false;
|
|
334984
|
+
if (ctx.variableContext?.allTestVariables?.DISCOVERY_SHOULD_REGISTER !== "true")
|
|
334985
|
+
return false;
|
|
334986
|
+
return (0, verification_inbox_js_1.isVerificationInboxConfigured)();
|
|
334987
|
+
}
|
|
334620
334988
|
async function runCanvasPreflight(ctx, sharedBrowser, log2) {
|
|
334621
334989
|
const cached = ctx.appRenderType ?? null;
|
|
334622
334990
|
const engine = (0, agentic_cli_constants_js_1.getAgenticEngine)(ctx.exploreModel);
|
|
@@ -335265,6 +335633,9 @@ Explore complete: ${exploreResult.pagesDiscovered} pages, ${exploreResult.elemen
|
|
|
335265
335633
|
// instead of authoring landing→login steps (which generate would override).
|
|
335266
335634
|
authRecipeAvailable: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && !!ctx.authRecipe,
|
|
335267
335635
|
authRedirectRoute: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) ? ctx.authRecipe?.redirectRoute ?? void 0 : void 0,
|
|
335636
|
+
// Email-verify capability — only for scenario-producing modes; a survey emits
|
|
335637
|
+
// featureGroups (no scenarios), so keep its prompt free of the round-trip block.
|
|
335638
|
+
emailVerifyArmed: mode !== "survey" && plannerEmailVerifyArmed(ctx),
|
|
335268
335639
|
testBudget
|
|
335269
335640
|
}, {
|
|
335270
335641
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
@@ -335849,6 +336220,12 @@ Plan (reused): ${resumeSavedScenarios.length} scenarios from the paused run.`);
|
|
|
335849
336220
|
// authenticated state when a verified login recipe is on file for this project.
|
|
335850
336221
|
authRecipeAvailable: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && !!ctx.authRecipe,
|
|
335851
336222
|
authRedirectRoute: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) ? ctx.authRecipe?.redirectRoute ?? void 0 : void 0,
|
|
336223
|
+
// Email-verify capability: when armed, let the planner plan the full
|
|
336224
|
+
// register→verify and forgot-password→reset round trips to completion
|
|
336225
|
+
// (see the planner's "## Verification Inbox Available" block). Armed on the
|
|
336226
|
+
// SAME project-level predicate the generator uses (flag + register intent +
|
|
336227
|
+
// configured inbox), so the planner never promises what generation can't do.
|
|
336228
|
+
emailVerifyArmed: plannerEmailVerifyArmed(ctx),
|
|
335852
336229
|
testBudget: featureTestBudget
|
|
335853
336230
|
}, {
|
|
335854
336231
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
@@ -350474,7 +350851,7 @@ var require_package4 = __commonJS({
|
|
|
350474
350851
|
"package.json"(exports2, module2) {
|
|
350475
350852
|
module2.exports = {
|
|
350476
350853
|
name: "@validate.qa/runner",
|
|
350477
|
-
version: "1.0.
|
|
350854
|
+
version: "1.0.62",
|
|
350478
350855
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
350479
350856
|
bin: {
|
|
350480
350857
|
"validate-runner": "dist/cli.js"
|
|
@@ -355554,7 +355931,13 @@ async function handleHealth(_req, res) {
|
|
|
355554
355931
|
},
|
|
355555
355932
|
devices,
|
|
355556
355933
|
capabilities,
|
|
355557
|
-
activeSession: state.appiumSessionId
|
|
355934
|
+
activeSession: state.appiumSessionId,
|
|
355935
|
+
// THIS runner's Appium is executing mobile (APPIUM) test run(s) right now.
|
|
355936
|
+
// The web pre-flight reads this to warn before /bridge/session/start would
|
|
355937
|
+
// 409 — it must never be inferred from server-side global run counts, which
|
|
355938
|
+
// include unrelated projects and runners.
|
|
355939
|
+
executorBusy: executorBusyCount > 0,
|
|
355940
|
+
recordingActive: !!state.appiumSessionId && mirrorSessionOwner === "recording"
|
|
355558
355941
|
});
|
|
355559
355942
|
}
|
|
355560
355943
|
function realDeviceCapabilities2(platform3) {
|
|
@@ -355629,7 +356012,11 @@ function validateSessionStartBody(body) {
|
|
|
355629
356012
|
}
|
|
355630
356013
|
async function handleDevices(_req, res) {
|
|
355631
356014
|
const devices = discoverAllDevices({ includeApps: true });
|
|
355632
|
-
sendJSON(res, 200, {
|
|
356015
|
+
sendJSON(res, 200, {
|
|
356016
|
+
devices,
|
|
356017
|
+
executorBusy: executorBusyCount > 0,
|
|
356018
|
+
recordingActive: !!state.appiumSessionId && mirrorSessionOwner === "recording"
|
|
356019
|
+
});
|
|
355633
356020
|
}
|
|
355634
356021
|
async function handleSessionStart(req, res) {
|
|
355635
356022
|
const body = await parseBody(req);
|
package/dist/cli.mjs
CHANGED
|
@@ -2241,6 +2241,16 @@ function selectorForValue(value) {
|
|
|
2241
2241
|
function isIdentityMobileXpath(value) {
|
|
2242
2242
|
return /@[A-Za-z_:][-A-Za-z0-9_:.]*\s*=/.test(value) && !/(?:contains|starts-with)\s*\(/i.test(value);
|
|
2243
2243
|
}
|
|
2244
|
+
function mobileLocatorBundleHasIdentity(bundle) {
|
|
2245
|
+
if (!bundle)
|
|
2246
|
+
return false;
|
|
2247
|
+
if (nonEmpty(bundle.accessibilityId) || nonEmpty(bundle.resourceId))
|
|
2248
|
+
return true;
|
|
2249
|
+
if (nonEmpty(bundle.text) || nonEmpty(bundle.contentDesc))
|
|
2250
|
+
return true;
|
|
2251
|
+
const xpath = nonEmpty(bundle.xpath);
|
|
2252
|
+
return !!xpath && isIdentityMobileXpath(xpath);
|
|
2253
|
+
}
|
|
2244
2254
|
function selectorCandidatesForStep(step) {
|
|
2245
2255
|
const target = step.target;
|
|
2246
2256
|
if (!target)
|
|
@@ -2576,7 +2586,7 @@ function generateMobileTestFromSession(input) {
|
|
|
2576
2586
|
const warnings = [];
|
|
2577
2587
|
const unrunnable = interactionSteps.length - runnableInteractions.length;
|
|
2578
2588
|
if (unrunnable > 0) {
|
|
2579
|
-
warnings.push(`${unrunnable} recorded step(s) could not be turned into a runnable action (missing locator
|
|
2589
|
+
warnings.push(`${unrunnable} recorded step(s) could not be turned into a runnable action (missing a replayable identity locator or coordinate) and were kept best-effort.`);
|
|
2580
2590
|
}
|
|
2581
2591
|
const locatorCoverage = interactionSteps.filter((step) => step.target?.primary).length;
|
|
2582
2592
|
const assertionCoverage = steps.filter((step) => step.action === "assertVisible" || step.action === "assertText").length;
|
|
@@ -2605,10 +2615,29 @@ function generateMobileTestFromSession(input) {
|
|
|
2605
2615
|
warnings
|
|
2606
2616
|
};
|
|
2607
2617
|
}
|
|
2618
|
+
function isGateStepResolvable(step) {
|
|
2619
|
+
const metadata = step.metadata;
|
|
2620
|
+
if (metadata?.appContext === "WEBVIEW") {
|
|
2621
|
+
return selectorCandidatesForStep(step).length > 0;
|
|
2622
|
+
}
|
|
2623
|
+
const target = step.target;
|
|
2624
|
+
if (!target)
|
|
2625
|
+
return false;
|
|
2626
|
+
if (mobileLocatorBundleHasIdentity(target.locatorBundle))
|
|
2627
|
+
return true;
|
|
2628
|
+
return [target.primary, ...target.fallbacks ?? []].some((value) => {
|
|
2629
|
+
const trimmed = nonEmpty(value);
|
|
2630
|
+
if (!trimmed || trimmed.startsWith("css="))
|
|
2631
|
+
return false;
|
|
2632
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("(//"))
|
|
2633
|
+
return isIdentityMobileXpath(trimmed);
|
|
2634
|
+
return true;
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2608
2637
|
function isRunnableStep(step) {
|
|
2609
2638
|
const bounds = step.metadata?.bounds;
|
|
2610
2639
|
const hasCoord = !!bounds && [bounds.x, bounds.y].every((n) => typeof n === "number" && Number.isFinite(n));
|
|
2611
|
-
const
|
|
2640
|
+
const hasExecutableLocator = selectorCandidatesForStep(step).length > 0;
|
|
2612
2641
|
switch (step.action) {
|
|
2613
2642
|
case "launchApp":
|
|
2614
2643
|
case "back":
|
|
@@ -2618,18 +2647,18 @@ function isRunnableStep(step) {
|
|
|
2618
2647
|
return true;
|
|
2619
2648
|
case "tap":
|
|
2620
2649
|
case "clear":
|
|
2621
|
-
return
|
|
2650
|
+
return hasExecutableLocator || hasCoord;
|
|
2622
2651
|
case "type": {
|
|
2623
2652
|
const keyboardSubmitOnly = typeof step.value === "string" && /^[\r\n]+$/.test(step.value);
|
|
2624
2653
|
if (keyboardSubmitOnly)
|
|
2625
2654
|
return true;
|
|
2626
|
-
return (
|
|
2655
|
+
return (hasExecutableLocator || hasCoord) && !!nonEmpty(step.value ?? void 0);
|
|
2627
2656
|
}
|
|
2628
2657
|
case "waitForElement":
|
|
2629
2658
|
case "assertVisible":
|
|
2630
|
-
return
|
|
2659
|
+
return isGateStepResolvable(step);
|
|
2631
2660
|
case "assertText":
|
|
2632
|
-
return
|
|
2661
|
+
return isGateStepResolvable(step) && !!nonEmpty(step.assertion ?? void 0);
|
|
2633
2662
|
default:
|
|
2634
2663
|
return false;
|
|
2635
2664
|
}
|
|
@@ -5902,6 +5931,7 @@ __export(dist_exports, {
|
|
|
5902
5931
|
jwtExpiryMs: () => jwtExpiryMs,
|
|
5903
5932
|
mediumForMobilePlatform: () => mediumForMobilePlatform,
|
|
5904
5933
|
mobileCredentialPlaceholder: () => mobileCredentialPlaceholder,
|
|
5934
|
+
mobileLocatorBundleHasIdentity: () => mobileLocatorBundleHasIdentity,
|
|
5905
5935
|
mobilePlatformFromMedium: () => mobilePlatformFromMedium,
|
|
5906
5936
|
normalizeControlName: () => normalizeControlName,
|
|
5907
5937
|
normalizeTtsVoiceId: () => normalizeTtsVoiceId,
|
|
@@ -78268,6 +78298,12 @@ ${creds.join(", ")}`);
|
|
|
78268
78298
|
A verified login recipe EXISTS for this app \u2014 the harness automatically signs in (and asserts the signed-in state) before every authenticated test. This SUPERSEDES the "all tests start from the landing page and sign in" rule for the non-auth-flow scenarios described here.
|
|
78269
78299
|
For a scenario that REQUIRES an authenticated session but is NOT itself exercising the authentication / session lifecycle, you MAY assume the test begins ALREADY SIGNED IN at ${startRoute}. Do NOT author sign-in navigation, sign-in clicks, or credential-entry steps for it \u2014 begin at the first real feature action. BUT if the feature is not already on ${startRoute}, DO still include the in-app navigation from the signed-in landing to the feature page.
|
|
78270
78300
|
Plan these LOGGED-OUT instead (keep their own establishment steps; do NOT assume a session): any scenario that exercises the auth / session / account-access lifecycle itself \u2014 signing in, signing up / registration, password reset, email / OTP / MFA / 2FA verification, SSO or magic-link sign-in, re-authentication, and SIGNING OUT (which starts signed-in and ends signed-out). Also plan logged-out any journey whose first steps must run without a session (e.g. "register a new account, then use it" \u2014 keep the registration steps).`);
|
|
78301
|
+
}
|
|
78302
|
+
if (context.emailVerifyArmed) {
|
|
78303
|
+
sections.push(`## Verification Inbox Available (runtime email capability)
|
|
78304
|
+
This runner CAN read THIS test's verification email at runtime \u2014 a one-time code AND a verification link \u2014 for a fresh throwaway account the harness registers per run. That lets a SIGN-UP email-verification flow RUN TO COMPLETION instead of stopping at the "enter the code" / "check your email" screen (a survey has no inbox, so exploration could only reach that screen). Plan it as a HAPPY_PATH:
|
|
78305
|
+
- Sign-up + email verification: register a brand-new account, RETRIEVE the emailed code or link, and submit/open it to complete verification.
|
|
78306
|
+
Write "retrieve the emailed code / open the emailed link" as an ordinary plain-language step; the generator supplies the runtime mechanism. This is an exception to Rule 1 ONLY for the ACTION of completing the email step \u2014 it does NOT license inventing an outcome. For the durable assertion, Rule 12 still governs: assert only the post-verification result the evidence supports \u2014 a specific observed control, a confirmation message the explorer actually saw, or a STRUCTURAL signal (the verification screen is replaced, the URL leaves the verify route, the login form is no longer shown). If no concrete signed-in signal was observed, assert the observed transition and NAME the missing signal as a coverage gap in reasoning \u2014 do NOT fabricate a Logout/account control exploration never saw (this app may land on a login page or a confirmation page after verifying, not a signed-in dashboard). Keep it LOGGED-OUT with its own registration steps, and still plan the sign-up / verification negative cases the EVIDENCE supports (e.g. an already-registered email, or an invalid / mismatched verification code) as separate scenarios.`);
|
|
78271
78307
|
}
|
|
78272
78308
|
sections.push(`## Constraints
|
|
78273
78309
|
- Test budget: ${context.testBudget} scenarios maximum`);
|
|
@@ -94879,6 +94915,7 @@ var require_discover_generator = __commonJS({
|
|
|
94879
94915
|
"use strict";
|
|
94880
94916
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
94881
94917
|
exports.MIN_BUG_DESCRIPTION_LENGTH = exports.VALID_PLAYWRIGHT_GETBY_METHODS = exports.BLOCKED_MCP_TOOLS_SCENARIO_GEN = void 0;
|
|
94918
|
+
exports.scenarioLooksLikeRegistration = scenarioLooksLikeRegistration;
|
|
94882
94919
|
exports.filterMcpToolsForScenarioGen = filterMcpToolsForScenarioGen;
|
|
94883
94920
|
exports.detectScenarioSignals = detectScenarioSignals;
|
|
94884
94921
|
exports.scenarioStepsCoverAuth = scenarioStepsCoverAuth;
|
|
@@ -94891,6 +94928,7 @@ var require_discover_generator = __commonJS({
|
|
|
94891
94928
|
exports.stripImportDeclarations = stripImportDeclarations;
|
|
94892
94929
|
exports.stripStringsAndComments = stripStringsAndComments;
|
|
94893
94930
|
exports.gotoBasePathFromBaseUrl = gotoBasePathFromBaseUrl;
|
|
94931
|
+
exports.repairInboxHelperScalarDestructure = repairInboxHelperScalarDestructure;
|
|
94894
94932
|
exports.sanitizeCode = sanitizeCode;
|
|
94895
94933
|
exports.validateTests = validateTests;
|
|
94896
94934
|
exports.formatEvidenceForLiveGenerate = formatEvidenceForLiveGenerate;
|
|
@@ -94937,7 +94975,8 @@ var require_discover_generator = __commonJS({
|
|
|
94937
94975
|
var test_inbox_broker_js_1 = require_test_inbox_broker();
|
|
94938
94976
|
var REGISTER_SCENARIO_RE = /\b(register|registration|sign[\s-]?up|create[\s-]?(a[n]?[\s-]?)?(new[\s-]?)?account|verify[\s-]?(your[\s-]?)?email|email[\s-]?verification|confirm[\s-]?(your[\s-]?)?(email|account)|activate[\s-]?(your[\s-]?)?account|new[\s-]?account)\b/i;
|
|
94939
94977
|
function scenarioLooksLikeRegistration(scenario) {
|
|
94940
|
-
const
|
|
94978
|
+
const stepText = (scenario.steps ?? []).map((s) => `${s?.action ?? ""} ${s?.target ?? ""}`).join(" ");
|
|
94979
|
+
const hay = `${scenario.name ?? ""} ${scenario.goal ?? ""} ${scenario.description ?? ""} ${scenario.entryRoute ?? ""} ${(scenario.targetPages ?? []).join(" ")} ${stepText}`;
|
|
94941
94980
|
return REGISTER_SCENARIO_RE.test(hay);
|
|
94942
94981
|
}
|
|
94943
94982
|
function buildEmailVerifyGenBlock(genEmail, genPassword) {
|
|
@@ -94960,6 +94999,7 @@ EMIT (the code you put in finish_generate) \u2014 do NOT hardcode the throwaway
|
|
|
94960
94999
|
- code flow: \`const code = await __vqaWaitForEmail('code');\` then type \`code\` into the verification field.
|
|
94961
95000
|
- link flow: \`const link = await __vqaWaitForEmail('link');\` then \`await page.goto(link);\`.
|
|
94962
95001
|
- if unsure: \`const email = await __vqaWaitForEmail();\` returns \`{ code, link, subject, from }\`.
|
|
95002
|
+
- IMPORTANT: \`__vqaWaitForEmail('code')\` returns the code STRING (and \`('link')\` the link string) \u2014 assign it directly with \`const code = \u2026\`. Do NOT destructure the scalar form (\`const { code } = await __vqaWaitForEmail('code')\` binds \`undefined\`, so \`code.split(...)\` then crashes). ONLY the no-arg \`__vqaWaitForEmail()\` returns an object you can destructure.
|
|
94963
95003
|
- Tag the test \`@email-verify\`. Emit EXACTLY ONE test() in this file (one registration per run).
|
|
94964
95004
|
- End with a DURABLE assertion of the post-verification signed-in state (e.g. a Logout control, an account/dashboard landmark) in your OWN test body \u2014 the helper call is not itself a proof.`;
|
|
94965
95005
|
}
|
|
@@ -96059,6 +96099,200 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
96059
96099
|
return void 0;
|
|
96060
96100
|
}
|
|
96061
96101
|
}
|
|
96102
|
+
function literalPositionMask(code) {
|
|
96103
|
+
const n = code.length;
|
|
96104
|
+
const mask = new Array(n).fill(false);
|
|
96105
|
+
const REGEX_PRECEDING = /[(,=;{}\[!&|?:+\-*%~^<>]$/;
|
|
96106
|
+
const REGEX_PRECEDING_KEYWORD = /\b(?:return|typeof|void|delete|in|of|new|instanceof|throw|await|yield|case)\s*$/;
|
|
96107
|
+
let i = 0;
|
|
96108
|
+
let codeSoFar = "";
|
|
96109
|
+
const isRegexContext = () => {
|
|
96110
|
+
const trimmed = codeSoFar.replace(/\s+$/, "");
|
|
96111
|
+
if (trimmed.length === 0)
|
|
96112
|
+
return true;
|
|
96113
|
+
if (REGEX_PRECEDING.test(trimmed))
|
|
96114
|
+
return true;
|
|
96115
|
+
if (REGEX_PRECEDING_KEYWORD.test(trimmed))
|
|
96116
|
+
return true;
|
|
96117
|
+
if (trimmed.endsWith("=>"))
|
|
96118
|
+
return true;
|
|
96119
|
+
return false;
|
|
96120
|
+
};
|
|
96121
|
+
const scanRegex = () => {
|
|
96122
|
+
i += 1;
|
|
96123
|
+
let inClass = false;
|
|
96124
|
+
while (i < n) {
|
|
96125
|
+
if (code[i] === "\\") {
|
|
96126
|
+
mask[i] = true;
|
|
96127
|
+
if (i + 1 < n)
|
|
96128
|
+
mask[i + 1] = true;
|
|
96129
|
+
i += 2;
|
|
96130
|
+
continue;
|
|
96131
|
+
}
|
|
96132
|
+
if (code[i] === "[") {
|
|
96133
|
+
inClass = true;
|
|
96134
|
+
mask[i] = true;
|
|
96135
|
+
i += 1;
|
|
96136
|
+
continue;
|
|
96137
|
+
}
|
|
96138
|
+
if (code[i] === "]") {
|
|
96139
|
+
inClass = false;
|
|
96140
|
+
mask[i] = true;
|
|
96141
|
+
i += 1;
|
|
96142
|
+
continue;
|
|
96143
|
+
}
|
|
96144
|
+
if (code[i] === "/" && !inClass) {
|
|
96145
|
+
i += 1;
|
|
96146
|
+
break;
|
|
96147
|
+
}
|
|
96148
|
+
if (code[i] === "\n")
|
|
96149
|
+
break;
|
|
96150
|
+
mask[i] = true;
|
|
96151
|
+
i += 1;
|
|
96152
|
+
}
|
|
96153
|
+
while (i < n && /[gimsuydv]/.test(code[i]))
|
|
96154
|
+
i += 1;
|
|
96155
|
+
};
|
|
96156
|
+
const scanQuoted = (quote) => {
|
|
96157
|
+
i += 1;
|
|
96158
|
+
while (i < n) {
|
|
96159
|
+
if (code[i] === "\\") {
|
|
96160
|
+
if (code[i + 1] === "\r" && code[i + 2] === "\n") {
|
|
96161
|
+
mask[i] = true;
|
|
96162
|
+
mask[i + 1] = true;
|
|
96163
|
+
mask[i + 2] = true;
|
|
96164
|
+
i += 3;
|
|
96165
|
+
continue;
|
|
96166
|
+
}
|
|
96167
|
+
mask[i] = true;
|
|
96168
|
+
if (i + 1 < n)
|
|
96169
|
+
mask[i + 1] = true;
|
|
96170
|
+
i += 2;
|
|
96171
|
+
continue;
|
|
96172
|
+
}
|
|
96173
|
+
if (code[i] === quote) {
|
|
96174
|
+
i += 1;
|
|
96175
|
+
break;
|
|
96176
|
+
}
|
|
96177
|
+
if (code[i] === "\n")
|
|
96178
|
+
break;
|
|
96179
|
+
mask[i] = true;
|
|
96180
|
+
i += 1;
|
|
96181
|
+
}
|
|
96182
|
+
};
|
|
96183
|
+
const scanTemplate = () => {
|
|
96184
|
+
while (i < n) {
|
|
96185
|
+
if (code[i] === "\\") {
|
|
96186
|
+
mask[i] = true;
|
|
96187
|
+
if (i + 1 < n)
|
|
96188
|
+
mask[i + 1] = true;
|
|
96189
|
+
i += 2;
|
|
96190
|
+
continue;
|
|
96191
|
+
}
|
|
96192
|
+
if (code[i] === "`") {
|
|
96193
|
+
i += 1;
|
|
96194
|
+
break;
|
|
96195
|
+
}
|
|
96196
|
+
if (code[i] === "$" && code[i + 1] === "{") {
|
|
96197
|
+
i += 2;
|
|
96198
|
+
const savedCodeSoFar = codeSoFar;
|
|
96199
|
+
codeSoFar = "";
|
|
96200
|
+
scanCode(true);
|
|
96201
|
+
codeSoFar = savedCodeSoFar;
|
|
96202
|
+
continue;
|
|
96203
|
+
}
|
|
96204
|
+
mask[i] = true;
|
|
96205
|
+
i += 1;
|
|
96206
|
+
}
|
|
96207
|
+
};
|
|
96208
|
+
function scanCode(stopAtCloseBrace) {
|
|
96209
|
+
let depth = 0;
|
|
96210
|
+
while (i < n) {
|
|
96211
|
+
const c = code[i];
|
|
96212
|
+
const next = i + 1 < n ? code[i + 1] : "";
|
|
96213
|
+
if (stopAtCloseBrace) {
|
|
96214
|
+
if (c === "}" && depth === 0) {
|
|
96215
|
+
i += 1;
|
|
96216
|
+
return;
|
|
96217
|
+
}
|
|
96218
|
+
if (c === "{") {
|
|
96219
|
+
depth += 1;
|
|
96220
|
+
codeSoFar += c;
|
|
96221
|
+
i += 1;
|
|
96222
|
+
continue;
|
|
96223
|
+
}
|
|
96224
|
+
if (c === "}") {
|
|
96225
|
+
depth -= 1;
|
|
96226
|
+
codeSoFar += c;
|
|
96227
|
+
i += 1;
|
|
96228
|
+
continue;
|
|
96229
|
+
}
|
|
96230
|
+
}
|
|
96231
|
+
if (c === "/" && next === "*") {
|
|
96232
|
+
const end = code.indexOf("*/", i + 2);
|
|
96233
|
+
const stop = end < 0 ? n : end + 2;
|
|
96234
|
+
for (let j = i; j < stop; j++)
|
|
96235
|
+
mask[j] = true;
|
|
96236
|
+
i = stop;
|
|
96237
|
+
continue;
|
|
96238
|
+
}
|
|
96239
|
+
if (c === "/" && next === "/") {
|
|
96240
|
+
const end = code.indexOf("\n", i + 2);
|
|
96241
|
+
const stop = end < 0 ? n : end;
|
|
96242
|
+
for (let j = i; j < stop; j++)
|
|
96243
|
+
mask[j] = true;
|
|
96244
|
+
i = stop;
|
|
96245
|
+
continue;
|
|
96246
|
+
}
|
|
96247
|
+
if (c === "/" && isRegexContext()) {
|
|
96248
|
+
scanRegex();
|
|
96249
|
+
codeSoFar += "x";
|
|
96250
|
+
continue;
|
|
96251
|
+
}
|
|
96252
|
+
if (c === "`") {
|
|
96253
|
+
i += 1;
|
|
96254
|
+
scanTemplate();
|
|
96255
|
+
codeSoFar += "x";
|
|
96256
|
+
continue;
|
|
96257
|
+
}
|
|
96258
|
+
if (c === '"' || c === "'") {
|
|
96259
|
+
scanQuoted(c);
|
|
96260
|
+
codeSoFar += "x";
|
|
96261
|
+
continue;
|
|
96262
|
+
}
|
|
96263
|
+
codeSoFar += c;
|
|
96264
|
+
i += 1;
|
|
96265
|
+
}
|
|
96266
|
+
}
|
|
96267
|
+
scanCode(false);
|
|
96268
|
+
return mask;
|
|
96269
|
+
}
|
|
96270
|
+
function atLineStart(code, start) {
|
|
96271
|
+
let j = start - 1;
|
|
96272
|
+
while (j >= 0 && (code[j] === " " || code[j] === " "))
|
|
96273
|
+
j--;
|
|
96274
|
+
return j < 0 || code[j] === "\n" || code[j] === "\r";
|
|
96275
|
+
}
|
|
96276
|
+
function repairInboxHelperScalarDestructure(code) {
|
|
96277
|
+
if (!code || code.indexOf("__vqaWaitForEmail") === -1)
|
|
96278
|
+
return code;
|
|
96279
|
+
const inLiteral = literalPositionMask(code);
|
|
96280
|
+
const re = /\b(const|let|var)\s*\{\s*([A-Za-z_$][\w$]*)\s*(?::\s*([A-Za-z_$][\w$]*)\s*)?\}\s*=\s*await\s+__vqaWaitForEmail\s*\(\s*(['"`])(code|link)\4\s*\)/g;
|
|
96281
|
+
let out = "";
|
|
96282
|
+
let last = 0;
|
|
96283
|
+
let m;
|
|
96284
|
+
while ((m = re.exec(code)) !== null) {
|
|
96285
|
+
const [full, decl, prop, alias, quote, kind] = m;
|
|
96286
|
+
const start = m.index;
|
|
96287
|
+
if (inLiteral[start] || !atLineStart(code, start) || prop !== kind)
|
|
96288
|
+
continue;
|
|
96289
|
+
out += code.slice(last, start);
|
|
96290
|
+
out += `${decl} ${alias || prop} = await __vqaWaitForEmail(${quote}${kind}${quote})`;
|
|
96291
|
+
last = start + full.length;
|
|
96292
|
+
}
|
|
96293
|
+
out += code.slice(last);
|
|
96294
|
+
return out;
|
|
96295
|
+
}
|
|
96062
96296
|
function sanitizeCode(code, options) {
|
|
96063
96297
|
let result = code;
|
|
96064
96298
|
const isNetworkIdleWaitLine = (line) => /^\s*await page\.waitForLoadState\((['"])networkidle\1\);\s*$/.test(line);
|
|
@@ -96149,6 +96383,7 @@ ${testOpen}`;
|
|
|
96149
96383
|
if (options?.authStamp) {
|
|
96150
96384
|
result = (0, auth_recipe_js_1.upsertAuthBlock)(result, options.authStamp);
|
|
96151
96385
|
}
|
|
96386
|
+
result = repairInboxHelperScalarDestructure(result);
|
|
96152
96387
|
try {
|
|
96153
96388
|
result = (0, inbox_stamp_js_1.upsertInboxBlock)(result);
|
|
96154
96389
|
} catch {
|
|
@@ -319993,6 +320228,9 @@ var require_survey_link_frontier = __commonJS({
|
|
|
319993
320228
|
exports.markLinkNavigationAttempt = markLinkNavigationAttempt;
|
|
319994
320229
|
exports.getFinishBlockingFrontier = getFinishBlockingFrontier;
|
|
319995
320230
|
exports.describeBlockingRoutes = describeBlockingRoutes;
|
|
320231
|
+
exports.isTransientLanding = isTransientLanding;
|
|
320232
|
+
exports.getAdaptiveDebtRoutes = getAdaptiveDebtRoutes;
|
|
320233
|
+
exports.listTransientDebtSuppressions = listTransientDebtSuppressions;
|
|
319996
320234
|
var exploration_state_js_1 = require_exploration_state();
|
|
319997
320235
|
var exploration_parser_js_1 = require_exploration_parser();
|
|
319998
320236
|
exports.MAX_FINISH_REDIRECTS = 2;
|
|
@@ -320352,6 +320590,132 @@ var require_survey_link_frontier = __commonJS({
|
|
|
320352
320590
|
function describeBlockingRoutes(items, max = 3) {
|
|
320353
320591
|
return items.slice(0, max).map((i) => i.label ? `${i.label} (${i.route})` : i.route).join(", ");
|
|
320354
320592
|
}
|
|
320593
|
+
function isOpaqueToken(v) {
|
|
320594
|
+
if (!v)
|
|
320595
|
+
return false;
|
|
320596
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v))
|
|
320597
|
+
return true;
|
|
320598
|
+
if (v.length >= 20 && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(v))
|
|
320599
|
+
return true;
|
|
320600
|
+
if (v.length >= 20 && !/[\s_-]/.test(v) && /[A-Za-z]/.test(v) && /\d/.test(v))
|
|
320601
|
+
return true;
|
|
320602
|
+
if (v.length >= 24 && /^[A-Za-z0-9+/=]+$/.test(v) && /\d/.test(v))
|
|
320603
|
+
return true;
|
|
320604
|
+
return false;
|
|
320605
|
+
}
|
|
320606
|
+
var FLASH_QUERY_KEYS = /* @__PURE__ */ new Set(["toast", "flash", "snackbar"]);
|
|
320607
|
+
var SESSION_TOKEN_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
320608
|
+
"sid",
|
|
320609
|
+
"sess",
|
|
320610
|
+
"sessid",
|
|
320611
|
+
"session",
|
|
320612
|
+
"sessionid",
|
|
320613
|
+
"jsessionid",
|
|
320614
|
+
"phpsessid"
|
|
320615
|
+
]);
|
|
320616
|
+
var ONE_TIME_TOKEN_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
320617
|
+
"token",
|
|
320618
|
+
"oobcode",
|
|
320619
|
+
"confirmation_token",
|
|
320620
|
+
"reset_token",
|
|
320621
|
+
"reset_password_token",
|
|
320622
|
+
"verification_token",
|
|
320623
|
+
"activation_token",
|
|
320624
|
+
"invitation_token",
|
|
320625
|
+
"unsubscribe_token"
|
|
320626
|
+
]);
|
|
320627
|
+
var ERROR_PATH_RE = /^\/(?:404|500|503|403|401|error|not[-_]?found|maintenance|oops|unauthorized|forbidden)(?:\/|$)/i;
|
|
320628
|
+
var OAUTH_CALLBACK_PATH_RE = /(?:^|\/)(?:oauth2?\/callback|auth\/callback|sso\/callback|oidc\/callback|signin[-_]oidc|auth\/response)(?:\/|$)/i;
|
|
320629
|
+
var ONE_TIME_PATH_PREFIX_RE = /^\/(?:verify|verify[-_]email|confirm|confirmation|activate|activation|reset[-_]password|password[-_]reset|magic[-_]?link|invitation|invite|unsubscribe|email[-_]confirm)\/([^/?#]+)/i;
|
|
320630
|
+
var INFRA_PATH_RE = /^\/(?:cdn-cgi|__\/auth|\.well-known|_next\/(?:static|data|image)|_nuxt|_vercel)(?:\/|$)/i;
|
|
320631
|
+
var DOWNLOAD_PATH_RE = /(?:^|\/)(?:download|export)\/([^/?#]+)$/i;
|
|
320632
|
+
function isTransientLanding(route2, depth = 0) {
|
|
320633
|
+
if (!route2 || route2 === "/" || depth > 4)
|
|
320634
|
+
return false;
|
|
320635
|
+
let url;
|
|
320636
|
+
try {
|
|
320637
|
+
url = new URL(route2, "http://transient.invalid");
|
|
320638
|
+
} catch {
|
|
320639
|
+
return false;
|
|
320640
|
+
}
|
|
320641
|
+
const path = url.pathname;
|
|
320642
|
+
const params = url.searchParams;
|
|
320643
|
+
if (url.hash.startsWith("#/") && isTransientLanding(url.hash.slice(1), depth + 1))
|
|
320644
|
+
return true;
|
|
320645
|
+
if (ERROR_PATH_RE.test(path))
|
|
320646
|
+
return true;
|
|
320647
|
+
if (INFRA_PATH_RE.test(path))
|
|
320648
|
+
return true;
|
|
320649
|
+
if (OAUTH_CALLBACK_PATH_RE.test(path))
|
|
320650
|
+
return true;
|
|
320651
|
+
if (isOpaqueToken(params.get("code")) && isOpaqueToken(params.get("state")))
|
|
320652
|
+
return true;
|
|
320653
|
+
if (params.has("error") && isOpaqueToken(params.get("state")))
|
|
320654
|
+
return true;
|
|
320655
|
+
if (/[#&?](?:access_token|id_token)=/.test(url.hash) || params.has("access_token") || params.has("id_token")) {
|
|
320656
|
+
return true;
|
|
320657
|
+
}
|
|
320658
|
+
for (const key of params.keys()) {
|
|
320659
|
+
if (FLASH_QUERY_KEYS.has(key.toLowerCase()))
|
|
320660
|
+
return true;
|
|
320661
|
+
}
|
|
320662
|
+
const otPath = ONE_TIME_PATH_PREFIX_RE.exec(path);
|
|
320663
|
+
if (otPath) {
|
|
320664
|
+
let seg = otPath[1];
|
|
320665
|
+
try {
|
|
320666
|
+
seg = decodeURIComponent(seg);
|
|
320667
|
+
} catch {
|
|
320668
|
+
}
|
|
320669
|
+
if (isOpaqueToken(seg))
|
|
320670
|
+
return true;
|
|
320671
|
+
}
|
|
320672
|
+
for (const [key, value] of params) {
|
|
320673
|
+
if (ONE_TIME_TOKEN_QUERY_KEYS.has(key.toLowerCase()) && isOpaqueToken(value))
|
|
320674
|
+
return true;
|
|
320675
|
+
}
|
|
320676
|
+
const dl = DOWNLOAD_PATH_RE.exec(path);
|
|
320677
|
+
if (dl && (/\.[a-z0-9]{2,5}$/i.test(dl[1]) || /\d/.test(dl[1])))
|
|
320678
|
+
return true;
|
|
320679
|
+
for (const [key, value] of params) {
|
|
320680
|
+
if (SESSION_TOKEN_QUERY_KEYS.has(key.toLowerCase()) && isOpaqueToken(value))
|
|
320681
|
+
return true;
|
|
320682
|
+
}
|
|
320683
|
+
return false;
|
|
320684
|
+
}
|
|
320685
|
+
function getAdaptiveDebtRoutes(state2) {
|
|
320686
|
+
const excluded = (r) => {
|
|
320687
|
+
if (r === "/")
|
|
320688
|
+
return true;
|
|
320689
|
+
const item = state2.frontier.get(r);
|
|
320690
|
+
if (item?.source === "link-seen" && item.attempted === true)
|
|
320691
|
+
return true;
|
|
320692
|
+
if (visitedSatisfies(state2, r))
|
|
320693
|
+
return true;
|
|
320694
|
+
if (item?.source !== "link-seen" && isTransientLanding(r))
|
|
320695
|
+
return true;
|
|
320696
|
+
return false;
|
|
320697
|
+
};
|
|
320698
|
+
const ranked = (0, exploration_state_js_1.getRankedFrontier)(state2, { preferShallow: true }).filter((r) => !excluded(r));
|
|
320699
|
+
const rankedSet = new Set(ranked);
|
|
320700
|
+
const navigatedUncaptured = [];
|
|
320701
|
+
for (const r of state2.frontier.keys()) {
|
|
320702
|
+
if (!rankedSet.has(r) && !excluded(r))
|
|
320703
|
+
navigatedUncaptured.push(r);
|
|
320704
|
+
}
|
|
320705
|
+
return ranked.concat(navigatedUncaptured);
|
|
320706
|
+
}
|
|
320707
|
+
function listTransientDebtSuppressions(state2) {
|
|
320708
|
+
const out = [];
|
|
320709
|
+
for (const [r, item] of state2.frontier) {
|
|
320710
|
+
if (r === "/" || item.source === "link-seen")
|
|
320711
|
+
continue;
|
|
320712
|
+
if (visitedSatisfies(state2, r))
|
|
320713
|
+
continue;
|
|
320714
|
+
if (isTransientLanding(r))
|
|
320715
|
+
out.push(r);
|
|
320716
|
+
}
|
|
320717
|
+
return out;
|
|
320718
|
+
}
|
|
320355
320719
|
}
|
|
320356
320720
|
});
|
|
320357
320721
|
|
|
@@ -324429,21 +324793,16 @@ ${inboxPromptBlock}`;
|
|
|
324429
324793
|
let discoveryCreditedThrough = 0;
|
|
324430
324794
|
let adaptiveLastDiscoveryIteration = Number.NEGATIVE_INFINITY;
|
|
324431
324795
|
let adaptiveDiscoveryLeaseUsed = false;
|
|
324796
|
+
let loggedTransientDebtSuppression = false;
|
|
324432
324797
|
const adaptiveDebtRoutes = () => {
|
|
324433
|
-
|
|
324434
|
-
const
|
|
324435
|
-
|
|
324436
|
-
|
|
324437
|
-
|
|
324438
|
-
const ranked = (0, exploration_state_js_1.getRankedFrontier)(state2, { preferShallow: true }).filter((r) => r !== "/" && !isAttemptedLinkEntry(r) && !isSatisfied(r));
|
|
324439
|
-
const rankedSet = new Set(ranked);
|
|
324440
|
-
const navigatedUncaptured = [];
|
|
324441
|
-
for (const r of state2.frontier.keys()) {
|
|
324442
|
-
if (r !== "/" && !rankedSet.has(r) && !isAttemptedLinkEntry(r) && !isSatisfied(r)) {
|
|
324443
|
-
navigatedUncaptured.push(r);
|
|
324798
|
+
if (!loggedTransientDebtSuppression) {
|
|
324799
|
+
const suppressed = (0, survey_link_frontier_js_1.listTransientDebtSuppressions)(state2);
|
|
324800
|
+
if (suppressed.length > 0) {
|
|
324801
|
+
loggedTransientDebtSuppression = true;
|
|
324802
|
+
logs2.push(` [debt-hygiene] excluded ${suppressed.length} transient landing(s) from adaptive debt: ${suppressed.slice(0, 6).join(", ")}${suppressed.length > 6 ? ` \u2026+${suppressed.length - 6}` : ""}`);
|
|
324444
324803
|
}
|
|
324445
324804
|
}
|
|
324446
|
-
return
|
|
324805
|
+
return (0, survey_link_frontier_js_1.getAdaptiveDebtRoutes)(state2);
|
|
324447
324806
|
};
|
|
324448
324807
|
const describeRankedRoutes = (routes, max) => routes.slice(0, max).map((r) => {
|
|
324449
324808
|
const label = state2.frontier.get(r)?.label;
|
|
@@ -334610,6 +334969,8 @@ var require_discover_agent = __commonJS({
|
|
|
334610
334969
|
var discover_planner_js_1 = require_discover_planner();
|
|
334611
334970
|
var discover_generator_js_1 = require_discover_generator();
|
|
334612
334971
|
var discover_ai_planner_js_1 = require_discover_ai_planner();
|
|
334972
|
+
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
334973
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
334613
334974
|
var sitemap_builder_js_1 = require_sitemap_builder();
|
|
334614
334975
|
Object.defineProperty(exports, "buildInMemorySiteMap", { enumerable: true, get: function() {
|
|
334615
334976
|
return sitemap_builder_js_1.buildInMemorySiteMap;
|
|
@@ -334622,6 +334983,13 @@ var require_discover_agent = __commonJS({
|
|
|
334622
334983
|
var agentic_cli_constants_js_1 = require_agentic_cli_constants();
|
|
334623
334984
|
var canvas_detector_js_1 = require_canvas_detector();
|
|
334624
334985
|
var auth_recipe_js_1 = require_auth_recipe();
|
|
334986
|
+
function plannerEmailVerifyArmed(ctx) {
|
|
334987
|
+
if (!(0, discovery_lint_flags_js_1.isTestgenEmailVerifyEnabled)())
|
|
334988
|
+
return false;
|
|
334989
|
+
if (ctx.variableContext?.allTestVariables?.DISCOVERY_SHOULD_REGISTER !== "true")
|
|
334990
|
+
return false;
|
|
334991
|
+
return (0, verification_inbox_js_1.isVerificationInboxConfigured)();
|
|
334992
|
+
}
|
|
334625
334993
|
async function runCanvasPreflight(ctx, sharedBrowser, log2) {
|
|
334626
334994
|
const cached = ctx.appRenderType ?? null;
|
|
334627
334995
|
const engine = (0, agentic_cli_constants_js_1.getAgenticEngine)(ctx.exploreModel);
|
|
@@ -335270,6 +335638,9 @@ Explore complete: ${exploreResult.pagesDiscovered} pages, ${exploreResult.elemen
|
|
|
335270
335638
|
// instead of authoring landing→login steps (which generate would override).
|
|
335271
335639
|
authRecipeAvailable: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && !!ctx.authRecipe,
|
|
335272
335640
|
authRedirectRoute: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) ? ctx.authRecipe?.redirectRoute ?? void 0 : void 0,
|
|
335641
|
+
// Email-verify capability — only for scenario-producing modes; a survey emits
|
|
335642
|
+
// featureGroups (no scenarios), so keep its prompt free of the round-trip block.
|
|
335643
|
+
emailVerifyArmed: mode !== "survey" && plannerEmailVerifyArmed(ctx),
|
|
335273
335644
|
testBudget
|
|
335274
335645
|
}, {
|
|
335275
335646
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
@@ -335854,6 +336225,12 @@ Plan (reused): ${resumeSavedScenarios.length} scenarios from the paused run.`);
|
|
|
335854
336225
|
// authenticated state when a verified login recipe is on file for this project.
|
|
335855
336226
|
authRecipeAvailable: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && !!ctx.authRecipe,
|
|
335856
336227
|
authRedirectRoute: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) ? ctx.authRecipe?.redirectRoute ?? void 0 : void 0,
|
|
336228
|
+
// Email-verify capability: when armed, let the planner plan the full
|
|
336229
|
+
// register→verify and forgot-password→reset round trips to completion
|
|
336230
|
+
// (see the planner's "## Verification Inbox Available" block). Armed on the
|
|
336231
|
+
// SAME project-level predicate the generator uses (flag + register intent +
|
|
336232
|
+
// configured inbox), so the planner never promises what generation can't do.
|
|
336233
|
+
emailVerifyArmed: plannerEmailVerifyArmed(ctx),
|
|
335857
336234
|
testBudget: featureTestBudget
|
|
335858
336235
|
}, {
|
|
335859
336236
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
@@ -350479,7 +350856,7 @@ var require_package4 = __commonJS({
|
|
|
350479
350856
|
"package.json"(exports, module) {
|
|
350480
350857
|
module.exports = {
|
|
350481
350858
|
name: "@validate.qa/runner",
|
|
350482
|
-
version: "1.0.
|
|
350859
|
+
version: "1.0.62",
|
|
350483
350860
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
350484
350861
|
bin: {
|
|
350485
350862
|
"validate-runner": "dist/cli.js"
|
|
@@ -355562,7 +355939,13 @@ async function handleHealth(_req, res) {
|
|
|
355562
355939
|
},
|
|
355563
355940
|
devices,
|
|
355564
355941
|
capabilities,
|
|
355565
|
-
activeSession: state.appiumSessionId
|
|
355942
|
+
activeSession: state.appiumSessionId,
|
|
355943
|
+
// THIS runner's Appium is executing mobile (APPIUM) test run(s) right now.
|
|
355944
|
+
// The web pre-flight reads this to warn before /bridge/session/start would
|
|
355945
|
+
// 409 — it must never be inferred from server-side global run counts, which
|
|
355946
|
+
// include unrelated projects and runners.
|
|
355947
|
+
executorBusy: executorBusyCount > 0,
|
|
355948
|
+
recordingActive: !!state.appiumSessionId && mirrorSessionOwner === "recording"
|
|
355566
355949
|
});
|
|
355567
355950
|
}
|
|
355568
355951
|
function realDeviceCapabilities2(platform3) {
|
|
@@ -355637,7 +356020,11 @@ function validateSessionStartBody(body) {
|
|
|
355637
356020
|
}
|
|
355638
356021
|
async function handleDevices(_req, res) {
|
|
355639
356022
|
const devices = discoverAllDevices({ includeApps: true });
|
|
355640
|
-
sendJSON(res, 200, {
|
|
356023
|
+
sendJSON(res, 200, {
|
|
356024
|
+
devices,
|
|
356025
|
+
executorBusy: executorBusyCount > 0,
|
|
356026
|
+
recordingActive: !!state.appiumSessionId && mirrorSessionOwner === "recording"
|
|
356027
|
+
});
|
|
355641
356028
|
}
|
|
355642
356029
|
async function handleSessionStart(req, res) {
|
|
355643
356030
|
const body = await parseBody(req);
|
package/package.json
CHANGED