mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
1fb276448f
* fix(wait): poll through a retriable runner refusal instead of surrendering the budget The iOS Smoke lane started failing on main at the merge of #2486: the new smoke:webview-remote-content scenario ended with `wait text "Jump to form" 20000` failing after 288 ms with RUNNER_BUSY. Three defects stacked up. The scenario reused acceptDeepLinkConfirmationIfPresent, whose readiness landmark was hard-coded to the Automation lab's text. Off that route it can never match, so the helper always fell through to its `alert get` probe — and an XCTest alert query against a live WKWebView screen exceeds the runner's 30 s main-thread execution watchdog (measured 10.1 s to fail locally, 10.6 s in CI), abandoning main-thread work and leaving the runner refusing every following command as RUNNER_BUSY. The landmark is now a parameter and each caller passes its own route's, so the probe runs only when the destination genuinely did not arrive. The depth-frontier scenario carried the same mismatch and is fixed with it. A `wait` is a budgeted retry loop, but it abandoned its whole budget on the first retriable refusal. A poll whose failure the producer itself marked retriable is now ridden out like an unreadable capture: the wait keeps polling to its deadline, records the poll as `retriable` in its timeout evidence, and surfaces the refusal only when no readable capture ever completed. RUNNER_WEDGED is not retriable and still ends the wait at once. That classification was also missing on the path the failure actually took. A runner error recovered from the lifecycle journal after a lost transport response was built with a bare toAppErrorCode, so RUNNER_BUSY reached callers as a RUNNER_BUSY wire code with no `retriable` flag, while the live-response path published it as COMMAND_FAILED plus details.runnerErrorCode and retriable: true. Both paths now read the runner's code through one classifier in runner-contract. Live-validated on a booted iPhone 17 Pro simulator against the fixture app: the destination landmark resolves in 389 ms with no alert probe, the page wait succeeds in 81 ms, and the snapshot still carries Link "Jump to form", the "Email address" field label, and the remote-content-boundary XCTest fallback warning. Driving the old sequence first reproduces the wedge, after which the fixed wait polls its full 20 s in `retriable` polls instead of failing instantly. * test(apple-runner): move journaled runner-code classification to the recovery test The two new cases landed in runner-command-retry.test.ts, which was already over the 1,000-line test-file tripwire, so the size ratchet refused its growth. They assert runnerStatusFailureError's reading of the lifecycle journal, so their home is runner-command-recovery.test.ts, which mirrors that module and drives recovery through the real stack against a scripted fake runner. * test(e2e): let a deep-link route mount before probing for its confirmation alert The 2500 ms destination budget was tuned to the Automation lab on a warm simulator. On CI the WebView lab rendered correctly but was not in the bridge tree that fast, so the helper fell through to its `alert get` probe — and that XCTest query against a live WKWebView exceeds the runner's execution watchdog, leaving every later command refused as RUNNER_BUSY. Measured on a freshly created simulator: with the confirmation alert up the probe is correct and costs 1.6 s, because the alert blocks the route and there is no web view to query; with no alert the landmark resolves in 0.1-1.7 s. The budget only has to outlast an honest mount, and overshooting it costs nothing when a confirmation really is up, since that route never renders until it is accepted. Cold-simulator run of the whole scenario: landmark 400 ms, page wait 613 ms, snapshot keeps the page link, the field label, and the XCTest fallback warning. * fix(wait): keep the poll timeline on a wait exhausted by retriable refusals Review finding on #2493: a wait that spent its whole budget being refused threw the last refusal raw, so the common all-RUNNER_BUSY case carried no captures, waitedMs or polls and could not show where its budget went — contradicting the evidence this PR documents. The mirror gap existed on the other exhaustion shape: when the deadline cancelled the final poll, the wait reported a generic stall and dropped the runner code and retry details instead. Both shapes now raise one error that keeps the producer's code, message, hint and retry details and carries the wait's own evidence, with reason wait_capture_stalled and the original as its cause. A content verdict is still preserved untouched, since it already describes the capture it came from, and whether it outranks the stall verdict stays the caller's policy (wait absent). Live-verified against a genuinely wedged simulator runner: COMMAND_FAILED, retriable true, runnerErrorCode RUNNER_BUSY, reason wait_capture_stalled, captures 6, readableCaptures 0, waitedMs 8041, polls retriable,retriable,retriable,retriable,retriable,deadline.