Skip to content

fix(mobilerun): bound the state snapshot so a stuck app can't hang it - #43

Merged
Timo972 merged 2 commits into
masterfrom
timo/droid-305-ios-tiktok-mobilerun-device-ui
Oct 23, 2026
Merged

Timo972 merged 2 commits into
masterfrom
timo/droid-305-ios-tiktok-mobilerun-device-ui

Conversation

@Timo972

@Timo972 Timo972 commented Oct 21, 2026 •

Copy link
Copy Markdown

Fixes DROID-305 (droidrun/mobilerun#484): mobilerun device ui --ios hangs on an iPhone X / iOS 16.7 with TikTok, and on the Home Screen after that.

Cause

GET /mobilerun/state took its snapshot through XCTest (snapshotWithError:). XCTest waits up to its 60s accessibility timeout per request and retries on top of that ("Find the Application … (retry N)"). The reporter's debug log shows WDA silent for 61s on a single TikTok snapshot request.

WDA handles one request at a time, so every later request queued behind the stuck snapshot. That includes requests made after going back to the Home Screen. mobilerun-ios hit its 60s deadline (and its recovery probe did too), and the CLI timed out after 30s on each of its 7 attempts.

Change

  • Snapshots are requested directly (FBXCAXClientProxy), without XCTest's resolution retries, under a temporary accessibility timeout.
    • 10s for the full tree.
    • Then 5s for a shallow tree: 15 levels, 20 children per element.
    • The timeout is global to the runner, so it is restored right after each snapshot. A test covers that.
  • If neither snapshot finishes, the state still names the foreground app (phone_state.packageName). The app is detected without a snapshot, and the app root alone serves as the tree. Released mobilerun-ios rejects a null tree and drops a mostly off-screen childless root, so the root spans a square covering the screen in either orientation.
  • New top-level truncated field. Released mobilerun-ios ignores it.
  • The route always answers 200, so a failed snapshot can no longer surface as an error (the 404 latch from fix(mobilerun): keep a failed state snapshot from answering 404 #38 is moot here).
  • Kept: when the snapshot fails because another app took over (Home press race), that app is snapshotted in full.

Verification

  • Simulator, iOS 27, IntegrationApp's "Deadlock app" (main thread asleep for 20s):
    • Before: the state snapshot waits until the app unfreezes (19.9s; on device this is up to 60s per try).
    • After: answers in 15.03s with truncated: true and the right packageName.
    • A normal app still gets the full tree in 0.01s, with labels, types and bounds.
  • Restore test (testSnapshotTimeoutDoesNotOutliveTheSnapshot): fails with the restore disabled (a later snapshot gave up after 1.01s), passes with it in place.
  • UnitTests 256/256 (1 skipped). FBMobilerunA11yIntegrationTests and FBMobilerunActionsIntegrationTests 7/7. tvOS lib builds.
  • The frozen-app integration tests skip on CI, like the existing accessibilityDeadline test.

Real device: iPhone XR, iOS 18.7.7, TikTok 45.7

Installed through mobilerun-ios setup --reinstall --wda-unsigned-url … (ASC signing), the same path users take.

Reproduced the hang on iOS 18. Right after a cold start, TikTok stops answering accessibility requests ("Error getting main window kAXErrorIPCTimeout") for minutes.

With this PR:

  • GET /mobilerun/state answers in 15.1s every time. The full snapshot gives up at 10s, the shallow one at 5s, and the answer is root-only with packageName: com.zhiliaoapp.musically and truncated: true.
  • Through mobilerun-ios /state_full it takes 15.1–18.8s. The extra time is spent in the portal, not in WDA.
  • Home Screen with TikTok stuck in the background: 0.39–0.45s.
  • mobilerun device ui --ios (mobilerun 0.6.20) with TikTok in front and stuck: returns in 16.5s with TikTok's package name and no retries.
  • Responsive TikTok (feed loaded, plus 5 swipes) and the Home Screen: 0.29–0.48s, full tree (142 TikTok nodes), truncated: false. Same speed as the old runner (0.31–0.75s).

Bug found on the device and fixed in f7e4623: the root-only fallback was sized from the runner's UIScreen, which on a device is the letterboxed 320×480 compatibility screen, so it came out 480×480. nativeBounds is scaled the same way. It now uses SpringBoard's frame (depth-1 snapshot, 2s limit), which gives 896×896 on the XR. The simulator can't show this, because the runner isn't letterboxed there.

Still unverified: iOS 16 (the reporter's iPhone X). If _setAXTimeout fails there, WDA logs it and falls back to the old, unbounded behavior. A preview build goes to the reporter.

Follow-ups (other repos)

  • mobilerun-ios: keep its WDA state deadline below the CLI's 30s, and read truncated.
  • mobilerun CLI: don't retry 7× on ReadTimeout.

🤖 Generated with Claude Code

Timo972 and others added 2 commits October 21, 2026 17:35
GET /mobilerun/state took one snapshot through XCTest, which waits up to
its 60s accessibility timeout per request and retries on top. On an
iPhone X / iOS 16 TikTok never finished snapshotting, so each state
request held the runner for minutes and every request queued behind it,
Home Screen included, timed out in mobilerun-ios and the CLI (DROID-305).

Snapshots are now requested directly, without XCTest's resolution
retries, under a temporary accessibility timeout: 10s for the full tree,
then 5s for a shallow one (15 levels, 20 children per element). If
neither finishes, the state still names the foreground app, which is
known without a snapshot, with the app root alone as the tree.
'truncated' says when the tree is incomplete. The request now always
answers 200, so a failed snapshot can't read as a missing route either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er's screen

On a device the runner app has no launch screen, so iOS runs it in the
letterboxed compatibility mode: its UIScreen bounds and nativeBounds both
describe a 320x480 screen. The root-only state for an app that could not
be snapshotted came out 480x480 on an iPhone XR (414x896). SpringBoard's
frame from a depth-1 snapshot (2s limit) gives the real screen, and it
answers while the app is stuck.

Verified on an iPhone XR (iOS 18.7.7): right after a cold start TikTok
stops answering accessibility requests ("Error getting main window
kAXErrorIPCTimeout"). /mobilerun/state now answers in 15.1s with
packageName com.zhiliaoapp.musically and an 896x896 root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Timo972
Timo972 marked this pull request as ready for review October 22, 2026 06:57
@Timo972
Timo972 requested a review from niko-droidrun October 22, 2026 16:20
@Timo972
Timo972 merged commit eecc5d9 into master Oct 23, 2026
49 of 61 checks passed
Timo972 added a commit that referenced this pull request Oct 23, 2026
Resolve the conflict with #43 (bounded state snapshot): the focused node is
picked from the snapshot master now passes into
fb_mobilerunA11yStateWithSnapshot:scale:truncated:, and the tree keeps
master's SpringBoard-sized fallback root when no snapshot could be taken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Timo972
Timo972 deleted the timo/droid-305-ios-tiktok-mobilerun-device-ui branch October 23, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants