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)
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).
- 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.
- 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.
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 appcom.callstack.agentdevicelab:home-open-form, taps ithome-open-formis a real control on the fixture app's home screen (HomeScreen.tsx:89), inside aScrollView, below the fold — the app's ownhelpers/open-checkout-form.yamlscrolls it into view the same way. Maestro reaches it; we time out.This is engine correctness in an advertised command (
scroll and scrollUntilVisibleis in the supported subset), not a documented deviation.Evidence
Run: https://github.com/callstack/agent-device/actions/runs/29504440599 (
differential-report.jsonartifact 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-swipeandoptional-warned-not-failedwerepass/passon both engines.Leads (untested hypotheses, ranked)
visibilityPercentage === 100boundary — first bet. Our preset isscrollUntilVisiblePercentage: 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).trunc(1000·(100−speed)/100)+1(mirrored inmaestroScrollDurationFromSpeed) vs our scroll-step geometry — a step size/direction mismatch could overscroll past the target.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:Acceptance
The oracle itself is the acceptance test. The scenario is currently marked
knownDivergenceindifferential/scenarios.tswith this issue as itstrackinglink, so the scheduled differential stays green while it is a known, declared gap. The fix PR removes theknownDivergencedeclaration — 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-tapis the settle-ordering scenario, and it cannot reach itstapOnstep whilescrollUntilVisiblefails.