@validate.qa/runner 1.0.82 → 1.0.83
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 +133 -196
- package/dist/cli.mjs +133 -196
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1019,8 +1019,7 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1019
1019
|
const isGenericView = /android\.view\.View$/i.test(role);
|
|
1020
1020
|
const moderateHitTarget = hasArea && bounds.width >= 24 && bounds.height >= 24 && bounds.width <= 2e3 && bounds.height <= 1800;
|
|
1021
1021
|
const webHybridLabelledControl = isGenericView && hasHumanIdentity && moderateHitTarget && (focusable || clickable || longClickable || Boolean(contentDesc));
|
|
1022
|
-
const
|
|
1023
|
-
const actionable = !isAnrChrome && enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
|
|
1022
|
+
const actionable = enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
|
|
1024
1023
|
return {
|
|
1025
1024
|
role,
|
|
1026
1025
|
label,
|
|
@@ -1198,7 +1197,6 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1198
1197
|
element.secure = fields.secure;
|
|
1199
1198
|
if (fields.enabled !== void 0)
|
|
1200
1199
|
element.enabled = fields.enabled;
|
|
1201
|
-
element.visible = fields.visible;
|
|
1202
1200
|
if (isCredibleStructuralNavigationTarget(element) && chainHasNativeNavigationSemantics(chain, platform3)) {
|
|
1203
1201
|
markNavigation(element, "native-navigation-semantics");
|
|
1204
1202
|
}
|
|
@@ -1573,8 +1571,6 @@ var require_mobile_boundary = __commonJS({
|
|
|
1573
1571
|
"../runner-core/dist/services/mobile/mobile-boundary.js"(exports2) {
|
|
1574
1572
|
"use strict";
|
|
1575
1573
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1576
|
-
exports2.isAndroidAnrSystemResourceId = isAndroidAnrSystemResourceId;
|
|
1577
|
-
exports2.looksLikeAndroidAnrUi = looksLikeAndroidAnrUi;
|
|
1578
1574
|
exports2.mobileOwnerFromScreenSignal = mobileOwnerFromScreenSignal;
|
|
1579
1575
|
exports2.isTransientMobileSystemOwner = isTransientMobileSystemOwner;
|
|
1580
1576
|
exports2.classifyMobileScreenBoundary = classifyMobileScreenBoundary2;
|
|
@@ -1592,28 +1588,6 @@ var require_mobile_boundary = __commonJS({
|
|
|
1592
1588
|
"com.miui.securitycenter",
|
|
1593
1589
|
"com.samsung.android.permissioncontroller"
|
|
1594
1590
|
]);
|
|
1595
|
-
var ANDROID_ANR_RESOURCE_ID_RE = /^(?:android:id\/)?(?:aerr_(?:wait|close|close_app|mute|report|restart)|alertTitle|button1|button2|button3|parentPanel|topPanel|contentPanel|customPanel|buttonPanel|scrollView|textSpacerNoButtons|textSpacerNoTitle)$/i;
|
|
1596
|
-
var ANDROID_ANR_TEXT_RE = /isn['’]?t responding|isn['’]?t responding\.|anr|close app|wait/i;
|
|
1597
|
-
function isAndroidAnrSystemResourceId(resourceId) {
|
|
1598
|
-
const rid = resourceId?.trim();
|
|
1599
|
-
if (!rid)
|
|
1600
|
-
return false;
|
|
1601
|
-
if (/(?:android:id\/)?aerr_/i.test(rid))
|
|
1602
|
-
return true;
|
|
1603
|
-
return ANDROID_ANR_RESOURCE_ID_RE.test(rid) && /aerr_/i.test(rid);
|
|
1604
|
-
}
|
|
1605
|
-
function looksLikeAndroidAnrUi(input) {
|
|
1606
|
-
if (isAndroidAnrSystemResourceId(input.resourceId))
|
|
1607
|
-
return true;
|
|
1608
|
-
const rid = input.resourceId?.trim() ?? "";
|
|
1609
|
-
const blob = [input.text, input.label, input.contentDesc].filter(Boolean).join(" ");
|
|
1610
|
-
if (/(?:android:id\/)?(?:alertTitle|aerr_)/i.test(rid) && ANDROID_ANR_TEXT_RE.test(blob)) {
|
|
1611
|
-
return true;
|
|
1612
|
-
}
|
|
1613
|
-
if (/isn['’]?t responding/i.test(blob))
|
|
1614
|
-
return true;
|
|
1615
|
-
return false;
|
|
1616
|
-
}
|
|
1617
1591
|
var IOS_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
|
|
1618
1592
|
"com.apple.springboard",
|
|
1619
1593
|
"com.apple.authenticationservicesui",
|
|
@@ -1771,7 +1745,7 @@ var init_constants = __esm({
|
|
|
1771
1745
|
MOBILE_APP_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,253}[A-Za-z0-9])?$/;
|
|
1772
1746
|
MOBILE_FOCUS_SETTLE_MS = 150;
|
|
1773
1747
|
MOBILE_POST_ACTION_SETTLE_MS = 1800;
|
|
1774
|
-
MOBILE_COLD_BOOT_SETTLE_MS =
|
|
1748
|
+
MOBILE_COLD_BOOT_SETTLE_MS = 12e3;
|
|
1775
1749
|
MOBILE_DEFAULT_SWIPE_DISTANCE = 500;
|
|
1776
1750
|
MOBILE_MAX_SWIPE_DISTANCE = 2e3;
|
|
1777
1751
|
SELECTOR_PRIORITIES = [
|
|
@@ -74913,7 +74887,7 @@ var require_mobile_explorer = __commonJS({
|
|
|
74913
74887
|
type: "function",
|
|
74914
74888
|
function: {
|
|
74915
74889
|
name: "mobile_relaunch",
|
|
74916
|
-
description: "Terminate and relaunch the app for a clean, hermetic state (sessions are noReset=true, so Home only backgrounds).
|
|
74890
|
+
description: "Terminate and relaunch the app for a clean, hermetic state (sessions are noReset=true, so Home only backgrounds). Use between exploration episodes when you want to start fresh. Re-snapshot afterward.",
|
|
74917
74891
|
parameters: { type: "object", properties: {} }
|
|
74918
74892
|
}
|
|
74919
74893
|
};
|
|
@@ -75713,11 +75687,11 @@ Rules:
|
|
|
75713
75687
|
**Phase 2 \u2014 Logged-in breadth first (~35% of budget).** Once signed in, find the primary navigation (bottom tab bar, drawer, or menu) and visit EVERY top-level destination ONCE, observing each screen, BEFORE drilling into any of them. A survey that maps one tab deeply but never opens the other tabs has failed \u2014 breadth across all tabs/drawer entries is mandatory.
|
|
75714
75688
|
**Phase 3 \u2014 Depth (remaining budget).** Return to each observed tab and explore its sub-screens: open ONE list row per list (rows share a template), open menus/sheets/dialogs, observe detail screens, and mobile_swipe up/down to reveal off-screen content. record_transition whenever you move between screens.
|
|
75715
75689
|
|
|
75716
|
-
Rules that apply throughout: snapshots and post-action observations capture screen evidence automatically. Use capture_screen sparingly to name/classify important stable destinations (use screenType "LANDING" for welcome/onboarding/intro and "AUTH" for sign-in/sign-up), but never spend turns renaming already-observed structural variants. Record transitions on every move; mobile_back to return
|
|
75690
|
+
Rules that apply throughout: snapshots and post-action observations capture screen evidence automatically. Use capture_screen sparingly to name/classify important stable destinations (use screenType "LANDING" for welcome/onboarding/intro and "AUTH" for sign-in/sign-up), but never spend turns renaming already-observed structural variants. Record transitions on every move; mobile_back to return; mobile_relaunch only for a clean restart between episodes. When no new screens or safe navigation controls remain, call finish_exploration.
|
|
75717
75691
|
|
|
75718
75692
|
## Boundaries (survey = map, do not mutate)
|
|
75719
75693
|
- Do NOT submit forms, save, delete, purchase, or send anything. You MAY open modals/menus to catalog them, then dismiss.
|
|
75720
|
-
- Stay inside this app.${targetLine} Do not leave to the system home screen or other apps (mobile_home backgrounds \u2014 avoid it). If a tap opens an app store, settings, browser, or another app, do not explore it; return with mobile_back
|
|
75694
|
+
- Stay inside this app.${targetLine} Do not leave to the system home screen or other apps (mobile_home backgrounds \u2014 avoid it). If a tap opens an app store, settings, browser, or another app, do not explore it; return with mobile_back or mobile_relaunch.
|
|
75721
75695
|
- If you hit a login wall and have no credentials, capture the auth screen and explore what is reachable.
|
|
75722
75696
|
|
|
75723
75697
|
Max iterations: ${maxIterations}. You will be warned near the end.`;
|
|
@@ -76128,8 +76102,6 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
76128
76102
|
const kickoff = buildKickoffMessage(mode);
|
|
76129
76103
|
const recentExchanges = [];
|
|
76130
76104
|
const transientDirectives = [];
|
|
76131
|
-
const resetPolicyForTools = ctx.target?.resetPolicy ?? "AUTO";
|
|
76132
|
-
const preserveAppDataForTools = resetPolicyForTools === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicyForTools === "AUTO";
|
|
76133
76105
|
const tools = [
|
|
76134
76106
|
MOBILE_SNAPSHOT_TOOL,
|
|
76135
76107
|
MOBILE_TAP_TOOL,
|
|
@@ -76139,8 +76111,7 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
76139
76111
|
MOBILE_SWIPE_TOOL,
|
|
76140
76112
|
MOBILE_BACK_TOOL,
|
|
76141
76113
|
MOBILE_HOME_TOOL,
|
|
76142
|
-
|
|
76143
|
-
...preserveAppDataForTools ? [] : [MOBILE_RELAUNCH_TOOL],
|
|
76114
|
+
MOBILE_RELAUNCH_TOOL,
|
|
76144
76115
|
MOBILE_OPEN_DEEPLINK_TOOL,
|
|
76145
76116
|
CAPTURE_SCREEN_TOOL,
|
|
76146
76117
|
RECORD_TRANSITION_TOOL,
|
|
@@ -76496,8 +76467,7 @@ Exploration complete: ${summary}`);
|
|
|
76496
76467
|
credentials: credentialSecrets,
|
|
76497
76468
|
redactionCredentials: credentialValuesForRedaction,
|
|
76498
76469
|
authTracker,
|
|
76499
|
-
inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null
|
|
76500
|
-
allowRelaunch: !preserveAppDataForTools
|
|
76470
|
+
inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null
|
|
76501
76471
|
});
|
|
76502
76472
|
pushResult(handled);
|
|
76503
76473
|
const handledRecord = handled && typeof handled === "object" ? handled : null;
|
|
@@ -76590,7 +76560,7 @@ Exploration complete: ${summary}`);
|
|
|
76590
76560
|
return result;
|
|
76591
76561
|
}
|
|
76592
76562
|
async function dispatchMobileTool(args) {
|
|
76593
|
-
const { toolName, toolArgs, iteration, driver, state: state2, enableRecordJourney, latest, buildResolved, absorbResolvedObservation, resolveRef, resolveHybridRef, currentBoundary, collectedTransitions, counters, journeys, log: log2, credentials, redactionCredentials, authTracker, inbox
|
|
76563
|
+
const { toolName, toolArgs, iteration, driver, state: state2, enableRecordJourney, latest, buildResolved, absorbResolvedObservation, resolveRef, resolveHybridRef, currentBoundary, collectedTransitions, counters, journeys, log: log2, credentials, redactionCredentials, authTracker, inbox } = args;
|
|
76594
76564
|
const absorbActionResult = (actionResult, source, preferCredentialSecondary = false) => {
|
|
76595
76565
|
(0, mobile_driver_errors_js_1.throwIfTerminalMobileActionFailure)(actionResult, source);
|
|
76596
76566
|
const resolved = buildResolved(actionResult.source, latest()?.screen.windowSize ?? null, actionResult.screenSignal);
|
|
@@ -76993,13 +76963,6 @@ Exploration complete: ${summary}`);
|
|
|
76993
76963
|
}
|
|
76994
76964
|
// ----- Relaunch (hermetic reset) -----
|
|
76995
76965
|
case "mobile_relaunch": {
|
|
76996
|
-
if (!allowRelaunch) {
|
|
76997
|
-
return {
|
|
76998
|
-
ok: false,
|
|
76999
|
-
error: "mobile_relaunch is blocked while app data is being preserved. Use mobile_back / mobile_snapshot to continue mapping; request CLEAR_APP_DATA for a true cold restart.",
|
|
77000
|
-
note: "Relaunch refused under preserve policy."
|
|
77001
|
-
};
|
|
77002
|
-
}
|
|
77003
76966
|
authTracker?.noteInterveningMutation(iteration);
|
|
77004
76967
|
await driver.relaunch();
|
|
77005
76968
|
log2(" Relaunched app for hermetic reset.");
|
|
@@ -82062,7 +82025,7 @@ Start by calling mobile_snapshot, then execute the steps above \u2014 the minimu
|
|
|
82062
82025
|
}
|
|
82063
82026
|
}
|
|
82064
82027
|
async function runScenarioLoop(args) {
|
|
82065
|
-
const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog
|
|
82028
|
+
const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog } = args;
|
|
82066
82029
|
const log2 = (line) => emitLog(redactCredentialValues(line, redactionCredentials));
|
|
82067
82030
|
const actions = [];
|
|
82068
82031
|
let assertCount = 0;
|
|
@@ -82636,7 +82599,6 @@ ${statePacket}`, redactionCredentials) },
|
|
|
82636
82599
|
setLatest,
|
|
82637
82600
|
credentials,
|
|
82638
82601
|
screenName: () => lastScreenName || "UnknownScreen",
|
|
82639
|
-
allowRelaunch,
|
|
82640
82602
|
recordAction: (action) => {
|
|
82641
82603
|
const screenHash = getLatest()?.signal.screenHash;
|
|
82642
82604
|
const structuralKey = structuralScreenKey(getLatest());
|
|
@@ -82671,7 +82633,7 @@ ${statePacket}`, redactionCredentials) },
|
|
|
82671
82633
|
return { actions, assertCount, finish, lastSnapshot: lastTargetSnapshot, lastScreenName: lastTargetScreenName };
|
|
82672
82634
|
}
|
|
82673
82635
|
async function dispatchGenerateTool(args) {
|
|
82674
|
-
const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName,
|
|
82636
|
+
const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName, recordAction, recordedActions, log: log2 } = args;
|
|
82675
82637
|
const unknownRef = (ref) => ({
|
|
82676
82638
|
ok: false,
|
|
82677
82639
|
error: `Unknown element ref "${String(ref)}". Call mobile_snapshot first; refs are only valid for the latest snapshot.`
|
|
@@ -83061,16 +83023,6 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83061
83023
|
return actionPayload(result, absorbed);
|
|
83062
83024
|
}
|
|
83063
83025
|
case "mobile_relaunch": {
|
|
83064
|
-
if (!allowRelaunch) {
|
|
83065
|
-
log2(" relaunch refused under PRESERVE_APP_DATA \u2014 staying on current app instance.");
|
|
83066
|
-
const snap2 = await driver.snapshot();
|
|
83067
|
-
const absorbed2 = await absorbObservation(snap2.xml, snap2.screenSignal, snap2.windowSize, snap2.screenshot, "mobile_relaunch_blocked");
|
|
83068
|
-
return {
|
|
83069
|
-
ok: false,
|
|
83070
|
-
error: "mobile_relaunch is blocked while app data is preserved. Continue from the current screen.",
|
|
83071
|
-
observation: absorbed2.observation
|
|
83072
|
-
};
|
|
83073
|
-
}
|
|
83074
83026
|
await driver.relaunch();
|
|
83075
83027
|
const snap = await driver.snapshot();
|
|
83076
83028
|
const absorbed = await absorbObservation(snap.xml, snap.screenSignal, snap.windowSize, snap.screenshot, "mobile_relaunch");
|
|
@@ -83289,48 +83241,14 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83289
83241
|
});
|
|
83290
83242
|
return { ok: true, label: metadata.targetLabel ?? accessibilityId ?? text ?? "element" };
|
|
83291
83243
|
}
|
|
83292
|
-
var AMBIENT_CHROME_LABEL_RE = /^(your\s*profile|you|home|search|profile|more|settings|notifications?|tab\s*\d+(\s*of\s*\d+)?|you,\s*tab.*)$/i;
|
|
83293
|
-
function isDisplayableLandingCandidate(el, windowSize) {
|
|
83294
|
-
if (el.visible === false)
|
|
83295
|
-
return false;
|
|
83296
|
-
const { x, y, width, height } = el.bounds;
|
|
83297
|
-
if (!(width > 0 && height > 0))
|
|
83298
|
-
return false;
|
|
83299
|
-
if (width < 8 || height < 8)
|
|
83300
|
-
return false;
|
|
83301
|
-
if (windowSize && windowSize.width > 0 && windowSize.height > 0) {
|
|
83302
|
-
const centerX = x + width / 2;
|
|
83303
|
-
const centerY = y + height / 2;
|
|
83304
|
-
if (centerX < 0 || centerY < 0 || centerX > windowSize.width || centerY > windowSize.height) {
|
|
83305
|
-
return false;
|
|
83306
|
-
}
|
|
83307
|
-
if (width >= windowSize.width * 0.92 && height >= windowSize.height * 0.55) {
|
|
83308
|
-
return false;
|
|
83309
|
-
}
|
|
83310
|
-
}
|
|
83311
|
-
return true;
|
|
83312
|
-
}
|
|
83313
|
-
function landingAnchorSortKey(el) {
|
|
83314
|
-
const label = (nonEmpty3(el.label) || nonEmpty3(el.text) || "").trim();
|
|
83315
|
-
const ambient = AMBIENT_CHROME_LABEL_RE.test(label) ? 1 : 0;
|
|
83316
|
-
const labelled = nonEmpty3(el.text) || nonEmpty3(el.label) ? 0 : 1;
|
|
83317
|
-
const area = Math.max(1, el.bounds.width * el.bounds.height);
|
|
83318
|
-
const areaBucket = area < 8e4 ? 0 : area < 25e4 ? 1 : 2;
|
|
83319
|
-
return [ambient, labelled, areaBucket];
|
|
83320
|
-
}
|
|
83321
83244
|
function pickLandingAnchor(snapshot) {
|
|
83322
83245
|
if (!snapshot || snapshot.screen.elements.length === 0)
|
|
83323
83246
|
return null;
|
|
83324
83247
|
const elements = snapshot.screen.elements;
|
|
83325
|
-
const
|
|
83326
|
-
|
|
83327
|
-
const
|
|
83328
|
-
|
|
83329
|
-
for (let i = 0; i < ka.length; i += 1) {
|
|
83330
|
-
if (ka[i] !== kb[i])
|
|
83331
|
-
return ka[i] - kb[i];
|
|
83332
|
-
}
|
|
83333
|
-
return 0;
|
|
83248
|
+
const stableAnchors = elements.filter((el) => hasStableId(el) && !weakAssertionTargetReason(el));
|
|
83249
|
+
stableAnchors.sort((a, b) => {
|
|
83250
|
+
const labelled2 = (el) => nonEmpty3(el.text) || nonEmpty3(el.label) ? 0 : 1;
|
|
83251
|
+
return labelled2(a) - labelled2(b);
|
|
83334
83252
|
});
|
|
83335
83253
|
const contentfulNonButton = stableAnchors.find((el) => !/button/i.test(el.role) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83336
83254
|
if (contentfulNonButton)
|
|
@@ -83340,10 +83258,8 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83340
83258
|
return nonButton;
|
|
83341
83259
|
if (stableAnchors.length > 0)
|
|
83342
83260
|
return stableAnchors[0];
|
|
83343
|
-
const labelled = elements.find((el) => !weakAssertionTargetReason(el) &&
|
|
83344
|
-
|
|
83345
|
-
return labelled;
|
|
83346
|
-
return elements.find((el) => !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize)) ?? null;
|
|
83261
|
+
const labelled = elements.find((el) => !weakAssertionTargetReason(el) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83262
|
+
return labelled ?? elements.find((el) => !weakAssertionTargetReason(el)) ?? null;
|
|
83347
83263
|
}
|
|
83348
83264
|
var ALWAYS_PRESENT_CONTAINER_ROLES = /^(XCUIElementTypeApplication|XCUIElementTypeWindow)$/;
|
|
83349
83265
|
var ALWAYS_PRESENT_CONTAINER_ID_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -83441,19 +83357,10 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83441
83357
|
const anchor = pickLandingAnchor(recording.lastSnapshot);
|
|
83442
83358
|
if (!anchor)
|
|
83443
83359
|
return false;
|
|
83444
|
-
const modelAssertCount = actions.filter((a) => a.type === "ASSERT_VISIBLE" || a.type === "ASSERT_TEXT").length;
|
|
83445
|
-
const anchorLabel = (nonEmpty3(anchor.label) || nonEmpty3(anchor.text) || "").trim();
|
|
83446
|
-
if (modelAssertCount > 0 && AMBIENT_CHROME_LABEL_RE.test(anchorLabel)) {
|
|
83447
|
-
return false;
|
|
83448
|
-
}
|
|
83449
|
-
const anchorSelector = (0, mobile_source_parser_js_2.preferredLocator)(anchor);
|
|
83450
|
-
const alreadyAsserted = actions.some((a) => (a.type === "ASSERT_VISIBLE" || a.type === "ASSERT_TEXT") && typeof a.selector === "string" && a.selector === anchorSelector);
|
|
83451
|
-
if (alreadyAsserted)
|
|
83452
|
-
return false;
|
|
83453
83360
|
const screen = recording.lastScreenName || "LandingScreen";
|
|
83454
83361
|
actions.push(finalizeAction({
|
|
83455
83362
|
type: "ASSERT_VISIBLE",
|
|
83456
|
-
selector:
|
|
83363
|
+
selector: (0, mobile_source_parser_js_2.preferredLocator)(anchor),
|
|
83457
83364
|
metadata: metadataForElement(anchor, screen, platform3, nativeElementsOf(recording.lastSnapshot))
|
|
83458
83365
|
}));
|
|
83459
83366
|
return true;
|
|
@@ -84329,8 +84236,7 @@ ${statePacket}`, redactionCredentials) },
|
|
|
84329
84236
|
targetAppId,
|
|
84330
84237
|
credentials: credentialSecrets,
|
|
84331
84238
|
redactionCredentials: credentialValuesForRedaction,
|
|
84332
|
-
log: log2
|
|
84333
|
-
allowRelaunch: (ctx.target?.resetPolicy ?? "AUTO") !== "PRESERVE_APP_DATA"
|
|
84239
|
+
log: log2
|
|
84334
84240
|
});
|
|
84335
84241
|
consecutiveInfraFailures = 0;
|
|
84336
84242
|
const test = assembleTest({
|
|
@@ -337263,11 +337169,10 @@ Fatal error: ${msg}`);
|
|
|
337263
337169
|
const shouldPreserveAppData = resetPolicy === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicy === "AUTO";
|
|
337264
337170
|
if (shouldPreserveAppData) {
|
|
337265
337171
|
try {
|
|
337266
|
-
await driver.
|
|
337267
|
-
log2("App activated with preserved data (no terminate/clear).");
|
|
337172
|
+
await driver.relaunch();
|
|
337268
337173
|
} catch (err) {
|
|
337269
337174
|
(0, mobile_driver_errors_js_1.rethrowIfTerminalMobileDriverError)(err);
|
|
337270
|
-
log2(`App
|
|
337175
|
+
log2(`App relaunch failed while preserving app data: ${err instanceof Error ? err.message : String(err)}`);
|
|
337271
337176
|
}
|
|
337272
337177
|
return false;
|
|
337273
337178
|
}
|
|
@@ -353432,7 +353337,7 @@ var require_package4 = __commonJS({
|
|
|
353432
353337
|
"package.json"(exports2, module2) {
|
|
353433
353338
|
module2.exports = {
|
|
353434
353339
|
name: "@validate.qa/runner",
|
|
353435
|
-
version: "1.0.
|
|
353340
|
+
version: "1.0.83",
|
|
353436
353341
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353437
353342
|
bin: {
|
|
353438
353343
|
"validate-runner": "dist/cli.js"
|
|
@@ -354298,9 +354203,9 @@ async function waitForColdBootSettle(sessionPath) {
|
|
|
354298
354203
|
timeoutMs: MOBILE_COLD_BOOT_SETTLE_MS,
|
|
354299
354204
|
minStableFrames: 2,
|
|
354300
354205
|
pollMs: 400,
|
|
354301
|
-
//
|
|
354302
|
-
//
|
|
354303
|
-
minElements:
|
|
354206
|
+
// WebView native shell alone is ~5–8 nodes; real first screens flatten to
|
|
354207
|
+
// dozens. Require enough nodes that the DOM has painted (mirrors executor).
|
|
354208
|
+
minElements: 12
|
|
354304
354209
|
});
|
|
354305
354210
|
}
|
|
354306
354211
|
async function getWindowSize(sessionPath) {
|
|
@@ -354449,33 +354354,20 @@ async function sendKeysToFocusedInput(sessionPath, value) {
|
|
|
354449
354354
|
}
|
|
354450
354355
|
async function typeText(sessionPath, value, bounds) {
|
|
354451
354356
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
354452
|
-
if (!elementId && bounds === void 0) {
|
|
354453
|
-
throw new Error("No focused element available for TYPE. Tap an input field first.");
|
|
354454
|
-
}
|
|
354455
354357
|
if (elementId) {
|
|
354456
354358
|
try {
|
|
354457
|
-
await appiumRequest(`${sessionPath}/element/${elementId}/
|
|
354359
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/value`, {
|
|
354458
354360
|
method: "POST",
|
|
354459
|
-
body: JSON.stringify({})
|
|
354361
|
+
body: JSON.stringify({ text: value })
|
|
354460
354362
|
});
|
|
354363
|
+
return;
|
|
354461
354364
|
} catch (err) {
|
|
354462
354365
|
if (isAppiumDriverDeadError(err)) throw err;
|
|
354463
354366
|
}
|
|
354367
|
+
} else if (bounds === void 0) {
|
|
354368
|
+
throw new Error("No focused element available for TYPE. Tap an input field first.");
|
|
354464
354369
|
}
|
|
354465
|
-
|
|
354466
|
-
await sendKeysToFocusedInput(sessionPath, value);
|
|
354467
|
-
return;
|
|
354468
|
-
} catch (err) {
|
|
354469
|
-
if (isAppiumDriverDeadError(err)) throw err;
|
|
354470
|
-
}
|
|
354471
|
-
if (elementId) {
|
|
354472
|
-
await appiumRequest(`${sessionPath}/element/${elementId}/value`, {
|
|
354473
|
-
method: "POST",
|
|
354474
|
-
body: JSON.stringify({ text: value })
|
|
354475
|
-
});
|
|
354476
|
-
return;
|
|
354477
|
-
}
|
|
354478
|
-
throw new Error("TYPE failed: could not send keys to the focused input.");
|
|
354370
|
+
await sendKeysToFocusedInput(sessionPath, value);
|
|
354479
354371
|
}
|
|
354480
354372
|
async function submitKeyboard(sessionPath, bounds) {
|
|
354481
354373
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
@@ -357282,10 +357174,17 @@ function xpathLiteral3(value) {
|
|
|
357282
357174
|
const parts = value.split("'").map((p) => `'${p}'`);
|
|
357283
357175
|
return `concat(${parts.join(`, "'", `)})`;
|
|
357284
357176
|
}
|
|
357177
|
+
var XPATH_ASCII_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
357178
|
+
var XPATH_ASCII_LOWER = "abcdefghijklmnopqrstuvwxyz";
|
|
357285
357179
|
function identityTextXpath2(value) {
|
|
357286
357180
|
const lit = xpathLiteral3(value);
|
|
357287
357181
|
return `//*[@text=${lit} or @content-desc=${lit} or @label=${lit} or @name=${lit} or @value=${lit}]`;
|
|
357288
357182
|
}
|
|
357183
|
+
function identityTextXpathCaseInsensitive(value) {
|
|
357184
|
+
const lit = xpathLiteral3(value.toLowerCase());
|
|
357185
|
+
const fold = (attr) => `translate(@${attr}, '${XPATH_ASCII_UPPER}', '${XPATH_ASCII_LOWER}')=${lit}`;
|
|
357186
|
+
return `//*[${fold("text")} or ${fold("content-desc")} or ${fold("label")} or ${fold("name")} or ${fold("value")}]`;
|
|
357187
|
+
}
|
|
357289
357188
|
function iosPredicateLiteral(value) {
|
|
357290
357189
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
357291
357190
|
}
|
|
@@ -357293,19 +357192,28 @@ function iosTextIdentityPredicate(value) {
|
|
|
357293
357192
|
const lit = iosPredicateLiteral(value);
|
|
357294
357193
|
return `label == ${lit} OR name == ${lit} OR value == ${lit}`;
|
|
357295
357194
|
}
|
|
357195
|
+
function iosTextIdentityPredicateCaseInsensitive(value) {
|
|
357196
|
+
const lit = iosPredicateLiteral(value);
|
|
357197
|
+
return `label ==[c] ${lit} OR name ==[c] ${lit} OR value ==[c] ${lit}`;
|
|
357198
|
+
}
|
|
357296
357199
|
function textIdentityStrategies(value, platform3) {
|
|
357297
357200
|
const strategies = [];
|
|
357298
357201
|
if (platform3 === "IOS") {
|
|
357299
357202
|
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicate(value) });
|
|
357203
|
+
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicateCaseInsensitive(value) });
|
|
357300
357204
|
}
|
|
357301
357205
|
strategies.push({ using: "xpath", value: identityTextXpath2(value) });
|
|
357206
|
+
strategies.push({ using: "xpath", value: identityTextXpathCaseInsensitive(value) });
|
|
357302
357207
|
return strategies;
|
|
357303
357208
|
}
|
|
357304
357209
|
function isIdentityIosPredicate(value) {
|
|
357305
|
-
return /(?:^|[\s(])(?:name|label|value)\s
|
|
357210
|
+
return /(?:^|[\s(])(?:name|label|value)\s*==(?:\[[cC]\])?\s*"/.test(value) && !/\b(?:CONTAINS|BEGINSWITH|ENDSWITH|LIKE|MATCHES|IN)\b/i.test(value);
|
|
357306
357211
|
}
|
|
357307
357212
|
function isIdentityXpath(value) {
|
|
357308
|
-
|
|
357213
|
+
if (/(?:contains|starts-with)\s*\(/i.test(value)) return false;
|
|
357214
|
+
if (/@[A-Za-z_:][-A-Za-z0-9_:.]*\s*=/.test(value)) return true;
|
|
357215
|
+
if (/translate\s*\(\s*@[A-Za-z_:][-A-Za-z0-9_:.]*\s*,/.test(value) && /\)\s*=/.test(value)) return true;
|
|
357216
|
+
return false;
|
|
357309
357217
|
}
|
|
357310
357218
|
function isIdentityStrategy(strategy) {
|
|
357311
357219
|
if (strategy.using === "accessibility id" || strategy.using === "id") return true;
|
|
@@ -358128,7 +358036,12 @@ function mobileMjpegPortForDevice(platform3, deviceId, env = process.env) {
|
|
|
358128
358036
|
return typeof port === "number" && Number.isFinite(port) ? port : null;
|
|
358129
358037
|
}
|
|
358130
358038
|
var PINNED_DEVICE_SNAPSHOT_TTL_MS = 25 * 6e4;
|
|
358131
|
-
var
|
|
358039
|
+
var pinnedDeviceSnapshots = /* @__PURE__ */ new Map();
|
|
358040
|
+
function prunePinnedDeviceSnapshots(now = Date.now()) {
|
|
358041
|
+
for (const [id, entry] of pinnedDeviceSnapshots) {
|
|
358042
|
+
if (now - entry.at >= PINNED_DEVICE_SNAPSHOT_TTL_MS) pinnedDeviceSnapshots.delete(id);
|
|
358043
|
+
}
|
|
358044
|
+
}
|
|
358132
358045
|
async function executeMobileTest(options) {
|
|
358133
358046
|
const logLines = [];
|
|
358134
358047
|
const log2 = (msg) => logLines.push(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
|
|
@@ -358203,8 +358116,10 @@ async function executeMobileTest(options) {
|
|
|
358203
358116
|
}
|
|
358204
358117
|
const platformFilter = options.target.platform;
|
|
358205
358118
|
const pinnedDeviceId = options.target.assignedDeviceId;
|
|
358206
|
-
const
|
|
358207
|
-
|
|
358119
|
+
const now = Date.now();
|
|
358120
|
+
prunePinnedDeviceSnapshots(now);
|
|
358121
|
+
const candidateSnapshot = pinnedDeviceId ? pinnedDeviceSnapshots.get(pinnedDeviceId) : void 0;
|
|
358122
|
+
const reusablePinnedSnapshot = pinnedDeviceId && candidateSnapshot && now - candidateSnapshot.at < PINNED_DEVICE_SNAPSHOT_TTL_MS ? candidateSnapshot : null;
|
|
358208
358123
|
if (reusablePinnedSnapshot) {
|
|
358209
358124
|
assertDeviceNotQuarantined(reusablePinnedSnapshot.device.id);
|
|
358210
358125
|
selectedDevice = reusablePinnedSnapshot.device;
|
|
@@ -358220,22 +358135,24 @@ async function executeMobileTest(options) {
|
|
|
358220
358135
|
`No ${platformFilter} ${platformFilter === "IOS" ? "simulator" : "emulator"} booted. Start one via ${platformFilter === "IOS" ? "Simulator.app" : "Android Studio"} first.`
|
|
358221
358136
|
);
|
|
358222
358137
|
}
|
|
358223
|
-
if (options.target.assignedDeviceId) {
|
|
358224
|
-
|
|
358225
|
-
|
|
358226
|
-
|
|
358227
|
-
`Assigned ${platformFilter} device ${options.target.assignedDeviceId} is no longer booted/available (booted: ${matchingDevices.map((d) => d.id).join(", ") || "none"}). Refusing to fall back to another device.`
|
|
358228
|
-
);
|
|
358229
|
-
}
|
|
358230
|
-
}
|
|
358231
|
-
if (!selectedDevice && options.target.recordedDeviceId) {
|
|
358232
|
-
selectedDevice = matchingDevices.find((d) => d.id === options.target.recordedDeviceId);
|
|
358138
|
+
if (!options.target.assignedDeviceId) {
|
|
358139
|
+
throw new Error(
|
|
358140
|
+
"CONFIG_ISSUE: mobile test requires an assigned device lease; refusing unpinned session create."
|
|
358141
|
+
);
|
|
358233
358142
|
}
|
|
358143
|
+
selectedDevice = matchingDevices.find((d) => d.id === options.target.assignedDeviceId);
|
|
358234
358144
|
if (!selectedDevice) {
|
|
358235
|
-
|
|
358145
|
+
throw new Error(
|
|
358146
|
+
`Assigned ${platformFilter} device ${options.target.assignedDeviceId} is no longer booted/available (booted: ${matchingDevices.map((d) => d.id).join(", ") || "none"}). Refusing to fall back to another device.`
|
|
358147
|
+
);
|
|
358236
358148
|
}
|
|
358237
358149
|
log2(`Using device: ${selectedDevice.name} (${selectedDevice.id})`);
|
|
358238
|
-
|
|
358150
|
+
if (options.target.assignedDeviceId) {
|
|
358151
|
+
pinnedDeviceSnapshots.set(options.target.assignedDeviceId, {
|
|
358152
|
+
device: selectedDevice,
|
|
358153
|
+
at: Date.now()
|
|
358154
|
+
});
|
|
358155
|
+
}
|
|
358239
358156
|
}
|
|
358240
358157
|
try {
|
|
358241
358158
|
capture = await startMobileNetworkCapture({
|
|
@@ -358571,20 +358488,16 @@ async function createMobileDiscoverySession(target, options) {
|
|
|
358571
358488
|
`No ${platformFilter} ${platformFilter === "IOS" ? "simulator" : "emulator"} booted. Start one via ${platformFilter === "IOS" ? "Simulator.app" : "Android Studio"} first.`
|
|
358572
358489
|
);
|
|
358573
358490
|
}
|
|
358574
|
-
|
|
358575
|
-
|
|
358576
|
-
|
|
358577
|
-
|
|
358578
|
-
throw new Error(
|
|
358579
|
-
`CONFIG_ISSUE: assigned ${platformFilter} device ${target.assignedDeviceId} is no longer booted for mobile discovery; will retry.`
|
|
358580
|
-
);
|
|
358581
|
-
}
|
|
358582
|
-
}
|
|
358583
|
-
if (!selectedDevice && target.recordedDeviceId) {
|
|
358584
|
-
selectedDevice = matchingDevices.find((d) => d.id === target.recordedDeviceId);
|
|
358491
|
+
if (!target.assignedDeviceId) {
|
|
358492
|
+
throw new Error(
|
|
358493
|
+
"CONFIG_ISSUE: mobile discovery requires an assigned device lease; refusing unpinned session create."
|
|
358494
|
+
);
|
|
358585
358495
|
}
|
|
358496
|
+
const selectedDevice = matchingDevices.find((d) => d.id === target.assignedDeviceId);
|
|
358586
358497
|
if (!selectedDevice) {
|
|
358587
|
-
|
|
358498
|
+
throw new Error(
|
|
358499
|
+
`CONFIG_ISSUE: assigned ${platformFilter} device ${target.assignedDeviceId} is no longer booted for mobile discovery; will retry.`
|
|
358500
|
+
);
|
|
358588
358501
|
}
|
|
358589
358502
|
if (selectedDevice.installedApps && selectedDevice.installedApps.length > 0 && !selectedDevice.installedApps.includes(target.appId)) {
|
|
358590
358503
|
throw new Error(
|
|
@@ -359026,6 +358939,10 @@ function computeMobileCapacity(input) {
|
|
|
359026
358939
|
}
|
|
359027
358940
|
return Math.max(0, cap);
|
|
359028
358941
|
}
|
|
358942
|
+
function discoveryKindOf(ctx) {
|
|
358943
|
+
if (!isNativeMobileDiscoveryContext(ctx)) return null;
|
|
358944
|
+
return ctx?.mode === "survey" ? "survey" : "deep";
|
|
358945
|
+
}
|
|
359029
358946
|
function decideMobileDeviceGate(input) {
|
|
359030
358947
|
const {
|
|
359031
358948
|
runMode,
|
|
@@ -359036,8 +358953,10 @@ function decideMobileDeviceGate(input) {
|
|
|
359036
358953
|
readyDevices,
|
|
359037
358954
|
capacity,
|
|
359038
358955
|
freeDeviceId,
|
|
359039
|
-
|
|
359040
|
-
|
|
358956
|
+
discoveryKind,
|
|
358957
|
+
activeMobileSurveys,
|
|
358958
|
+
activeMobileDeeps,
|
|
358959
|
+
maxMobileDeeps
|
|
359041
358960
|
} = input;
|
|
359042
358961
|
if (!platform3) {
|
|
359043
358962
|
return { claim: false, disposition: "pending", reason: "mobile run has no supported target platform" };
|
|
@@ -359049,8 +358968,18 @@ function decideMobileDeviceGate(input) {
|
|
|
359049
358968
|
reason: `no booted ${platform3 === "IOS" ? "iOS" : "Android"} device for mobile run`
|
|
359050
358969
|
};
|
|
359051
358970
|
}
|
|
359052
|
-
if (
|
|
359053
|
-
return { claim: false, disposition: "defer", reason: "native
|
|
358971
|
+
if (discoveryKind === "survey" && activeMobileSurveys >= 1) {
|
|
358972
|
+
return { claim: false, disposition: "defer", reason: "native survey serialized to 1 concurrent" };
|
|
358973
|
+
}
|
|
358974
|
+
if (discoveryKind === "deep") {
|
|
358975
|
+
const deepCap = Math.max(0, maxMobileDeeps);
|
|
358976
|
+
if (activeMobileDeeps >= deepCap) {
|
|
358977
|
+
return {
|
|
358978
|
+
claim: false,
|
|
358979
|
+
disposition: "defer",
|
|
358980
|
+
reason: `native deep discovery capacity reached: ${activeMobileDeeps}/${deepCap}`
|
|
358981
|
+
};
|
|
358982
|
+
}
|
|
359054
358983
|
}
|
|
359055
358984
|
const isHealForHeldTest = runMode === "heal" && !!testCaseId && healHoldTestCaseIds.has(testCaseId);
|
|
359056
358985
|
const reservedForHeals = isHealForHeldTest ? Math.max(0, healHoldTestCaseIds.size - 1) : healHoldTestCaseIds.size;
|
|
@@ -359583,9 +359512,6 @@ async function startSession(validated, res) {
|
|
|
359583
359512
|
...deviceId ? { "appium:udid": deviceId } : {},
|
|
359584
359513
|
...platformVersion ? { "appium:platformVersion": platformVersion } : {},
|
|
359585
359514
|
"appium:noReset": true,
|
|
359586
|
-
// Keep pace with executor createSession: Expo "Open in App?" sheets and
|
|
359587
|
-
// permission dialogs otherwise freeze WDA for recording/mirror sessions.
|
|
359588
|
-
...process.env.APPIUM_IOS_AUTO_ACCEPT_ALERTS !== "false" ? { "appium:autoAcceptAlerts": true } : {},
|
|
359589
359515
|
// Same Xcode 27+ workaround as the executor's createSession (appium-executor.ts):
|
|
359590
359516
|
// Simulator.app is no longer at <DEVELOPER_DIR>/Applications, so XCUITest's
|
|
359591
359517
|
// "open Simulator UI" step fails. The sim is already booted via CoreSimulator;
|
|
@@ -361461,11 +361387,12 @@ var RUNNER_BUILD_ID = process.env.VALIDATE_RUNNER_BUILD_ID ?? process.env.RAILWA
|
|
|
361461
361387
|
var DEFAULT_MAX_CONCURRENT = IS_CLOUD ? 4 : 40;
|
|
361462
361388
|
var DEFAULT_TYPE_LIMITS = {
|
|
361463
361389
|
discovery: IS_CLOUD ? 2 : 15,
|
|
361464
|
-
//
|
|
361465
|
-
//
|
|
361466
|
-
// (
|
|
361467
|
-
// and
|
|
361468
|
-
|
|
361390
|
+
// Max concurrent native deep discoveries on one local runner (survey is always
|
|
361391
|
+
// 1 via decideMobileDeviceGate). Applied by the device gate as maxMobileDeeps
|
|
361392
|
+
// (MAX_MOBILE_DISCOVERY); the resource-governor type key is run.mode='discovery'
|
|
361393
|
+
// and does NOT read this entry — do not treat TYPE_LIMITS['mobile-discovery'] as
|
|
361394
|
+
// the live governor. Env default 2 enables two deeps when two free devices exist.
|
|
361395
|
+
"mobile-discovery": 2,
|
|
361469
361396
|
heal: IS_CLOUD ? 2 : 8,
|
|
361470
361397
|
"regression-suite": IS_CLOUD ? 1 : 2,
|
|
361471
361398
|
"system-regression-suite": IS_CLOUD ? 1 : 2,
|
|
@@ -363114,10 +363041,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363114
363041
|
log.fail("ERROR - malformed locator-discovery context");
|
|
363115
363042
|
return;
|
|
363116
363043
|
}
|
|
363117
|
-
if (getBridgeState().
|
|
363044
|
+
if (getBridgeState().recordingActive) {
|
|
363118
363045
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
363119
363046
|
status: "ERROR",
|
|
363120
|
-
error: "CONFIG_ISSUE:
|
|
363047
|
+
error: "CONFIG_ISSUE: a mobile recording session is active; retry once it ends."
|
|
363121
363048
|
});
|
|
363122
363049
|
log.fail("ERROR - device busy (recording) for locator discovery");
|
|
363123
363050
|
return;
|
|
@@ -363546,6 +363473,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363546
363473
|
if (!mobileTarget || !mobileTarget.appId) {
|
|
363547
363474
|
throw new Error("Native mobile discovery requires a target with an appId (bundleId/appPackage).");
|
|
363548
363475
|
}
|
|
363476
|
+
if (!assignedDeviceId) {
|
|
363477
|
+
throw new Error(
|
|
363478
|
+
"CONFIG_ISSUE: mobile discovery was claimed without a device lease; refusing unpinned session create."
|
|
363479
|
+
);
|
|
363480
|
+
}
|
|
363549
363481
|
const targetPlatform = mobileTarget.platform;
|
|
363550
363482
|
const platform3 = mobileMedium === "IOS_NATIVE" ? "IOS" : mobileMedium === "ANDROID_NATIVE" ? "ANDROID" : targetPlatform === "IOS" || targetPlatform === "ANDROID" ? targetPlatform : null;
|
|
363551
363483
|
if (!platform3) {
|
|
@@ -363554,8 +363486,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363554
363486
|
if (mobileMedium === "IOS_NATIVE" && targetPlatform === "ANDROID" || mobileMedium === "ANDROID_NATIVE" && targetPlatform === "IOS") {
|
|
363555
363487
|
throw new Error("Native mobile discovery medium does not match target.platform.");
|
|
363556
363488
|
}
|
|
363557
|
-
if (getBridgeState().
|
|
363558
|
-
throw new Error("CONFIG_ISSUE:
|
|
363489
|
+
if (getBridgeState().recordingActive) {
|
|
363490
|
+
throw new Error("CONFIG_ISSUE: a mobile recording session is active; mobile discovery will retry once it ends.");
|
|
363559
363491
|
}
|
|
363560
363492
|
const mobileProgressLines = [];
|
|
363561
363493
|
const mobileOnLog = (line) => {
|
|
@@ -364033,10 +363965,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
364033
363965
|
return;
|
|
364034
363966
|
}
|
|
364035
363967
|
if (runMode === "heal" && run2.mobileAiHeal === true) {
|
|
364036
|
-
if (getBridgeState().
|
|
363968
|
+
if (getBridgeState().recordingActive) {
|
|
364037
363969
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
364038
363970
|
status: "ERROR",
|
|
364039
|
-
error: "CONFIG_ISSUE:
|
|
363971
|
+
error: "CONFIG_ISSUE: a mobile recording session is active; the AI heal will retry once it ends.",
|
|
364040
363972
|
duration: Date.now() - startTime
|
|
364041
363973
|
});
|
|
364042
363974
|
log.warn("Skipped mobile AI heal (device busy with a recording)");
|
|
@@ -364774,7 +364706,9 @@ async function startRunner(opts) {
|
|
|
364774
364706
|
};
|
|
364775
364707
|
const leasedDeviceIds = /* @__PURE__ */ new Map();
|
|
364776
364708
|
const activeMobileRuns = /* @__PURE__ */ new Map();
|
|
364777
|
-
let
|
|
364709
|
+
let activeMobileSurveys = 0;
|
|
364710
|
+
let activeMobileDeeps = 0;
|
|
364711
|
+
const MAX_MOBILE_DEEPS = Math.max(0, envInt("MAX_MOBILE_DISCOVERY", DEFAULT_TYPE_LIMITS["mobile-discovery"]));
|
|
364778
364712
|
const mobileHealHold = /* @__PURE__ */ new Map();
|
|
364779
364713
|
const pruneMobileHealHolds = () => {
|
|
364780
364714
|
if (mobileHealHold.size === 0) return;
|
|
@@ -365119,7 +365053,7 @@ async function startRunner(opts) {
|
|
|
365119
365053
|
if (activeRuns >= MAX_CONCURRENT) break;
|
|
365120
365054
|
const runMode = run2.mode ?? "run";
|
|
365121
365055
|
const isMobileOp = enableMobile && isMobileDeviceOp(run2);
|
|
365122
|
-
const
|
|
365056
|
+
const discoveryKind = isMobileOp ? discoveryKindOf(run2.discoveryContext) : null;
|
|
365123
365057
|
let leasedDeviceId = null;
|
|
365124
365058
|
let mobileLeaseError;
|
|
365125
365059
|
const runProject = projectKey(run2);
|
|
@@ -365138,7 +365072,7 @@ async function startRunner(opts) {
|
|
|
365138
365072
|
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (${admission.reason})`);
|
|
365139
365073
|
continue;
|
|
365140
365074
|
}
|
|
365141
|
-
if (enableMobile &&
|
|
365075
|
+
if (enableMobile && isMobileOp && getBridgeState().recordingActive) {
|
|
365142
365076
|
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (mobile recording session active)`);
|
|
365143
365077
|
continue;
|
|
365144
365078
|
}
|
|
@@ -365154,10 +365088,6 @@ async function startRunner(opts) {
|
|
|
365154
365088
|
logDeferredOnce(run2.runId, ` Left PENDING ${run2.runId} (no booted ${discoveryMedium === "IOS_NATIVE" ? "iOS" : "Android"} device for native mobile discovery)`);
|
|
365155
365089
|
continue;
|
|
365156
365090
|
}
|
|
365157
|
-
if (getBridgeState().hasActiveSession) {
|
|
365158
|
-
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (mobile recording session active on shared device)`);
|
|
365159
|
-
continue;
|
|
365160
|
-
}
|
|
365161
365091
|
}
|
|
365162
365092
|
const mobilePlatform = isMobileOp ? mobilePlatformOf(run2) : null;
|
|
365163
365093
|
if (isMobileOp && mobilePlatform) {
|
|
@@ -365199,13 +365129,18 @@ async function startRunner(opts) {
|
|
|
365199
365129
|
readyDevices: virtual + physical,
|
|
365200
365130
|
capacity,
|
|
365201
365131
|
freeDeviceId,
|
|
365202
|
-
|
|
365203
|
-
|
|
365132
|
+
discoveryKind,
|
|
365133
|
+
activeMobileSurveys,
|
|
365134
|
+
activeMobileDeeps,
|
|
365135
|
+
maxMobileDeeps: MAX_MOBILE_DEEPS
|
|
365204
365136
|
});
|
|
365205
365137
|
if (!decision.claim) {
|
|
365206
365138
|
logDeferredOnce(run2.runId, ` ${decision.disposition === "pending" ? "Left PENDING" : "Deferred"} ${run2.runId} (${decision.reason})`);
|
|
365207
365139
|
continue;
|
|
365208
365140
|
}
|
|
365141
|
+
log.dim(
|
|
365142
|
+
` [mobile-gate] admit ${discoveryKind ?? runMode} device=${freeDeviceId} surveys=${activeMobileSurveys} deeps=${activeMobileDeeps}/${MAX_MOBILE_DEEPS} ops=${getActiveMobileOps(mobilePlatform)}/${capacity}`
|
|
365143
|
+
);
|
|
365209
365144
|
leasedDeviceId = freeDeviceId;
|
|
365210
365145
|
leasedDeviceIds.set(freeDeviceId, run2.runId);
|
|
365211
365146
|
}
|
|
@@ -365233,7 +365168,8 @@ async function startRunner(opts) {
|
|
|
365233
365168
|
incrementProject(runProject);
|
|
365234
365169
|
if (isMobileOp) {
|
|
365235
365170
|
if (mobilePlatform) bumpActiveMobileOps(mobilePlatform, 1);
|
|
365236
|
-
if (
|
|
365171
|
+
if (discoveryKind === "survey") activeMobileSurveys++;
|
|
365172
|
+
else if (discoveryKind === "deep") activeMobileDeeps++;
|
|
365237
365173
|
if (runMode === "heal" && run2.testCaseId) mobileHealHold.delete(run2.testCaseId);
|
|
365238
365174
|
}
|
|
365239
365175
|
if (claimedThisPoll > 0 && staggerMs > 0) {
|
|
@@ -365246,7 +365182,8 @@ async function startRunner(opts) {
|
|
|
365246
365182
|
decrementProject(runProject);
|
|
365247
365183
|
if (isMobileOp) {
|
|
365248
365184
|
if (mobilePlatform) bumpActiveMobileOps(mobilePlatform, -1);
|
|
365249
|
-
if (
|
|
365185
|
+
if (discoveryKind === "survey") activeMobileSurveys = Math.max(0, activeMobileSurveys - 1);
|
|
365186
|
+
else if (discoveryKind === "deep") activeMobileDeeps = Math.max(0, activeMobileDeeps - 1);
|
|
365250
365187
|
if (leasedDeviceId) leasedDeviceIds.delete(leasedDeviceId);
|
|
365251
365188
|
activeMobileRuns.delete(run2.runId);
|
|
365252
365189
|
}
|