Skip to content

scrollUntilVisible: agent-device fails to find an element real Maestro finds (layer-3 differential) #1299

Description

@thymikee

Found by the layer-3 conformance differential on its first working run (#1289) — real Maestro and agent-device executing the same flow on the same simulator, same app, same pinned Maestro 2.5.1.

The divergence

Flow: scripts/maestro-conformance/differential/flows/settle-after-tap.yaml, against the fixture app com.callstack.agentdevicelab:

- launchApp:
    clearState: true
- assertVisible: Agent Device Tester
- scrollUntilVisible:
    element:
      id: home-open-form
- tapOn:
    id: home-open-form
Engine Outcome
Maestro 2.5.1 pass (exit 0) — scrolls, finds home-open-form, taps it
agent-device fail (exit 1)
Replay failed at step 3 (scrollUntilVisible "home-open-form"):
Maestro scrollUntilVisible target did not become visible.
(settle-after-tap.yaml:line 11)

home-open-form is a real control on the fixture app's home screen (HomeScreen.tsx:89), inside a ScrollView, below the fold — the app's own helpers/open-checkout-form.yaml scrolls it into view the same way. Maestro reaches it; we time out.

This is engine correctness in an advertised command (scroll and scrollUntilVisible is in the supported subset), not a documented deviation.

Evidence

Run: https://github.com/callstack/agent-device/actions/runs/29504440599 (differential-report.json artifact attached to the run)

{
  "id": "settle-after-tap",
  "maestro":     { "outcome": "pass", "exitCode": 0 },
  "agentDevice": { "outcome": "fail", "exitCode": 1 },
  "outcomeDiverged": true
}

The other three scenarios in the same run rule out environment as the cause — percent-swipe and optional-warned-not-failed were pass/pass on both engines.

Leads (untested hypotheses, ranked)

  1. visibilityPercentage === 100 boundary — first bet. Our preset is scrollUntilVisiblePercentage: 100 (compatibility-policy.ts). If upstream's visibility threshold accepts partial visibility at the default while we demand the element be fully visible, Maestro finds it at ~97% visible and we keep scrolling until the timeout — exactly the observed shape (a timeout, not a miss).
  2. speed→duration mapping. Upstream trunc(1000·(100−speed)/100)+1 (mirrored in maestroScrollDurationFromSpeed) vs our scroll-step geometry — a step size/direction mismatch could overscroll past the target.
  3. Settle interplay after each scroll step — if we settle differently between steps, the visibility check may sample at the wrong moment.

Reproducing locally (do NOT iterate via CI)

The differential runner takes --only, so this loops in minutes on a local sim rather than 40-minute CI cycles:

pnpm test-app:install && pnpm --dir examples/test-app exec expo run:ios --configuration Release
pnpm maestro:conformance:differential -- --platform ios --only settle-after-tap --trace-root .agent-device

Acceptance

The oracle itself is the acceptance test. The scenario is currently marked knownDivergence in differential/scenarios.ts with this issue as its tracking link, so the scheduled differential stays green while it is a known, declared gap. The fix PR removes the knownDivergence declaration — the differential then enforces that it stays fixed, and the runner fails if a declared divergence starts passing, so a stale declaration cannot linger.

Note this also currently blocks bug class 4's device detector: settle-after-tap is the settle-ordering scenario, and it cannot reach its tapOn step while scrollUntilVisible fails.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    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