Quick start
1. Install the Lightpanda browser
# macOS
brew install lightpanda-io/lightpanda/lightpanda
# Linux — download the static binary from the release page
curl -L https://github.com/lightpanda-io/browser/releases/latest/download/lightpanda-x86_64-linux \
-o /usr/local/bin/lightpanda
chmod +x /usr/local/bin/lightpanda
Verify the install:
lightpanda --version
2. Add the gem
# Gemfile
group :test do
gem "capybara-lightpanda"
end
bundle install
3. Register the driver
# spec/support/capybara.rb (or test/support/capybara.rb)
require "capybara-lightpanda"
Capybara::Lightpanda.configure do |config|
config.host = "127.0.0.1"
config.port = 9222
config.timeout = 15
end
Capybara.default_driver = :lightpanda
Capybara.javascript_driver = :lightpanda
That’s it. Run your suite and the driver will boot a Lightpanda process and connect over CDP.
Configuration
Capybara::Lightpanda.configure do |config|
config.host = "127.0.0.1" # Lightpanda bind host
config.port = 9222 # CDP port
config.timeout = 15 # navigation/command timeout (seconds)
config.process_timeout = 10 # browser startup timeout
config.browser_path = nil # path to lightpanda binary; nil = auto-detect
end
| Option | Default | Notes |
|---|---|---|
host | "127.0.0.1" | Bind address for the CDP server |
port | 9222 | TCP port; use a dynamic port for parallel suites |
timeout | 15 | Per-CDP-command timeout, also covers navigation polling |
process_timeout | 10 | Wait this long for lightpanda serve to start before failing |
browser_path | nil | If nil, the driver searches PATH and common Homebrew paths |
Dynamic port for parallel tests
def available_port
server = TCPServer.new("127.0.0.1", 0)
port = server.addr[1]
server.close
port
end
Capybara::Lightpanda.configure do |config|
config.port = ENV.fetch("LIGHTPANDA_PORT", available_port).to_i
end
Setup recipes
Single driver (Lightpanda everywhere)
For projects that don’t depend on rendered visuals:
require "capybara-lightpanda"
Capybara.default_driver = :lightpanda
Capybara.javascript_driver = :lightpanda
Dual driver (Cuprite + Lightpanda)
Keep Cuprite for visual specs (anything that takes screenshots or asserts on pixels) and route the rest through Lightpanda:
if ENV["BROWSER"] == "lightpanda"
require "capybara-lightpanda"
Capybara::Lightpanda.configure do |config|
config.timeout = 15
end
Capybara.default_driver = :lightpanda
Capybara.javascript_driver = :lightpanda
else
# existing Cuprite setup
Capybara.default_driver = :cuprite
end
# fast headless run
BROWSER=lightpanda bundle exec rspec spec/system/
# default (Chrome via Cuprite)
bundle exec rspec spec/system/
Login helper via cookies
Set a session cookie before navigating, so you don’t have to drive the login form on every spec:
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
def login_as(user)
session = user.sessions.first_or_create!
cookie_jar = ActionDispatch::TestRequest
.create({ "REQUEST_METHOD" => "GET" })
.cookie_jar
cookie_jar.signed[:session_id] = { value: session.id }
page.driver.set_cookie(
"session_id",
cookie_jar[:session_id],
domain: "127.0.0.1",
httpOnly: true,
secure: false,
)
end
end
What works
| Capybara surface | Status |
|---|---|
Navigation — visit, click_link, go_back, go_forward, refresh | ✓ |
JavaScript — evaluate_script, execute_script, evaluate_async_script | ✓ (V8) |
Forms — fill_in, click_button, select, choose, check, uncheck | ✓ |
Finders — find, all, within, CSS + XPath | ✓ (XPath via polyfill) |
Matchers — assert_selector, assert_text, has_field?, has_select? | ✓ |
Cookies — set_cookie, clear_cookies, remove_cookie | ✓ |
Frames — within_frame, scoped finding | ✓ |
Keyboard — send_keys with modifiers | ✓ |
Turbo Rails
The driver handles Turbo-enabled Rails apps transparently.
| Feature | Status | Mechanism |
|---|---|---|
| Turbo Frames | Native | Lazy-load (src=) and scoped link navigation use Turbo’s existing fetch + innerHTML swap |
| Turbo Drive | Native | Lightpanda’s body.replaceWith works since v0.2.9; the driver’s selector polyfill keeps #id lookups working through the snapshot+swap pattern |
| Form submission | Auto-handled | fetch() + document.write() shim bypasses Turbo’s interception when needed |
| Turbo Streams | Not supported | Lightpanda lacks the rendering pipeline Streams depend on |
Known limitations
These are upstream Lightpanda limits, not driver bugs:
| Surface | Status |
|---|---|
| Screenshots | Not supported — no rendering engine |
window.getComputedStyle() | Returns defaults — no CSS engine |
scroll_to, resize | No layout engine |
File uploads (<input type="file">) | Not yet supported (upstream #2175) |
| Complex Stimulus controllers | Some may not execute fully |
| XPath axes / functions | Polyfill covers ~80% of Capybara’s usage |
If you need any of these, run that spec under Cuprite and keep the rest on Lightpanda.
How it works
| Component | Responsibility |
|---|---|
Capybara::Lightpanda::Browser | High-level page API; falls back to document.readyState polling when Page.loadEventFired is unreliable |
Capybara::Lightpanda::Client | CDP command dispatch over WebSocket with timeouts and event subscription |
Capybara::Lightpanda::Driver | The Capybara driver — registers as :lightpanda, exposes set_cookie / clear_cookies / remove_cookie |
Capybara::Lightpanda::Node | DOM operations via Runtime.callFunctionOn with object-id binding |
Capybara::Lightpanda::Cookies | Wraps Network.getCookies / setCookie / deleteCookies with safe fallbacks |
javascripts/index.js | XPath polyfill, Turbo activity tracking, requestSubmit polyfill, #id selector rewrite for Lightpanda’s CSS-engine quirk |
The driver speaks the same CDP dialect Cuprite and Ferrum use, so most patterns from those projects translate directly. Where Lightpanda diverges from Chromium, the driver papers over it.
Examples
Runnable Rails demos in the repo, covering both RSpec and Minitest, with and without Turbo:
rails_minitest_example.rb— system test with Minitestrails_rspec_example.rb— system spec with RSpecrails_turbo_minitest_example.rb— Turbo Drive + Frames with Minitestrails_turbo_rspec_example.rb— Turbo Drive + Frames with RSpec
Reference
- README on GitHub
- CHANGELOG
- Issues
- Lightpanda upstream — the browser that powers this driver