Repository navigation
Description
Problem
After dismissing an overlay, a control beneath it can have the same accessibility identity, bounds, and keyboard state as before the dismissal while the overlay is still fading. The mobile adapter deliberately acts immediately on that unchanged control, so raising transition does not add a synchronization point for this case.
I am requesting an opt-in API, preserving that default fast path. This is separate from the keyboard-state refresh in #1296/#1297 and from root-screen scroll timing.
Proposed API
await screen.getByRole('button', 'Close overlay').tap();
await device.waitForTransition();
await screen.getByRole('button', 'Underlying action').tap();device.waitForTransition(): Promise<void> would wait only for the remainder of the configured mobile({ transition }) budget since the last completed screen-changing action. It would send no input, would not start a new transition budget, and would obey the fixture action timeout/cancellation. If the budget has elapsed, it would return immediately. The wait would appear as a fixture step in the report.
This is a bounded timing choice, not a guarantee that a native animation has finished.
Tradeoffs
- Tests could opt in at a known dismissal without delaying every capture or changing unchanged-control behavior elsewhere.
- The existing transition duration supplies the budget; repeated calls would not add another full delay.
- Test authors must identify the affected boundary and choose a suitable duration. This remains time-based synchronization rather than native animation/readiness detection.
Native reproduction on main
Verified 2026-11-10 with a real UIKit fixture and unmodified main d8b694ffa48b04c94d70a11cc280267f1ea9a100, after merged #1564. Framework source builds identify as e2e 0.23.0 / @e2e-dev/mobile 0.12.6, with agent-device 0.21.36, on iPhone 15 Pro / iOS 27.0.
Public native app, commands, reports and all recordings. The 750 ms custom composite fade reproduces with transition budgets of 1,000 ms and 2,000 ms. Trace step 4 reports a successful tap, but the native counters show underlying taps 0 / overlay touches 1. Native events place that touch before animation completion. The stock UIKit page-sheet control passes.
This is a constructed native reproduction with a touch-owning parent retained while its children fade, not a claim that all native modals behave this way. The public API proposal still needs maintainer agreement.
Earlier scripted policy model
The real built CLI and mobile adapter run against a scripted SDK client. Both controls remain unchanged; the client models a 750 ms overlay dismissal with a 1,000 ms transition budget. The normal run fails (1/1, 1.65 s) because the underlying input is intercepted. A comparison run with an explicit wait passes (1/1, 2.13 s). The comparison sleep illustrates the requested synchronization point; it is not an implementation of this API or a recommendation to add fixed sleeps to tests.
No simulator, native app, model, or credentials are used. This proves the adapter's current policy and the modeled interaction, not that a current native producer repeats this condition.
Per CONTRIBUTING.md, I am seeking maintainer agreement before implementing the public API or opening a feature PR.
Activity
okwasniewski commentedon Nov 10, 2026
[claude-opus-5-5] automated triage on behalf of @okwasniewski
Thanks. A missing detail before this can be decided: the repro uses a scripted client, and the issue says it doesn't show that a real native app hits this. Can you share a run against a real app (simulator or device) where a tap right after an overlay dismissal lands on the overlay? Please include the app (public repo),
e2e/@e2e-dev/mobile/agent-deviceversions, and the trace.md step.Also please check whether it still happens on main after #1564 (screen swipes now wait for the transition budget). A new API that waits on a fixed time budget is a maintainer call, and a real-app repro is what that decision depends on.
shamilovtim commentedon Nov 10, 2026
[Codex] responding on behalf of @shamilovtim
Reproduced with a real UIKit app and the built CLI on unmodified main
d8b694f, after #1564. Public app source, commands and all evidence.Versions:
e2e0.23.0 and@e2e-dev/mobile0.12.6, both built from that commit;agent-device0.21.36; Node 24.22.0; iPhone 15 Pro simulator on iOS 27.0. No SDK mock or engine patch was used.The 750 ms fade fails with
transition: 1000, and all three repeats withtransition: 2000fail the same way. Trace step 4 reports that the underlying button's tap succeeded in 365 ms. Step 5 fails with underlying taps 0 / overlay touches 1. Video.The native event log records the overlay receiving the actual touch 283.48 ms after dismissal begins. UIKit completes the fade at +752.70 ms. The underlying action callback never runs. This is a locator tap, so #1564's root-swipe wait does not cover it.
The scope is a custom composite overlay constructed for this reproduction. Its backdrop and panel fade while their touch-owning parent remains until the animation completion callback. The underlying control stays exposed to accessibility at unchanged bounds. A stock UIKit
pageSheetpasses twice. A diagnostic 1,000 ms pause before the next tap also passes; that comparison demonstrates synchronization, rather than implementing the proposed API. All eight attempts completed cleanup, and every failure has a validated recording.The API proposal remains pending maintainer agreement. App-side readiness signals or changing when the overlay releases touches are alternatives; this evidence establishes the native case without choosing that API for the project.
shamilovtim commentedon Nov 10, 2026
[Codex] responding on behalf of @shamilovtim
Withdrawing the explicit-wait API proposal. The underlying problem is implicit action readiness: an ordinary tap should honor the existing transition budget instead of treating unchanged bounds as proof that an overlay has released input.
Replaced by #1682, which carries forward the public native reproduction and records the related coverage/readiness work already checked. The replacement fix will use the existing transition machinery without adding a public wait method.