This guide helps contributors write tests, maintain code style, and generate documentation for the Selenium Ruby bindings.
common/src/web/.driver, wait, short_wait, long_wait, url_for.module Selenium module WebDriver describe Element do it 'returns element text' do driver.get(url_for('simpleTest.html')) expect(driver.find_element(id: 'foo').text).to eq('expected') end it 'clicks element', pending_if: {browser: :safari, reason: 'Safari bug'} do # Pending on Safari end end end end
Bazel generates a target per spec file for each supported browser and variant.
bazel test //rb/spec/... # All tests bazel test //rb/spec/unit:unit # All unit specs (single target) bazel test //rb/spec/integration/... --test_tag_filters=chrome # Every Chrome variant bazel test //rb/spec/integration/... --test_tag_filters=firefox-local # Local Firefox only bazel test //rb/spec/integration/... --test_tag_filters=firefox-remote # Remote (Grid) Firefox only bazel test //rb/spec/integration/... --test_tag_filters=bidi # BiDi-mode across supporting browsers # Viewing Output bazel test //rb/... --test_output=all # See console output at the end bazel test //rb/... --test_output=streamed # See output in real-time (no parallel execution)
A browser tag (e.g. firefox) matches every variant of that browser (local, remote, beta, bidi); add a -local, -remote, or -bidi suffix to narrow it. Integration targets are named after the spec file with _spec.rb removed plus a browser-variant suffix (e.g. driver-chrome, driver-chrome-remote, driver-chrome-beta-bidi), so you can run a single spec directly:
bazel test //rb/spec/integration/selenium/webdriver:driver-chrome
The rb/Rakefile provides shortcuts for common tasks:
rake update # Setup everything to run tests in RubyMine rake unit # Run unit tests rake spec # Run all integration tests in Chrome
Before running tests, navigate to the rb/ directory and install the required dependencies. A recent Bundler (2.4 or newer) is recommended.
cd rb gem install bundler -v '~> 2.4' bundle install
Note: Local development still requires running Bazel to generate the atoms and devtools code. You can generate these artifacts by running:
bundle exec rake update
Or from the parent selenium directory:
./go rb:update
To test against a specific Ruby version, set it in rb/.ruby-version (Bazel‘s hermetic Ruby toolchain reads this file). It is checked in and bumped repo-wide, so treat a local edit as a temporary override and don’t commit it:
echo '<X.Y.Z>' > rb/.ruby-version
If you want to use RubyMine for development, you can configure it to use Bazel artifacts:
rb/ as a main project directory.bundle exec rake update as necessary to create up-to-date artifacts. If this does not work, run ./go rb:update from the selenium (parent) directory.../bazel-selenium/external/rules_ruby_dist/dist/bin/ruby.Guards are RSpec metadata on describe, context, or it blocks (all enclosing guards combine). Use one of the five keywords, and always include a reason: (a String, or an issue number):
| Guard | Effect |
|---|---|
skip_if | Skip when the config matches. |
skip_unless | Skip unless the config matches (run only when it matches). |
pending_if | Expect failure (pending) when the config matches. |
pending_unless | Expect failure (pending) unless the config matches. |
flaky | Like skip_if, reserved for unreliable/intermittent tests. |
except, only, exclude, and exclusive are older aliases of pending_if, pending_unless, skip_if, and skip_unless respectively.
Matching: within one Hash every pair must match (AND); an Array value for a key matches any listed value (OR). For an Array of Hashes, skip_if/pending_if trigger if any Hash matches, while skip_unless/pending_unless apply unless every Hash matches.
Conditions are registered in spec/integration/selenium/webdriver/spec_helper.rb.
| Condition | Values |
|---|---|
browser | :chrome, :firefox, :edge, :safari, :safari_preview, :ie |
browser_family | :chromium (Chrome/Edge), :safari (Safari/Safari Preview), otherwise the browser value (e.g. :firefox) |
driver | :remote |
platform | :linux, :macosx, :windows |
headless | true, false |
bidi | true, false |
version | Browser version string, e.g. 'stable' (from WD_BROWSER_VERSION) |
rbe | true, false (running on Remote Build Execution) |
ci | :github, :jenkins, :appveyor |
Prefer browser_family over listing every member browser when a guard applies to a whole engine (e.g. browser_family: :chromium instead of browser: %i[chrome edge]). Use the exact browser condition when a guard is specific to one channel, such as browser: :safari_preview or browser: :chrome, version: 'beta'.
# Pending on Safari it 'does something', pending_if: {browser: :safari, reason: 'Safari bug'} do end # Pending everywhere except Chrome and Firefox it 'does something', pending_unless: {browser: %i[chrome firefox], reason: 'Only implemented in Chrome/Firefox'} do end # Pending on any Chromium-based browser (Chrome and Edge) it 'does something', pending_if: {browser_family: :chromium, reason: 'Chromium bug'} do end # Skip on the stable Firefox channel it 'does something', skip_if: {browser: :firefox, version: 'stable', reason: 'https://bugzil.la/123'} do end # Skip when running remotely on RBE it 'does something', skip_if: {driver: :remote, rbe: true, reason: 'Cannot start 2+ drivers at once.'} do end # Known-flaky on GitHub Actions it 'does something', flaky: {browser: :safari, ci: :github, reason: 'unreliable with downloads'} do end # Array of Hashes — skip if Firefox OR macOS (reason may be an issue number) it 'something', skip_if: [ {browser: :firefox, reason: 1234}, {platform: :macosx, reason: 5678} ] do end
pending_if/except also accept exception: {class:, message:} (message: optional — a Regexp matches as a pattern, a String matches exactly, like raise_error). The example is marked pending only when it fails with that exception; a wrong exception, invalid argument, assertion failure, or timeout still fails.
it 'does something', pending_if: {browser: :firefox, exception: {class: Selenium::WebDriver::Error::WebDriverError, message: /\Aunknown command:/}, reason: 'Firefox does not implement this command'} do end
From spec_support/helpers.rb:
| Helper | Description |
|---|---|
driver | Current WebDriver instance. |
reset_driver!(...) | Reset driver with new options. |
url_for(filename) | Get test page URL (from common/src/web). |
wait / short_wait / long_wait | Wait instances (10s, 3s, 30s). |
wait_for_element(locator) | Wait for element to appear. |
wait_for_alert | Wait for alert presence. |
Every WebDriver.logger call should include an id: symbol (e.g. logger.warn(msg, id: :safari_bidi)). To assert on logging content (and hide it from test logs), do not stub the logger, instead use one of the custom matchers: have_error, have_warning, have_info, and have_deprecated.
expect { SeleniumManager.binary }.to have_info(:selenium_manager) # id was logged, at info level expect { save_screenshot(png_path) }.not_to have_warning(:screenshot) # id was not logged
The match is the exact set of ids at that severity — an unexpected entry fails rather than slipping by — so assert several entries by passing the full set, e.g. have_warning(%i[general specific]).
The id is provided so you don't have to assert on specific text, but if the message comes from an external source, you can assert on the contents as well:
expect { navigate }.to have_error(:ws, /This is fine!/)
Deprecations (logger.deprecate) are asserted with have_deprecated:
WebDriver.logger.deprecate('Old thing', 'New thing', id: :old_thing) # lib expect { call_old_thing }.to have_deprecated(:old_thing) # spec
Instead of using irb, you can create an interactive REPL with all gems loaded using:
bazel run //rb:console
debug gemUse the debug gem to set breakpoints inside specs or library code:
Add binding.break where you want the debugger to stop.
Run the test with the ruby_debug configuration:
bazel test --config ruby_debug <test>
In a separate terminal, attach to the running debugger:
bazel-selenium/external/bundle/bin/rdbg -A
BiDi-enabled targets are generated with a -bidi suffix for the browsers that support it (they set WEBDRIVER_BIDI=true internally). Run them directly or filter by the bidi tag:
# Run a spec's BiDi variant bazel test //rb/spec/integration/selenium/webdriver:driver-chrome-bidi # Run every BiDi variant across browsers that support it bazel test //rb/spec/integration/... --test_tag_filters=bidi
To enable BiDi on a target that is not a -bidi variant, pass the variable through --test_env:
bazel test //rb/spec/integration/... --test_tag_filters=chrome --test_env=WEBDRIVER_BIDI=true
Bazel sets the variables below automatically for each generated target. To override one (or set it on a target that does not), pass it with --test_env, e.g. bazel test //rb/spec/integration/... --test_tag_filters=chrome --test_env=HEADLESS=true.
| Variable | Purpose |
|---|---|
WEBDRIVER_BIDI | Enable the BiDi protocol (set by the -bidi targets). |
HEADLESS | Run Chrome, Edge, and Firefox in headless mode. |
WD_SPEC_DRIVER | Driver to test; a browser name or remote (set by Bazel). |
WD_REMOTE_BROWSER | When WD_SPEC_DRIVER is remote, the browser to test (set by Bazel). |
WD_REMOTE_URL | URL of an already-running server to use for remote tests. |
DOWNLOAD_SERVER | When WD_REMOTE_URL is unset, download and use the most recently released server for remote tests. |
DISABLE_BUILD_CHECK | For Chrome and Edge, ignore driver/browser version mismatches (allows testing Canary builds). |
CHROME_BINARY / CHROMEDRIVER_BINARY | Paths to a specific Chrome browser / ChromeDriver. |
EDGE_BINARY / MSEDGEDRIVER_BINARY | Paths to a specific Edge browser / msedgedriver. |
FIREFOX_BINARY / GECKODRIVER_BINARY | Paths to a specific Firefox browser / GeckoDriver. |
Selenium enforces strict code style using Rubocop. CI will fail if linting errors are present.
Configuration is defined in .rubocop.yml. Prefer updating the configuration file over using in-file rubocop guards (like # rubocop:disable) to maintain consistency across the codebase.
# Check code style bundle exec rubocop # Auto-correct simple offenses bundle exec rubocop -A
Selenium Ruby uses Steep for gradual type checking with RBS (Ruby Signature) files. When you create a new class or modify existing classes, add or update the corresponding .rbs file in sig/, mirroring the directory structure of lib/.
# Type check all files bundle exec steep check # Type check specific files bundle exec steep check lib/selenium/webdriver/my_class.rb
Tips: start with untyped for complex types and refine over time; prefer concrete types (String, Integer) where possible; use generics for collections (Array[String], Hash[Symbol, String]). CI runs Steep, so ensure signatures are correct before submitting a PR.
We use YARD for inline documentation. Ensure your changes are documented and generate valid HTML.
# Generate documentation bundle exec yard doc # Run a local documentation server (view at http://localhost:8808) bundle exec yard server --reload
rb/spec/ ├── unit/ # Unit tests (no browser) │ └── selenium/webdriver/ └── integration/ # Integration tests └── selenium/webdriver/ ├── chrome/ ├── firefox/ ├── safari/ ├── bidi/ └── spec_support/ # Test helpers
Test files must end in _spec.rb (e.g., driver_spec.rb).
rb_integration_test uses glob patterns.*_spec.rb files are in a directory with a BUILD.bazel containing rb_integration_test.