Skip to content

Mobile: explicit wait for the remaining transition budget after overlay dismissal #1563

Closed as not planned

Description

@shamilovtim

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

Public fixture and commands.

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

  1. okwasniewski commented on Nov 10, 2026

    @okwasniewski
    Member

    [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-device versions, 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.

  2. shamilovtim commented on Nov 10, 2026

    @shamilovtim
    ContributorAuthor

    [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: e2e 0.23.0 and @e2e-dev/mobile 0.12.6, both built from that commit; agent-device 0.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 with transition: 2000 fail 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 pageSheet passes 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.

  3. shamilovtim commented on Nov 10, 2026

    @shamilovtim
    ContributorAuthor

    [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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmobileMobile engine (@e2e-dev/mobile)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions