├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE ├── MIT-LICENSE ├── README.md ├── Rakefile ├── action_dispatch-testing-integration-capybara.gemspec ├── bin └── test ├── lib ├── action_dispatch │ └── testing │ │ └── integration │ │ ├── capybara.rb │ │ └── capybara │ │ ├── minitest.rb │ │ ├── railtie.rb │ │ ├── rspec.rb │ │ └── version.rb └── tasks │ └── action_dispatch │ └── testing │ └── integration │ └── capybara_tasks.rake ├── spec ├── action_dispatch │ └── testing │ │ └── integration │ │ └── capybara │ │ └── rspec_spec.rb └── rails_helper.rb └── test ├── action_dispatch └── testing │ └── integration │ └── capybara │ └── minitest_test.rb ├── dummy ├── Rakefile ├── app │ ├── assets │ │ └── config │ │ │ └── manifest.js │ ├── controllers │ │ ├── application_controller.rb │ │ └── templates_controller.rb │ ├── helpers │ │ └── application_helper.rb │ └── views │ │ └── layouts │ │ └── application.html.erb ├── bin │ ├── rails │ ├── rake │ └── setup ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── content_security_policy.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── permissions_policy.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── puma.rb │ └── routes.rb └── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ └── favicon.ico └── test_helper.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI Tests" 2 | 3 | on: "pull_request" 4 | 5 | jobs: 6 | build: 7 | name: "Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}" 8 | 9 | runs-on: "ubuntu-latest" 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | ruby: ["2.7", "3.0", "3.1", "3.2"] 15 | rails: ["6.0", "6.1", "7.0", "main"] 16 | 17 | env: 18 | RAILS_VERSION: "${{ matrix.rails }}" 19 | 20 | steps: 21 | - uses: "actions/checkout@v2" 22 | 23 | - name: "Install Ruby ${{ matrix.ruby }}" 24 | uses: "ruby/setup-ruby@v1" 25 | with: 26 | rubygems: "3.3.13" 27 | ruby-version: "${{ matrix.ruby }}" 28 | 29 | - name: "Generate lockfile" 30 | run: | 31 | bundle config path vendor/bundle 32 | bundle lock 33 | 34 | - uses: "actions/cache@v1" 35 | with: 36 | path: "vendor/bundle" 37 | key: bundle-${{ hashFiles('Gemfile.lock') }} 38 | 39 | - name: "Install dependencies" 40 | run: bundle install 41 | 42 | - name: "Minitest support" 43 | run: bin/test 44 | 45 | - name: "RSpec support" 46 | run: bundle exec rspec 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /doc/ 3 | /log/*.log 4 | /pkg/ 5 | /tmp/ 6 | /test/dummy/log/*.log 7 | /test/dummy/tmp/ 8 | Gemfile.lock 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The noteworthy changes for each `ActionDispatch::Testing::Integration::Capybara` 4 | version are included here. For a complete changelog, see the [commits] for each 5 | version via the version links. 6 | 7 | [commits]: https://github.com/thoughtbot/action_dispatch-testing-integration-capybara/commits/main 8 | 9 | ## main 10 | 11 | ## 0.1.1 (Jan 26, 2023) 12 | 13 | * Invoke [Capybara::RackTest::Browser#reset_cache!](https://github.com/teamcapybara/capybara/blob/master/lib/capybara/rack_test/browser.rb#L112-L114) while issuing multiple requests 14 | 15 | *Sean Doyle* 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | By participating in this project, you agree to abide by the 4 | [thoughtbot code of conduct][1]. 5 | 6 | [1]: https://thoughtbot.com/open-source-code-of-conduct 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | # Specify your gem's dependencies in action_dispatch-testing-integration-capybara.gemspec. 5 | gemspec 6 | 7 | gem "sprockets-rails" 8 | 9 | case (rails_version = ENV.fetch("RAILS_VERSION", "main")) 10 | when "main" 11 | gem "rails", github: "rails/rails" 12 | else 13 | gem "rails", "~>#{rails_version}.0" 14 | end 15 | 16 | # Start debugger with binding.b -- Read more: https://github.com/ruby/debug 17 | # gem "debug", ">= 1.0.0", group: %i[ development test ] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Sean Doyle and thoughtbot, inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Sean Doyle 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActionDispatch::Testing::Integration::Capybara 2 | 3 | Use [Capybara][] from within [ActionDispatch::IntegrationTest][]. 4 | 5 | ## Why? 6 | 7 | If your application relies on your server to generate and transmit _all_ of its 8 | HTML, then the structure and 9 | contents of those HTTP requests 10 | and responses is **crucial**. 11 | 12 | Testing the overlap between Controllers and Views 13 | --- 14 | 15 | Out of the box, Action Dispatch depends on the [rails-dom-testing][] gem to 16 | provide tests with a way of asserting the structure and contents of a request's 17 | HTML response body. For example, consider an HTML response for an HTTP [GET][] 18 | request to `/articles/hello-world`: 19 | 20 | ```html 21 | 22 | 23 | Hello, World! 24 | 25 | 26 |
27 |

Hello, World!

28 |
29 | 30 | 31 | ``` 32 | 33 | The `rails-dom-testing` gem provides a collection of methods that transform [CSS 34 | selectors][] and text into assertions about the structure and contents of the 35 | response body's HTML: 36 | 37 | ```ruby 38 | class ArticlesTest < ActionDispatch::IntegrationTest 39 | test "index" do 40 | get article_path("hello-world") 41 | 42 | assert_select "title", "Hello, World!" 43 | assert_select "body main h1", "Hello, World!" 44 | end 45 | end 46 | ``` 47 | 48 | In their most simple form, they provide a lot of utility in their flexibility. 49 | In spite of that flexibility, writing assertions in this style can become 50 | complicated in the face of _Real World features_. 51 | 52 | For example, consider an HTML response for an HTTP [GET][] request to 53 | `/sessions/new`: 54 | 55 | ```html 56 | 57 | 58 | Sign in 59 | 60 | 61 |
62 |

Sign in

63 | 64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | ``` 77 | 78 | The `ActionDispatch::IntegrationTest` cases for this page might cover several 79 | facets of the HTML, namely: 80 | 81 | 1. The page has a `` element containing the text `"Sign in"` 82 | 2. The page has the text `"Sign in"` in its header _and_ a `<button>` with 83 | `"Sign in"` as its content, and that they're _two different_ elements. 84 | 3. The page has an `<input type="email">` element to collect the user's email 85 | address, and [that field is labelled][] with the text `"Email address"` 86 | 4. The page has an `<input type="password">` element to collect the user's 87 | password, and [that field is labelled][] with the text `"Password"`. 88 | 89 | We could cover those requirements with the following `rails-dom-testing`-capable 90 | test: 91 | 92 | ```ruby 93 | class SessionsTest < ActionDispatch::IntegrationTest 94 | test "new" do 95 | get new_session_path 96 | 97 | assert_select "title", "Sign in" 98 | assert_select "body main h1", "Sign in" 99 | assert_select "body main button", "Sign in" 100 | assert_select %(input[id="session_email_address"][type="email"]) 101 | assert_select %(label[for="session_email_address"]), "Email address" 102 | assert_select %(input[id="session_password"][type="password"]) 103 | assert_select %(label[for="session_password"]), "Password" 104 | end 105 | end 106 | ``` 107 | 108 | These assertions are sufficient to exercise the page, making sure it meets all 109 | of our requirements. Unfortunately, the assertions have some issues. 110 | 111 | For instance, the assertions about the relationships between the `<input>` and 112 | `<label>` elements are extremely brittle. They encode the 113 | `session_email_address` and `session_password` element `[id]` attribute directly 114 | into the test. If that page's HTML were to change, those tests would fail even 115 | if the new HTML declared the `<label>` elements with the same text content and 116 | in a way that continued to properly reference their `<input>` elements. 117 | 118 | Similarly, the specificity required by the `title`, `h1`, and `button` selectors 119 | is tedious compared to how different a `<title>`, `<h1>` and `<button>` are 120 | treated by a browser. 121 | 122 | These types of issues could be addressed by introducing abstractions 123 | to account for the varying semantics of each requirement. Luckily, a tool that 124 | already does that on our behalf exists: System Tests! 125 | 126 | If we were to write a System Test to exercise our page to make sure it meets the 127 | requirements, it might resemble something like: 128 | 129 | ```ruby 130 | class SessionsTest < ActionDispatch::SystemTestCase 131 | test "new" do 132 | visit new_session_path 133 | 134 | assert_title "Sign in" 135 | assert_css "h1", "Sign in" 136 | assert_button "Sign in" 137 | assert_field "Email address", type: "email" 138 | assert_field "Password", type: "password" 139 | end 140 | end 141 | ``` 142 | 143 | These assertions are not only more terse, they're also more robust. For example, 144 | the `assert_button` will continue to pass if the `<button>` element were 145 | replaced with an `<input type="submit">` element with the same text. Similarly, 146 | the `assert_field` calls would continue to pass if the `<input>` elements `[id]` 147 | attributes were changed, or if they were moved to be direct descendants of the 148 | `<label>` element. What an improvement! 149 | 150 | Unfortunately, these improvements come with a cost. 151 | 152 | [that field is labelled]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Text_labels_and_names#form_elements_must_be_labeled 153 | 154 | The Challenges of a growing test suite 155 | --- 156 | 157 | In practice, a Rails application's test suite will exercise its controller and 158 | view code in **two** ways: through [ActionDispatch::IntegrationTest][] tests, 159 | and through [ActionDispatch::SystemTestCase][] tests. 160 | 161 | [System Tests][] are often executed by driving **real** browsers with tools like 162 | [Selenium][], [Webdriver][], or over the [Chrome DevTools Protocol][] (via 163 | [apparition][]). The good news: driving a browser through your test cases 164 | provides _an extremely high_ and valuable level of fidelity and confidence in 165 | the end-user experience. The bad news: driving a browser through your test 166 | incurs _an extremely high_ cost: speed. 167 | 168 | That cost can be recuperated by configuring System Tests to be [`driven_by 169 | :rack_test`][]. When configured this way, test cases rely on a Rack Test 170 | "browser" to make HTTP requests, "click" on buttons, and "fill in" form fields 171 | on their behalf. These types of tests are valuable in their own right, but they 172 | don't provide the same level on control as `ActionDispatch::IntegrationTest` 173 | cases. 174 | 175 | For example, an `ActionDispatch::IntegrationTest` can submit an HTTP request 176 | with any [HTTP verb][], whereas a System Test can only directly submit `GET` 177 | requests through calls to [visit][]. Similarly, System Tests can't access the 178 | response's [status code][] or its [headers][] directly, nor can it read from 179 | Rails-specific utilities like the [`cookies`, `flash`, or `session`][]. For 180 | example, consider a test that exercises a controller's response to an invalid 181 | request: 182 | 183 | ```ruby 184 | class ArticlesTest < ActionDispatch::IntegrationTest 185 | test "update" do 186 | valid_attributes = { title: "A valid Article", body: "It's valid!" } 187 | invalid_attributes = { title: "Will be invalid", body: "" } 188 | article = Article.create! valid_attributes 189 | 190 | assert_no_changes -> { article.reload.attributes } do 191 | put article_path(article), params: { article: invalid_attributes } 192 | end 193 | 194 | assert_response :unprocessable_entity 195 | assert_equal "Failed to create Article!", flash[:alert] 196 | end 197 | end 198 | ``` 199 | 200 | Conversely, `ActionDispatch::IntegrationTest` cases exercise the application one 201 | layer of abstraction below the browser: at the HTTP request-response layer. They 202 | don't drive a **real** browser, but they do make the same kinds of HTTP 203 | requests! On top of that, they provide built-in mechanisms to read directly from 204 | the response, session, flash, or cookies. 205 | 206 | It can be challenging to strike a balance between the confidence & fidelity of 207 | System Tests and the speed and granularity of Integration Tests when exercising 208 | the structure and contents of a feature's HTML. It can be even more challenging 209 | if you need to maintain and context switch between two parallel sets of HTML 210 | assertions. Since both System Tests and Integration Tests are Rack 211 | Test-compliant, wouldn't it be nice if there was a way to get the best of both 212 | worlds? 213 | 214 | Combining `ActionDispatch::IntegrationTest` with `Capybara` 215 | --- 216 | 217 | [Capybara][] provides Rails' System Tests with its collection of finders, 218 | actions, and assertions. Conceptually, the value provided by Capybara's finders 219 | and assertions overlaps entirely with the assertions provided by 220 | `rails-dom-testing`. If we wanted to share the same `Rack::Test` session between 221 | our `ActionDispatch::IntegrationTest` case and our `Capybara` assertions, we'd 222 | need to integrate a `Capybara::Session` instance with the 223 | [`integration_session`][] at the heart of our tests. If we wanted to modify the 224 | `ActionDispatch::IntegrationTest` class, we could do so with an 225 | [ActiveSupport::Concern][] mixed-into the class from within an 226 | [`ActiveSupport.on_load`] hook: 227 | 228 | ```ruby 229 | ActiveSupport.on_load :action_dispatch_integration_test do 230 | include(Module.new do 231 | extend ActiveSupport::Concern 232 | 233 | included do 234 | setup do 235 | integration_session.extend(Module.new do 236 | # ... 237 | end) 238 | end 239 | end 240 | end) 241 | end 242 | ``` 243 | 244 | Within that concern, we'd want to declare a `#page` method like the one our 245 | System Tests provide. Within the `#page` method, we'd construct, memoize, and 246 | return an instance of a `Capybara::Session`: 247 | 248 | ```diff 249 | ActiveSupport.on_load :action_dispatch_integration_test do 250 | include(Module.new do 251 | extend ActiveSupport::Concern 252 | 253 | included do 254 | setup do 255 | integration_session.extend(Module.new do 256 | + def page 257 | + @page ||= ::Capybara::Session.new(:rack_test, @app) 258 | + end 259 | end) 260 | end 261 | end 262 | end) 263 | end 264 | ``` 265 | 266 | Once we constructed that instance, we'd need to make it available to the 267 | inner-working mechanics of the `ActionDispatch::IntegrationTest` case. Right 268 | now, the only avenue toward that goal is re-declaring the [`_mock_session`][] 269 | private method: 270 | 271 | ```diff 272 | ActiveSupport.on_load :action_dispatch_integration_test do 273 | include(Module.new do 274 | extend ActiveSupport::Concern 275 | 276 | included do 277 | setup do 278 | integration_session.extend(Module.new do 279 | def page 280 | @page ||= ::Capybara::Session.new(:rack_test, @app) 281 | end 282 | + 283 | + def _mock_session 284 | + @_mock_session ||= page.driver.browser.rack_mock_session 285 | + end 286 | end) 287 | end 288 | end 289 | end) 290 | end 291 | ``` 292 | 293 | Depending on Rails' private interfaces is _very_ risky and _highly_ discouraged. 294 | There is an ongoing discussion about adding public-facing hooks for this type of 295 | integration (see [rails/rails#41291][] and [rails/rails#43361][]). Since these a 296 | test-level dependencies, changes to the private implementation won't lead to 297 | production-level outages. Ideally, this will only be temporary! 298 | 299 | With those changes in place, the last step is to mix-in Capybara's assertions: 300 | 301 | ```diff 302 | +require "capybara/minitest" 303 | + 304 | ActiveSupport.on_load :action_dispatch_integration_test do 305 | include(Module.new do 306 | extend ActiveSupport::Concern 307 | 308 | included do 309 | + include Capybara::Minitest::Assertions 310 | + 311 | setup do 312 | integration_session.extend(Module.new do 313 | def page 314 | @page ||= ::Capybara::Session.new(:rack_test, @app) 315 | end 316 | 317 | def _mock_session 318 | @_mock_session ||= page.driver.browser.rack_mock_session 319 | end 320 | end) 321 | end 322 | end 323 | end) 324 | end 325 | ``` 326 | 327 | Now we can re-write our `SessionsTest` to inherit from 328 | `ActionDispatch::IntegrationTest` instead of `ActionDispatch::SystemTestCase`: 329 | 330 | ```diff 331 | -class SessionsTest < ActionDispatch::SystemTestCase 332 | +class SessionsTest < ActionDispatch::IntegrationTest 333 | test "new" do 334 | - visit new_session_path 335 | + get new_session_path 336 | 337 | assert_title "Sign in" 338 | assert_css "h1", "Sign in" 339 | assert_button "Sign in" 340 | assert_field "Email address", type: "email" 341 | assert_field "Password", type: "password" 342 | end 343 | end 344 | ``` 345 | 346 | If we wanted to use other Capybara-provided helpers like [within][], we could 347 | delegate those calls to our `page` instance: 348 | 349 | ```diff 350 | require "capybara/minitest" 351 | 352 | ActiveSupport.on_load :action_dispatch_integration_test do 353 | include(Module.new do 354 | extend ActiveSupport::Concern 355 | 356 | included do 357 | include Capybara::Minitest::Assertions 358 | + 359 | + delegate :within, to: :page 360 | 361 | setup do 362 | integration_session.extend(Module.new do 363 | def page 364 | @page ||= ::Capybara::Session.new(:rack_test, @app) 365 | end 366 | 367 | def _mock_session 368 | @_mock_session ||= page.driver.browser.rack_mock_session 369 | end 370 | end) 371 | end 372 | end 373 | end) 374 | end 375 | ``` 376 | 377 | Then we could use them in our tests: 378 | 379 | ```diff 380 | class SessionsTest < ActionDispatch::IntegrationTest 381 | test "new" do 382 | get new_session_path 383 | 384 | assert_title "Sign in" 385 | + within "main" do 386 | assert_css "h1", "Sign in" 387 | assert_button "Sign in" 388 | assert_field "Email address", type: "email" 389 | assert_field "Password", type: "password" 390 | + end 391 | end 392 | end 393 | ``` 394 | 395 | With this integration in place, there's nothing stopping us from [declaring 396 | custom Capybara selectors][], or adding a dependency like 397 | [`citizensadvice/capybara_accessible_selectors`][] that declares them on our 398 | behalf: 399 | 400 | ```diff 401 | class SessionsTest < ActionDispatch::IntegrationTest 402 | test "new" do 403 | get new_session_path 404 | 405 | assert_title "Sign in" 406 | - within "main" do 407 | + within_section "Sign in" do 408 | - assert_css "h1", "Sign in" 409 | assert_button "Sign in" 410 | assert_field "Email address", type: "email" 411 | assert_field "Password", type: "password" 412 | end 413 | end 414 | end 415 | ``` 416 | 417 | With access to assertions and selectors like the ones that Capybara or 418 | [`citizensadvice/capybara_accessible_selectors`][] provide (like 419 | [assert_field][], the [described_by:][] filter, and the [alert][] selector), we 420 | have an opportunity to make assertions about the structure _and_ semantics of 421 | the response to an invalid request: 422 | 423 | ```diff 424 | class ArticlesTest < ActionDispatch::IntegrationTest 425 | test "update" do 426 | valid_attributes = { title: "A valid Article", body: "It's valid!" } 427 | invalid_attributes = { title: "Will be invalid", body: "" } 428 | article = Article.create! valid_attributes 429 | 430 | assert_no_changes -> { article.reload.attributes } do 431 | put article_path(article), params: { article: invalid_attributes } 432 | end 433 | 434 | assert_response :unprocessable_entity 435 | - assert_equal "Failed to create Article!", flash[:alert] 436 | + assert_selector :alert, "Failed to create Article!" 437 | + assert_field "Body", described_by: "can't be blank" 438 | end 439 | end 440 | ``` 441 | 442 | [assert_field]: https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Minitest/Assertions#assert_field-instance_method 443 | [described_by:]: https://github.com/citizensadvice/capybara_accessible_selectors/tree/v0.4.2#described_by-string 444 | [alert]: https://github.com/citizensadvice/capybara_accessible_selectors/tree/v0.4.2#alert 445 | 446 | Hopefully, an integration like this will become publicly supported in the 447 | future. In the meantime, if you'd like to add support for Capybara assertions in 448 | your Minitest suite's `ActionDispatch::IntegrationTest` cases, or your RSpec 449 | suite's `type: :request` tests, check out the 450 | [`thoughtbot/action_dispatch-testing-integration-capybara`][] gem today! 451 | 452 | [rails-dom-testing]: https://github.com/rails/rails-dom-testing#railsdomtesting 453 | [GET]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET 454 | [CSS selectors]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors 455 | [ActionDispatch::IntegrationTest]: https://edgeapi.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html 456 | [ActionDispatch::SystemTestCase]: https://edgeapi.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html 457 | [System Tests]: https://guides.rubyonrails.org/testing.html#system-testing 458 | [Selenium]: https://www.selenium.dev 459 | [Webdriver]: https://developer.mozilla.org/en-US/docs/Web/WebDriver 460 | [Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/ 461 | [apparition]: https://github.com/twalpole/apparition 462 | [`driven_by :rack_test`]: https://edgeapi.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html#method-c-driven_by 463 | [HTTP verb]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods 464 | [visit]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Session#visit-instance_method 465 | [status code]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 466 | [headers]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers 467 | [`cookies`, `flash`, or `session`]: https://guides.rubyonrails.org/testing.html#the-three-hashes-of-the-apocalypse 468 | [Capybara]: http://teamcapybara.github.io/capybara/ 469 | [`integration_session`]: https://edgeapi.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html#method-i-integration_session 470 | [ActiveSupport::Concern]: https://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html 471 | [`ActiveSupport.on_load`]: https://guides.rubyonrails.org/engines.html#available-load-hooks 472 | [`_mock_session`]: https://github.com/rails/rails/blob/v7.0.0.alpha2/actionpack/lib/action_dispatch/testing/integration.rb#L300 473 | [rails/rails#41291]: https://github.com/rails/rails/pull/41291 474 | [rails/rails#43361]: https://github.com/rails/rails/pull/43361 475 | [within]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Session#within-instance_method 476 | [declaring custom Capybara selectors]: https://github.com/teamcapybara/capybara#xpath-css-and-selectors 477 | [`citizensadvice/capybara_accessible_selectors`]: https://github.com/citizensadvice/capybara_accessible_selectors#documentation 478 | [`thoughtbot/action_dispatch-testing-integration-capybara`]: https://github.com/thoughtbot/action_dispatch-testing-integration-capybara 479 | 480 | ## Installation 481 | 482 | This gem re-opens existing Action Dispatch-provided classes. That fact is 483 | reflected in the gem's name. Since the name is dependent on the structure of the 484 | `action_dispatch/` directory within the `action_pack` gem, and that gem is part 485 | of Rails' core suite of packages, this project _will not_ be published to 486 | Rubygems in its current form. 487 | 488 | There is an on-going discussion (see [rails/rails#41291][] and 489 | [rails/rails#43361][]) about building this behavior into Rails itself. Until 490 | that discussion concludes, this gem will serve as a temporary solution. 491 | 492 | To reflect the temporary nature of this project, `Gemfile` entries should refer 493 | to the GitHub URL and release tags with the `github:` and `tag:` options. 494 | 495 | Install with `minitest` 496 | --- 497 | 498 | To use the gem with `minitest`, add the following entry, making sure to declare 499 | `require: "action_dispatch/testing/integration/capybara/minitest"`: 500 | 501 | ```ruby 502 | gem "action_dispatch-testing-integration-capybara", 503 | github: "thoughtbot/action_dispatch-testing-integration-capybara", tag: "v0.1.0", 504 | require: "action_dispatch/testing/integration/capybara/minitest" 505 | ``` 506 | 507 | And then execute: 508 | ```bash 509 | $ bundle 510 | ``` 511 | 512 | Install with `rspec` 513 | --- 514 | 515 | To use the gem with `rspec`, add the following entry, making sure to declare 516 | `require: "action_dispatch/testing/integration/capybara/rspec"`: 517 | 518 | ```ruby 519 | gem "action_dispatch-testing-integration-capybara", 520 | github: "thoughtbot/action_dispatch-testing-integration-capybara", tag: "v0.1.0", 521 | require: "action_dispatch/testing/integration/capybara/rspec" 522 | ``` 523 | 524 | And then execute: 525 | ```bash 526 | $ bundle 527 | ``` 528 | 529 | ## License 530 | 531 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 532 | 533 | ## About 534 | 535 | This project is maintained by Sean Doyle. 536 | 537 | ![thoughtbot](http://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg) 538 | 539 | This project is maintained and funded by thoughtbot, inc. 540 | The names and logos for thoughtbot are trademarks of thoughtbot, inc. 541 | 542 | We love open source software! 543 | See [our other projects][community] 544 | or [hire us][hire] to help build your product. 545 | 546 | [community]: https://thoughtbot.com/community?utm_source=github 547 | [hire]: https://thoughtbot.com/hire-us?utm_source=github 548 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | 3 | require "bundler/gem_tasks" 4 | require "rake/testtask" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.pattern = "test/**/*_test.rb" 9 | t.verbose = false 10 | end 11 | 12 | task default: :test 13 | -------------------------------------------------------------------------------- /action_dispatch-testing-integration-capybara.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/action_dispatch/testing/integration/capybara/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "action_dispatch-testing-integration-capybara" 5 | spec.version = ActionDispatch::Testing::Integration::Capybara::VERSION 6 | spec.authors = ["Sean Doyle"] 7 | spec.email = ["sean.p.doyle24@gmail.com"] 8 | spec.homepage = "https://github.com/thoughtbot/action_dispatch-testing-integration-capybara" 9 | spec.summary = "Use Capybara from within ActionDispatch::IntegrationTest" 10 | spec.description = "Use Capybara from within ActionDispatch::IntegrationTest" 11 | spec.license = "MIT" 12 | 13 | spec.metadata["homepage_uri"] = spec.homepage 14 | spec.metadata["source_code_uri"] = "https://github.com/thoughtbot/action_dispatch-integration_test-capybara" 15 | spec.metadata["changelog_uri"] = "https://github.com/thoughtbot/action_dispatch-integration_test-capybara/blob/main/CHANGELOG" 16 | 17 | spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 18 | 19 | spec.add_dependency "actionpack" 20 | spec.add_dependency "activesupport" 21 | spec.add_dependency "capybara" 22 | 23 | spec.add_development_dependency "rspec-rails" 24 | end 25 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $: << File.expand_path("../test", __dir__) 3 | 4 | require "bundler/setup" 5 | require "rails/plugin/test" 6 | -------------------------------------------------------------------------------- /lib/action_dispatch/testing/integration/capybara.rb: -------------------------------------------------------------------------------- 1 | require "action_dispatch/testing/integration/capybara/version" 2 | require "action_dispatch/testing/integration/capybara/railtie" 3 | 4 | module ActionDispatch 5 | module Testing 6 | module Integration 7 | module Capybara 8 | # Your code goes here... 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/action_dispatch/testing/integration/capybara/minitest.rb: -------------------------------------------------------------------------------- 1 | require "action_dispatch/testing/integration/capybara" 2 | 3 | ActiveSupport.on_load :action_dispatch_integration_test do 4 | require "capybara/minitest" 5 | 6 | include ::Capybara::Minitest::Assertions 7 | end 8 | -------------------------------------------------------------------------------- /lib/action_dispatch/testing/integration/capybara/railtie.rb: -------------------------------------------------------------------------------- 1 | require "action_dispatch/testing/integration" 2 | require "active_support/concern" 3 | require "capybara" 4 | 5 | module ActionDispatch 6 | module Testing 7 | module Integration 8 | module Capybara 9 | class Railtie < ::Rails::Railtie 10 | initializer "action_dispatch-testing-integration-capybara.extensions" do 11 | ActiveSupport.on_load :action_dispatch_integration_test do 12 | include(Module.new do 13 | extend ActiveSupport::Concern 14 | 15 | included do 16 | delegate :within, to: :page 17 | 18 | setup do 19 | integration_session.extend(Module.new do 20 | def process(*arguments, **options, &block) 21 | if @page && @page.driver.browser.respond_to?(:reset_cache!) 22 | @page.driver.browser.reset_cache! 23 | end 24 | 25 | super 26 | end 27 | 28 | def page 29 | @page ||= ::Capybara::Session.new(:rack_test, @app) 30 | end 31 | 32 | def _mock_session 33 | @_mock_session ||= page.driver.browser.rack_mock_session 34 | end 35 | end) 36 | end 37 | end 38 | end) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/action_dispatch/testing/integration/capybara/rspec.rb: -------------------------------------------------------------------------------- 1 | require "action_dispatch/testing/integration/capybara" 2 | require "capybara/rspec" 3 | -------------------------------------------------------------------------------- /lib/action_dispatch/testing/integration/capybara/version.rb: -------------------------------------------------------------------------------- 1 | module ActionDispatch 2 | module Testing 3 | module Integration 4 | module Capybara 5 | VERSION = "0.1.1" 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/tasks/action_dispatch/testing/integration/capybara_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :action_dispatch_testing_integration_capybara do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /spec/action_dispatch/testing/integration/capybara/rspec_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | require "action_dispatch/testing/integration/capybara/rspec" 3 | 4 | RSpec.describe "Capybara extensions", type: :request do 5 | it "supports Capybara matchers" do 6 | post templates_path, params: { template: <<~HTML } 7 | <button>A button</button> 8 | HTML 9 | 10 | expect(page).to have_button "A button" 11 | end 12 | 13 | it "clears page across multiple requests" do 14 | post templates_path, params: { template: <<~HTML } 15 | <button>Request 1</button> 16 | HTML 17 | 18 | expect(page).to have_button "Request 1" 19 | 20 | post templates_path, params: { template: <<~HTML } 21 | <button>Request 2</button> 22 | HTML 23 | 24 | expect(page).to have_button "Request 2" 25 | end 26 | 27 | it "supports Capybara::Session#within" do 28 | post templates_path, params: { template: <<~HTML } 29 | <section><h1>Hello, World!</h1></section> 30 | HTML 31 | 32 | within("section") { expect(page).to have_text "Hello, World!" } 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../test/dummy/config/environment" 3 | require "rspec/rails" 4 | 5 | RSpec.configure do |config| 6 | config.infer_spec_type_from_file_location! 7 | config.filter_rails_from_backtrace! 8 | end 9 | -------------------------------------------------------------------------------- /test/action_dispatch/testing/integration/capybara/minitest_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "action_dispatch/testing/integration/capybara/minitest" 3 | 4 | class ActionDispatch::Testing::Integration::CapybaraTest < ActionDispatch::IntegrationTest 5 | test "supports Capybara assertions" do 6 | post templates_path, params: { template: <<~HTML } 7 | <button>A button</button> 8 | HTML 9 | 10 | assert_button "A button" 11 | end 12 | 13 | test "clears page across multiple requests" do 14 | post templates_path, params: { template: <<~HTML } 15 | <button>Request 1</button> 16 | HTML 17 | 18 | assert_button "Request 1" 19 | 20 | post templates_path, params: { template: <<~HTML } 21 | <button>Request 2</button> 22 | HTML 23 | 24 | assert_button "Request 2" 25 | end 26 | 27 | test "supports Capybara::Session#within" do 28 | post templates_path, params: { template: <<~HTML } 29 | <section><h1>Hello, World!</h1></section> 30 | HTML 31 | 32 | within("section") { assert_text "Hello, World!" } 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /test/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtbot/action_dispatch-testing-integration-capybara/b4811cb0a6f3e96630c379c06293e9b82f068bb4/test/dummy/app/assets/config/manifest.js -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/templates_controller.rb: -------------------------------------------------------------------------------- 1 | class TemplatesController < ApplicationController 2 | def create 3 | render layout: "application", inline: params.fetch(:template, <<~HTML) 4 | <form method="post"> 5 | <p>Submit a POST request with a <tt>template</tt> value</p> 6 | 7 | <button name="template" value="Hello, world">Submit</button> 8 | </form> 9 | HTML 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <title>Dummy 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | 9 | 10 | <%= yield %> 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | puts "\n== Removing old logs and tempfiles ==" 21 | system! "bin/rails log:clear tmp:clear" 22 | 23 | puts "\n== Restarting application server ==" 24 | system! "bin/rails restart" 25 | end 26 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | # require "active_job/railtie" 7 | # require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | # require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | # require "action_cable/engine" 15 | require "sprockets/railtie" 16 | require "rails/test_unit/railtie" 17 | 18 | # Require the gems listed in Gemfile, including any gems 19 | # you've limited to :test, :development, or :production. 20 | Bundler.require(*Rails.groups) 21 | require "action_dispatch/testing/integration/capybara" 22 | 23 | module Dummy 24 | class Application < Rails::Application 25 | config.load_defaults Rails::VERSION::STRING.to_f 26 | 27 | # Configuration for the application, engines, and railties goes here. 28 | # 29 | # These settings can be overridden in specific environments using the files 30 | # in config/environments, which are processed later. 31 | # 32 | # config.time_zone = "Central Time (US & Canada)" 33 | # config.eager_load_paths << Rails.root.join("extras") 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) 3 | 4 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 5 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) 6 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join("tmp/caching-dev.txt").exist? 20 | config.action_controller.perform_caching = true 21 | config.action_controller.enable_fragment_cache_logging = true 22 | 23 | config.cache_store = :memory_store 24 | config.public_file_server.headers = { 25 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 26 | } 27 | else 28 | config.action_controller.perform_caching = false 29 | 30 | config.cache_store = :null_store 31 | end 32 | 33 | # Print deprecation notices to the Rails logger. 34 | config.active_support.deprecation = :log 35 | 36 | # Raise exceptions for disallowed deprecations. 37 | config.active_support.disallowed_deprecation = :raise 38 | 39 | # Tell Active Support which deprecation messages to disallow. 40 | config.active_support.disallowed_deprecation_warnings = [] 41 | 42 | # Suppress logger output for asset requests. 43 | config.assets.quiet = true 44 | 45 | # Raises error for missing translations. 46 | # config.i18n.raise_on_missing_translations = true 47 | 48 | # Annotate rendered view with file names. 49 | # config.action_view.annotate_rendered_view_with_filenames = true 50 | 51 | # Uncomment if you wish to allow Action Cable access from any origin. 52 | # config.action_cable.disable_request_forgery_protection = true 53 | end 54 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 26 | 27 | # Compress CSS using a preprocessor. 28 | # config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 34 | # config.asset_host = "http://assets.example.com" 35 | 36 | # Specifies the header that your server uses for sending files. 37 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 38 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 39 | 40 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 41 | # config.force_ssl = true 42 | 43 | # Include generic and useful information about system operation, but avoid logging too much 44 | # information to avoid inadvertent exposure of personally identifiable information (PII). 45 | config.log_level = :info 46 | 47 | # Prepend all log lines with the following tags. 48 | config.log_tags = [ :request_id ] 49 | 50 | # Use a different cache store in production. 51 | # config.cache_store = :mem_cache_store 52 | 53 | 54 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 55 | # the I18n.default_locale when a translation cannot be found). 56 | config.i18n.fallbacks = true 57 | 58 | # Don't log any deprecations. 59 | config.active_support.report_deprecations = false 60 | 61 | # Use default logging formatter so that PID and timestamp are not suppressed. 62 | config.log_formatter = ::Logger::Formatter.new 63 | 64 | # Use a different logger for distributed setups. 65 | # require "syslog/logger" 66 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") 67 | 68 | if ENV["RAILS_LOG_TO_STDOUT"].present? 69 | logger = ActiveSupport::Logger.new(STDOUT) 70 | logger.formatter = config.log_formatter 71 | config.logger = ActiveSupport::TaggedLogging.new(logger) 72 | end 73 | 74 | # Inserts middleware to perform automatic connection switching. 75 | # The `database_selector` hash is used to pass options to the DatabaseSelector 76 | # middleware. The `delay` is used to determine how long to wait after a write 77 | # to send a subsequent read to the primary. 78 | # 79 | # The `database_resolver` class is used by the middleware to determine which 80 | # database is appropriate to use based on the time delay. 81 | # 82 | # The `database_resolver_context` class is used by the middleware to set 83 | # timestamps for the last write to the primary. The resolver uses the context 84 | # class timestamps to determine how long to wait before reading from the 85 | # replica. 86 | # 87 | # By default Rails will store a last write timestamp in the session. The 88 | # DatabaseSelector middleware is designed as such you can define your own 89 | # strategy for connection switching and pass that into the middleware through 90 | # these configuration options. 91 | # config.active_record.database_selector = { delay: 2.seconds } 92 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 93 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 94 | end 95 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # Turn false under Spring and add config.action_view.cache_template_loading = true 12 | config.cache_classes = true 13 | 14 | # Do not eager load code on boot. This avoids loading your whole application 15 | # just for the purpose of running a single test. If you are using a tool that 16 | # preloads Rails for running tests, you may have to set it to true. 17 | config.eager_load = false 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Print deprecation notices to the stderr. 37 | config.active_support.deprecation = :stderr 38 | 39 | # Raise exceptions for disallowed deprecations. 40 | config.active_support.disallowed_deprecation = :raise 41 | 42 | # Tell Active Support which deprecation messages to disallow. 43 | config.active_support.disallowed_deprecation_warnings = [] 44 | 45 | # Raises error for missing translations. 46 | # config.i18n.raise_on_missing_translations = true 47 | 48 | # Annotate rendered view with file names. 49 | # config.action_view.annotate_rendered_view_with_filenames = true 50 | end 51 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | # # Specify URI for violation reports 15 | # # policy.report_uri "/csp-violation-report-endpoint" 16 | # end 17 | 18 | # If you are using UJS then enable automatic nonce generation 19 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 20 | 21 | # Set the nonce only to specific directives 22 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 23 | 24 | # Report CSP violations to a specified URI 25 | # For further information see the following documentation: 26 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 27 | # Rails.application.config.content_security_policy_report_only = true 28 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [ 5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 6 | ] 7 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # "true": "foo" 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /test/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 12 | # terminating a worker in development environments. 13 | # 14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 15 | 16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 17 | # 18 | port ENV.fetch("PORT") { 3000 } 19 | 20 | # Specifies the `environment` that Puma will run in. 21 | # 22 | environment ENV.fetch("RAILS_ENV") { "development" } 23 | 24 | # Specifies the `pidfile` that Puma will use. 25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 26 | 27 | # Specifies the number of `workers` to boot in clustered mode. 28 | # Workers are forked web server processes. If using threads and workers together 29 | # the concurrency of the application would be max `threads` * `workers`. 30 | # Workers do not work on JRuby or Windows (both of which do not support 31 | # processes). 32 | # 33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 34 | 35 | # Use the `preload_app!` method when specifying a `workers` number. 36 | # This directive tells Puma to first boot the application and load code 37 | # before forking the application. This takes advantage of Copy On Write 38 | # process behavior so workers use less memory. 39 | # 40 | # preload_app! 41 | 42 | # Allow puma to be restarted by `bin/rails restart` command. 43 | plugin :tmp_restart 44 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html 3 | 4 | # Almost every application defines a route for the root path ("/") at the top of this file. 5 | # root "articles#index" 6 | resources :templates, only: :create 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtbot/action_dispatch-testing-integration-capybara/b4811cb0a6f3e96630c379c06293e9b82f068bb4/test/dummy/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtbot/action_dispatch-testing-integration-capybara/b4811cb0a6f3e96630c379c06293e9b82f068bb4/test/dummy/public/apple-touch-icon.png -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtbot/action_dispatch-testing-integration-capybara/b4811cb0a6f3e96630c379c06293e9b82f068bb4/test/dummy/public/favicon.ico -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require_relative "../test/dummy/config/environment" 5 | require "rails/test_help" 6 | 7 | require "rails/test_unit/reporter" 8 | Rails::TestUnitReporter.executable = "bin/test" 9 | 10 | --------------------------------------------------------------------------------