Capybara: Ruby Integration & Acceptance Testing Guide
Capybara is a Ruby acceptance testing framework — drives a real browser via Selenium/Cuprite. Used with RSpec/Minitest for end-to-end Rails tests.
What is Capybara?
Capybara is a Ruby acceptance and integration testing library that simulates how a real user interacts with a web application. It provides a high-level DSL for visiting pages, filling forms, clicking links, and asserting on rendered content. Capybara is the de-facto standard for end-to-end testing in the Ruby/Rails ecosystem.
Capybara is a test driver, not a test framework — it works with RSpec, Minitest, or Cucumber. It supports multiple browser drivers: rack-test (no real browser, fast), Selenium (real Chrome/Firefox), Cuprite (Chrome via CDP), apparition (deprecated), and webkit (via capybara-webkit, also deprecated).
Why Capybara?
- Reads like English. Tests describe user actions, not DOM internals.
- Driver-agnostic. Same test runs against rack-test (fast) or Selenium (real browser).
- Smart waiting. Auto-waits for elements to appear (no manual
sleep). - Rails integration. Works out-of-box with Rails system tests (since Rails 5.1).
- Mature ecosystem. Stable since 2009; battle-tested.
Basic Capybara test (with RSpec)
require 'rails_helper'
RSpec.feature 'User signup', type: :feature do
scenario 'user signs up successfully' do
visit '/signup'
fill_in 'Email', with: 'alice@example.com'
fill_in 'Password', with: 'secret123'
click_button 'Create account'
expect(page).to have_content 'Welcome, alice'
expect(current_path).to eq '/dashboard'
end
endCommon Capybara DSL methods
| Action | Method |
|---|---|
| Navigate | visit '/path' |
| Click link/button | click_link 'text' / click_button 'text' |
| Fill form field | fill_in 'Label', with: 'value' |
| Select dropdown | select 'Option', from: 'Label' |
| Check checkbox | check 'Label' / uncheck 'Label' |
| Choose radio | choose 'Label' |
| Attach file | attach_file 'Label', '/path/file.png' |
| Find element | find('.selector') / find_by_id('id') |
| Assert content | expect(page).to have_content('text') |
| Assert selector | expect(page).to have_selector('.class') |
| Within scope | within('.sidebar') { ... } |
Capybara drivers compared
| Driver | Real browser? | Speed | JS support? |
|---|---|---|---|
| rack-test | No (in-process) | ⚡ Fastest | No |
| Selenium | Yes (Chrome/Firefox) | 🐢 Slow | Full |
| Cuprite | Yes (Chrome via CDP) | Medium | Full |
| apparition (deprecated) | Yes | Medium | Full |
| capybara-webkit (deprecated) | Yes (QtWebKit) | Medium | Full |
Recommended: rack-test for non-JS tests, Cuprite for JS tests (faster + more stable than Selenium).
Configuring Capybara
# spec/rails_helper.rb
require 'capybara/rails'
require 'capybara/rspec'
Capybara.default_driver = :rack_test
Capybara.javascript_driver = :cuprite
Capybara.default_max_wait_time = 5 # seconds to wait for elements
Capybara.server = :puma, { Silent: true }
RSpec.configure do |config|
config.before(:each, type: :feature, js: true) do
Capybara.current_driver = :cuprite
end
endCapybara best practices
- Use semantic selectors.
fill_in 'Email', with: ...beatsfind('#user_email_field').set(...). Survives UI refactors. - Don't disable auto-wait. Avoid
sleep; let Capybara wait for elements naturally. - Scope assertions. Use
within '.modal' do ... endto avoid matching wrong elements. - Database cleanup. Use
database_cleanerwith truncation strategy for JS tests; transactions for non-JS. - Headless in CI. Run Chrome headless in CI for speed and stability.
- Screenshots on failure.
capybara-screenshotauto-captures on failure for debugging. - Page Objects. For complex apps, encapsulate selectors in Page Object classes (
site_prismgem). - Avoid testing internals. Test what users see, not DOM structure.
Common pitfalls
- Flaky tests. Most flake = race conditions. Use Capybara matchers (
have_content,have_selector) which retry, notfind().text == ...which doesn't. - Shared state across tests. Use database_cleaner; reset session.
- JS not loading. Make sure you set
js: trueon the spec or feature. - Selenium hanging in CI. Use Cuprite — fewer hangs than ChromeDriver.
- Different behavior in dev vs CI. Often headless vs headed; or different Chrome version.
- Not testing real browser. rack-test misses JS-driven bugs entirely.
Capybara vs other tools
| Tool | Language | Approach |
|---|---|---|
| Capybara | Ruby | DSL on top of Selenium/CDP drivers |
| Cypress | JS | Full test runner; runs in-browser |
| Playwright | JS/Python/Java/.NET | Cross-browser via CDP |
| Selenium WebDriver | Multi-language | Lower-level; more boilerplate |
| Watir | Ruby | Selenium wrapper; alternative to Capybara |
FAQ: Capybara
Is Capybara still maintained?
Yes. Active development; widely used in Rails community. Latest major release: Capybara 3.x.
Should I use Capybara or Cypress?
Capybara if Ruby/Rails (lives in your test suite). Cypress if JS-only stack or you want a richer debugging UI.
What's the best driver for JS tests?
Cuprite. Faster + more reliable than Selenium ChromeDriver. Direct CDP connection skips WebDriver overhead.
How do I test JavaScript-driven UIs?
Tag specs with js: true and configure Capybara.javascript_driver = :cuprite.
Why are my Capybara tests flaky?
Usually race conditions or shared state. Use Capybara matchers (auto-retry); use database_cleaner for isolation; never sleep.
Can Capybara run in headless mode?
Yes — Cuprite runs headless by default; Selenium can be configured with headless: true.
How do I test file uploads?
attach_file 'File', Rails.root.join('spec/fixtures/test.png').
Load test your Capybara-tested app with LoadFocus
Capybara tests verify behavior; load tests verify scale. LoadFocus runs JMeter and k6 scripts against your Rails app from 25+ regions with up to 12,500 VUs. Sign up free at loadfocus.com/signup.
Related LoadFocus Tools
Put this concept into practice with LoadFocus — the same platform that powers everything you just read about.