@validate.qa/runner 1.0.81 → 1.0.82
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 +268 -68
- package/dist/cli.mjs +268 -68
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1019,7 +1019,8 @@ 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
|
|
1022
|
+
const isAnrChrome = typeof resourceId === "string" && /(?:android:id\/)?aerr_/i.test(resourceId) || typeof text === "string" && /isn['’]?t responding/i.test(text) || typeof label === "string" && /isn['’]?t responding/i.test(label);
|
|
1023
|
+
const actionable = !isAnrChrome && enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
|
|
1023
1024
|
return {
|
|
1024
1025
|
role,
|
|
1025
1026
|
label,
|
|
@@ -1197,6 +1198,7 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1197
1198
|
element.secure = fields.secure;
|
|
1198
1199
|
if (fields.enabled !== void 0)
|
|
1199
1200
|
element.enabled = fields.enabled;
|
|
1201
|
+
element.visible = fields.visible;
|
|
1200
1202
|
if (isCredibleStructuralNavigationTarget(element) && chainHasNativeNavigationSemantics(chain, platform3)) {
|
|
1201
1203
|
markNavigation(element, "native-navigation-semantics");
|
|
1202
1204
|
}
|
|
@@ -1571,6 +1573,8 @@ var require_mobile_boundary = __commonJS({
|
|
|
1571
1573
|
"../runner-core/dist/services/mobile/mobile-boundary.js"(exports2) {
|
|
1572
1574
|
"use strict";
|
|
1573
1575
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1576
|
+
exports2.isAndroidAnrSystemResourceId = isAndroidAnrSystemResourceId;
|
|
1577
|
+
exports2.looksLikeAndroidAnrUi = looksLikeAndroidAnrUi;
|
|
1574
1578
|
exports2.mobileOwnerFromScreenSignal = mobileOwnerFromScreenSignal;
|
|
1575
1579
|
exports2.isTransientMobileSystemOwner = isTransientMobileSystemOwner;
|
|
1576
1580
|
exports2.classifyMobileScreenBoundary = classifyMobileScreenBoundary2;
|
|
@@ -1588,6 +1592,28 @@ var require_mobile_boundary = __commonJS({
|
|
|
1588
1592
|
"com.miui.securitycenter",
|
|
1589
1593
|
"com.samsung.android.permissioncontroller"
|
|
1590
1594
|
]);
|
|
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
|
+
}
|
|
1591
1617
|
var IOS_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
|
|
1592
1618
|
"com.apple.springboard",
|
|
1593
1619
|
"com.apple.authenticationservicesui",
|
|
@@ -1745,7 +1771,7 @@ var init_constants = __esm({
|
|
|
1745
1771
|
MOBILE_APP_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,253}[A-Za-z0-9])?$/;
|
|
1746
1772
|
MOBILE_FOCUS_SETTLE_MS = 150;
|
|
1747
1773
|
MOBILE_POST_ACTION_SETTLE_MS = 1800;
|
|
1748
|
-
MOBILE_COLD_BOOT_SETTLE_MS =
|
|
1774
|
+
MOBILE_COLD_BOOT_SETTLE_MS = 25e3;
|
|
1749
1775
|
MOBILE_DEFAULT_SWIPE_DISTANCE = 500;
|
|
1750
1776
|
MOBILE_MAX_SWIPE_DISTANCE = 2e3;
|
|
1751
1777
|
SELECTOR_PRIORITIES = [
|
|
@@ -74887,7 +74913,7 @@ var require_mobile_explorer = __commonJS({
|
|
|
74887
74913
|
type: "function",
|
|
74888
74914
|
function: {
|
|
74889
74915
|
name: "mobile_relaunch",
|
|
74890
|
-
description: "Terminate and relaunch the app for a clean, hermetic state (sessions are noReset=true, so Home only backgrounds).
|
|
74916
|
+
description: "Terminate and relaunch the app for a clean, hermetic state (sessions are noReset=true, so Home only backgrounds). Only for CLEAR_APP_DATA / cold flows. Blocked when app data is being preserved (PRESERVE_APP_DATA or survey AUTO). Do not use on a working product screen. Re-snapshot afterward.",
|
|
74891
74917
|
parameters: { type: "object", properties: {} }
|
|
74892
74918
|
}
|
|
74893
74919
|
};
|
|
@@ -75687,11 +75713,11 @@ Rules:
|
|
|
75687
75713
|
**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.
|
|
75688
75714
|
**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.
|
|
75689
75715
|
|
|
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
|
|
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. Prefer staying on the current app instance \u2014 do not cold-relaunch unless the product UI is truly stuck after a long wait. When no new screens or safe navigation controls remain, call finish_exploration.
|
|
75691
75717
|
|
|
75692
75718
|
## Boundaries (survey = map, do not mutate)
|
|
75693
75719
|
- Do NOT submit forms, save, delete, purchase, or send anything. You MAY open modals/menus to catalog them, then dismiss.
|
|
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
|
|
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 (not a cold relaunch).
|
|
75695
75721
|
- If you hit a login wall and have no credentials, capture the auth screen and explore what is reachable.
|
|
75696
75722
|
|
|
75697
75723
|
Max iterations: ${maxIterations}. You will be warned near the end.`;
|
|
@@ -76102,6 +76128,8 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
76102
76128
|
const kickoff = buildKickoffMessage(mode);
|
|
76103
76129
|
const recentExchanges = [];
|
|
76104
76130
|
const transientDirectives = [];
|
|
76131
|
+
const resetPolicyForTools = ctx.target?.resetPolicy ?? "AUTO";
|
|
76132
|
+
const preserveAppDataForTools = resetPolicyForTools === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicyForTools === "AUTO";
|
|
76105
76133
|
const tools = [
|
|
76106
76134
|
MOBILE_SNAPSHOT_TOOL,
|
|
76107
76135
|
MOBILE_TAP_TOOL,
|
|
@@ -76111,7 +76139,8 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
|
|
|
76111
76139
|
MOBILE_SWIPE_TOOL,
|
|
76112
76140
|
MOBILE_BACK_TOOL,
|
|
76113
76141
|
MOBILE_HOME_TOOL,
|
|
76114
|
-
|
|
76142
|
+
// Omit relaunch under preserve — model otherwise cold-restarts RN into splash.
|
|
76143
|
+
...preserveAppDataForTools ? [] : [MOBILE_RELAUNCH_TOOL],
|
|
76115
76144
|
MOBILE_OPEN_DEEPLINK_TOOL,
|
|
76116
76145
|
CAPTURE_SCREEN_TOOL,
|
|
76117
76146
|
RECORD_TRANSITION_TOOL,
|
|
@@ -76467,7 +76496,8 @@ Exploration complete: ${summary}`);
|
|
|
76467
76496
|
credentials: credentialSecrets,
|
|
76468
76497
|
redactionCredentials: credentialValuesForRedaction,
|
|
76469
76498
|
authTracker,
|
|
76470
|
-
inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null
|
|
76499
|
+
inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null,
|
|
76500
|
+
allowRelaunch: !preserveAppDataForTools
|
|
76471
76501
|
});
|
|
76472
76502
|
pushResult(handled);
|
|
76473
76503
|
const handledRecord = handled && typeof handled === "object" ? handled : null;
|
|
@@ -76560,7 +76590,7 @@ Exploration complete: ${summary}`);
|
|
|
76560
76590
|
return result;
|
|
76561
76591
|
}
|
|
76562
76592
|
async function dispatchMobileTool(args) {
|
|
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;
|
|
76593
|
+
const { toolName, toolArgs, iteration, driver, state: state2, enableRecordJourney, latest, buildResolved, absorbResolvedObservation, resolveRef, resolveHybridRef, currentBoundary, collectedTransitions, counters, journeys, log: log2, credentials, redactionCredentials, authTracker, inbox, allowRelaunch } = args;
|
|
76564
76594
|
const absorbActionResult = (actionResult, source, preferCredentialSecondary = false) => {
|
|
76565
76595
|
(0, mobile_driver_errors_js_1.throwIfTerminalMobileActionFailure)(actionResult, source);
|
|
76566
76596
|
const resolved = buildResolved(actionResult.source, latest()?.screen.windowSize ?? null, actionResult.screenSignal);
|
|
@@ -76963,6 +76993,13 @@ Exploration complete: ${summary}`);
|
|
|
76963
76993
|
}
|
|
76964
76994
|
// ----- Relaunch (hermetic reset) -----
|
|
76965
76995
|
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
|
+
}
|
|
76966
77003
|
authTracker?.noteInterveningMutation(iteration);
|
|
76967
77004
|
await driver.relaunch();
|
|
76968
77005
|
log2(" Relaunched app for hermetic reset.");
|
|
@@ -82025,7 +82062,7 @@ Start by calling mobile_snapshot, then execute the steps above \u2014 the minimu
|
|
|
82025
82062
|
}
|
|
82026
82063
|
}
|
|
82027
82064
|
async function runScenarioLoop(args) {
|
|
82028
|
-
const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog } = args;
|
|
82065
|
+
const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog, allowRelaunch } = args;
|
|
82029
82066
|
const log2 = (line) => emitLog(redactCredentialValues(line, redactionCredentials));
|
|
82030
82067
|
const actions = [];
|
|
82031
82068
|
let assertCount = 0;
|
|
@@ -82599,6 +82636,7 @@ ${statePacket}`, redactionCredentials) },
|
|
|
82599
82636
|
setLatest,
|
|
82600
82637
|
credentials,
|
|
82601
82638
|
screenName: () => lastScreenName || "UnknownScreen",
|
|
82639
|
+
allowRelaunch,
|
|
82602
82640
|
recordAction: (action) => {
|
|
82603
82641
|
const screenHash = getLatest()?.signal.screenHash;
|
|
82604
82642
|
const structuralKey = structuralScreenKey(getLatest());
|
|
@@ -82633,7 +82671,7 @@ ${statePacket}`, redactionCredentials) },
|
|
|
82633
82671
|
return { actions, assertCount, finish, lastSnapshot: lastTargetSnapshot, lastScreenName: lastTargetScreenName };
|
|
82634
82672
|
}
|
|
82635
82673
|
async function dispatchGenerateTool(args) {
|
|
82636
|
-
const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName, recordAction, recordedActions, log: log2 } = args;
|
|
82674
|
+
const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName, allowRelaunch, recordAction, recordedActions, log: log2 } = args;
|
|
82637
82675
|
const unknownRef = (ref) => ({
|
|
82638
82676
|
ok: false,
|
|
82639
82677
|
error: `Unknown element ref "${String(ref)}". Call mobile_snapshot first; refs are only valid for the latest snapshot.`
|
|
@@ -83023,6 +83061,16 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83023
83061
|
return actionPayload(result, absorbed);
|
|
83024
83062
|
}
|
|
83025
83063
|
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
|
+
}
|
|
83026
83074
|
await driver.relaunch();
|
|
83027
83075
|
const snap = await driver.snapshot();
|
|
83028
83076
|
const absorbed = await absorbObservation(snap.xml, snap.screenSignal, snap.windowSize, snap.screenshot, "mobile_relaunch");
|
|
@@ -83241,14 +83289,48 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83241
83289
|
});
|
|
83242
83290
|
return { ok: true, label: metadata.targetLabel ?? accessibilityId ?? text ?? "element" };
|
|
83243
83291
|
}
|
|
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
|
+
}
|
|
83244
83321
|
function pickLandingAnchor(snapshot) {
|
|
83245
83322
|
if (!snapshot || snapshot.screen.elements.length === 0)
|
|
83246
83323
|
return null;
|
|
83247
83324
|
const elements = snapshot.screen.elements;
|
|
83248
|
-
const
|
|
83249
|
-
stableAnchors.sort((a, b) => {
|
|
83250
|
-
const
|
|
83251
|
-
|
|
83325
|
+
const windowSize = snapshot.screen.windowSize ?? null;
|
|
83326
|
+
const stableAnchors = elements.filter((el) => hasStableId(el) && !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize)).sort((a, b) => {
|
|
83327
|
+
const ka = landingAnchorSortKey(a);
|
|
83328
|
+
const kb = landingAnchorSortKey(b);
|
|
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;
|
|
83252
83334
|
});
|
|
83253
83335
|
const contentfulNonButton = stableAnchors.find((el) => !/button/i.test(el.role) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83254
83336
|
if (contentfulNonButton)
|
|
@@ -83258,8 +83340,10 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83258
83340
|
return nonButton;
|
|
83259
83341
|
if (stableAnchors.length > 0)
|
|
83260
83342
|
return stableAnchors[0];
|
|
83261
|
-
const labelled = elements.find((el) => !weakAssertionTargetReason(el) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83262
|
-
|
|
83343
|
+
const labelled = elements.find((el) => !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
|
|
83344
|
+
if (labelled)
|
|
83345
|
+
return labelled;
|
|
83346
|
+
return elements.find((el) => !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize)) ?? null;
|
|
83263
83347
|
}
|
|
83264
83348
|
var ALWAYS_PRESENT_CONTAINER_ROLES = /^(XCUIElementTypeApplication|XCUIElementTypeWindow)$/;
|
|
83265
83349
|
var ALWAYS_PRESENT_CONTAINER_ID_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -83357,10 +83441,19 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83357
83441
|
const anchor = pickLandingAnchor(recording.lastSnapshot);
|
|
83358
83442
|
if (!anchor)
|
|
83359
83443
|
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;
|
|
83360
83453
|
const screen = recording.lastScreenName || "LandingScreen";
|
|
83361
83454
|
actions.push(finalizeAction({
|
|
83362
83455
|
type: "ASSERT_VISIBLE",
|
|
83363
|
-
selector:
|
|
83456
|
+
selector: anchorSelector,
|
|
83364
83457
|
metadata: metadataForElement(anchor, screen, platform3, nativeElementsOf(recording.lastSnapshot))
|
|
83365
83458
|
}));
|
|
83366
83459
|
return true;
|
|
@@ -84236,7 +84329,8 @@ ${statePacket}`, redactionCredentials) },
|
|
|
84236
84329
|
targetAppId,
|
|
84237
84330
|
credentials: credentialSecrets,
|
|
84238
84331
|
redactionCredentials: credentialValuesForRedaction,
|
|
84239
|
-
log: log2
|
|
84332
|
+
log: log2,
|
|
84333
|
+
allowRelaunch: (ctx.target?.resetPolicy ?? "AUTO") !== "PRESERVE_APP_DATA"
|
|
84240
84334
|
});
|
|
84241
84335
|
consecutiveInfraFailures = 0;
|
|
84242
84336
|
const test = assembleTest({
|
|
@@ -105530,7 +105624,7 @@ var require_typescript = __commonJS({
|
|
|
105530
105624
|
walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
|
|
105531
105625
|
whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
|
|
105532
105626
|
writeCommentRange: () => writeCommentRange,
|
|
105533
|
-
writeFile: () =>
|
|
105627
|
+
writeFile: () => writeFile3,
|
|
105534
105628
|
writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
|
|
105535
105629
|
zipWith: () => zipWith
|
|
105536
105630
|
});
|
|
@@ -111862,7 +111956,7 @@ ${lanes.join("\n")}
|
|
|
111862
111956
|
writeOutputIsTTY() {
|
|
111863
111957
|
return process.stdout.isTTY;
|
|
111864
111958
|
},
|
|
111865
|
-
readFile:
|
|
111959
|
+
readFile: readFile3,
|
|
111866
111960
|
writeFile: writeFile22,
|
|
111867
111961
|
watchFile: watchFile2,
|
|
111868
111962
|
watchDirectory,
|
|
@@ -112068,7 +112162,7 @@ ${lanes.join("\n")}
|
|
|
112068
112162
|
callback
|
|
112069
112163
|
);
|
|
112070
112164
|
}
|
|
112071
|
-
function
|
|
112165
|
+
function readFile3(fileName, _encoding) {
|
|
112072
112166
|
let buffer;
|
|
112073
112167
|
try {
|
|
112074
112168
|
buffer = _fs.readFileSync(fileName);
|
|
@@ -124173,7 +124267,7 @@ ${lanes.join("\n")}
|
|
|
124173
124267
|
sourceFilePath = isSourceFileInCommonSourceDirectory ? sourceFilePath.substring(commonSourceDirectory.length) : sourceFilePath;
|
|
124174
124268
|
return combinePaths(newDirPath, sourceFilePath);
|
|
124175
124269
|
}
|
|
124176
|
-
function
|
|
124270
|
+
function writeFile3(host, diagnostics, fileName, text, writeByteOrderMark, sourceFiles, data) {
|
|
124177
124271
|
host.writeFile(
|
|
124178
124272
|
fileName,
|
|
124179
124273
|
text,
|
|
@@ -148624,7 +148718,7 @@ ${lanes.join("\n")}
|
|
|
148624
148718
|
const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
|
|
148625
148719
|
return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
|
|
148626
148720
|
}
|
|
148627
|
-
function parseCommandLineWorker(diagnostics, commandLine,
|
|
148721
|
+
function parseCommandLineWorker(diagnostics, commandLine, readFile3) {
|
|
148628
148722
|
const options = {};
|
|
148629
148723
|
let watchOptions;
|
|
148630
148724
|
const fileNames = [];
|
|
@@ -148672,7 +148766,7 @@ ${lanes.join("\n")}
|
|
|
148672
148766
|
}
|
|
148673
148767
|
}
|
|
148674
148768
|
function parseResponseFile(fileName) {
|
|
148675
|
-
const text = tryReadFile(fileName,
|
|
148769
|
+
const text = tryReadFile(fileName, readFile3 || ((fileName2) => sys.readFile(fileName2)));
|
|
148676
148770
|
if (!isString(text)) {
|
|
148677
148771
|
errors2.push(text);
|
|
148678
148772
|
return;
|
|
@@ -148775,8 +148869,8 @@ ${lanes.join("\n")}
|
|
|
148775
148869
|
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
|
|
148776
148870
|
optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
|
|
148777
148871
|
};
|
|
148778
|
-
function parseCommandLine(commandLine,
|
|
148779
|
-
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine,
|
|
148872
|
+
function parseCommandLine(commandLine, readFile3) {
|
|
148873
|
+
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile3);
|
|
148780
148874
|
}
|
|
148781
148875
|
function getOptionFromName(optionName, allowShort) {
|
|
148782
148876
|
return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
|
|
@@ -148858,8 +148952,8 @@ ${lanes.join("\n")}
|
|
|
148858
148952
|
watchOptionsToExtend
|
|
148859
148953
|
);
|
|
148860
148954
|
}
|
|
148861
|
-
function readConfigFile(fileName,
|
|
148862
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
148955
|
+
function readConfigFile(fileName, readFile3) {
|
|
148956
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile3);
|
|
148863
148957
|
return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
|
|
148864
148958
|
}
|
|
148865
148959
|
function parseConfigFileTextToJson(fileName, jsonText) {
|
|
@@ -148874,14 +148968,14 @@ ${lanes.join("\n")}
|
|
|
148874
148968
|
error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : void 0
|
|
148875
148969
|
};
|
|
148876
148970
|
}
|
|
148877
|
-
function readJsonConfigFile(fileName,
|
|
148878
|
-
const textOrDiagnostic = tryReadFile(fileName,
|
|
148971
|
+
function readJsonConfigFile(fileName, readFile3) {
|
|
148972
|
+
const textOrDiagnostic = tryReadFile(fileName, readFile3);
|
|
148879
148973
|
return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
|
|
148880
148974
|
}
|
|
148881
|
-
function tryReadFile(fileName,
|
|
148975
|
+
function tryReadFile(fileName, readFile3) {
|
|
148882
148976
|
let text;
|
|
148883
148977
|
try {
|
|
148884
|
-
text =
|
|
148978
|
+
text = readFile3(fileName);
|
|
148885
148979
|
} catch (e) {
|
|
148886
148980
|
return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message);
|
|
148887
148981
|
}
|
|
@@ -232924,7 +233018,7 @@ ${lanes.join("\n")}
|
|
|
232924
233018
|
return;
|
|
232925
233019
|
}
|
|
232926
233020
|
const buildInfo = host.getBuildInfo() || { version };
|
|
232927
|
-
|
|
233021
|
+
writeFile3(
|
|
232928
233022
|
host,
|
|
232929
233023
|
emitterDiagnostics,
|
|
232930
233024
|
buildInfoPath,
|
|
@@ -233136,7 +233230,7 @@ ${lanes.join("\n")}
|
|
|
233136
233230
|
}
|
|
233137
233231
|
if (sourceMapFilePath) {
|
|
233138
233232
|
const sourceMap = sourceMapGenerator.toString();
|
|
233139
|
-
|
|
233233
|
+
writeFile3(
|
|
233140
233234
|
host,
|
|
233141
233235
|
emitterDiagnostics,
|
|
233142
233236
|
sourceMapFilePath,
|
|
@@ -233151,7 +233245,7 @@ ${lanes.join("\n")}
|
|
|
233151
233245
|
}
|
|
233152
233246
|
const text = writer.getText();
|
|
233153
233247
|
const data = { sourceMapUrlPos, diagnostics: transform2.diagnostics };
|
|
233154
|
-
|
|
233248
|
+
writeFile3(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
|
|
233155
233249
|
writer.clear();
|
|
233156
233250
|
return !data.skippedDtsWrite;
|
|
233157
233251
|
}
|
|
@@ -238539,12 +238633,12 @@ ${lanes.join("\n")}
|
|
|
238539
238633
|
function createCompilerHost(options, setParentNodes) {
|
|
238540
238634
|
return createCompilerHostWorker(options, setParentNodes);
|
|
238541
238635
|
}
|
|
238542
|
-
function createGetSourceFile(
|
|
238636
|
+
function createGetSourceFile(readFile3, setParentNodes) {
|
|
238543
238637
|
return (fileName, languageVersionOrOptions, onError) => {
|
|
238544
238638
|
let text;
|
|
238545
238639
|
try {
|
|
238546
238640
|
mark("beforeIORead");
|
|
238547
|
-
text =
|
|
238641
|
+
text = readFile3(fileName);
|
|
238548
238642
|
mark("afterIORead");
|
|
238549
238643
|
measure("I/O Read", "beforeIORead", "afterIORead");
|
|
238550
238644
|
} catch (e) {
|
|
@@ -239448,7 +239542,7 @@ ${lanes.join("\n")}
|
|
|
239448
239542
|
getRedirectFromOutput,
|
|
239449
239543
|
forEachResolvedProjectReference: forEachResolvedProjectReference2
|
|
239450
239544
|
});
|
|
239451
|
-
const
|
|
239545
|
+
const readFile3 = host.readFile.bind(host);
|
|
239452
239546
|
(_e = tracing) == null ? void 0 : _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
|
|
239453
239547
|
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
|
239454
239548
|
(_f = tracing) == null ? void 0 : _f.pop();
|
|
@@ -239674,7 +239768,7 @@ ${lanes.join("\n")}
|
|
|
239674
239768
|
shouldTransformImportCall,
|
|
239675
239769
|
emitBuildInfo,
|
|
239676
239770
|
fileExists,
|
|
239677
|
-
readFile:
|
|
239771
|
+
readFile: readFile3,
|
|
239678
239772
|
directoryExists,
|
|
239679
239773
|
getSymlinkCache,
|
|
239680
239774
|
realpath: (_o = host.realpath) == null ? void 0 : _o.bind(host),
|
|
@@ -301033,7 +301127,7 @@ ${options.prefix}` : "\n" : options.prefix
|
|
|
301033
301127
|
walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
|
|
301034
301128
|
whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
|
|
301035
301129
|
writeCommentRange: () => writeCommentRange,
|
|
301036
|
-
writeFile: () =>
|
|
301130
|
+
writeFile: () => writeFile3,
|
|
301037
301131
|
writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
|
|
301038
301132
|
zipWith: () => zipWith
|
|
301039
301133
|
});
|
|
@@ -337169,10 +337263,11 @@ Fatal error: ${msg}`);
|
|
|
337169
337263
|
const shouldPreserveAppData = resetPolicy === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicy === "AUTO";
|
|
337170
337264
|
if (shouldPreserveAppData) {
|
|
337171
337265
|
try {
|
|
337172
|
-
await driver.
|
|
337266
|
+
await driver.launchApp();
|
|
337267
|
+
log2("App activated with preserved data (no terminate/clear).");
|
|
337173
337268
|
} catch (err) {
|
|
337174
337269
|
(0, mobile_driver_errors_js_1.rethrowIfTerminalMobileDriverError)(err);
|
|
337175
|
-
log2(`App
|
|
337270
|
+
log2(`App activate failed while preserving app data: ${err instanceof Error ? err.message : String(err)}`);
|
|
337176
337271
|
}
|
|
337177
337272
|
return false;
|
|
337178
337273
|
}
|
|
@@ -347535,7 +347630,7 @@ var require_mkdir = __commonJS({
|
|
|
347535
347630
|
const gid = opt.gid;
|
|
347536
347631
|
const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
|
|
347537
347632
|
const preserve = opt.preserve;
|
|
347538
|
-
const
|
|
347633
|
+
const unlink3 = opt.unlink;
|
|
347539
347634
|
const cache = opt.cache;
|
|
347540
347635
|
const cwd = normPath(opt.cwd);
|
|
347541
347636
|
const done = (er, created) => {
|
|
@@ -347563,33 +347658,33 @@ var require_mkdir = __commonJS({
|
|
|
347563
347658
|
}
|
|
347564
347659
|
const sub = normPath(path.relative(cwd, dir));
|
|
347565
347660
|
const parts = sub.split("/");
|
|
347566
|
-
mkdir_(cwd, parts, mode, cache,
|
|
347661
|
+
mkdir_(cwd, parts, mode, cache, unlink3, cwd, null, done);
|
|
347567
347662
|
};
|
|
347568
|
-
var mkdir_ = (base2, parts, mode, cache,
|
|
347663
|
+
var mkdir_ = (base2, parts, mode, cache, unlink3, cwd, created, cb) => {
|
|
347569
347664
|
if (!parts.length) {
|
|
347570
347665
|
return cb(null, created);
|
|
347571
347666
|
}
|
|
347572
347667
|
const p = parts.shift();
|
|
347573
347668
|
const part = normPath(path.resolve(base2 + "/" + p));
|
|
347574
347669
|
if (cGet(cache, part)) {
|
|
347575
|
-
return mkdir_(part, parts, mode, cache,
|
|
347670
|
+
return mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347576
347671
|
}
|
|
347577
|
-
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache,
|
|
347672
|
+
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
|
|
347578
347673
|
};
|
|
347579
|
-
var onmkdir = (part, parts, mode, cache,
|
|
347674
|
+
var onmkdir = (part, parts, mode, cache, unlink3, cwd, created, cb) => (er) => {
|
|
347580
347675
|
if (er) {
|
|
347581
347676
|
fs.lstat(part, (statEr, st) => {
|
|
347582
347677
|
if (statEr) {
|
|
347583
347678
|
statEr.path = statEr.path && normPath(statEr.path);
|
|
347584
347679
|
cb(statEr);
|
|
347585
347680
|
} else if (st.isDirectory()) {
|
|
347586
|
-
mkdir_(part, parts, mode, cache,
|
|
347587
|
-
} else if (
|
|
347681
|
+
mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347682
|
+
} else if (unlink3) {
|
|
347588
347683
|
fs.unlink(part, (er2) => {
|
|
347589
347684
|
if (er2) {
|
|
347590
347685
|
return cb(er2);
|
|
347591
347686
|
}
|
|
347592
|
-
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache,
|
|
347687
|
+
fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
|
|
347593
347688
|
});
|
|
347594
347689
|
} else if (st.isSymbolicLink()) {
|
|
347595
347690
|
return cb(new SymlinkError(part, part + "/" + parts.join("/")));
|
|
@@ -347599,7 +347694,7 @@ var require_mkdir = __commonJS({
|
|
|
347599
347694
|
});
|
|
347600
347695
|
} else {
|
|
347601
347696
|
created = created || part;
|
|
347602
|
-
mkdir_(part, parts, mode, cache,
|
|
347697
|
+
mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
|
|
347603
347698
|
}
|
|
347604
347699
|
};
|
|
347605
347700
|
var checkCwdSync = (dir) => {
|
|
@@ -347624,7 +347719,7 @@ var require_mkdir = __commonJS({
|
|
|
347624
347719
|
const gid = opt.gid;
|
|
347625
347720
|
const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
|
|
347626
347721
|
const preserve = opt.preserve;
|
|
347627
|
-
const
|
|
347722
|
+
const unlink3 = opt.unlink;
|
|
347628
347723
|
const cache = opt.cache;
|
|
347629
347724
|
const cwd = normPath(opt.cwd);
|
|
347630
347725
|
const done = (created2) => {
|
|
@@ -347663,7 +347758,7 @@ var require_mkdir = __commonJS({
|
|
|
347663
347758
|
if (st.isDirectory()) {
|
|
347664
347759
|
cSet(cache, part, true);
|
|
347665
347760
|
continue;
|
|
347666
|
-
} else if (
|
|
347761
|
+
} else if (unlink3) {
|
|
347667
347762
|
fs.unlinkSync(part);
|
|
347668
347763
|
fs.mkdirSync(part, mode);
|
|
347669
347764
|
created = created || part;
|
|
@@ -353337,7 +353432,7 @@ var require_package4 = __commonJS({
|
|
|
353337
353432
|
"package.json"(exports2, module2) {
|
|
353338
353433
|
module2.exports = {
|
|
353339
353434
|
name: "@validate.qa/runner",
|
|
353340
|
-
version: "1.0.
|
|
353435
|
+
version: "1.0.82",
|
|
353341
353436
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
353342
353437
|
bin: {
|
|
353343
353438
|
"validate-runner": "dist/cli.js"
|
|
@@ -354203,9 +354298,9 @@ async function waitForColdBootSettle(sessionPath) {
|
|
|
354203
354298
|
timeoutMs: MOBILE_COLD_BOOT_SETTLE_MS,
|
|
354204
354299
|
minStableFrames: 2,
|
|
354205
354300
|
pollMs: 400,
|
|
354206
|
-
// WebView
|
|
354207
|
-
//
|
|
354208
|
-
minElements:
|
|
354301
|
+
// Native splash / WebView shell is ~5–8 nodes; RN product screens and rich
|
|
354302
|
+
// onboarding typically flatten to 15+ once JS has painted.
|
|
354303
|
+
minElements: 15
|
|
354209
354304
|
});
|
|
354210
354305
|
}
|
|
354211
354306
|
async function getWindowSize(sessionPath) {
|
|
@@ -354354,20 +354449,33 @@ async function sendKeysToFocusedInput(sessionPath, value) {
|
|
|
354354
354449
|
}
|
|
354355
354450
|
async function typeText(sessionPath, value, bounds) {
|
|
354356
354451
|
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
|
+
}
|
|
354357
354455
|
if (elementId) {
|
|
354358
354456
|
try {
|
|
354359
|
-
await appiumRequest(`${sessionPath}/element/${elementId}/
|
|
354457
|
+
await appiumRequest(`${sessionPath}/element/${elementId}/clear`, {
|
|
354360
354458
|
method: "POST",
|
|
354361
|
-
body: JSON.stringify({
|
|
354459
|
+
body: JSON.stringify({})
|
|
354362
354460
|
});
|
|
354363
|
-
return;
|
|
354364
354461
|
} catch (err) {
|
|
354365
354462
|
if (isAppiumDriverDeadError(err)) throw err;
|
|
354366
354463
|
}
|
|
354367
|
-
} else if (bounds === void 0) {
|
|
354368
|
-
throw new Error("No focused element available for TYPE. Tap an input field first.");
|
|
354369
354464
|
}
|
|
354370
|
-
|
|
354465
|
+
try {
|
|
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.");
|
|
354371
354479
|
}
|
|
354372
354480
|
async function submitKeyboard(sessionPath, bounds) {
|
|
354373
354481
|
const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
|
|
@@ -355285,6 +355393,7 @@ var import_node_child_process2 = require("child_process");
|
|
|
355285
355393
|
var import_node_util2 = require("util");
|
|
355286
355394
|
var import_node_os2 = require("os");
|
|
355287
355395
|
var import_node_path2 = require("path");
|
|
355396
|
+
var import_promises3 = require("fs/promises");
|
|
355288
355397
|
var import_runner_core8 = __toESM(require_dist2());
|
|
355289
355398
|
|
|
355290
355399
|
// src/services/device-discovery.ts
|
|
@@ -356719,6 +356828,12 @@ async function createSession(target) {
|
|
|
356719
356828
|
// whole survey. The runner owns session teardown explicitly, so a long
|
|
356720
356829
|
// idle window is safe. Env-overridable.
|
|
356721
356830
|
"appium:newCommandTimeout": MOBILE_NEW_COMMAND_TIMEOUT_SECONDS,
|
|
356831
|
+
// Expo / universal-link "Open in App?" system sheets and permission dialogs
|
|
356832
|
+
// otherwise sit on top of the app and can stall WDA snapshot/command proxy
|
|
356833
|
+
// for the entire discovery session (observed: 20+ min hung XCUITest with
|
|
356834
|
+
// ECONNREFUSED on wdaLocalPort). Auto-accept keeps the AUT interactive.
|
|
356835
|
+
// Override with APPIUM_IOS_AUTO_ACCEPT_ALERTS=false for explicit dialog tests.
|
|
356836
|
+
...process.env.APPIUM_IOS_AUTO_ACCEPT_ALERTS !== "false" ? { "appium:autoAcceptAlerts": true } : {},
|
|
356722
356837
|
// Xcode 27+ relocates Simulator.app out of <DEVELOPER_DIR>/Applications, so
|
|
356723
356838
|
// XCUITest's default "open Simulator UI" step fails ("Simulator.app does not
|
|
356724
356839
|
// exist") on this toolchain. The simulator is already booted and driven
|
|
@@ -356974,20 +357089,102 @@ async function startScreenRecording(sessionId, platform3, log2) {
|
|
|
356974
357089
|
if (platform3 === "ANDROID") {
|
|
356975
357090
|
options.bitRate = readPositiveIntEnv("VALIDATEQA_MOBILE_RECORDING_BITRATE", 75e4, 1e5, 4e6);
|
|
356976
357091
|
} else {
|
|
356977
|
-
options.
|
|
357092
|
+
options.videoType = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_VIDEO_TYPE || "libx264";
|
|
357093
|
+
options.videoQuality = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium";
|
|
357094
|
+
options.videoFilters = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_FILTERS || "scale=trunc(iw/2)*2:trunc(ih/2)*2";
|
|
356978
357095
|
}
|
|
356979
357096
|
try {
|
|
356980
357097
|
await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
|
|
356981
357098
|
method: "POST",
|
|
356982
357099
|
body: JSON.stringify({ options })
|
|
356983
357100
|
});
|
|
356984
|
-
log2(`[mobile-video] screen recording started (${timeLimit}s max)`);
|
|
357101
|
+
log2(`[mobile-video] screen recording started (${timeLimit}s max${platform3 === "IOS" ? `, type=${options.videoType}` : ""})`);
|
|
356985
357102
|
return true;
|
|
356986
357103
|
} catch (err) {
|
|
357104
|
+
if (platform3 === "IOS" && options.videoType) {
|
|
357105
|
+
try {
|
|
357106
|
+
const fallback = {
|
|
357107
|
+
timeLimit: String(timeLimit),
|
|
357108
|
+
forceRestart: true,
|
|
357109
|
+
videoQuality: process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium"
|
|
357110
|
+
};
|
|
357111
|
+
await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
|
|
357112
|
+
method: "POST",
|
|
357113
|
+
body: JSON.stringify({ options: fallback })
|
|
357114
|
+
});
|
|
357115
|
+
log2(`[mobile-video] screen recording started with quality-only fallback (${timeLimit}s max); will re-encode on stop`);
|
|
357116
|
+
return true;
|
|
357117
|
+
} catch (fallbackErr) {
|
|
357118
|
+
log2(`[mobile-video] screen recording unavailable: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`);
|
|
357119
|
+
return false;
|
|
357120
|
+
}
|
|
357121
|
+
}
|
|
356987
357122
|
log2(`[mobile-video] screen recording unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
356988
357123
|
return false;
|
|
356989
357124
|
}
|
|
356990
357125
|
}
|
|
357126
|
+
async function ensureBrowserPlayableMp4Base64(base64Video, log2) {
|
|
357127
|
+
if (process.env.VALIDATEQA_MOBILE_VIDEO_REENCODE === "0") {
|
|
357128
|
+
return base64Video;
|
|
357129
|
+
}
|
|
357130
|
+
const inPath = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `validateqa-mobile-rec-in-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
|
|
357131
|
+
const outPath = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), `validateqa-mobile-rec-out-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
|
|
357132
|
+
try {
|
|
357133
|
+
await (0, import_promises3.writeFile)(inPath, Buffer.from(base64Video, "base64"));
|
|
357134
|
+
try {
|
|
357135
|
+
const { stdout } = await execFileAsync3("ffprobe", [
|
|
357136
|
+
"-v",
|
|
357137
|
+
"error",
|
|
357138
|
+
"-select_streams",
|
|
357139
|
+
"v:0",
|
|
357140
|
+
"-show_entries",
|
|
357141
|
+
"stream=codec_name",
|
|
357142
|
+
"-of",
|
|
357143
|
+
"csv=p=0",
|
|
357144
|
+
inPath
|
|
357145
|
+
], { timeout: 15e3 });
|
|
357146
|
+
const codec = stdout.trim().toLowerCase();
|
|
357147
|
+
if (codec === "h264" || codec === "avc1") {
|
|
357148
|
+
log2(`[mobile-video] recording already H.264 (${codec}); skipping re-encode`);
|
|
357149
|
+
return base64Video;
|
|
357150
|
+
}
|
|
357151
|
+
log2(`[mobile-video] re-encoding ${codec || "unknown"} \u2192 H.264 for browser preview`);
|
|
357152
|
+
} catch {
|
|
357153
|
+
log2("[mobile-video] codec probe failed; attempting H.264 re-encode anyway");
|
|
357154
|
+
}
|
|
357155
|
+
await execFileAsync3("ffmpeg", [
|
|
357156
|
+
"-y",
|
|
357157
|
+
"-i",
|
|
357158
|
+
inPath,
|
|
357159
|
+
"-vf",
|
|
357160
|
+
"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
|
357161
|
+
"-c:v",
|
|
357162
|
+
"libx264",
|
|
357163
|
+
"-pix_fmt",
|
|
357164
|
+
"yuv420p",
|
|
357165
|
+
"-preset",
|
|
357166
|
+
"veryfast",
|
|
357167
|
+
"-crf",
|
|
357168
|
+
"28",
|
|
357169
|
+
"-movflags",
|
|
357170
|
+
"+faststart",
|
|
357171
|
+
"-an",
|
|
357172
|
+
outPath
|
|
357173
|
+
], { timeout: 12e4 });
|
|
357174
|
+
const encoded = await (0, import_promises3.readFile)(outPath);
|
|
357175
|
+
if (encoded.length < 100) {
|
|
357176
|
+
log2("[mobile-video] re-encode produced empty file; keeping original");
|
|
357177
|
+
return base64Video;
|
|
357178
|
+
}
|
|
357179
|
+
log2(`[mobile-video] re-encoded for browser (${Math.round(encoded.length / 1024)}KB)`);
|
|
357180
|
+
return encoded.toString("base64");
|
|
357181
|
+
} catch (err) {
|
|
357182
|
+
log2(`[mobile-video] re-encode skipped/failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
357183
|
+
return base64Video;
|
|
357184
|
+
} finally {
|
|
357185
|
+
await Promise.allSettled([(0, import_promises3.unlink)(inPath), (0, import_promises3.unlink)(outPath)]);
|
|
357186
|
+
}
|
|
357187
|
+
}
|
|
356991
357188
|
async function stopScreenRecording(sessionId, log2) {
|
|
356992
357189
|
try {
|
|
356993
357190
|
const result = await wdRequest(`/session/${sessionId}/appium/stop_recording_screen`, {
|
|
@@ -357000,7 +357197,7 @@ async function stopScreenRecording(sessionId, log2) {
|
|
|
357000
357197
|
return void 0;
|
|
357001
357198
|
}
|
|
357002
357199
|
log2(`[mobile-video] screen recording captured (${Math.round(value.length / 1024)}KB base64)`);
|
|
357003
|
-
return value;
|
|
357200
|
+
return ensureBrowserPlayableMp4Base64(value, log2);
|
|
357004
357201
|
} catch (err) {
|
|
357005
357202
|
log2(`[mobile-video] screen recording stop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
357006
357203
|
return void 0;
|
|
@@ -359386,6 +359583,9 @@ async function startSession(validated, res) {
|
|
|
359386
359583
|
...deviceId ? { "appium:udid": deviceId } : {},
|
|
359387
359584
|
...platformVersion ? { "appium:platformVersion": platformVersion } : {},
|
|
359388
359585
|
"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 } : {},
|
|
359389
359589
|
// Same Xcode 27+ workaround as the executor's createSession (appium-executor.ts):
|
|
359390
359590
|
// Simulator.app is no longer at <DEVELOPER_DIR>/Applications, so XCUITest's
|
|
359391
359591
|
// "open Simulator UI" step fails. The sim is already booted via CoreSimulator;
|
|
@@ -361285,7 +361485,7 @@ function envInt(name, fallback) {
|
|
|
361285
361485
|
}
|
|
361286
361486
|
var MOBILE_DEVICE_CEILING = Math.max(0, envInt("MOBILE_DEVICE_CEILING", 0));
|
|
361287
361487
|
var MOBILE_HEAL_HOLD_MS = Math.max(0, envInt("MOBILE_HEAL_HOLD_MS", 2e4));
|
|
361288
|
-
var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS",
|
|
361488
|
+
var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS", 60 * 6e4));
|
|
361289
361489
|
var MOBILE_RUN_ABORT_GRACE_MS = Math.max(0, envInt("MOBILE_RUN_ABORT_GRACE_MS", 2 * 6e4));
|
|
361290
361490
|
var MOBILE_CANCEL_POLL_MS = Math.max(0, envInt("MOBILE_CANCEL_POLL_MS", 15e3));
|
|
361291
361491
|
var MobileRunWatchdog = class {
|