- cuprite ferrum CDP
- Chromium multi-process · Blink · V8 · Skia · …
Boots the entire Chromium pipeline for every headless test.
v0.1.0 · public beta we want your feedback
Open source
A Capybara driver that speaks CDP to
Lightpanda
— the same way cuprite speaks CDP to Chromium. If
you've already shipped a Rails app on Cuprite, the architecture is
identical; only the binary at the other end of the WebSocket
changes.
Keep cuprite for visual specs.
Run capybara-lightpanda for the headless
rest.
Stop fighting flaky Turbo Drive specs.
Only the driver changes.
Not a Chromium fork. Not a WebKit patch. A new browser. lightpanda-io/browser —
Benchmarks · lightpanda.io · 933 pages · AWS m5.large
cuprite drives Chromium for real rendering. capybara-lightpanda speaks the same CDP on a leaner runtime — sized for headless tests. Run them side by side; each owns the specs it's best at.
Shared · same Capybara API on both sides
Boots the entire Chromium pipeline for every headless test.
A single binary. No rendering engine. JS where you need it.
Drop in alongside cuprite, gate the lanes with one env var. Keep
cuprite where you need pixels, run
capybara-lightpanda
where you don't — and roll back by unsetting
BROWSER=lightpanda. Honest numbers from a real Rails 8
suite below.
If you already drive Capybara with cuprite, the architecture you’ve chosen is right — same page.driver surface, same Capybara semantics, same DSL. capybara-lightpanda is the headless companion that runs alongside it, not a rewrite of how you write tests.
# Gemfile — same group, additional gem
group :test do
gem "cuprite" # keep for visual specs / pixel diffs
gem "capybara-lightpanda"
end
Wire the driver in your test setup. In Rails 7+ / Rails 8 with system tests:
require "capybara-lightpanda"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :lightpanda
end
Install the browser once:
# macOS
brew install lightpanda-io/lightpanda/lightpanda
# Linux x86_64
curl -L -o lightpanda https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux
chmod +x lightpanda
BROWSER=lightpandaThe dual-driver pattern lets your CI keep cuprite for the few specs that need pixels while everything else runs on lightpanda. One env var, no rewrites:
# spec/support/capybara.rb (or test/support/...)
if ENV["BROWSER"] == "lightpanda"
require "capybara-lightpanda"
Capybara.default_driver = :lightpanda
Capybara.javascript_driver = :lightpanda
else
# your existing cuprite (or selenium-chrome) setup
end
# fast headless lane — DOM, forms, Turbo, network, cookies
BROWSER=lightpanda bundle exec rails test test/system/
# pixel lane — visual specs, screenshots, layout-dependent assertions
bundle exec rails test test/system/
In GitHub Actions the dual-lane pattern is two short jobs that share a checkout.
Drop the binary in once and gate on the env var:
# .github/workflows/system_tests.yml
jobs:
headless:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with: { ruby-version: "3.3", bundler-cache: true }
- name: Install Lightpanda
run: |
curl -sSL -o /usr/local/bin/lightpanda \
https://github.com/lightpanda-io/browser/releases/download/nightly/lightpanda-x86_64-linux
chmod +x /usr/local/bin/lightpanda
- run: bundle exec rails test test/system/
env: { BROWSER: lightpanda }
visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with: { ruby-version: "3.3", bundler-cache: true }
- run: bundle exec rails test test/system/ # cuprite / chrome lane
page.driver — same surfaceIf your specs already call into page.driver (cookies, network, debug, headers), most of those calls work identically because the API mirrors what cuprite users expect:
# Cookies — same names, same shape
page.driver.set_cookie("session_id", value, domain: "127.0.0.1", httpOnly: true)
page.driver.cookies # Hash of cookies on the current page
page.driver.clear_cookies
page.driver.remove_cookie("session_id")
# Navigation
page.driver.go_back
page.driver.go_forward
page.driver.refresh
# JS — Capybara's standard methods route through the V8 engine
execute_script "window.example = 'hi'"
evaluate_script "document.title"
Honest numbers from a Rails 8.1 app — Turbo + Stimulus, 24 DOM-only system tests on an M-series laptop:
| Driver | Tests | Time | RSS / worker |
|---|---|---|---|
| Lightpanda | 24 / 24 | ||
| Chrome | 24 / 24 |
Identical results. On a 24-test suite the wall-clock margin is small — Capybara’s own wait time dominates, just as it does on Chrome — so don’t read the 3% as “the speed claim.” The win that pays for itself is the right column:
The upstream 9× / 16× numbers on this page are reproduced from lightpanda.io — a 933-page synthetic crawl on AWS m5.large. They show the ceiling at scale; the table above shows the floor on a real Rails suite.
Lightpanda is a headless browser by design —
no layout, no paint, no compositor.
Here's the
honest matrix for system-test authors.
| Capability | cuprite | lightpanda | Notes |
|---|---|---|---|
| Performance — where lightpanda earns its place | |||
| Memory per session | ~250 MB | ~10 MB | 25× less the whole point |
| Cold-start time | ~1.5 s | <200 ms | 7× faster per worker process |
| Core test surface — at parity | |||
| Visit, click, fill, submit, navigate, history | ✓ | ✓ | core Capybara surface |
| CSS + XPath selectors | ✓ | ✓ | XPath polyfilled (~80% coverage) |
| Cookies, headers, redirects | ✓ | ✓ | — |
| Turbo Drive, Frames, Stimulus | ✓ | ✓ | — |
execute_script / evaluate_script | ✓ | ✓ | — |
| iframes, frame switching | ✓ | ✓ | — |
| Browser-engine features — keep cuprite on those specific tests if you need these | |||
| JS dialogs | ✓ | ~ | accept_modal :alert, dismiss_modal |
Scroll, resize, full getComputedStyle | ✓ | ~ | CSSOM landed; no layout |
| Web Workers | ✓ | ~ | limited APIs |
| Real screenshots | ✓ | — | no compositor (by design) |
| Visual regression / pixel tests | ✓ | — | keep cuprite for these |
File uploads
(input[type=file]) | ✓ | — | upstream tracking |
| Service Workers, WebAuthn | ✓ | — | browser-engine territory |
Need pixels? Keep cuprite for visual specs and let
lightpanda own the rest.
You can run both side by side
— only the driver changes.
capybara-lightpanda is in public beta. Real Rails suites are how we find the edges. The cost to try it is one Gemfile line and one env var — the cost to abandon it is unsetting that env var.
Add to your Gemfile, in the
:test group.
gem "capybara-lightpanda"Gate it on an env var so your existing driver stays the default.
# spec/support/capybara.rb · or
test/support/... if ENV["BROWSER"] == "lightpanda" require
"capybara-lightpanda" Capybara.javascript_driver = :lightpanda
endRun one suite. Decide.
$ BROWSER=lightpanda bundle exec rails
test test/system/
Rollback in 30 s. Drop the env var — your
suite goes back to Cuprite. Gemfile.lock is the only
persistent change.