Skip to content

fix(ios): read status bars from a throwing snapshot so a keyboard race cannot kill the runner - #6

Merged
jochen-testingbot merged 1 commit into
tb-modsfrom
fix/ios-statusbar-keyboard-race
Nov 12, 2026
Merged

jochen-testingbot merged 1 commit into
tb-modsfrom
fix/ios-statusbar-keyboard-race

Conversation

@jochen-testingbot

Copy link
Copy Markdown
Collaborator

Problem

An iOS 26 simulator run failed 13/13 flows with Unable to set permissions for app <app>: Failed to connect to /127.0.0.1:<port>. The re-run on a fresh runner failed the same way. This isn't a permissions problem: the XCTest runner had exited.

The runner log shows the cause:

error: -[maestro_driver_iosUITests testHttpServer] : Failed to resolve query:
Failed to resolve remote element AX element pid: <InputUI> ...
(Underlying Error: Error kAXErrorInvalidUIElement getting snapshot for element ...)
Tear Down
  • The pid belongs to com.apple.InputUI, the iOS 26 keyboard process. It was moving to the background at that moment.
  • Every /viewHierarchy request runs fullStatusBars(springboard), which calls statusBars.firstMatch.exists and statusBars.allElementsBoundByIndex. One of SpringBoard's two status bars ("Element at index 1") is hosted by InputUI.
  • When resolving that element fails, these APIs don't throw. They record an XCTest failure, which ends testHttpServer (continueAfterFailure doesn't help), and the runner exits.

This is the same mechanism as the webViews.count issue fixed in #4, in a different call.

Fix

  • fullStatusBars now takes a single throwing snapshot() of SpringBoard and collects the .statusBar nodes from it.
  • A new helper, AXElement.descendants(ofType:) in MaestroDriverLib, returns nodes in the same pre-order as allElementsBoundByIndex, including nested matches. It has unit tests.
  • If the snapshot fails, the error is caught and logged, and the hierarchy is returned without status bars.
  • Rebuilt the simulator driver zips. fix(ios): dismiss Apple Account password alerts with Not Now #5 didn't commit its zips, so they now also include the account-alert change.

Testing (iPhone 17, iOS 26.5 simulator, Xcode 26.4.1)

  • MaestroDriverLib swift test: all tests pass, including the 2 new ones.
  • maestro hierarchy before and after: the same 24 nodes. The only differences are the clock text and the order of the two status bars (the empty InputUI one is now listed second).
  • The fixed runner log has no Descendants matching type StatusBar queries.
  • Stress flows with a small SwiftUI login app (type email → Enter → password → Enter → optional tapOn "Not now" → log out):
    • 15 cycles: pass.
    • 6 cycles while killing the simulator's InputUI every 1.5 s (14 kills): pass, and no Failed to resolve in the runner log.
  • Status bar step time: average 0.062 s → 0.040 s; max 0.175 s → 0.122 s.

Not reproduced: I couldn't trigger the original race locally. The unfixed runner also survived the InputUI-kill stress. The failing run hit it while InputUI was moving to the background, not when it was killed. The fix works by taking the non-throwing APIs out of this code path, not by a repro that now passes.

Deployment note

The simulator hosts that ran the failing job use stock Maestro 2.6.0. Neither this fix nor #4 helps there until the forked runner is deployed to them.

…e cannot kill the runner

Every view-hierarchy request fetched SpringBoard's status bars with
`statusBars.firstMatch.exists` and `statusBars.allElementsBoundByIndex`.
On iOS 26 one of those status bars is hosted by the keyboard process
(com.apple.InputUI). When the keyboard goes away while the element is
being resolved, XCTest gets kAXErrorInvalidUIElement. These APIs do not
throw: they record a test failure ("Failed to resolve query: Failed to
resolve remote element AX element pid: <InputUI>"), which ends
testHttpServer and the runner exits. The CLI then fails every remaining
flow with "Unable to set permissions ... Failed to connect to 127.0.0.1".

Take one throwing `snapshot()` of SpringBoard and collect the status bar
nodes from it (new AXElement.descendants(ofType:), unit-tested). A race
now throws, is caught, and the hierarchy is returned without status bars.

Verified on an iPhone 17 / iOS 26.5 simulator: the hierarchy has the same
24 nodes as before, with the two status bars in a different order; there
are no StatusBar element queries left; two stress flows (15 login cycles,
and 6 cycles while InputUI was killed 14 times) pass; the status bar step
went from 0.062 s to 0.040 s on average.

Also rebuilds the simulator driver zips, which now include #5 as well.
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.

1 participant