@validate.qa/runner 1.0.32 → 1.0.33
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 +441 -155
- package/dist/cli.mjs +441 -155
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -62290,7 +62290,7 @@ var require_mobile_explorer = __commonJS({
|
|
|
62290
62290
|
return { screen, signal, refMap, observation };
|
|
62291
62291
|
}
|
|
62292
62292
|
function buildStatePacket(input) {
|
|
62293
|
-
const { state: state2, mode, iteration, maxIterations, latestObservation, journeys, transitions } = input;
|
|
62293
|
+
const { state: state2, mode, iteration, maxIterations, latestObservation, journeys, transitions, featureName, featureContext, userNotes } = input;
|
|
62294
62294
|
const sections = [];
|
|
62295
62295
|
sections.push(`## Mobile Exploration Status (iteration ${iteration}/${maxIterations})`);
|
|
62296
62296
|
sections.push(`Mode: ${mode} | Budget remaining: ${maxIterations - iteration} iterations`);
|
|
@@ -62303,10 +62303,46 @@ var require_mobile_explorer = __commonJS({
|
|
|
62303
62303
|
sections.push("");
|
|
62304
62304
|
if (mode === "survey") {
|
|
62305
62305
|
sections.push("**Mission:** Map every reachable screen. For each new screen: mobile_snapshot -> capture_screen -> record_transition. Get PAST the entry gate into the app's real main content \u2014 the login/landing screens are the ENTRANCE, not the app. If test credentials are provided (see the Test Credentials section), SIGN IN with them FIRST and map the authenticated app; that is the real product surface. Prefer a real login when credentials are provided; only if login genuinely cannot be completed after honest attempts (persistent error, the form will not accept the credential, no valid account) fall back to any guest/demo/skip entry the app offers and map the app fully from there \u2014 reaching the real feature surface that way is far better coverage than getting stuck on the login wall. Do NOT submit forms with junk data or mutate real data. Call finish_exploration ONLY when every reachable screen \u2014 including the authenticated content \u2014 has been captured and no unvisited frontier screens remain.");
|
|
62306
|
+
} else if (featureName?.trim()) {
|
|
62307
|
+
sections.push(`**Mission:** Feature-scoped deep exploration of **"${featureName.trim()}"**. Drive the app like a real user through THIS feature only: tap, type, submit, and observe outcomes. After a meaningful flow, call record_journey. Do NOT deep-dive unrelated features. Call finish_exploration when this feature's flows are covered.`);
|
|
62306
62308
|
} else {
|
|
62307
62309
|
sections.push("**Mission:** Use the focus features like a real user. Tap, type, submit, and observe outcomes. After a meaningful flow, call record_journey. Call finish_exploration when the flows are covered.");
|
|
62308
62310
|
}
|
|
62309
62311
|
sections.push("");
|
|
62312
|
+
if (mode === "deep" && featureName?.trim()) {
|
|
62313
|
+
sections.push(`## Feature: ${featureName.trim()}`);
|
|
62314
|
+
if (featureContext?.plannerDescription?.trim()) {
|
|
62315
|
+
sections.push(featureContext.plannerDescription.trim());
|
|
62316
|
+
}
|
|
62317
|
+
if (featureContext?.priorInsight?.trim()) {
|
|
62318
|
+
sections.push(`Prior insight: ${featureContext.priorInsight.trim()}`);
|
|
62319
|
+
}
|
|
62320
|
+
if (featureContext?.pageIntel && featureContext.pageIntel.length > 0) {
|
|
62321
|
+
sections.push("### Target screens / routes for this feature");
|
|
62322
|
+
for (const page of featureContext.pageIntel.slice(0, 12)) {
|
|
62323
|
+
sections.push(`- ${page.route} (${page.type}): ${page.summary}`);
|
|
62324
|
+
}
|
|
62325
|
+
}
|
|
62326
|
+
if (featureContext?.flowIntel && featureContext.flowIntel.length > 0) {
|
|
62327
|
+
sections.push("### Known flows");
|
|
62328
|
+
for (const flow of featureContext.flowIntel.slice(0, 8)) {
|
|
62329
|
+
sections.push(`- ${flow}`);
|
|
62330
|
+
}
|
|
62331
|
+
}
|
|
62332
|
+
if (featureContext?.otherFeatures && featureContext.otherFeatures.length > 0) {
|
|
62333
|
+
sections.push("### Other features \u2014 do NOT explore these (navigation context only)");
|
|
62334
|
+
for (const other of featureContext.otherFeatures.slice(0, 12)) {
|
|
62335
|
+
const pages = other.pages?.length ? ` [${other.pages.slice(0, 4).join(", ")}]` : "";
|
|
62336
|
+
sections.push(`- ${other.name}${pages}`);
|
|
62337
|
+
}
|
|
62338
|
+
}
|
|
62339
|
+
sections.push("");
|
|
62340
|
+
}
|
|
62341
|
+
if (userNotes?.trim()) {
|
|
62342
|
+
sections.push(`## Operator notes
|
|
62343
|
+
${userNotes.trim()}`);
|
|
62344
|
+
sections.push("");
|
|
62345
|
+
}
|
|
62310
62346
|
const screenCount = state2.getScreenCount();
|
|
62311
62347
|
sections.push(`## Screens Mapped: ${screenCount} | Transitions: ${transitions.length} | Journeys: ${journeys.length}`);
|
|
62312
62348
|
const currentScreenId = state2.getCurrentScreenId();
|
|
@@ -62463,7 +62499,7 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
62463
62499
|
} catch {
|
|
62464
62500
|
}
|
|
62465
62501
|
};
|
|
62466
|
-
const mode = ctx.mode === "
|
|
62502
|
+
const mode = ctx.mode === "survey" ? "survey" : "deep";
|
|
62467
62503
|
const platform3 = platformForMedium(ctx.medium);
|
|
62468
62504
|
const enableRecordJourney = config?.enableRecordJourney ?? mode !== "survey";
|
|
62469
62505
|
const targetAppId = ctx.target?.appId?.trim() || void 0;
|
|
@@ -62751,7 +62787,10 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
62751
62787
|
maxIterations,
|
|
62752
62788
|
latestObservation: getLatest()?.observation ?? null,
|
|
62753
62789
|
journeys,
|
|
62754
|
-
transitions: collectedTransitions
|
|
62790
|
+
transitions: collectedTransitions,
|
|
62791
|
+
featureName: ctx.featureName,
|
|
62792
|
+
featureContext: ctx.featureContext,
|
|
62793
|
+
userNotes: ctx.userNotes
|
|
62755
62794
|
});
|
|
62756
62795
|
const messages = [
|
|
62757
62796
|
{ role: "system", content: systemPrompt },
|
|
@@ -66320,6 +66359,34 @@ ${targetBits.length > 0 ? targetBits.join(" ") : "native mobile app"}`);
|
|
|
66320
66359
|
if (context.featureName) {
|
|
66321
66360
|
sections.push(`## Feature: ${context.featureName}
|
|
66322
66361
|
SCOPE: this is a feature-focused deep run. Plan scenarios ONLY for the "${context.featureName}" feature \u2014 its screens, forms, and flows. Screens belonging to other features are navigation context / prerequisites only; do NOT plan scenarios whose primary goal exercises another feature.`);
|
|
66362
|
+
if (context.featureContext?.plannerDescription?.trim()) {
|
|
66363
|
+
sections.push(context.featureContext.plannerDescription.trim());
|
|
66364
|
+
}
|
|
66365
|
+
if (context.featureContext?.priorInsight?.trim()) {
|
|
66366
|
+
sections.push(`Prior insight: ${context.featureContext.priorInsight.trim()}`);
|
|
66367
|
+
}
|
|
66368
|
+
if (context.featureContext?.pageIntel && context.featureContext.pageIntel.length > 0) {
|
|
66369
|
+
sections.push("### Feature page intel");
|
|
66370
|
+
for (const page of context.featureContext.pageIntel.slice(0, 12)) {
|
|
66371
|
+
sections.push(`- ${page.route} (${page.type}): ${page.summary}`);
|
|
66372
|
+
}
|
|
66373
|
+
}
|
|
66374
|
+
if (context.featureContext?.flowIntel && context.featureContext.flowIntel.length > 0) {
|
|
66375
|
+
sections.push("### Known flows");
|
|
66376
|
+
for (const flow of context.featureContext.flowIntel.slice(0, 8)) {
|
|
66377
|
+
sections.push(`- ${flow}`);
|
|
66378
|
+
}
|
|
66379
|
+
}
|
|
66380
|
+
if (context.featureContext?.otherFeatures && context.featureContext.otherFeatures.length > 0) {
|
|
66381
|
+
sections.push("### Other features (do not plan primary scenarios for these)");
|
|
66382
|
+
for (const other of context.featureContext.otherFeatures.slice(0, 12)) {
|
|
66383
|
+
sections.push(`- ${other.name}`);
|
|
66384
|
+
}
|
|
66385
|
+
}
|
|
66386
|
+
}
|
|
66387
|
+
if (context.userNotes?.trim()) {
|
|
66388
|
+
sections.push(`## Operator notes
|
|
66389
|
+
${context.userNotes.trim()}`);
|
|
66323
66390
|
}
|
|
66324
66391
|
const orderedScreens = plannableTargetAppScreens(graph, context.appId).sort((a, b) => a.firstSeenIteration - b.firstSeenIteration);
|
|
66325
66392
|
const includedScreenIds = new Set(orderedScreens.map((state2) => state2.screenId));
|
|
@@ -66446,7 +66513,7 @@ ${constraints.join("\n")}`);
|
|
|
66446
66513
|
routePattern: null,
|
|
66447
66514
|
title: state2.screenName ?? null,
|
|
66448
66515
|
pageType: state2.screenType ?? "UNKNOWN",
|
|
66449
|
-
requiresAuth:
|
|
66516
|
+
requiresAuth: state2.screenType?.toLowerCase() === "auth",
|
|
66450
66517
|
visitCount: state2.visited ? 1 : 0,
|
|
66451
66518
|
lastVisitedAt: state2.visited ? now : null,
|
|
66452
66519
|
elementsCovered: 0,
|
|
@@ -66466,6 +66533,8 @@ ${constraints.join("\n")}`);
|
|
|
66466
66533
|
const toPageId = idByScreen.get(t.toScreenId);
|
|
66467
66534
|
if (!fromPageId || !toPageId)
|
|
66468
66535
|
continue;
|
|
66536
|
+
const fromScreen = graph.screens.get(t.fromScreenId);
|
|
66537
|
+
const toScreen = graph.screens.get(t.toScreenId);
|
|
66469
66538
|
transitions.push({
|
|
66470
66539
|
id: `mtx-${txSeq++}`,
|
|
66471
66540
|
fromPageId,
|
|
@@ -66473,7 +66542,7 @@ ${constraints.join("\n")}`);
|
|
|
66473
66542
|
triggerType: t.action.toUpperCase(),
|
|
66474
66543
|
triggerLabel: t.viaRef ?? null,
|
|
66475
66544
|
observedCount: 1,
|
|
66476
|
-
requiresAuth:
|
|
66545
|
+
requiresAuth: fromScreen?.screenType?.toLowerCase() === "auth" || toScreen?.screenType?.toLowerCase() === "auth"
|
|
66477
66546
|
});
|
|
66478
66547
|
}
|
|
66479
66548
|
return {
|
|
@@ -66676,7 +66745,9 @@ ${constraints.join("\n")}`);
|
|
|
66676
66745
|
recordedJourneys,
|
|
66677
66746
|
credentials: input.credentials,
|
|
66678
66747
|
testBudget: input.testBudget,
|
|
66679
|
-
incremental: input.incremental
|
|
66748
|
+
incremental: input.incremental,
|
|
66749
|
+
userNotes: input.userNotes,
|
|
66750
|
+
featureContext: input.featureContext
|
|
66680
66751
|
})
|
|
66681
66752
|
});
|
|
66682
66753
|
const plannerFeatureGroups = filterFeatureGroupsToSiteMap(result.featureGroups, siteMap, input.graph, input.appId);
|
|
@@ -66935,8 +67006,38 @@ var require_mobile_generator = __commonJS({
|
|
|
66935
67006
|
return (labelIsEditableValue ? void 0 : label) ?? nonEmpty2(element.accessibilityId) ?? nonEmpty2(element.resourceId) ?? (editable ? void 0 : text) ?? "element";
|
|
66936
67007
|
}
|
|
66937
67008
|
var EDITABLE_ELEMENT_ROLE_RE = /(EditText|AutoCompleteTextView|XCUIElementTypeTextField|XCUIElementTypeSecureTextField|XCUIElementTypeSearchField|XCUIElementTypeTextView)$/;
|
|
67009
|
+
var EDITABLE_IDENTITY_RE = /\b(email|e-?mail|password|passwd|passcode|username|user[_-]?name|login|search|query|otp|pin|phone|mobile|input|textfield|text[_-]?field|edit|typehere)\b/i;
|
|
66938
67010
|
function isEditableMobileElement(element) {
|
|
66939
|
-
|
|
67011
|
+
if (EDITABLE_ELEMENT_ROLE_RE.test(element.role.trim()))
|
|
67012
|
+
return true;
|
|
67013
|
+
const identity = [element.accessibilityId, element.resourceId, element.label].filter((v) => typeof v === "string" && v.trim().length > 0).join(" ");
|
|
67014
|
+
if (identity && EDITABLE_IDENTITY_RE.test(identity))
|
|
67015
|
+
return true;
|
|
67016
|
+
return false;
|
|
67017
|
+
}
|
|
67018
|
+
function isIdentityBearingTapTarget(element) {
|
|
67019
|
+
if (weakAssertionTargetReason(element))
|
|
67020
|
+
return false;
|
|
67021
|
+
if (isBareContainerLikeElement(element))
|
|
67022
|
+
return false;
|
|
67023
|
+
if (isEditableMobileElement(element))
|
|
67024
|
+
return false;
|
|
67025
|
+
if (/button|link|tab|menuitem|checkbox|switch|radio/i.test(element.role))
|
|
67026
|
+
return true;
|
|
67027
|
+
const label = [
|
|
67028
|
+
element.label,
|
|
67029
|
+
element.text,
|
|
67030
|
+
element.accessibilityId,
|
|
67031
|
+
element.resourceId
|
|
67032
|
+
].filter((v) => typeof v === "string" && v.trim().length > 0).join(" ");
|
|
67033
|
+
return /\b(sign\s*in|log\s*in|login|submit|save|continue|create|send|confirm|done|next|apply|checkout|pay|finish|update|register|sign\s*up)\b/i.test(label);
|
|
67034
|
+
}
|
|
67035
|
+
function isBareContainerLikeElement(element) {
|
|
67036
|
+
if (BARE_CONTAINER_CLASS_RE.test(element.role)) {
|
|
67037
|
+
const hasIdentity = Boolean(nonEmpty2(element.text) || nonEmpty2(element.label) || nonEmpty2(element.accessibilityId));
|
|
67038
|
+
return !hasIdentity;
|
|
67039
|
+
}
|
|
67040
|
+
return false;
|
|
66940
67041
|
}
|
|
66941
67042
|
function nonEmpty2(value) {
|
|
66942
67043
|
if (typeof value !== "string")
|
|
@@ -67428,7 +67529,12 @@ ${statePacket}` },
|
|
|
67428
67529
|
};
|
|
67429
67530
|
}
|
|
67430
67531
|
const absorbed = await absorbObservation(result.source, result.screenSignal, latest()?.screen.windowSize ?? null, result.screenshot, "mobile_tap");
|
|
67431
|
-
|
|
67532
|
+
const screenChanged = changedScreen(before, absorbed);
|
|
67533
|
+
const identityTap = isIdentityBearingTapTarget(element);
|
|
67534
|
+
if (shouldRecordAction(result, absorbed) && (screenChanged || identityTap)) {
|
|
67535
|
+
if (!screenChanged && identityTap) {
|
|
67536
|
+
log2(` tap left content signature unchanged but target has identity \u2014 recording commit control "${labelForElement(element)}".`);
|
|
67537
|
+
}
|
|
67432
67538
|
recordAction({
|
|
67433
67539
|
type: "TAP",
|
|
67434
67540
|
selector: (0, mobile_source_parser_js_2.preferredLocator)(element),
|
|
@@ -67572,6 +67678,13 @@ ${statePacket}` },
|
|
|
67572
67678
|
const element = resolveRef(toolArgs.ref);
|
|
67573
67679
|
if (!element)
|
|
67574
67680
|
return unknownRef(toolArgs.ref);
|
|
67681
|
+
const weakReason = weakAssertionTargetReason(element);
|
|
67682
|
+
if (weakReason) {
|
|
67683
|
+
return {
|
|
67684
|
+
ok: false,
|
|
67685
|
+
error: `Ref ${element.ref} is not a durable assertion target (${weakReason}). Re-snapshot and assert a human-visible label, text, or stable control that proves the screen outcome.`
|
|
67686
|
+
};
|
|
67687
|
+
}
|
|
67575
67688
|
const expected = nonEmpty2(typeof toolArgs.expected === "string" ? toolArgs.expected : String(toolArgs.expected ?? ""));
|
|
67576
67689
|
if (!expected)
|
|
67577
67690
|
return { ok: false, error: "mobile_assert_text needs a non-empty `expected` value." };
|
|
@@ -67830,19 +67943,46 @@ ${statePacket}` },
|
|
|
67830
67943
|
if (loginTapWithoutCredentialPath && !hasAnyCredentialEntry && !uiOnlyCredentialScenario && !emptyFieldValidationScenario && (sessionLifecycleScenario || /\b(valid credentials?|happy path|successful sign\s*in|successful login)\b/.test(text))) {
|
|
67831
67944
|
return "auth/session scenario tapped a login or sign-in control without recording a credential path";
|
|
67832
67945
|
}
|
|
67833
|
-
if (
|
|
67946
|
+
if (isCredentialSignIn) {
|
|
67947
|
+
const typedEmail = typedEmailIndexes.length > 0;
|
|
67948
|
+
const typedPassword = typedPasswordIndexes.length > 0;
|
|
67949
|
+
const tappedSubmit = submitAfterCredentials;
|
|
67950
|
+
if (typedEmail && typedPassword && tappedSubmit) {
|
|
67951
|
+
} else {
|
|
67952
|
+
const missing = [
|
|
67953
|
+
typedEmail ? null : "email/username entry",
|
|
67954
|
+
typedPassword ? null : "password entry",
|
|
67955
|
+
tappedSubmit ? null : "sign-in submit tap"
|
|
67956
|
+
].filter((value) => value !== null);
|
|
67957
|
+
return `credential sign-in scenario did not record ${missing.join(", ")}`;
|
|
67958
|
+
}
|
|
67959
|
+
}
|
|
67960
|
+
const isHappyPath = /\b(happy\s*path|successful|success|create|save|submit|complete|checkout|send|post)\b/.test(text) || scenario.type === "HAPPY_PATH";
|
|
67961
|
+
if (!isHappyPath)
|
|
67834
67962
|
return null;
|
|
67835
|
-
|
|
67836
|
-
|
|
67837
|
-
|
|
67838
|
-
|
|
67963
|
+
let lastTypeIndex = -1;
|
|
67964
|
+
let lastAssertIndex = -1;
|
|
67965
|
+
actions.forEach((action, index) => {
|
|
67966
|
+
if (action.type === "TYPE")
|
|
67967
|
+
lastTypeIndex = index;
|
|
67968
|
+
if (action.type === "ASSERT_VISIBLE" || action.type === "ASSERT_TEXT")
|
|
67969
|
+
lastAssertIndex = index;
|
|
67970
|
+
});
|
|
67971
|
+
if (lastTypeIndex < 0 || lastAssertIndex <= lastTypeIndex)
|
|
67839
67972
|
return null;
|
|
67840
|
-
const
|
|
67841
|
-
|
|
67842
|
-
|
|
67843
|
-
|
|
67844
|
-
|
|
67845
|
-
|
|
67973
|
+
const typeScreen = actions[lastTypeIndex]?.metadata?.screenName?.trim() ?? "";
|
|
67974
|
+
const assertScreen = actions[lastAssertIndex]?.metadata?.screenName?.trim() ?? "";
|
|
67975
|
+
const screensDiffer = typeScreen !== "" && assertScreen !== "" && typeScreen !== assertScreen;
|
|
67976
|
+
const commitAfterType = actions.some((action, index) => {
|
|
67977
|
+
if (action.type !== "TAP" || index <= lastTypeIndex || index >= lastAssertIndex)
|
|
67978
|
+
return false;
|
|
67979
|
+
const textForAction = actionText(action);
|
|
67980
|
+
return /\b(sign\s*in|log\s*in|login|continue|submit|save|create|send|confirm|done|next|apply|post|checkout|pay|finish|update|register|sign\s*up)\b/i.test(textForAction);
|
|
67981
|
+
});
|
|
67982
|
+
if (screensDiffer && !commitAfterType) {
|
|
67983
|
+
return "form fields were filled then a success assert was recorded without a commit/submit tap between them";
|
|
67984
|
+
}
|
|
67985
|
+
return null;
|
|
67846
67986
|
}
|
|
67847
67987
|
function actionCycleKey(action) {
|
|
67848
67988
|
return `${action.type}|${action.selector}|${action.value ?? ""}`;
|
|
@@ -68777,8 +68917,21 @@ For a fix, return the COMPLETE steps array (not a diff).`;
|
|
|
68777
68917
|
function coerceSteps(value) {
|
|
68778
68918
|
if (!Array.isArray(value) || value.length === 0)
|
|
68779
68919
|
return void 0;
|
|
68780
|
-
const
|
|
68781
|
-
|
|
68920
|
+
const steps = [];
|
|
68921
|
+
for (let i = 0; i < value.length; i++) {
|
|
68922
|
+
const step = value[i];
|
|
68923
|
+
if (!isRecord(step) || typeof step.action !== "string" || !step.action.trim()) {
|
|
68924
|
+
return void 0;
|
|
68925
|
+
}
|
|
68926
|
+
const orderRaw = step.order;
|
|
68927
|
+
const order = typeof orderRaw === "number" && Number.isFinite(orderRaw) ? orderRaw : typeof orderRaw === "string" && orderRaw.trim() !== "" && Number.isFinite(Number(orderRaw)) ? Number(orderRaw) : i;
|
|
68928
|
+
steps.push({
|
|
68929
|
+
...step,
|
|
68930
|
+
order,
|
|
68931
|
+
action: step.action
|
|
68932
|
+
});
|
|
68933
|
+
}
|
|
68934
|
+
return steps.map((step, order) => ({ ...step, order }));
|
|
68782
68935
|
}
|
|
68783
68936
|
function parseHealVerdict(content) {
|
|
68784
68937
|
const cleaned = stripCodeFences(content);
|
|
@@ -72999,6 +73152,48 @@ var require_snapshot_observation = __commonJS({
|
|
|
72999
73152
|
var PROMOTED_FORM_FIELD_CAP = 12;
|
|
73000
73153
|
var FORM_MEMBER_CONTAINER_SEGMENT_RE = /(?:^|>\s*)(?:form|group)\b/i;
|
|
73001
73154
|
var DATA_GRID_CONTAINER_SEGMENT_RE = /(?:^|>\s*)(?:table|grid|rowgroup|row|gridcell|cell)\b/i;
|
|
73155
|
+
var DEEP_NONNAV_CAP_CENSUS = 16;
|
|
73156
|
+
var CENSUS_NAV_LINK_CAP = 60;
|
|
73157
|
+
var CENSUS_MIN_ACTION_GROUP = 3;
|
|
73158
|
+
var DESTRUCTIVE_ACTION_VERB_RE = /^(?:delete|remove|destroy|suspend|deactivate|disable|revoke|ban|block|archive|terminate|reject|cancel|reset|purge|wipe|unpublish|unsubscribe)$/i;
|
|
73159
|
+
function leadingActionVerb(label) {
|
|
73160
|
+
if (!label)
|
|
73161
|
+
return null;
|
|
73162
|
+
const m = label.trim().match(/[A-Za-z]+/);
|
|
73163
|
+
return m ? m[0].toLowerCase() : null;
|
|
73164
|
+
}
|
|
73165
|
+
function isNavLinkEntry(entry) {
|
|
73166
|
+
return entry.element.elementType === "LINK" && !!entry.element.triggersNavigation;
|
|
73167
|
+
}
|
|
73168
|
+
function isRowRepeatedControl(element) {
|
|
73169
|
+
return !!element.enrichment?.rowContext || !!element.containerPath && DATA_GRID_CONTAINER_SEGMENT_RE.test(element.containerPath);
|
|
73170
|
+
}
|
|
73171
|
+
function rowContainerScope(containerPath) {
|
|
73172
|
+
if (!containerPath)
|
|
73173
|
+
return null;
|
|
73174
|
+
const m = containerPath.match(/^(.*?)\s*>\s*row\b/i);
|
|
73175
|
+
return m ? m[1].trim() : containerPath.trim();
|
|
73176
|
+
}
|
|
73177
|
+
function containerScopeLabel(scope) {
|
|
73178
|
+
if (!scope)
|
|
73179
|
+
return null;
|
|
73180
|
+
const landmarks = [...scope.matchAll(/(?:table|grid|treegrid|list|listbox|tree|rowgroup):([^>]+)/gi)];
|
|
73181
|
+
if (landmarks.length > 0)
|
|
73182
|
+
return landmarks[landmarks.length - 1][1].trim();
|
|
73183
|
+
const segs = scope.split(">").map((s) => s.trim()).filter(Boolean);
|
|
73184
|
+
return segs.length ? segs[segs.length - 1] : null;
|
|
73185
|
+
}
|
|
73186
|
+
function rowIdentityKey(element) {
|
|
73187
|
+
const rc = element.enrichment?.rowContext;
|
|
73188
|
+
if (rc)
|
|
73189
|
+
return `rc:${rc}`;
|
|
73190
|
+
const cp = element.containerPath;
|
|
73191
|
+
if (cp) {
|
|
73192
|
+
const m = cp.match(/>\s*row:(.+)$/i);
|
|
73193
|
+
return m ? `row:${m[1].trim()}` : `cp:${cp}`;
|
|
73194
|
+
}
|
|
73195
|
+
return element.ref ? `ref:${element.ref}` : "unknown";
|
|
73196
|
+
}
|
|
73002
73197
|
function isGroupedFormField(element) {
|
|
73003
73198
|
if (!FORM_CONTROL_ROLE_RE.test(element.role ?? ""))
|
|
73004
73199
|
return false;
|
|
@@ -73014,6 +73209,7 @@ var require_snapshot_observation = __commonJS({
|
|
|
73014
73209
|
const sourceKind = page.snapshotMeta?.sourceKind ?? page.snapshotArtifact?.sourceKind ?? "missing";
|
|
73015
73210
|
const artifact = page.snapshotArtifact;
|
|
73016
73211
|
const richRender = options.richRender ?? process.env.DISCOVERY_SNAPSHOT_RICH_RENDER === "true";
|
|
73212
|
+
const rowActionCensus = richRender && options.mode !== "survey" && (options.rowActionCensus ?? process.env.DISCOVERY_SNAPSHOT_ROW_CENSUS === "true");
|
|
73017
73213
|
const activeScope = inferActiveSnapshotScope(page);
|
|
73018
73214
|
const ranked = rankSnapshotElements(getScopedSnapshotElements(page, activeScope), withScopeContext({ ...context, richRank: richRender }, activeScope));
|
|
73019
73215
|
const allInteractive = ranked.filter(({ element }) => isInteractiveCandidate(element)).filter(({ element }) => {
|
|
@@ -73027,10 +73223,17 @@ var require_snapshot_observation = __commonJS({
|
|
|
73027
73223
|
});
|
|
73028
73224
|
let interactive;
|
|
73029
73225
|
if (options.mode === "survey") {
|
|
73030
|
-
const navLinks = allInteractive.filter(
|
|
73031
|
-
const others = allInteractive.filter((
|
|
73226
|
+
const navLinks = allInteractive.filter(isNavLinkEntry);
|
|
73227
|
+
const others = allInteractive.filter((entry) => !isNavLinkEntry(entry));
|
|
73032
73228
|
const othersCap = richRender ? 6 : 4;
|
|
73033
73229
|
interactive = [...navLinks, ...others.slice(0, othersCap)];
|
|
73230
|
+
} else if (rowActionCensus) {
|
|
73231
|
+
const navLinks = allInteractive.filter(isNavLinkEntry);
|
|
73232
|
+
const others = allInteractive.filter((entry) => !isNavLinkEntry(entry));
|
|
73233
|
+
interactive = [
|
|
73234
|
+
...navLinks.slice(0, CENSUS_NAV_LINK_CAP),
|
|
73235
|
+
...others.slice(0, options.elementLimit ?? DEEP_NONNAV_CAP_CENSUS)
|
|
73236
|
+
];
|
|
73034
73237
|
} else {
|
|
73035
73238
|
interactive = allInteractive.slice(0, options.elementLimit ?? (richRender ? 8 : 6));
|
|
73036
73239
|
}
|
|
@@ -73043,6 +73246,36 @@ var require_snapshot_observation = __commonJS({
|
|
|
73043
73246
|
}
|
|
73044
73247
|
}
|
|
73045
73248
|
}
|
|
73249
|
+
if (rowActionCensus && interactive.length > 0) {
|
|
73250
|
+
const shownVerbs = new Set(interactive.filter((entry) => !isNavLinkEntry(entry) && isRowRepeatedControl(entry.element)).map(({ element }) => leadingActionVerb(qualifyElementLabel(element))).filter(Boolean));
|
|
73251
|
+
const bestByVerb = /* @__PURE__ */ new Map();
|
|
73252
|
+
for (const entry of allInteractive) {
|
|
73253
|
+
if (isNavLinkEntry(entry) || !isRowRepeatedControl(entry.element))
|
|
73254
|
+
continue;
|
|
73255
|
+
const verb = leadingActionVerb(qualifyElementLabel(entry.element));
|
|
73256
|
+
if (!verb || !DESTRUCTIVE_ACTION_VERB_RE.test(verb))
|
|
73257
|
+
continue;
|
|
73258
|
+
if (shownVerbs.has(verb))
|
|
73259
|
+
continue;
|
|
73260
|
+
if (interactive.includes(entry))
|
|
73261
|
+
continue;
|
|
73262
|
+
const existing = bestByVerb.get(verb);
|
|
73263
|
+
if (!existing) {
|
|
73264
|
+
bestByVerb.set(verb, entry);
|
|
73265
|
+
continue;
|
|
73266
|
+
}
|
|
73267
|
+
if (existing.element.isDisabled && !entry.element.isDisabled)
|
|
73268
|
+
bestByVerb.set(verb, entry);
|
|
73269
|
+
}
|
|
73270
|
+
const swapIns = [...bestByVerb.values()];
|
|
73271
|
+
if (swapIns.length > 0) {
|
|
73272
|
+
const navShown = interactive.filter(isNavLinkEntry);
|
|
73273
|
+
const nonNavShown = interactive.filter((entry) => !isNavLinkEntry(entry));
|
|
73274
|
+
const evictable = Math.min(swapIns.length, nonNavShown.length);
|
|
73275
|
+
const trimmed = nonNavShown.slice(0, nonNavShown.length - evictable);
|
|
73276
|
+
interactive = [...navShown, ...trimmed, ...swapIns.slice(0, evictable)];
|
|
73277
|
+
}
|
|
73278
|
+
}
|
|
73046
73279
|
const duplicates = detectDuplicateRoleNameGroups(duplicateScanCandidates);
|
|
73047
73280
|
const allForms = artifact?.formGroups ?? page.formGroups ?? [];
|
|
73048
73281
|
const forms = allForms.slice(0, options.formLimit ?? 3);
|
|
@@ -73342,6 +73575,54 @@ var require_snapshot_observation = __commonJS({
|
|
|
73342
73575
|
const hidden = allInteractive.length - interactive.length;
|
|
73343
73576
|
if (hidden > 0) {
|
|
73344
73577
|
lines.push(` # +${hidden} more interactive element(s) not individually listed (capped) \u2014 some may appear in the duplicates block above; if your target isn't here, narrow the scope or act on it by ref.`);
|
|
73578
|
+
if (rowActionCensus) {
|
|
73579
|
+
const shownSet = new Set(interactive.map((entry) => entry.element));
|
|
73580
|
+
const groups = /* @__PURE__ */ new Map();
|
|
73581
|
+
for (const entry of allInteractive) {
|
|
73582
|
+
const el = entry.element;
|
|
73583
|
+
if (!isRowRepeatedControl(el))
|
|
73584
|
+
continue;
|
|
73585
|
+
const verb = leadingActionVerb(qualifyElementLabel(el));
|
|
73586
|
+
if (!verb)
|
|
73587
|
+
continue;
|
|
73588
|
+
const role = (el.role ?? el.elementType ?? "control").toLowerCase();
|
|
73589
|
+
const scope = rowContainerScope(el.containerPath);
|
|
73590
|
+
const key = JSON.stringify([scope ?? "", role, verb]);
|
|
73591
|
+
let group = groups.get(key);
|
|
73592
|
+
if (!group) {
|
|
73593
|
+
group = { verb, role, scopeLabel: containerScopeLabel(scope), rows: /* @__PURE__ */ new Set(), shownRows: /* @__PURE__ */ new Set(), disabledRows: /* @__PURE__ */ new Set(), controls: 0 };
|
|
73594
|
+
groups.set(key, group);
|
|
73595
|
+
}
|
|
73596
|
+
const rowKey = rowIdentityKey(el);
|
|
73597
|
+
group.rows.add(rowKey);
|
|
73598
|
+
group.controls += 1;
|
|
73599
|
+
if (shownSet.has(el))
|
|
73600
|
+
group.shownRows.add(rowKey);
|
|
73601
|
+
if (el.isDisabled)
|
|
73602
|
+
group.disabledRows.add(rowKey);
|
|
73603
|
+
}
|
|
73604
|
+
const collapsed = [...groups.values()].map((g) => ({
|
|
73605
|
+
verb: g.verb,
|
|
73606
|
+
role: g.role,
|
|
73607
|
+
scopeLabel: g.scopeLabel,
|
|
73608
|
+
total: g.rows.size,
|
|
73609
|
+
shown: g.shownRows.size,
|
|
73610
|
+
disabled: g.disabledRows.size,
|
|
73611
|
+
controls: g.controls
|
|
73612
|
+
})).filter((g) => g.total >= CENSUS_MIN_ACTION_GROUP && g.total > g.shown).sort((a, b) => {
|
|
73613
|
+
const aDestructive = DESTRUCTIVE_ACTION_VERB_RE.test(a.verb) ? 0 : 1;
|
|
73614
|
+
const bDestructive = DESTRUCTIVE_ACTION_VERB_RE.test(b.verb) ? 0 : 1;
|
|
73615
|
+
return aDestructive - bDestructive || b.total - b.shown - (a.total - a.shown);
|
|
73616
|
+
});
|
|
73617
|
+
for (const g of collapsed) {
|
|
73618
|
+
const verbLabel = g.verb.charAt(0).toUpperCase() + g.verb.slice(1);
|
|
73619
|
+
const destructiveTag = DESTRUCTIVE_ACTION_VERB_RE.test(g.verb) ? " [destructive]" : "";
|
|
73620
|
+
const where = g.scopeLabel ? ` in ${yamlScalar(g.scopeLabel)}` : "";
|
|
73621
|
+
const controlsNote = g.controls > g.total ? ` (${g.controls} controls across ${g.total} rows)` : "";
|
|
73622
|
+
const disabledNote = g.disabled > 0 ? `; ${g.disabled} disabled at capture` : "";
|
|
73623
|
+
lines.push(` # \u21B3 "${verbLabel}" ${g.role}${destructiveTag}${where}: present on ${g.total} rows${controlsNote} \u2014 ${g.shown} listed above, ${g.total - g.shown} collapsed${disabledNote}; the same action repeats down these rows \u2014 target a specific row by its row identity (the row's Edit link / row_context above), not a bare "${verbLabel}" locator.`);
|
|
73624
|
+
}
|
|
73625
|
+
}
|
|
73345
73626
|
}
|
|
73346
73627
|
}
|
|
73347
73628
|
}
|
|
@@ -74364,13 +74645,14 @@ var require_auth_recipe = __commonJS({
|
|
|
74364
74645
|
"../runner-core/dist/services/auth-recipe.js"(exports) {
|
|
74365
74646
|
"use strict";
|
|
74366
74647
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74367
|
-
exports.AUTH_MARKER_VERSION = void 0;
|
|
74648
|
+
exports.RESERVED_RUNNER_ENV_VARS = exports.AUTH_MARKER_VERSION = void 0;
|
|
74368
74649
|
exports.computeRecipeHash = computeRecipeHash;
|
|
74369
74650
|
exports.buildAuthRecipe = buildAuthRecipe;
|
|
74370
74651
|
exports.authProofLocator = authProofLocator;
|
|
74371
74652
|
exports.urlProofPredicateExpr = urlProofPredicateExpr;
|
|
74372
74653
|
exports.renderAuthProofAssertion = renderAuthProofAssertion;
|
|
74373
74654
|
exports.renderAuthFn = renderAuthFn;
|
|
74655
|
+
exports.isReservedRunnerEnvVar = isReservedRunnerEnvVar;
|
|
74374
74656
|
exports.absoluteRoute = absoluteRoute;
|
|
74375
74657
|
exports.renderReplayClickArrow = renderReplayClickArrow;
|
|
74376
74658
|
exports.renderReplaySettleArrow = renderReplaySettleArrow;
|
|
@@ -74489,6 +74771,17 @@ var require_auth_recipe = __commonJS({
|
|
|
74489
74771
|
lines.push(END_MARKER);
|
|
74490
74772
|
return lines.join("\n");
|
|
74491
74773
|
}
|
|
74774
|
+
exports.RESERVED_RUNNER_ENV_VARS = /* @__PURE__ */ new Set([
|
|
74775
|
+
"AUTH_FILE",
|
|
74776
|
+
"SHARED_AUTH_INJECT",
|
|
74777
|
+
"VALIDATE_RUN_ID",
|
|
74778
|
+
"VALIDATE_TASK_ARN",
|
|
74779
|
+
"VALIDATE_RUNNER_AUTH",
|
|
74780
|
+
"VALIDATE_SERVER_URL"
|
|
74781
|
+
]);
|
|
74782
|
+
function isReservedRunnerEnvVar(key) {
|
|
74783
|
+
return key.startsWith("VALIDATE_") || exports.RESERVED_RUNNER_ENV_VARS.has(key);
|
|
74784
|
+
}
|
|
74492
74785
|
function absoluteRoute(route2, baseUrl) {
|
|
74493
74786
|
try {
|
|
74494
74787
|
return new URL(route2, baseUrl).href;
|
|
@@ -315593,9 +315886,15 @@ var require_mobile_discovery_agent = __commonJS({
|
|
|
315593
315886
|
log2(`
|
|
315594
315887
|
Explore complete: ${explore.exploreStats.pagesDiscovered} screens, ${explore.exploreStats.elementsFound} elements, ${explore.exploreStats.transitionsFound} transitions (${exploreElapsed}s)`);
|
|
315595
315888
|
if (callbacks?.onExploreComplete) {
|
|
315596
|
-
|
|
315597
|
-
|
|
315598
|
-
})
|
|
315889
|
+
try {
|
|
315890
|
+
await callbacks.onExploreComplete(toExploreCompleteData(explore, collectedTransitions));
|
|
315891
|
+
} catch (err) {
|
|
315892
|
+
const message = checkpointErrorMessage(err);
|
|
315893
|
+
log2(`[stream] explore-complete callback failed: ${message}`);
|
|
315894
|
+
if (isTerminalDiscoveryCheckpointError(err)) {
|
|
315895
|
+
throw err;
|
|
315896
|
+
}
|
|
315897
|
+
}
|
|
315599
315898
|
}
|
|
315600
315899
|
if (!isSurvey && shouldPauseAfterTruncatedExplore(explore)) {
|
|
315601
315900
|
const duration2 = (Date.now() - startTime) / 1e3;
|
|
@@ -315623,8 +315922,10 @@ Explore complete: ${explore.exploreStats.pagesDiscovered} screens, ${explore.exp
|
|
|
315623
315922
|
pausePhase: "explore"
|
|
315624
315923
|
};
|
|
315625
315924
|
}
|
|
315626
|
-
|
|
315627
|
-
|
|
315925
|
+
if (!isSurvey) {
|
|
315926
|
+
log2("\nRelaunching app for a clean state before planning/generation...");
|
|
315927
|
+
await driver.relaunch();
|
|
315928
|
+
}
|
|
315628
315929
|
log2("\n\u2500\u2500 Phase 2: PLAN \u2500\u2500");
|
|
315629
315930
|
const graph = state2.toScreenGraph();
|
|
315630
315931
|
const screenMap = state2.buildInMemoryScreenMap();
|
|
@@ -315647,7 +315948,9 @@ Explore complete: ${explore.exploreStats.pagesDiscovered} screens, ${explore.exp
|
|
|
315647
315948
|
hasPassword: !!ctx.variableContext?.allTestVariables?.TEST_USER_PASSWORD || !!ctx.variableContext?.publicTestVariables?.TEST_USER_PASSWORD
|
|
315648
315949
|
},
|
|
315649
315950
|
incremental: ctx.incremental,
|
|
315650
|
-
testBudget
|
|
315951
|
+
testBudget,
|
|
315952
|
+
userNotes: ctx.userNotes,
|
|
315953
|
+
featureContext: ctx.featureContext
|
|
315651
315954
|
}, {
|
|
315652
315955
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
315653
315956
|
onAICall
|
|
@@ -315820,10 +316123,20 @@ Fatal error: ${msg}`);
|
|
|
315820
316123
|
log2(`App-data reset failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
315821
316124
|
return false;
|
|
315822
316125
|
}) ?? false;
|
|
315823
|
-
if (
|
|
315824
|
-
|
|
316126
|
+
if (cleared)
|
|
316127
|
+
return true;
|
|
316128
|
+
if (resetPolicy === "CLEAR_APP_DATA") {
|
|
316129
|
+
log2("CLEAR_APP_DATA requested but app-data reset was unavailable; a post-explore relaunch will reset state before planning.");
|
|
316130
|
+
}
|
|
316131
|
+
if (mode !== "survey") {
|
|
316132
|
+
return false;
|
|
315825
316133
|
}
|
|
315826
|
-
|
|
316134
|
+
try {
|
|
316135
|
+
await driver.relaunch();
|
|
316136
|
+
} catch (err) {
|
|
316137
|
+
log2(`App relaunch failed after unsuccessful app-data reset: ${err instanceof Error ? err.message : String(err)}`);
|
|
316138
|
+
}
|
|
316139
|
+
return false;
|
|
315827
316140
|
}
|
|
315828
316141
|
function shouldPauseAfterTruncatedExplore(explore) {
|
|
315829
316142
|
return explore.truncated === true && (explore.truncatedReason === "rate_limit" || explore.truncatedReason === "timeout");
|
|
@@ -315870,6 +316183,7 @@ var require_discover_agent = __commonJS({
|
|
|
315870
316183
|
var claude_discovery_runner_js_1 = require_claude_discovery_runner();
|
|
315871
316184
|
var agentic_cli_constants_js_1 = require_agentic_cli_constants();
|
|
315872
316185
|
var canvas_detector_js_1 = require_canvas_detector();
|
|
316186
|
+
var auth_recipe_js_1 = require_auth_recipe();
|
|
315873
316187
|
async function runCanvasPreflight(ctx, sharedBrowser, log2) {
|
|
315874
316188
|
const cached = ctx.appRenderType ?? null;
|
|
315875
316189
|
const engine = (0, agentic_cli_constants_js_1.getAgenticEngine)(ctx.exploreModel);
|
|
@@ -316565,7 +316879,7 @@ Health: ${healthSummary.totalIssues} issue(s) - ${healthSummary.consoleErrors.le
|
|
|
316565
316879
|
}
|
|
316566
316880
|
if (envRefs.size > 0) {
|
|
316567
316881
|
const existingKeys = new Set(ctx.variableContext.availableVariableKeys);
|
|
316568
|
-
const missing = [...envRefs].filter((k) => !existingKeys.has(k));
|
|
316882
|
+
const missing = [...envRefs].filter((k) => !existingKeys.has(k) && !(0, auth_recipe_js_1.isReservedRunnerEnvVar)(k));
|
|
316569
316883
|
if (missing.length > 0) {
|
|
316570
316884
|
autoVariableDefaults = {};
|
|
316571
316885
|
for (const key of missing) {
|
|
@@ -317081,7 +317395,7 @@ Health: ${healthSummary.totalIssues} issue(s) - ${healthSummary.consoleErrors.le
|
|
|
317081
317395
|
envRefs.add(match[1]);
|
|
317082
317396
|
if (envRefs.size > 0) {
|
|
317083
317397
|
const existingKeys = new Set(ctx.variableContext.availableVariableKeys);
|
|
317084
|
-
const missing = [...envRefs].filter((k) => !existingKeys.has(k));
|
|
317398
|
+
const missing = [...envRefs].filter((k) => !existingKeys.has(k) && !(0, auth_recipe_js_1.isReservedRunnerEnvVar)(k));
|
|
317085
317399
|
if (missing.length > 0) {
|
|
317086
317400
|
autoVariableDefaults = {};
|
|
317087
317401
|
for (const key of missing) {
|
|
@@ -329963,9 +330277,9 @@ var require_dist2 = __commonJS({
|
|
|
329963
330277
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
|
|
329964
330278
|
};
|
|
329965
330279
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
329966
|
-
exports.
|
|
329967
|
-
exports.
|
|
329968
|
-
exports.UNSUPPORTED_RENDERER_PREFIX = void 0;
|
|
330280
|
+
exports.buildImportContractMd = exports.MAX_ARCHIVE_BYTES = exports.isUnsafeEntryName = exports.extractZipSafely = exports.downloadArchiveCapped = exports.detectArchiveKind = exports.acquireRepoForImport = exports.runFrameworkImportOnRunner = exports.getAgenticEngine = exports.resolveRefFromSnapshot = exports.replayAuthRecipeIntoBrowser = exports.runRegisterCaptureRun = exports.runLoginCaptureRun = 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.canonicalizeRoute = 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.runMobileGeneratePhase = exports.runMobileAIPlanner = exports.buildMobileSiteMap = exports.formatMobileEvidenceForPlanner = exports.MOBILE_SCENARIO_SYSTEM_PROMPT = exports.MOBILE_SURVEY_SYSTEM_PROMPT = exports.runMobileExplorePhase = void 0;
|
|
330281
|
+
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 = 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.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.IMPORT_SCHEMA_VERSION = exports.validateImportResultFile = exports.validateImportedTest = exports.buildImportSystemRules = exports.buildImportKickoff = void 0;
|
|
330282
|
+
exports.UNSUPPORTED_RENDERER_PREFIX = exports.UnsupportedRendererError = exports.buildCanvasModeAuthoringAddendum = void 0;
|
|
329969
330283
|
__exportStar(require_ai_provider(), exports);
|
|
329970
330284
|
__exportStar(require_mobile_types(), exports);
|
|
329971
330285
|
__exportStar(require_mobile_source_parser(), exports);
|
|
@@ -330102,6 +330416,12 @@ var require_dist2 = __commonJS({
|
|
|
330102
330416
|
Object.defineProperty(exports, "AUTH_MARKER_VERSION", { enumerable: true, get: function() {
|
|
330103
330417
|
return auth_recipe_js_1.AUTH_MARKER_VERSION;
|
|
330104
330418
|
} });
|
|
330419
|
+
Object.defineProperty(exports, "RESERVED_RUNNER_ENV_VARS", { enumerable: true, get: function() {
|
|
330420
|
+
return auth_recipe_js_1.RESERVED_RUNNER_ENV_VARS;
|
|
330421
|
+
} });
|
|
330422
|
+
Object.defineProperty(exports, "isReservedRunnerEnvVar", { enumerable: true, get: function() {
|
|
330423
|
+
return auth_recipe_js_1.isReservedRunnerEnvVar;
|
|
330424
|
+
} });
|
|
330105
330425
|
var auth_capture_run_js_1 = require_auth_capture_run();
|
|
330106
330426
|
Object.defineProperty(exports, "runLoginCaptureRun", { enumerable: true, get: function() {
|
|
330107
330427
|
return auth_capture_run_js_1.runLoginCaptureRun;
|
|
@@ -330329,7 +330649,7 @@ var require_package4 = __commonJS({
|
|
|
330329
330649
|
"package.json"(exports, module) {
|
|
330330
330650
|
module.exports = {
|
|
330331
330651
|
name: "@validate.qa/runner",
|
|
330332
|
-
version: "1.0.
|
|
330652
|
+
version: "1.0.33",
|
|
330333
330653
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
330334
330654
|
bin: {
|
|
330335
330655
|
"validate-runner": "dist/cli.js"
|
|
@@ -330945,25 +331265,53 @@ async function resolveTypeTargetElementId(sessionPath, bounds) {
|
|
|
330945
331265
|
}
|
|
330946
331266
|
return elementId;
|
|
330947
331267
|
}
|
|
331268
|
+
async function sendKeysToFocusedInput(sessionPath, value) {
|
|
331269
|
+
await appiumRequest(`${sessionPath}/keys`, {
|
|
331270
|
+
method: "POST",
|
|
331271
|
+
body: JSON.stringify({ text: value, value: Array.from(value) })
|
|
331272
|
+
});
|
|
331273
|
+
}
|
|
330948
331274
|
async function typeText(sessionPath, value, bounds) {
|
|
330949
331275
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
330950
|
-
if (
|
|
330951
|
-
|
|
331276
|
+
if (elementId) {
|
|
331277
|
+
try {
|
|
331278
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/value`, {
|
|
331279
|
+
method: "POST",
|
|
331280
|
+
body: JSON.stringify({ text: value })
|
|
331281
|
+
});
|
|
331282
|
+
return;
|
|
331283
|
+
} catch (err) {
|
|
331284
|
+
if (isAppiumDriverDeadError(err)) throw err;
|
|
331285
|
+
}
|
|
330952
331286
|
}
|
|
330953
|
-
await
|
|
330954
|
-
method: "POST",
|
|
330955
|
-
body: JSON.stringify({ text: value })
|
|
330956
|
-
});
|
|
331287
|
+
await sendKeysToFocusedInput(sessionPath, value);
|
|
330957
331288
|
}
|
|
330958
331289
|
async function clearField(sessionPath, bounds) {
|
|
330959
331290
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
330960
|
-
if (
|
|
330961
|
-
|
|
331291
|
+
if (elementId) {
|
|
331292
|
+
try {
|
|
331293
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/clear`, {
|
|
331294
|
+
method: "POST",
|
|
331295
|
+
body: JSON.stringify({})
|
|
331296
|
+
});
|
|
331297
|
+
return;
|
|
331298
|
+
} catch (err) {
|
|
331299
|
+
if (isAppiumDriverDeadError(err)) throw err;
|
|
331300
|
+
}
|
|
331301
|
+
}
|
|
331302
|
+
try {
|
|
331303
|
+
await appiumRequest(`${sessionPath}/keys`, {
|
|
331304
|
+
method: "POST",
|
|
331305
|
+
// Common select-all + delete sequence is platform-dependent; empty keys
|
|
331306
|
+
// after focus is the portable baseline Appium accepts for many hybrids.
|
|
331307
|
+
body: JSON.stringify({ text: "", value: [] })
|
|
331308
|
+
});
|
|
331309
|
+
} catch (err) {
|
|
331310
|
+
if (isAppiumDriverDeadError(err)) throw err;
|
|
331311
|
+
throw new Error(
|
|
331312
|
+
`No focused element available for CLEAR. Tap an input field first.${err instanceof Error ? ` (${err.message})` : ""}`
|
|
331313
|
+
);
|
|
330962
331314
|
}
|
|
330963
|
-
await appiumRequest(`${sessionPath}/element/${elementId}/clear`, {
|
|
330964
|
-
method: "POST",
|
|
330965
|
-
body: JSON.stringify({})
|
|
330966
|
-
});
|
|
330967
331315
|
}
|
|
330968
331316
|
async function swipe(sessionPath, direction, distance, windowSize) {
|
|
330969
331317
|
const dist = clampMobileSwipeDistance(distance);
|
|
@@ -332204,6 +332552,10 @@ async function startMobileNetworkCapture(options) {
|
|
|
332204
332552
|
|
|
332205
332553
|
// src/services/appium-executor.ts
|
|
332206
332554
|
var DEFAULT_STEP_TIMEOUT_MS = 1e4;
|
|
332555
|
+
var ELEMENT_ID_PATTERN2 = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
332556
|
+
function validateElementId(elementId) {
|
|
332557
|
+
return typeof elementId === "string" && ELEMENT_ID_PATTERN2.test(elementId) ? elementId : null;
|
|
332558
|
+
}
|
|
332207
332559
|
var MOBILE_NEW_COMMAND_TIMEOUT_SECONDS = (() => {
|
|
332208
332560
|
const raw = Number(process.env.VALIDATEQA_MOBILE_NEW_COMMAND_TIMEOUT);
|
|
332209
332561
|
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 600;
|
|
@@ -332338,6 +332690,9 @@ async function createSession(target) {
|
|
|
332338
332690
|
});
|
|
332339
332691
|
const sessionId = result?.value?.sessionId ?? result?.sessionId;
|
|
332340
332692
|
if (!sessionId) throw new Error("Appium session creation returned no session ID");
|
|
332693
|
+
if (target.platform === "ANDROID") {
|
|
332694
|
+
await prepareAndroidInputEnvironment(target.deviceId);
|
|
332695
|
+
}
|
|
332341
332696
|
return sessionId;
|
|
332342
332697
|
}
|
|
332343
332698
|
var TERMINAL_SESSION_START = /not installed|no such (app|file)|could not confirm|bundle ?id .*(not|missing)|xcodeorgid|signing|provisioning|not authorized|unauthorized/i;
|
|
@@ -332725,7 +333080,9 @@ async function findElement(sessionId, target, timeoutMs = DEFAULT_STEP_TIMEOUT_M
|
|
|
332725
333080
|
method: "POST",
|
|
332726
333081
|
body: JSON.stringify({ using: strategy.using, value: strategy.value })
|
|
332727
333082
|
});
|
|
332728
|
-
const elementId =
|
|
333083
|
+
const elementId = validateElementId(
|
|
333084
|
+
result?.value?.ELEMENT || result?.value?.["element-6066-11e4-a52e-4f735466cecf"]
|
|
333085
|
+
);
|
|
332729
333086
|
if (elementId) return elementId;
|
|
332730
333087
|
} catch (err) {
|
|
332731
333088
|
if (isTransportError(err)) throw err;
|
|
@@ -332765,24 +333122,25 @@ async function tapCoordinates(sessionId, bounds) {
|
|
|
332765
333122
|
})
|
|
332766
333123
|
});
|
|
332767
333124
|
}
|
|
332768
|
-
async function getActiveElementId2(sessionId) {
|
|
332769
|
-
try {
|
|
332770
|
-
const activeEl = await wdRequest(`/session/${sessionId}/element/active`);
|
|
332771
|
-
return activeEl?.value?.ELEMENT || activeEl?.value?.["element-6066-11e4-a52e-4f735466cecf"] || null;
|
|
332772
|
-
} catch {
|
|
332773
|
-
return null;
|
|
332774
|
-
}
|
|
332775
|
-
}
|
|
332776
|
-
async function sendKeysToFocusedInput(sessionId, value) {
|
|
332777
|
-
await wdRequest(`/session/${sessionId}/keys`, {
|
|
332778
|
-
method: "POST",
|
|
332779
|
-
body: JSON.stringify({ text: value, value: Array.from(value) })
|
|
332780
|
-
});
|
|
332781
|
-
}
|
|
332782
333125
|
function encodeAndroidInputText(value) {
|
|
332783
333126
|
const inputEncoded = value.replace(/%/g, "%25").replace(/\s/g, "%s");
|
|
332784
333127
|
return `'${inputEncoded.replace(/'/g, `'\\''`)}'`;
|
|
332785
333128
|
}
|
|
333129
|
+
async function prepareAndroidInputEnvironment(deviceId) {
|
|
333130
|
+
if (!deviceId) return;
|
|
333131
|
+
const settingsToDisable = [
|
|
333132
|
+
["secure", "stylus_handwriting_enabled", "0"]
|
|
333133
|
+
];
|
|
333134
|
+
for (const [namespace, key, value] of settingsToDisable) {
|
|
333135
|
+
try {
|
|
333136
|
+
await execFileAsync2("adb", ["-s", deviceId, "shell", "settings", "put", namespace, key, value], {
|
|
333137
|
+
timeout: 8e3,
|
|
333138
|
+
windowsHide: true
|
|
333139
|
+
});
|
|
333140
|
+
} catch {
|
|
333141
|
+
}
|
|
333142
|
+
}
|
|
333143
|
+
}
|
|
332786
333144
|
async function sendAndroidTextToFocusedInput(deviceId, value) {
|
|
332787
333145
|
if (!deviceId) {
|
|
332788
333146
|
throw new Error("Android keyboard fallback requires a device id");
|
|
@@ -332844,100 +333202,28 @@ async function executeType(sessionId, step, context) {
|
|
|
332844
333202
|
if (step.value === void 0 || step.value === null || step.value === "") return;
|
|
332845
333203
|
const value = String(step.value);
|
|
332846
333204
|
const bounds = boundsForRun(step, context.platform, context.isCrossPlatform ?? false);
|
|
332847
|
-
|
|
332848
|
-
|
|
332849
|
-
|
|
332850
|
-
|
|
332851
|
-
|
|
332852
|
-
|
|
332853
|
-
|
|
332854
|
-
|
|
332855
|
-
|
|
332856
|
-
|
|
332857
|
-
|
|
332858
|
-
|
|
332859
|
-
|
|
332860
|
-
|
|
332861
|
-
|
|
332862
|
-
try {
|
|
332863
|
-
elementId = await findElement(sessionId, step.target, readStepTimeout(step), { platform: context.platform });
|
|
332864
|
-
lastInputError = await trySendElementValue(sessionId, elementId, value);
|
|
332865
|
-
if (!lastInputError) return;
|
|
332866
|
-
elementId = null;
|
|
332867
|
-
} catch (error2) {
|
|
332868
|
-
if (isTransportError(error2) || !bounds || buildLocatorStrategies(step.target, context.platform).length > 0) throw error2;
|
|
332869
|
-
lastInputError = error2;
|
|
332870
|
-
}
|
|
332871
|
-
}
|
|
332872
|
-
if (!elementId && bounds) {
|
|
332873
|
-
await tapCoordinates(sessionId, bounds);
|
|
332874
|
-
tappedBounds = true;
|
|
332875
|
-
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
332876
|
-
elementId = await getActiveElementId2(sessionId);
|
|
332877
|
-
}
|
|
332878
|
-
if (!elementId) {
|
|
332879
|
-
elementId = await getActiveElementId2(sessionId);
|
|
332880
|
-
}
|
|
332881
|
-
if (elementId) {
|
|
332882
|
-
lastInputError = await trySendElementValue(sessionId, elementId, value);
|
|
332883
|
-
if (!lastInputError) return;
|
|
332884
|
-
elementId = null;
|
|
332885
|
-
if (!bounds) {
|
|
332886
|
-
throw lastInputError instanceof Error ? lastInputError : new Error(String(lastInputError));
|
|
332887
|
-
}
|
|
332888
|
-
}
|
|
332889
|
-
if (bounds) {
|
|
332890
|
-
if (!tappedBounds) {
|
|
332891
|
-
await tapCoordinates(sessionId, bounds);
|
|
332892
|
-
tappedBounds = true;
|
|
332893
|
-
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
332894
|
-
}
|
|
332895
|
-
try {
|
|
332896
|
-
await sendKeysToFocusedInput(sessionId, value);
|
|
332897
|
-
return;
|
|
332898
|
-
} catch (error2) {
|
|
332899
|
-
if (isTransportError(error2)) throw error2;
|
|
332900
|
-
lastInputError = error2;
|
|
332901
|
-
}
|
|
332902
|
-
}
|
|
332903
|
-
if (context.platform === "ANDROID" && tappedBounds) {
|
|
332904
|
-
try {
|
|
332905
|
-
context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
|
|
332906
|
-
await sendAndroidTextToFocusedInput(context.deviceId, value);
|
|
332907
|
-
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
332908
|
-
return;
|
|
332909
|
-
} catch (error2) {
|
|
332910
|
-
if (isTransportError(error2)) throw error2;
|
|
332911
|
-
lastInputError = error2;
|
|
333205
|
+
const sessionPath = `/session/${sessionId}`;
|
|
333206
|
+
try {
|
|
333207
|
+
await typeText(sessionPath, value, bounds);
|
|
333208
|
+
return;
|
|
333209
|
+
} catch (error2) {
|
|
333210
|
+
if (isTransportError(error2)) throw error2;
|
|
333211
|
+
if (context.platform === "ANDROID" && bounds) {
|
|
333212
|
+
try {
|
|
333213
|
+
context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
|
|
333214
|
+
await sendAndroidTextToFocusedInput(context.deviceId, value);
|
|
333215
|
+
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
333216
|
+
return;
|
|
333217
|
+
} catch (androidError) {
|
|
333218
|
+
if (isTransportError(androidError)) throw androidError;
|
|
333219
|
+
}
|
|
332912
333220
|
}
|
|
333221
|
+
throw error2 instanceof Error ? error2 : new Error(String(error2));
|
|
332913
333222
|
}
|
|
332914
|
-
throw new Error(`TYPE step could not send text to the focused target${lastInputError instanceof Error ? `: ${lastInputError.message}` : ""}`);
|
|
332915
333223
|
}
|
|
332916
333224
|
async function executeClear(sessionId, step, platform3, isCrossPlatform = false) {
|
|
332917
333225
|
const bounds = boundsForRun(step, platform3, isCrossPlatform);
|
|
332918
|
-
|
|
332919
|
-
if (step.target) {
|
|
332920
|
-
try {
|
|
332921
|
-
elementId = await findElement(sessionId, step.target, readStepTimeout(step), { platform: platform3 });
|
|
332922
|
-
} catch (error2) {
|
|
332923
|
-
if (isTransportError(error2) || !bounds || buildLocatorStrategies(step.target, platform3).length > 0) throw error2;
|
|
332924
|
-
}
|
|
332925
|
-
}
|
|
332926
|
-
if (!elementId && bounds) {
|
|
332927
|
-
await tapCoordinates(sessionId, bounds);
|
|
332928
|
-
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
332929
|
-
elementId = await getActiveElementId2(sessionId);
|
|
332930
|
-
}
|
|
332931
|
-
if (!elementId) {
|
|
332932
|
-
elementId = await getActiveElementId2(sessionId);
|
|
332933
|
-
}
|
|
332934
|
-
if (!elementId) {
|
|
332935
|
-
throw new Error("CLEAR step could not resolve a target or focused element");
|
|
332936
|
-
}
|
|
332937
|
-
await wdRequest(`/session/${sessionId}/element/${elementId}/clear`, {
|
|
332938
|
-
method: "POST",
|
|
332939
|
-
body: "{}"
|
|
332940
|
-
});
|
|
333226
|
+
await clearField(`/session/${sessionId}`, bounds);
|
|
332941
333227
|
}
|
|
332942
333228
|
async function executeSwipe(sessionId, step) {
|
|
332943
333229
|
const direction = step.metadata?.direction || "up";
|