fix(android): bound the scroll-hint dumpsys probe and skip it in wait polling (#1270)#1288
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
…ndler (#1290) dispatchFindReadOnlyViaRuntime intercepts every read-only find action (exists/wait/get_text/get_attrs) inside handleFindCommands and always returns a response for them, so the legacy handleFindWait polling loop, handleFindExists/GetText/GetAttrs, attachIssuedRefsGeneration, and the sessionless-find device resolution behind them were unreachable (verified empirically in #1288 and again here by planting throws in all four handlers — the full find suite stays green). With only mutating actions (click/fill/focus/type) able to reach the snapshot path, requiresRect is constant true; fold it and drop the now-dead scope threading, including the legacyIosSparse recovery scope field that was only ever populated with undefined.
|
Review at exact head The 1.5s cap and the skip for the distinct
The new regression directly exercises only Please disable hidden-content-hint derivation for the presence-only standalone selector/text/ref wait paths (stable wait may retain full snapshot semantics), and add a daemon/public-route regression matching the exact repro. All current CI checks are green, but the live n=20 helper-path evidence is still absent. The branch is also behind current |
… polling (#1270) deriveScrollableContentHintsIfNeeded's dumpsys activity top probe ran with an 8s timeout equal to a whole wait/get budget, and measured latency ranges from 37ms to ~5.9s under contention. Cap it at 1.5s so one pathological call can't starve a caller's timeout, and skip hint derivation entirely during find ... wait polling, since a presence check never consumes scroll hints. The wait polling loop actually lives in commands/interaction/runtime/selector-read.ts's waitForFindMatch (daemon/handlers/find.ts's own handleFindWait is unreachable for wait today — dispatchFindReadOnlyViaRuntime always intercepts read-only find actions first), so the skip-hints option threads through that capture path down to snapshotAndroid via the existing flags -> contextFromFlags -> dispatchCommand -> interactor.snapshot channel.
…too (#1270) The issue's motivating repro — wait 'label="Battery"' 8000 — polls waitForSelector, not the find-wait loop, and text/ref waits poll waitForText (backend.findText -> captureWaitSnapshot on the daemon, or the snapshotContainsText fallback). All three presence-only polling captures now disable hidden-content-hint derivation, matching the Android alert-wait capture which already did. Stable wait keeps full snapshot semantics. Adds daemon-route regressions for the exact repro shape on both the selector-wait and text-wait routes, asserting every per-poll snapshot dispatch carries snapshotIncludeHiddenContentHints: false.
1550a1d to
5046190
Compare
|
Addressed at Wait polling paths — full enumeration and now-state
Post-timeout diagnostics ( New daemon/public-route regressionsIn
Revert-sensitivity verified: with the skips stripped from Consolidation-candidate note (not done here — single concern)The enumeration surfaced five distinct hand-rolled polling loops for wait-shaped commands ( Gates at
|
|
Current-head re-review ( Residual merge evidence: the requested live Android n=20 helper-path run is still pending. Please attach that evidence before merge; it is not a code-review finding. |
|
Post-merge live confirmation — n=20 replay matrix + Leg E overlay acceptanceFull n=20 replay of the Android Settings native-list flow (
FDR(runs) = 0.05, median clean wall 8.0s. Per-class:
The lone divergence (plain run #2, step 6 Leg E — #1264/#1265 overlay acceptanceResume-replay from the (a) Volume dialog — not reproduced (timing/hittability limited, not a burial regression). Across plain, timed, and aggressive-pump (VOLUME_UP every 0.3s spanning the whole replay) attempts, the async divergence fast-capture never contained a systemui/volume node (0 systemui in (b) Quick-settings shade — captured-but-filtered (candidate new issue). Disclosure: combined tree |
#1270) (#1314) The snapshot helper is the only capture backend and emits `can-scroll-forward`/ `can-scroll-backward` for exactly the nodes Android reports as scrollable. Their absence therefore means nothing on screen scrolls, not that scroll state is unknown — so the `dumpsys activity top` probe only ever ran when there was no scrollable content for it to describe. #1288 bounded that probe at 1.5s rather than removing it, leaving every capture of a screen with a scrollable-typed but non-scrollable node (a list short enough to fit) paying the cap for hints that cannot exist. `dumpsys activity top` serializes a view dump of every top activity through each app's main thread, so one busy app stalls the call until Android's own 10s service-dump timeout — the mechanism behind the 37ms-to-5.9s spread in the issue's evidence. Hints for genuinely scrollable nodes are unaffected: they come from the parsed helper attributes, which is where they already came from on every screen that reports a scroll action.
Summary
Fixes the latent hazard identified in the diagnosis comment on #1270:
deriveScrollableContentHintsIfNeeded'sdumpsys activity topprobe (dumpActivityTopinsrc/platforms/android/snapshot.ts) ran withtimeoutMs: 8_000— a whole typicalwait/getbudget — inside every poll iteration, and the call's latency was measured ranging from 37ms to ~5.9s under contention (10/10 manual samples pathological on the same unchanged screen). One slow call could eat the entire timeout budget of an otherwise-successful capture.Per the diagnosis's own rescope: this is a timeout-hygiene fix, not a capture-completeness bug. The originally-filed "viewport-bottom element missing" framing was ruled out — the node is always present in the raw dump; the cost comes entirely from this auxiliary scroll-hint derivation call running after the real capture already succeeded.
Changes
dumpActivityTop'stimeoutMsdrops from8_000to1_500(healthy latency is ~37ms, so 1.5s is generous headroom while bounding the pathological tail). It already returnsnullon failure/timeout and callers already degrade gracefully to an empty hint map — verified via existing + new tests.find ... waitpolling — a presence check never consumes scroll hints. The wait poll loop turned out to live inwaitForFindMatch(src/commands/interaction/runtime/selector-read.ts), notdaemon/handlers/find.ts'shandleFindWaitas the diagnosis comment named —dispatchFindReadOnlyViaRuntimenow always intercepts read-only find actions (wait/exists/get_text/get_attrs) beforehandleFindWaitis ever reached, so that loop is dead code onmaintoday (confirmed empirically). The newincludeHiddenContentHintsoption threads through the same existing channel other snapshot flags use:BackendSnapshotOptions→captureSelectorSnapshot→CommandFlags/contextFromFlags→DispatchContext→handleSnapshotCommand→Interactor.snapshot→snapshotAndroid's pre-existingincludeHiddenContentHintsoption (already used byalert.tsfor system-dialog captures).snapshotAndroid caps the activity-top scroll-hint probe at 1.5s— scrollable-typed node, XML withoutcan-scroll-*attributes →dumpActivityTopfires withtimeoutMs: 1500.snapshotAndroid skips the activity-top probe when the helper XML already carries scroll-action attributes— pins the existing short-circuit behavior explicitly.runtime find wait skips hidden-content hint derivation on every poll (#1270)— every poll iteration ofwaitForFindMatchpassesincludeHiddenContentHints: falsedown to the backend capture call.Test plan
pnpm typecheckpnpm exec oxlint --deny-warningspnpm format:checkpnpm check:affected --base origin/main --run(green modulo known-flaky-under-contention tests below, all confirmed passing in isolation)pnpm check:fallow --base origin/main(0 new findings; pre-existinghandleSettingsCommandcomplexity + aselector-read-shared.ts/resolution.tsduplicate clone group correctly excluded as inherited baseline findings)src/platforms/android/__tests__/snapshot.test.ts(40/40),src/commands/interaction/runtime/selector-read.test.ts(17/17)runtime-hints.test.ts,runner-client.test.ts,apple index.test.tsfailed once under heavy host contention (load avg ~25) during a fullcheck:affectedrun, unrelated to this diff's files — reran each in isolation and all pass cleanly