Skip to content

[rb] support custom vendor specific capabilities in options classes - #17862

Merged
titusfortner merged 2 commits into
SeleniumHQ:trunkfrom
titusfortner:rb-vendor-options
Aug 3, 2026
Merged

[rb] support custom vendor specific capabilities in options classes#17862
titusfortner merged 2 commits into
SeleniumHQ:trunkfrom
titusfortner:rb-vendor-options

Conversation

@titusfortner

Copy link
Copy Markdown
Member

🔗 Related Issues

💥 What does this PR do?

  • Adds #add_chromium_option and #add_firefox_option to set vendor capabilities.
  • Fixes a bug where a hand-built vendor options hash passed to add_option was silently dropped during serialization.

🔧 Implementation Notes

  • This matches the .NET naming convention and not the Java/Python "experimental option" convention, which is a less obvious name and limited to Chromium in those bindings.
  • Method applies to Chrome and Edge, writing to the applicable custom capability (goog:chromeOptions / ms:edgeOptions).
  • On a key conflict the user-supplied value wins over the binding's.

🤖 AI assistance

  • No substantial AI assistance used
  • AI assisted (complete below)
    • Tool(s): Claude Code
    • What was generated: the two new methods, the serialization merge fix, and the unit tests
    • I reviewed all AI output and can explain the change

💡 Additional Considerations

  • Neither Java nor Python support custom Firefox specific capabilities currently

🔄 Types of changes

  • Bug fix (backwards compatible)
  • New feature (non-breaking change which adds functionality and tests!)

@selenium-ci selenium-ci added the C-rb Ruby Bindings label Aug 3, 2026
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Ruby: add vendor option escape hatches and preserve vendor option merging

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add add_chromium_option/add_firefox_option to set unmodeled vendor capabilities.
• Fix Options#as_json to merge vendor option hashes instead of dropping user values.
• Add unit tests and update RBS signatures for the new APIs.
Diagram

graph TD
  U["Client code"] --> C["Chromium Options"] --> AJ["Options#as_json"] --> MB["merge_browser_options"] --> CAP["Capabilities JSON"] --> D["WebDriver server"]
  U["Client code"] --> F["Firefox Options"] --> AJ["Options#as_json"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Single generic `add_vendor_option(namespace, name, value)` API
  • ➕ One escape hatch works for any vendor key, not just Chromium/Firefox
  • ➕ Avoids proliferating per-browser methods as new browsers are added
  • ➖ Less discoverable than browser-specific methods
  • ➖ Easier for users to pass an incorrect namespace/key for the current browser
2. Use a deep-merge strategy for nested hashes
  • ➕ More robust if future vendor capabilities contain nested structures
  • ➕ Reduces the chance of losing nested keys when both sides set the same top-level key
  • ➖ Higher risk of surprising merge semantics (arrays and nested conflicts)
  • ➖ More complexity for a fix that currently only needs one-level hash merging

Recommendation: Keep the PR’s approach: explicit per-browser escape hatches are discoverable and align with existing binding conventions, and the targeted one-level merge in Options#as_json fixes the silent-drop bug while minimizing behavioral surprises. Avoid deep-merge until a concrete nested-capability use case requires it.

Files changed (8) +105 / -1

Enhancement (2) +39 / -0
options.rbAdd Chromium vendor capability escape hatch and merge into options payload +20/-0

Add Chromium vendor capability escape hatch and merge into options payload

• Initializes '@vendor_options' and adds '#add_chromium_option' for setting unmodeled Chromium/Edge vendor capabilities. During serialization, merges these vendor options into the browser options object so they are sent to the driver.

rb/lib/selenium/webdriver/chromium/options.rb

options.rbAdd Firefox vendor capability escape hatch and merge into moz:firefoxOptions +19/-0

Add Firefox vendor capability escape hatch and merge into moz:firefoxOptions

• Initializes '@vendor_options' and adds '#add_firefox_option' for injecting unmodeled capabilities under 'moz:firefoxOptions'. Merges these vendor options into the serialized options payload in 'process_browser_options'.

rb/lib/selenium/webdriver/firefox/options.rb

Bug fix (1) +13 / -1
options.rbFix options serialization to merge vendor option hashes instead of overwriting +13/-1

Fix options serialization to merge vendor option hashes instead of overwriting

• Changes '#as_json' to use a new 'merge_browser_options' helper. When both the W3C/vendor options and the binding-built browser options contain hashes for the same top-level key (e.g., 'goog:chromeOptions'), the hashes are merged so user-provided keys are preserved and can override binding defaults.

rb/lib/selenium/webdriver/common/options.rb

Tests (2) +43 / -0
options_spec.rbAdd tests for add_chromium_option and vendor-hash merge behavior +26/-0

Add tests for add_chromium_option and vendor-hash merge behavior

• Adds coverage ensuring 'add_chromium_option' nests capabilities under 'goog:chromeOptions' and coexists with dedicated setters. Adds a regression test verifying that a hand-built 'goog:chromeOptions' hash passed via 'add_option' is merged rather than overwritten.

rb/spec/unit/selenium/webdriver/chrome/options_spec.rb

options_spec.rbAdd tests for add_firefox_option nesting and merging with existing options +17/-0

Add tests for add_firefox_option nesting and merging with existing options

• Adds coverage ensuring 'add_firefox_option' nests capabilities under 'moz:firefoxOptions' and merges cleanly with options set via dedicated methods (e.g., 'add_argument').

rb/spec/unit/selenium/webdriver/firefox/options_spec.rb

Other (3) +10 / -0
options.rbsAdd RBS typing for Chromium vendor options and new method +4/-0

Add RBS typing for Chromium vendor options and new method

• Adds '@vendor_options' ivar typing and declares the 'add_chromium_option' method in the Chromium options RBS signature.

rb/sig/lib/selenium/webdriver/chromium/options.rbs

options.rbsAdd RBS signature for merge_browser_options helper +2/-0

Add RBS signature for merge_browser_options helper

• Declares the new 'merge_browser_options' private helper method in the shared Options RBS signature.

rb/sig/lib/selenium/webdriver/common/options.rbs

options.rbsAdd RBS typing for Firefox vendor options and new method +4/-0

Add RBS typing for Firefox vendor options and new method

• Adds '@vendor_options' ivar typing and declares the 'add_firefox_option' method in the Firefox options RBS signature.

rb/sig/lib/selenium/webdriver/firefox/options.rbs

@qodo-code-review

qodo-code-review Bot commented Aug 3, 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. Chromium vendor key mismatch ✓ Resolved 🐞 Bug ≡ Correctness
Description
Chromium::Options#add_chromium_option and Firefox::Options#add_firefox_option accept `String |
Symbol` vendor option names but store keys verbatim, while downstream option processing and
camelization/special-casing logic assumes string keys like 'binary', 'args', 'prefs', and
'localState'. This mismatch means symbol/snake_case keys (e.g., :binary, :prefs,
:local_state) can be ignored/overridden by string-keyed defaults and can cause nested
prefs/localState content to be camelized unexpectedly.
Code

rb/lib/selenium/webdriver/chromium/options.rb[R202-204]

+        def add_chromium_option(name, value)
+          @vendor_options[name] = value
+        end
Evidence
In both implementations, the vendor “escape hatch” stores the provided key object as-is in
@vendor_options, but later process_browser_options logic merges these options and then applies
defaults via string-key access (e.g., options['binary'] ||= ..., and Chromium similarly for
'args'), so symbol keys won’t be seen as the same entries and can lose precedence. Additionally,
during JSON serialization, nested-hash camelization is decided by calling camelize?(key) before
converting Symbol keys to Strings, and the exemptions/special-casing for keys like 'prefs'
(Firefox) and 'prefs'/'localState' (Chromium) are string comparisons; consequently,
:prefs/:local_state can bypass the intended special-case behavior and trigger unexpected
camelization of nested preference/state hashes.

rb/lib/selenium/webdriver/chromium/options.rb[188-204]
rb/lib/selenium/webdriver/chromium/options.rb[227-237]
rb/lib/selenium/webdriver/chromium/options.rb[259-261]
rb/lib/selenium/webdriver/common/options.rb[200-213]
rb/lib/selenium/webdriver/firefox/options.rb[106-121]
rb/lib/selenium/webdriver/firefox/options.rb[171-176]
rb/lib/selenium/webdriver/firefox/options.rb[190-192]

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

## Issue description
`add_chromium_option` and `add_firefox_option` accept `String | Symbol` but store vendor option keys without normalization. Downstream, `process_browser_options` and JSON serialization rely on string keys and on `camelize?` string comparisons for special-case keys (e.g., `'prefs'`, `'localState'`), so symbol/snake_case vendor keys can be overwritten by defaults and/or serialized with unintended nested camelization.

## Issue Context
- For both browsers, `process_browser_options` merges `@vendor_options` and then sets defaults using string-key access (e.g., `'binary'`, and Chromium also `'args'`).
- `process_json_hash` determines whether to camelize nested hashes based on `camelize?(key)` **before** converting Symbol keys to Strings, so Symbol keys can bypass string-based special-casing such as `'prefs'` and `'localState'`.
- This can cause `:binary`/`:prefs`/`:local_state` to behave differently from `'binary'`/`'prefs'`/`'localState'`, affecting precedence and nested preference/state serialization.

## Fix Focus Areas
- rb/lib/selenium/webdriver/chromium/options.rb[202-204]
- rb/lib/selenium/webdriver/chromium/options.rb[227-237]
- rb/lib/selenium/webdriver/firefox/options.rb[119-121]
- rb/lib/selenium/webdriver/firefox/options.rb[171-176]
- rb/lib/selenium/webdriver/firefox/options.rb[190-192]
- rb/lib/selenium/webdriver/common/options.rb[200-213]

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


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread rb/lib/selenium/webdriver/chromium/options.rb
@qodo-code-review

Copy link
Copy Markdown
Contributor

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

@titusfortner titusfortner changed the title [rb] add add_chromium_option/add_firefox_option escape hatches and merge hand-built vendor options [rb] support custom vendor specific capabilities in options classes Aug 3, 2026
@titusfortner
titusfortner merged commit 0ad0c51 into SeleniumHQ:trunk Aug 3, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-rb Ruby Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants