Skip to content

[docs] ADR: Selenium waits for interaction readiness before it interacts - #17886

Open
AutomatedTester wants to merge 2 commits into
trunkfrom
adr-interaction-readiness-waits
Open

[docs] ADR: Selenium waits for interaction readiness before it interacts#17886
AutomatedTester wants to merge 2 commits into
trunkfrom
adr-interaction-readiness-waits

Conversation

@AutomatedTester

@AutomatedTester AutomatedTester commented Aug 6, 2026

Copy link
Copy Markdown
Member

📄 The decision, its rationale, considered options and consequences are in the record file this PR
adds; read it there. The sections below are proposal notes and review logistics.

Revised after review (a9f895c):
an earlier revision proposed that readiness waiting become the default for every BiDi session. It no
longer does. Readiness is enabled by a new se:interactionReadiness capability, off by default, and
when enabled it replaces the implicit wait rather than adding a second timeout beside it. Making
it the default is now a follow-up decision, probably release-gated.

🔗 Related

  • Builds on 17670 — BiDi implementation boundaries:
    this is high-level, protocol-neutral API with BiDi as an implementation mechanism that must not
    leak into signatures. No method signature changes and no BiDi type is named; the behavior is
    capability-gated.
  • Reference implementation (Python): py-quiescence-bidi-preload
    — the readiness oracle as a JavaScript atom registered as a BiDi preload script, with ~1,500 lines
    of behavioral tests. Evidence that the semantics are implementable and testable; not the
    proposed API shape (see divergences below).
  • Intended to become a proposed WebDriver BiDi quiescence module; no working-group issue filed yet.

📝 Proposal notes

A correction the record now carries. The first revision claimed WebDriver does not wait before
interacting. That is wrong, and the corrected version is a stronger argument:

Command Waiting behavior in the spec
Element Clear Steps 6–10: takes the implicit wait timeout, starts a timer, waits for the element to become interactable
Element Send Keys Steps 7.2–7.6: the same pattern, waits for keyboard-interactable
Element Click Steps 5–7: scroll, then fail. No wait

keyboard-interactable is "any element that has a focusable area, is a body element, or is the
document element" — invisible, covered, mid-animation and aria-disabled elements all satisfy it,
and interactable is satisfied the same way. The timeout is initially 0, and when set it is also
spent on element retrieval. So the gap is not that WebDriver never waits: it is that the predicate is
too weak to prevent the failures users hit, it is absent for click, it cannot say why it expired,
it shares one budget with element location, and remote ends improvise past it (chromedriver waits for
displayed before clicking).

Why the capability replaces the implicit wait instead of adding a timeout. For send_keys and
clear the remote end already runs its own interactability wait on the implicit wait timeout. A
separate readiness timeout would stack with it and hand a user who set an implicit wait a total they
never asked for. So when the capability is on, Selenium stops forwarding a non-zero implicit wait and
spends that budget client-side on a predicate that is actually useful.

The consequence that needs a decision. Because the implicit wait is no longer forwarded,
findElement loses its remote-end retry, so the record proposes Selenium take over element-location
waiting on the client for these sessions. That trades local spinning at the remote end for polling
round-trips. It is decision 2 and considered option 8, and it is the part of the design I would most
like confirmed.

What the wait covers. Actionability only — visible, enabled (including aria-disabled and the
fieldset/legend exception), editable where the interaction writes, in the viewport, unobstructed,
not moving. It deliberately does not require page settledness: an application that long-polls or
animates continuously would pay a settle timeout on every click. The low-level Actions API does not
auto-wait either.

How users reach the rest. There is no new wait method on the driver or on the element. The wait
is inside the interaction commands; the underlying state is reachable as a snapshot (composable with
an existing WebDriverWait) and as a settledness handler following the existing
driver.script.add_dom_mutation_handler pattern — preload script, ChannelValue, script.onMessage
— so no client library is forced into Selenium's waiting strategy to use Selenium's readiness data.
Concrete names and signatures are deliberately left open.

Also proposed: a settled page load strategy, managed locally because pageLoadStrategy is a
validated none/eager/normal capability, plus a per-action strategy override.

Compatibility. Nothing changes for a session that does not set the capability. Where it is set,
an expired wait raises a readiness-specific subclass of what the command raises today
(ElementClickInterceptedException, ElementNotInteractableException), so existing
catch/except/rescue blocks keep matching while the readiness case stays distinguishable.

Deliberately out of scope, as follow-up decisions: making readiness the default for BiDi sessions
and in which release; a variant for classic sessions (considered option 10 argues it cannot be done
at full fidelity without a preload); deprecating ExpectedConditions.elementToBeClickable and
equivalents; the concrete snapshot and handler signatures; the BiDi module proposal itself.

Divergences from the reference implementation. It exposes two driver.* wait methods and one
combined atom, which decisions 4, 5 and 8 replace. The capability, the implicit-wait replacement, the
client-side element-location retry, the error subclasses and the settled strategy are proposed here
and not yet built.

Cross-binding impact. Every binding gains the capability, the interaction-path change, the
snapshot and handler surface, the error subclasses, the page load strategy value, and the packaging
wiring to ship a JavaScript resource; bindings that already ship atoms have most of the last part.
Because the capability is off by default, existing tests are unaffected — each binding instead needs
new coverage for on, off, and preload registration failing.

🗣 Discussion

Questions I would most like input on:

  1. Element location. Is Selenium taking over findElement retry on the client the intended
    reading of "readiness replaces implicit wait", or should this be scoped to interaction only and
    the implicit wait still forwarded?
  2. The budget when no implicit wait is set. The record proposes 10s. Playwright uses 30s. A high
    value makes failing suites slow; a low one makes the feature look unreliable on slow CI.
  3. Naming and placement of the snapshot and handler surface — left open here on purpose, for a
    separate discussion.
  4. Scope of the guarantee. The heuristics will be wrong sometimes; a false "not ready" turns a
    passing test into a timeout. Is "best-effort, documented limits, off unless asked for" enough?
  5. The path to default-on. Off by default is shippable but reaches only users who know the
    capability exists — which is not the population that flakes. What would make default-on
    acceptable: a major release, a release cycle of feedback, something else?

Not yet discussed at a TLC meeting — requesting an agenda slot.

📌 Tracking

Tracking issue: (linked on acceptance)

@AutomatedTester
AutomatedTester force-pushed the adr-interaction-readiness-waits branch 2 times, most recently from 181658f to d03f066 Compare August 6, 2026 10:37
Proposes that Selenium wait for interaction readiness before it acts:
click, send_keys, clear and submit wait for the element to be actionable
by default on a BiDi-enabled session, bounded by a new readiness timeout
and raising today's interaction errors with the diagnosis appended.
Classic sessions are unchanged, and a session toggle disables it.

Readiness is modelled in three composable layers - a pending-work
ledger, DOM settledness, and element actionability - also exposed as
explicit protocol-neutral waits, with the semantics defined once in a
shared JavaScript atom injected as a BiDi preload script, and framed as
a prototype of a proposed BiDi quiescence module.

Status: Proposed.
@AutomatedTester
AutomatedTester force-pushed the adr-interaction-readiness-waits branch from d03f066 to e71ef1c Compare August 10, 2026 08:29
@AutomatedTester AutomatedTester changed the title [docs] ADR: Selenium waits for interaction readiness, not just element presence [docs] ADR: Selenium waits for interaction readiness, by default on BiDi sessions Aug 10, 2026

@titusfortner titusfortner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a better way to wait before acting will be a huge improvement, but I'd like more flexibility in how this functionality is provided to users. Some of my thoughts suggestions on how this would work below. These all assume that users enable bidi, even for classic methods that have not yet been re-implemented with BiDi.

  1. Actionable should be a replacement for "implicit wait" when bidi is enabled, rather than its own thing.
  2. Actionable still needs to default to 0 to maintain expectations from existing code. Honestly, I don't see how we force this through without breaking users existing code. Maybe a major release?
  3. Provide snapshot access to actionable so users can integrate into their existing explicit wait code without needing to rewrite.
  4. Provide events for settledness that users can subscribe to with a add_dom_settled_handler.
  5. Provide snapshots for settledness so users can wrap with whatever waiting strategy they want.
  6. Have settledness be a 4th supported PageLoadStrategy that we manage locally for navigations and clicks.
  7. Allow navigation and click methods to override page load strategy per action.
    Have different atoms for each action (that take only the arguments applicable to the feature without requiring additional client code).

default behavior of the ordinary interaction commands; the semantics are defined once, implemented
once, and also exposed as explicit waits.

**1. Readiness is modelled in three composable layers.**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't three composable layers: settled builds on pending work, but actionable is completely independent (which is what decision 2 depends on). Conflating these makes some of the other decisions less clear.

not yet ready: the page is still hydrating, a spinner is still animating, a modal is fading in over
the button, a list is re-rendering under the pointer. WebDriver's own actionability checks run at
the moment of the command — click scrolls into view and verifies the in-view centre point is
pointer-interactable — and then either succeeds or throws. They do not wait, and they do not

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple important clarifications here. The spec does say to wait for interactability on send keys and clear, using the implicit wait timeout. Additionally, Chrome waits for the element to be displayed before clicking, so we can't assume that nothing is happening currently with an implicit wait value set.

interaction point, and not moving. Which checks apply depends on the interaction: `type` and
`clear` additionally require editable; `drop` and `screenshot` do not require enabled.

**2. Interaction commands wait for actionability by default when the session has BiDi enabled.**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for actionable will stack with the interactability waits remote ends already run under the implicit wait timeout as I mentioned above. I don't think we can make this default behavior without causing problems for users. As such, I think it makes the most sense to replace implicit wait setting with this behavior because we don't want to send an implicit wait value to the remote end if we are also executing this actionable wait. But we'll still need to keep implicit wait value at 0 by default.

named, nothing new is reachable off the driver — 17670 constrains what the API *says*, and it stays
silent about BiDi here.

**3. A new `readiness` session timeout bounds the implicit wait, and failures keep their current

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree failures should keep their current type ,and since the timeout raises client-side we could even use per-type subclasses so readiness timeouts are distinguishable without breaking existing rescues. As per decision 2 comment I don't think we want a new toggle or timeout value.

appended to the message. Existing `catch`/`except` blocks keep working; a test that fails today
fails the same way, later and with a better message.

**4. Two explicit entry points remain, both on the driver, both protocol-neutral.** Names follow

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern of driver.method(element) breaks object orientation, and would work better either acting on the element object, or in the driver.script domain. The latter seems the easier change, especially since I don't think we want either of these methods to be the primary way users access this functionality. I'd much rather see us integrate them better into our existing API, and then provide more flexibility beyond just the remote-end controlled wait. Maybe we can discuss what these might look like on Slack.

The pending-work ledger is an input to the other two (`requirePendingQuiet`), not a third public
entry point — "are there pending timers?" is not a question users should have to ask.

**5. An explicit wait that times out raises the binding's existing timeout error, and the error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to limit exposure to this functionality to two remote-end managed wait methods. The plan for script module is to have a synchronous script execution method and then support async behavior with handlers. So the examples in your python repo won't fit what we're planning to support.

I think we want to provide more powerful access to settledness via a callable method similar to add_dom_mutation_handler, and there is a lot of value to providing access to a snapshot of the record for any client library to manage with their own waiting strategies instead of forcing a single option.

to fix. A non-blocking inspection call returning the same snapshot without waiting is available for
diagnostics and for users building their own conditions.

**6. One implementation, shared by all bindings.** The readiness oracle is a single JavaScript

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is conflating a shared implementation with a single atom combining all the functionality described here. We can share the functionality with multiple atoms that perform specific functions with specific arguments that are directly applicable so the bindings don't need to add additional logic beyond passing arguments to the atom.

- correct the Context: Element Clear and Element Send Keys do wait on the
  implicit wait timeout; the gap is the weak predicate, click having no wait,
  the shared budget, and the absent diagnosis
- separate the two readiness signals; actionability no longer presented as a
  layer stacked on settledness
- gate on a se:interactionReadiness capability and replace the implicit wait
  instead of adding a second timeout that stacks with the remote end's
- take over element-location waiting on the client where the implicit wait is
  no longer forwarded
- raise readiness-specific subclasses of the existing interaction errors
- drop the driver-level wait methods; expose snapshots and a settledness
  handler in the script domain instead
- add a locally managed settled page load strategy and per-action override
- split the single atom into narrow per-signal entry points
@AutomatedTester AutomatedTester changed the title [docs] ADR: Selenium waits for interaction readiness, by default on BiDi sessions [docs] ADR: Selenium waits for interaction readiness before it interacts Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants