Skip to content

Make waits settle instead of stalling, harden the iOS driver, and add the all: recording key - #228

Merged
handstandsam merged 2 commits into
mainfrom
upstream-2026.08.10
Aug 10, 2026
Merged

Make waits settle instead of stalling, harden the iOS driver, and add the all: recording key#228
handstandsam merged 2 commits into
mainfrom
upstream-2026.08.10

Conversation

@handstandsam

@handstandsam handstandsam commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Syncs the latest upstream changes through 2026.08.10.

Waiting on a loading screen now works the way you'd expect. wait and waitForChange are settles, not timers — each returns the moment the UI goes quiet, and the duration you pass is only a ceiling. They used to report a duration they never actually spent. There's now a real sleep tool for the rare case you genuinely want wall-clock time to pass, and the agent's system prompt steers toward positive-signal waits (assert on the thing you're waiting for) instead of padding with timers.

The iOS driver got a long hardening pass. Web page content inside a WKWebView now reaches the accessibility tree, so trails can assert on it. Launches pre-grant app permissions so replays stay dialog-free. A failed state-clear can no longer leave the app uninstalled for the rest of the suite, recordings that omit stopApp cold-start the app again, off-screen elements fail asserts and taps loudly instead of silently, placeholder text is findable on iOS 18, and coordinates clamp to bounds. Every action in an IOS_AXE run now gets a screenshot in the report, and simulator video/screenshots record correctly even when the JVM temp dir sits on a non-boot volume.

Declare a recording once for every device with all:. A new universal classifier key resolves for every device at the lowest priority, so a cross-platform trail whose platforms genuinely share a trailhead or recording writes one block instead of byte-identical android: and ios: duplicates. Any explicitly-declared classifier still outranks it.

Run reports are faster and cleaner. The run list paints instantly instead of blocking on parsing the whole report; the index, timeline, and video views got a polish pass; self-heal shows up when only the logs recorded it; and there's one report column per platform rather than one per CI shard's simulator.

trailblaze check no longer passes when it never type-checked your recordings. A framework JAR built without its bundled TypeScript compiler used to skip trail-recording validation silently and still go green — that's now a hard build failure at package time and a fatal error at check time.

Also in here: a recordable assertion for the number of matching elements, an unrecognized --driver failing loud with exit 3 instead of silently running the default, workspace-local toolsets being used when compiling and checking trailmaps, scripted-tool sources found in trailmaps shipped inside a jar, a record-time warning when a tap falls back to raw screen coordinates, and the default per-objective agent-loop cap cut from 50 to 25.

Notes for reviewers

  • gemma-4 model id corrected in the generated doc. add gemma4 as a model for google #225 added gemma-4-31b-it to google.yaml but hand-wrote the LLM_MODELS.md row as gemma-4-26b-a4b-it. LLM_MODELS.md is generated from google.yaml, so regenerating it produces the row matching the source. Flagging it since it changes a row that landed in add gemma4 as a model for google #225.

  • Carries Always await on-device completion for MCP tool dispatch #221's on-device MCP dispatch fix forward unchanged; the surrounding KDoc is rewritten and screenState() now reuses the already-built device manager rather than constructing a second one.

  • Second commit: a tool-list fix this sync's CI surfaced. Connecting a device kicks off a tool-list refresh that finishes after the connect call returns, and that refresh removed every device tool up front before adding the new ones back — so an MCP client asking for tools right after connecting could see only the session tools and conclude the device had none. It now registers the new surface first and removes only what the new surface no longer has (the shape the host-tool half of the same method already used). Whether you hit the window was pure timing; this branch's connect got fast enough to lose the race, which is what android-tests-mcp-dispatch was reporting. Same commit also makes the MockRpcServer test fixture wait for its port to be bindable before handing it to Ktor — the port sits inside the OS ephemeral range, an unrelated loopback socket can hold it, and letting Ktor take the BindException also failed whichever unrelated test ran next. Landing in the internal repo in parallel.

Connecting a device could leave an MCP client (Claude Code, Cursor, Goose)
believing the device has no tools at all. Asking for the tool list right after
connecting returned only the session tools — no tap, no swipe, no inputText —
and the client had no reason to ask again.

Connecting a device kicks off a tool-list refresh that finishes after the
connect call has already returned, so "right after connecting" lands in the
middle of it. That refresh used to remove every device tool up front and add the
new ones back at the end, so anything asking during the window saw an empty
surface. It now registers the new surface first and removes only what the new
surface no longer has, which is what the host-tool half of the same method
already did.

Also stops the MockRpcServer test fixture from failing when its port is
momentarily held by an unrelated socket: it waits for the port to be bindable
before handing it to Ktor. The port is inside the OS ephemeral range, so a
loopback connection elsewhere on a busy machine can own it — and letting Ktor
take the BindException also surfaced on its own coroutine, failing whichever
unrelated test ran next.

Implementation detail: the swap is in TrailblazeMcpServer.registerToolsLocked;
MCP's FeatureRegistry is keyed by tool name and addTool atomically replaces an
existing entry, so re-registering in place never duplicates and never leaves a
gap. MockRpcServer.awaitBindable is the new gate.
@handstandsam
handstandsam merged commit e94ac81 into main Aug 10, 2026
12 checks passed
@handstandsam
handstandsam deleted the upstream-2026.08.10 branch August 10, 2026 16:45
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