v0.1 open beta

v0.1.0 · public beta we want your feedback

Open source

Faster system tests for , without Chromium.

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.

scroll
01

Why this engine

Not a Chromium fork. Not a WebKit patch. A new browser. lightpanda-io/browser

Built from scratch new
Designed for the work tests actually do — automation, not browsing. No legacy of human-facing assumptions.
Zig V8 stack
Low-level systems language paired with Chrome's JavaScript engine. Native cold-start, native concurrency.
Headless by design pure
No rendering engine, no compositor — every byte spent on what your tests actually need.

Benchmarks · lightpanda.io · 933 pages · AWS m5.large

02

The split

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

Capybara visit · click · fill_in · assert_selector · …
Headless + visual ~ 280 MB RSS / test
  1. cuprite ferrum CDP
  2. Chromium multi-process · Blink · V8 · Skia · …

Boots the entire Chromium pipeline for every headless test.

Headless only ~ 17 MB RSS / test
  1. capybara-lightpanda CDP client
  2. Lightpanda Zig · V8 · html5ever · libcurl

A single binary. No rendering engine. JS where you need it.

03

Pair with Cuprite

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

Run both, gated by BROWSER=lightpanda

The 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 surface

If 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"

In your test suite

Honest numbers from a Rails 8.1 app — Turbo + Stimulus, 24 DOM-only system tests on an M-series laptop:

DriverTestsTimeRSS / worker
Lightpanda24 / 246.89s ~17 MB
Chrome24 / 247.09s ~280 MB

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:

  • Memory headroom. A 2 GB CI runner that hosts one Chromium can host 8–10 lightpanda processes. Parallelism scales where it didn’t before.
  • Cold start. No Skia, no Blink, no GPU process, no compositor — the binary is resident in milliseconds.
  • Less flake. Removing the Chromium boot removes one of the loudest sources of CI noise.

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.

04

What works, what doesn't

Lightpanda is a headless browser by design — no layout, no paint, no compositor.
Here's the honest matrix for system-test authors.

CapabilitycupritelightpandaNotes
Performance — where lightpanda earns its place
Memory per session~250 MB~10 MB25× less the whole point
Cold-start time~1.5 s<200 ms7× faster per worker process
Core test surface — at parity
Visit, click, fill, submit, navigate, historycore Capybara surface
CSS + XPath selectorsXPath 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 screenshotsno compositor (by design)
Visual regression / pixel testskeep cuprite for these
File uploads (input[type=file])upstream tracking
Service Workers, WebAuthnbrowser-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.

05

Help us ship 1.0

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.

Try it in 5 minutes

  1. Add to your Gemfile, in the :test group.

    gem "capybara-lightpanda"
  2. 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
                    end
  3. Run 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.