@validate.qa/runner 1.0.80 → 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 +269 -74
  2. package/dist/cli.mjs +269 -74
  3. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -1024,7 +1024,8 @@ 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 actionable = enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
1027
+ 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);
1028
+ const actionable = !isAnrChrome && enabled && hasArea && (clickable || longClickable || classActionable || focusable && classActionable || focusable && hasHumanIdentity && !isGenericView || webHybridLabelledControl);
1028
1029
  return {
1029
1030
  role,
1030
1031
  label,
@@ -1202,6 +1203,7 @@ var require_mobile_source_parser = __commonJS({
1202
1203
  element.secure = fields.secure;
1203
1204
  if (fields.enabled !== void 0)
1204
1205
  element.enabled = fields.enabled;
1206
+ element.visible = fields.visible;
1205
1207
  if (isCredibleStructuralNavigationTarget(element) && chainHasNativeNavigationSemantics(chain, platform3)) {
1206
1208
  markNavigation(element, "native-navigation-semantics");
1207
1209
  }
@@ -1576,6 +1578,8 @@ var require_mobile_boundary = __commonJS({
1576
1578
  "../runner-core/dist/services/mobile/mobile-boundary.js"(exports) {
1577
1579
  "use strict";
1578
1580
  Object.defineProperty(exports, "__esModule", { value: true });
1581
+ exports.isAndroidAnrSystemResourceId = isAndroidAnrSystemResourceId;
1582
+ exports.looksLikeAndroidAnrUi = looksLikeAndroidAnrUi;
1579
1583
  exports.mobileOwnerFromScreenSignal = mobileOwnerFromScreenSignal;
1580
1584
  exports.isTransientMobileSystemOwner = isTransientMobileSystemOwner;
1581
1585
  exports.classifyMobileScreenBoundary = classifyMobileScreenBoundary2;
@@ -1593,6 +1597,28 @@ var require_mobile_boundary = __commonJS({
1593
1597
  "com.miui.securitycenter",
1594
1598
  "com.samsung.android.permissioncontroller"
1595
1599
  ]);
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
+ }
1596
1622
  var IOS_TRANSIENT_SYSTEM_APP_IDS = /* @__PURE__ */ new Set([
1597
1623
  "com.apple.springboard",
1598
1624
  "com.apple.authenticationservicesui",
@@ -1750,7 +1776,7 @@ var init_constants = __esm({
1750
1776
  MOBILE_APP_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,253}[A-Za-z0-9])?$/;
1751
1777
  MOBILE_FOCUS_SETTLE_MS = 150;
1752
1778
  MOBILE_POST_ACTION_SETTLE_MS = 1800;
1753
- MOBILE_COLD_BOOT_SETTLE_MS = 12e3;
1779
+ MOBILE_COLD_BOOT_SETTLE_MS = 25e3;
1754
1780
  MOBILE_DEFAULT_SWIPE_DISTANCE = 500;
1755
1781
  MOBILE_MAX_SWIPE_DISTANCE = 2e3;
1756
1782
  SELECTOR_PRIORITIES = [
@@ -74892,7 +74918,7 @@ var require_mobile_explorer = __commonJS({
74892
74918
  type: "function",
74893
74919
  function: {
74894
74920
  name: "mobile_relaunch",
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.",
74921
+ 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.",
74896
74922
  parameters: { type: "object", properties: {} }
74897
74923
  }
74898
74924
  };
@@ -75692,11 +75718,11 @@ Rules:
75692
75718
  **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.
75693
75719
  **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.
75694
75720
 
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.
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. 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.
75696
75722
 
75697
75723
  ## Boundaries (survey = map, do not mutate)
75698
75724
  - Do NOT submit forms, save, delete, purchase, or send anything. You MAY open modals/menus to catalog them, then dismiss.
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.
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 (not a cold relaunch).
75700
75726
  - If you hit a login wall and have no credentials, capture the auth screen and explore what is reachable.
75701
75727
 
75702
75728
  Max iterations: ${maxIterations}. You will be warned near the end.`;
@@ -76107,6 +76133,8 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
76107
76133
  const kickoff = buildKickoffMessage(mode);
76108
76134
  const recentExchanges = [];
76109
76135
  const transientDirectives = [];
76136
+ const resetPolicyForTools = ctx.target?.resetPolicy ?? "AUTO";
76137
+ const preserveAppDataForTools = resetPolicyForTools === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicyForTools === "AUTO";
76110
76138
  const tools = [
76111
76139
  MOBILE_SNAPSHOT_TOOL,
76112
76140
  MOBILE_TAP_TOOL,
@@ -76116,7 +76144,8 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
76116
76144
  MOBILE_SWIPE_TOOL,
76117
76145
  MOBILE_BACK_TOOL,
76118
76146
  MOBILE_HOME_TOOL,
76119
- MOBILE_RELAUNCH_TOOL,
76147
+ // Omit relaunch under preserve — model otherwise cold-restarts RN into splash.
76148
+ ...preserveAppDataForTools ? [] : [MOBILE_RELAUNCH_TOOL],
76120
76149
  MOBILE_OPEN_DEEPLINK_TOOL,
76121
76150
  CAPTURE_SCREEN_TOOL,
76122
76151
  RECORD_TRANSITION_TOOL,
@@ -76472,7 +76501,8 @@ Exploration complete: ${summary}`);
76472
76501
  credentials: credentialSecrets,
76473
76502
  redactionCredentials: credentialValuesForRedaction,
76474
76503
  authTracker,
76475
- inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null
76504
+ inbox: inboxEnabled && inboxState && inboxReader ? { state: inboxState, reader: inboxReader } : null,
76505
+ allowRelaunch: !preserveAppDataForTools
76476
76506
  });
76477
76507
  pushResult(handled);
76478
76508
  const handledRecord = handled && typeof handled === "object" ? handled : null;
@@ -76565,7 +76595,7 @@ Exploration complete: ${summary}`);
76565
76595
  return result;
76566
76596
  }
76567
76597
  async function dispatchMobileTool(args) {
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;
76598
+ 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;
76569
76599
  const absorbActionResult = (actionResult, source, preferCredentialSecondary = false) => {
76570
76600
  (0, mobile_driver_errors_js_1.throwIfTerminalMobileActionFailure)(actionResult, source);
76571
76601
  const resolved = buildResolved(actionResult.source, latest()?.screen.windowSize ?? null, actionResult.screenSignal);
@@ -76968,6 +76998,13 @@ Exploration complete: ${summary}`);
76968
76998
  }
76969
76999
  // ----- Relaunch (hermetic reset) -----
76970
77000
  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
+ }
76971
77008
  authTracker?.noteInterveningMutation(iteration);
76972
77009
  await driver.relaunch();
76973
77010
  log2(" Relaunched app for hermetic reset.");
@@ -82030,7 +82067,7 @@ Start by calling mobile_snapshot, then execute the steps above \u2014 the minimu
82030
82067
  }
82031
82068
  }
82032
82069
  async function runScenarioLoop(args) {
82033
- const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog } = args;
82070
+ const { scenario, driver, platform: platform3, maxIterations, agentModel, provider, aiClient, auditGroupId, onAICall, onScreenshot, targetAppId, credentials, redactionCredentials, log: emitLog, allowRelaunch } = args;
82034
82071
  const log2 = (line) => emitLog(redactCredentialValues(line, redactionCredentials));
82035
82072
  const actions = [];
82036
82073
  let assertCount = 0;
@@ -82604,6 +82641,7 @@ ${statePacket}`, redactionCredentials) },
82604
82641
  setLatest,
82605
82642
  credentials,
82606
82643
  screenName: () => lastScreenName || "UnknownScreen",
82644
+ allowRelaunch,
82607
82645
  recordAction: (action) => {
82608
82646
  const screenHash = getLatest()?.signal.screenHash;
82609
82647
  const structuralKey = structuralScreenKey(getLatest());
@@ -82638,7 +82676,7 @@ ${statePacket}`, redactionCredentials) },
82638
82676
  return { actions, assertCount, finish, lastSnapshot: lastTargetSnapshot, lastScreenName: lastTargetScreenName };
82639
82677
  }
82640
82678
  async function dispatchGenerateTool(args) {
82641
- const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName, recordAction, recordedActions, log: log2 } = args;
82679
+ const { toolName, toolArgs, driver, platform: platform3, targetAppId, latest, absorbObservation, resolveRef, resolveHybridRef, maybeEnrichHybridWeb, setLatest, credentials, screenName, allowRelaunch, recordAction, recordedActions, log: log2 } = args;
82642
82680
  const unknownRef = (ref) => ({
82643
82681
  ok: false,
82644
82682
  error: `Unknown element ref "${String(ref)}". Call mobile_snapshot first; refs are only valid for the latest snapshot.`
@@ -83028,6 +83066,16 @@ ${statePacket}`, redactionCredentials) },
83028
83066
  return actionPayload(result, absorbed);
83029
83067
  }
83030
83068
  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
+ }
83031
83079
  await driver.relaunch();
83032
83080
  const snap = await driver.snapshot();
83033
83081
  const absorbed = await absorbObservation(snap.xml, snap.screenSignal, snap.windowSize, snap.screenshot, "mobile_relaunch");
@@ -83246,14 +83294,48 @@ ${statePacket}`, redactionCredentials) },
83246
83294
  });
83247
83295
  return { ok: true, label: metadata.targetLabel ?? accessibilityId ?? text ?? "element" };
83248
83296
  }
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
+ }
83249
83326
  function pickLandingAnchor(snapshot) {
83250
83327
  if (!snapshot || snapshot.screen.elements.length === 0)
83251
83328
  return null;
83252
83329
  const elements = snapshot.screen.elements;
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);
83330
+ const windowSize = snapshot.screen.windowSize ?? null;
83331
+ const stableAnchors = elements.filter((el) => hasStableId(el) && !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize)).sort((a, b) => {
83332
+ const ka = landingAnchorSortKey(a);
83333
+ const kb = landingAnchorSortKey(b);
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;
83257
83339
  });
83258
83340
  const contentfulNonButton = stableAnchors.find((el) => !/button/i.test(el.role) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
83259
83341
  if (contentfulNonButton)
@@ -83263,8 +83345,10 @@ ${statePacket}`, redactionCredentials) },
83263
83345
  return nonButton;
83264
83346
  if (stableAnchors.length > 0)
83265
83347
  return stableAnchors[0];
83266
- const labelled = elements.find((el) => !weakAssertionTargetReason(el) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
83267
- return labelled ?? elements.find((el) => !weakAssertionTargetReason(el)) ?? null;
83348
+ const labelled = elements.find((el) => !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize) && (nonEmpty3(el.label) || nonEmpty3(el.text)));
83349
+ if (labelled)
83350
+ return labelled;
83351
+ return elements.find((el) => !weakAssertionTargetReason(el) && isDisplayableLandingCandidate(el, windowSize)) ?? null;
83268
83352
  }
83269
83353
  var ALWAYS_PRESENT_CONTAINER_ROLES = /^(XCUIElementTypeApplication|XCUIElementTypeWindow)$/;
83270
83354
  var ALWAYS_PRESENT_CONTAINER_ID_NAMES = /* @__PURE__ */ new Set([
@@ -83362,10 +83446,19 @@ ${statePacket}`, redactionCredentials) },
83362
83446
  const anchor = pickLandingAnchor(recording.lastSnapshot);
83363
83447
  if (!anchor)
83364
83448
  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;
83365
83458
  const screen = recording.lastScreenName || "LandingScreen";
83366
83459
  actions.push(finalizeAction({
83367
83460
  type: "ASSERT_VISIBLE",
83368
- selector: (0, mobile_source_parser_js_2.preferredLocator)(anchor),
83461
+ selector: anchorSelector,
83369
83462
  metadata: metadataForElement(anchor, screen, platform3, nativeElementsOf(recording.lastSnapshot))
83370
83463
  }));
83371
83464
  return true;
@@ -84241,7 +84334,8 @@ ${statePacket}`, redactionCredentials) },
84241
84334
  targetAppId,
84242
84335
  credentials: credentialSecrets,
84243
84336
  redactionCredentials: credentialValuesForRedaction,
84244
- log: log2
84337
+ log: log2,
84338
+ allowRelaunch: (ctx.target?.resetPolicy ?? "AUTO") !== "PRESERVE_APP_DATA"
84245
84339
  });
84246
84340
  consecutiveInfraFailures = 0;
84247
84341
  const test = assembleTest({
@@ -105535,7 +105629,7 @@ var require_typescript = __commonJS({
105535
105629
  walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
105536
105630
  whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
105537
105631
  writeCommentRange: () => writeCommentRange,
105538
- writeFile: () => writeFile2,
105632
+ writeFile: () => writeFile3,
105539
105633
  writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
105540
105634
  zipWith: () => zipWith
105541
105635
  });
@@ -111867,7 +111961,7 @@ ${lanes.join("\n")}
111867
111961
  writeOutputIsTTY() {
111868
111962
  return process.stdout.isTTY;
111869
111963
  },
111870
- readFile: readFile2,
111964
+ readFile: readFile3,
111871
111965
  writeFile: writeFile22,
111872
111966
  watchFile: watchFile2,
111873
111967
  watchDirectory,
@@ -112073,7 +112167,7 @@ ${lanes.join("\n")}
112073
112167
  callback
112074
112168
  );
112075
112169
  }
112076
- function readFile2(fileName, _encoding) {
112170
+ function readFile3(fileName, _encoding) {
112077
112171
  let buffer;
112078
112172
  try {
112079
112173
  buffer = _fs.readFileSync(fileName);
@@ -124178,7 +124272,7 @@ ${lanes.join("\n")}
124178
124272
  sourceFilePath = isSourceFileInCommonSourceDirectory ? sourceFilePath.substring(commonSourceDirectory.length) : sourceFilePath;
124179
124273
  return combinePaths(newDirPath, sourceFilePath);
124180
124274
  }
124181
- function writeFile2(host, diagnostics, fileName, text, writeByteOrderMark, sourceFiles, data) {
124275
+ function writeFile3(host, diagnostics, fileName, text, writeByteOrderMark, sourceFiles, data) {
124182
124276
  host.writeFile(
124183
124277
  fileName,
124184
124278
  text,
@@ -148629,7 +148723,7 @@ ${lanes.join("\n")}
148629
148723
  const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
148630
148724
  return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
148631
148725
  }
148632
- function parseCommandLineWorker(diagnostics, commandLine, readFile2) {
148726
+ function parseCommandLineWorker(diagnostics, commandLine, readFile3) {
148633
148727
  const options = {};
148634
148728
  let watchOptions;
148635
148729
  const fileNames = [];
@@ -148677,7 +148771,7 @@ ${lanes.join("\n")}
148677
148771
  }
148678
148772
  }
148679
148773
  function parseResponseFile(fileName) {
148680
- const text = tryReadFile(fileName, readFile2 || ((fileName2) => sys.readFile(fileName2)));
148774
+ const text = tryReadFile(fileName, readFile3 || ((fileName2) => sys.readFile(fileName2)));
148681
148775
  if (!isString(text)) {
148682
148776
  errors2.push(text);
148683
148777
  return;
@@ -148780,8 +148874,8 @@ ${lanes.join("\n")}
148780
148874
  unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
148781
148875
  optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
148782
148876
  };
148783
- function parseCommandLine(commandLine, readFile2) {
148784
- return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile2);
148877
+ function parseCommandLine(commandLine, readFile3) {
148878
+ return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile3);
148785
148879
  }
148786
148880
  function getOptionFromName(optionName, allowShort) {
148787
148881
  return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
@@ -148863,8 +148957,8 @@ ${lanes.join("\n")}
148863
148957
  watchOptionsToExtend
148864
148958
  );
148865
148959
  }
148866
- function readConfigFile(fileName, readFile2) {
148867
- const textOrDiagnostic = tryReadFile(fileName, readFile2);
148960
+ function readConfigFile(fileName, readFile3) {
148961
+ const textOrDiagnostic = tryReadFile(fileName, readFile3);
148868
148962
  return isString(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
148869
148963
  }
148870
148964
  function parseConfigFileTextToJson(fileName, jsonText) {
@@ -148879,14 +148973,14 @@ ${lanes.join("\n")}
148879
148973
  error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : void 0
148880
148974
  };
148881
148975
  }
148882
- function readJsonConfigFile(fileName, readFile2) {
148883
- const textOrDiagnostic = tryReadFile(fileName, readFile2);
148976
+ function readJsonConfigFile(fileName, readFile3) {
148977
+ const textOrDiagnostic = tryReadFile(fileName, readFile3);
148884
148978
  return isString(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
148885
148979
  }
148886
- function tryReadFile(fileName, readFile2) {
148980
+ function tryReadFile(fileName, readFile3) {
148887
148981
  let text;
148888
148982
  try {
148889
- text = readFile2(fileName);
148983
+ text = readFile3(fileName);
148890
148984
  } catch (e) {
148891
148985
  return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e.message);
148892
148986
  }
@@ -232929,7 +233023,7 @@ ${lanes.join("\n")}
232929
233023
  return;
232930
233024
  }
232931
233025
  const buildInfo = host.getBuildInfo() || { version };
232932
- writeFile2(
233026
+ writeFile3(
232933
233027
  host,
232934
233028
  emitterDiagnostics,
232935
233029
  buildInfoPath,
@@ -233141,7 +233235,7 @@ ${lanes.join("\n")}
233141
233235
  }
233142
233236
  if (sourceMapFilePath) {
233143
233237
  const sourceMap = sourceMapGenerator.toString();
233144
- writeFile2(
233238
+ writeFile3(
233145
233239
  host,
233146
233240
  emitterDiagnostics,
233147
233241
  sourceMapFilePath,
@@ -233156,7 +233250,7 @@ ${lanes.join("\n")}
233156
233250
  }
233157
233251
  const text = writer.getText();
233158
233252
  const data = { sourceMapUrlPos, diagnostics: transform2.diagnostics };
233159
- writeFile2(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
233253
+ writeFile3(host, emitterDiagnostics, jsFilePath, text, !!compilerOptions.emitBOM, sourceFiles, data);
233160
233254
  writer.clear();
233161
233255
  return !data.skippedDtsWrite;
233162
233256
  }
@@ -238544,12 +238638,12 @@ ${lanes.join("\n")}
238544
238638
  function createCompilerHost(options, setParentNodes) {
238545
238639
  return createCompilerHostWorker(options, setParentNodes);
238546
238640
  }
238547
- function createGetSourceFile(readFile2, setParentNodes) {
238641
+ function createGetSourceFile(readFile3, setParentNodes) {
238548
238642
  return (fileName, languageVersionOrOptions, onError) => {
238549
238643
  let text;
238550
238644
  try {
238551
238645
  mark("beforeIORead");
238552
- text = readFile2(fileName);
238646
+ text = readFile3(fileName);
238553
238647
  mark("afterIORead");
238554
238648
  measure("I/O Read", "beforeIORead", "afterIORead");
238555
238649
  } catch (e) {
@@ -239453,7 +239547,7 @@ ${lanes.join("\n")}
239453
239547
  getRedirectFromOutput,
239454
239548
  forEachResolvedProjectReference: forEachResolvedProjectReference2
239455
239549
  });
239456
- const readFile2 = host.readFile.bind(host);
239550
+ const readFile3 = host.readFile.bind(host);
239457
239551
  (_e = tracing) == null ? void 0 : _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
239458
239552
  const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
239459
239553
  (_f = tracing) == null ? void 0 : _f.pop();
@@ -239679,7 +239773,7 @@ ${lanes.join("\n")}
239679
239773
  shouldTransformImportCall,
239680
239774
  emitBuildInfo,
239681
239775
  fileExists,
239682
- readFile: readFile2,
239776
+ readFile: readFile3,
239683
239777
  directoryExists,
239684
239778
  getSymlinkCache,
239685
239779
  realpath: (_o = host.realpath) == null ? void 0 : _o.bind(host),
@@ -301038,7 +301132,7 @@ ${options.prefix}` : "\n" : options.prefix
301038
301132
  walkUpParenthesizedTypesAndGetParentAndChild: () => walkUpParenthesizedTypesAndGetParentAndChild,
301039
301133
  whitespaceOrMapCommentRegExp: () => whitespaceOrMapCommentRegExp,
301040
301134
  writeCommentRange: () => writeCommentRange,
301041
- writeFile: () => writeFile2,
301135
+ writeFile: () => writeFile3,
301042
301136
  writeFileEnsuringDirectories: () => writeFileEnsuringDirectories,
301043
301137
  zipWith: () => zipWith
301044
301138
  });
@@ -337174,10 +337268,11 @@ Fatal error: ${msg}`);
337174
337268
  const shouldPreserveAppData = resetPolicy === "PRESERVE_APP_DATA" || mode === "survey" && resetPolicy === "AUTO";
337175
337269
  if (shouldPreserveAppData) {
337176
337270
  try {
337177
- await driver.relaunch();
337271
+ await driver.launchApp();
337272
+ log2("App activated with preserved data (no terminate/clear).");
337178
337273
  } catch (err) {
337179
337274
  (0, mobile_driver_errors_js_1.rethrowIfTerminalMobileDriverError)(err);
337180
- log2(`App relaunch failed while preserving app data: ${err instanceof Error ? err.message : String(err)}`);
337275
+ log2(`App activate failed while preserving app data: ${err instanceof Error ? err.message : String(err)}`);
337181
337276
  }
337182
337277
  return false;
337183
337278
  }
@@ -347540,7 +347635,7 @@ var require_mkdir = __commonJS({
347540
347635
  const gid = opt.gid;
347541
347636
  const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
347542
347637
  const preserve = opt.preserve;
347543
- const unlink2 = opt.unlink;
347638
+ const unlink3 = opt.unlink;
347544
347639
  const cache = opt.cache;
347545
347640
  const cwd = normPath(opt.cwd);
347546
347641
  const done = (er, created) => {
@@ -347568,33 +347663,33 @@ var require_mkdir = __commonJS({
347568
347663
  }
347569
347664
  const sub = normPath(path.relative(cwd, dir));
347570
347665
  const parts = sub.split("/");
347571
- mkdir_(cwd, parts, mode, cache, unlink2, cwd, null, done);
347666
+ mkdir_(cwd, parts, mode, cache, unlink3, cwd, null, done);
347572
347667
  };
347573
- var mkdir_ = (base2, parts, mode, cache, unlink2, cwd, created, cb) => {
347668
+ var mkdir_ = (base2, parts, mode, cache, unlink3, cwd, created, cb) => {
347574
347669
  if (!parts.length) {
347575
347670
  return cb(null, created);
347576
347671
  }
347577
347672
  const p = parts.shift();
347578
347673
  const part = normPath(path.resolve(base2 + "/" + p));
347579
347674
  if (cGet(cache, part)) {
347580
- return mkdir_(part, parts, mode, cache, unlink2, cwd, created, cb);
347675
+ return mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
347581
347676
  }
347582
- fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink2, cwd, created, cb));
347677
+ fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
347583
347678
  };
347584
- var onmkdir = (part, parts, mode, cache, unlink2, cwd, created, cb) => (er) => {
347679
+ var onmkdir = (part, parts, mode, cache, unlink3, cwd, created, cb) => (er) => {
347585
347680
  if (er) {
347586
347681
  fs.lstat(part, (statEr, st) => {
347587
347682
  if (statEr) {
347588
347683
  statEr.path = statEr.path && normPath(statEr.path);
347589
347684
  cb(statEr);
347590
347685
  } else if (st.isDirectory()) {
347591
- mkdir_(part, parts, mode, cache, unlink2, cwd, created, cb);
347592
- } else if (unlink2) {
347686
+ mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
347687
+ } else if (unlink3) {
347593
347688
  fs.unlink(part, (er2) => {
347594
347689
  if (er2) {
347595
347690
  return cb(er2);
347596
347691
  }
347597
- fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink2, cwd, created, cb));
347692
+ fs.mkdir(part, mode, onmkdir(part, parts, mode, cache, unlink3, cwd, created, cb));
347598
347693
  });
347599
347694
  } else if (st.isSymbolicLink()) {
347600
347695
  return cb(new SymlinkError(part, part + "/" + parts.join("/")));
@@ -347604,7 +347699,7 @@ var require_mkdir = __commonJS({
347604
347699
  });
347605
347700
  } else {
347606
347701
  created = created || part;
347607
- mkdir_(part, parts, mode, cache, unlink2, cwd, created, cb);
347702
+ mkdir_(part, parts, mode, cache, unlink3, cwd, created, cb);
347608
347703
  }
347609
347704
  };
347610
347705
  var checkCwdSync = (dir) => {
@@ -347629,7 +347724,7 @@ var require_mkdir = __commonJS({
347629
347724
  const gid = opt.gid;
347630
347725
  const doChown = typeof uid === "number" && typeof gid === "number" && (uid !== opt.processUid || gid !== opt.processGid);
347631
347726
  const preserve = opt.preserve;
347632
- const unlink2 = opt.unlink;
347727
+ const unlink3 = opt.unlink;
347633
347728
  const cache = opt.cache;
347634
347729
  const cwd = normPath(opt.cwd);
347635
347730
  const done = (created2) => {
@@ -347668,7 +347763,7 @@ var require_mkdir = __commonJS({
347668
347763
  if (st.isDirectory()) {
347669
347764
  cSet(cache, part, true);
347670
347765
  continue;
347671
- } else if (unlink2) {
347766
+ } else if (unlink3) {
347672
347767
  fs.unlinkSync(part);
347673
347768
  fs.mkdirSync(part, mode);
347674
347769
  created = created || part;
@@ -353342,7 +353437,7 @@ var require_package4 = __commonJS({
353342
353437
  "package.json"(exports, module) {
353343
353438
  module.exports = {
353344
353439
  name: "@validate.qa/runner",
353345
- version: "1.0.80",
353440
+ version: "1.0.82",
353346
353441
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
353347
353442
  bin: {
353348
353443
  "validate-runner": "dist/cli.js"
@@ -354208,9 +354303,9 @@ async function waitForColdBootSettle(sessionPath) {
354208
354303
  timeoutMs: MOBILE_COLD_BOOT_SETTLE_MS,
354209
354304
  minStableFrames: 2,
354210
354305
  pollMs: 400,
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
354306
+ // Native splash / WebView shell is ~5–8 nodes; RN product screens and rich
354307
+ // onboarding typically flatten to 15+ once JS has painted.
354308
+ minElements: 15
354214
354309
  });
354215
354310
  }
354216
354311
  async function getWindowSize(sessionPath) {
@@ -354359,20 +354454,33 @@ async function sendKeysToFocusedInput(sessionPath, value) {
354359
354454
  }
354360
354455
  async function typeText(sessionPath, value, bounds) {
354361
354456
  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
+ }
354362
354460
  if (elementId) {
354363
354461
  try {
354364
- await appiumRequest(`${sessionPath}/element/${elementId}/value`, {
354462
+ await appiumRequest(`${sessionPath}/element/${elementId}/clear`, {
354365
354463
  method: "POST",
354366
- body: JSON.stringify({ text: value })
354464
+ body: JSON.stringify({})
354367
354465
  });
354368
- return;
354369
354466
  } catch (err) {
354370
354467
  if (isAppiumDriverDeadError(err)) throw err;
354371
354468
  }
354372
- } else if (bounds === void 0) {
354373
- throw new Error("No focused element available for TYPE. Tap an input field first.");
354374
354469
  }
354375
- await sendKeysToFocusedInput(sessionPath, value);
354470
+ try {
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.");
354376
354484
  }
354377
354485
  async function submitKeyboard(sessionPath, bounds) {
354378
354486
  const elementId = await resolveTypeTargetElementId(sessionPath, bounds);
@@ -355291,6 +355399,7 @@ import { execFile as execFile3 } from "child_process";
355291
355399
  import { promisify as promisify3 } from "util";
355292
355400
  import { tmpdir as tmpdir3 } from "os";
355293
355401
  import { join as join5 } from "path";
355402
+ import { readFile as readFile2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
355294
355403
 
355295
355404
  // src/services/device-discovery.ts
355296
355405
  import { execFileSync as execFileSync2 } from "child_process";
@@ -356727,6 +356836,12 @@ async function createSession(target) {
356727
356836
  // whole survey. The runner owns session teardown explicitly, so a long
356728
356837
  // idle window is safe. Env-overridable.
356729
356838
  "appium:newCommandTimeout": MOBILE_NEW_COMMAND_TIMEOUT_SECONDS,
356839
+ // Expo / universal-link "Open in App?" system sheets and permission dialogs
356840
+ // otherwise sit on top of the app and can stall WDA snapshot/command proxy
356841
+ // for the entire discovery session (observed: 20+ min hung XCUITest with
356842
+ // ECONNREFUSED on wdaLocalPort). Auto-accept keeps the AUT interactive.
356843
+ // Override with APPIUM_IOS_AUTO_ACCEPT_ALERTS=false for explicit dialog tests.
356844
+ ...process.env.APPIUM_IOS_AUTO_ACCEPT_ALERTS !== "false" ? { "appium:autoAcceptAlerts": true } : {},
356730
356845
  // Xcode 27+ relocates Simulator.app out of <DEVELOPER_DIR>/Applications, so
356731
356846
  // XCUITest's default "open Simulator UI" step fails ("Simulator.app does not
356732
356847
  // exist") on this toolchain. The simulator is already booted and driven
@@ -356982,20 +357097,102 @@ async function startScreenRecording(sessionId, platform3, log2) {
356982
357097
  if (platform3 === "ANDROID") {
356983
357098
  options.bitRate = readPositiveIntEnv("VALIDATEQA_MOBILE_RECORDING_BITRATE", 75e4, 1e5, 4e6);
356984
357099
  } else {
356985
- options.videoQuality = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "low";
357100
+ options.videoType = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_VIDEO_TYPE || "libx264";
357101
+ options.videoQuality = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium";
357102
+ options.videoFilters = process.env.VALIDATEQA_MOBILE_RECORDING_IOS_FILTERS || "scale=trunc(iw/2)*2:trunc(ih/2)*2";
356986
357103
  }
356987
357104
  try {
356988
357105
  await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
356989
357106
  method: "POST",
356990
357107
  body: JSON.stringify({ options })
356991
357108
  });
356992
- log2(`[mobile-video] screen recording started (${timeLimit}s max)`);
357109
+ log2(`[mobile-video] screen recording started (${timeLimit}s max${platform3 === "IOS" ? `, type=${options.videoType}` : ""})`);
356993
357110
  return true;
356994
357111
  } catch (err) {
357112
+ if (platform3 === "IOS" && options.videoType) {
357113
+ try {
357114
+ const fallback = {
357115
+ timeLimit: String(timeLimit),
357116
+ forceRestart: true,
357117
+ videoQuality: process.env.VALIDATEQA_MOBILE_RECORDING_IOS_QUALITY || "medium"
357118
+ };
357119
+ await wdRequest(`/session/${sessionId}/appium/start_recording_screen`, {
357120
+ method: "POST",
357121
+ body: JSON.stringify({ options: fallback })
357122
+ });
357123
+ log2(`[mobile-video] screen recording started with quality-only fallback (${timeLimit}s max); will re-encode on stop`);
357124
+ return true;
357125
+ } catch (fallbackErr) {
357126
+ log2(`[mobile-video] screen recording unavailable: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`);
357127
+ return false;
357128
+ }
357129
+ }
356995
357130
  log2(`[mobile-video] screen recording unavailable: ${err instanceof Error ? err.message : String(err)}`);
356996
357131
  return false;
356997
357132
  }
356998
357133
  }
357134
+ async function ensureBrowserPlayableMp4Base64(base64Video, log2) {
357135
+ if (process.env.VALIDATEQA_MOBILE_VIDEO_REENCODE === "0") {
357136
+ return base64Video;
357137
+ }
357138
+ const inPath = join5(tmpdir3(), `validateqa-mobile-rec-in-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
357139
+ const outPath = join5(tmpdir3(), `validateqa-mobile-rec-out-${Date.now()}-${Math.random().toString(36).slice(2)}.mp4`);
357140
+ try {
357141
+ await writeFile2(inPath, Buffer.from(base64Video, "base64"));
357142
+ try {
357143
+ const { stdout } = await execFileAsync3("ffprobe", [
357144
+ "-v",
357145
+ "error",
357146
+ "-select_streams",
357147
+ "v:0",
357148
+ "-show_entries",
357149
+ "stream=codec_name",
357150
+ "-of",
357151
+ "csv=p=0",
357152
+ inPath
357153
+ ], { timeout: 15e3 });
357154
+ const codec = stdout.trim().toLowerCase();
357155
+ if (codec === "h264" || codec === "avc1") {
357156
+ log2(`[mobile-video] recording already H.264 (${codec}); skipping re-encode`);
357157
+ return base64Video;
357158
+ }
357159
+ log2(`[mobile-video] re-encoding ${codec || "unknown"} \u2192 H.264 for browser preview`);
357160
+ } catch {
357161
+ log2("[mobile-video] codec probe failed; attempting H.264 re-encode anyway");
357162
+ }
357163
+ await execFileAsync3("ffmpeg", [
357164
+ "-y",
357165
+ "-i",
357166
+ inPath,
357167
+ "-vf",
357168
+ "scale=trunc(iw/2)*2:trunc(ih/2)*2",
357169
+ "-c:v",
357170
+ "libx264",
357171
+ "-pix_fmt",
357172
+ "yuv420p",
357173
+ "-preset",
357174
+ "veryfast",
357175
+ "-crf",
357176
+ "28",
357177
+ "-movflags",
357178
+ "+faststart",
357179
+ "-an",
357180
+ outPath
357181
+ ], { timeout: 12e4 });
357182
+ const encoded = await readFile2(outPath);
357183
+ if (encoded.length < 100) {
357184
+ log2("[mobile-video] re-encode produced empty file; keeping original");
357185
+ return base64Video;
357186
+ }
357187
+ log2(`[mobile-video] re-encoded for browser (${Math.round(encoded.length / 1024)}KB)`);
357188
+ return encoded.toString("base64");
357189
+ } catch (err) {
357190
+ log2(`[mobile-video] re-encode skipped/failed: ${err instanceof Error ? err.message : String(err)}`);
357191
+ return base64Video;
357192
+ } finally {
357193
+ await Promise.allSettled([unlink2(inPath), unlink2(outPath)]);
357194
+ }
357195
+ }
356999
357196
  async function stopScreenRecording(sessionId, log2) {
357000
357197
  try {
357001
357198
  const result = await wdRequest(`/session/${sessionId}/appium/stop_recording_screen`, {
@@ -357008,7 +357205,7 @@ async function stopScreenRecording(sessionId, log2) {
357008
357205
  return void 0;
357009
357206
  }
357010
357207
  log2(`[mobile-video] screen recording captured (${Math.round(value.length / 1024)}KB base64)`);
357011
- return value;
357208
+ return ensureBrowserPlayableMp4Base64(value, log2);
357012
357209
  } catch (err) {
357013
357210
  log2(`[mobile-video] screen recording stop failed: ${err instanceof Error ? err.message : String(err)}`);
357014
357211
  return void 0;
@@ -359394,6 +359591,9 @@ async function startSession(validated, res) {
359394
359591
  ...deviceId ? { "appium:udid": deviceId } : {},
359395
359592
  ...platformVersion ? { "appium:platformVersion": platformVersion } : {},
359396
359593
  "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 } : {},
359397
359597
  // Same Xcode 27+ workaround as the executor's createSession (appium-executor.ts):
359398
359598
  // Simulator.app is no longer at <DEVELOPER_DIR>/Applications, so XCUITest's
359399
359599
  // "open Simulator UI" step fails. The sim is already booted via CoreSimulator;
@@ -359602,12 +359802,7 @@ function tryStartChannelMjpeg(ch) {
359602
359802
  }
359603
359803
  function hasChannelDemand(ch) {
359604
359804
  if (ch.clients.size > 0) return true;
359605
- if (!ch.relay) return false;
359606
- try {
359607
- return ch.relay.wanted();
359608
- } catch {
359609
- return false;
359610
- }
359805
+ return ch.relay !== null;
359611
359806
  }
359612
359807
  function ensureRelayDemandSupervisor() {
359613
359808
  if (relayDemandTimer) return;
@@ -361298,7 +361493,7 @@ function envInt(name, fallback) {
361298
361493
  }
361299
361494
  var MOBILE_DEVICE_CEILING = Math.max(0, envInt("MOBILE_DEVICE_CEILING", 0));
361300
361495
  var MOBILE_HEAL_HOLD_MS = Math.max(0, envInt("MOBILE_HEAL_HOLD_MS", 2e4));
361301
- var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS", 30 * 6e4));
361496
+ var MOBILE_RUN_WATCHDOG_MS = Math.max(0, envInt("MOBILE_RUN_WATCHDOG_MS", 60 * 6e4));
361302
361497
  var MOBILE_RUN_ABORT_GRACE_MS = Math.max(0, envInt("MOBILE_RUN_ABORT_GRACE_MS", 2 * 6e4));
361303
361498
  var MOBILE_CANCEL_POLL_MS = Math.max(0, envInt("MOBILE_CANCEL_POLL_MS", 15e3));
361304
361499
  var MobileRunWatchdog = class {