@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.js
CHANGED
|
@@ -62285,7 +62285,7 @@ var require_mobile_explorer = __commonJS({
|
|
|
62285
62285
|
return { screen, signal, refMap, observation };
|
|
62286
62286
|
}
|
|
62287
62287
|
function buildStatePacket(input) {
|
|
62288
|
-
const { state: state2, mode, iteration, maxIterations, latestObservation, journeys, transitions } = input;
|
|
62288
|
+
const { state: state2, mode, iteration, maxIterations, latestObservation, journeys, transitions, featureName, featureContext, userNotes } = input;
|
|
62289
62289
|
const sections = [];
|
|
62290
62290
|
sections.push(`## Mobile Exploration Status (iteration ${iteration}/${maxIterations})`);
|
|
62291
62291
|
sections.push(`Mode: ${mode} | Budget remaining: ${maxIterations - iteration} iterations`);
|
|
@@ -62298,10 +62298,46 @@ var require_mobile_explorer = __commonJS({
|
|
|
62298
62298
|
sections.push("");
|
|
62299
62299
|
if (mode === "survey") {
|
|
62300
62300
|
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.");
|
|
62301
|
+
} else if (featureName?.trim()) {
|
|
62302
|
+
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.`);
|
|
62301
62303
|
} else {
|
|
62302
62304
|
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.");
|
|
62303
62305
|
}
|
|
62304
62306
|
sections.push("");
|
|
62307
|
+
if (mode === "deep" && featureName?.trim()) {
|
|
62308
|
+
sections.push(`## Feature: ${featureName.trim()}`);
|
|
62309
|
+
if (featureContext?.plannerDescription?.trim()) {
|
|
62310
|
+
sections.push(featureContext.plannerDescription.trim());
|
|
62311
|
+
}
|
|
62312
|
+
if (featureContext?.priorInsight?.trim()) {
|
|
62313
|
+
sections.push(`Prior insight: ${featureContext.priorInsight.trim()}`);
|
|
62314
|
+
}
|
|
62315
|
+
if (featureContext?.pageIntel && featureContext.pageIntel.length > 0) {
|
|
62316
|
+
sections.push("### Target screens / routes for this feature");
|
|
62317
|
+
for (const page of featureContext.pageIntel.slice(0, 12)) {
|
|
62318
|
+
sections.push(`- ${page.route} (${page.type}): ${page.summary}`);
|
|
62319
|
+
}
|
|
62320
|
+
}
|
|
62321
|
+
if (featureContext?.flowIntel && featureContext.flowIntel.length > 0) {
|
|
62322
|
+
sections.push("### Known flows");
|
|
62323
|
+
for (const flow of featureContext.flowIntel.slice(0, 8)) {
|
|
62324
|
+
sections.push(`- ${flow}`);
|
|
62325
|
+
}
|
|
62326
|
+
}
|
|
62327
|
+
if (featureContext?.otherFeatures && featureContext.otherFeatures.length > 0) {
|
|
62328
|
+
sections.push("### Other features \u2014 do NOT explore these (navigation context only)");
|
|
62329
|
+
for (const other of featureContext.otherFeatures.slice(0, 12)) {
|
|
62330
|
+
const pages = other.pages?.length ? ` [${other.pages.slice(0, 4).join(", ")}]` : "";
|
|
62331
|
+
sections.push(`- ${other.name}${pages}`);
|
|
62332
|
+
}
|
|
62333
|
+
}
|
|
62334
|
+
sections.push("");
|
|
62335
|
+
}
|
|
62336
|
+
if (userNotes?.trim()) {
|
|
62337
|
+
sections.push(`## Operator notes
|
|
62338
|
+
${userNotes.trim()}`);
|
|
62339
|
+
sections.push("");
|
|
62340
|
+
}
|
|
62305
62341
|
const screenCount = state2.getScreenCount();
|
|
62306
62342
|
sections.push(`## Screens Mapped: ${screenCount} | Transitions: ${transitions.length} | Journeys: ${journeys.length}`);
|
|
62307
62343
|
const currentScreenId = state2.getCurrentScreenId();
|
|
@@ -62458,7 +62494,7 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
62458
62494
|
} catch {
|
|
62459
62495
|
}
|
|
62460
62496
|
};
|
|
62461
|
-
const mode = ctx.mode === "
|
|
62497
|
+
const mode = ctx.mode === "survey" ? "survey" : "deep";
|
|
62462
62498
|
const platform3 = platformForMedium(ctx.medium);
|
|
62463
62499
|
const enableRecordJourney = config?.enableRecordJourney ?? mode !== "survey";
|
|
62464
62500
|
const targetAppId = ctx.target?.appId?.trim() || void 0;
|
|
@@ -62746,7 +62782,10 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
62746
62782
|
maxIterations,
|
|
62747
62783
|
latestObservation: getLatest()?.observation ?? null,
|
|
62748
62784
|
journeys,
|
|
62749
|
-
transitions: collectedTransitions
|
|
62785
|
+
transitions: collectedTransitions,
|
|
62786
|
+
featureName: ctx.featureName,
|
|
62787
|
+
featureContext: ctx.featureContext,
|
|
62788
|
+
userNotes: ctx.userNotes
|
|
62750
62789
|
});
|
|
62751
62790
|
const messages = [
|
|
62752
62791
|
{ role: "system", content: systemPrompt },
|
|
@@ -66315,6 +66354,34 @@ ${targetBits.length > 0 ? targetBits.join(" ") : "native mobile app"}`);
|
|
|
66315
66354
|
if (context.featureName) {
|
|
66316
66355
|
sections.push(`## Feature: ${context.featureName}
|
|
66317
66356
|
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.`);
|
|
66357
|
+
if (context.featureContext?.plannerDescription?.trim()) {
|
|
66358
|
+
sections.push(context.featureContext.plannerDescription.trim());
|
|
66359
|
+
}
|
|
66360
|
+
if (context.featureContext?.priorInsight?.trim()) {
|
|
66361
|
+
sections.push(`Prior insight: ${context.featureContext.priorInsight.trim()}`);
|
|
66362
|
+
}
|
|
66363
|
+
if (context.featureContext?.pageIntel && context.featureContext.pageIntel.length > 0) {
|
|
66364
|
+
sections.push("### Feature page intel");
|
|
66365
|
+
for (const page of context.featureContext.pageIntel.slice(0, 12)) {
|
|
66366
|
+
sections.push(`- ${page.route} (${page.type}): ${page.summary}`);
|
|
66367
|
+
}
|
|
66368
|
+
}
|
|
66369
|
+
if (context.featureContext?.flowIntel && context.featureContext.flowIntel.length > 0) {
|
|
66370
|
+
sections.push("### Known flows");
|
|
66371
|
+
for (const flow of context.featureContext.flowIntel.slice(0, 8)) {
|
|
66372
|
+
sections.push(`- ${flow}`);
|
|
66373
|
+
}
|
|
66374
|
+
}
|
|
66375
|
+
if (context.featureContext?.otherFeatures && context.featureContext.otherFeatures.length > 0) {
|
|
66376
|
+
sections.push("### Other features (do not plan primary scenarios for these)");
|
|
66377
|
+
for (const other of context.featureContext.otherFeatures.slice(0, 12)) {
|
|
66378
|
+
sections.push(`- ${other.name}`);
|
|
66379
|
+
}
|
|
66380
|
+
}
|
|
66381
|
+
}
|
|
66382
|
+
if (context.userNotes?.trim()) {
|
|
66383
|
+
sections.push(`## Operator notes
|
|
66384
|
+
${context.userNotes.trim()}`);
|
|
66318
66385
|
}
|
|
66319
66386
|
const orderedScreens = plannableTargetAppScreens(graph, context.appId).sort((a, b) => a.firstSeenIteration - b.firstSeenIteration);
|
|
66320
66387
|
const includedScreenIds = new Set(orderedScreens.map((state2) => state2.screenId));
|
|
@@ -66441,7 +66508,7 @@ ${constraints.join("\n")}`);
|
|
|
66441
66508
|
routePattern: null,
|
|
66442
66509
|
title: state2.screenName ?? null,
|
|
66443
66510
|
pageType: state2.screenType ?? "UNKNOWN",
|
|
66444
|
-
requiresAuth:
|
|
66511
|
+
requiresAuth: state2.screenType?.toLowerCase() === "auth",
|
|
66445
66512
|
visitCount: state2.visited ? 1 : 0,
|
|
66446
66513
|
lastVisitedAt: state2.visited ? now : null,
|
|
66447
66514
|
elementsCovered: 0,
|
|
@@ -66461,6 +66528,8 @@ ${constraints.join("\n")}`);
|
|
|
66461
66528
|
const toPageId = idByScreen.get(t.toScreenId);
|
|
66462
66529
|
if (!fromPageId || !toPageId)
|
|
66463
66530
|
continue;
|
|
66531
|
+
const fromScreen = graph.screens.get(t.fromScreenId);
|
|
66532
|
+
const toScreen = graph.screens.get(t.toScreenId);
|
|
66464
66533
|
transitions.push({
|
|
66465
66534
|
id: `mtx-${txSeq++}`,
|
|
66466
66535
|
fromPageId,
|
|
@@ -66468,7 +66537,7 @@ ${constraints.join("\n")}`);
|
|
|
66468
66537
|
triggerType: t.action.toUpperCase(),
|
|
66469
66538
|
triggerLabel: t.viaRef ?? null,
|
|
66470
66539
|
observedCount: 1,
|
|
66471
|
-
requiresAuth:
|
|
66540
|
+
requiresAuth: fromScreen?.screenType?.toLowerCase() === "auth" || toScreen?.screenType?.toLowerCase() === "auth"
|
|
66472
66541
|
});
|
|
66473
66542
|
}
|
|
66474
66543
|
return {
|
|
@@ -66671,7 +66740,9 @@ ${constraints.join("\n")}`);
|
|
|
66671
66740
|
recordedJourneys,
|
|
66672
66741
|
credentials: input.credentials,
|
|
66673
66742
|
testBudget: input.testBudget,
|
|
66674
|
-
incremental: input.incremental
|
|
66743
|
+
incremental: input.incremental,
|
|
66744
|
+
userNotes: input.userNotes,
|
|
66745
|
+
featureContext: input.featureContext
|
|
66675
66746
|
})
|
|
66676
66747
|
});
|
|
66677
66748
|
const plannerFeatureGroups = filterFeatureGroupsToSiteMap(result.featureGroups, siteMap, input.graph, input.appId);
|
|
@@ -66930,8 +67001,38 @@ var require_mobile_generator = __commonJS({
|
|
|
66930
67001
|
return (labelIsEditableValue ? void 0 : label) ?? nonEmpty2(element.accessibilityId) ?? nonEmpty2(element.resourceId) ?? (editable ? void 0 : text) ?? "element";
|
|
66931
67002
|
}
|
|
66932
67003
|
var EDITABLE_ELEMENT_ROLE_RE = /(EditText|AutoCompleteTextView|XCUIElementTypeTextField|XCUIElementTypeSecureTextField|XCUIElementTypeSearchField|XCUIElementTypeTextView)$/;
|
|
67004
|
+
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;
|
|
66933
67005
|
function isEditableMobileElement(element) {
|
|
66934
|
-
|
|
67006
|
+
if (EDITABLE_ELEMENT_ROLE_RE.test(element.role.trim()))
|
|
67007
|
+
return true;
|
|
67008
|
+
const identity = [element.accessibilityId, element.resourceId, element.label].filter((v) => typeof v === "string" && v.trim().length > 0).join(" ");
|
|
67009
|
+
if (identity && EDITABLE_IDENTITY_RE.test(identity))
|
|
67010
|
+
return true;
|
|
67011
|
+
return false;
|
|
67012
|
+
}
|
|
67013
|
+
function isIdentityBearingTapTarget(element) {
|
|
67014
|
+
if (weakAssertionTargetReason(element))
|
|
67015
|
+
return false;
|
|
67016
|
+
if (isBareContainerLikeElement(element))
|
|
67017
|
+
return false;
|
|
67018
|
+
if (isEditableMobileElement(element))
|
|
67019
|
+
return false;
|
|
67020
|
+
if (/button|link|tab|menuitem|checkbox|switch|radio/i.test(element.role))
|
|
67021
|
+
return true;
|
|
67022
|
+
const label = [
|
|
67023
|
+
element.label,
|
|
67024
|
+
element.text,
|
|
67025
|
+
element.accessibilityId,
|
|
67026
|
+
element.resourceId
|
|
67027
|
+
].filter((v) => typeof v === "string" && v.trim().length > 0).join(" ");
|
|
67028
|
+
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);
|
|
67029
|
+
}
|
|
67030
|
+
function isBareContainerLikeElement(element) {
|
|
67031
|
+
if (BARE_CONTAINER_CLASS_RE.test(element.role)) {
|
|
67032
|
+
const hasIdentity = Boolean(nonEmpty2(element.text) || nonEmpty2(element.label) || nonEmpty2(element.accessibilityId));
|
|
67033
|
+
return !hasIdentity;
|
|
67034
|
+
}
|
|
67035
|
+
return false;
|
|
66935
67036
|
}
|
|
66936
67037
|
function nonEmpty2(value) {
|
|
66937
67038
|
if (typeof value !== "string")
|
|
@@ -67423,7 +67524,12 @@ ${statePacket}` },
|
|
|
67423
67524
|
};
|
|
67424
67525
|
}
|
|
67425
67526
|
const absorbed = await absorbObservation(result.source, result.screenSignal, latest()?.screen.windowSize ?? null, result.screenshot, "mobile_tap");
|
|
67426
|
-
|
|
67527
|
+
const screenChanged = changedScreen(before, absorbed);
|
|
67528
|
+
const identityTap = isIdentityBearingTapTarget(element);
|
|
67529
|
+
if (shouldRecordAction(result, absorbed) && (screenChanged || identityTap)) {
|
|
67530
|
+
if (!screenChanged && identityTap) {
|
|
67531
|
+
log2(` tap left content signature unchanged but target has identity \u2014 recording commit control "${labelForElement(element)}".`);
|
|
67532
|
+
}
|
|
67427
67533
|
recordAction({
|
|
67428
67534
|
type: "TAP",
|
|
67429
67535
|
selector: (0, mobile_source_parser_js_2.preferredLocator)(element),
|
|
@@ -67567,6 +67673,13 @@ ${statePacket}` },
|
|
|
67567
67673
|
const element = resolveRef(toolArgs.ref);
|
|
67568
67674
|
if (!element)
|
|
67569
67675
|
return unknownRef(toolArgs.ref);
|
|
67676
|
+
const weakReason = weakAssertionTargetReason(element);
|
|
67677
|
+
if (weakReason) {
|
|
67678
|
+
return {
|
|
67679
|
+
ok: false,
|
|
67680
|
+
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.`
|
|
67681
|
+
};
|
|
67682
|
+
}
|
|
67570
67683
|
const expected = nonEmpty2(typeof toolArgs.expected === "string" ? toolArgs.expected : String(toolArgs.expected ?? ""));
|
|
67571
67684
|
if (!expected)
|
|
67572
67685
|
return { ok: false, error: "mobile_assert_text needs a non-empty `expected` value." };
|
|
@@ -67825,19 +67938,46 @@ ${statePacket}` },
|
|
|
67825
67938
|
if (loginTapWithoutCredentialPath && !hasAnyCredentialEntry && !uiOnlyCredentialScenario && !emptyFieldValidationScenario && (sessionLifecycleScenario || /\b(valid credentials?|happy path|successful sign\s*in|successful login)\b/.test(text))) {
|
|
67826
67939
|
return "auth/session scenario tapped a login or sign-in control without recording a credential path";
|
|
67827
67940
|
}
|
|
67828
|
-
if (
|
|
67941
|
+
if (isCredentialSignIn) {
|
|
67942
|
+
const typedEmail = typedEmailIndexes.length > 0;
|
|
67943
|
+
const typedPassword = typedPasswordIndexes.length > 0;
|
|
67944
|
+
const tappedSubmit = submitAfterCredentials;
|
|
67945
|
+
if (typedEmail && typedPassword && tappedSubmit) {
|
|
67946
|
+
} else {
|
|
67947
|
+
const missing = [
|
|
67948
|
+
typedEmail ? null : "email/username entry",
|
|
67949
|
+
typedPassword ? null : "password entry",
|
|
67950
|
+
tappedSubmit ? null : "sign-in submit tap"
|
|
67951
|
+
].filter((value) => value !== null);
|
|
67952
|
+
return `credential sign-in scenario did not record ${missing.join(", ")}`;
|
|
67953
|
+
}
|
|
67954
|
+
}
|
|
67955
|
+
const isHappyPath = /\b(happy\s*path|successful|success|create|save|submit|complete|checkout|send|post)\b/.test(text) || scenario.type === "HAPPY_PATH";
|
|
67956
|
+
if (!isHappyPath)
|
|
67829
67957
|
return null;
|
|
67830
|
-
|
|
67831
|
-
|
|
67832
|
-
|
|
67833
|
-
|
|
67958
|
+
let lastTypeIndex = -1;
|
|
67959
|
+
let lastAssertIndex = -1;
|
|
67960
|
+
actions.forEach((action, index) => {
|
|
67961
|
+
if (action.type === "TYPE")
|
|
67962
|
+
lastTypeIndex = index;
|
|
67963
|
+
if (action.type === "ASSERT_VISIBLE" || action.type === "ASSERT_TEXT")
|
|
67964
|
+
lastAssertIndex = index;
|
|
67965
|
+
});
|
|
67966
|
+
if (lastTypeIndex < 0 || lastAssertIndex <= lastTypeIndex)
|
|
67834
67967
|
return null;
|
|
67835
|
-
const
|
|
67836
|
-
|
|
67837
|
-
|
|
67838
|
-
|
|
67839
|
-
|
|
67840
|
-
|
|
67968
|
+
const typeScreen = actions[lastTypeIndex]?.metadata?.screenName?.trim() ?? "";
|
|
67969
|
+
const assertScreen = actions[lastAssertIndex]?.metadata?.screenName?.trim() ?? "";
|
|
67970
|
+
const screensDiffer = typeScreen !== "" && assertScreen !== "" && typeScreen !== assertScreen;
|
|
67971
|
+
const commitAfterType = actions.some((action, index) => {
|
|
67972
|
+
if (action.type !== "TAP" || index <= lastTypeIndex || index >= lastAssertIndex)
|
|
67973
|
+
return false;
|
|
67974
|
+
const textForAction = actionText(action);
|
|
67975
|
+
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);
|
|
67976
|
+
});
|
|
67977
|
+
if (screensDiffer && !commitAfterType) {
|
|
67978
|
+
return "form fields were filled then a success assert was recorded without a commit/submit tap between them";
|
|
67979
|
+
}
|
|
67980
|
+
return null;
|
|
67841
67981
|
}
|
|
67842
67982
|
function actionCycleKey(action) {
|
|
67843
67983
|
return `${action.type}|${action.selector}|${action.value ?? ""}`;
|
|
@@ -68772,8 +68912,21 @@ For a fix, return the COMPLETE steps array (not a diff).`;
|
|
|
68772
68912
|
function coerceSteps(value) {
|
|
68773
68913
|
if (!Array.isArray(value) || value.length === 0)
|
|
68774
68914
|
return void 0;
|
|
68775
|
-
const
|
|
68776
|
-
|
|
68915
|
+
const steps = [];
|
|
68916
|
+
for (let i = 0; i < value.length; i++) {
|
|
68917
|
+
const step = value[i];
|
|
68918
|
+
if (!isRecord(step) || typeof step.action !== "string" || !step.action.trim()) {
|
|
68919
|
+
return void 0;
|
|
68920
|
+
}
|
|
68921
|
+
const orderRaw = step.order;
|
|
68922
|
+
const order = typeof orderRaw === "number" && Number.isFinite(orderRaw) ? orderRaw : typeof orderRaw === "string" && orderRaw.trim() !== "" && Number.isFinite(Number(orderRaw)) ? Number(orderRaw) : i;
|
|
68923
|
+
steps.push({
|
|
68924
|
+
...step,
|
|
68925
|
+
order,
|
|
68926
|
+
action: step.action
|
|
68927
|
+
});
|
|
68928
|
+
}
|
|
68929
|
+
return steps.map((step, order) => ({ ...step, order }));
|
|
68777
68930
|
}
|
|
68778
68931
|
function parseHealVerdict(content) {
|
|
68779
68932
|
const cleaned = stripCodeFences(content);
|
|
@@ -72994,6 +73147,48 @@ var require_snapshot_observation = __commonJS({
|
|
|
72994
73147
|
var PROMOTED_FORM_FIELD_CAP = 12;
|
|
72995
73148
|
var FORM_MEMBER_CONTAINER_SEGMENT_RE = /(?:^|>\s*)(?:form|group)\b/i;
|
|
72996
73149
|
var DATA_GRID_CONTAINER_SEGMENT_RE = /(?:^|>\s*)(?:table|grid|rowgroup|row|gridcell|cell)\b/i;
|
|
73150
|
+
var DEEP_NONNAV_CAP_CENSUS = 16;
|
|
73151
|
+
var CENSUS_NAV_LINK_CAP = 60;
|
|
73152
|
+
var CENSUS_MIN_ACTION_GROUP = 3;
|
|
73153
|
+
var DESTRUCTIVE_ACTION_VERB_RE = /^(?:delete|remove|destroy|suspend|deactivate|disable|revoke|ban|block|archive|terminate|reject|cancel|reset|purge|wipe|unpublish|unsubscribe)$/i;
|
|
73154
|
+
function leadingActionVerb(label) {
|
|
73155
|
+
if (!label)
|
|
73156
|
+
return null;
|
|
73157
|
+
const m = label.trim().match(/[A-Za-z]+/);
|
|
73158
|
+
return m ? m[0].toLowerCase() : null;
|
|
73159
|
+
}
|
|
73160
|
+
function isNavLinkEntry(entry) {
|
|
73161
|
+
return entry.element.elementType === "LINK" && !!entry.element.triggersNavigation;
|
|
73162
|
+
}
|
|
73163
|
+
function isRowRepeatedControl(element) {
|
|
73164
|
+
return !!element.enrichment?.rowContext || !!element.containerPath && DATA_GRID_CONTAINER_SEGMENT_RE.test(element.containerPath);
|
|
73165
|
+
}
|
|
73166
|
+
function rowContainerScope(containerPath) {
|
|
73167
|
+
if (!containerPath)
|
|
73168
|
+
return null;
|
|
73169
|
+
const m = containerPath.match(/^(.*?)\s*>\s*row\b/i);
|
|
73170
|
+
return m ? m[1].trim() : containerPath.trim();
|
|
73171
|
+
}
|
|
73172
|
+
function containerScopeLabel(scope) {
|
|
73173
|
+
if (!scope)
|
|
73174
|
+
return null;
|
|
73175
|
+
const landmarks = [...scope.matchAll(/(?:table|grid|treegrid|list|listbox|tree|rowgroup):([^>]+)/gi)];
|
|
73176
|
+
if (landmarks.length > 0)
|
|
73177
|
+
return landmarks[landmarks.length - 1][1].trim();
|
|
73178
|
+
const segs = scope.split(">").map((s) => s.trim()).filter(Boolean);
|
|
73179
|
+
return segs.length ? segs[segs.length - 1] : null;
|
|
73180
|
+
}
|
|
73181
|
+
function rowIdentityKey(element) {
|
|
73182
|
+
const rc = element.enrichment?.rowContext;
|
|
73183
|
+
if (rc)
|
|
73184
|
+
return `rc:${rc}`;
|
|
73185
|
+
const cp = element.containerPath;
|
|
73186
|
+
if (cp) {
|
|
73187
|
+
const m = cp.match(/>\s*row:(.+)$/i);
|
|
73188
|
+
return m ? `row:${m[1].trim()}` : `cp:${cp}`;
|
|
73189
|
+
}
|
|
73190
|
+
return element.ref ? `ref:${element.ref}` : "unknown";
|
|
73191
|
+
}
|
|
72997
73192
|
function isGroupedFormField(element) {
|
|
72998
73193
|
if (!FORM_CONTROL_ROLE_RE.test(element.role ?? ""))
|
|
72999
73194
|
return false;
|
|
@@ -73009,6 +73204,7 @@ var require_snapshot_observation = __commonJS({
|
|
|
73009
73204
|
const sourceKind = page.snapshotMeta?.sourceKind ?? page.snapshotArtifact?.sourceKind ?? "missing";
|
|
73010
73205
|
const artifact = page.snapshotArtifact;
|
|
73011
73206
|
const richRender = options.richRender ?? process.env.DISCOVERY_SNAPSHOT_RICH_RENDER === "true";
|
|
73207
|
+
const rowActionCensus = richRender && options.mode !== "survey" && (options.rowActionCensus ?? process.env.DISCOVERY_SNAPSHOT_ROW_CENSUS === "true");
|
|
73012
73208
|
const activeScope = inferActiveSnapshotScope(page);
|
|
73013
73209
|
const ranked = rankSnapshotElements(getScopedSnapshotElements(page, activeScope), withScopeContext({ ...context, richRank: richRender }, activeScope));
|
|
73014
73210
|
const allInteractive = ranked.filter(({ element }) => isInteractiveCandidate(element)).filter(({ element }) => {
|
|
@@ -73022,10 +73218,17 @@ var require_snapshot_observation = __commonJS({
|
|
|
73022
73218
|
});
|
|
73023
73219
|
let interactive;
|
|
73024
73220
|
if (options.mode === "survey") {
|
|
73025
|
-
const navLinks = allInteractive.filter(
|
|
73026
|
-
const others = allInteractive.filter((
|
|
73221
|
+
const navLinks = allInteractive.filter(isNavLinkEntry);
|
|
73222
|
+
const others = allInteractive.filter((entry) => !isNavLinkEntry(entry));
|
|
73027
73223
|
const othersCap = richRender ? 6 : 4;
|
|
73028
73224
|
interactive = [...navLinks, ...others.slice(0, othersCap)];
|
|
73225
|
+
} else if (rowActionCensus) {
|
|
73226
|
+
const navLinks = allInteractive.filter(isNavLinkEntry);
|
|
73227
|
+
const others = allInteractive.filter((entry) => !isNavLinkEntry(entry));
|
|
73228
|
+
interactive = [
|
|
73229
|
+
...navLinks.slice(0, CENSUS_NAV_LINK_CAP),
|
|
73230
|
+
...others.slice(0, options.elementLimit ?? DEEP_NONNAV_CAP_CENSUS)
|
|
73231
|
+
];
|
|
73029
73232
|
} else {
|
|
73030
73233
|
interactive = allInteractive.slice(0, options.elementLimit ?? (richRender ? 8 : 6));
|
|
73031
73234
|
}
|
|
@@ -73038,6 +73241,36 @@ var require_snapshot_observation = __commonJS({
|
|
|
73038
73241
|
}
|
|
73039
73242
|
}
|
|
73040
73243
|
}
|
|
73244
|
+
if (rowActionCensus && interactive.length > 0) {
|
|
73245
|
+
const shownVerbs = new Set(interactive.filter((entry) => !isNavLinkEntry(entry) && isRowRepeatedControl(entry.element)).map(({ element }) => leadingActionVerb(qualifyElementLabel(element))).filter(Boolean));
|
|
73246
|
+
const bestByVerb = /* @__PURE__ */ new Map();
|
|
73247
|
+
for (const entry of allInteractive) {
|
|
73248
|
+
if (isNavLinkEntry(entry) || !isRowRepeatedControl(entry.element))
|
|
73249
|
+
continue;
|
|
73250
|
+
const verb = leadingActionVerb(qualifyElementLabel(entry.element));
|
|
73251
|
+
if (!verb || !DESTRUCTIVE_ACTION_VERB_RE.test(verb))
|
|
73252
|
+
continue;
|
|
73253
|
+
if (shownVerbs.has(verb))
|
|
73254
|
+
continue;
|
|
73255
|
+
if (interactive.includes(entry))
|
|
73256
|
+
continue;
|
|
73257
|
+
const existing = bestByVerb.get(verb);
|
|
73258
|
+
if (!existing) {
|
|
73259
|
+
bestByVerb.set(verb, entry);
|
|
73260
|
+
continue;
|
|
73261
|
+
}
|
|
73262
|
+
if (existing.element.isDisabled && !entry.element.isDisabled)
|
|
73263
|
+
bestByVerb.set(verb, entry);
|
|
73264
|
+
}
|
|
73265
|
+
const swapIns = [...bestByVerb.values()];
|
|
73266
|
+
if (swapIns.length > 0) {
|
|
73267
|
+
const navShown = interactive.filter(isNavLinkEntry);
|
|
73268
|
+
const nonNavShown = interactive.filter((entry) => !isNavLinkEntry(entry));
|
|
73269
|
+
const evictable = Math.min(swapIns.length, nonNavShown.length);
|
|
73270
|
+
const trimmed = nonNavShown.slice(0, nonNavShown.length - evictable);
|
|
73271
|
+
interactive = [...navShown, ...trimmed, ...swapIns.slice(0, evictable)];
|
|
73272
|
+
}
|
|
73273
|
+
}
|
|
73041
73274
|
const duplicates = detectDuplicateRoleNameGroups(duplicateScanCandidates);
|
|
73042
73275
|
const allForms = artifact?.formGroups ?? page.formGroups ?? [];
|
|
73043
73276
|
const forms = allForms.slice(0, options.formLimit ?? 3);
|
|
@@ -73337,6 +73570,54 @@ var require_snapshot_observation = __commonJS({
|
|
|
73337
73570
|
const hidden = allInteractive.length - interactive.length;
|
|
73338
73571
|
if (hidden > 0) {
|
|
73339
73572
|
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.`);
|
|
73573
|
+
if (rowActionCensus) {
|
|
73574
|
+
const shownSet = new Set(interactive.map((entry) => entry.element));
|
|
73575
|
+
const groups = /* @__PURE__ */ new Map();
|
|
73576
|
+
for (const entry of allInteractive) {
|
|
73577
|
+
const el = entry.element;
|
|
73578
|
+
if (!isRowRepeatedControl(el))
|
|
73579
|
+
continue;
|
|
73580
|
+
const verb = leadingActionVerb(qualifyElementLabel(el));
|
|
73581
|
+
if (!verb)
|
|
73582
|
+
continue;
|
|
73583
|
+
const role = (el.role ?? el.elementType ?? "control").toLowerCase();
|
|
73584
|
+
const scope = rowContainerScope(el.containerPath);
|
|
73585
|
+
const key = JSON.stringify([scope ?? "", role, verb]);
|
|
73586
|
+
let group = groups.get(key);
|
|
73587
|
+
if (!group) {
|
|
73588
|
+
group = { verb, role, scopeLabel: containerScopeLabel(scope), rows: /* @__PURE__ */ new Set(), shownRows: /* @__PURE__ */ new Set(), disabledRows: /* @__PURE__ */ new Set(), controls: 0 };
|
|
73589
|
+
groups.set(key, group);
|
|
73590
|
+
}
|
|
73591
|
+
const rowKey = rowIdentityKey(el);
|
|
73592
|
+
group.rows.add(rowKey);
|
|
73593
|
+
group.controls += 1;
|
|
73594
|
+
if (shownSet.has(el))
|
|
73595
|
+
group.shownRows.add(rowKey);
|
|
73596
|
+
if (el.isDisabled)
|
|
73597
|
+
group.disabledRows.add(rowKey);
|
|
73598
|
+
}
|
|
73599
|
+
const collapsed = [...groups.values()].map((g) => ({
|
|
73600
|
+
verb: g.verb,
|
|
73601
|
+
role: g.role,
|
|
73602
|
+
scopeLabel: g.scopeLabel,
|
|
73603
|
+
total: g.rows.size,
|
|
73604
|
+
shown: g.shownRows.size,
|
|
73605
|
+
disabled: g.disabledRows.size,
|
|
73606
|
+
controls: g.controls
|
|
73607
|
+
})).filter((g) => g.total >= CENSUS_MIN_ACTION_GROUP && g.total > g.shown).sort((a, b) => {
|
|
73608
|
+
const aDestructive = DESTRUCTIVE_ACTION_VERB_RE.test(a.verb) ? 0 : 1;
|
|
73609
|
+
const bDestructive = DESTRUCTIVE_ACTION_VERB_RE.test(b.verb) ? 0 : 1;
|
|
73610
|
+
return aDestructive - bDestructive || b.total - b.shown - (a.total - a.shown);
|
|
73611
|
+
});
|
|
73612
|
+
for (const g of collapsed) {
|
|
73613
|
+
const verbLabel = g.verb.charAt(0).toUpperCase() + g.verb.slice(1);
|
|
73614
|
+
const destructiveTag = DESTRUCTIVE_ACTION_VERB_RE.test(g.verb) ? " [destructive]" : "";
|
|
73615
|
+
const where = g.scopeLabel ? ` in ${yamlScalar(g.scopeLabel)}` : "";
|
|
73616
|
+
const controlsNote = g.controls > g.total ? ` (${g.controls} controls across ${g.total} rows)` : "";
|
|
73617
|
+
const disabledNote = g.disabled > 0 ? `; ${g.disabled} disabled at capture` : "";
|
|
73618
|
+
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.`);
|
|
73619
|
+
}
|
|
73620
|
+
}
|
|
73340
73621
|
}
|
|
73341
73622
|
}
|
|
73342
73623
|
}
|
|
@@ -74359,13 +74640,14 @@ var require_auth_recipe = __commonJS({
|
|
|
74359
74640
|
"../runner-core/dist/services/auth-recipe.js"(exports2) {
|
|
74360
74641
|
"use strict";
|
|
74361
74642
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
74362
|
-
exports2.AUTH_MARKER_VERSION = void 0;
|
|
74643
|
+
exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = void 0;
|
|
74363
74644
|
exports2.computeRecipeHash = computeRecipeHash;
|
|
74364
74645
|
exports2.buildAuthRecipe = buildAuthRecipe;
|
|
74365
74646
|
exports2.authProofLocator = authProofLocator;
|
|
74366
74647
|
exports2.urlProofPredicateExpr = urlProofPredicateExpr;
|
|
74367
74648
|
exports2.renderAuthProofAssertion = renderAuthProofAssertion;
|
|
74368
74649
|
exports2.renderAuthFn = renderAuthFn;
|
|
74650
|
+
exports2.isReservedRunnerEnvVar = isReservedRunnerEnvVar;
|
|
74369
74651
|
exports2.absoluteRoute = absoluteRoute;
|
|
74370
74652
|
exports2.renderReplayClickArrow = renderReplayClickArrow;
|
|
74371
74653
|
exports2.renderReplaySettleArrow = renderReplaySettleArrow;
|
|
@@ -74484,6 +74766,17 @@ var require_auth_recipe = __commonJS({
|
|
|
74484
74766
|
lines.push(END_MARKER);
|
|
74485
74767
|
return lines.join("\n");
|
|
74486
74768
|
}
|
|
74769
|
+
exports2.RESERVED_RUNNER_ENV_VARS = /* @__PURE__ */ new Set([
|
|
74770
|
+
"AUTH_FILE",
|
|
74771
|
+
"SHARED_AUTH_INJECT",
|
|
74772
|
+
"VALIDATE_RUN_ID",
|
|
74773
|
+
"VALIDATE_TASK_ARN",
|
|
74774
|
+
"VALIDATE_RUNNER_AUTH",
|
|
74775
|
+
"VALIDATE_SERVER_URL"
|
|
74776
|
+
]);
|
|
74777
|
+
function isReservedRunnerEnvVar(key) {
|
|
74778
|
+
return key.startsWith("VALIDATE_") || exports2.RESERVED_RUNNER_ENV_VARS.has(key);
|
|
74779
|
+
}
|
|
74487
74780
|
function absoluteRoute(route2, baseUrl) {
|
|
74488
74781
|
try {
|
|
74489
74782
|
return new URL(route2, baseUrl).href;
|
|
@@ -315588,9 +315881,15 @@ var require_mobile_discovery_agent = __commonJS({
|
|
|
315588
315881
|
log2(`
|
|
315589
315882
|
Explore complete: ${explore.exploreStats.pagesDiscovered} screens, ${explore.exploreStats.elementsFound} elements, ${explore.exploreStats.transitionsFound} transitions (${exploreElapsed}s)`);
|
|
315590
315883
|
if (callbacks?.onExploreComplete) {
|
|
315591
|
-
|
|
315592
|
-
|
|
315593
|
-
})
|
|
315884
|
+
try {
|
|
315885
|
+
await callbacks.onExploreComplete(toExploreCompleteData(explore, collectedTransitions));
|
|
315886
|
+
} catch (err) {
|
|
315887
|
+
const message = checkpointErrorMessage(err);
|
|
315888
|
+
log2(`[stream] explore-complete callback failed: ${message}`);
|
|
315889
|
+
if (isTerminalDiscoveryCheckpointError(err)) {
|
|
315890
|
+
throw err;
|
|
315891
|
+
}
|
|
315892
|
+
}
|
|
315594
315893
|
}
|
|
315595
315894
|
if (!isSurvey && shouldPauseAfterTruncatedExplore(explore)) {
|
|
315596
315895
|
const duration2 = (Date.now() - startTime) / 1e3;
|
|
@@ -315618,8 +315917,10 @@ Explore complete: ${explore.exploreStats.pagesDiscovered} screens, ${explore.exp
|
|
|
315618
315917
|
pausePhase: "explore"
|
|
315619
315918
|
};
|
|
315620
315919
|
}
|
|
315621
|
-
|
|
315622
|
-
|
|
315920
|
+
if (!isSurvey) {
|
|
315921
|
+
log2("\nRelaunching app for a clean state before planning/generation...");
|
|
315922
|
+
await driver.relaunch();
|
|
315923
|
+
}
|
|
315623
315924
|
log2("\n\u2500\u2500 Phase 2: PLAN \u2500\u2500");
|
|
315624
315925
|
const graph = state2.toScreenGraph();
|
|
315625
315926
|
const screenMap = state2.buildInMemoryScreenMap();
|
|
@@ -315642,7 +315943,9 @@ Explore complete: ${explore.exploreStats.pagesDiscovered} screens, ${explore.exp
|
|
|
315642
315943
|
hasPassword: !!ctx.variableContext?.allTestVariables?.TEST_USER_PASSWORD || !!ctx.variableContext?.publicTestVariables?.TEST_USER_PASSWORD
|
|
315643
315944
|
},
|
|
315644
315945
|
incremental: ctx.incremental,
|
|
315645
|
-
testBudget
|
|
315946
|
+
testBudget,
|
|
315947
|
+
userNotes: ctx.userNotes,
|
|
315948
|
+
featureContext: ctx.featureContext
|
|
315646
315949
|
}, {
|
|
315647
315950
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
315648
315951
|
onAICall
|
|
@@ -315815,10 +316118,20 @@ Fatal error: ${msg}`);
|
|
|
315815
316118
|
log2(`App-data reset failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
315816
316119
|
return false;
|
|
315817
316120
|
}) ?? false;
|
|
315818
|
-
if (
|
|
315819
|
-
|
|
316121
|
+
if (cleared)
|
|
316122
|
+
return true;
|
|
316123
|
+
if (resetPolicy === "CLEAR_APP_DATA") {
|
|
316124
|
+
log2("CLEAR_APP_DATA requested but app-data reset was unavailable; a post-explore relaunch will reset state before planning.");
|
|
316125
|
+
}
|
|
316126
|
+
if (mode !== "survey") {
|
|
316127
|
+
return false;
|
|
315820
316128
|
}
|
|
315821
|
-
|
|
316129
|
+
try {
|
|
316130
|
+
await driver.relaunch();
|
|
316131
|
+
} catch (err) {
|
|
316132
|
+
log2(`App relaunch failed after unsuccessful app-data reset: ${err instanceof Error ? err.message : String(err)}`);
|
|
316133
|
+
}
|
|
316134
|
+
return false;
|
|
315822
316135
|
}
|
|
315823
316136
|
function shouldPauseAfterTruncatedExplore(explore) {
|
|
315824
316137
|
return explore.truncated === true && (explore.truncatedReason === "rate_limit" || explore.truncatedReason === "timeout");
|
|
@@ -315865,6 +316178,7 @@ var require_discover_agent = __commonJS({
|
|
|
315865
316178
|
var claude_discovery_runner_js_1 = require_claude_discovery_runner();
|
|
315866
316179
|
var agentic_cli_constants_js_1 = require_agentic_cli_constants();
|
|
315867
316180
|
var canvas_detector_js_1 = require_canvas_detector();
|
|
316181
|
+
var auth_recipe_js_1 = require_auth_recipe();
|
|
315868
316182
|
async function runCanvasPreflight(ctx, sharedBrowser, log2) {
|
|
315869
316183
|
const cached = ctx.appRenderType ?? null;
|
|
315870
316184
|
const engine = (0, agentic_cli_constants_js_1.getAgenticEngine)(ctx.exploreModel);
|
|
@@ -316560,7 +316874,7 @@ Health: ${healthSummary.totalIssues} issue(s) - ${healthSummary.consoleErrors.le
|
|
|
316560
316874
|
}
|
|
316561
316875
|
if (envRefs.size > 0) {
|
|
316562
316876
|
const existingKeys = new Set(ctx.variableContext.availableVariableKeys);
|
|
316563
|
-
const missing = [...envRefs].filter((k) => !existingKeys.has(k));
|
|
316877
|
+
const missing = [...envRefs].filter((k) => !existingKeys.has(k) && !(0, auth_recipe_js_1.isReservedRunnerEnvVar)(k));
|
|
316564
316878
|
if (missing.length > 0) {
|
|
316565
316879
|
autoVariableDefaults = {};
|
|
316566
316880
|
for (const key of missing) {
|
|
@@ -317076,7 +317390,7 @@ Health: ${healthSummary.totalIssues} issue(s) - ${healthSummary.consoleErrors.le
|
|
|
317076
317390
|
envRefs.add(match[1]);
|
|
317077
317391
|
if (envRefs.size > 0) {
|
|
317078
317392
|
const existingKeys = new Set(ctx.variableContext.availableVariableKeys);
|
|
317079
|
-
const missing = [...envRefs].filter((k) => !existingKeys.has(k));
|
|
317393
|
+
const missing = [...envRefs].filter((k) => !existingKeys.has(k) && !(0, auth_recipe_js_1.isReservedRunnerEnvVar)(k));
|
|
317080
317394
|
if (missing.length > 0) {
|
|
317081
317395
|
autoVariableDefaults = {};
|
|
317082
317396
|
for (const key of missing) {
|
|
@@ -329958,9 +330272,9 @@ var require_dist2 = __commonJS({
|
|
|
329958
330272
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
329959
330273
|
};
|
|
329960
330274
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
329961
|
-
exports2.
|
|
329962
|
-
exports2.
|
|
329963
|
-
exports2.UNSUPPORTED_RENDERER_PREFIX = void 0;
|
|
330275
|
+
exports2.buildImportContractMd = exports2.MAX_ARCHIVE_BYTES = exports2.isUnsafeEntryName = exports2.extractZipSafely = exports2.downloadArchiveCapped = exports2.detectArchiveKind = exports2.acquireRepoForImport = exports2.runFrameworkImportOnRunner = exports2.getAgenticEngine = exports2.resolveRefFromSnapshot = exports2.replayAuthRecipeIntoBrowser = exports2.runRegisterCaptureRun = exports2.runLoginCaptureRun = 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.canonicalizeRoute = 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.runMobileGeneratePhase = exports2.runMobileAIPlanner = exports2.buildMobileSiteMap = exports2.formatMobileEvidenceForPlanner = exports2.MOBILE_SCENARIO_SYSTEM_PROMPT = exports2.MOBILE_SURVEY_SYSTEM_PROMPT = exports2.runMobileExplorePhase = void 0;
|
|
330276
|
+
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 = 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.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.IMPORT_SCHEMA_VERSION = exports2.validateImportResultFile = exports2.validateImportedTest = exports2.buildImportSystemRules = exports2.buildImportKickoff = void 0;
|
|
330277
|
+
exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = void 0;
|
|
329964
330278
|
__exportStar(require_ai_provider(), exports2);
|
|
329965
330279
|
__exportStar(require_mobile_types(), exports2);
|
|
329966
330280
|
__exportStar(require_mobile_source_parser(), exports2);
|
|
@@ -330097,6 +330411,12 @@ var require_dist2 = __commonJS({
|
|
|
330097
330411
|
Object.defineProperty(exports2, "AUTH_MARKER_VERSION", { enumerable: true, get: function() {
|
|
330098
330412
|
return auth_recipe_js_1.AUTH_MARKER_VERSION;
|
|
330099
330413
|
} });
|
|
330414
|
+
Object.defineProperty(exports2, "RESERVED_RUNNER_ENV_VARS", { enumerable: true, get: function() {
|
|
330415
|
+
return auth_recipe_js_1.RESERVED_RUNNER_ENV_VARS;
|
|
330416
|
+
} });
|
|
330417
|
+
Object.defineProperty(exports2, "isReservedRunnerEnvVar", { enumerable: true, get: function() {
|
|
330418
|
+
return auth_recipe_js_1.isReservedRunnerEnvVar;
|
|
330419
|
+
} });
|
|
330100
330420
|
var auth_capture_run_js_1 = require_auth_capture_run();
|
|
330101
330421
|
Object.defineProperty(exports2, "runLoginCaptureRun", { enumerable: true, get: function() {
|
|
330102
330422
|
return auth_capture_run_js_1.runLoginCaptureRun;
|
|
@@ -330324,7 +330644,7 @@ var require_package4 = __commonJS({
|
|
|
330324
330644
|
"package.json"(exports2, module2) {
|
|
330325
330645
|
module2.exports = {
|
|
330326
330646
|
name: "@validate.qa/runner",
|
|
330327
|
-
version: "1.0.
|
|
330647
|
+
version: "1.0.33",
|
|
330328
330648
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
330329
330649
|
bin: {
|
|
330330
330650
|
"validate-runner": "dist/cli.js"
|
|
@@ -330940,25 +331260,53 @@ async function resolveTypeTargetElementId(sessionPath, bounds) {
|
|
|
330940
331260
|
}
|
|
330941
331261
|
return elementId;
|
|
330942
331262
|
}
|
|
331263
|
+
async function sendKeysToFocusedInput(sessionPath, value) {
|
|
331264
|
+
await appiumRequest(`${sessionPath}/keys`, {
|
|
331265
|
+
method: "POST",
|
|
331266
|
+
body: JSON.stringify({ text: value, value: Array.from(value) })
|
|
331267
|
+
});
|
|
331268
|
+
}
|
|
330943
331269
|
async function typeText(sessionPath, value, bounds) {
|
|
330944
331270
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
330945
|
-
if (
|
|
330946
|
-
|
|
331271
|
+
if (elementId) {
|
|
331272
|
+
try {
|
|
331273
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/value`, {
|
|
331274
|
+
method: "POST",
|
|
331275
|
+
body: JSON.stringify({ text: value })
|
|
331276
|
+
});
|
|
331277
|
+
return;
|
|
331278
|
+
} catch (err) {
|
|
331279
|
+
if (isAppiumDriverDeadError(err)) throw err;
|
|
331280
|
+
}
|
|
330947
331281
|
}
|
|
330948
|
-
await
|
|
330949
|
-
method: "POST",
|
|
330950
|
-
body: JSON.stringify({ text: value })
|
|
330951
|
-
});
|
|
331282
|
+
await sendKeysToFocusedInput(sessionPath, value);
|
|
330952
331283
|
}
|
|
330953
331284
|
async function clearField(sessionPath, bounds) {
|
|
330954
331285
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
330955
|
-
if (
|
|
330956
|
-
|
|
331286
|
+
if (elementId) {
|
|
331287
|
+
try {
|
|
331288
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/clear`, {
|
|
331289
|
+
method: "POST",
|
|
331290
|
+
body: JSON.stringify({})
|
|
331291
|
+
});
|
|
331292
|
+
return;
|
|
331293
|
+
} catch (err) {
|
|
331294
|
+
if (isAppiumDriverDeadError(err)) throw err;
|
|
331295
|
+
}
|
|
331296
|
+
}
|
|
331297
|
+
try {
|
|
331298
|
+
await appiumRequest(`${sessionPath}/keys`, {
|
|
331299
|
+
method: "POST",
|
|
331300
|
+
// Common select-all + delete sequence is platform-dependent; empty keys
|
|
331301
|
+
// after focus is the portable baseline Appium accepts for many hybrids.
|
|
331302
|
+
body: JSON.stringify({ text: "", value: [] })
|
|
331303
|
+
});
|
|
331304
|
+
} catch (err) {
|
|
331305
|
+
if (isAppiumDriverDeadError(err)) throw err;
|
|
331306
|
+
throw new Error(
|
|
331307
|
+
`No focused element available for CLEAR. Tap an input field first.${err instanceof Error ? ` (${err.message})` : ""}`
|
|
331308
|
+
);
|
|
330957
331309
|
}
|
|
330958
|
-
await appiumRequest(`${sessionPath}/element/${elementId}/clear`, {
|
|
330959
|
-
method: "POST",
|
|
330960
|
-
body: JSON.stringify({})
|
|
330961
|
-
});
|
|
330962
331310
|
}
|
|
330963
331311
|
async function swipe(sessionPath, direction, distance, windowSize) {
|
|
330964
331312
|
const dist = clampMobileSwipeDistance(distance);
|
|
@@ -332196,6 +332544,10 @@ async function startMobileNetworkCapture(options) {
|
|
|
332196
332544
|
|
|
332197
332545
|
// src/services/appium-executor.ts
|
|
332198
332546
|
var DEFAULT_STEP_TIMEOUT_MS = 1e4;
|
|
332547
|
+
var ELEMENT_ID_PATTERN2 = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
332548
|
+
function validateElementId(elementId) {
|
|
332549
|
+
return typeof elementId === "string" && ELEMENT_ID_PATTERN2.test(elementId) ? elementId : null;
|
|
332550
|
+
}
|
|
332199
332551
|
var MOBILE_NEW_COMMAND_TIMEOUT_SECONDS = (() => {
|
|
332200
332552
|
const raw = Number(process.env.VALIDATEQA_MOBILE_NEW_COMMAND_TIMEOUT);
|
|
332201
332553
|
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 600;
|
|
@@ -332330,6 +332682,9 @@ async function createSession(target) {
|
|
|
332330
332682
|
});
|
|
332331
332683
|
const sessionId = result?.value?.sessionId ?? result?.sessionId;
|
|
332332
332684
|
if (!sessionId) throw new Error("Appium session creation returned no session ID");
|
|
332685
|
+
if (target.platform === "ANDROID") {
|
|
332686
|
+
await prepareAndroidInputEnvironment(target.deviceId);
|
|
332687
|
+
}
|
|
332333
332688
|
return sessionId;
|
|
332334
332689
|
}
|
|
332335
332690
|
var TERMINAL_SESSION_START = /not installed|no such (app|file)|could not confirm|bundle ?id .*(not|missing)|xcodeorgid|signing|provisioning|not authorized|unauthorized/i;
|
|
@@ -332717,7 +333072,9 @@ async function findElement(sessionId, target, timeoutMs = DEFAULT_STEP_TIMEOUT_M
|
|
|
332717
333072
|
method: "POST",
|
|
332718
333073
|
body: JSON.stringify({ using: strategy.using, value: strategy.value })
|
|
332719
333074
|
});
|
|
332720
|
-
const elementId =
|
|
333075
|
+
const elementId = validateElementId(
|
|
333076
|
+
result?.value?.ELEMENT || result?.value?.["element-6066-11e4-a52e-4f735466cecf"]
|
|
333077
|
+
);
|
|
332721
333078
|
if (elementId) return elementId;
|
|
332722
333079
|
} catch (err) {
|
|
332723
333080
|
if (isTransportError(err)) throw err;
|
|
@@ -332757,24 +333114,25 @@ async function tapCoordinates(sessionId, bounds) {
|
|
|
332757
333114
|
})
|
|
332758
333115
|
});
|
|
332759
333116
|
}
|
|
332760
|
-
async function getActiveElementId2(sessionId) {
|
|
332761
|
-
try {
|
|
332762
|
-
const activeEl = await wdRequest(`/session/${sessionId}/element/active`);
|
|
332763
|
-
return activeEl?.value?.ELEMENT || activeEl?.value?.["element-6066-11e4-a52e-4f735466cecf"] || null;
|
|
332764
|
-
} catch {
|
|
332765
|
-
return null;
|
|
332766
|
-
}
|
|
332767
|
-
}
|
|
332768
|
-
async function sendKeysToFocusedInput(sessionId, value) {
|
|
332769
|
-
await wdRequest(`/session/${sessionId}/keys`, {
|
|
332770
|
-
method: "POST",
|
|
332771
|
-
body: JSON.stringify({ text: value, value: Array.from(value) })
|
|
332772
|
-
});
|
|
332773
|
-
}
|
|
332774
333117
|
function encodeAndroidInputText(value) {
|
|
332775
333118
|
const inputEncoded = value.replace(/%/g, "%25").replace(/\s/g, "%s");
|
|
332776
333119
|
return `'${inputEncoded.replace(/'/g, `'\\''`)}'`;
|
|
332777
333120
|
}
|
|
333121
|
+
async function prepareAndroidInputEnvironment(deviceId) {
|
|
333122
|
+
if (!deviceId) return;
|
|
333123
|
+
const settingsToDisable = [
|
|
333124
|
+
["secure", "stylus_handwriting_enabled", "0"]
|
|
333125
|
+
];
|
|
333126
|
+
for (const [namespace, key, value] of settingsToDisable) {
|
|
333127
|
+
try {
|
|
333128
|
+
await execFileAsync2("adb", ["-s", deviceId, "shell", "settings", "put", namespace, key, value], {
|
|
333129
|
+
timeout: 8e3,
|
|
333130
|
+
windowsHide: true
|
|
333131
|
+
});
|
|
333132
|
+
} catch {
|
|
333133
|
+
}
|
|
333134
|
+
}
|
|
333135
|
+
}
|
|
332778
333136
|
async function sendAndroidTextToFocusedInput(deviceId, value) {
|
|
332779
333137
|
if (!deviceId) {
|
|
332780
333138
|
throw new Error("Android keyboard fallback requires a device id");
|
|
@@ -332836,100 +333194,28 @@ async function executeType(sessionId, step, context) {
|
|
|
332836
333194
|
if (step.value === void 0 || step.value === null || step.value === "") return;
|
|
332837
333195
|
const value = String(step.value);
|
|
332838
333196
|
const bounds = boundsForRun(step, context.platform, context.isCrossPlatform ?? false);
|
|
332839
|
-
|
|
332840
|
-
|
|
332841
|
-
|
|
332842
|
-
|
|
332843
|
-
|
|
332844
|
-
|
|
332845
|
-
|
|
332846
|
-
|
|
332847
|
-
|
|
332848
|
-
|
|
332849
|
-
|
|
332850
|
-
|
|
332851
|
-
|
|
332852
|
-
|
|
332853
|
-
|
|
332854
|
-
try {
|
|
332855
|
-
elementId = await findElement(sessionId, step.target, readStepTimeout(step), { platform: context.platform });
|
|
332856
|
-
lastInputError = await trySendElementValue(sessionId, elementId, value);
|
|
332857
|
-
if (!lastInputError) return;
|
|
332858
|
-
elementId = null;
|
|
332859
|
-
} catch (error2) {
|
|
332860
|
-
if (isTransportError(error2) || !bounds || buildLocatorStrategies(step.target, context.platform).length > 0) throw error2;
|
|
332861
|
-
lastInputError = error2;
|
|
332862
|
-
}
|
|
332863
|
-
}
|
|
332864
|
-
if (!elementId && bounds) {
|
|
332865
|
-
await tapCoordinates(sessionId, bounds);
|
|
332866
|
-
tappedBounds = true;
|
|
332867
|
-
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
332868
|
-
elementId = await getActiveElementId2(sessionId);
|
|
332869
|
-
}
|
|
332870
|
-
if (!elementId) {
|
|
332871
|
-
elementId = await getActiveElementId2(sessionId);
|
|
332872
|
-
}
|
|
332873
|
-
if (elementId) {
|
|
332874
|
-
lastInputError = await trySendElementValue(sessionId, elementId, value);
|
|
332875
|
-
if (!lastInputError) return;
|
|
332876
|
-
elementId = null;
|
|
332877
|
-
if (!bounds) {
|
|
332878
|
-
throw lastInputError instanceof Error ? lastInputError : new Error(String(lastInputError));
|
|
332879
|
-
}
|
|
332880
|
-
}
|
|
332881
|
-
if (bounds) {
|
|
332882
|
-
if (!tappedBounds) {
|
|
332883
|
-
await tapCoordinates(sessionId, bounds);
|
|
332884
|
-
tappedBounds = true;
|
|
332885
|
-
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
332886
|
-
}
|
|
332887
|
-
try {
|
|
332888
|
-
await sendKeysToFocusedInput(sessionId, value);
|
|
332889
|
-
return;
|
|
332890
|
-
} catch (error2) {
|
|
332891
|
-
if (isTransportError(error2)) throw error2;
|
|
332892
|
-
lastInputError = error2;
|
|
332893
|
-
}
|
|
332894
|
-
}
|
|
332895
|
-
if (context.platform === "ANDROID" && tappedBounds) {
|
|
332896
|
-
try {
|
|
332897
|
-
context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
|
|
332898
|
-
await sendAndroidTextToFocusedInput(context.deviceId, value);
|
|
332899
|
-
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
332900
|
-
return;
|
|
332901
|
-
} catch (error2) {
|
|
332902
|
-
if (isTransportError(error2)) throw error2;
|
|
332903
|
-
lastInputError = error2;
|
|
333197
|
+
const sessionPath = `/session/${sessionId}`;
|
|
333198
|
+
try {
|
|
333199
|
+
await typeText(sessionPath, value, bounds);
|
|
333200
|
+
return;
|
|
333201
|
+
} catch (error2) {
|
|
333202
|
+
if (isTransportError(error2)) throw error2;
|
|
333203
|
+
if (context.platform === "ANDROID" && bounds) {
|
|
333204
|
+
try {
|
|
333205
|
+
context.log?.(" Appium active-element send failed; using Android focused-keyboard input fallback");
|
|
333206
|
+
await sendAndroidTextToFocusedInput(context.deviceId, value);
|
|
333207
|
+
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
333208
|
+
return;
|
|
333209
|
+
} catch (androidError) {
|
|
333210
|
+
if (isTransportError(androidError)) throw androidError;
|
|
333211
|
+
}
|
|
332904
333212
|
}
|
|
333213
|
+
throw error2 instanceof Error ? error2 : new Error(String(error2));
|
|
332905
333214
|
}
|
|
332906
|
-
throw new Error(`TYPE step could not send text to the focused target${lastInputError instanceof Error ? `: ${lastInputError.message}` : ""}`);
|
|
332907
333215
|
}
|
|
332908
333216
|
async function executeClear(sessionId, step, platform3, isCrossPlatform = false) {
|
|
332909
333217
|
const bounds = boundsForRun(step, platform3, isCrossPlatform);
|
|
332910
|
-
|
|
332911
|
-
if (step.target) {
|
|
332912
|
-
try {
|
|
332913
|
-
elementId = await findElement(sessionId, step.target, readStepTimeout(step), { platform: platform3 });
|
|
332914
|
-
} catch (error2) {
|
|
332915
|
-
if (isTransportError(error2) || !bounds || buildLocatorStrategies(step.target, platform3).length > 0) throw error2;
|
|
332916
|
-
}
|
|
332917
|
-
}
|
|
332918
|
-
if (!elementId && bounds) {
|
|
332919
|
-
await tapCoordinates(sessionId, bounds);
|
|
332920
|
-
await new Promise((resolve) => setTimeout(resolve, MOBILE_FOCUS_SETTLE_MS));
|
|
332921
|
-
elementId = await getActiveElementId2(sessionId);
|
|
332922
|
-
}
|
|
332923
|
-
if (!elementId) {
|
|
332924
|
-
elementId = await getActiveElementId2(sessionId);
|
|
332925
|
-
}
|
|
332926
|
-
if (!elementId) {
|
|
332927
|
-
throw new Error("CLEAR step could not resolve a target or focused element");
|
|
332928
|
-
}
|
|
332929
|
-
await wdRequest(`/session/${sessionId}/element/${elementId}/clear`, {
|
|
332930
|
-
method: "POST",
|
|
332931
|
-
body: "{}"
|
|
332932
|
-
});
|
|
333218
|
+
await clearField(`/session/${sessionId}`, bounds);
|
|
332933
333219
|
}
|
|
332934
333220
|
async function executeSwipe(sessionId, step) {
|
|
332935
333221
|
const direction = step.metadata?.direction || "up";
|