Description
On the Android DeviceLab driver, FindAndClick computes its tap point from a malformed, clipped element rect (top > bottom, negative height) and injects the tap outside the physical screen. This happens when the target lives inside a just-opened bottom sheet (React Native, @gorhom/bottom-sheet v5) and the find samples the sheet's first laid-out frame, where the content is still translated below the viewport. With app-side animations disabled (common in CI), the sheet snaps into place one frame later, so there is exactly one stale frame — and the find/tap pipeline accepts it.
The v1.1.16 release notes say pre-tap settle was extended to ID-based taps because they "could fire at mid-animation/off-screen bounds" — this is a case that fix doesn't catch (the settle apparently completes before/across the single-frame snap). The assert side got a viewport check back in #39; the tap path has no equivalent sanity check.
The damage compounds: because the radio tap lands nowhere, the sheet stays open, and the next step (tapOn: "Confirm") resolves the background CTA behind the sheet and injects at its coordinates — which land inside the open sheet's list, selecting a random row. The flow is then permanently desynced and fails several steps later with a misleading signature.
Steps to Reproduce
- RN app (Expo), animations disabled; a screen with a dropdown that opens a ~90%-height bottom sheet containing a long radio list (testIDs
radio-button-XX), behind which sits a bottom "Confirm" CTA.
- Flow:
tapOn the dropdown → extendedWaitUntil: visible: id: radio-button-AL → tapOn: id: radio-button-AL → tapOn: "Confirm" → assert next screen.
- Run with
--driver devicelab --parallel 4 on emulators under CI load. Intermittent: fails when the find samples the sheet's frame 0.
Expected Behavior
An element whose rect has non-positive width/height, or whose centre lies outside the display, should be treated as not-yet-visible/tappable — keep polling instead of injecting an off-screen tap (mirroring the assert-side viewport check from #39).
Actual Behavior
From maestro-runner.log (device 1080x2400, byte-identical bounds in 4 independent CI occurrences; green runs show the same element at settled bounds [63,1033][1017,1191]):
recordTap SET hash=15c7345e4dfee912 for #radio-button-AL
FindAndClick hit for #radio-button-AL via -android uiautomator=new UiSelector().resourceId("radio-button-AL"):
bounds=[63,2836][1017,2400] (w=954 h=-436) center=(540,2618) <- top>bottom, centre is off a 2400px screen
...
recordTap SET hash=15c7345e4dfee912 for Confirm <- same hash as before the radio tap: sheet still open
FindAndClick hit for Confirm via -android uiautomator=new UiSelector().textContains("Confirm").clickable(true):
bounds=[63,2085][1017,2211] center=(540,2148) <- background CTA; tap lands inside the open sheet's list
The preceding extendedWaitUntil: visible: id: radio-button-AL also passed (in ~50 ms) against the same malformed rect, so the visibility path accepts it too.
Environment
- OS: Ubuntu (GitHub Actions runner)
- maestro-runner version: v1.1.16
- Executor:
--driver devicelab, --parallel 4
- Device/Simulator: Android emulator, API 35, x86_64, 1080x2400
- App: React Native / Expo, animations disabled for E2E
Flow File
- tapOn:
id: "nationality-country-dropdown-input-wrapper-pressable"
waitToSettleTimeoutMs: 200
- extendedWaitUntil:
visible:
id: "radio-button-AL"
timeout: 5000
- tapOn:
id: "radio-button-AL"
waitToSettleTimeoutMs: 200
- tapOn:
text: "Confirm"
waitToSettleTimeoutMs: 200
- assertVisible: "Your legal address" # fails ~13s later; app provably never left the screen
Related: found alongside a second lazy-retry issue on the same suite (filed separately).
Description
On the Android DeviceLab driver,
FindAndClickcomputes its tap point from a malformed, clipped element rect (top > bottom, negative height) and injects the tap outside the physical screen. This happens when the target lives inside a just-opened bottom sheet (React Native,@gorhom/bottom-sheetv5) and the find samples the sheet's first laid-out frame, where the content is still translated below the viewport. With app-side animations disabled (common in CI), the sheet snaps into place one frame later, so there is exactly one stale frame — and the find/tap pipeline accepts it.The v1.1.16 release notes say pre-tap settle was extended to ID-based taps because they "could fire at mid-animation/off-screen bounds" — this is a case that fix doesn't catch (the settle apparently completes before/across the single-frame snap). The assert side got a viewport check back in #39; the tap path has no equivalent sanity check.
The damage compounds: because the radio tap lands nowhere, the sheet stays open, and the next step (
tapOn: "Confirm") resolves the background CTA behind the sheet and injects at its coordinates — which land inside the open sheet's list, selecting a random row. The flow is then permanently desynced and fails several steps later with a misleading signature.Steps to Reproduce
radio-button-XX), behind which sits a bottom "Confirm" CTA.tapOnthe dropdown →extendedWaitUntil: visible: id: radio-button-AL→tapOn: id: radio-button-AL→tapOn: "Confirm"→ assert next screen.--driver devicelab --parallel 4on emulators under CI load. Intermittent: fails when the find samples the sheet's frame 0.Expected Behavior
An element whose rect has non-positive width/height, or whose centre lies outside the display, should be treated as not-yet-visible/tappable — keep polling instead of injecting an off-screen tap (mirroring the assert-side viewport check from #39).
Actual Behavior
From
maestro-runner.log(device 1080x2400, byte-identical bounds in 4 independent CI occurrences; green runs show the same element at settled bounds[63,1033][1017,1191]):The preceding
extendedWaitUntil: visible: id: radio-button-ALalso passed (in ~50 ms) against the same malformed rect, so the visibility path accepts it too.Environment
--driver devicelab,--parallel 4Flow File
Related: found alongside a second lazy-retry issue on the same suite (filed separately).