Getting Started with Capybara
Capybara is a robust testing framework for Ruby that enables you to write integration and acceptance tests by simulating how real users interact with your web application. Its intuitive API and extensive browser automation capabilities have made it the de facto standard for testing Rails applications, though it works equally well with Sinatra, Hanami, and other Ruby web frameworks.
This tutorial will walk you through building a comprehensive testing suite for your Ruby web application using Capybara.
Prerequisites
Before working through this tutorial, make sure you have Ruby (version 3.0 or higher recommended) installed on your system. You should also have a basic understanding of Ruby syntax and testing concepts. This article assumes you're familiar with RSpec or Minitest, as we'll be using these frameworks alongside Capybara.
Setting up your first Capybara project
To demonstrate Capybara's capabilities effectively, we'll create a simple Sinatra application that you can use to follow along. Begin by creating a new directory for the project:
Initialize a Gemfile to manage dependencies:
Open the newly created Gemfile and add the required gems:
Install all dependencies by running:
Create a simple Sinatra application in an app.rb file:
Set up the corresponding view templates. First, create a views directory:
Add the main page template:
Create the result page that displays form submissions:
Add an about page:
Now configure RSpec and Capybara by creating a spec/spec_helper.rb file:
This configuration tells Capybara which application to test and integrates it with RSpec. The Capybara.app assignment points to your Sinatra application, while config.include Capybara::DSL makes Capybara's methods available in your tests.
Create your first test file:
Run the test suite:
You should see output confirming your test passed:
The test you just wrote demonstrates Capybara's fundamental workflow: visit a page, interact with it, and verify the expected outcome. The visit method loads the specified URL, while have_content checks if the page contains specific text.
Understanding Capybara's driver architecture
Capybara's flexibility comes from its driver system, which allows you to switch between different browser automation backends without rewriting your tests. Each driver offers different capabilities and trade-offs in terms of speed, JavaScript support, and debugging features.
The default driver is :rack_test, which provides extremely fast test execution by bypassing actual browser rendering. This driver directly tests your Rack application without JavaScript support, making it ideal for testing server-rendered pages and basic interactions:
This test runs in milliseconds because :rack_test doesn't start an actual browser. However, if your application relies heavily on JavaScript, you'll need a driver that supports it.
Configuring Selenium WebDriver
For testing JavaScript functionality, Capybara integrates seamlessly with Selenium WebDriver. You need to register a custom Selenium driver in your spec helper file.
Open your spec/spec_helper.rb file and add the Selenium configuration below the existing Capybara setup:
This configuration creates a custom Selenium driver that runs Chrome in headless mode. The headless option means the browser runs without a graphical interface, significantly speeding up test execution while still providing full JavaScript support.
You can mark individual tests to use the JavaScript driver by adding js: true metadata:
Choosing the right driver
Different scenarios call for different drivers. Use :rack_test for:
- Testing server-rendered HTML pages
- Form submissions without JavaScript
- Basic navigation and link clicking
- Scenarios where maximum speed is critical
Switch to Selenium (or similar JavaScript-capable drivers) when:
- Testing single-page applications
- Verifying JavaScript-driven interactions
- Testing AJAX requests and responses
- Debugging complex browser behavior
The ability to mix drivers within the same test suite gives you the best of both worlds: fast execution for simple tests and comprehensive JavaScript support when needed.
Finding and interacting with page elements
Capybara provides multiple strategies for locating elements on your pages, each with specific use cases and advantages. Understanding when to use each finder helps you write more reliable and maintainable tests.
Using semantic finders
The most robust approach involves using Capybara's semantic finders, which locate elements based on their purpose rather than implementation details.
Create a new test file to demonstrate form interactions:
Add the following test code:
Run your tests to verify everything works:
You should see three passing tests:
This approach uses the visible labels on your form rather than IDs or CSS classes. The fill_in method can locate inputs by their label text, placeholder, or name attribute, making your tests resilient to implementation changes.
Alternative element selectors
When semantic finders aren't sufficient, Capybara offers several other locator strategies. Open your existing spec/features/form_interaction_spec.rb file and add a new test to demonstrate these different approaches:
Run the tests again:
You should now see four passing tests:
Using data- attributes specifically for testing is often recommended because they make your selectors immune to styling changes while clearly documenting which elements are used in tests. For example, you could add data-test-id="submit-button" to your button and find it with find('[data-test-id="submit"]').
Scoping interactions
You can scope your interactions to specific sections of the page using within blocks, which is particularly useful for complex pages with repeating elements. The within block ensures that Capybara only searches for elements inside the specified container, preventing accidental interactions with similarly-named elements elsewhere on the page.
Here's an example that demonstrates scoping (this is just an illustration—you don't need to add this to your test suite right now):
Handling asynchronous behavior and waiting
One of Capybara's most valuable features is its automatic waiting mechanism, which dramatically reduces flaky tests caused by timing issues. Understanding how this works helps you write more reliable tests.
Automatic waiting behavior
By default, Capybara waits up to 2 seconds for elements to appear before failing. This happens transparently whenever you use finder methods. Let's see this in action by creating a page with delayed content.
First, update your Gemfile to add the puma gem:
Install the gem:
Now update your spec/spec_helper.rb to require selenium-webdriver:
Update your app.rb file to add a new route:
Create a new view that simulates delayed content:
This page uses JavaScript to display content after a 1-second delay, simulating a typical AJAX request.
Now create a test for this delayed content:
Add the test code:
Run the tests:
You should see the test pass:
Notice how Capybara automatically started a Puma server to host your application during testing. The test took about 2.6 seconds to complete. Capybara automatically waited for the content to appear without any explicit sleep commands.
Adjusting wait times for specific tests
For individual scenarios requiring different timeouts, use the using_wait_time helper. Add this test to your waiting_spec.rb:
The using_wait_time block temporarily overrides the default wait time for operations that need more patience, like slow API calls or complex animations.
Run the tests again:
You should see two passing tests. The using_wait_time block temporarily overrides the default 5-second wait time we set globally. This is useful for operations that need more patience, like slow API calls or complex animations. In this case, we increased the wait time to 10 seconds, though our delayed content still loads in just 1 second. If you had content that took longer to load, this extended timeout would prevent premature test failures.
Waiting for elements to disappear
Capybara can also wait for elements to disappear, which is useful when testing loading indicators. Update your views/delayed.erb to include a loading spinner:
The page now shows a loading message that disappears when the content loads.
Add a test that waits for the loading indicator to disappear:
The have_no_content matcher automatically waits for the element to disappear, making it perfect for testing loading states.
Run all the waiting tests:
Common waiting pitfalls
Avoid using Ruby's sleep in your tests, as it introduces arbitrary delays that make your suite slower and more brittle:
The Capybara approach is both faster (no unnecessary waiting) and more reliable (adapts to varying server response times).
Final thoughts
Throughout this tutorial, you've built a solid foundation for testing Ruby web applications with Capybara. You've learned how to set up the framework, configure drivers for different testing scenarios, interact with page elements using semantic finders, and handle asynchronous behavior with automatic waiting mechanisms.
The concepts covered here provide the essential tools for writing effective integration tests. By simulating real user interactions, Capybara helps you catch issues that unit tests might miss while keeping your tests readable and maintainable.
For more advanced usage and detailed API documentation, consult the official Capybara documentation.
Thanks for reading, and happy testing!