@validate.qa/runner 1.0.59 → 1.0.61
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 +381 -12
- package/dist/cli.mjs +381 -12
- 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,
|
|
@@ -73342,6 +73372,58 @@ var require_verification_inbox = __commonJS({
|
|
|
73342
73372
|
}
|
|
73343
73373
|
});
|
|
73344
73374
|
|
|
73375
|
+
// ../runner-core/dist/services/brokered-inbox-reader.js
|
|
73376
|
+
var require_brokered_inbox_reader = __commonJS({
|
|
73377
|
+
"../runner-core/dist/services/brokered-inbox-reader.js"(exports2) {
|
|
73378
|
+
"use strict";
|
|
73379
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
73380
|
+
exports2.BrokeredInboxReader = void 0;
|
|
73381
|
+
var BROKERED_WAIT_CHUNK_MS = 55e3;
|
|
73382
|
+
var BrokeredInboxReader = class {
|
|
73383
|
+
transport;
|
|
73384
|
+
constructor(transport) {
|
|
73385
|
+
this.transport = transport;
|
|
73386
|
+
}
|
|
73387
|
+
async getUidWatermark() {
|
|
73388
|
+
const value = await this.transport("getUidWatermark", {});
|
|
73389
|
+
const record = value ?? {};
|
|
73390
|
+
return {
|
|
73391
|
+
uid: typeof record.uid === "number" ? record.uid : 0,
|
|
73392
|
+
uidValidity: typeof record.uidValidity === "number" ? record.uidValidity : 0
|
|
73393
|
+
};
|
|
73394
|
+
}
|
|
73395
|
+
async search(opts) {
|
|
73396
|
+
const value = await this.transport("search", { opts });
|
|
73397
|
+
return Array.isArray(value) ? value : [];
|
|
73398
|
+
}
|
|
73399
|
+
async getLatest(opts) {
|
|
73400
|
+
const value = await this.transport("getLatest", { opts });
|
|
73401
|
+
return value ?? null;
|
|
73402
|
+
}
|
|
73403
|
+
async getById(uid, address, altAddress, freshness) {
|
|
73404
|
+
const value = await this.transport("getById", { uid, address, altAddress, freshness });
|
|
73405
|
+
return value ?? null;
|
|
73406
|
+
}
|
|
73407
|
+
async waitForMessage(opts) {
|
|
73408
|
+
const totalBudgetMs = Math.max(opts.maxWaitMs ?? 9e4, 1e3);
|
|
73409
|
+
const deadline = Date.now() + totalBudgetMs;
|
|
73410
|
+
for (; ; ) {
|
|
73411
|
+
const remaining = deadline - Date.now();
|
|
73412
|
+
if (remaining <= 0)
|
|
73413
|
+
return null;
|
|
73414
|
+
const chunkMs = Math.min(remaining, BROKERED_WAIT_CHUNK_MS);
|
|
73415
|
+
const value = await this.transport("waitForMessage", {
|
|
73416
|
+
opts: { ...opts, maxWaitMs: chunkMs }
|
|
73417
|
+
});
|
|
73418
|
+
if (value)
|
|
73419
|
+
return value;
|
|
73420
|
+
}
|
|
73421
|
+
}
|
|
73422
|
+
};
|
|
73423
|
+
exports2.BrokeredInboxReader = BrokeredInboxReader;
|
|
73424
|
+
}
|
|
73425
|
+
});
|
|
73426
|
+
|
|
73345
73427
|
// ../runner-core/dist/services/inbox-tools.js
|
|
73346
73428
|
var require_inbox_tools = __commonJS({
|
|
73347
73429
|
"../runner-core/dist/services/inbox-tools.js"(exports2) {
|
|
@@ -74051,6 +74133,7 @@ var require_mobile_explorer = __commonJS({
|
|
|
74051
74133
|
var mobile_auth_coverage_js_1 = require_mobile_auth_coverage();
|
|
74052
74134
|
var mobile_driver_errors_js_1 = require_mobile_driver_errors();
|
|
74053
74135
|
var verification_inbox_js_1 = require_verification_inbox();
|
|
74136
|
+
var brokered_inbox_reader_js_1 = require_brokered_inbox_reader();
|
|
74054
74137
|
var inbox_tools_js_1 = require_inbox_tools();
|
|
74055
74138
|
var mobile_discovery_auth_js_1 = require_mobile_discovery_auth();
|
|
74056
74139
|
var MAX_MOBILE_ITERATIONS_SURVEY = 40;
|
|
@@ -75372,6 +75455,23 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
75372
75455
|
log: (line) => log2(line)
|
|
75373
75456
|
});
|
|
75374
75457
|
log2("Verification inbox ENABLED for registration \u2014 monitored identity provisioned.");
|
|
75458
|
+
} else if (registerRequested && ctx.inboxBroker?.address && ctx.inboxBrokerRead) {
|
|
75459
|
+
inboxState = (0, inbox_tools_js_1.createInboxRunState)({ address: ctx.inboxBroker.address }, {
|
|
75460
|
+
tagSeed: targetAppId ?? ctx.projectId,
|
|
75461
|
+
projectScope: ctx.projectId
|
|
75462
|
+
});
|
|
75463
|
+
inboxReader = new brokered_inbox_reader_js_1.BrokeredInboxReader(ctx.inboxBrokerRead);
|
|
75464
|
+
await (0, inbox_tools_js_1.applyAndPersistInboxIdentity)({
|
|
75465
|
+
variableContext: ctx.variableContext,
|
|
75466
|
+
registration: {
|
|
75467
|
+
email: inboxState.email,
|
|
75468
|
+
legacyEmail: inboxState.legacyEmail,
|
|
75469
|
+
passwordKey: inboxState.passwordKey ?? "TEST_USER_PASSWORD"
|
|
75470
|
+
},
|
|
75471
|
+
persist: ctx.onCredentialUpdate,
|
|
75472
|
+
log: (line) => log2(line)
|
|
75473
|
+
});
|
|
75474
|
+
log2("Verification inbox ENABLED via server broker \u2014 monitored identity provisioned (IMAP credentials stay server-side).");
|
|
75375
75475
|
} else if (registerRequested) {
|
|
75376
75476
|
log2("Registration requested without a verification inbox \u2014 preserving the seeded credential identity and reporting any verification wall as unauthenticated.");
|
|
75377
75477
|
}
|
|
@@ -78193,6 +78293,12 @@ ${creds.join(", ")}`);
|
|
|
78193
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.
|
|
78194
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.
|
|
78195
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.`);
|
|
78196
78302
|
}
|
|
78197
78303
|
sections.push(`## Constraints
|
|
78198
78304
|
- Test budget: ${context.testBudget} scenarios maximum`);
|
|
@@ -84286,12 +84392,14 @@ ${args.directive}`);
|
|
|
84286
84392
|
{ role: "user", content: userContent }
|
|
84287
84393
|
];
|
|
84288
84394
|
let content;
|
|
84395
|
+
let finishReason;
|
|
84289
84396
|
try {
|
|
84290
84397
|
const response = await (0, ai_retry_js_1.resilientAICall)((signal, arm) => (0, ai_queue_js_1.queuedAICall)(() => {
|
|
84291
84398
|
arm();
|
|
84292
84399
|
return aiClient.chat.completions.create({ model, messages, max_tokens: 8e3, temperature: 0.1 }, { signal });
|
|
84293
84400
|
}), { label: `mobile-heal-attempt-${attempt}`, deferTimeoutUntilArmed: true });
|
|
84294
84401
|
content = response?.choices?.[0]?.message?.content ?? "";
|
|
84402
|
+
finishReason = response?.choices?.[0]?.finish_reason ?? void 0;
|
|
84295
84403
|
} catch {
|
|
84296
84404
|
options.onProgress?.(`Phase 1 attempt ${attempt}: model call failed in transport \u2014 granting a recovery turn`);
|
|
84297
84405
|
markRecoverableTurn();
|
|
@@ -84301,7 +84409,7 @@ ${args.directive}`);
|
|
|
84301
84409
|
aiResponseCount += 1;
|
|
84302
84410
|
const parsed = parseHealVerdict(content);
|
|
84303
84411
|
if (!parsed) {
|
|
84304
|
-
options.onProgress?.(`Phase 1 attempt ${attempt}: response was not a valid verdict \u2014 granting a recovery turn`);
|
|
84412
|
+
options.onProgress?.(`Phase 1 attempt ${attempt}: response was not a valid verdict (${content.length} chars, finish=${finishReason ?? "unknown"}) \u2014 granting a recovery turn`);
|
|
84305
84413
|
markRecoverableTurn();
|
|
84306
84414
|
pendingDirective = "Your last response was not valid JSON matching the verdict schema. Return exactly one JSON verdict object.";
|
|
84307
84415
|
continue;
|
|
@@ -94802,6 +94910,7 @@ var require_discover_generator = __commonJS({
|
|
|
94802
94910
|
"use strict";
|
|
94803
94911
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
94804
94912
|
exports2.MIN_BUG_DESCRIPTION_LENGTH = exports2.VALID_PLAYWRIGHT_GETBY_METHODS = exports2.BLOCKED_MCP_TOOLS_SCENARIO_GEN = void 0;
|
|
94913
|
+
exports2.scenarioLooksLikeRegistration = scenarioLooksLikeRegistration;
|
|
94805
94914
|
exports2.filterMcpToolsForScenarioGen = filterMcpToolsForScenarioGen;
|
|
94806
94915
|
exports2.detectScenarioSignals = detectScenarioSignals;
|
|
94807
94916
|
exports2.scenarioStepsCoverAuth = scenarioStepsCoverAuth;
|
|
@@ -94814,6 +94923,7 @@ var require_discover_generator = __commonJS({
|
|
|
94814
94923
|
exports2.stripImportDeclarations = stripImportDeclarations;
|
|
94815
94924
|
exports2.stripStringsAndComments = stripStringsAndComments;
|
|
94816
94925
|
exports2.gotoBasePathFromBaseUrl = gotoBasePathFromBaseUrl;
|
|
94926
|
+
exports2.repairInboxHelperScalarDestructure = repairInboxHelperScalarDestructure;
|
|
94817
94927
|
exports2.sanitizeCode = sanitizeCode;
|
|
94818
94928
|
exports2.validateTests = validateTests;
|
|
94819
94929
|
exports2.formatEvidenceForLiveGenerate = formatEvidenceForLiveGenerate;
|
|
@@ -94860,7 +94970,8 @@ var require_discover_generator = __commonJS({
|
|
|
94860
94970
|
var test_inbox_broker_js_1 = require_test_inbox_broker();
|
|
94861
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;
|
|
94862
94972
|
function scenarioLooksLikeRegistration(scenario) {
|
|
94863
|
-
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}`;
|
|
94864
94975
|
return REGISTER_SCENARIO_RE.test(hay);
|
|
94865
94976
|
}
|
|
94866
94977
|
function buildEmailVerifyGenBlock(genEmail, genPassword) {
|
|
@@ -94883,6 +94994,7 @@ EMIT (the code you put in finish_generate) \u2014 do NOT hardcode the throwaway
|
|
|
94883
94994
|
- code flow: \`const code = await __vqaWaitForEmail('code');\` then type \`code\` into the verification field.
|
|
94884
94995
|
- link flow: \`const link = await __vqaWaitForEmail('link');\` then \`await page.goto(link);\`.
|
|
94885
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.
|
|
94886
94998
|
- Tag the test \`@email-verify\`. Emit EXACTLY ONE test() in this file (one registration per run).
|
|
94887
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.`;
|
|
94888
95000
|
}
|
|
@@ -95982,6 +96094,200 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
95982
96094
|
return void 0;
|
|
95983
96095
|
}
|
|
95984
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
|
+
}
|
|
95985
96291
|
function sanitizeCode(code, options) {
|
|
95986
96292
|
let result = code;
|
|
95987
96293
|
const isNetworkIdleWaitLine = (line) => /^\s*await page\.waitForLoadState\((['"])networkidle\1\);\s*$/.test(line);
|
|
@@ -96072,6 +96378,7 @@ ${testOpen}`;
|
|
|
96072
96378
|
if (options?.authStamp) {
|
|
96073
96379
|
result = (0, auth_recipe_js_1.upsertAuthBlock)(result, options.authStamp);
|
|
96074
96380
|
}
|
|
96381
|
+
result = repairInboxHelperScalarDestructure(result);
|
|
96075
96382
|
try {
|
|
96076
96383
|
result = (0, inbox_stamp_js_1.upsertInboxBlock)(result);
|
|
96077
96384
|
} catch {
|
|
@@ -334533,6 +334840,8 @@ var require_discover_agent = __commonJS({
|
|
|
334533
334840
|
var discover_planner_js_1 = require_discover_planner();
|
|
334534
334841
|
var discover_generator_js_1 = require_discover_generator();
|
|
334535
334842
|
var discover_ai_planner_js_1 = require_discover_ai_planner();
|
|
334843
|
+
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
334844
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
334536
334845
|
var sitemap_builder_js_1 = require_sitemap_builder();
|
|
334537
334846
|
Object.defineProperty(exports2, "buildInMemorySiteMap", { enumerable: true, get: function() {
|
|
334538
334847
|
return sitemap_builder_js_1.buildInMemorySiteMap;
|
|
@@ -334545,6 +334854,13 @@ var require_discover_agent = __commonJS({
|
|
|
334545
334854
|
var agentic_cli_constants_js_1 = require_agentic_cli_constants();
|
|
334546
334855
|
var canvas_detector_js_1 = require_canvas_detector();
|
|
334547
334856
|
var auth_recipe_js_1 = require_auth_recipe();
|
|
334857
|
+
function plannerEmailVerifyArmed(ctx) {
|
|
334858
|
+
if (!(0, discovery_lint_flags_js_1.isTestgenEmailVerifyEnabled)())
|
|
334859
|
+
return false;
|
|
334860
|
+
if (ctx.variableContext?.allTestVariables?.DISCOVERY_SHOULD_REGISTER !== "true")
|
|
334861
|
+
return false;
|
|
334862
|
+
return (0, verification_inbox_js_1.isVerificationInboxConfigured)();
|
|
334863
|
+
}
|
|
334548
334864
|
async function runCanvasPreflight(ctx, sharedBrowser, log2) {
|
|
334549
334865
|
const cached = ctx.appRenderType ?? null;
|
|
334550
334866
|
const engine = (0, agentic_cli_constants_js_1.getAgenticEngine)(ctx.exploreModel);
|
|
@@ -335193,6 +335509,9 @@ Explore complete: ${exploreResult.pagesDiscovered} pages, ${exploreResult.elemen
|
|
|
335193
335509
|
// instead of authoring landing→login steps (which generate would override).
|
|
335194
335510
|
authRecipeAvailable: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && !!ctx.authRecipe,
|
|
335195
335511
|
authRedirectRoute: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) ? ctx.authRecipe?.redirectRoute ?? void 0 : void 0,
|
|
335512
|
+
// Email-verify capability — only for scenario-producing modes; a survey emits
|
|
335513
|
+
// featureGroups (no scenarios), so keep its prompt free of the round-trip block.
|
|
335514
|
+
emailVerifyArmed: mode !== "survey" && plannerEmailVerifyArmed(ctx),
|
|
335196
335515
|
testBudget
|
|
335197
335516
|
}, {
|
|
335198
335517
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
@@ -335777,6 +336096,12 @@ Plan (reused): ${resumeSavedScenarios.length} scenarios from the paused run.`);
|
|
|
335777
336096
|
// authenticated state when a verified login recipe is on file for this project.
|
|
335778
336097
|
authRecipeAvailable: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && !!ctx.authRecipe,
|
|
335779
336098
|
authRedirectRoute: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) ? ctx.authRecipe?.redirectRoute ?? void 0 : void 0,
|
|
336099
|
+
// Email-verify capability: when armed, let the planner plan the full
|
|
336100
|
+
// register→verify and forgot-password→reset round trips to completion
|
|
336101
|
+
// (see the planner's "## Verification Inbox Available" block). Armed on the
|
|
336102
|
+
// SAME project-level predicate the generator uses (flag + register intent +
|
|
336103
|
+
// configured inbox), so the planner never promises what generation can't do.
|
|
336104
|
+
emailVerifyArmed: plannerEmailVerifyArmed(ctx),
|
|
335780
336105
|
testBudget: featureTestBudget
|
|
335781
336106
|
}, {
|
|
335782
336107
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
@@ -349952,7 +350277,7 @@ var require_dist2 = __commonJS({
|
|
|
349952
350277
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
349953
350278
|
exports2.resolveRefFromSnapshot = exports2.replayAuthRecipeIntoBrowser = exports2.runRegisterCaptureRun = exports2.runLoginCaptureRun = exports2.filterLoginCaptureCredentials = exports2.buildLoginCaptureCredentials = exports2.isReservedRunnerEnvVar = exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = exports2.buildHealAuthStamp = exports2.extractAuthRegion = exports2.upsertAuthBlock = exports2.buildAuthRecipe = exports2.commandToReplayStep = exports2.buildReplayPlan = exports2.absoluteRoute = exports2.renderReplaySettleArrow = exports2.renderReplayClickArrow = exports2.renderAuthFn = exports2.filterFeatureGroupsToPathScope = exports2.isAuthFlowRoute = exports2.deriveSurveyPathScope = exports2.canonicalizeRoute = exports2.codeUsesInboxHelper = exports2.codeUsesTestInboxIdentity = exports2.codeSelfProvisionsRegistration = exports2.executeGrokPerBatchHeal = exports2.executeGrokPerTestHeal = exports2.headerVal = exports2.classifyAuthType = exports2.isTextExcerptable = exports2.isStaticAssetByExt = exports2.isApiCall = exports2.buildApiCallSummary = exports2.computeMobileHealOutcome = exports2.detectMobileStepSecretLeak = exports2.redactMobileSecretsFromText = exports2.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports2.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports2.executeMobileLiveHeal = exports2.executeMobileTestWithHealing = exports2.executeMobileScriptDrivenHeal = exports2.prepareMobileScenarioAppState = exports2.runMobileGeneratePhase = exports2.runMobileAIPlanner = exports2.buildMobileSiteMap = exports2.formatMobileEvidenceForPlanner = exports2.MOBILE_SCENARIO_SYSTEM_PROMPT = exports2.MOBILE_SURVEY_SYSTEM_PROMPT = exports2.runMobileExplorePhase = void 0;
|
|
349954
350279
|
exports2.MAX_MANIFEST_FILES = exports2.toDiffSummary = exports2.isLikelyTestFile = exports2.sanitizeRepoRelPath = exports2.sha256Hex = exports2.hashFiles = exports2.enumerateTestFiles = exports2.buildNextManifest = exports2.detectRepoSwitch = exports2.classifyImportDiff = exports2.parseImportManifest = exports2.validateAuthoredMobileTest = exports2.buildMobileRepoAuthorSystemRules = exports2.buildMobileRepoAuthorKickoff = exports2.buildMobileRepoAuthorContractMd = exports2.REPO_ANALYSIS_SCHEMA_VERSION = exports2.validateSourceEndpoints = exports2.validateAuthoredTest = exports2.validateRepoMapFile = exports2.validateRepoMemoryFile = exports2.buildTestingStandardsSection = exports2.buildRepoAuthorSystemRules = exports2.buildRepoAuthorKickoff = exports2.buildRepoAuthorContractMd = exports2.buildRepoMapSystemRules = exports2.buildRepoMapKickoff = exports2.buildRepoMapContractMd = exports2.buildRepoMemorySystemRules = exports2.buildRepoMemoryKickoff = exports2.buildRepoMemoryContractMd = exports2.runRepoAnalysisOnRunner = exports2.MOBILE_IMPORT_SCHEMA_VERSION = exports2.validateImportedMobileTest = exports2.buildMobileImportSystemRules = exports2.buildMobileImportKickoff = exports2.buildMobileImportContractMd = exports2.IMPORT_SCHEMA_VERSION = exports2.validateImportResultFile = exports2.validateImportedTest = exports2.buildImportSystemRules = exports2.buildImportKickoff = exports2.buildImportContractMd = exports2.MAX_ARCHIVE_BYTES = exports2.isUnsafeEntryName = exports2.extractZipSafely = exports2.downloadArchiveCapped = exports2.detectArchiveKind = exports2.acquireRepoForImport = exports2.runFrameworkImportOnRunner = exports2.getAgenticEngine = void 0;
|
|
349955
|
-
exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = exports2.buildUnsupportedRendererMessage = exports2.modelSupportsVision = exports2.decideCanvasStrategy = exports2.decideAppRenderType = exports2.detectCanvasApp = exports2.detectRateLimitBlock = exports2.isSafeCanonicalRedirect = exports2.resolveBaseUrlPreflight = exports2.checkBaseUrlRedirect = exports2.stripJsonFences = exports2.scrubCredentials = exports2.withAIRetry = exports2.snapshotMatchingChildren = exports2.findNewChromiumChildPid = exports2.killProcessTree = exports2.gracefulCloseWithTreeKill = exports2.liveBrowserRegistry = exports2.REPO_SWITCH_REMOVED_FRACTION = void 0;
|
|
350280
|
+
exports2.BrokeredInboxReader = exports2.applyPlusTag = exports2.deriveProjectInboxTag = exports2.deriveInboxTag = exports2.loadVerificationInboxConfig = exports2.VerificationInboxReader = exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = exports2.buildUnsupportedRendererMessage = exports2.modelSupportsVision = exports2.decideCanvasStrategy = exports2.decideAppRenderType = exports2.detectCanvasApp = exports2.detectRateLimitBlock = exports2.isSafeCanonicalRedirect = exports2.resolveBaseUrlPreflight = exports2.checkBaseUrlRedirect = exports2.stripJsonFences = exports2.scrubCredentials = exports2.withAIRetry = exports2.snapshotMatchingChildren = exports2.findNewChromiumChildPid = exports2.killProcessTree = exports2.gracefulCloseWithTreeKill = exports2.liveBrowserRegistry = exports2.REPO_SWITCH_REMOVED_FRACTION = void 0;
|
|
349956
350281
|
__exportStar(require_ai_provider(), exports2);
|
|
349957
350282
|
__exportStar(require_mobile_types(), exports2);
|
|
349958
350283
|
__exportStar(require_mobile_source_parser(), exports2);
|
|
@@ -350374,6 +350699,26 @@ var require_dist2 = __commonJS({
|
|
|
350374
350699
|
Object.defineProperty(exports2, "UNSUPPORTED_RENDERER_PREFIX", { enumerable: true, get: function() {
|
|
350375
350700
|
return canvas_detector_js_1.UNSUPPORTED_RENDERER_PREFIX;
|
|
350376
350701
|
} });
|
|
350702
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
350703
|
+
Object.defineProperty(exports2, "VerificationInboxReader", { enumerable: true, get: function() {
|
|
350704
|
+
return verification_inbox_js_1.VerificationInboxReader;
|
|
350705
|
+
} });
|
|
350706
|
+
Object.defineProperty(exports2, "loadVerificationInboxConfig", { enumerable: true, get: function() {
|
|
350707
|
+
return verification_inbox_js_1.loadVerificationInboxConfig;
|
|
350708
|
+
} });
|
|
350709
|
+
Object.defineProperty(exports2, "deriveInboxTag", { enumerable: true, get: function() {
|
|
350710
|
+
return verification_inbox_js_1.deriveInboxTag;
|
|
350711
|
+
} });
|
|
350712
|
+
Object.defineProperty(exports2, "deriveProjectInboxTag", { enumerable: true, get: function() {
|
|
350713
|
+
return verification_inbox_js_1.deriveProjectInboxTag;
|
|
350714
|
+
} });
|
|
350715
|
+
Object.defineProperty(exports2, "applyPlusTag", { enumerable: true, get: function() {
|
|
350716
|
+
return verification_inbox_js_1.applyPlusTag;
|
|
350717
|
+
} });
|
|
350718
|
+
var brokered_inbox_reader_js_1 = require_brokered_inbox_reader();
|
|
350719
|
+
Object.defineProperty(exports2, "BrokeredInboxReader", { enumerable: true, get: function() {
|
|
350720
|
+
return brokered_inbox_reader_js_1.BrokeredInboxReader;
|
|
350721
|
+
} });
|
|
350377
350722
|
}
|
|
350378
350723
|
});
|
|
350379
350724
|
|
|
@@ -350382,7 +350727,7 @@ var require_package4 = __commonJS({
|
|
|
350382
350727
|
"package.json"(exports2, module2) {
|
|
350383
350728
|
module2.exports = {
|
|
350384
350729
|
name: "@validate.qa/runner",
|
|
350385
|
-
version: "1.0.
|
|
350730
|
+
version: "1.0.61",
|
|
350386
350731
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
350387
350732
|
bin: {
|
|
350388
350733
|
"validate-runner": "dist/cli.js"
|
|
@@ -355462,7 +355807,13 @@ async function handleHealth(_req, res) {
|
|
|
355462
355807
|
},
|
|
355463
355808
|
devices,
|
|
355464
355809
|
capabilities,
|
|
355465
|
-
activeSession: state.appiumSessionId
|
|
355810
|
+
activeSession: state.appiumSessionId,
|
|
355811
|
+
// THIS runner's Appium is executing mobile (APPIUM) test run(s) right now.
|
|
355812
|
+
// The web pre-flight reads this to warn before /bridge/session/start would
|
|
355813
|
+
// 409 — it must never be inferred from server-side global run counts, which
|
|
355814
|
+
// include unrelated projects and runners.
|
|
355815
|
+
executorBusy: executorBusyCount > 0,
|
|
355816
|
+
recordingActive: !!state.appiumSessionId && mirrorSessionOwner === "recording"
|
|
355466
355817
|
});
|
|
355467
355818
|
}
|
|
355468
355819
|
function realDeviceCapabilities2(platform3) {
|
|
@@ -355537,7 +355888,11 @@ function validateSessionStartBody(body) {
|
|
|
355537
355888
|
}
|
|
355538
355889
|
async function handleDevices(_req, res) {
|
|
355539
355890
|
const devices = discoverAllDevices({ includeApps: true });
|
|
355540
|
-
sendJSON(res, 200, {
|
|
355891
|
+
sendJSON(res, 200, {
|
|
355892
|
+
devices,
|
|
355893
|
+
executorBusy: executorBusyCount > 0,
|
|
355894
|
+
recordingActive: !!state.appiumSessionId && mirrorSessionOwner === "recording"
|
|
355895
|
+
});
|
|
355541
355896
|
}
|
|
355542
355897
|
async function handleSessionStart(req, res) {
|
|
355543
355898
|
const body = await parseBody(req);
|
|
@@ -358915,6 +359270,20 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
358915
359270
|
{ ...input, actor: "ai-explorer" },
|
|
358916
359271
|
run2.claimEpoch
|
|
358917
359272
|
);
|
|
359273
|
+
if (ctx.inboxBroker?.address) {
|
|
359274
|
+
ctx.inboxBrokerRead = async (op, payload) => {
|
|
359275
|
+
const res = await fetch(`${opts.serverUrl}/api/runner/inbox/read`, {
|
|
359276
|
+
method: "POST",
|
|
359277
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
359278
|
+
body: JSON.stringify({ runId: run2.runId, op, payload })
|
|
359279
|
+
});
|
|
359280
|
+
if (!res.ok) {
|
|
359281
|
+
const detail = await res.text().catch(() => "");
|
|
359282
|
+
throw new Error(`inbox broker ${op} failed: HTTP ${res.status}${detail ? ` \u2014 ${detail.slice(0, 200)}` : ""}`);
|
|
359283
|
+
}
|
|
359284
|
+
return (await res.json()).value ?? null;
|
|
359285
|
+
};
|
|
359286
|
+
}
|
|
358918
359287
|
log.info(`Discovery (${ctx.mode}) on ${ctx.baseUrl}`);
|
|
358919
359288
|
const { push: pushAudit, drain: drainAudit } = createAuditFlusher(
|
|
358920
359289
|
`${opts.serverUrl}/api/runner/runs/${run2.runId}/ai-audit-batch`,
|
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,
|
|
@@ -73347,6 +73377,58 @@ var require_verification_inbox = __commonJS({
|
|
|
73347
73377
|
}
|
|
73348
73378
|
});
|
|
73349
73379
|
|
|
73380
|
+
// ../runner-core/dist/services/brokered-inbox-reader.js
|
|
73381
|
+
var require_brokered_inbox_reader = __commonJS({
|
|
73382
|
+
"../runner-core/dist/services/brokered-inbox-reader.js"(exports) {
|
|
73383
|
+
"use strict";
|
|
73384
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
73385
|
+
exports.BrokeredInboxReader = void 0;
|
|
73386
|
+
var BROKERED_WAIT_CHUNK_MS = 55e3;
|
|
73387
|
+
var BrokeredInboxReader = class {
|
|
73388
|
+
transport;
|
|
73389
|
+
constructor(transport) {
|
|
73390
|
+
this.transport = transport;
|
|
73391
|
+
}
|
|
73392
|
+
async getUidWatermark() {
|
|
73393
|
+
const value = await this.transport("getUidWatermark", {});
|
|
73394
|
+
const record = value ?? {};
|
|
73395
|
+
return {
|
|
73396
|
+
uid: typeof record.uid === "number" ? record.uid : 0,
|
|
73397
|
+
uidValidity: typeof record.uidValidity === "number" ? record.uidValidity : 0
|
|
73398
|
+
};
|
|
73399
|
+
}
|
|
73400
|
+
async search(opts) {
|
|
73401
|
+
const value = await this.transport("search", { opts });
|
|
73402
|
+
return Array.isArray(value) ? value : [];
|
|
73403
|
+
}
|
|
73404
|
+
async getLatest(opts) {
|
|
73405
|
+
const value = await this.transport("getLatest", { opts });
|
|
73406
|
+
return value ?? null;
|
|
73407
|
+
}
|
|
73408
|
+
async getById(uid, address, altAddress, freshness) {
|
|
73409
|
+
const value = await this.transport("getById", { uid, address, altAddress, freshness });
|
|
73410
|
+
return value ?? null;
|
|
73411
|
+
}
|
|
73412
|
+
async waitForMessage(opts) {
|
|
73413
|
+
const totalBudgetMs = Math.max(opts.maxWaitMs ?? 9e4, 1e3);
|
|
73414
|
+
const deadline = Date.now() + totalBudgetMs;
|
|
73415
|
+
for (; ; ) {
|
|
73416
|
+
const remaining = deadline - Date.now();
|
|
73417
|
+
if (remaining <= 0)
|
|
73418
|
+
return null;
|
|
73419
|
+
const chunkMs = Math.min(remaining, BROKERED_WAIT_CHUNK_MS);
|
|
73420
|
+
const value = await this.transport("waitForMessage", {
|
|
73421
|
+
opts: { ...opts, maxWaitMs: chunkMs }
|
|
73422
|
+
});
|
|
73423
|
+
if (value)
|
|
73424
|
+
return value;
|
|
73425
|
+
}
|
|
73426
|
+
}
|
|
73427
|
+
};
|
|
73428
|
+
exports.BrokeredInboxReader = BrokeredInboxReader;
|
|
73429
|
+
}
|
|
73430
|
+
});
|
|
73431
|
+
|
|
73350
73432
|
// ../runner-core/dist/services/inbox-tools.js
|
|
73351
73433
|
var require_inbox_tools = __commonJS({
|
|
73352
73434
|
"../runner-core/dist/services/inbox-tools.js"(exports) {
|
|
@@ -74056,6 +74138,7 @@ var require_mobile_explorer = __commonJS({
|
|
|
74056
74138
|
var mobile_auth_coverage_js_1 = require_mobile_auth_coverage();
|
|
74057
74139
|
var mobile_driver_errors_js_1 = require_mobile_driver_errors();
|
|
74058
74140
|
var verification_inbox_js_1 = require_verification_inbox();
|
|
74141
|
+
var brokered_inbox_reader_js_1 = require_brokered_inbox_reader();
|
|
74059
74142
|
var inbox_tools_js_1 = require_inbox_tools();
|
|
74060
74143
|
var mobile_discovery_auth_js_1 = require_mobile_discovery_auth();
|
|
74061
74144
|
var MAX_MOBILE_ITERATIONS_SURVEY = 40;
|
|
@@ -75377,6 +75460,23 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
75377
75460
|
log: (line) => log2(line)
|
|
75378
75461
|
});
|
|
75379
75462
|
log2("Verification inbox ENABLED for registration \u2014 monitored identity provisioned.");
|
|
75463
|
+
} else if (registerRequested && ctx.inboxBroker?.address && ctx.inboxBrokerRead) {
|
|
75464
|
+
inboxState = (0, inbox_tools_js_1.createInboxRunState)({ address: ctx.inboxBroker.address }, {
|
|
75465
|
+
tagSeed: targetAppId ?? ctx.projectId,
|
|
75466
|
+
projectScope: ctx.projectId
|
|
75467
|
+
});
|
|
75468
|
+
inboxReader = new brokered_inbox_reader_js_1.BrokeredInboxReader(ctx.inboxBrokerRead);
|
|
75469
|
+
await (0, inbox_tools_js_1.applyAndPersistInboxIdentity)({
|
|
75470
|
+
variableContext: ctx.variableContext,
|
|
75471
|
+
registration: {
|
|
75472
|
+
email: inboxState.email,
|
|
75473
|
+
legacyEmail: inboxState.legacyEmail,
|
|
75474
|
+
passwordKey: inboxState.passwordKey ?? "TEST_USER_PASSWORD"
|
|
75475
|
+
},
|
|
75476
|
+
persist: ctx.onCredentialUpdate,
|
|
75477
|
+
log: (line) => log2(line)
|
|
75478
|
+
});
|
|
75479
|
+
log2("Verification inbox ENABLED via server broker \u2014 monitored identity provisioned (IMAP credentials stay server-side).");
|
|
75380
75480
|
} else if (registerRequested) {
|
|
75381
75481
|
log2("Registration requested without a verification inbox \u2014 preserving the seeded credential identity and reporting any verification wall as unauthenticated.");
|
|
75382
75482
|
}
|
|
@@ -78198,6 +78298,12 @@ ${creds.join(", ")}`);
|
|
|
78198
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.
|
|
78199
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.
|
|
78200
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.`);
|
|
78201
78307
|
}
|
|
78202
78308
|
sections.push(`## Constraints
|
|
78203
78309
|
- Test budget: ${context.testBudget} scenarios maximum`);
|
|
@@ -84291,12 +84397,14 @@ ${args.directive}`);
|
|
|
84291
84397
|
{ role: "user", content: userContent }
|
|
84292
84398
|
];
|
|
84293
84399
|
let content;
|
|
84400
|
+
let finishReason;
|
|
84294
84401
|
try {
|
|
84295
84402
|
const response = await (0, ai_retry_js_1.resilientAICall)((signal, arm) => (0, ai_queue_js_1.queuedAICall)(() => {
|
|
84296
84403
|
arm();
|
|
84297
84404
|
return aiClient.chat.completions.create({ model, messages, max_tokens: 8e3, temperature: 0.1 }, { signal });
|
|
84298
84405
|
}), { label: `mobile-heal-attempt-${attempt}`, deferTimeoutUntilArmed: true });
|
|
84299
84406
|
content = response?.choices?.[0]?.message?.content ?? "";
|
|
84407
|
+
finishReason = response?.choices?.[0]?.finish_reason ?? void 0;
|
|
84300
84408
|
} catch {
|
|
84301
84409
|
options.onProgress?.(`Phase 1 attempt ${attempt}: model call failed in transport \u2014 granting a recovery turn`);
|
|
84302
84410
|
markRecoverableTurn();
|
|
@@ -84306,7 +84414,7 @@ ${args.directive}`);
|
|
|
84306
84414
|
aiResponseCount += 1;
|
|
84307
84415
|
const parsed = parseHealVerdict(content);
|
|
84308
84416
|
if (!parsed) {
|
|
84309
|
-
options.onProgress?.(`Phase 1 attempt ${attempt}: response was not a valid verdict \u2014 granting a recovery turn`);
|
|
84417
|
+
options.onProgress?.(`Phase 1 attempt ${attempt}: response was not a valid verdict (${content.length} chars, finish=${finishReason ?? "unknown"}) \u2014 granting a recovery turn`);
|
|
84310
84418
|
markRecoverableTurn();
|
|
84311
84419
|
pendingDirective = "Your last response was not valid JSON matching the verdict schema. Return exactly one JSON verdict object.";
|
|
84312
84420
|
continue;
|
|
@@ -94807,6 +94915,7 @@ var require_discover_generator = __commonJS({
|
|
|
94807
94915
|
"use strict";
|
|
94808
94916
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
94809
94917
|
exports.MIN_BUG_DESCRIPTION_LENGTH = exports.VALID_PLAYWRIGHT_GETBY_METHODS = exports.BLOCKED_MCP_TOOLS_SCENARIO_GEN = void 0;
|
|
94918
|
+
exports.scenarioLooksLikeRegistration = scenarioLooksLikeRegistration;
|
|
94810
94919
|
exports.filterMcpToolsForScenarioGen = filterMcpToolsForScenarioGen;
|
|
94811
94920
|
exports.detectScenarioSignals = detectScenarioSignals;
|
|
94812
94921
|
exports.scenarioStepsCoverAuth = scenarioStepsCoverAuth;
|
|
@@ -94819,6 +94928,7 @@ var require_discover_generator = __commonJS({
|
|
|
94819
94928
|
exports.stripImportDeclarations = stripImportDeclarations;
|
|
94820
94929
|
exports.stripStringsAndComments = stripStringsAndComments;
|
|
94821
94930
|
exports.gotoBasePathFromBaseUrl = gotoBasePathFromBaseUrl;
|
|
94931
|
+
exports.repairInboxHelperScalarDestructure = repairInboxHelperScalarDestructure;
|
|
94822
94932
|
exports.sanitizeCode = sanitizeCode;
|
|
94823
94933
|
exports.validateTests = validateTests;
|
|
94824
94934
|
exports.formatEvidenceForLiveGenerate = formatEvidenceForLiveGenerate;
|
|
@@ -94865,7 +94975,8 @@ var require_discover_generator = __commonJS({
|
|
|
94865
94975
|
var test_inbox_broker_js_1 = require_test_inbox_broker();
|
|
94866
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;
|
|
94867
94977
|
function scenarioLooksLikeRegistration(scenario) {
|
|
94868
|
-
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}`;
|
|
94869
94980
|
return REGISTER_SCENARIO_RE.test(hay);
|
|
94870
94981
|
}
|
|
94871
94982
|
function buildEmailVerifyGenBlock(genEmail, genPassword) {
|
|
@@ -94888,6 +94999,7 @@ EMIT (the code you put in finish_generate) \u2014 do NOT hardcode the throwaway
|
|
|
94888
94999
|
- code flow: \`const code = await __vqaWaitForEmail('code');\` then type \`code\` into the verification field.
|
|
94889
95000
|
- link flow: \`const link = await __vqaWaitForEmail('link');\` then \`await page.goto(link);\`.
|
|
94890
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.
|
|
94891
95003
|
- Tag the test \`@email-verify\`. Emit EXACTLY ONE test() in this file (one registration per run).
|
|
94892
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.`;
|
|
94893
95005
|
}
|
|
@@ -95987,6 +96099,200 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
95987
96099
|
return void 0;
|
|
95988
96100
|
}
|
|
95989
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
|
+
}
|
|
95990
96296
|
function sanitizeCode(code, options) {
|
|
95991
96297
|
let result = code;
|
|
95992
96298
|
const isNetworkIdleWaitLine = (line) => /^\s*await page\.waitForLoadState\((['"])networkidle\1\);\s*$/.test(line);
|
|
@@ -96077,6 +96383,7 @@ ${testOpen}`;
|
|
|
96077
96383
|
if (options?.authStamp) {
|
|
96078
96384
|
result = (0, auth_recipe_js_1.upsertAuthBlock)(result, options.authStamp);
|
|
96079
96385
|
}
|
|
96386
|
+
result = repairInboxHelperScalarDestructure(result);
|
|
96080
96387
|
try {
|
|
96081
96388
|
result = (0, inbox_stamp_js_1.upsertInboxBlock)(result);
|
|
96082
96389
|
} catch {
|
|
@@ -334538,6 +334845,8 @@ var require_discover_agent = __commonJS({
|
|
|
334538
334845
|
var discover_planner_js_1 = require_discover_planner();
|
|
334539
334846
|
var discover_generator_js_1 = require_discover_generator();
|
|
334540
334847
|
var discover_ai_planner_js_1 = require_discover_ai_planner();
|
|
334848
|
+
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
334849
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
334541
334850
|
var sitemap_builder_js_1 = require_sitemap_builder();
|
|
334542
334851
|
Object.defineProperty(exports, "buildInMemorySiteMap", { enumerable: true, get: function() {
|
|
334543
334852
|
return sitemap_builder_js_1.buildInMemorySiteMap;
|
|
@@ -334550,6 +334859,13 @@ var require_discover_agent = __commonJS({
|
|
|
334550
334859
|
var agentic_cli_constants_js_1 = require_agentic_cli_constants();
|
|
334551
334860
|
var canvas_detector_js_1 = require_canvas_detector();
|
|
334552
334861
|
var auth_recipe_js_1 = require_auth_recipe();
|
|
334862
|
+
function plannerEmailVerifyArmed(ctx) {
|
|
334863
|
+
if (!(0, discovery_lint_flags_js_1.isTestgenEmailVerifyEnabled)())
|
|
334864
|
+
return false;
|
|
334865
|
+
if (ctx.variableContext?.allTestVariables?.DISCOVERY_SHOULD_REGISTER !== "true")
|
|
334866
|
+
return false;
|
|
334867
|
+
return (0, verification_inbox_js_1.isVerificationInboxConfigured)();
|
|
334868
|
+
}
|
|
334553
334869
|
async function runCanvasPreflight(ctx, sharedBrowser, log2) {
|
|
334554
334870
|
const cached = ctx.appRenderType ?? null;
|
|
334555
334871
|
const engine = (0, agentic_cli_constants_js_1.getAgenticEngine)(ctx.exploreModel);
|
|
@@ -335198,6 +335514,9 @@ Explore complete: ${exploreResult.pagesDiscovered} pages, ${exploreResult.elemen
|
|
|
335198
335514
|
// instead of authoring landing→login steps (which generate would override).
|
|
335199
335515
|
authRecipeAvailable: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && !!ctx.authRecipe,
|
|
335200
335516
|
authRedirectRoute: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) ? ctx.authRecipe?.redirectRoute ?? void 0 : void 0,
|
|
335517
|
+
// Email-verify capability — only for scenario-producing modes; a survey emits
|
|
335518
|
+
// featureGroups (no scenarios), so keep its prompt free of the round-trip block.
|
|
335519
|
+
emailVerifyArmed: mode !== "survey" && plannerEmailVerifyArmed(ctx),
|
|
335201
335520
|
testBudget
|
|
335202
335521
|
}, {
|
|
335203
335522
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
@@ -335782,6 +336101,12 @@ Plan (reused): ${resumeSavedScenarios.length} scenarios from the paused run.`);
|
|
|
335782
336101
|
// authenticated state when a verified login recipe is on file for this project.
|
|
335783
336102
|
authRecipeAvailable: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && !!ctx.authRecipe,
|
|
335784
336103
|
authRedirectRoute: (0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) ? ctx.authRecipe?.redirectRoute ?? void 0 : void 0,
|
|
336104
|
+
// Email-verify capability: when armed, let the planner plan the full
|
|
336105
|
+
// register→verify and forgot-password→reset round trips to completion
|
|
336106
|
+
// (see the planner's "## Verification Inbox Available" block). Armed on the
|
|
336107
|
+
// SAME project-level predicate the generator uses (flag + register intent +
|
|
336108
|
+
// configured inbox), so the planner never promises what generation can't do.
|
|
336109
|
+
emailVerifyArmed: plannerEmailVerifyArmed(ctx),
|
|
335785
336110
|
testBudget: featureTestBudget
|
|
335786
336111
|
}, {
|
|
335787
336112
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
@@ -349957,7 +350282,7 @@ var require_dist2 = __commonJS({
|
|
|
349957
350282
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
349958
350283
|
exports.resolveRefFromSnapshot = exports.replayAuthRecipeIntoBrowser = exports.runRegisterCaptureRun = exports.runLoginCaptureRun = exports.filterLoginCaptureCredentials = exports.buildLoginCaptureCredentials = exports.isReservedRunnerEnvVar = exports.RESERVED_RUNNER_ENV_VARS = exports.AUTH_MARKER_VERSION = exports.buildHealAuthStamp = exports.extractAuthRegion = exports.upsertAuthBlock = exports.buildAuthRecipe = exports.commandToReplayStep = exports.buildReplayPlan = exports.absoluteRoute = exports.renderReplaySettleArrow = exports.renderReplayClickArrow = exports.renderAuthFn = exports.filterFeatureGroupsToPathScope = exports.isAuthFlowRoute = exports.deriveSurveyPathScope = exports.canonicalizeRoute = exports.codeUsesInboxHelper = exports.codeUsesTestInboxIdentity = exports.codeSelfProvisionsRegistration = exports.executeGrokPerBatchHeal = exports.executeGrokPerTestHeal = exports.headerVal = exports.classifyAuthType = exports.isTextExcerptable = exports.isStaticAssetByExt = exports.isApiCall = exports.buildApiCallSummary = exports.computeMobileHealOutcome = exports.detectMobileStepSecretLeak = exports.redactMobileSecretsFromText = exports.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports.executeMobileLiveHeal = exports.executeMobileTestWithHealing = exports.executeMobileScriptDrivenHeal = exports.prepareMobileScenarioAppState = exports.runMobileGeneratePhase = exports.runMobileAIPlanner = exports.buildMobileSiteMap = exports.formatMobileEvidenceForPlanner = exports.MOBILE_SCENARIO_SYSTEM_PROMPT = exports.MOBILE_SURVEY_SYSTEM_PROMPT = exports.runMobileExplorePhase = void 0;
|
|
349959
350284
|
exports.MAX_MANIFEST_FILES = exports.toDiffSummary = exports.isLikelyTestFile = exports.sanitizeRepoRelPath = exports.sha256Hex = exports.hashFiles = exports.enumerateTestFiles = exports.buildNextManifest = exports.detectRepoSwitch = exports.classifyImportDiff = exports.parseImportManifest = exports.validateAuthoredMobileTest = exports.buildMobileRepoAuthorSystemRules = exports.buildMobileRepoAuthorKickoff = exports.buildMobileRepoAuthorContractMd = exports.REPO_ANALYSIS_SCHEMA_VERSION = exports.validateSourceEndpoints = exports.validateAuthoredTest = exports.validateRepoMapFile = exports.validateRepoMemoryFile = exports.buildTestingStandardsSection = exports.buildRepoAuthorSystemRules = exports.buildRepoAuthorKickoff = exports.buildRepoAuthorContractMd = exports.buildRepoMapSystemRules = exports.buildRepoMapKickoff = exports.buildRepoMapContractMd = exports.buildRepoMemorySystemRules = exports.buildRepoMemoryKickoff = exports.buildRepoMemoryContractMd = exports.runRepoAnalysisOnRunner = exports.MOBILE_IMPORT_SCHEMA_VERSION = exports.validateImportedMobileTest = exports.buildMobileImportSystemRules = exports.buildMobileImportKickoff = exports.buildMobileImportContractMd = exports.IMPORT_SCHEMA_VERSION = exports.validateImportResultFile = exports.validateImportedTest = exports.buildImportSystemRules = exports.buildImportKickoff = exports.buildImportContractMd = exports.MAX_ARCHIVE_BYTES = exports.isUnsafeEntryName = exports.extractZipSafely = exports.downloadArchiveCapped = exports.detectArchiveKind = exports.acquireRepoForImport = exports.runFrameworkImportOnRunner = exports.getAgenticEngine = void 0;
|
|
349960
|
-
exports.UNSUPPORTED_RENDERER_PREFIX = exports.UnsupportedRendererError = exports.buildCanvasModeAuthoringAddendum = exports.buildUnsupportedRendererMessage = exports.modelSupportsVision = exports.decideCanvasStrategy = exports.decideAppRenderType = exports.detectCanvasApp = exports.detectRateLimitBlock = exports.isSafeCanonicalRedirect = exports.resolveBaseUrlPreflight = exports.checkBaseUrlRedirect = exports.stripJsonFences = exports.scrubCredentials = exports.withAIRetry = exports.snapshotMatchingChildren = exports.findNewChromiumChildPid = exports.killProcessTree = exports.gracefulCloseWithTreeKill = exports.liveBrowserRegistry = exports.REPO_SWITCH_REMOVED_FRACTION = void 0;
|
|
350285
|
+
exports.BrokeredInboxReader = exports.applyPlusTag = exports.deriveProjectInboxTag = exports.deriveInboxTag = exports.loadVerificationInboxConfig = exports.VerificationInboxReader = exports.UNSUPPORTED_RENDERER_PREFIX = exports.UnsupportedRendererError = exports.buildCanvasModeAuthoringAddendum = exports.buildUnsupportedRendererMessage = exports.modelSupportsVision = exports.decideCanvasStrategy = exports.decideAppRenderType = exports.detectCanvasApp = exports.detectRateLimitBlock = exports.isSafeCanonicalRedirect = exports.resolveBaseUrlPreflight = exports.checkBaseUrlRedirect = exports.stripJsonFences = exports.scrubCredentials = exports.withAIRetry = exports.snapshotMatchingChildren = exports.findNewChromiumChildPid = exports.killProcessTree = exports.gracefulCloseWithTreeKill = exports.liveBrowserRegistry = exports.REPO_SWITCH_REMOVED_FRACTION = void 0;
|
|
349961
350286
|
__exportStar(require_ai_provider(), exports);
|
|
349962
350287
|
__exportStar(require_mobile_types(), exports);
|
|
349963
350288
|
__exportStar(require_mobile_source_parser(), exports);
|
|
@@ -350379,6 +350704,26 @@ var require_dist2 = __commonJS({
|
|
|
350379
350704
|
Object.defineProperty(exports, "UNSUPPORTED_RENDERER_PREFIX", { enumerable: true, get: function() {
|
|
350380
350705
|
return canvas_detector_js_1.UNSUPPORTED_RENDERER_PREFIX;
|
|
350381
350706
|
} });
|
|
350707
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
350708
|
+
Object.defineProperty(exports, "VerificationInboxReader", { enumerable: true, get: function() {
|
|
350709
|
+
return verification_inbox_js_1.VerificationInboxReader;
|
|
350710
|
+
} });
|
|
350711
|
+
Object.defineProperty(exports, "loadVerificationInboxConfig", { enumerable: true, get: function() {
|
|
350712
|
+
return verification_inbox_js_1.loadVerificationInboxConfig;
|
|
350713
|
+
} });
|
|
350714
|
+
Object.defineProperty(exports, "deriveInboxTag", { enumerable: true, get: function() {
|
|
350715
|
+
return verification_inbox_js_1.deriveInboxTag;
|
|
350716
|
+
} });
|
|
350717
|
+
Object.defineProperty(exports, "deriveProjectInboxTag", { enumerable: true, get: function() {
|
|
350718
|
+
return verification_inbox_js_1.deriveProjectInboxTag;
|
|
350719
|
+
} });
|
|
350720
|
+
Object.defineProperty(exports, "applyPlusTag", { enumerable: true, get: function() {
|
|
350721
|
+
return verification_inbox_js_1.applyPlusTag;
|
|
350722
|
+
} });
|
|
350723
|
+
var brokered_inbox_reader_js_1 = require_brokered_inbox_reader();
|
|
350724
|
+
Object.defineProperty(exports, "BrokeredInboxReader", { enumerable: true, get: function() {
|
|
350725
|
+
return brokered_inbox_reader_js_1.BrokeredInboxReader;
|
|
350726
|
+
} });
|
|
350382
350727
|
}
|
|
350383
350728
|
});
|
|
350384
350729
|
|
|
@@ -350387,7 +350732,7 @@ var require_package4 = __commonJS({
|
|
|
350387
350732
|
"package.json"(exports, module) {
|
|
350388
350733
|
module.exports = {
|
|
350389
350734
|
name: "@validate.qa/runner",
|
|
350390
|
-
version: "1.0.
|
|
350735
|
+
version: "1.0.61",
|
|
350391
350736
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
350392
350737
|
bin: {
|
|
350393
350738
|
"validate-runner": "dist/cli.js"
|
|
@@ -355470,7 +355815,13 @@ async function handleHealth(_req, res) {
|
|
|
355470
355815
|
},
|
|
355471
355816
|
devices,
|
|
355472
355817
|
capabilities,
|
|
355473
|
-
activeSession: state.appiumSessionId
|
|
355818
|
+
activeSession: state.appiumSessionId,
|
|
355819
|
+
// THIS runner's Appium is executing mobile (APPIUM) test run(s) right now.
|
|
355820
|
+
// The web pre-flight reads this to warn before /bridge/session/start would
|
|
355821
|
+
// 409 — it must never be inferred from server-side global run counts, which
|
|
355822
|
+
// include unrelated projects and runners.
|
|
355823
|
+
executorBusy: executorBusyCount > 0,
|
|
355824
|
+
recordingActive: !!state.appiumSessionId && mirrorSessionOwner === "recording"
|
|
355474
355825
|
});
|
|
355475
355826
|
}
|
|
355476
355827
|
function realDeviceCapabilities2(platform3) {
|
|
@@ -355545,7 +355896,11 @@ function validateSessionStartBody(body) {
|
|
|
355545
355896
|
}
|
|
355546
355897
|
async function handleDevices(_req, res) {
|
|
355547
355898
|
const devices = discoverAllDevices({ includeApps: true });
|
|
355548
|
-
sendJSON(res, 200, {
|
|
355899
|
+
sendJSON(res, 200, {
|
|
355900
|
+
devices,
|
|
355901
|
+
executorBusy: executorBusyCount > 0,
|
|
355902
|
+
recordingActive: !!state.appiumSessionId && mirrorSessionOwner === "recording"
|
|
355903
|
+
});
|
|
355549
355904
|
}
|
|
355550
355905
|
async function handleSessionStart(req, res) {
|
|
355551
355906
|
const body = await parseBody(req);
|
|
@@ -358923,6 +359278,20 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
358923
359278
|
{ ...input, actor: "ai-explorer" },
|
|
358924
359279
|
run2.claimEpoch
|
|
358925
359280
|
);
|
|
359281
|
+
if (ctx.inboxBroker?.address) {
|
|
359282
|
+
ctx.inboxBrokerRead = async (op, payload) => {
|
|
359283
|
+
const res = await fetch(`${opts.serverUrl}/api/runner/inbox/read`, {
|
|
359284
|
+
method: "POST",
|
|
359285
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
359286
|
+
body: JSON.stringify({ runId: run2.runId, op, payload })
|
|
359287
|
+
});
|
|
359288
|
+
if (!res.ok) {
|
|
359289
|
+
const detail = await res.text().catch(() => "");
|
|
359290
|
+
throw new Error(`inbox broker ${op} failed: HTTP ${res.status}${detail ? ` \u2014 ${detail.slice(0, 200)}` : ""}`);
|
|
359291
|
+
}
|
|
359292
|
+
return (await res.json()).value ?? null;
|
|
359293
|
+
};
|
|
359294
|
+
}
|
|
358926
359295
|
log.info(`Discovery (${ctx.mode}) on ${ctx.baseUrl}`);
|
|
358927
359296
|
const { push: pushAudit, drain: drainAudit } = createAuditFlusher(
|
|
358928
359297
|
`${opts.serverUrl}/api/runner/runs/${run2.runId}/ai-audit-batch`,
|
package/package.json
CHANGED