# action_dispatch-testing-integration-capybara
**Repository Path**: mirrors_thoughtbot/action_dispatch-testing-integration-capybara
## Basic Information
- **Project Name**: action_dispatch-testing-integration-capybara
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-10-22
- **Last Updated**: 2026-01-03
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# ActionDispatch::Testing::Integration::Capybara
Use [Capybara][] from within [ActionDispatch::IntegrationTest][].
## Usage
Use [Capybara][] selectors in tests outside of your System Test suite:
```ruby
class SessionsTest < ActionDispatch::IntegrationTest
test "new" do
get new_session_path
assert_title "Sign in"
assert_element "main", "aria-labelledby": true do |main|
assert_css main, "h1", id: main["aria-labelledby"], text: "Sign in"
assert_field main, "Email address", type: "email"
assert_field main, "Password", type: "password"
assert_button main, "Sign in", type: "submit"
end
end
end
```
## Installation
This gem re-opens existing Action Dispatch-provided classes. That fact is
reflected in the gem's name. Since the name is dependent on the structure of the
`action_dispatch/` directory within the `action_pack` gem, and that gem is part
of Rails' core suite of packages, this project _will not_ be published to
Rubygems in its current form.
There is an on-going discussion (see [rails/rails#41291][] and
[rails/rails#43361][]) about building this behavior into Rails itself. Until
that discussion concludes, this gem will serve as a temporary solution.
To reflect the temporary nature of this project, `Gemfile` entries should refer
to the GitHub URL and release tags with the `github:` and `tag:` options.
Install with `minitest`
---
To use the gem with `minitest`, add the following entry, making sure to declare
`require: "action_dispatch/testing/integration/capybara/minitest"`:
```ruby
gem "action_dispatch-testing-integration-capybara",
github: "thoughtbot/action_dispatch-testing-integration-capybara", tag: "v0.2.0",
require: "action_dispatch/testing/integration/capybara/minitest"
```
And then execute:
```bash
$ bundle
```
Install with `rspec`
---
To use the gem with `rspec`, add the following entry, making sure to declare
`require: "action_dispatch/testing/integration/capybara/rspec"`:
```ruby
gem "action_dispatch-testing-integration-capybara",
github: "thoughtbot/action_dispatch-testing-integration-capybara", tag: "v0.2.0",
require: "action_dispatch/testing/integration/capybara/rspec"
```
And then execute:
```bash
$ bundle
```
## Why?
If your application relies on your server to generate and transmit _all_ of its
HTML , then the structure and
contents of those HTTP requests
and responses is **crucial**.
Testing the overlap between Controllers and Views
---
Out of the box, Action Dispatch depends on the [rails-dom-testing][] gem to
provide tests with a way of asserting the structure and contents of a request's
HTML response body. For example, consider an HTML response for an HTTP [GET][]
request to `/articles/hello-world`:
```html
Hello, World!
Hello, World!
```
The `rails-dom-testing` gem provides a collection of methods that transform [CSS
selectors][] and text into assertions about the structure and contents of the
response body's HTML:
```ruby
class ArticlesTest < ActionDispatch::IntegrationTest
test "index" do
get article_path("hello-world")
assert_select "title", "Hello, World!"
assert_select "body main h1", "Hello, World!"
end
end
```
In their most simple form, they provide a lot of utility in their flexibility.
In spite of that flexibility, writing assertions in this style can become
complicated in the face of _Real World features_.
For example, consider an HTML response for an HTTP [GET][] request to
`/sessions/new`:
```html
Sign in
Sign in
```
The `ActionDispatch::IntegrationTest` cases for this page might cover several
facets of the HTML, namely:
1. The page has a `` element containing the text `"Sign in"`
2. The page has the text `"Sign in"` in its header _and_ a `` with
`"Sign in"` as its content, and that they're _two different_ elements.
3. The page has an ` ` element to collect the user's email
address, and [that field is labelled][] with the text `"Email address"`
4. The page has an ` ` element to collect the user's
password, and [that field is labelled][] with the text `"Password"`.
We could cover those requirements with the following `rails-dom-testing`-capable
test:
```ruby
class SessionsTest < ActionDispatch::IntegrationTest
test "new" do
get new_session_path
assert_select "title", "Sign in"
assert_select "body main h1", "Sign in"
assert_select "body main button", "Sign in"
assert_select %(input[id="session_email_address"][type="email"])
assert_select %(label[for="session_email_address"]), "Email address"
assert_select %(input[id="session_password"][type="password"])
assert_select %(label[for="session_password"]), "Password"
end
end
```
These assertions are sufficient to exercise the page, making sure it meets all
of our requirements. Unfortunately, the assertions have some issues.
For instance, the assertions about the relationships between the ` ` and
`` elements are extremely brittle. They encode the
`session_email_address` and `session_password` element `[id]` attribute directly
into the test. If that page's HTML were to change, those tests would fail even
if the new HTML declared the `` elements with the same text content and
in a way that continued to properly reference their ` ` elements.
Similarly, the specificity required by the `title`, `h1`, and `button` selectors
is tedious compared to how different a ``, `` and `` are
treated by a browser.
These types of issues could be addressed by introducing abstractions
to account for the varying semantics of each requirement. Luckily, a tool that
already does that on our behalf exists: System Tests!
If we were to write a System Test to exercise our page to make sure it meets the
requirements, it might resemble something like:
```ruby
class SessionsTest < ActionDispatch::SystemTestCase
test "new" do
visit new_session_path
assert_title "Sign in"
assert_css "h1", "Sign in"
assert_button "Sign in"
assert_field "Email address", type: "email"
assert_field "Password", type: "password"
end
end
```
These assertions are not only more terse, they're also more robust. For example,
the `assert_button` will continue to pass if the `` element were
replaced with an ` ` element with the same text. Similarly,
the `assert_field` calls would continue to pass if the ` ` elements `[id]`
attributes were changed, or if they were moved to be direct descendants of the
`` element. What an improvement!
Unfortunately, these improvements come with a cost.
[that field is labelled]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Text_labels_and_names#form_elements_must_be_labeled
The Challenges of a growing test suite
---
In practice, a Rails application's test suite will exercise its controller and
view code in **two** ways: through [ActionDispatch::IntegrationTest][] tests,
and through [ActionDispatch::SystemTestCase][] tests.
[System Tests][] are often executed by driving **real** browsers with tools like
[Selenium][], [Webdriver][], or over the [Chrome DevTools Protocol][] (via
[apparition][]). The good news: driving a browser through your test cases
provides _an extremely high_ and valuable level of fidelity and confidence in
the end-user experience. The bad news: driving a browser through your test
incurs _an extremely high_ cost: speed.
That cost can be recuperated by configuring System Tests to be [`driven_by
:rack_test`][]. When configured this way, test cases rely on a Rack Test
"browser" to make HTTP requests, "click" on buttons, and "fill in" form fields
on their behalf. These types of tests are valuable in their own right, but they
don't provide the same level on control as `ActionDispatch::IntegrationTest`
cases.
For example, an `ActionDispatch::IntegrationTest` can submit an HTTP request
with any [HTTP verb][], whereas a System Test can only directly submit `GET`
requests through calls to [visit][]. Similarly, System Tests can't access the
response's [status code][] or its [headers][] directly, nor can it read from
Rails-specific utilities like the [`cookies`, `flash`, or `session`][]. For
example, consider a test that exercises a controller's response to an invalid
request:
```ruby
class ArticlesTest < ActionDispatch::IntegrationTest
test "update" do
valid_attributes = { title: "A valid Article", body: "It's valid!" }
invalid_attributes = { title: "Will be invalid", body: "" }
article = Article.create! valid_attributes
assert_no_changes -> { article.reload.attributes } do
put article_path(article), params: { article: invalid_attributes }
end
assert_response :unprocessable_entity
assert_equal "Failed to create Article!", flash[:alert]
end
end
```
Conversely, `ActionDispatch::IntegrationTest` cases exercise the application one
layer of abstraction below the browser: at the HTTP request-response layer. They
don't drive a **real** browser, but they do make the same kinds of HTTP
requests! On top of that, they provide built-in mechanisms to read directly from
the response, session, flash, or cookies.
It can be challenging to strike a balance between the confidence & fidelity of
System Tests and the speed and granularity of Integration Tests when exercising
the structure and contents of a feature's HTML. It can be even more challenging
if you need to maintain and context switch between two parallel sets of HTML
assertions. Since both System Tests and Integration Tests are Rack
Test-compliant, wouldn't it be nice if there was a way to get the best of both
worlds?
Combining `ActionDispatch::IntegrationTest` with `Capybara`
---
[Capybara][] provides Rails' System Tests with its collection of finders,
actions, and assertions. Conceptually, the value provided by Capybara's finders
and assertions overlaps entirely with the assertions provided by
`rails-dom-testing`. If we wanted to share the same `Rack::Test` session between
our `ActionDispatch::IntegrationTest` case and our `Capybara` assertions, we'd
need to integrate a `Capybara::Session` instance with the
[`integration_session`][] at the heart of our tests. If we wanted to modify the
`ActionDispatch::IntegrationTest` class, we could do so with an
[ActiveSupport::Concern][] mixed-into the class from within an
[`ActiveSupport.on_load`] hook:
```ruby
ActiveSupport.on_load :action_dispatch_integration_test do
include(Module.new do
extend ActiveSupport::Concern
included do
setup do
integration_session.extend(Module.new do
# ...
end)
end
end
end)
end
```
Within that concern, we'd want to declare a `#page` method like the one our
System Tests provide. Within the `#page` method, we'd construct, memoize, and
return an instance of a `Capybara::Session`:
```diff
ActiveSupport.on_load :action_dispatch_integration_test do
include(Module.new do
extend ActiveSupport::Concern
included do
setup do
integration_session.extend(Module.new do
+ def page
+ @page ||= ::Capybara::Session.new(:rack_test, @app)
+ end
end)
end
end
end)
end
```
Once we constructed that instance, we'd need to make it available to the
inner-working mechanics of the `ActionDispatch::IntegrationTest` case. Right
now, the only avenue toward that goal is re-declaring the [`_mock_session`][]
private method:
```diff
ActiveSupport.on_load :action_dispatch_integration_test do
include(Module.new do
extend ActiveSupport::Concern
included do
setup do
integration_session.extend(Module.new do
def page
@page ||= ::Capybara::Session.new(:rack_test, @app)
end
+
+ def _mock_session
+ @_mock_session ||= page.driver.browser.rack_mock_session
+ end
end)
end
end
end)
end
```
Depending on Rails' private interfaces is _very_ risky and _highly_ discouraged.
There is an ongoing discussion about adding public-facing hooks for this type of
integration (see [rails/rails#41291][] and [rails/rails#43361][]). Since these a
test-level dependencies, changes to the private implementation won't lead to
production-level outages. Ideally, this will only be temporary!
With those changes in place, the last step is to mix-in Capybara's assertions:
```diff
+require "capybara/minitest"
+
ActiveSupport.on_load :action_dispatch_integration_test do
include(Module.new do
extend ActiveSupport::Concern
included do
+ include Capybara::Minitest::Assertions
+
setup do
integration_session.extend(Module.new do
def page
@page ||= ::Capybara::Session.new(:rack_test, @app)
end
def _mock_session
@_mock_session ||= page.driver.browser.rack_mock_session
end
end)
end
end
end)
end
```
Now we can re-write our `SessionsTest` to inherit from
`ActionDispatch::IntegrationTest` instead of `ActionDispatch::SystemTestCase`:
```diff
-class SessionsTest < ActionDispatch::SystemTestCase
+class SessionsTest < ActionDispatch::IntegrationTest
test "new" do
- visit new_session_path
+ get new_session_path
assert_title "Sign in"
assert_css "h1", "Sign in"
assert_button "Sign in"
assert_field "Email address", type: "email"
assert_field "Password", type: "password"
end
end
```
If we wanted to use other Capybara-provided helpers like [within][], we could
delegate those calls to our `page` instance:
```diff
require "capybara/minitest"
ActiveSupport.on_load :action_dispatch_integration_test do
include(Module.new do
extend ActiveSupport::Concern
included do
include Capybara::Minitest::Assertions
+
+ delegate :within, to: :page
setup do
integration_session.extend(Module.new do
def page
@page ||= ::Capybara::Session.new(:rack_test, @app)
end
def _mock_session
@_mock_session ||= page.driver.browser.rack_mock_session
end
end)
end
end
end)
end
```
Then we could use them in our tests:
```diff
class SessionsTest < ActionDispatch::IntegrationTest
test "new" do
get new_session_path
assert_title "Sign in"
+ within "main" do
assert_css "h1", "Sign in"
assert_button "Sign in"
assert_field "Email address", type: "email"
assert_field "Password", type: "password"
+ end
end
end
```
With this integration in place, there's nothing stopping us from [declaring
custom Capybara selectors][], or adding a dependency like
[`citizensadvice/capybara_accessible_selectors`][] that declares them on our
behalf:
```diff
class SessionsTest < ActionDispatch::IntegrationTest
test "new" do
get new_session_path
assert_title "Sign in"
- within "main" do
+ within_section "Sign in" do
- assert_css "h1", "Sign in"
assert_button "Sign in"
assert_field "Email address", type: "email"
assert_field "Password", type: "password"
end
end
end
```
With access to assertions and selectors like the ones that Capybara or
[`citizensadvice/capybara_accessible_selectors`][] provide (like
[assert_field][], the [described_by:][] filter, and the [alert][] selector), we
have an opportunity to make assertions about the structure _and_ semantics of
the response to an invalid request:
```diff
class ArticlesTest < ActionDispatch::IntegrationTest
test "update" do
valid_attributes = { title: "A valid Article", body: "It's valid!" }
invalid_attributes = { title: "Will be invalid", body: "" }
article = Article.create! valid_attributes
assert_no_changes -> { article.reload.attributes } do
put article_path(article), params: { article: invalid_attributes }
end
assert_response :unprocessable_entity
- assert_equal "Failed to create Article!", flash[:alert]
+ assert_selector :alert, "Failed to create Article!"
+ assert_field "Body", described_by: "can't be blank"
end
end
```
[assert_field]: https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Minitest/Assertions#assert_field-instance_method
[described_by:]: https://github.com/citizensadvice/capybara_accessible_selectors/tree/v0.4.2#described_by-string
[alert]: https://github.com/citizensadvice/capybara_accessible_selectors/tree/v0.4.2#alert
Hopefully, an integration like this will become publicly supported in the
future. In the meantime, if you'd like to add support for Capybara assertions in
your Minitest suite's `ActionDispatch::IntegrationTest` cases, or your RSpec
suite's `type: :request` tests, check out the
[`thoughtbot/action_dispatch-testing-integration-capybara`][] gem today!
[rails-dom-testing]: https://github.com/rails/rails-dom-testing#railsdomtesting
[GET]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET
[CSS selectors]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors
[ActionDispatch::IntegrationTest]: https://edgeapi.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html
[ActionDispatch::SystemTestCase]: https://edgeapi.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html
[System Tests]: https://guides.rubyonrails.org/testing.html#system-testing
[Selenium]: https://www.selenium.dev
[Webdriver]: https://developer.mozilla.org/en-US/docs/Web/WebDriver
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
[apparition]: https://github.com/twalpole/apparition
[`driven_by :rack_test`]: https://edgeapi.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html#method-c-driven_by
[HTTP verb]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
[visit]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Session#visit-instance_method
[status code]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
[headers]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
[`cookies`, `flash`, or `session`]: https://guides.rubyonrails.org/testing.html#the-three-hashes-of-the-apocalypse
[Capybara]: http://teamcapybara.github.io/capybara/
[`integration_session`]: https://edgeapi.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html#method-i-integration_session
[ActiveSupport::Concern]: https://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html
[`ActiveSupport.on_load`]: https://guides.rubyonrails.org/engines.html#available-load-hooks
[`_mock_session`]: https://github.com/rails/rails/blob/v7.0.0.alpha2/actionpack/lib/action_dispatch/testing/integration.rb#L300
[rails/rails#41291]: https://github.com/rails/rails/pull/41291
[rails/rails#43361]: https://github.com/rails/rails/pull/43361
[within]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Session#within-instance_method
[declaring custom Capybara selectors]: https://github.com/teamcapybara/capybara#xpath-css-and-selectors
[`citizensadvice/capybara_accessible_selectors`]: https://github.com/citizensadvice/capybara_accessible_selectors#documentation
[`thoughtbot/action_dispatch-testing-integration-capybara`]: https://github.com/thoughtbot/action_dispatch-testing-integration-capybara
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
## About
This project is maintained by Sean Doyle.

This project is maintained and funded by thoughtbot, inc.
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
We love open source software!
See [our other projects][community]
or [hire us][hire] to help build your product.
[community]: https://thoughtbot.com/community?utm_source=github
[hire]: https://thoughtbot.com/hire-us?utm_source=github