Headless Chrome/Chromium driver for Capybara
1,395
stars
503
commits
Ruby
primary language
Sep 3, 2026
updated
Cuprite is a pure Ruby driver (read as no Selenium/WebDriver/ChromeDriver dependency) for Capybara. It allows you to run Capybara tests on a headless Chrome or Chromium. Under the hood it uses Ferrum which is high-level API to the browser by CDP protocol.
Add this to your Gemfile and run bundle install.
group :test do
gem "cuprite"
end
In your test setup add:
require "capybara/cuprite"
Capybara.javascript_driver = :cuprite
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app, window_size: [1200, 800])
end
If Chrome or Chromium cannot use its sandbox inside a container—for example,
because it runs as root—enable Ferrum's container mode:
Capybara::Cuprite::Driver.new(app, dockerize: true)
The dockerize option requires Ferrum 0.17.2 or newer. With an older Ferrum
version, pass the browser option explicitly:
Capybara::Cuprite::Driver.new(
app,
browser_options: { "no-sandbox" => nil }
)
Both approaches disable the browser's sandbox, so do not use them merely because Docker is involved. Prefer running the browser as a non-root user with a working sandbox when possible.
Since Cuprite uses Ferrum there are many useful methods you can call even using this driver:
browser = page.driver.browser
browser.mouse.move(x: 123, y: 456).down.up
For Selenium you better check your code for manage calls because it works
differently in Cuprite, see the documentation below.
See the full list of options for Ferrum.
You can pass options with the following code in your test setup:
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app, options)
end
Cuprite-specific options are:
Hash
:raise_on_unhandled_modal (Boolean) - When set to false, output a warning. When set to true, raise
Capybara::Cuprite::UnhandledModalError instead. The dialog is always auto-accepted either way; the raise is
deferred and surfaces on the next command sent to the browser. In practice that's almost always the same action
that triggered the dialog (a click, visit, evaluate_script, ...), since a JS dialog blocks the page until
answered, so that action's own command is what was waiting. A dialog fired with nothing in flight (e.g. a bare JS
timer) only surfaces on whatever command runs next.:url_blacklist (Array) - array of regexes to match against requested URLs:url_whitelist (Array) - array of regexes to match against requested URLsYou can put page.driver.debug or page.driver.debug(binding) in your test to pause it.
This will launch the browser where you can inspect the content.
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 the middle of the execution Chrome will open a new tab where you can inspect
the content and also if you passed binding an irb or pry console will be
opened where you can further experiment with the test.
page.driver.click(x, y) Click a very specific area of the screen.page.driver.scroll_to(left, top) Scroll to a given position.element.send_keys(*keys) Send keys to a given node.Manipulate HTTP request headers like a boss:
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" }
Notice that headers= will overwrite already set headers. You should use
add_headers if you want to add a few more. These headers will apply to all
subsequent HTTP requests (including requests for assets, AJAX, etc). They will
be automatically cleared at the end of the test.
page.driver.network_traffic allows you to inspect network traffic (i.e., loaded resources) on the current page. It returns an array of Ferrum::Network::Exchange objects, each representing a network request/response exchange. You can query both the request and response details of each exchange.# Retrieve all network exchanges
network_traffic = page.driver.network_traffic
# Access the first exchange
first_exchange = network_traffic.first
# Inspect the response of the first request
response = first_exchange.response
page.driver.wait_for_network_idle Natively waits for network idle and if
there are no active connections returns or raises TimeoutError error. Accepts
the same options as
wait_for_idlepage.driver.wait_for_network_idle
page.driver.refresh
Please note that network traffic is not cleared when you visit new page. You can
manually clear the network traffic by calling page.driver.clear_network_traffic
or page.driver.reset
page.driver.wait_for_reload unlike wait_for_network_idle will wait until
the whole page is reloaded or raise a timeout error. It's useful when you know
that for example after clicking autocomplete suggestion you expect page to be
reloaded, you have a few choices - put sleep or wait for network idle, but both
are bad. Sleep makes you wait longer or less than needed, network idle can
return earlier even before the whole page is started to reload. Here's the
rescue.The following methods are used to inspect and manipulate cookies:
page.driver.cookies - a hash of cookies accessible to the current
page. The keys are cookie names. The values are Cookie objects, with
the following methods: name, value, domain, path, size, secure?,
httponly?, session?, expires.page.driver.set_cookie(name, value, options = {}) - set a cookie.
The options hash can take the following keys: :domain, :path,
:secure, :httponly, :expires. :expires should be a
Time object.page.driver.remove_cookie(name) - remove a cookiepage.driver.clear_cookies - clear all cookiesBesides capybara screenshot method you can get image as Base64:
page.driver.render_base64(format, options)page.driver.basic_authorize(user, password)page.driver.set_proxy(ip, port, user, password)Cuprite supports URL blocklisting, which allows you to prevent scripts from running on designated domains:
page.driver.browser.url_blocklist = %r{http://www.example.com}
and also URL allowlisting, which allows scripts to only run on designated domains:
page.driver.browser.url_allowlist = %r{http://www.example.com}
For legacy support, url_blacklist= and url_whitelist= continue to work respectively.
If you are experiencing slower run times, consider creating a URL allowlist of domains that are essential or a blocklist of domains that are not essential, such as ad networks or analytics, to your testing environment.
The gem is available as open source under the terms of the MIT License.
(top 30 of 46)
Ruby
69.2%
HTML
20.8%
JavaScript
10.0%
Headless Chrome/Chromium driver for Capybara
1,395
stars
503
commits
Ruby
primary language
Sep 3, 2026
updated
Cuprite is a pure Ruby driver (read as no Selenium/WebDriver/ChromeDriver dependency) for Capybara. It allows you to run Capybara tests on a headless Chrome or Chromium. Under the hood it uses Ferrum which is high-level API to the browser by CDP protocol.
Add this to your Gemfile and run bundle install.
group :test do
gem "cuprite"
end
In your test setup add:
require "capybara/cuprite"
Capybara.javascript_driver = :cuprite
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app, window_size: [1200, 800])
end
If Chrome or Chromium cannot use its sandbox inside a container—for example,
because it runs as root—enable Ferrum's container mode:
Capybara::Cuprite::Driver.new(app, dockerize: true)
The dockerize option requires Ferrum 0.17.2 or newer. With an older Ferrum
version, pass the browser option explicitly:
Capybara::Cuprite::Driver.new(
app,
browser_options: { "no-sandbox" => nil }
)
Both approaches disable the browser's sandbox, so do not use them merely because Docker is involved. Prefer running the browser as a non-root user with a working sandbox when possible.
Since Cuprite uses Ferrum there are many useful methods you can call even using this driver:
browser = page.driver.browser
browser.mouse.move(x: 123, y: 456).down.up
For Selenium you better check your code for manage calls because it works
differently in Cuprite, see the documentation below.
See the full list of options for Ferrum.
You can pass options with the following code in your test setup:
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app, options)
end
Cuprite-specific options are:
Hash
:raise_on_unhandled_modal (Boolean) - When set to false, output a warning. When set to true, raise
Capybara::Cuprite::UnhandledModalError instead. The dialog is always auto-accepted either way; the raise is
deferred and surfaces on the next command sent to the browser. In practice that's almost always the same action
that triggered the dialog (a click, visit, evaluate_script, ...), since a JS dialog blocks the page until
answered, so that action's own command is what was waiting. A dialog fired with nothing in flight (e.g. a bare JS
timer) only surfaces on whatever command runs next.:url_blacklist (Array) - array of regexes to match against requested URLs:url_whitelist (Array) - array of regexes to match against requested URLsYou can put page.driver.debug or page.driver.debug(binding) in your test to pause it.
This will launch the browser where you can inspect the content.
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 the middle of the execution Chrome will open a new tab where you can inspect
the content and also if you passed binding an irb or pry console will be
opened where you can further experiment with the test.
page.driver.click(x, y) Click a very specific area of the screen.page.driver.scroll_to(left, top) Scroll to a given position.element.send_keys(*keys) Send keys to a given node.Manipulate HTTP request headers like a boss:
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" }
Notice that headers= will overwrite already set headers. You should use
add_headers if you want to add a few more. These headers will apply to all
subsequent HTTP requests (including requests for assets, AJAX, etc). They will
be automatically cleared at the end of the test.
page.driver.network_traffic allows you to inspect network traffic (i.e., loaded resources) on the current page. It returns an array of Ferrum::Network::Exchange objects, each representing a network request/response exchange. You can query both the request and response details of each exchange.# Retrieve all network exchanges
network_traffic = page.driver.network_traffic
# Access the first exchange
first_exchange = network_traffic.first
# Inspect the response of the first request
response = first_exchange.response
page.driver.wait_for_network_idle Natively waits for network idle and if
there are no active connections returns or raises TimeoutError error. Accepts
the same options as
wait_for_idlepage.driver.wait_for_network_idle
page.driver.refresh
Please note that network traffic is not cleared when you visit new page. You can
manually clear the network traffic by calling page.driver.clear_network_traffic
or page.driver.reset
page.driver.wait_for_reload unlike wait_for_network_idle will wait until
the whole page is reloaded or raise a timeout error. It's useful when you know
that for example after clicking autocomplete suggestion you expect page to be
reloaded, you have a few choices - put sleep or wait for network idle, but both
are bad. Sleep makes you wait longer or less than needed, network idle can
return earlier even before the whole page is started to reload. Here's the
rescue.The following methods are used to inspect and manipulate cookies:
page.driver.cookies - a hash of cookies accessible to the current
page. The keys are cookie names. The values are Cookie objects, with
the following methods: name, value, domain, path, size, secure?,
httponly?, session?, expires.page.driver.set_cookie(name, value, options = {}) - set a cookie.
The options hash can take the following keys: :domain, :path,
:secure, :httponly, :expires. :expires should be a
Time object.page.driver.remove_cookie(name) - remove a cookiepage.driver.clear_cookies - clear all cookiesBesides capybara screenshot method you can get image as Base64:
page.driver.render_base64(format, options)page.driver.basic_authorize(user, password)page.driver.set_proxy(ip, port, user, password)Cuprite supports URL blocklisting, which allows you to prevent scripts from running on designated domains:
page.driver.browser.url_blocklist = %r{http://www.example.com}
and also URL allowlisting, which allows scripts to only run on designated domains:
page.driver.browser.url_allowlist = %r{http://www.example.com}
For legacy support, url_blacklist= and url_whitelist= continue to work respectively.
If you are experiencing slower run times, consider creating a URL allowlist of domains that are essential or a blocklist of domains that are not essential, such as ad networks or analytics, to your testing environment.
The gem is available as open source under the terms of the MIT License.
(top 30 of 46)
Ruby
69.2%
HTML
20.8%
JavaScript
10.0%