@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.
Files changed (3) hide show
  1. package/dist/cli.js +268 -68
  2. package/dist/cli.mjs +268 -68
  3. 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 actionable = enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
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 = 12e3;
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). Use between exploration episodes when you want to start fresh. Re-snapshot afterward.",
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; mobile_relaunch only for a clean restart between episodes. When no new screens or safe navigation controls remain, call finish_exploration.
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 or mobile_relaunch.
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
- MOBILE_RELAUNCH_TOOL,
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 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);
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
- return labelled ?? elements.find((el) => !weakAssertionTargetReason(el)) ?? null;
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: (0, mobile_source_parser_js_2.preferredLocator)(anchor),
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: () => writeFile2,
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: readFile2,
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 readFile2(fileName, _encoding) {
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 writeFile2(host, diagnostics, fileName, text, writeByteOrderMark, sourceFiles, data) {
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, readFile2) {
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, readFile2 || ((fileName2) => sys.readFile(fileName2)));
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, readFile2) {
148779
- return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile2);
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, readFile2) {
148862
- const textOrDiagnostic = tryReadFile(fileName, readFile2);
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, readFile2) {
148878
- const textOrDiagnostic = tryReadFile(fileName, readFile2);
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, readFile2) {
148975
+ function tryReadFile(fileName, readFile3) {
148882
148976
  let text;
148883
148977
  try {
148884
- text = readFile2(fileName);
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
- writeFile2(
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
- writeFile2(
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
- writeFile2(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
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(readFile2, setParentNodes) {
238636
+ function createGetSourceFile(readFile3, setParentNodes) {
238543
238637
  return (fileName, languageVersionOrOptions, onError) => {
238544
238638
  let text;
238545
238639
  try {
238546
238640
  mark("beforeIORead");
238547
- text = readFile2(fileName);
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 readFile2 = host.readFile.bind(host);
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: readFile2,
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: () => writeFile2,
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.relaunch();
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 relaunch failed while preserving app data: ${err instanceof Error ? err.message : String(err)}`);
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 unlink2 = opt.unlink;
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, unlink2, cwd, null, done);
347661
+ mkdir_(cwd, parts, mode, cache, unlink3, cwd, null, done);
347567
347662
  };
347568
- var mkdir_ = (base2, parts, mode, cache, unlink2, cwd, created, cb) => {
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, unlink2, cwd, created, cb);
347670
+ return mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
347576
347671
  }
347577
- fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink2, cwd, created, cb));
347672
+ fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
347578
347673
  };
347579
- var onmkdir = (part, parts, mode, cache, unlink2, cwd, created, cb) => (er) => {
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, unlink2, cwd, created, cb);
347587
- } else if (unlink2) {
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, unlink2, cwd, created, cb));
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, unlink2, cwd, created, cb);
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 unlink2 = opt.unlink;
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 (unlink2) {
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.81",
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 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
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}/value`, {
354457
+ await appiumRequest(`${sessionPath}/element/${elementId}/clear`, {
354360
354458
  method: "POST",
354361
- body: JSON.stringify({ text: value })
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
- await sendKeysToFocusedInput(sessionPath, value);
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.videoQuality = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "low";
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", 30 * 6e4));
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 {