Self-hosted service
rubycdp/cuprite avatar
rubycdp/cuprite

Cuprite: A Pure Ruby Capybara Driver for Headless Chrome

Headless Chrome/Chromium driver for Capybara

1,398 stars103 forksRubyMIT

At a glance

What is it?
Cuprite connects Capybara tests directly to Chrome or Chromium through Ferrum and the Chrome DevTools Protocol, without Selenium, WebDriver, or ChromeDriver. It fits Ruby teams that want browser-level test control without maintaining a separate driver executable, although its Chrome dependency and direct protocol access make it unsuitable for every Capybara setup.
Who is it for?
Cuprite is a practical choice for Ruby teams already using Capybara with Chrome or Chromium and willing to manage browser availability and Ferrum configuration. It is a poor fit for suites that require browser-independent drivers, non-Chromium coverage, or transparent Selenium compatibility.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Cuprite's CDP Path from Capybara to Chrome

Cuprite addresses a specific integration problem: running Capybara's browser-facing test API against headless Chrome or Chromium without introducing Selenium, WebDriver, or ChromeDriver. It is intended for Ruby test suites that already use Capybara and want browser automation driven from Ruby code. The separation matters because Selenium tests normally communicate with a browser through a WebDriver layer, while Cuprite is described as a pure Ruby driver.

The actual browser connection is delegated to Ferrum. According to the README, Ferrum provides a high-level API over the Chrome DevTools Protocol, or CDP. Cuprite therefore acts as the Capybara-facing driver, while Ferrum handles the lower-level browser session and protocol operations. This architecture gives test code access to Capybara actions such as visiting a page and filling in a field, plus Cuprite-specific operations for headers, cookies, network traffic, screenshots, and debugging.

That design is not browser-neutral. The documented target is Chrome or Chromium, and the README directs users to Ferrum for the full option list. A suite that needs Firefox, WebKit, or another browser abstraction cannot treat Cuprite as a universal replacement for its existing driver stack.

Bundler Installation and a Capybara Driver Registration

The documented installation path is Bundler. Add Cuprite to the test group in the application's `Gemfile`, then run the standard Bundler install command. The README does not describe a separate ChromeDriver installation because Cuprite has no ChromeDriver dependency. Chrome or Chromium still needs to exist in the environment where the tests execute.

Add the dependency to the test group:

ruby
group :test do
  gem "cuprite"
end

Then run:

bash
bundle install

The test setup needs to require the Capybara integration, select `:cuprite` as the JavaScript driver, and register a driver. The README uses a window size of `1200` by `800` in this setup:

ruby
require "capybara/cuprite"
Capybara.javascript_driver = :cuprite
Capybara.register_driver(:cuprite) do |app|
  Capybara::Cuprite::Driver.new(app, window_size: [1200, 800])
end

This is the first real integration point. After this registration, a Capybara test can use the normal Capybara API against the registered browser driver. The README's debugging example shows a short test sequence using `visit`, `fill_in`, and an expectation for page content:

ruby
it "does something useful" do
  visit root_path

  fill_in "field", with: "value"
  page.driver.debug(binding)

  expect(page).to have_content("value")
end

In that example, `root_path` and the field label are application-specific. Cuprite does not create the application route or input field; it connects the test to the browser.

Container Configuration and the Sandbox Trade-off

Browser sandboxing is the first deployment decision the README calls out. In a container where Chrome or Chromium cannot use its sandbox, including a case where the browser runs as `root`, Cuprite documents two ways to adjust Ferrum's browser configuration. The preferred option is Ferrum's container mode:

ruby
Capybara::Cuprite::Driver.new(app, dockerize: true)

The README states that `dockerize` requires Ferrum `0.17.2` or newer. For an older Ferrum version, it documents passing the browser option directly:

ruby
Capybara::Cuprite::Driver.new(
  app,
  browser_options: { "no-sandbox" => nil }
)

Both approaches disable the browser sandbox. The README explicitly warns against enabling them merely because Docker is involved. It says to prefer running the browser as a non-root user with a working sandbox when possible. That is a meaningful security boundary for test infrastructure. A passing browser test does not remove the operational consequences of running Chrome without its sandbox, and the setting should be tied to the actual container limitation rather than added as a routine Docker convention.

The driver also accepts an options hash, with Cuprite-specific options including `:raise_on_unhandled_modal`, `:url_blacklist`, and `:url_whitelist`. Modal handling has a deferred failure model that can affect test timing, as explained below.

Direct Browser Control Beyond the Capybara Surface

Cuprite exposes the Ferrum browser object through `page.driver.browser`, which is useful when a Capybara interaction is not specific enough for the test. The README gives this example for direct mouse control:

ruby
browser = page.driver.browser
browser.mouse.move(x: 123, y: 456).down.up

It also documents coordinate-based clicking, scrolling, and element key input through the driver or element API. These operations expand the test surface, but they also make the test more dependent on rendered coordinates and browser-level behavior. A coordinate click is tied to the current viewport and layout, while Capybara's element-oriented actions are generally easier to relate to application semantics. The README provides both styles without prescribing when one should replace the other.

HTTP headers can be changed through the driver. Assignment replaces the existing header set, whereas `add_headers` adds headers without discarding what is already configured:

ruby
page.driver.headers # => {}
page.driver.headers = { "User-Agent" => "Cuprite" }
page.driver.add_headers("Referer" => "https://example.com")
page.driver.headers # => { "User-Agent" => "Cuprite", "Referer" => "https://example.com" }

These headers apply to subsequent HTTP requests, including asset and AJAX requests, and the README states that they are cleared at the end of the test. This is a test-driver behavior with broad request scope, not merely a header change for the initial document request.

Network Inspection, Reload Waiting, and Dialog Timing

Cuprite includes browser-driver facilities for tests that depend on asynchronous network activity. `page.driver.network_traffic` returns an array of `Ferrum::Network::Exchange` objects, with request and response details available on each exchange. The documented access pattern is straightforward:

ruby
network_traffic = page.driver.network_traffic
first_exchange = network_traffic.first
response = first_exchange.response

Traffic is not cleared automatically when a new page is visited. The README gives `page.driver.clear_network_traffic` and `page.driver.reset` as ways to clear it. That persistence can be useful when a test needs to inspect activity across navigation, but it also means assertions about traffic must account for earlier requests.

For synchronization, `page.driver.wait_for_network_idle` waits for network idle and returns immediately when there are no active connections, or raises `TimeoutError` according to the supplied wait options. The README also provides `page.driver.wait_for_reload`, which waits until the whole page is reloaded. It recommends neither `sleep` nor network idle for every reload case: network idle may return before the page has started reloading, while a fixed sleep can wait for the wrong amount of time. These are concrete timing trade-offs rather than general Selenium parity.

Unhandled JavaScript dialogs have a separate failure model. The dialog is always auto-accepted. With `:raise_on_unhandled_modal` set to `true`, Cuprite raises `Capybara::Cuprite::UnhandledModalError` on the next browser command rather than necessarily at the dialog's origin. A timer-created dialog can therefore appear to fail on a later command, which requires deliberate test design.

Cookies, Screenshots, Debugging, and Selenium Compatibility

The driver provides direct methods for common browser-state operations. `page.driver.cookies` returns cookie objects with fields such as `name`, `value`, `domain`, `path`, `secure?`, `httponly?`, `session?`, and `expires`. Tests can change that state with `set_cookie`, `remove_cookie`, and `clear_cookies`. The `set_cookie` options include `:domain`, `:path`, `:secure`, `:httponly`, and `:expires`, with `:expires` requiring a `Time` object. This is useful when a Capybara test needs to establish browser state, although the README does not claim that these methods reproduce every storage or cookie behavior of another driver.

Screenshots can be captured through Capybara's screenshot method or as Base64 with:

ruby
page.driver.render_base64(format, options)

Debugging is unusually direct. Calling `page.driver.debug` pauses execution and opens a browser tab for inspection. Passing `binding` also opens an `irb` or `pry` console, as shown by the `page.driver.debug(binding)` call in the test example. This can shorten diagnosis of a broken interaction because the test can stop with the page visible rather than trying to infer state from a failure message alone.

Migration cost depends on the existing suite. The README specifically tells Selenium users to check `manage` calls because they work differently in Cuprite. A Capybara test suite that relies on Selenium-specific driver management should not be assumed to run unchanged. Cuprite also exposes `basic_authorize` and `set_proxy`, with the proxy passed to Chrome as a command-line flag. Proxy configuration therefore belongs in browser startup options rather than behaving like a Selenium-style mutable session setting.

Cuprite Maintenance, License, and Adoption Boundary

The repository is not archived, and its last push was on 2026-10-15. The recent release history includes `v0.18` on 2026-09-03, `v0.17` on 2025-05-11, and `v0.16` on 2025-05-10. These facts show recent project activity, but they do not remove compatibility work from an adoption decision. Cuprite's public surface includes Capybara, Ferrum, Chrome or Chromium, and browser configuration, so upgrades can involve the Ruby gem set as well as the browser environment.

The repository lists the MIT license. That identifies the project's published license, but applications still need to review dependency licenses and their own distribution obligations separately. No legal conclusion can be drawn from the license label alone.

The main alternative is Selenium-backed Capybara. Selenium uses a WebDriver-mediated path and commonly involves a browser driver component; Cuprite uses Ferrum's Ruby API over CDP and removes the Selenium/WebDriver/ChromeDriver dependency. Selenium may be the better fit when a suite depends on established WebDriver-specific behavior or needs to move beyond Chrome and Chromium. Cuprite is the more specific choice when the team wants direct Ferrum access, Capybara integration, and Chrome-oriented browser control from Ruby. The decision comes down to browser scope and driver compatibility, not a general claim that one approach is better.

Editorial conclusion

Cuprite is a practical choice for Ruby teams already using Capybara with Chrome or Chromium and willing to manage browser availability and Ferrum configuration. It is a poor fit for suites that require browser-independent drivers, non-Chromium coverage, or transparent Selenium compatibility. Before adoption, verify that the existing test code does not depend on Selenium-specific `manage` behavior, that Chrome or Chromium is available in the test environment, and that any required `dockerize: true` or `no-sandbox` setting matches the container's actual security constraints.

Frequently asked questions

Does Cuprite require Selenium or ChromeDriver?

No. Cuprite is described as a pure Ruby Capybara driver without a Selenium, WebDriver, or ChromeDriver dependency. It uses Ferrum to communicate with Chrome or Chromium through the Chrome DevTools Protocol.

How do I configure Cuprite in a container where Chrome cannot use its sandbox?

The README documents `dockerize: true` for Ferrum `0.17.2` or newer. With an older Ferrum version, it documents `browser_options: { "no-sandbox" => nil }`. Both approaches disable the sandbox, so the README recommends a non-root browser user with a working sandbox when possible.

Can Cuprite inspect network requests and cookies from a Capybara test?

Yes. `page.driver.network_traffic` returns `Ferrum::Network::Exchange` objects, while `page.driver.cookies`, `set_cookie`, `remove_cookie`, and `clear_cookies` provide cookie inspection and manipulation. Network traffic is not cleared automatically when visiting a new page.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. rubycdp/cuprite on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rubycdp-cuprite.svg)](https://hysenlabs.com/projects/rubycdp-cuprite)

Was this useful?