Skip to content

[BUG] Android DeviceLab: FindAndClick taps off-screen centre from a malformed negative-height rect (bottom-sheet first frame) #94

@laiskajoonas

Description

@laiskajoonas

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

  1. 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.
  2. Flow: tapOn the dropdown → extendedWaitUntil: visible: id: radio-button-ALtapOn: id: radio-button-ALtapOn: "Confirm" → assert next screen.
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions