@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.mjs
CHANGED
|
@@ -1024,8 +1024,7 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1024
1024
|
const isGenericView = /android\.view\.View$/i.test(role);
|
|
1025
1025
|
const moderateHitTarget = hasArea && bounds.width >= 24 && bounds.height >= 24 && bounds.width <= 2e3 && bounds.height <= 1800;
|
|
1026
1026
|
const webHybridLabelledControl = isGenericView && hasHumanIdentity && moderateHitTarget && (focusable || clickable || longClickable || Boolean(contentDesc));
|
|
1027
|
-
const
|
|
1028
|
-
const actionable = !isAnrChrome && enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
|
|
1027
|
+
const actionable = enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
|
|
1029
1028
|
return {
|
|
1030
1029
|
role,
|
|
1031
1030
|
label,
|
|
@@ -1203,7 +1202,6 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1203
1202
|
element.secure = fields.secure;
|
|
1204
1203
|
if (fields.enabled !== void 0)
|
|
1205
1204
|
element.enabled = fields.enabled;
|
|
1206
|
-
element.visible = fields.visible;
|
|
1207
1205
|
if (isCredibleStructuralNavigationTarget(element) && chainHasNativeNavigationSemantics(chain, platform3)) {
|
|
1208
1206
|
markNavigation(element, "native-navigation-semantics");
|
|
1209
1207
|
}
|
|
@@ -1578,8 +1576,6 @@ var require_mobile_boundary = __commonJS({
|
|
|
1578
1576
|
"../runner-core/dist/services/mobile/mobile-boundary.js"(exports) {
|
|
1579
1577
|
"use strict";
|
|
1580
1578
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1581
|
-
exports.isAndroidAnrSystemResourceId = isAndroidAnrSystemResourceId;
|
|
1582
|
-
exports.looksLikeAndroidAnrUi = looksLikeAndroidAnrUi;
|
|
1583
1579
|
exports.mobileOwnerFromScreenSignal = mobileOwnerFromScreenSignal;
|
|
1584
1580
|
exports.isTransientMobileSystemOwner = isTransientMobileSystemOwner;
|
|
1585
1581
|
exports.classifyMobileScreenBoundary = classifyMobileScreenBoundary2;
|
|
@@ -1597,28 +1593,6 @@ var require_mobile_boundary = __commonJS({
|
|
|
1597
1593
|
"com.miui.securitycenter",
|
|
1598
1594
|
"com.samsung.android.permissioncontroller"
|
|
1599
1595
|
]);
|
|
1600
|
-
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;
|
|
1601
|
-
var ANDROID_ANR_TEXT_RE = /isn['’]?t responding|isn['’]?t responding\.|anr|close app|wait/i;
|
|
1602
|
-
function isAndroidAnrSystemResourceId(resourceId) {
|
|
1603
|
-
const rid = resourceId?.trim();
|
|
1604
|
-
if (!rid)
|
|
1605
|
-
return false;
|
|
1606
|
-
if (/(?:android:id\/)?aerr_/i.test(rid))
|
|
1607
|
-
return true;
|
|
1608
|
-
return ANDROID_ANR_RESOURCE_ID_RE.test(rid) && /aerr_/i.test(rid);
|
|
1609
|
-
}
|
|
1610
|
-
function looksLikeAndroidAnrUi(input) {
|
|
1611
|
-
if (isAndroidAnrSystemResourceId(input.resourceId))
|
|
1612
|
-
return true;
|
|
1613
|
-
const rid = input.resourceId?.trim() ?? "";
|
|
1614
|
-
const blob = [input.text, input.label, input.contentDesc].filter(Boolean).join(" ");
|
|
1615
|
-
if (/(?:android:id\/)?(?:alertTitle|aerr_)/i.test(rid) && ANDROID_ANR_TEXT_RE.test(blob)) {
|
|
1616
|
-
return true;
|
|
1617
|
-
}
|
|
1618
|
-
if (/isn['’]?t responding/i.test(blob))
|
|
1619
|
-
return true;
|
|
1620
|
-
return false;
|
|
1621
|
-
}
|
|
1622
1596
|
var IOS_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
|
|
1623
1597
|
"com.apple.springboard",
|
|
1624
1598
|
"com.apple.authenticationservicesui",
|
|
@@ -1776,7 +1750,7 @@ var init_constants = __esm({
|
|
|
1776
1750
|
MOBILE_APP_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,253}[A-Za-z0-9])?$/;
|
|
1777
1751
|
MOBILE_FOCUS_SETTLE_MS = 150;
|
|
1778
1752
|
MOBILE_POST_ACTION_SETTLE_MS = 1800;
|
|
1779
|
-
MOBILE_COLD_BOOT_SETTLE_MS =
|
|
1753
|
+
MOBILE_COLD_BOOT_SETTLE_MS = 12e3;
|
|
1780
1754
|
MOBILE_DEFAULT_SWIPE_DISTANCE = 500;
|
|
1781
1755
|
MOBILE_MAX_SWIPE_DISTANCE = 2e3;
|
|
1782
1756
|
SELECTOR_PRIORITIES = [
|
|
@@ -74918,7 +74892,7 @@ var require_mobile_explorer = __commonJS({
|
|
|
74918
74892
|
type: "function",
|
|
74919
74893
|
function: {
|
|
74920
74894
|
name: "mobile_relaunch",
|
|
74921
|
-
description: "Terminate and relaunch the app for a clean, hermetic state (sessions are noReset=true, so Home only backgrounds).
|
|
74895
|
+
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.",
|
|
74922
74896
|
parameters: { type: "object", properties: {} }
|
|
74923
74897
|
}
|
|
74924
74898
|
};
|
|
@@ -75718,11 +75692,11 @@ Rules:
|
|
|
75718
75692
|
**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.
|
|
75719
75693
|
**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.
|
|
75720
75694
|
|
|
75721
|
-
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
|
|
75695
|
+
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.
|
|
75722
75696
|
|
|
75723
75697
|
## Boundaries (survey = map, do not mutate)
|
|
75724
75698
|
- Do NOT submit forms, save, delete, purchase, or send anything. You MAY open modals/menus to catalog them, then dismiss.
|
|
75725
|
-
- 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
|
|
75699
|
+
- 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.
|
|
75726
75700
|
- If you hit a login wall and have no credentials, capture the auth screen and explore what is reachable.
|
|
75727
75701
|
|
|
75728
75702
|
Max iterations: ${maxIterations}. You will be warned near the end.`;
|
|
@@ -76133,8 +76107,6 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
76133
76107
|
const kickoff = buildKickoffMessage(mode);
|
|
76134
76108
|
const recentExchanges = [];
|
|
76135
76109
|
const transientDirectives = [];
|
|
76136
|
-
const resetPolicyForTools = ctx.target?.resetPolicy ?? "AUTO";
|
|
76137
|
-
const preserveAppDataForTools = resetPolicyForTools === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicyForTools === "AUTO";
|
|
76138
76110
|
const tools = [
|
|
76139
76111
|
MOBILE_SNAPSHOT_TOOL,
|
|
76140
76112
|
MOBILE_TAP_TOOL,
|
|
@@ -76144,8 +76116,7 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
76144
76116
|
MOBILE_SWIPE_TOOL,
|
|
76145
76117
|
MOBILE_BACK_TOOL,
|
|
76146
76118
|
MOBILE_HOME_TOOL,
|
|
76147
|
-
|
|
76148
|
-
...preserveAppDataForTools ? [] : [MOBILE_RELAUNCH_TOOL],
|
|
76119
|
+
MOBILE_RELAUNCH_TOOL,
|
|
76149
76120
|
MOBILE_OPEN_DEEPLINK_TOOL,
|
|
76150
76121
|
CAPTURE_SCREEN_TOOL,
|
|
76151
76122
|
RECORD_TRANSITION_TOOL,
|
|
@@ -76501,8 +76472,7 @@ Exploration complete: ${summary}`);
|
|
|
76501
76472
|
credentials: credentialSecrets,
|
|
76502
76473
|
redactionCredentials: credentialValuesForRedaction,
|
|
76503
76474
|
authTracker,
|
|
76504
|
-
inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null
|
|
76505
|
-
allowRelaunch: !preserveAppDataForTools
|
|
76475
|
+
inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null
|
|
76506
76476
|
});
|
|
76507
76477
|
pushResult(handled);
|
|
76508
76478
|
const handledRecord = handled && typeof handled === "object" ? handled : null;
|
|
@@ -76595,7 +76565,7 @@ Exploration complete: ${summary}`);
|
|
|
76595
76565
|
return result;
|
|
76596
76566
|
}
|
|
76597
76567
|
async function dispatchMobileTool(args) {
|
|
76598
|
-
const { toolName, toolArgs, iteration, driver, state: state2, enableRecordJourney, latest, buildResolved, absorbResolvedObservation, resolveRef, resolveHybridRef, currentBoundary, collectedTransitions, counters, journeys, log: log2, credentials, redactionCredentials, authTracker, inbox
|
|
76568
|
+
const { toolName, toolArgs, iteration, driver, state: state2, enableRecordJourney, latest, buildResolved, absorbResolvedObservation, resolveRef, resolveHybridRef, currentBoundary, collectedTransitions, counters, journeys, log: log2, credentials, redactionCredentials, authTracker, inbox } = args;
|
|
76599
76569
|
const absorbActionResult = (actionResult, source, preferCredentialSecondary = false) => {
|
|
76600
76570
|
(0, mobile_driver_errors_js_1.throwIfTerminalMobileActionFailure)(actionResult, source);
|
|
76601
76571
|
const resolved = buildResolved(actionResult.source, latest()?.screen.windowSize ?? null, actionResult.screenSignal);
|
|
@@ -76998,13 +76968,6 @@ Exploration complete: ${summary}`);
|
|
|
76998
76968
|
}
|
|
76999
76969
|
// ----- Relaunch (hermetic reset) -----
|
|
77000
76970
|
case "mobile_relaunch": {
|
|
77001
|
-
if (!allowRelaunch) {
|
|
77002
|
-
return {
|
|
77003
|
-
ok: false,
|
|
77004
|
-
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.",
|
|
77005
|
-
note: "Relaunch refused under preserve policy."
|
|
77006
|
-
};
|
|
77007
|
-
}
|
|
77008
76971
|
authTracker?.noteInterveningMutation(iteration);
|
|
77009
76972
|
await driver.relaunch();
|
|
77010
76973
|
log2(" Relaunched app for hermetic reset.");
|
|
@@ -82067,7 +82030,7 @@ Start by calling mobile_snapshot, then execute the steps above \u2014 the minimu
|
|
|
82067
82030
|
}
|
|
82068
82031
|
}
|
|
82069
82032
|
async function runScenarioLoop(args) {
|
|
82070
|
-
const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog
|
|
82033
|
+
const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog } = args;
|
|
82071
82034
|
const log2 = (line) => emitLog(redactCredentialValues(line, redactionCredentials));
|
|
82072
82035
|
const actions = [];
|
|
82073
82036
|
let assertCount = 0;
|
|
@@ -82641,7 +82604,6 @@ ${statePacket}`, redactionCredentials) },
|
|
|
82641
82604
|
setLatest,
|
|
82642
82605
|
credentials,
|
|
82643
82606
|
screenName: () => lastScreenName || "UnknownScreen",
|
|
82644
|
-
allowRelaunch,
|
|
82645
82607
|
recordAction: (action) => {
|
|
82646
82608
|
const screenHash = getLatest()?.signal.screenHash;
|
|
82647
82609
|
const structuralKey = structuralScreenKey(getLatest());
|
|
@@ -82676,7 +82638,7 @@ ${statePacket}`, redactionCredentials) },
|
|
|
82676
82638
|
return { actions, assertCount, finish, lastSnapshot: lastTargetSnapshot, lastScreenName: lastTargetScreenName };
|
|
82677
82639
|
}
|
|
82678
82640
|
async function dispatchGenerateTool(args) {
|
|
82679
|
-
const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName,
|
|
82641
|
+
const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName, recordAction, recordedActions, log: log2 } = args;
|
|
82680
82642
|
const unknownRef = (ref) => ({
|
|
82681
82643
|
ok: false,
|
|
82682
82644
|
error: `Unknown element ref "${String(ref)}". Call mobile_snapshot first; refs are only valid for the latest snapshot.`
|
|
@@ -83066,16 +83028,6 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83066
83028
|
return actionPayload(result, absorbed);
|
|
83067
83029
|
}
|
|
83068
83030
|
case "mobile_relaunch": {
|
|
83069
|
-
if (!allowRelaunch) {
|
|
83070
|
-
log2(" relaunch refused under PRESERVE_APP_DATA \u2014 staying on current app instance.");
|
|
83071
|
-
const snap2 = await driver.snapshot();
|
|
83072
|
-
const absorbed2 = await absorbObservation(snap2.xml, snap2.screenSignal, snap2.windowSize, snap2.screenshot, "mobile_relaunch_blocked");
|
|
83073
|
-
return {
|
|
83074
|
-
ok: false,
|
|
83075
|
-
error: "mobile_relaunch is blocked while app data is preserved. Continue from the current screen.",
|
|
83076
|
-
observation: absorbed2.observation
|
|
83077
|
-
};
|
|
83078
|
-
}
|
|
83079
83031
|
await driver.relaunch();
|
|
83080
83032
|
const snap = await driver.snapshot();
|
|
83081
83033
|
const absorbed = await absorbObservation(snap.xml, snap.screenSignal, snap.windowSize, snap.screenshot, "mobile_relaunch");
|
|
@@ -83294,48 +83246,14 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83294
83246
|
});
|
|
83295
83247
|
return { ok: true, label: metadata.targetLabel ?? accessibilityId ?? text ?? "element" };
|
|
83296
83248
|
}
|
|
83297
|
-
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;
|
|
83298
|
-
function isDisplayableLandingCandidate(el, windowSize) {
|
|
83299
|
-
if (el.visible === false)
|
|
83300
|
-
return false;
|
|
83301
|
-
const { x, y, width, height } = el.bounds;
|
|
83302
|
-
if (!(width > 0 && height > 0))
|
|
83303
|
-
return false;
|
|
83304
|
-
if (width < 8 || height < 8)
|
|
83305
|
-
return false;
|
|
83306
|
-
if (windowSize && windowSize.width > 0 && windowSize.height > 0) {
|
|
83307
|
-
const centerX = x + width / 2;
|
|
83308
|
-
const centerY = y + height / 2;
|
|
83309
|
-
if (centerX < 0 || centerY < 0 || centerX > windowSize.width || centerY > windowSize.height) {
|
|
83310
|
-
return false;
|
|
83311
|
-
}
|
|
83312
|
-
if (width >= windowSize.width * 0.92 && height >= windowSize.height * 0.55) {
|
|
83313
|
-
return false;
|
|
83314
|
-
}
|
|
83315
|
-
}
|
|
83316
|
-
return true;
|
|
83317
|
-
}
|
|
83318
|
-
function landingAnchorSortKey(el) {
|
|
83319
|
-
const label = (nonEmpty3(el.label) || nonEmpty3(el.text) || "").trim();
|
|
83320
|
-
const ambient = AMBIENT_CHROME_LABEL_RE.test(label) ? 1 : 0;
|
|
83321
|
-
const labelled = nonEmpty3(el.text) || nonEmpty3(el.label) ? 0 : 1;
|
|
83322
|
-
const area = Math.max(1, el.bounds.width * el.bounds.height);
|
|
83323
|
-
const areaBucket = area < 8e4 ? 0 : area < 25e4 ? 1 : 2;
|
|
83324
|
-
return [ambient, labelled, areaBucket];
|
|
83325
|
-
}
|
|
83326
83249
|
function pickLandingAnchor(snapshot) {
|
|
83327
83250
|
if (!snapshot || snapshot.screen.elements.length === 0)
|
|
83328
83251
|
return null;
|
|
83329
83252
|
const elements = snapshot.screen.elements;
|
|
83330
|
-
const
|
|
83331
|
-
|
|
83332
|
-
const
|
|
83333
|
-
|
|
83334
|
-
for (let i = 0; i < ka.length; i += 1) {
|
|
83335
|
-
if (ka[i] !== kb[i])
|
|
83336
|
-
return ka[i] - kb[i];
|
|
83337
|
-
}
|
|
83338
|
-
return 0;
|
|
83253
|
+
const stableAnchors = elements.filter((el) => hasStableId(el) && !weakAssertionTargetReason(el));
|
|
83254
|
+
stableAnchors.sort((a, b) => {
|
|
83255
|
+
const labelled2 = (el) => nonEmpty3(el.text) || nonEmpty3(el.label) ? 0 : 1;
|
|
83256
|
+
return labelled2(a) - labelled2(b);
|
|
83339
83257
|
});
|
|
83340
83258
|
const contentfulNonButton = stableAnchors.find((el) => !/button/i.test(el.role) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83341
83259
|
if (contentfulNonButton)
|
|
@@ -83345,10 +83263,8 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83345
83263
|
return nonButton;
|
|
83346
83264
|
if (stableAnchors.length > 0)
|
|
83347
83265
|
return stableAnchors[0];
|
|
83348
|
-
const labelled = elements.find((el) => !weakAssertionTargetReason(el) &&
|
|
83349
|
-
|
|
83350
|
-
return labelled;
|
|
83351
|
-
return elements.find((el) => !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize)) ?? null;
|
|
83266
|
+
const labelled = elements.find((el) => !weakAssertionTargetReason(el) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83267
|
+
return labelled ?? elements.find((el) => !weakAssertionTargetReason(el)) ?? null;
|
|
83352
83268
|
}
|
|
83353
83269
|
var ALWAYS_PRESENT_CONTAINER_ROLES = /^(XCUIElementTypeApplication|XCUIElementTypeWindow)$/;
|
|
83354
83270
|
var ALWAYS_PRESENT_CONTAINER_ID_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -83446,19 +83362,10 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83446
83362
|
const anchor = pickLandingAnchor(recording.lastSnapshot);
|
|
83447
83363
|
if (!anchor)
|
|
83448
83364
|
return false;
|
|
83449
|
-
const modelAssertCount = actions.filter((a) => a.type === "ASSERT_VISIBLE" || a.type === "ASSERT_TEXT").length;
|
|
83450
|
-
const anchorLabel = (nonEmpty3(anchor.label) || nonEmpty3(anchor.text) || "").trim();
|
|
83451
|
-
if (modelAssertCount > 0 && AMBIENT_CHROME_LABEL_RE.test(anchorLabel)) {
|
|
83452
|
-
return false;
|
|
83453
|
-
}
|
|
83454
|
-
const anchorSelector = (0, mobile_source_parser_js_2.preferredLocator)(anchor);
|
|
83455
|
-
const alreadyAsserted = actions.some((a) => (a.type === "ASSERT_VISIBLE" || a.type === "ASSERT_TEXT") && typeof a.selector === "string" && a.selector === anchorSelector);
|
|
83456
|
-
if (alreadyAsserted)
|
|
83457
|
-
return false;
|
|
83458
83365
|
const screen = recording.lastScreenName || "LandingScreen";
|
|
83459
83366
|
actions.push(finalizeAction({
|
|
83460
83367
|
type: "ASSERT_VISIBLE",
|
|
83461
|
-
selector:
|
|
83368
|
+
selector: (0, mobile_source_parser_js_2.preferredLocator)(anchor),
|
|
83462
83369
|
metadata: metadataForElement(anchor, screen, platform3, nativeElementsOf(recording.lastSnapshot))
|
|
83463
83370
|
}));
|
|
83464
83371
|
return true;
|
|
@@ -84334,8 +84241,7 @@ ${statePacket}`, redactionCredentials) },
|
|
|
84334
84241
|
targetAppId,
|
|
84335
84242
|
credentials: credentialSecrets,
|
|
84336
84243
|
redactionCredentials: credentialValuesForRedaction,
|
|
84337
|
-
log: log2
|
|
84338
|
-
allowRelaunch: (ctx.target?.resetPolicy ?? "AUTO") !== "PRESERVE_APP_DATA"
|
|
84244
|
+
log: log2
|
|
84339
84245
|
});
|
|
84340
84246
|
consecutiveInfraFailures = 0;
|
|
84341
84247
|
const test = assembleTest({
|
|
@@ -337268,11 +337174,10 @@ Fatal error: ${msg}`);
|
|
|
337268
337174
|
const shouldPreserveAppData = resetPolicy === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicy === "AUTO";
|
|
337269
337175
|
if (shouldPreserveAppData) {
|
|
337270
337176
|
try {
|
|
337271
|
-
await driver.
|
|
337272
|
-
log2("App activated with preserved data (no terminate/clear).");
|
|
337177
|
+
await driver.relaunch();
|
|
337273
337178
|
} catch (err) {
|
|
337274
337179
|
(0, mobile_driver_errors_js_1.rethrowIfTerminalMobileDriverError)(err);
|
|
337275
|
-
log2(`App
|
|
337180
|
+
log2(`App relaunch failed while preserving app data: ${err instanceof Error ? err.message : String(err)}`);
|
|
337276
337181
|
}
|
|
337277
337182
|
return false;
|
|
337278
337183
|
}
|
|
@@ -353437,7 +353342,7 @@ var require_package4 = __commonJS({
|
|
|
353437
353342
|
"package.json"(exports, module) {
|
|
353438
353343
|
module.exports = {
|
|
353439
353344
|
name: "@validate.qa/runner",
|
|
353440
|
-
version: "1.0.
|
|
353345
|
+
version: "1.0.83",
|
|
353441
353346
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353442
353347
|
bin: {
|
|
353443
353348
|
"validate-runner": "dist/cli.js"
|
|
@@ -354303,9 +354208,9 @@ async function waitForColdBootSettle(sessionPath) {
|
|
|
354303
354208
|
timeoutMs: MOBILE_COLD_BOOT_SETTLE_MS,
|
|
354304
354209
|
minStableFrames: 2,
|
|
354305
354210
|
pollMs: 400,
|
|
354306
|
-
//
|
|
354307
|
-
//
|
|
354308
|
-
minElements:
|
|
354211
|
+
// WebView native shell alone is ~5–8 nodes; real first screens flatten to
|
|
354212
|
+
// dozens. Require enough nodes that the DOM has painted (mirrors executor).
|
|
354213
|
+
minElements: 12
|
|
354309
354214
|
});
|
|
354310
354215
|
}
|
|
354311
354216
|
async function getWindowSize(sessionPath) {
|
|
@@ -354454,33 +354359,20 @@ async function sendKeysToFocusedInput(sessionPath, value) {
|
|
|
354454
354359
|
}
|
|
354455
354360
|
async function typeText(sessionPath, value, bounds) {
|
|
354456
354361
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
354457
|
-
if (!elementId && bounds === void 0) {
|
|
354458
|
-
throw new Error("No focused element available for TYPE. Tap an input field first.");
|
|
354459
|
-
}
|
|
354460
354362
|
if (elementId) {
|
|
354461
354363
|
try {
|
|
354462
|
-
await appiumRequest(`${sessionPath}/element/${elementId}/
|
|
354364
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/value`, {
|
|
354463
354365
|
method: "POST",
|
|
354464
|
-
body: JSON.stringify({})
|
|
354366
|
+
body: JSON.stringify({ text: value })
|
|
354465
354367
|
});
|
|
354368
|
+
return;
|
|
354466
354369
|
} catch (err) {
|
|
354467
354370
|
if (isAppiumDriverDeadError(err)) throw err;
|
|
354468
354371
|
}
|
|
354372
|
+
} else if (bounds === void 0) {
|
|
354373
|
+
throw new Error("No focused element available for TYPE. Tap an input field first.");
|
|
354469
354374
|
}
|
|
354470
|
-
|
|
354471
|
-
await sendKeysToFocusedInput(sessionPath, value);
|
|
354472
|
-
return;
|
|
354473
|
-
} catch (err) {
|
|
354474
|
-
if (isAppiumDriverDeadError(err)) throw err;
|
|
354475
|
-
}
|
|
354476
|
-
if (elementId) {
|
|
354477
|
-
await appiumRequest(`${sessionPath}/element/${elementId}/value`, {
|
|
354478
|
-
method: "POST",
|
|
354479
|
-
body: JSON.stringify({ text: value })
|
|
354480
|
-
});
|
|
354481
|
-
return;
|
|
354482
|
-
}
|
|
354483
|
-
throw new Error("TYPE failed: could not send keys to the focused input.");
|
|
354375
|
+
await sendKeysToFocusedInput(sessionPath, value);
|
|
354484
354376
|
}
|
|
354485
354377
|
async function submitKeyboard(sessionPath, bounds) {
|
|
354486
354378
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
@@ -357290,10 +357182,17 @@ function xpathLiteral3(value) {
|
|
|
357290
357182
|
const parts = value.split("'").map((p) => `'${p}'`);
|
|
357291
357183
|
return `concat(${parts.join(`, "'", `)})`;
|
|
357292
357184
|
}
|
|
357185
|
+
var XPATH_ASCII_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
357186
|
+
var XPATH_ASCII_LOWER = "abcdefghijklmnopqrstuvwxyz";
|
|
357293
357187
|
function identityTextXpath2(value) {
|
|
357294
357188
|
const lit = xpathLiteral3(value);
|
|
357295
357189
|
return `//*[@text=${lit} or @content-desc=${lit} or @label=${lit} or @name=${lit} or @value=${lit}]`;
|
|
357296
357190
|
}
|
|
357191
|
+
function identityTextXpathCaseInsensitive(value) {
|
|
357192
|
+
const lit = xpathLiteral3(value.toLowerCase());
|
|
357193
|
+
const fold = (attr) => `translate(@${attr}, '${XPATH_ASCII_UPPER}', '${XPATH_ASCII_LOWER}')=${lit}`;
|
|
357194
|
+
return `//*[${fold("text")} or ${fold("content-desc")} or ${fold("label")} or ${fold("name")} or ${fold("value")}]`;
|
|
357195
|
+
}
|
|
357297
357196
|
function iosPredicateLiteral(value) {
|
|
357298
357197
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
357299
357198
|
}
|
|
@@ -357301,19 +357200,28 @@ function iosTextIdentityPredicate(value) {
|
|
|
357301
357200
|
const lit = iosPredicateLiteral(value);
|
|
357302
357201
|
return `label == ${lit} OR name == ${lit} OR value == ${lit}`;
|
|
357303
357202
|
}
|
|
357203
|
+
function iosTextIdentityPredicateCaseInsensitive(value) {
|
|
357204
|
+
const lit = iosPredicateLiteral(value);
|
|
357205
|
+
return `label ==[c] ${lit} OR name ==[c] ${lit} OR value ==[c] ${lit}`;
|
|
357206
|
+
}
|
|
357304
357207
|
function textIdentityStrategies(value, platform3) {
|
|
357305
357208
|
const strategies = [];
|
|
357306
357209
|
if (platform3 === "IOS") {
|
|
357307
357210
|
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicate(value) });
|
|
357211
|
+
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicateCaseInsensitive(value) });
|
|
357308
357212
|
}
|
|
357309
357213
|
strategies.push({ using: "xpath", value: identityTextXpath2(value) });
|
|
357214
|
+
strategies.push({ using: "xpath", value: identityTextXpathCaseInsensitive(value) });
|
|
357310
357215
|
return strategies;
|
|
357311
357216
|
}
|
|
357312
357217
|
function isIdentityIosPredicate(value) {
|
|
357313
|
-
return /(?:^|[\s(])(?:name|label|value)\s
|
|
357218
|
+
return /(?:^|[\s(])(?:name|label|value)\s*==(?:\[[cC]\])?\s*"/.test(value) && !/\b(?:CONTAINS|BEGINSWITH|ENDSWITH|LIKE|MATCHES|IN)\b/i.test(value);
|
|
357314
357219
|
}
|
|
357315
357220
|
function isIdentityXpath(value) {
|
|
357316
|
-
|
|
357221
|
+
if (/(?:contains|starts-with)\s*\(/i.test(value)) return false;
|
|
357222
|
+
if (/@[A-Za-z_:][-A-Za-z0-9_:.]*\s*=/.test(value)) return true;
|
|
357223
|
+
if (/translate\s*\(\s*@[A-Za-z_:][-A-Za-z0-9_:.]*\s*,/.test(value) && /\)\s*=/.test(value)) return true;
|
|
357224
|
+
return false;
|
|
357317
357225
|
}
|
|
357318
357226
|
function isIdentityStrategy(strategy) {
|
|
357319
357227
|
if (strategy.using === "accessibility id" || strategy.using === "id") return true;
|
|
@@ -358136,7 +358044,12 @@ function mobileMjpegPortForDevice(platform3, deviceId, env = process.env) {
|
|
|
358136
358044
|
return typeof port === "number" && Number.isFinite(port) ? port : null;
|
|
358137
358045
|
}
|
|
358138
358046
|
var PINNED_DEVICE_SNAPSHOT_TTL_MS = 25 * 6e4;
|
|
358139
|
-
var
|
|
358047
|
+
var pinnedDeviceSnapshots = /* @__PURE__ */ new Map();
|
|
358048
|
+
function prunePinnedDeviceSnapshots(now = Date.now()) {
|
|
358049
|
+
for (const [id, entry] of pinnedDeviceSnapshots) {
|
|
358050
|
+
if (now - entry.at >= PINNED_DEVICE_SNAPSHOT_TTL_MS) pinnedDeviceSnapshots.delete(id);
|
|
358051
|
+
}
|
|
358052
|
+
}
|
|
358140
358053
|
async function executeMobileTest(options) {
|
|
358141
358054
|
const logLines = [];
|
|
358142
358055
|
const log2 = (msg) => logLines.push(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}`);
|
|
@@ -358211,8 +358124,10 @@ async function executeMobileTest(options) {
|
|
|
358211
358124
|
}
|
|
358212
358125
|
const platformFilter = options.target.platform;
|
|
358213
358126
|
const pinnedDeviceId = options.target.assignedDeviceId;
|
|
358214
|
-
const
|
|
358215
|
-
|
|
358127
|
+
const now = Date.now();
|
|
358128
|
+
prunePinnedDeviceSnapshots(now);
|
|
358129
|
+
const candidateSnapshot = pinnedDeviceId ? pinnedDeviceSnapshots.get(pinnedDeviceId) : void 0;
|
|
358130
|
+
const reusablePinnedSnapshot = pinnedDeviceId && candidateSnapshot && now - candidateSnapshot.at < PINNED_DEVICE_SNAPSHOT_TTL_MS ? candidateSnapshot : null;
|
|
358216
358131
|
if (reusablePinnedSnapshot) {
|
|
358217
358132
|
assertDeviceNotQuarantined(reusablePinnedSnapshot.device.id);
|
|
358218
358133
|
selectedDevice = reusablePinnedSnapshot.device;
|
|
@@ -358228,22 +358143,24 @@ async function executeMobileTest(options) {
|
|
|
358228
358143
|
`No ${platformFilter} ${platformFilter === "IOS" ? "simulator" : "emulator"} booted. Start one via ${platformFilter === "IOS" ? "Simulator.app" : "Android Studio"} first.`
|
|
358229
358144
|
);
|
|
358230
358145
|
}
|
|
358231
|
-
if (options.target.assignedDeviceId) {
|
|
358232
|
-
|
|
358233
|
-
|
|
358234
|
-
|
|
358235
|
-
`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.`
|
|
358236
|
-
);
|
|
358237
|
-
}
|
|
358238
|
-
}
|
|
358239
|
-
if (!selectedDevice && options.target.recordedDeviceId) {
|
|
358240
|
-
selectedDevice = matchingDevices.find((d) => d.id === options.target.recordedDeviceId);
|
|
358146
|
+
if (!options.target.assignedDeviceId) {
|
|
358147
|
+
throw new Error(
|
|
358148
|
+
"CONFIG_ISSUE: mobile test requires an assigned device lease; refusing unpinned session create."
|
|
358149
|
+
);
|
|
358241
358150
|
}
|
|
358151
|
+
selectedDevice = matchingDevices.find((d) => d.id === options.target.assignedDeviceId);
|
|
358242
358152
|
if (!selectedDevice) {
|
|
358243
|
-
|
|
358153
|
+
throw new Error(
|
|
358154
|
+
`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.`
|
|
358155
|
+
);
|
|
358244
358156
|
}
|
|
358245
358157
|
log2(`Using device: ${selectedDevice.name} (${selectedDevice.id})`);
|
|
358246
|
-
|
|
358158
|
+
if (options.target.assignedDeviceId) {
|
|
358159
|
+
pinnedDeviceSnapshots.set(options.target.assignedDeviceId, {
|
|
358160
|
+
device: selectedDevice,
|
|
358161
|
+
at: Date.now()
|
|
358162
|
+
});
|
|
358163
|
+
}
|
|
358247
358164
|
}
|
|
358248
358165
|
try {
|
|
358249
358166
|
capture = await startMobileNetworkCapture({
|
|
@@ -358579,20 +358496,16 @@ async function createMobileDiscoverySession(target, options) {
|
|
|
358579
358496
|
`No ${platformFilter} ${platformFilter === "IOS" ? "simulator" : "emulator"} booted. Start one via ${platformFilter === "IOS" ? "Simulator.app" : "Android Studio"} first.`
|
|
358580
358497
|
);
|
|
358581
358498
|
}
|
|
358582
|
-
|
|
358583
|
-
|
|
358584
|
-
|
|
358585
|
-
|
|
358586
|
-
throw new Error(
|
|
358587
|
-
`CONFIG_ISSUE: assigned ${platformFilter} device ${target.assignedDeviceId} is no longer booted for mobile discovery; will retry.`
|
|
358588
|
-
);
|
|
358589
|
-
}
|
|
358590
|
-
}
|
|
358591
|
-
if (!selectedDevice && target.recordedDeviceId) {
|
|
358592
|
-
selectedDevice = matchingDevices.find((d) => d.id === target.recordedDeviceId);
|
|
358499
|
+
if (!target.assignedDeviceId) {
|
|
358500
|
+
throw new Error(
|
|
358501
|
+
"CONFIG_ISSUE: mobile discovery requires an assigned device lease; refusing unpinned session create."
|
|
358502
|
+
);
|
|
358593
358503
|
}
|
|
358504
|
+
const selectedDevice = matchingDevices.find((d) => d.id === target.assignedDeviceId);
|
|
358594
358505
|
if (!selectedDevice) {
|
|
358595
|
-
|
|
358506
|
+
throw new Error(
|
|
358507
|
+
`CONFIG_ISSUE: assigned ${platformFilter} device ${target.assignedDeviceId} is no longer booted for mobile discovery; will retry.`
|
|
358508
|
+
);
|
|
358596
358509
|
}
|
|
358597
358510
|
if (selectedDevice.installedApps && selectedDevice.installedApps.length > 0 && !selectedDevice.installedApps.includes(target.appId)) {
|
|
358598
358511
|
throw new Error(
|
|
@@ -359034,6 +358947,10 @@ function computeMobileCapacity(input) {
|
|
|
359034
358947
|
}
|
|
359035
358948
|
return Math.max(0, cap);
|
|
359036
358949
|
}
|
|
358950
|
+
function discoveryKindOf(ctx) {
|
|
358951
|
+
if (!isNativeMobileDiscoveryContext(ctx)) return null;
|
|
358952
|
+
return ctx?.mode === "survey" ? "survey" : "deep";
|
|
358953
|
+
}
|
|
359037
358954
|
function decideMobileDeviceGate(input) {
|
|
359038
358955
|
const {
|
|
359039
358956
|
runMode,
|
|
@@ -359044,8 +358961,10 @@ function decideMobileDeviceGate(input) {
|
|
|
359044
358961
|
readyDevices,
|
|
359045
358962
|
capacity,
|
|
359046
358963
|
freeDeviceId,
|
|
359047
|
-
|
|
359048
|
-
|
|
358964
|
+
discoveryKind,
|
|
358965
|
+
activeMobileSurveys,
|
|
358966
|
+
activeMobileDeeps,
|
|
358967
|
+
maxMobileDeeps
|
|
359049
358968
|
} = input;
|
|
359050
358969
|
if (!platform3) {
|
|
359051
358970
|
return { claim: false, disposition: "pending", reason: "mobile run has no supported target platform" };
|
|
@@ -359057,8 +358976,18 @@ function decideMobileDeviceGate(input) {
|
|
|
359057
358976
|
reason: `no booted ${platform3 === "IOS" ? "iOS" : "Android"} device for mobile run`
|
|
359058
358977
|
};
|
|
359059
358978
|
}
|
|
359060
|
-
if (
|
|
359061
|
-
return { claim: false, disposition: "defer", reason: "native
|
|
358979
|
+
if (discoveryKind === "survey" && activeMobileSurveys >= 1) {
|
|
358980
|
+
return { claim: false, disposition: "defer", reason: "native survey serialized to 1 concurrent" };
|
|
358981
|
+
}
|
|
358982
|
+
if (discoveryKind === "deep") {
|
|
358983
|
+
const deepCap = Math.max(0, maxMobileDeeps);
|
|
358984
|
+
if (activeMobileDeeps >= deepCap) {
|
|
358985
|
+
return {
|
|
358986
|
+
claim: false,
|
|
358987
|
+
disposition: "defer",
|
|
358988
|
+
reason: `native deep discovery capacity reached: ${activeMobileDeeps}/${deepCap}`
|
|
358989
|
+
};
|
|
358990
|
+
}
|
|
359062
358991
|
}
|
|
359063
358992
|
const isHealForHeldTest = runMode === "heal" && !!testCaseId && healHoldTestCaseIds.has(testCaseId);
|
|
359064
358993
|
const reservedForHeals = isHealForHeldTest ? Math.max(0, healHoldTestCaseIds.size - 1) : healHoldTestCaseIds.size;
|
|
@@ -359591,9 +359520,6 @@ async function startSession(validated, res) {
|
|
|
359591
359520
|
...deviceId ? { "appium:udid": deviceId } : {},
|
|
359592
359521
|
...platformVersion ? { "appium:platformVersion": platformVersion } : {},
|
|
359593
359522
|
"appium:noReset": true,
|
|
359594
|
-
// Keep pace with executor createSession: Expo "Open in App?" sheets and
|
|
359595
|
-
// permission dialogs otherwise freeze WDA for recording/mirror sessions.
|
|
359596
|
-
...process.env.APPIUM_IOS_AUTO_ACCEPT_ALERTS !== "false" ? { "appium:autoAcceptAlerts": true } : {},
|
|
359597
359523
|
// Same Xcode 27+ workaround as the executor's createSession (appium-executor.ts):
|
|
359598
359524
|
// Simulator.app is no longer at <DEVELOPER_DIR>/Applications, so XCUITest's
|
|
359599
359525
|
// "open Simulator UI" step fails. The sim is already booted via CoreSimulator;
|
|
@@ -361469,11 +361395,12 @@ var RUNNER_BUILD_ID = process.env.VALIDATE_RUNNER_BUILD_ID ?? process.env.RAILWA
|
|
|
361469
361395
|
var DEFAULT_MAX_CONCURRENT = IS_CLOUD ? 4 : 40;
|
|
361470
361396
|
var DEFAULT_TYPE_LIMITS = {
|
|
361471
361397
|
discovery: IS_CLOUD ? 2 : 15,
|
|
361472
|
-
//
|
|
361473
|
-
//
|
|
361474
|
-
// (
|
|
361475
|
-
// and
|
|
361476
|
-
|
|
361398
|
+
// Max concurrent native deep discoveries on one local runner (survey is always
|
|
361399
|
+
// 1 via decideMobileDeviceGate). Applied by the device gate as maxMobileDeeps
|
|
361400
|
+
// (MAX_MOBILE_DISCOVERY); the resource-governor type key is run.mode='discovery'
|
|
361401
|
+
// and does NOT read this entry — do not treat TYPE_LIMITS['mobile-discovery'] as
|
|
361402
|
+
// the live governor. Env default 2 enables two deeps when two free devices exist.
|
|
361403
|
+
"mobile-discovery": 2,
|
|
361477
361404
|
heal: IS_CLOUD ? 2 : 8,
|
|
361478
361405
|
"regression-suite": IS_CLOUD ? 1 : 2,
|
|
361479
361406
|
"system-regression-suite": IS_CLOUD ? 1 : 2,
|
|
@@ -363122,10 +363049,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363122
363049
|
log.fail("ERROR - malformed locator-discovery context");
|
|
363123
363050
|
return;
|
|
363124
363051
|
}
|
|
363125
|
-
if (getBridgeState().
|
|
363052
|
+
if (getBridgeState().recordingActive) {
|
|
363126
363053
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
363127
363054
|
status: "ERROR",
|
|
363128
|
-
error: "CONFIG_ISSUE:
|
|
363055
|
+
error: "CONFIG_ISSUE: a mobile recording session is active; retry once it ends."
|
|
363129
363056
|
});
|
|
363130
363057
|
log.fail("ERROR - device busy (recording) for locator discovery");
|
|
363131
363058
|
return;
|
|
@@ -363554,6 +363481,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363554
363481
|
if (!mobileTarget || !mobileTarget.appId) {
|
|
363555
363482
|
throw new Error("Native mobile discovery requires a target with an appId (bundleId/appPackage).");
|
|
363556
363483
|
}
|
|
363484
|
+
if (!assignedDeviceId) {
|
|
363485
|
+
throw new Error(
|
|
363486
|
+
"CONFIG_ISSUE: mobile discovery was claimed without a device lease; refusing unpinned session create."
|
|
363487
|
+
);
|
|
363488
|
+
}
|
|
363557
363489
|
const targetPlatform = mobileTarget.platform;
|
|
363558
363490
|
const platform3 = mobileMedium === "IOS_NATIVE" ? "IOS" : mobileMedium === "ANDROID_NATIVE" ? "ANDROID" : targetPlatform === "IOS" || targetPlatform === "ANDROID" ? targetPlatform : null;
|
|
363559
363491
|
if (!platform3) {
|
|
@@ -363562,8 +363494,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
363562
363494
|
if (mobileMedium === "IOS_NATIVE" && targetPlatform === "ANDROID" || mobileMedium === "ANDROID_NATIVE" && targetPlatform === "IOS") {
|
|
363563
363495
|
throw new Error("Native mobile discovery medium does not match target.platform.");
|
|
363564
363496
|
}
|
|
363565
|
-
if (getBridgeState().
|
|
363566
|
-
throw new Error("CONFIG_ISSUE:
|
|
363497
|
+
if (getBridgeState().recordingActive) {
|
|
363498
|
+
throw new Error("CONFIG_ISSUE: a mobile recording session is active; mobile discovery will retry once it ends.");
|
|
363567
363499
|
}
|
|
363568
363500
|
const mobileProgressLines = [];
|
|
363569
363501
|
const mobileOnLog = (line) => {
|
|
@@ -364041,10 +363973,10 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
364041
363973
|
return;
|
|
364042
363974
|
}
|
|
364043
363975
|
if (runMode === "heal" && run2.mobileAiHeal === true) {
|
|
364044
|
-
if (getBridgeState().
|
|
363976
|
+
if (getBridgeState().recordingActive) {
|
|
364045
363977
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
364046
363978
|
status: "ERROR",
|
|
364047
|
-
error: "CONFIG_ISSUE:
|
|
363979
|
+
error: "CONFIG_ISSUE: a mobile recording session is active; the AI heal will retry once it ends.",
|
|
364048
363980
|
duration: Date.now() - startTime
|
|
364049
363981
|
});
|
|
364050
363982
|
log.warn("Skipped mobile AI heal (device busy with a recording)");
|
|
@@ -364782,7 +364714,9 @@ async function startRunner(opts) {
|
|
|
364782
364714
|
};
|
|
364783
364715
|
const leasedDeviceIds = /* @__PURE__ */ new Map();
|
|
364784
364716
|
const activeMobileRuns = /* @__PURE__ */ new Map();
|
|
364785
|
-
let
|
|
364717
|
+
let activeMobileSurveys = 0;
|
|
364718
|
+
let activeMobileDeeps = 0;
|
|
364719
|
+
const MAX_MOBILE_DEEPS = Math.max(0, envInt("MAX_MOBILE_DISCOVERY", DEFAULT_TYPE_LIMITS["mobile-discovery"]));
|
|
364786
364720
|
const mobileHealHold = /* @__PURE__ */ new Map();
|
|
364787
364721
|
const pruneMobileHealHolds = () => {
|
|
364788
364722
|
if (mobileHealHold.size === 0) return;
|
|
@@ -365127,7 +365061,7 @@ async function startRunner(opts) {
|
|
|
365127
365061
|
if (activeRuns >= MAX_CONCURRENT) break;
|
|
365128
365062
|
const runMode = run2.mode ?? "run";
|
|
365129
365063
|
const isMobileOp = enableMobile && isMobileDeviceOp(run2);
|
|
365130
|
-
const
|
|
365064
|
+
const discoveryKind = isMobileOp ? discoveryKindOf(run2.discoveryContext) : null;
|
|
365131
365065
|
let leasedDeviceId = null;
|
|
365132
365066
|
let mobileLeaseError;
|
|
365133
365067
|
const runProject = projectKey(run2);
|
|
@@ -365146,7 +365080,7 @@ async function startRunner(opts) {
|
|
|
365146
365080
|
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (${admission.reason})`);
|
|
365147
365081
|
continue;
|
|
365148
365082
|
}
|
|
365149
|
-
if (enableMobile &&
|
|
365083
|
+
if (enableMobile && isMobileOp && getBridgeState().recordingActive) {
|
|
365150
365084
|
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (mobile recording session active)`);
|
|
365151
365085
|
continue;
|
|
365152
365086
|
}
|
|
@@ -365162,10 +365096,6 @@ async function startRunner(opts) {
|
|
|
365162
365096
|
logDeferredOnce(run2.runId, ` Left PENDING ${run2.runId} (no booted ${discoveryMedium === "IOS_NATIVE" ? "iOS" : "Android"} device for native mobile discovery)`);
|
|
365163
365097
|
continue;
|
|
365164
365098
|
}
|
|
365165
|
-
if (getBridgeState().hasActiveSession) {
|
|
365166
|
-
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (mobile recording session active on shared device)`);
|
|
365167
|
-
continue;
|
|
365168
|
-
}
|
|
365169
365099
|
}
|
|
365170
365100
|
const mobilePlatform = isMobileOp ? mobilePlatformOf(run2) : null;
|
|
365171
365101
|
if (isMobileOp && mobilePlatform) {
|
|
@@ -365207,13 +365137,18 @@ async function startRunner(opts) {
|
|
|
365207
365137
|
readyDevices: virtual + physical,
|
|
365208
365138
|
capacity,
|
|
365209
365139
|
freeDeviceId,
|
|
365210
|
-
|
|
365211
|
-
|
|
365140
|
+
discoveryKind,
|
|
365141
|
+
activeMobileSurveys,
|
|
365142
|
+
activeMobileDeeps,
|
|
365143
|
+
maxMobileDeeps: MAX_MOBILE_DEEPS
|
|
365212
365144
|
});
|
|
365213
365145
|
if (!decision.claim) {
|
|
365214
365146
|
logDeferredOnce(run2.runId, ` ${decision.disposition === "pending" ? "Left PENDING" : "Deferred"} ${run2.runId} (${decision.reason})`);
|
|
365215
365147
|
continue;
|
|
365216
365148
|
}
|
|
365149
|
+
log.dim(
|
|
365150
|
+
` [mobile-gate] admit ${discoveryKind ?? runMode} device=${freeDeviceId} surveys=${activeMobileSurveys} deeps=${activeMobileDeeps}/${MAX_MOBILE_DEEPS} ops=${getActiveMobileOps(mobilePlatform)}/${capacity}`
|
|
365151
|
+
);
|
|
365217
365152
|
leasedDeviceId = freeDeviceId;
|
|
365218
365153
|
leasedDeviceIds.set(freeDeviceId, run2.runId);
|
|
365219
365154
|
}
|
|
@@ -365241,7 +365176,8 @@ async function startRunner(opts) {
|
|
|
365241
365176
|
incrementProject(runProject);
|
|
365242
365177
|
if (isMobileOp) {
|
|
365243
365178
|
if (mobilePlatform) bumpActiveMobileOps(mobilePlatform, 1);
|
|
365244
|
-
if (
|
|
365179
|
+
if (discoveryKind === "survey") activeMobileSurveys++;
|
|
365180
|
+
else if (discoveryKind === "deep") activeMobileDeeps++;
|
|
365245
365181
|
if (runMode === "heal" && run2.testCaseId) mobileHealHold.delete(run2.testCaseId);
|
|
365246
365182
|
}
|
|
365247
365183
|
if (claimedThisPoll > 0 && staggerMs > 0) {
|
|
@@ -365254,7 +365190,8 @@ async function startRunner(opts) {
|
|
|
365254
365190
|
decrementProject(runProject);
|
|
365255
365191
|
if (isMobileOp) {
|
|
365256
365192
|
if (mobilePlatform) bumpActiveMobileOps(mobilePlatform, -1);
|
|
365257
|
-
if (
|
|
365193
|
+
if (discoveryKind === "survey") activeMobileSurveys = Math.max(0, activeMobileSurveys - 1);
|
|
365194
|
+
else if (discoveryKind === "deep") activeMobileDeeps = Math.max(0, activeMobileDeeps - 1);
|
|
365258
365195
|
if (leasedDeviceId) leasedDeviceIds.delete(leasedDeviceId);
|
|
365259
365196
|
activeMobileRuns.delete(run2.runId);
|
|
365260
365197
|
}
|