Skip to content

[adr] Network async/event API proposal - #17685

Merged
titusfortner merged 21 commits into
SeleniumHQ:trunkfrom
titusfortner:adr_handler_behavior
Sep 10, 2026
Merged

titusfortner merged 21 commits into
SeleniumHQ:trunkfrom
titusfortner:adr_handler_behavior

Conversation

@titusfortner

@titusfortner titusfortner commented Jun 16, 2026 •

Copy link
Copy Markdown
Member

📄 The decision, its rationale, considered options, and consequences are in the record file this PR
adds (docs/decisions/17685-network-handler-behavior.md);
read it there. This body is context for the decision, not a restatement of it.

🔗 Related

📝 Proposal notes

  • Satisfies a charter requirement. This is the ADR the charter ([docs] Selenium 5 release charter #17717) names for the Network
    async/event API bucket; on acceptance the charter links back to it.
  • Already agreed — this record consolidates. From the summit ([🚀 Feature]: Implement high level BiDi network commands #13993): the driver.network
    accessor, and add / remove / clear handlers for requests, responses, and authentication. The rest
    of the record settles how those handlers behave.
  • Non-blocking handlers are out of scope. Handlers block by default, and that is all this record
    settles. Whether to add non-blocking observation — and, if so, whether as a mode on these methods
    or its own surface — is a separate ADR; the last TLC discussion leaned toward blocking everything
    for now, and @nvborisenko and @diemol have argued observation is distinct enough to stand on its
    own. Nothing here forecloses that: it arrives later as an opt-in, leaving the blocking default
    intact.
  • Implementation is unspecified, but the behaviors must compose into one dispatch. For
    illustration, this Ruby sketch — each applicable handler run last-registered-first, the first to settle winning, and a raise failing the event — satisfies them:
def process_request(request)
  @handlers.reverse_each do |h|
    next unless h.applies?(request)
    begin
      h.call(request)
    rescue
      fail_request(request)
      raise
    end
    return fail_request(request) if request.failed?
    return provide_response(request) if request.response?
    return continue_request(request) if request.submit?
  end
  continue_request(request)
end

🗣 Discussion

Discussed at the TLC meetings below; see the minutes for the full discussion and attribution.

  • 2026-06-18 — the
    handler manages data collection, not the user.
  • 2026-07-16 — walked through point by point: handler
    references become objects, URL filtering belongs on the add methods with the argument type left
    to each binding, and the default disposition is to process other handlers. submit and
    observation-vs-interception left to revise.
  • 2026-07-23 — URLs pass through as strings rather than
    being decomposed or rejected; wildcards error for now; authentication gets a convenience method
    alongside the callable handler.
  • 2026-08-06 — observation vs interception dropped from
    the record, intercept by default; valid URL patterns pass through with an optional per-binding
    warning; scoping to user and browser context added.
  • 2026-08-13 — an uncaught handler exception skips
    later handlers and submits the pre-handler state.
  • 2026-09-03 — an uncaught
    handler exception fails the event rather than sending the handler's partial state, the Rack-style middleware
    behavior @p0deje suggested (strict for now, relaxable later); the context-scoping text (one window handle or
    one user context) confirmed.

📌 Tracking

Tracking issue: #18019

@titusfortner
titusfortner requested a review from a team June 16, 2026 16:15
@titusfortner titusfortner added the A-needs decision TLC needs to discuss and agree label Jun 16, 2026
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

ADR: Define network handler disposition, ordering, and failure semantics
📝 Documentation 🕐 20-40 Minutes

Grey Divider

Description

• Proposes user-facing behaviors for resolving conflicting network handlers.
• Defines handler disposition, default chaining, LIFO ordering, and exception handling.
• Specifies completion capture and body collection responsibilities for handlers.
Diagram

graph TD
U(["User/Test"]) --> API["Network API"] --> HS["Handler stack"] --> HC["Handler callable"] --> D{"Disposition?"}
D --> F["Fail request"] --> B["BiDi command"]
D --> R["Provide response"] --> B
D --> S["Continue/Submit"] --> B
HC --> E["Log & skip"]
HC --> C[("Completed event")]

subgraph Legend
  direction LR
  _actor(["Actor"]) ~~~ _proc["Process"] ~~~ _dec{"Decision"} ~~~ _store[("Stored state")]
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Require explicit disposition (Playwright-style fallback)
  • ➕ Avoids ambiguity: every handler must declare whether it stops or continues processing
  • ➕ Reduces surprises when handlers accidentally “chain” due to omitted disposition
  • ➖ More verbose for common “mutate and continue” cases (headers, URL rewrites, etc.)
  • ➖ Makes composition of multiple small handlers more cumbersome
2. Run all handlers then reconcile by fixed priority
  • ➕ Deterministic resolution even if multiple handlers set conflicting dispositions
  • ➕ Potentially simpler mental model for some users (priority table)
  • ➖ Prevents a specific handler from intentionally short-circuiting others
  • ➖ Can unintentionally apply mutations from handlers that “should not have run” under a user-chosen stop condition
3. FIFO ordering (first registered wins)
  • ➕ Matches many event-listener models where registration order implies precedence
  • ➕ Potentially easier to reason about for simple setups
  • ➖ Makes local overrides difficult (tests can’t reliably override shared/global handlers)
  • ➖ Encourages brittle global configuration and test coupling

Recommendation: The ADR’s proposed model (explicit disposition when needed, default chaining, LIFO precedence, and exception isolation) is the most composable for real test suites where local overrides must trump shared defaults. The main point to validate with stakeholders is whether the default “continue to next handler” is acceptable ergonomically and safety-wise; if not, consider the explicit-fallback alternative, but expect materially more boilerplate across bindings.

Files changed (1) +230 / -0

Documentation (1) +230 / -0
network-handler-behavior.mdAdd ADR defining network handler behavior and precedence rules +230/-0

Add ADR defining network handler behavior and precedence rules

• Introduces a proposed ADR that specifies how request/response handlers compose and resolve conflicts. Defines disposition verbs, default chaining behavior, LIFO ordering, exception handling, ignoring return values, access to original vs mutated event state, completion capture, and body-collection responsibilities.

docs/decisions/network-handler-behavior.md

@qodo-code-review

qodo-code-review Bot commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. UrlPattern naming mismatch ✓ Resolved 🐞 Bug ≡ Correctness ⭐ New
Description
Decision 2 defines URL-pattern components as host/path/query and uses UrlPattern.host(...),
but existing Selenium BiDi UrlPatternPattern implementations use hostname/pathname/search and
Java’s UrlPattern exposes hostname() (no host()). Without an explicit canonical
naming/mapping, bindings are likely to implement incompatible pattern objects or conflicting
examples.
Code

docs/decisions/17685-network-handler-behavior.md[R57-72]

+2. **A handler is scoped by an optional list of URL patterns given as structured objects.** Each
+   pattern is an object of URL components — protocol, host, port, path, query — each optional: a
+   component that is set matches exactly, one left unset matches any value. A handler matches a
+   request against any pattern in its list; with no list it matches every request. A pattern is an
+   object, not a URL or glob string.
+
+```ruby
+# One or more component objects; a request matches any of them
+network.add_request_handler(url_patterns: [{host: "api.example.com"}, {host: "cdn.example.com"}]) { |r| r.fail }
+```
+
+```java
+network.addRequestHandler(
+    List.of(UrlPattern.host("api.example.com"), UrlPattern.host("cdn.example.com")),
+    r -> r.fail());
+```
Evidence
The ADR defines and exemplifies URL pattern components using host and UrlPattern.host(...),
while the existing Selenium BiDi URL pattern types and helpers across bindings consistently use
hostname/pathname/search and Java has no host()/host(...) API. This mismatch demonstrates
the ADR’s current wording/examples are not aligned with the established BiDi shape in the repo and
must be clarified to avoid divergent implementations.

docs/decisions/17685-network-handler-behavior.md[57-72]
docs/decisions/17685-network-handler-behavior.md[84-87]
java/src/org/openqa/selenium/bidi/network/UrlPattern.java[25-56]
javascript/selenium-webdriver/bidi/urlPattern.js[18-89]
rb/lib/selenium/webdriver/bidi/protocol/network.rb[238-255]
py/private/_network_handlers.py[213-243]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Decision #2 introduces URL pattern component names (`host`, `path`, `query`) and Java example factories (`UrlPattern.host(...)`) that conflict with Selenium’s current BiDi UrlPatternPattern field naming (`hostname`, `pathname`, `search`). This creates an ambiguous cross-binding contract that can lead to incompatible implementations.

### Issue Context
Selenium already has BiDi URL pattern representations in multiple bindings (Java/JS/Ruby/Python) that follow the BiDi field names.

### Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[57-72]
- docs/decisions/17685-network-handler-behavior.md[84-87]

### What to change
- Pick one of:
 - **Adopt the BiDi field names in the ADR** (`hostname`, `pathname`, `search`) and update the Ruby/Java snippets accordingly (e.g., `new UrlPattern().hostname("api.example.com")...`).
 - **Keep the ADR’s conceptual names** (`host`/`path`/`query`) but explicitly define the normative mapping to BiDi (`host→hostname`, `path→pathname`, `query→search`) and update the Java examples so they are valid (don’t call non-existent `UrlPattern.host(...)` unless the ADR also specifies adding such factories).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Auth handler API ambiguous ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
Decision #2 states authentication handlers "return credentials" and "do not ... respond", but the
adjacent example calls c.respond(...) (a side-effect API) instead of returning credentials. This
internal contradiction makes the proposed auth-handler contract unclear and risks different bindings
implementing incompatible semantics.
Code

docs/decisions/17685-network-handler-behavior.md[R55-64]

+2. **A handler is a callable, including authentication.** A request or response callable receives the
+   event object and acts on it. An authentication callable receives the challenge and returns
+   credentials for it; it does not fail, respond, or submit, and is not part of the disposition
+   chain. A callable lets credentials be computed per challenge; a static username and password for a
+   URL pattern is also accepted directly, without a callable.
+
+```ruby
+network.add_authentication_handler { |c| c.respond(vault.credentials_for(c.url)) }
+network.add_authentication_handler(username: "user", password: "pass", uri: "https://secure.example.com/*")
+```
Evidence
The ADR’s normative text says auth handlers return credentials and "do not ... respond", but the
included Ruby sketch demonstrates c.respond(...), creating an explicit contradiction within the
same decision section.

docs/decisions/17685-network-handler-behavior.md[55-64]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Decision #2 describes authentication handlers as returning credentials and explicitly says they do not `respond`, but the example immediately uses `c.respond(...)`. This is a normative ADR, so the prose and example must align to avoid inconsistent cross-binding implementations.

### Issue Context
You can resolve this either by:
- Making the contract **return-based** (handler returns credentials; no `respond` method), **or**
- Making the contract **command-based** (handler calls `respond(credentials)` / similar on the challenge object; it is not part of the request/response disposition chain).

### Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[55-64]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Undefined add_x_handler name ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
Decision #10 says body collection happens only through add_x_handler, but the ADR otherwise uses
concrete method names like add_request_handler and add_response_handler. Leaving an undefined
placeholder in the normative text can confuse implementers about the intended API surface and where
body collection is supported.
Code

docs/decisions/17685-network-handler-behavior.md[R165-177]

+10. **Body data is collected only when the handler opts in at registration.** A body is not available
+    by default; the handler declares that it needs the body when it is registered — not from inside
+    the callback, since the collector must be in place before the event — and Selenium then owns the
+    collector's lifecycle, size cap, and browser-support quirks. The body is readable on the event
+    inside that handler.
+   * The user never calls `addDataCollector` / `getData` or tears a collector down.
+   * There is no way to collect or read body data outside a handler; collection happens only through
+     `add_x_handler`.
+
+```ruby
+# Declare body collection at registration; the body is then available on the event
+network.add_response_handler(collect_body: true) { |r| log(r.body) }
+```
Evidence
The body-collection rule references add_x_handler, but the same ADR’s examples and API discussion
use add_request_handler and add_response_handler, indicating add_x_handler is not a defined
method name here.

docs/decisions/17685-network-handler-behavior.md[165-172]
docs/decisions/17685-network-handler-behavior.md[49-53]
docs/decisions/17685-network-handler-behavior.md[174-177]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Decision #10 uses `add_x_handler` without defining it as shorthand or naming the actual methods. Elsewhere the ADR uses `add_request_handler` / `add_response_handler`, so the placeholder is ambiguous.

### Issue Context
If body collection is supported for both request and response handlers, name both explicitly (or define `add_x_handler` as shorthand once and use it consistently). If it’s only supported for response handlers, say so directly.

### Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[165-177]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Java order nondeterministic ✓ Resolved 🐞 Bug ≡ Correctness
Description
The ADR table says Java runs “only the first matching handler”, but the current Java implementation
selects a matching handler from a ConcurrentHashMap stream with no defined encounter order, so the
chosen handler is effectively arbitrary and can vary between runs. This can mislead
readers/implementers about Java’s current precedence semantics.
Code

docs/decisions/17685-network-handler-behavior.md[18]

+| Java       | Only the first matching handler runs; disposition is always continue; a throwing handler propagates and leaves the request blocked; return-value driven; no response handler or managed body collection. |
Evidence
The ADR claims a “first” handler semantics for Java, but Java stores handlers in a ConcurrentHashMap
and selects via values().stream()...findFirst(), which does not guarantee ordering; therefore the
selected handler is not consistently the first-registered or otherwise deterministic handler.

docs/decisions/17685-network-handler-behavior.md[16-22]
java/src/org/openqa/selenium/remote/RemoteNetwork.java[47-50]
java/src/org/openqa/selenium/remote/RemoteNetwork.java[143-148]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The ADR’s “Current behavior” row for Java states “Only the first matching handler runs”, which implies a deterministic ordering. In the current Java implementation, handler selection is performed by streaming `ConcurrentHashMap.values()` and taking `findFirst()`, which has no guaranteed iteration/encounter order.

### Issue Context
This is in the ADR’s binding comparison table; the goal is to accurately describe current behavior so readers understand what is changing.

### Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[16-22]

### Suggested change
Reword the Java row to avoid implying deterministic precedence (e.g., “Only one matching handler runs (selection order is unspecified) ...”), or explicitly state the current selection is not ordered by registration.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. No cross-binding source references 📘 Rule violation ≡ Correctness
Description
The ADR describes binding differences and proposes user-visible handler behavior changes but does
not provide any concrete cross-binding source references (e.g., rg results or links to the
Java/Python/Ruby/.NET/JS implementations) demonstrating the comparison was actually performed. This
risks inconsistent implementation across bindings because reviewers/implementers cannot verify the
stated current behaviors or the proposed alignment points.
Code

docs/decisions/17685-network-handler-behavior.md[R13-22]

+The bindings diverge today: each grew its handler API independently, so dispatch order,
+multi-handler resolution, error handling, and what an event exposes are all inconsistent.
+
+| Binding    | Current behavior |
+|------------|------------------|
+| Java       | Only the first matching handler runs; disposition is always continue; a throwing handler propagates and leaves the request blocked; return-value driven; no response handler or managed body collection. |
+| Python     | An explicit `continue` in a handler fires immediately and wins; otherwise staged outcomes reconcile by `fail` > `provide_response` > `continue`; response handlers have no `fail`; dispatch is FIFO; a throwing handler's staged mutations are still sent; only the mutated event is visible; body is not collected behind the handler. |
+| Ruby       | Handlers run in parallel threads, so multi-handler disposition races; exceptions are logged; dispatch is FIFO with no default-continue; only the mutated event is visible; body collection is user-managed. |
+| .NET       | No request or response handler API. |
+| JavaScript | No request or response handler API. |
Evidence
PR Compliance ID 389265 requires evidence of cross-binding comparison (e.g., project-wide search
output or references to compared files). The ADR includes a cross-binding behavior table but
provides no file references or search evidence to substantiate the claims, making the required
comparison non-auditable.

Rule 389265: Compare cross-language bindings when changing user-visible behavior
docs/decisions/17685-network-handler-behavior.md[13-22]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR asserts current cross-binding behaviors and proposes new user-visible behavior, but it lacks verifiable evidence of cross-binding comparison (project-wide search output and/or direct links to the analogous implementations in other bindings).

## Issue Context
Compliance requires that behavior-changing proposals include evidence of cross-language comparison so implementers can validate baseline behavior and keep bindings logically consistent.

## Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[13-22]

## Suggested remediation
- Add a short section (e.g., "Cross-binding references") that lists at least one concrete file/link per compared binding (Java/Python/Ruby at minimum) that supports the "Current behavior" table entries.
- Optionally include a small snippet of the exact `rg ...` commands used (and the key hits) to demonstrate the repo-wide search comparison was performed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (5)
6. Auth callable contradiction ✓ Resolved 🐞 Bug ≡ Correctness
Description
The ADR excludes authentication handlers because “authentication should not use a callable,” but
Selenium Ruby’s add_authentication_handler explicitly accepts a block and existing tests use
block-based auth handlers (&:skip, &:cancel). This makes the ADR’s rationale inaccurate for at
least one binding and risks confusing implementers about the scope/baseline.
Code

docs/decisions/17685-network-handler-behavior.md[R20-21]

+This applies to request and response handlers, but not authentication handlers, since
+authentication should not use a callable.
Evidence
Ruby’s Network API accepts an optional block for add_authentication_handler and uses it when
credentials aren’t provided, and Ruby integration tests exercise callable auth handlers via &:skip
and &:cancel, contradicting the ADR’s claim that authentication should not use a callable.

rb/lib/selenium/webdriver/common/network.rb[47-63]
rb/spec/integration/selenium/webdriver/network_spec.rb[86-101]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR states authentication handlers are out of scope because authentication should not use a callable. In current Selenium Ruby, authentication handlers can be provided as a callable/block, so the statement is not universally true across bindings.

## Issue Context
The ADR is meant to be cross-binding guidance; blanket statements that contradict an existing binding’s public API are likely to cause confusion.

## Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[20-21]

## Suggested change
Reword to scope the statement as a proposal or explicitly note that auth handler behavior is out of scope and that existing bindings differ (e.g., “This ADR does not cover authentication handlers; bindings currently vary, and we may revisit whether auth should be callable-based separately.”).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Wrong Python behavior claim 🐞 Bug ≡ Correctness
Description
The ADR claims current Python behavior lets continueRequest override failures and stubs, but
Python’s BiDi network handler registry resolves outcomes with fixed precedence where any fail()
wins and provide_response() wins over mutations. This misstates the existing semantics and can
mislead readers about what is actually changing.
Code

docs/decisions/17685-network-handler-behavior.md[R158-159]

+  - We could run every handler but have `continueRequest` override failures and stubs (current
+    Python behavior), but it is not obvious why that command should have precedence.
Evidence
Python’s handler registry documents and implements reconciliation where any handler calling fail()
causes the request to be failed during _resolve(), and the integration tests assert that fail
wins when handlers disagree—so continueRequest does not override failures/stubs in current Python
behavior.

py/private/_network_handlers.py[28-37]
py/private/_network_handlers.py[428-437]
py/test/selenium/webdriver/common/bidi_network_tests.py[251-266]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR’s “Considered options → Reconciliation” section states that in current Python behavior `continueRequest` overrides failures and stubs. The Python implementation instead reconciles handler actions with a strict priority (fail > provide_response > continue-with-mutations/default continue), so the ADR’s comparison baseline is inaccurate.

## Issue Context
This ADR is intended to guide cross-binding behavior decisions; incorrect statements about existing behavior can skew review/consensus and lead to incorrect implementations.

## Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[154-160]

## Suggested change
Reword the parenthetical to reflect the actual Python semantics (e.g., “current Python behavior reconciles outcomes with fixed priority (fail > provide_response > continue) after running all matching handlers”).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Overgeneralized exception behavior ⊘ Outdated 🐞 Bug ⚙ Maintainability
Description
The ADR states (in a comparative bullet) that Selenium logs uncaught handler exceptions instead of
ending the session, but current behavior is binding-specific (e.g., Ruby handler blocks are executed
without rescue/logging, while Python does catch/log). This should be reworded as a proposed
cross-binding behavior or explicitly scoped to the bindings that implement it today.
Code

docs/decisions/network-handler-behavior.md[R77-80]

+   * In Playwright, uncaught exceptions propagate to end the session, which causes problems when
+     something unrelated to the test's intent goes wrong.
+   * Selenium is more lenient and only logs the error to the console.
+
Evidence
Ruby currently invokes handler blocks without rescue/logging, while Python explicitly catches and
logs exceptions, so the ADR’s statement is not uniformly true across Selenium bindings today.

docs/decisions/network-handler-behavior.md[77-80]
rb/lib/selenium/webdriver/common/network.rb[89-96]
py/private/_network_handlers.py[746-756]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The ADR text implies a universal Selenium behavior (“only logs the error to the console”) for handler exceptions, but current bindings differ.

### Issue Context
Ruby’s handler dispatch yields directly to user code without rescue; Python catches/logs and continues.

### Fix Focus Areas
- docs/decisions/network-handler-behavior.md[72-80]

### What to change
- Rephrase the bullet to one of:
 - “In some Selenium bindings (e.g., Python), uncaught handler exceptions are logged and processing continues…”
 - or “Proposed: Selenium should log uncaught handler exceptions and continue processing…”
- If desired, add a short note indicating current binding differences to avoid readers assuming uniform behavior today.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Wrong handler verb names ⊘ Outdated 🐞 Bug ≡ Correctness
Description
The ADR states Selenium request/response handlers use respond/submit (and response fail), but
existing bindings expose different verbs (e.g., Python provide_response/continue_request, Ruby
provide_response/continue) and do not offer a response-level fail. This mismatch will mislead
implementers/readers about current APIs and what exactly is being proposed to change.
Code

docs/decisions/network-handler-behavior.md[R31-43]

+   * Selenium supports:
+     * Request: `fail` (Playwright's `abort`, BiDi's `FailRequest`), `respond` (Playwright's `fulfill`, BiDi's `ProvideResponse`), and `submit` (Playwright's `continue`, BiDi's `ContinueRequest`).
+     * Response: `fail` (BiDi's `FailRequest`), and `submit`: note that since we don't need to prevent a round trip from a request, whether this is a BiDi `ContinueResponse` or `ProvideResponse` can be an implementation detail based on whether a replacement body value is provided.
+
+```ruby
+# Specifics of parameters and names can match spec details
+network.add_request_handler { |r| r.fail if something }
+network.add_request_handler { |r| r.respond(content: mocked_response) if something }
+network.add_request_handler { |r| r.add_header("X-Test", true) && r.submit if something }
+
+network.add_response_handler { |r| r.fail if something }
+network.add_response_handler { |r| r.submit(content: mocked_response) if something }
+network.add_response_handler { |r| r.add_header("X-Test", true) && r.submit if something }
Evidence
The ADR’s listed verbs don’t exist in current binding implementations, which use different method
names and capabilities for request/response interception.

docs/decisions/network-handler-behavior.md[31-43]
py/private/_network_handlers.py[325-438]
py/private/_network_handlers.py[440-581]
rb/lib/selenium/webdriver/bidi/network/intercepted_request.rb[30-58]
rb/lib/selenium/webdriver/bidi/network/intercepted_response.rb[31-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The ADR uses handler disposition verbs (`respond`, `submit`, and response `fail`) that don’t match the verbs exposed by current Selenium bindings, which makes the document confusing as a proposal baseline.

### Issue Context
Current Python and Ruby bindings use `provide_response`/`continue_request` (Python) and `provide_response`/`continue` (Ruby), and do not expose a response-level `fail`.

### Fix Focus Areas
- docs/decisions/network-handler-behavior.md[26-44]

### What to change
- Replace “Selenium supports:” wording with either:
 - a binding-neutral set of verbs (and explicitly state they are *proposed* names), or
 - the currently used names (and optionally add a mapping table: proposed ↔ existing per binding).
- Update the Ruby/Python code examples accordingly (e.g., `provide_response`/`continue_request`/`continue`) and remove or clearly label response `fail` as a proposed addition if that’s intended.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Example violates stated behavior ⊘ Outdated 🐞 Bug ≡ Correctness
Description
The Appendix claims the sample process_request implementation satisfies the ADR behaviors, but it
does not handle handler exceptions (behavior 4) and would propagate exceptions from
h.call(request) instead of logging and continuing. As written, it also cannot discard per-handler
staged mutations on exception because it mutates a shared request object.
Code

docs/decisions/network-handler-behavior.md[R208-226]

+The behaviors in this ADR explicitly do not specify an implementation. For illustrative
+purposes, this code — with state stored in the request wrapper object and evaluated after
+execution inside the loop — will satisfy the above behaviors:
+
+```ruby
+def process_request(request)
+  @handlers.reverse_each do |h|
+    h.call(request)
+    if request.complete?
+      h.request = request
+      remove_handler(h)
+    end
+    if request.failed?
+      return fail_request(request)
+    elsif request.response?
+      return provide_response(request)
+    elsif request.submit? || request.complete?
+      return continue_request(request)
+    end
Evidence
The ADR requires log-and-continue on handler exceptions, but the appendix code calls handlers
without any rescue/try-catch while still claiming it satisfies the behaviors.

docs/decisions/network-handler-behavior.md[72-79]
docs/decisions/network-handler-behavior.md[208-226]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The appendix implementation is presented as satisfying the ADR behaviors, but it lacks exception handling around handler invocation and does not show how staged mutations are discarded when a handler errors.

### Issue Context
Behavior 4 requires uncaught exceptions to be logged, staged changes discarded, and processing to continue as if the handler was not registered.

### Fix Focus Areas
- docs/decisions/network-handler-behavior.md[72-85]
- docs/decisions/network-handler-behavior.md[208-229]

### What to change
- Wrap `h.call(request)` in a begin/rescue (or language-appropriate equivalent) and explicitly note logging + continuing.
- Either:
 - update the pseudocode to show per-handler isolation/rollback of staged mutations (e.g., snapshot/clone before calling handler and restore on exception), or
 - amend the text to state the pseudocode is simplified and *does not* model rollback semantics (so it’s not claimed to fully satisfy behavior 4).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

11. Exception propagation unclear ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
Behavior 5 says intercept-handler exceptions “propagate” while also requiring the event to “keep
flowing as if that handler had not run,” but it doesn’t explicitly state how propagation should be
surfaced without interrupting dispatch/request resolution. This ambiguity can lead to inconsistent
cross-binding implementations of the same proposed behavior.
Code

docs/decisions/17685-network-handler-behavior.md[R104-106]

+5. **An uncaught exception discards the handler's staged changes; it propagates for an intercept
+   handler and is logged for an observe handler.** Either way the event keeps flowing as if that
+   handler had not run, so one broken handler cannot corrupt live traffic or stall the page. The
Evidence
The ADR simultaneously requires exception “propagation” for intercept handlers and continued event
flow, and the provided example implies remaining handlers still take effect; existing handler
dispatch code in-repo shows continued flow is typically implemented by catching exceptions and
continuing, which highlights why the ADR should be explicit about how “propagate” is achieved
without interrupting dispatch.

docs/decisions/17685-network-handler-behavior.md[104-106]
docs/decisions/17685-network-handler-behavior.md[114-120]
py/private/_network_handlers.py[746-766]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Behavior 5 mixes two distinct requirements (error visibility and continued network flow) without stating the intended mechanism for “propagate” in intercept mode. Readers may interpret “propagate” as letting the exception abort handler dispatch, which conflicts with the “event keeps flowing” requirement.

## Issue Context
The ADR’s example text also implies that other handlers’ effects still apply even when one handler errors.

## Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[104-111]
- docs/decisions/17685-network-handler-behavior.md[114-120]

## Suggested direction
Adjust behavior 5 wording to explicitly separate:
1) **Network disposition guarantee:** Selenium must still resolve/unblock the event as if the failing handler did not run (discarding its staged mutations).
2) **Error visibility guarantee:** For intercept handlers, the error is surfaced to the user (e.g., recorded and raised at a defined observation point / next driver call), while dispatch continues; for observe handlers, it is logged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Markdown nesting broken ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
Under decision #7, the first bullet line is indented less than the preceding continuation lines of
the same list item, which can cause CommonMark/GitHub rendering to treat it as a separate top-level
list instead of nested under item 7. This makes the ADR harder to read and can change the visual
structure of the decision.
Code

docs/decisions/17685-network-handler-behavior.md[R129-134]

+7. **Body data is collected only when the handler opts in at registration.** A body is not
+   available by default; the handler declares that it needs the body when it is registered — not
+   from inside the callback, since the collector must be in place before the event — and Selenium
+   then owns the collector's lifecycle, size cap, and browser-support quirks. The body is readable
+   on the event inside that handler.
+  * The user never calls `addDataCollector` / `getData` or tears a collector down.
Evidence
Within list item 7, the continuation lines are indented deeper than the bullet that follows,
indicating inconsistent nesting that can alter Markdown rendering.

docs/decisions/17685-network-handler-behavior.md[129-134]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The nested bullet list under item 7 is not consistently indented, which can break Markdown nesting and render incorrectly.

### Issue Context
Under ordered list item `7.`, the paragraph continuation lines are indented more than the subsequent `*` bullet line, so the bullets may not be considered part of item 7.

### Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[129-134]

### Suggested change
Indent the `*` bullet lines to the same level as the continuation lines for item 7 (typically 3–4 spaces after the margin for CommonMark nested content under an ordered list item).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🚀 Fast: This is a single, self-contained documentation-only ADR addition; while its proposed API semantics warrant review, the change has no runtime or configuration behavior.

Grey Divider

Tip of the day
💡 Did you know, you can turn these tips off under Display preferences

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@titusfortner
titusfortner force-pushed the adr_handler_behavior branch from b09a2bc to 18895d1 Compare June 16, 2026 16:19
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 18895d1

Comment thread docs/decisions/17685-network-handler-behavior.md Outdated

@diemol diemol 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.

I think this ADR will help us unify handler behavior across all language bindings, which is currently handled differently in each language.

If I understand this correctly, @titusfortner suggests this ADR is an alternative to #17671. But what I see is that this ADR covers network interception, which blocks because it waits for a handler disposition. Whereas #17671 is about observation, which should not block, and only subscribes to events (did I get that right, @AutomatedTester?).

That is why I believe the two ADRs complement each other.

In addition, this ADR, like #17671, seems to me to be a middle-layer API. One that users would use for specific use cases that the high-level API won't cover.

Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
@diemol diemol mentioned this pull request Jun 22, 2026
2 tasks
@titusfortner

Copy link
Copy Markdown
Member Author

@diemol

Whereas #17671 is about observation, which should not block, and only subscribes to events

In the last TLC meeting people were pushing for the idea that for now everything that can block will block in our implementations and we won't handle it any differently.

I'm saying they are in opposition because if you adopt this ADR, you don't need the extra methods proposed by the other ADR.

Also, I think it is wrong to characterize these as "middle-layer API." Anything that affects the code the user needs to write is user-facing and needs to be part of the high-level API contract we are creating that we want to maintain.

@diemol

diemol commented Jun 22, 2026

Copy link
Copy Markdown
Member

@diemol

Whereas #17671 is about observation, which should not block, and only subscribes to events

In the last TLC meeting people were pushing for the idea that for now everything that can block will block in our implementations and we won't handle it any differently.

I'm saying they are in opposition because if you adopt this ADR, you don't need the extra methods proposed by the other ADR.

Also, I think it is wrong to characterize these as "middle-layer API." Anything that affects the code the user needs to write is user-facing and needs to be part of the high-level API contract we are creating that we want to maintain.

The way I see these two ADRs:

  • This one is to actually manipulate the network call.
  • The other one is for observation, because if you want to do observation and interception, then this could actually slow down tests, because you're intercepting and manipulating the call. I don't think that's what we want to do.

That's why I think there are two other options: either observe or intercept here.

I still think that this is a middle-layer API. I don't want the user to have to handle all these callbacks and other things to manipulate their code in BiDi. I want this API to land and be used in a higher-level API to do network routing, mocking, and method interception, covering all of this implementation.

In my head, we should have:

  1. The CDDL parsing to generate the low-level stuff
  2. The part where we map commands and do the instantiation of the driver
  3. This current ADR, which is like the middle level
  4. Some high-level features that make it very easy for users to actually interact with BiDi

If this current ADR is what we're doing at the high level, it is way too complex.

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 281e9bb

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 895d532

@titusfortner

Copy link
Copy Markdown
Member Author
  1. I removed the requirement for a "complete" resolution method because it was confusing things and also because I no longer think it is necessary right now
  2. I added the requirement for toggling observation vs interception to minimize the confusion around this one vs [docs] decision: BiDi events are awaited with expect_* context managers #17671. Our normal handler method needs to support observation directly either now or in the future, we don't need (and shouldn't have) a separate method just because of that.
  3. I want to push back on calling this middle layer. This is what we agreed to as the high layer at the TLC summit in SF 2 years ago. To clarify, what I think you are suggesting is methods that wrap the handler so users don't need to work with lambdas for common cases? If so, that represents a major expansion of the API across all the bindings for no functional gain when we don't even have the underlying behavior we want sorted out.

Can we get the functionality implemented across the bindings then figure out which wrappers and helpers and fixtures to add later?

I feel like we may need to step back from the ADRs and agree to the milestones and requirements for Selenium 5 again.

@diemol

diemol commented Jun 24, 2026

Copy link
Copy Markdown
Member

I removed the requirement for a "complete" resolution method because it was confusing things and also because I no longer think it is necessary right now

This is confusing because my interpretation is that the complete resolution is one of the core items of this ADR. Specially for network interception.

Our normal handler method needs to support observation directly either now or in the future, we don't need (and shouldn't have) a separate method just because of that.

I think this bit is what #17671 and this ADR need to agree on. That is the foundation for both.

I want to push back on calling this middle layer. This is what we agreed to as the high layer at the TLC summit in SF 2 years ago. To clarify, what I think you are suggesting is methods that wrap the handler so users don't need to work with lambdas for common cases? If so, that represents a major expansion of the API across all the bindings for no functional gain when we don't even have the underlying behavior we want sorted out.

I am calling it a middle layer, which is core to what we want to achieve, because when I check Playwright's API, I see a high-level API that lets users handle less. But I think you have a point: landing all this "first and later" about major abstractions.

@titusfortner

Copy link
Copy Markdown
Member Author

my interpretation is that the complete resolution is one of the core items of this ADR.

Based on your questions it didn't seem like you understood my intention for including it or how it worked (it was never blocking anything, there was nothing in the lambda that needed resolving or a true state), so it seemed distracting from what I consider to be the primary things I want us to agree on.

Essentially 17671 and the "complete" method are 2 different ways to manage a wrapper around behavior the user can already easily do themselves for network calls.

desired_request = nil
handler = driver.network.add_request_handler { |req| desired_request = req if matching_condition(req) }
do_something
wait.until { !desired_request.nil? }
driver.network.remove_request_handler(handler)

Is it actually a priority to provide convenience methods to replaces those 4 lines?

When I started thinking in those terms I decided that we don't need wrapper behavior right now, so if I want to reject 17671 on that basis, I should also reject my "complete" method. If we decide we want wrapper behavior, then I would still prefer "complete" over "expect_*"

But also it feels like we're arguing over implementation details of a concept we haven't even agreed is behavior we need right now, so I also want to be able to step back one level on it.

For instance, I don't think "how to differentiate between observation and interception" is actually fundamental to any of our other conversations. The TLC meeting 2 weeks ago the general consensus from everyone else was to defer this decision until later because "intercept everything for now" works well enough for most things. I'd rather hammer it out now, but I'm fine with pushing it off. I only added it here to show that supporting it isn't a sufficient reason on its own to expand the API and accept 17671.

Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
@titusfortner
titusfortner force-pushed the adr_handler_behavior branch from 895d532 to 2d7b847 Compare July 13, 2026 15:42
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 2d7b847

@titusfortner titusfortner changed the title [adr] network handler behavior proposal [adr] Network async/event API proposal Jul 13, 2026
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
Comment thread docs/decisions/17685-network-handler-behavior.md
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 1d78efe

Comment thread docs/decisions/17685-network-handler-behavior.md
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 0e068c9

Comment thread docs/decisions/17685-network-handler-behavior.md
Comment thread docs/decisions/17685-network-handler-behavior.md
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit d87ed06

Comment thread docs/decisions/17685-network-handler-behavior.md Outdated
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 7d08abd

Comment thread docs/decisions/17685-network-handler-behavior.md
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit aea88db

AutomatedTester and others added 2 commits August 17, 2026 15:45
…r context, never both

Decision 11 said a handler applies to "the current browsing context" and that
the user may pass a browsing context, a user context, or both. None of that
matches WebDriver BiDi:

- network.addIntercept takes phases/contexts/urlPatterns only; it has no
  userContexts parameter (w3c/webdriver-bidi#845). Only session.subscribe
  accepts one.
- contexts and userContexts are mutually exclusive; passing both is an
  invalid argument error.
- addIntercept rejects a non-top-level navigable, and session.subscribe
  widens a child id to its top-level traversable, so frame-level network
  scoping does not exist.

Scope is now stated as one top-level browsing context (the current window
handle by default) or a user context, never both, with the user-context
contract spelled out as covering tabs opened later. Consequences record that
a binding resolves a user-context scope itself until SeleniumHQ#845 lands. The scope
argument is named instead of the ambiguous `context:`, following 17776 for
events and 17681 for handle objects.
…etail; scope filters the chain; link 17670 by record path
Comment thread docs/decisions/17685-network-handler-behavior.md
Comment thread docs/decisions/17685-network-handler-behavior.md
Comment thread docs/decisions/17685-network-handler-behavior.md
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 6dcc669

@qodo-code-review

qodo-code-review Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. User-context handlers miss windows opened later 🐞 Bug ≡ Correctness
Description
The proposed user-context scope requires one handler to cover every current and future window in
that context, but BiDi network.addIntercept accepts only explicit browsing-context IDs.
Implementations must therefore either omit newly opened windows or register a global interception
and filter locally, which would block events outside the handler's scope and contradict the
event-dispatch rule.
Code

docs/decisions/17685-network-handler-behavior.md[R281-285]

+    A window handle targets that one window or tab, including one in the background without focus. A user
+    context targets every window handle it contains, including ones opened later, so it scopes
+    interception to a whole user context rather than a single known tab. The two are mutually
+    exclusive: a handler is scoped by one or the other, and a binding rejects being given both. A
+    handler must only act on events within its scope.
Evidence
The ADR requires user-context scoping to cover windows opened later, while the protocol schema only
provides an optional contexts list typed as browsing-context IDs and no user-context field.
Existing binding implementations mirror that limitation: .NET exposes only Contexts and
UrlPatterns, and Ruby sends only contexts and urlPatterns; using a global intercept to emulate
the scope would block events before the client can exclude out-of-scope handlers, conflicting with
the dispatch clarification.

common/bidi/schema.json[8179-8213]
dotnet/src/webdriver/BiDi/Network/AddIntercept.cs[25-36]
rb/lib/selenium/webdriver/bidi/network.rb[49-55]
docs/decisions/17685-network-handler-behavior.md[135-145]
docs/decisions/17685-network-handler-behavior.md[275-285]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR promises that a handler scoped to a user context applies to all windows in that context, including windows opened later, but the current BiDi `network.addIntercept` command only accepts explicit browsing-context IDs. A global intercept with client-side filtering would also violate the ADR's rule that out-of-scope handlers are not consulted for an event.

## Issue Context
The protocol schema defines `network.AddInterceptParameters` with `phases`, `contexts` (browsing-context IDs), and `urlPatterns`; it has no user-context scope. Update the ADR to specify an implementable behavior, or explicitly require a protocol extension and explain the interim behavior and dispatch implications.

## Fix Focus Areas
- docs/decisions/17685-network-handler-behavior.md[135-145]
- docs/decisions/17685-network-handler-behavior.md[275-285]
- common/bidi/schema.json[8179-8213]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🚀 Fast: This is a single, self-contained documentation-only ADR addition; while its proposed API semantics warrant review, the change has no runtime or configuration behavior.

Grey Divider

Tip of the day
💡 Did you know, you can turn these tips off under Display preferences

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread docs/decisions/17685-network-handler-behavior.md
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit d1a64c8

@diemol diemol 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.

This looks good to me, should we merge it?

@titusfortner
titusfortner merged commit 4d27eb3 into SeleniumHQ:trunk Sep 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-needs decision TLC needs to discuss and agree

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants