Skip to content

[adr] Install browser extensions from the driver directly - #17817

Open
titusfortner wants to merge 4 commits into
SeleniumHQ:trunkfrom
titusfortner:c/driver-extension-install-adr
Open

[adr] Install browser extensions from the driver directly#17817
titusfortner wants to merge 4 commits into
SeleniumHQ:trunkfrom
titusfortner:c/driver-extension-install-adr

Conversation

@titusfortner

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.

🔗 Related

📝 Proposal notes

  • Why one record. The driver method is the decision; the Chromium transport default and the BiDi-or-CDP session model are the two things required to make that method work without the user passing browser flags. They share one rationale and would be reversed together, so splitting them would leave a reader unable to understand any one of them alone.
  • Deliberately not here. Per-binding API shape, naming casing, and rollout order — those go in the tracking issue on acceptance. Grid needs no changes. No deprecation of the existing Firefox methods is proposed; they route through the new method when BiDi is enabled.
  • Support Firefox's install options. Firefox's moz:permanent (permanent install) and moz:allowPrivateBrowsing have no cross-browser equivalent, but the new method must support them regardless of how it is called, not only preserve them for the classic methods that route through it. They fall outside what the CDDL generation covers today — closing that gap is in progress upstream: w3c/webdriver-bidi#1140 adds a CDDL vendor-extension mechanism for webExtension.install, and Mozilla bug 2057588 asks Firefox to publish a machine-readable schema for those params. Generation isn't required to send them either way, so implementing them is tracking-issue work, not a blocker on this proposal.

🗣 Discussion

📌 Tracking

Tracking issue: (linked on acceptance)

@qodo-code-review

qodo-code-review Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Pipe described as BiDi-only ✓ Resolved 🐞 Bug ≡ Correctness
Description
The ADR claims Chromium’s pipe transport “carries WebDriver BiDi only” / “not CDP”, but the repo’s
bundled Chrome DevTools Protocol explicitly documents CDP extension commands as available when
connected via --remote-debugging-pipe. This overbroad phrasing can mislead readers/implementers
about what the pipe transport is; if the intent is “CDP is not exposed to the Selenium caller in a
BiDi session”, it should be stated that way.
Code

docs/decisions/17817-driver-extension-install.md[R17-20]

+A Chromium driver reaches the browser over one of two transports: a local TCP debugging port, or a
+pipe inherited from the process that launched the browser. The port is what makes CDP reachable;
+the pipe is private to the driver and carries WebDriver BiDi only. Chrome accepts extension install
+over BiDi only on the pipe.
Evidence
The ADR says the pipe carries BiDi only, but the vendored Chrome DevTools Protocol defines extension
commands (CDP Extensions.*) as available when the client is connected via
--remote-debugging-pipe, demonstrating the pipe is a CDP transport too. Selenium’s own Chromium
options also use --remote-debugging-pipe in the context of extension support, reinforcing that
this flag is associated with remote debugging (CDP) rather than being BiDi-only.

docs/decisions/17817-driver-extension-install.md[17-20]
common/devtools/chromium/v148/browser_protocol.pdl[5637-5652]
py/selenium/webdriver/chromium/options.py[140-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 currently states that the Chromium remote-debugging pipe "carries WebDriver BiDi only" and implies the pipe cannot carry CDP. In Chromium, `--remote-debugging-pipe` is a DevTools/CDP transport; the real intended point appears to be that enabling BiDi should not expose a caller-accessible CDP endpoint/capability.

### Issue Context
This ADR is setting rationale/constraints for future implementation. Overstating the pipe as BiDi-only conflicts with the DevTools protocol docs vendored in this repo and with existing Selenium options that treat the pipe as a remote-debugging (CDP) mechanism.

### Fix Focus Areas
- docs/decisions/17817-driver-extension-install.md[17-20]
- docs/decisions/17817-driver-extension-install.md[47-49]

### Suggested change
Reword to distinguish **(a)** the underlying browser<->driver transport (which can carry DevTools/CDP traffic over the pipe) from **(b)** what Selenium exposes to the user (e.g., no `se:cdp` endpoint / no caller-accessible CDP when BiDi is enabled), if that is the intended decision.

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



Remediation recommended

2. JS BiDi support overstated ✓ Resolved 🐞 Bug ≡ Correctness
Description
The ADR states that bindings already expose a BiDi module for extension install/uninstall, but the
same table indicates the JavaScript binding has no advertised BiDi approach (“none”), which is
contradictory and can mislead readers about cross-binding availability.
Code

docs/decisions/17817-driver-extension-install.md[R22-33]

+WebDriver BiDi specifies extension install and uninstall, which both Firefox and Chromium
+implement. Bindings already expose the BiDi module for it, and pointing users at that module is
+what we advertise today.
+
+| Binding    | Firefox-only method (classic) | Currently advertised BiDi approach |
+|------------|-------------------------------|------------------------------------|
+| Java       | `installExtension` (on `FirefoxDriver`) | `new WebExtension(driver).install(...)` |
+| Python     | `install_addon` | `driver.webextension.install(...)` |
+| Ruby       | `install_addon` (`HasAddons`) | `BiDi::Protocol::WebExtension` (protocol module) |
+| .NET       | `InstallAddOn`, `InstallAddOnFromFile`, `InstallAddOnFromDirectory` | `driver.AsBiDiAsync()` → `BiDi.WebExtension.InstallAsync(...)` |
+| JavaScript | `installAddon` | none |
+
Evidence
The Context section says bindings already expose a BiDi module for extension install/uninstall, but
the JavaScript row in the table immediately below states the JavaScript binding has no advertised
BiDi approach ("none"), creating an internal contradiction in the ADR.

docs/decisions/17817-driver-extension-install.md[22-33]

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 claims that "Bindings already expose the BiDi module" for extension install/uninstall, but then the JavaScript row in the table says there is no advertised BiDi approach ("none"). This contradiction should be resolved by either qualifying the earlier statement (e.g., "Most bindings..." or "All bindings except JavaScript...") or updating the table/statement to match the intended reality.

## Issue Context
This ADR is used as a decision record; contradictory statements in the Context section can lead to incorrect assumptions by readers and implementers.

## Fix Focus Areas
- docs/decisions/17817-driver-extension-install.md[22-33]

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



Informational

3. Broken rationale sentence ✗ Dismissed 🐞 Bug ⚙ Maintainability ⭐ New
Description
The sentence "forces users to know which protocol services the command" is missing a verb/object,
making the ADR rationale unclear and easy to misinterpret. This is a documentation correctness issue
that can confuse readers about why the BiDi module indirection is undesirable.
Code

docs/decisions/17817-driver-extension-install.md[63]

+  JavaScript. It also forces users to know which protocol services the command in order to use it.
Evidence
Line 63 contains the incomplete sentence fragment "which protocol services the command in order to
use it", which is missing a verb/object and reads incorrectly.

docs/decisions/17817-driver-extension-install.md[63-63]

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

### Issue description
In the ADR, a sentence in the rationale for rejecting “Keep pointing users at the BiDi module” is grammatically incomplete (missing a verb/object after “the command”), which harms readability and clarity.

### Issue Context
The intent appears to be describing that users must know *which protocol/service provides the command* (or *which protocol the command uses*) in order to invoke it.

### Fix Focus Areas
- docs/decisions/17817-driver-extension-install.md[63-63]

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


4. Ambiguous .NET BiDi example ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The ADR’s .NET “Currently advertised BiDi approach” example is ambiguous because it doesn’t show
that WebExtension is accessed from the *instance* returned by driver.AsBiDiAsync(), which can be
misread as BiDi being a static type/member access.
Code

docs/decisions/17817-driver-extension-install.md[R30-32]

+| Ruby       | `install_addon` (`HasAddons`) | `BiDi::Protocol::WebExtension` (protocol module) |
+| .NET       | `InstallAddOn`, `InstallAddOnFromFile`, `InstallAddOnFromDirectory` | `driver.AsBiDiAsync()` → `BiDi.WebExtension.InstallAsync(...)` |
+| JavaScript | `installAddon` | none |
Evidence
The ADR table uses driver.AsBiDiAsync() followed by BiDi.WebExtension.InstallAsync(...), but the
actual .NET usage is instance-based (bidi.WebExtension.InstallAsync(...)) and WebExtension is an
instance property on IBiDi.

docs/decisions/17817-driver-extension-install.md[26-33]
dotnet/src/webdriver/BiDi/IBiDi.cs[34-53]
dotnet/test/webdriver/BiDi/WebExtension/WebExtensionTests.cs[38-45]

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 .NET example should demonstrate instance-based usage explicitly to avoid confusion/compile errors for readers who interpret `BiDi.WebExtension.InstallAsync(...)` as a static call.

## Issue Context
In the .NET binding, `AsBiDiAsync()` returns an `IBiDi` instance which exposes a `WebExtension` module property.

## Fix Focus Areas
- docs/decisions/17817-driver-extension-install.md[30-32]

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


Grey Divider

Previous review results

Review updated until commit cfdf561

Results up to commit 551bc46 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Remediation recommended
1. JS BiDi support overstated ✓ Resolved 🐞 Bug ≡ Correctness
Description
The ADR states that bindings already expose a BiDi module for extension install/uninstall, but the
same table indicates the JavaScript binding has no advertised BiDi approach (“none”), which is
contradictory and can mislead readers about cross-binding availability.
Code

docs/decisions/17817-driver-extension-install.md[R22-33]

+WebDriver BiDi specifies extension install and uninstall, which both Firefox and Chromium
+implement. Bindings already expose the BiDi module for it, and pointing users at that module is
+what we advertise today.
+
+| Binding    | Firefox-only method (classic) | Currently advertised BiDi approach |
+|------------|-------------------------------|------------------------------------|
+| Java       | `installExtension` (on `FirefoxDriver`) | `new WebExtension(driver).install(...)` |
+| Python     | `install_addon` | `driver.webextension.install(...)` |
+| Ruby       | `install_addon` (`HasAddons`) | `BiDi::Protocol::WebExtension` (protocol module) |
+| .NET       | `InstallAddOn`, `InstallAddOnFromFile`, `InstallAddOnFromDirectory` | `driver.AsBiDiAsync()` → `BiDi.WebExtension.InstallAsync(...)` |
+| JavaScript | `installAddon` | none |
+
Evidence
The Context section says bindings already expose a BiDi module for extension install/uninstall, but
the JavaScript row in the table immediately below states the JavaScript binding has no advertised
BiDi approach ("none"), creating an internal contradiction in the ADR.

docs/decisions/17817-driver-extension-install.md[22-33]

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 claims that "Bindings already expose the BiDi module" for extension install/uninstall, but then the JavaScript row in the table says there is no advertised BiDi approach ("none"). This contradiction should be resolved by either qualifying the earlier statement (e.g., "Most bindings..." or "All bindings except JavaScript...") or updating the table/statement to match the intended reality.

## Issue Context
This ADR is used as a decision record; contradictory statements in the Context section can lead to incorrect assumptions by readers and implementers.

## Fix Focus Areas
- docs/decisions/17817-driver-extension-install.md[22-33]

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



Informational
2. Ambiguous .NET BiDi example ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The ADR’s .NET “Currently advertised BiDi approach” example is ambiguous because it doesn’t show
that WebExtension is accessed from the *instance* returned by driver.AsBiDiAsync(), which can be
misread as BiDi being a static type/member access.
Code

docs/decisions/17817-driver-extension-install.md[R30-32]

+| Ruby       | `install_addon` (`HasAddons`) | `BiDi::Protocol::WebExtension` (protocol module) |
+| .NET       | `InstallAddOn`, `InstallAddOnFromFile`, `InstallAddOnFromDirectory` | `driver.AsBiDiAsync()` → `BiDi.WebExtension.InstallAsync(...)` |
+| JavaScript | `installAddon` | none |
Evidence
The ADR table uses driver.AsBiDiAsync() followed by BiDi.WebExtension.InstallAsync(...), but the
actual .NET usage is instance-based (bidi.WebExtension.InstallAsync(...)) and WebExtension is an
instance property on IBiDi.

docs/decisions/17817-driver-extension-install.md[26-33]
dotnet/src/webdriver/BiDi/IBiDi.cs[34-53]
dotnet/test/webdriver/BiDi/WebExtension/WebExtensionTests.cs[38-45]

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 .NET example should demonstrate instance-based usage explicitly to avoid confusion/compile errors for readers who interpret `BiDi.WebExtension.InstallAsync(...)` as a static call.

## Issue Context
In the .NET binding, `AsBiDiAsync()` returns an `IBiDi` instance which exposes a `WebExtension` module property.

## Fix Focus Areas
- docs/decisions/17817-driver-extension-install.md[30-32]

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


Qodo Logo

Comment thread docs/decisions/17817-driver-extension-install.md Outdated
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Propose ADR: install/uninstall extensions via driver API (BiDi pipe on Chromium)

📝 Documentation 🕐 10-20 Minutes

Grey Divider

AI Description

• Add ADR proposing cross-browser driver-level extension install/uninstall APIs.
• Document Chromium’s BiDi pipe transport requirement and BiDi-vs-CDP session tradeoff.
• Record rationale, alternatives, and consequences for bindings and Grid behavior.
Diagram

graph TD
  A["User test code"] --> B["Driver API"] --> C{"BiDi enabled?"}
  C -->|"Yes"| D["Pipe transport"] --> E["BiDi WebExtension commands"] --> F["Browser"]
  C -->|"No"| G["TCP debug port"] --> H["CDP available"] --> F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Split into separate ADRs (API vs transport vs CDP/BiDi model)
  • ➕ Smaller, more focused decisions with clearer ownership
  • ➕ Lets reviewers accept the driver API without committing to transport defaults
  • ➖ Readers lose the end-to-end rationale needed to understand feasibility
  • ➖ Creates interdependent ADRs that must be read together anyway
2. Driver-level `extensions` namespace instead of flat methods
  • ➕ Extensible surface for future operations (list/enable/disable, etc.)
  • ➕ Consistent with other higher-level namespaces (e.g., network/script)
  • ➖ Extra indirection for a two-verb API
  • ➖ May diverge from existing Firefox mental model and existing method names
3. Keep extension install as a BiDi module feature (status quo)
  • ➕ No new public API surface to maintain across bindings
  • ➕ Direct mapping to the underlying protocol feature
  • ➖ Binding access patterns are inconsistent (and missing in JS)
  • ➖ Public API mirrors an internal implementation detail (per ADR 17670)

Recommendation: Keep the single ADR as proposed: the driver-level method, Chromium pipe default when BiDi is enabled, and the BiDi-vs-CDP exclusivity are tightly coupled and need to be justified together for the decision to be actionable. The document already captures the main alternatives (BiDi module vs driver API, namespacing) and makes the key consequence (no CDP in BiDi sessions) explicit; the main follow-up is ensuring release notes and tracking issues cover the CDP tradeoff and binding rollout.

Files changed (1) +110 / -0

Documentation (1) +110 / -0
17817-driver-extension-install.mdAdd proposed ADR for driver-level extension install/uninstall +110/-0

Add proposed ADR for driver-level extension install/uninstall

• Introduces ADR 17817 describing a cross-browser driver API for installing/uninstalling extensions mid-session. Documents Chromium’s requirement to use BiDi over the pipe transport for extension install and the consequence that sessions are BiDi-only or CDP-only (not both), along with considered alternatives and expected impacts (including Grid).

docs/decisions/17817-driver-extension-install.md

Comment thread docs/decisions/17817-driver-extension-install.md
Comment thread docs/decisions/17817-driver-extension-install.md
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 551bc46

@qodo-code-review

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a proposed Architecture Decision Record (ADR) documenting a cross-binding design decision to support mid-session browser extension installation directly from the driver, using BiDi as the implementation mechanism and adjusting Chromium transport/CDP behavior to make this work without extra user flags.

Changes:

  • Adds ADR 17817 describing a new driver-level installExtension/uninstallExtension surface across bindings.
  • Documents the rationale for using BiDi + pipe transport in Chromium and the resulting CDP API limitations.
  • Captures alternatives considered (API placement, naming, transport default, unsigned extension posture) and their tradeoffs.

Comment thread docs/decisions/17817-driver-extension-install.md Outdated
Comment thread docs/decisions/17817-driver-extension-install.md Outdated
@titusfortner titusfortner changed the title [docs] Propose ADR: the driver installs extensions directly [adr] Install browser extensions from the driver directly Jul 24, 2026
Comment thread docs/decisions/17817-driver-extension-install.md
@qodo-code-review

Copy link
Copy Markdown
Contributor

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

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