
Selenium WebDriver 跨浏览器扩展安装17817 决策与 installWebExtension / uninstallWebExtension 实践指南【免费下载链接】seleniumA browser automation framework and ecosystem.项目地址: https://gitcode.com/GitHub_Trending/se/selenium本指南围绕 Selenium 项目的一项关键架构决策展开让installWebExtension与uninstallWebExtension直接挂载在 driver 实例上统一五个语言绑定Java、Python、Ruby、.NET、JavaScript的 Web Extension 安装体验。文章先梳理该决策的技术背景与内容再结合仓库源码逐语言给出可运行的调用示例并深入讲解返回对象、向后兼容、错误处理与 Grid 分布式环境下的实现约束帮助读者掌握标准、跨浏览器的扩展安装方案。背景为什么需要一套统一的扩展安装入口在 WebDriver 生态中安装一个浏览器扩展Web Extension这件事长期没有统一标准Firefox可以通过 WebDriver-Classic 的专有接口在会话进行中mid-session安装扩展但每个语言绑定都把这一能力挂接在浏览器特有类型上例如 Java 的FirefoxDriver、Ruby 的HasAddons而不是挂在 driver 本身上接口形态五花八门。Chromium传统上通过会话创建时的 capabilities 接收扩展。但品牌化的 Chrome 从 137 版本起不再遵循这条路径Chrome for Testing 与无品牌 Chromium 仍然支持因此在会话开始之后安装扩展变成了硬性需求。WebDriver BiDi规范中明确定义了扩展的安装与卸载命令Firefox 与 Chromium 均已实现。多数绑定已经对外暴露了对应的 BiDi 模块Selenium 目前的对外宣传也指向该模块。下表是各绑定在决策之前的现状经典方法均为 Firefox 专有BindingFirefox-only 方法classic此前宣传的 BiDi 方案JavainstallExtension挂在FirefoxDriver上new WebExtension(driver).install(...)Pythoninstall_addondriver.webextension.install(...)Rubyinstall_addonHasAddonsBiDi::Protocol::WebExtension协议模块.NETInstallAddOn、InstallAddOnFromFile、InstallAddOnFromDirectory(await driver.AsBiDiAsync()).WebExtension.InstallAsync(...)JavaScriptinstallAddon无问题的核心是能力存在但入口分散、命名不一致、且仅在 Firefox 上可用。这正是决策文档 docs/decisions/17817-driver-extension-install.md 要解决的痛点。决策内容方法挂在 driver 实例上本决策状态Accepted跟踪于 issue #17933给出了三条核心结论在 driver 实例上新增两个方法。每个语言绑定都在 driver 实例本身暴露installWebExtension与uninstallWebExtension而不是挂在浏览器特有类型或 BiDi 模块上。安装行为接受归档包archive、目录directory或 base64 编码内容同时支持厂商特定选项Firefox 上有permanent与allowPrivateBrowsing。实现必须能在 Grid 环境下工作。方法返回一个包装了扩展 id 的WebExtension对象。卸载行为接受WebExtension对象而非裸的 id 字符串。向后兼容。在 Firefox 上当 BiDi 未启用时这两个方法回退到 WebDriver-Classic 端点因此不会丢失任何既有能力原有的经典安装方法与参数被标记为废弃deprecated。目标无法满足请求时抛错。当浏览器或传输通道无法完成请求时应抛错而非静默降级——例如在未启用 BiDi 的 Chromium 上调用installWebExtension。备选方案的取舍为什么最终这样设计决策文档完整记录了被评估但未采纳的替代方案理解这些取舍有助于把握 API 设计意图。方法挂载位置的取舍直接复用现有方法而非新增项目总体方向是给既有方法赋予新行为而非扩大 API 表面。但该方案在此处无法成立现有方法都是 Firefox 专有且命名不一致Chromium 根本没有可复用的方法因此不存在一个统一的方法来承载跨浏览器能力。只有新增一个命名统一的方法才能实现跨浏览器。专门的webExtensions命名空间与network/script模块风格一致且扩展空间大但只有两个方法显得过度设计何况 Firefox 的先例就是直接把方法放在 driver 上。条件可用性的取舍只在可用的地方暴露方法例如在未启用 BiDi 的 Chromium 会话上隐藏该方法而非抛错。未被采纳Java 无法条件性地实现接口而只在个别绑定如 Ruby中这样做又会造成各绑定行为不一致不如统一暴露、调用时抛出清晰错误来得简单。命名与返回类型的取舍installExtension返回Extension更短且复用了 Java 已有的名字但 extension 一词含义过载且与 Java 经典方法installExtension(Path)在返回类型上冲突被迫做参数区分绕行。installWebExtension采用标准的 web extension 名词且让五个绑定的废弃策略保持一致公开的WebExtension类型放在独立包中与内部 BiDi 的WebExtension模块分离。裸 id 字符串作为返回无类型签名会接受任意字符串被否。自操作对象webExtension.uninstall()目前超出范围留待未来。经典 installAddon 方法的去向BiDi 启用时将installAddon重定向到installWebExtension让旧名字继续作为别名可用但这样两个名字会长期并存而非收敛。完全分离installAddon永远是 classic 实现、installWebExtension永远是 BiDi 实现。这与项目对其他过渡的管理方式不一致项目希望收敛到单一统一方法。未签名扩展要求显式 opt-in浏览器对未签名扩展的限制保护的是用户真实浏览的 profile而非自动化启动的会话因此该限制只增加步骤、不带来保护未采纳。跨语言落地从源码看实现形态决策最终落地到各语言绑定的实际代码中。以下是仓库中可验证的实现证据与典型用法。PythonBiDi 模块与 Firefox 经典方法并存Python 绑定在远程 driver 上通过webextension属性暴露 BiDi 模块见 remote/webdriver.py 中def webextension(self)内部惰性构建WebExtension(self._websocket_connection)extension_result driver.webextension.install(pathextension_path) driver.webextension.uninstall(extension_result)同时Firefox 经典路径保留install_addon/uninstall_addon见 firefox/webdriver.pydef install_addon(self, path, temporaryFalse) - str: Install an addon. Args: path: Absolute path to the addon that will be installed. temporary: Allows you to load browser extensions temporarily during a session. addon base64.b64encode(open(path, rb).read()).decode(utf-8) payload {addon: addon, temporary: temporary} return self.execute(INSTALL_ADDON, payload)[value] def uninstall_addon(self, identifier) - None: payload {id: identifier} self.execute(UNINSTALL_ADDON, payload)Java独立包中的 WebExtension 类型Java 的公开WebExtension类型位于独立包org.openqa.selenium.bidi.webextension见 bidi/webextension/WebExtension.java与内部 BiDi 模块隔离。其构造函数要求 driver 支持 BiDi否则抛出IllegalArgumentExceptionBeta public class WebExtension { private final BiDi bidi; public WebExtension(WebDriver driver) { Require.nonNull(WebDriver, driver); if (!(driver instanceof HasBiDi)) { throw new IllegalArgumentException(WebDriver instance must support BiDi protocol); } this.bidi ((HasBiDi) driver).getBiDi(); } public MapString, Object install(InstallExtensionParameters parameters) { Require.nonNull(Install parameters, parameters); return bidi.send( new Command(webExtension.install, parameters.getExtensionData().toMap(), Map.class)); } public MapString, Object uninstall(UninstallExtensionParameters parameters) { Require.nonNull(Uninstall parameters, parameters); return bidi.send(new Command(webExtension.uninstall, parameters.extension, Map.class)); } }典型用法new WebExtension(driver).install(...);Ruby协议模块中的三种数据形态与 Firefox 厂商参数Ruby 的BiDi::Protocol::WebExtension见 bidi/protocol/web_extension.rb完整实现了 BiDi 规范中的 CDDL 类型ExtensionData是一个带type判别器的联合类型三种变体对应三种安装数据形态archive_path→ 线上键archivePath归档包路径base64→ 线上键base64base64 编码内容path→ 线上键path目录路径install(extension_data:)执行webExtension.installuninstall(extension:)执行webExtension.uninstall。厂商子类Moz在install上额外支持allow_private_browsing与permanent参数映射为moz:allowPrivateBrowsing与moz:permanent扩展字段未设置的参数会被剔除def install(extension_data:, allow_private_browsing: Serialization::UNSET, permanent: Serialization::UNSET) extensions { moz:allowPrivateBrowsing allow_private_browsing, moz:permanent permanent }.reject { |_, value| Serialization::UNSET.equal?(value) } params InstallParameters.new(extension_data: extension_data, extensions: extensions) execute(cmd: webExtension.install, params: params, result: WebExtension::InstallResult) end该文件为生成产物由 bidi/support/bidi_generate.rb 通过bazel run //rb/lib/selenium/webdriver:bidi-generate重新生成。WebDriver BiDi 协议侧的完整结构从上述实现可以反推出协议层的数据流对应 WebDriver BiDi 规范的webExtension模块输入统一封装为带type判别器的三种形态archivePath、base64、path其中archivePath与path携带path字段、base64携带value字段安装命令webExtension.install接收extensionData可扩展允许厂商追加参数如moz:permanent安装结果webExtension.install返回InstallResult内含extension即扩展 id 字符串卸载命令webExtension.uninstall接收extensionid 字符串无返回对象。关键语义返回 WebExtension 对象、回退与抛错三个设计要点直接影响使用方式返回WebExtension对象而非裸 id安装后拿到的是包装了扩展 id 的对象卸载时直接把这个对象传回去避免无类型字符串带来的误用风险。Firefox 上的双向回退BiDi 未启用时installWebExtension/uninstallWebExtension回退到 WebDriver-Classic 端点既有的经典方法如 Python 的install_addon、.NET 的InstallAddOn*与其参数被废弃。从实现看Python 的经典端点INSTALL_ADDON与 BiDi 的webExtension.install是两条独立通道回退正是决策要求保留下来的能力。失败即抛错当目标无法满足请求时必须抛错而非静默降级。最典型的例子是未启用 BiDi 的 Chromium 上调用installWebExtension——Chromium 从 Chrome 137 起不再在会话创建时接受 capabilities 路径若传输通道也不支持 BiDi正确行为是直接抛出错误让调用者明确知道该会话无法安装扩展。分布式环境约束为什么不能传本地路径决策文档的 Consequences 部分揭示了实现上的硬约束remote end 可能与 client 运行在不同的主机上例如 Grid node因此实现不能传递 client 本地的文件系统路径。必须先把扩展传送到 remote end再以 remote end 能够解析的形式引用它内联内容inline content例如将扩展打包后以 base64 形式随命令发送——这正是 Pythoninstall_addon的做法base64.b64encode(...)后放入 payloadBiDi 的base64数据形态同理。先上传再引用先将扩展上传到 remote end 获得一个位置再用该位置引用。这一约束解释了为什么决策要求实现必须能在 Grid 环境下工作本地路径在本地运行时可行但在 Grid 这样的分布式拓扑下必然失效。迁移路径与使用建议新代码统一使用installWebExtension/uninstallWebExtension无论目标是 Firefox 还是 Chromium语义一致、跨浏览器可用。Firefox 存量代码经典方法install_addon、InstallAddOn等仍可用但已被废弃应逐步迁移迁移期间 BiDi 未启用时会自动回退不影响既有会话。Chromium 场景从 Chrome 137 起品牌化 Chrome 不再接受会话创建时的 capabilities 扩展路径会话中途安装是必需能力若当前会话没有启用 BiDi调用会直接抛错请先确保会话启用了 BiDi。分布式/Grid 场景不要传递 client 本地路径优先使用 base64 内联或先上传再引用。Firefox 厂商选项需要permanent持久安装或allowPrivateBrowsing允许隐私窗口时使用厂商特定的参数形态Ruby 中为Moz子类Java/.NET/Python 亦有对应入口。相关资源决策文档docs/decisions/17817-driver-extension-install.md配套决策docs/decisions/17670-bidi-implementation-boundaries.mdPython BiDi 模块挂载点py/selenium/webdriver/remote/webdriver.pyPython Firefox 经典端点py/selenium/webdriver/firefox/webdriver.pyJava 公开类型java/src/org/openqa/selenium/bidi/webextension/WebExtension.javaRuby 协议实现rb/lib/selenium/webdriver/bidi/protocol/web_extension.rb【免费下载链接】seleniumA browser automation framework and ecosystem.项目地址: https://gitcode.com/GitHub_Trending/se/selenium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考