No release in over 3 years
A Capybara driver for Lightpanda, the fast headless browser built in Zig. Provides a production-ready driver with XPath polyfill, reliable navigation, and cookie management — ready for real-world Rails test suites.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

Capybara::Lightpanda

A Capybara driver for Lightpanda, the fast headless browser built in Zig.

This gem provides a self-contained, production-ready Capybara driver with a built-in CDP client. No external browser-client gem required — just install and go:

  • Reliable navigation — falls back to document.readyState polling when Page.loadEventFired doesn't fire (a known Lightpanda limitation on pages with complex JS)
  • XPath polyfill — auto-injected after each navigation so Capybara's internal XPath selectors work (find, click_on, fill_in, assert_selector, etc.)
  • Cookie managementset_cookie, clear_cookies, remove_cookie on the driver + graceful fallback when Network.clearBrowserCookies crashes the CDP connection
  • Drop-in Capybara integration — registers a :lightpanda driver, configure and go

Architecture

Similar to how Cuprite builds on Ferrum, but as a single gem:

Capybara  →  capybara-lightpanda (driver + CDP client)  →  Lightpanda browser

Installation

1. Install the Lightpanda browser

# macOS
brew install lightpanda-io/lightpanda/lightpanda

# Linux (Debian/Ubuntu) — see https://lightpanda.io/docs/

2. Add the gem

# Gemfile
group :test do
  gem "capybara-lightpanda"
end
bundle install

Usage

Basic setup

# test/support/capybara.rb or spec/support/capybara.rb
require "capybara-lightpanda"

Capybara::Lightpanda.configure do |config|
  config.host = "127.0.0.1"
  config.port = 9222
  config.timeout = 15
  config.browser_path = "/usr/local/bin/lightpanda" # optional, auto-detected
end

Capybara.default_driver = :lightpanda
Capybara.javascript_driver = :lightpanda

Dual-driver setup (recommended)

Run most tests with Chrome, use Lightpanda for fast DOM-only tests:

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
  # Your existing Chrome/Cuprite setup
  Capybara.default_driver = :cuprite
end
# Run with Lightpanda
BROWSER=lightpanda bundle exec rails test test/system/

# Run with Chrome (default)
bundle exec rails test test/system/

Setting cookies (e.g. login helper)

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

  • Navigation (visit, click_link, go_back, go_forward, refresh)
  • JavaScript execution (V8 engine) — evaluate_script, execute_script, evaluate_async_script
  • Forms — fill_in, click_button, select, choose, check, uncheck
  • Finding — find, all, within, CSS and XPath selectors
  • Matchers — assert_selector, assert_text, assert_current_path, has_field?, has_select?
  • Cookies — set/get/clear/remove via CDP
  • Frames — within_frame, scoped finding
  • Keyboard — send_keys with modifiers and special keys
  • Network — traffic tracking, custom headers, idle waiting

Turbo Rails support

The gem handles Turbo-enabled Rails apps transparently:

Feature Status How
Turbo Frames Works natively Lazy-loading (src=), scoped link navigation
Turbo Drive Auto-disabled Gem disables Drive (body replacement fails in Lightpanda) — standard link navigation restored
Form submission Auto-handled When Turbo is present, forms submit via fetch() + document.write() to bypass Turbo's interception
Turbo Streams Not supported Depends on Turbo's fetch pipeline which Lightpanda can't render

Root cause: Lightpanda's document.body is read-only — Turbo Drive's body replacement and frame form responses can't be applied. The gem works around this automatically.

Known limitations

These are Lightpanda browser limitations, not driver limitations:

Feature Status
Screenshots Not supported (no rendering engine)
window.getComputedStyle() Returns defaults (no CSS engine)
scroll_to, resize No layout engine
Complex Stimulus controllers Some may not execute fully
XPath axes/functions Polyfill covers ~80% of Capybara usage
File uploads Not yet supported
Turbo Streams Not supported (Turbo's fetch-then-render pipeline)

Benchmark

Tested on a Rails 8.1 app (Turbo + Stimulus), 24 DOM-only tests:

Driver Tests Time Speed
Lightpanda 24/24 pass 6.89s 3.48 tests/s
Chrome 24/24 pass 7.09s 3.38 tests/s

Lightpanda's advantage is expected to grow on larger suites due to faster startup and lower memory usage.

Configuration

Capybara::Lightpanda.configure do |config|
  config.host = "127.0.0.1"       # Lightpanda bind host
  config.port = 9222              # Lightpanda CDP port
  config.timeout = 15             # Navigation/command timeout (seconds)
  config.process_timeout = 10     # Browser process startup timeout
  config.browser_path = nil       # Path to lightpanda binary (auto-detected)
end

Dynamic port (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

How it works

Component Description
Browser High-level API with readyState polling fallback when Page.loadEventFired never fires
Cookies Catches BrowserError from unsupported Network.clearBrowserCookies, deletes cookies individually
XPathPolyfill Provides document.evaluate + XPathResult shim for Capybara's XPath selectors
Client CDP command dispatch over WebSocket with timeout and event subscription
Driver Complete Capybara driver with set_cookie, clear_cookies, remove_cookie
Node DOM interactions via JavaScript evaluation

Credits

Patterns adapted from these MIT-licensed projects (cookies API, frame switching, node call/error conventions, retry/event utilities) are acknowledged with the original copyright notices in NOTICE.md.

Contributing

Bug reports and pull requests are welcome on GitHub.

License

MIT License