actionpack 5.2.1 → 7.0.2.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -74,8 +74,8 @@ module ActionDispatch
74
74
  # For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper
75
75
  # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
76
76
  #
77
- # get 'post/:id' => 'posts#show'
78
- # post 'post/:id' => 'posts#create_comment'
77
+ # get 'post/:id', to: 'posts#show'
78
+ # post 'post/:id', to: 'posts#create_comment'
79
79
  #
80
80
  # Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
81
81
  # URL will route to the <tt>show</tt> action.
@@ -83,7 +83,7 @@ module ActionDispatch
83
83
  # If your route needs to respond to more than one HTTP method (or all methods) then using the
84
84
  # <tt>:via</tt> option on <tt>match</tt> is preferable.
85
85
  #
86
- # match 'post/:id' => 'posts#show', via: [:get, :post]
86
+ # match 'post/:id', to: 'posts#show', via: [:get, :post]
87
87
  #
88
88
  # == Named routes
89
89
  #
@@ -94,7 +94,7 @@ module ActionDispatch
94
94
  # Example:
95
95
  #
96
96
  # # In config/routes.rb
97
- # get '/login' => 'accounts#login', as: 'login'
97
+ # get '/login', to: 'accounts#login', as: 'login'
98
98
  #
99
99
  # # With render, redirect_to, tests, etc.
100
100
  # redirect_to login_url
@@ -120,9 +120,9 @@ module ActionDispatch
120
120
  #
121
121
  # # In config/routes.rb
122
122
  # controller :blog do
123
- # get 'blog/show' => :list
124
- # get 'blog/delete' => :delete
125
- # get 'blog/edit' => :edit
123
+ # get 'blog/show', to: :list
124
+ # get 'blog/delete', to: :delete
125
+ # get 'blog/edit', to: :edit
126
126
  # end
127
127
  #
128
128
  # # provides named routes for show, delete, and edit
@@ -132,7 +132,7 @@ module ActionDispatch
132
132
  #
133
133
  # Routes can generate pretty URLs. For example:
134
134
  #
135
- # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
135
+ # get '/articles/:year/:month/:day', to: 'articles#find_by_id', constraints: {
136
136
  # year: /\d{4}/,
137
137
  # month: /\d{1,2}/,
138
138
  # day: /\d{1,2}/
@@ -147,7 +147,7 @@ module ActionDispatch
147
147
  # You can specify a regular expression to define a format for a parameter.
148
148
  #
149
149
  # controller 'geocode' do
150
- # get 'geocode/:postalcode' => :show, constraints: {
150
+ # get 'geocode/:postalcode', to: :show, constraints: {
151
151
  # postalcode: /\d{5}(-\d{4})?/
152
152
  # }
153
153
  # end
@@ -156,13 +156,13 @@ module ActionDispatch
156
156
  # expression modifiers:
157
157
  #
158
158
  # controller 'geocode' do
159
- # get 'geocode/:postalcode' => :show, constraints: {
159
+ # get 'geocode/:postalcode', to: :show, constraints: {
160
160
  # postalcode: /hx\d\d\s\d[a-z]{2}/i
161
161
  # }
162
162
  # end
163
163
  #
164
164
  # controller 'geocode' do
165
- # get 'geocode/:postalcode' => :show, constraints: {
165
+ # get 'geocode/:postalcode', to: :show, constraints: {
166
166
  # postalcode: /# Postalcode format
167
167
  # \d{5} #Prefix
168
168
  # (-\d{4})? #Suffix
@@ -178,13 +178,13 @@ module ActionDispatch
178
178
  #
179
179
  # You can redirect any path to another path using the redirect helper in your router:
180
180
  #
181
- # get "/stories" => redirect("/posts")
181
+ # get "/stories", to: redirect("/posts")
182
182
  #
183
183
  # == Unicode character routes
184
184
  #
185
185
  # You can specify unicode character routes in your router:
186
186
  #
187
- # get "こんにちは" => "welcome#index"
187
+ # get "こんにちは", to: "welcome#index"
188
188
  #
189
189
  # == Routing to Rack Applications
190
190
  #
@@ -192,7 +192,7 @@ module ActionDispatch
192
192
  # index action in the PostsController, you can specify any Rack application
193
193
  # as the endpoint for a matcher:
194
194
  #
195
- # get "/application.js" => Sprockets
195
+ # get "/application.js", to: Sprockets
196
196
  #
197
197
  # == Reloading routes
198
198
  #
@@ -210,8 +210,8 @@ module ActionDispatch
210
210
  # === +assert_routing+
211
211
  #
212
212
  # def test_movie_route_properly_splits
213
- # opts = {controller: "plugin", action: "checkout", id: "2"}
214
- # assert_routing "plugin/checkout/2", opts
213
+ # opts = {controller: "plugin", action: "checkout", id: "2"}
214
+ # assert_routing "plugin/checkout/2", opts
215
215
  # end
216
216
  #
217
217
  # +assert_routing+ lets you test whether or not the route properly resolves into options.
@@ -219,8 +219,8 @@ module ActionDispatch
219
219
  # === +assert_recognizes+
220
220
  #
221
221
  # def test_route_has_options
222
- # opts = {controller: "plugin", action: "show", id: "12"}
223
- # assert_recognizes opts, "/plugins/show/12"
222
+ # opts = {controller: "plugin", action: "show", id: "12"}
223
+ # assert_recognizes opts, "/plugins/show/12"
224
224
  # end
225
225
  #
226
226
  # Note the subtle difference between the two: +assert_routing+ tests that
@@ -243,8 +243,9 @@ module ActionDispatch
243
243
  #
244
244
  # rails routes
245
245
  #
246
- # Target specific controllers by prefixing the command with <tt>-c</tt> option.
247
- #
246
+ # Target a specific controller with <tt>-c</tt>, or grep routes
247
+ # using <tt>-g</tt>. Useful in conjunction with <tt>--expanded</tt>
248
+ # which displays routes vertically.
248
249
  module Routing
249
250
  extend ActiveSupport::Autoload
250
251
 
@@ -254,7 +255,7 @@ module ActionDispatch
254
255
  autoload :UrlFor
255
256
  autoload :PolymorphicRoutes
256
257
 
257
- SEPARATORS = %w( / . ? ) #:nodoc:
258
- HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
258
+ SEPARATORS = %w( / . ? ) # :nodoc:
259
+ HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] # :nodoc:
259
260
  end
260
261
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- gem "capybara", ">= 2.15"
3
+ gem "capybara", ">= 3.26"
4
4
 
5
5
  require "capybara/dsl"
6
6
  require "capybara/minitest"
@@ -10,7 +10,6 @@ require "action_dispatch/system_testing/browser"
10
10
  require "action_dispatch/system_testing/server"
11
11
  require "action_dispatch/system_testing/test_helpers/screenshot_helper"
12
12
  require "action_dispatch/system_testing/test_helpers/setup_and_teardown"
13
- require "action_dispatch/system_testing/test_helpers/undef_methods"
14
13
 
15
14
  module ActionDispatch
16
15
  # = System Testing
@@ -27,7 +26,7 @@ module ActionDispatch
27
26
  #
28
27
  # Here is an example system test:
29
28
  #
30
- # require 'application_system_test_case'
29
+ # require "application_system_test_case"
31
30
  #
32
31
  # class Users::CreateTest < ApplicationSystemTestCase
33
32
  # test "adding a new user" do
@@ -73,8 +72,8 @@ module ActionDispatch
73
72
  # Headless browsers such as headless Chrome and headless Firefox are also supported.
74
73
  # You can use these browsers by setting the +:using+ argument to +:headless_chrome+ or +:headless_firefox+.
75
74
  #
76
- # To use a headless driver, like Poltergeist, update your Gemfile to use
77
- # Poltergeist instead of Selenium and then declare the driver name in the
75
+ # To use a headless driver, like Cuprite, update your Gemfile to use
76
+ # Cuprite instead of Selenium and then declare the driver name in the
78
77
  # +application_system_test_case.rb+ file. In this case, you would leave out
79
78
  # the +:using+ option because the driver is headless, but you can still use
80
79
  # +:screen_size+ to change the size of the browser screen, also you can use
@@ -82,25 +81,45 @@ module ActionDispatch
82
81
  # driver documentation to learn about supported options.
83
82
  #
84
83
  # require "test_helper"
85
- # require "capybara/poltergeist"
84
+ # require "capybara/cuprite"
86
85
  #
87
86
  # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
88
- # driven_by :poltergeist, screen_size: [1400, 1400], options:
87
+ # driven_by :cuprite, screen_size: [1400, 1400], options:
89
88
  # { js_errors: true }
90
89
  # end
91
90
  #
91
+ # Some drivers require browser capabilities to be passed as a block instead
92
+ # of through the +options+ hash.
93
+ #
94
+ # As an example, if you want to add mobile emulation on chrome, you'll have to
95
+ # create an instance of selenium's +Chrome::Options+ object and add
96
+ # capabilities with a block.
97
+ #
98
+ # The block will be passed an instance of <tt><Driver>::Options</tt> where you can
99
+ # define the capabilities you want. Please refer to your driver documentation
100
+ # to learn about supported options.
101
+ #
102
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
103
+ # driven_by :selenium, using: :chrome, screen_size: [1024, 768] do |driver_option|
104
+ # driver_option.add_emulation(device_name: 'iPhone 6')
105
+ # driver_option.add_extension('path/to/chrome_extension.crx')
106
+ # end
107
+ # end
108
+ #
92
109
  # Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara
93
110
  # and Rails, any driver that is supported by Capybara is supported by system
94
111
  # tests as long as you include the required gems and files.
95
- class SystemTestCase < IntegrationTest
112
+ class SystemTestCase < ActiveSupport::TestCase
96
113
  include Capybara::DSL
97
114
  include Capybara::Minitest::Assertions
98
115
  include SystemTesting::TestHelpers::SetupAndTeardown
99
116
  include SystemTesting::TestHelpers::ScreenshotHelper
100
- include SystemTesting::TestHelpers::UndefMethods
117
+
118
+ DEFAULT_HOST = "http://127.0.0.1"
101
119
 
102
120
  def initialize(*) # :nodoc:
103
121
  super
122
+ self.class.driven_by(:selenium) unless self.class.driver?
104
123
  self.class.driver.use
105
124
  end
106
125
 
@@ -123,7 +142,7 @@ module ActionDispatch
123
142
  #
124
143
  # Examples:
125
144
  #
126
- # driven_by :poltergeist
145
+ # driven_by :cuprite
127
146
  #
128
147
  # driven_by :selenium, screen_size: [800, 800]
129
148
  #
@@ -134,14 +153,44 @@ module ActionDispatch
134
153
  # driven_by :selenium, using: :firefox
135
154
  #
136
155
  # driven_by :selenium, using: :headless_firefox
137
- def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {})
138
- self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options)
156
+ def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}, &capabilities)
157
+ driver_options = { using: using, screen_size: screen_size, options: options }
158
+
159
+ self.driver = SystemTesting::Driver.new(driver, **driver_options, &capabilities)
139
160
  end
140
161
 
141
- driven_by :selenium
162
+ private
163
+ def url_helpers
164
+ @url_helpers ||=
165
+ if ActionDispatch.test_app
166
+ Class.new do
167
+ include ActionDispatch.test_app.routes.url_helpers
168
+ include ActionDispatch.test_app.routes.mounted_helpers
142
169
 
143
- ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self)
144
- end
170
+ def url_options
171
+ default_url_options.reverse_merge(host: app_host)
172
+ end
173
+
174
+ def app_host
175
+ Capybara.app_host || Capybara.current_session.server_url || DEFAULT_HOST
176
+ end
177
+ end.new
178
+ end
179
+ end
145
180
 
146
- SystemTestCase.start_application
181
+ def method_missing(name, *args, &block)
182
+ if url_helpers.respond_to?(name)
183
+ url_helpers.public_send(name, *args, &block)
184
+ else
185
+ super
186
+ end
187
+ end
188
+
189
+ def respond_to_missing?(name, include_private = false)
190
+ url_helpers.respond_to?(name)
191
+ end
192
+ end
147
193
  end
194
+
195
+ ActiveSupport.run_load_hooks :action_dispatch_system_test_case, ActionDispatch::SystemTestCase
196
+ ActionDispatch::SystemTestCase.start_application
@@ -3,10 +3,11 @@
3
3
  module ActionDispatch
4
4
  module SystemTesting
5
5
  class Browser # :nodoc:
6
- attr_reader :name
6
+ attr_reader :name, :options
7
7
 
8
8
  def initialize(name)
9
9
  @name = name
10
+ set_default_options
10
11
  end
11
12
 
12
13
  def type
@@ -20,29 +21,55 @@ module ActionDispatch
20
21
  end
21
22
  end
22
23
 
23
- def options
24
- case name
25
- when :headless_chrome
26
- headless_chrome_browser_options
27
- when :headless_firefox
28
- headless_firefox_browser_options
24
+ def configure
25
+ initialize_options
26
+ yield options if block_given? && options
27
+ end
28
+
29
+ # driver_path can be configured as a proc. The webdrivers gem uses this
30
+ # proc to update web drivers. Running this proc early allows us to only
31
+ # update the webdriver once and avoid race conditions when using
32
+ # parallel tests.
33
+ def preload
34
+ case type
35
+ when :chrome
36
+ ::Selenium::WebDriver::Chrome::Service.driver_path&.call
37
+ when :firefox
38
+ ::Selenium::WebDriver::Firefox::Service.driver_path&.call
29
39
  end
30
40
  end
31
41
 
32
42
  private
33
- def headless_chrome_browser_options
34
- options = Selenium::WebDriver::Chrome::Options.new
35
- options.args << "--headless"
36
- options.args << "--disable-gpu" if Gem.win_platform?
43
+ def initialize_options
44
+ @options ||=
45
+ case type
46
+ when :chrome
47
+ ::Selenium::WebDriver::Chrome::Options.new
48
+ when :firefox
49
+ ::Selenium::WebDriver::Firefox::Options.new
50
+ end
51
+ end
37
52
 
38
- options
53
+ def set_default_options
54
+ case name
55
+ when :headless_chrome
56
+ set_headless_chrome_browser_options
57
+ when :headless_firefox
58
+ set_headless_firefox_browser_options
59
+ end
39
60
  end
40
61
 
41
- def headless_firefox_browser_options
42
- options = Selenium::WebDriver::Firefox::Options.new
43
- options.args << "-headless"
62
+ def set_headless_chrome_browser_options
63
+ configure do |capabilities|
64
+ capabilities.add_argument("--headless")
65
+ capabilities.add_argument("--disable-gpu") if Gem.win_platform?
66
+ end
67
+ end
44
68
 
45
- options
69
+ def set_headless_firefox_browser_options
70
+ configure do |capabilities|
71
+ capabilities.add_argument("-headless")
72
+ end
46
73
  end
47
74
  end
48
75
  end
@@ -3,11 +3,31 @@
3
3
  module ActionDispatch
4
4
  module SystemTesting
5
5
  class Driver # :nodoc:
6
- def initialize(name, **options)
7
- @name = name
8
- @browser = Browser.new(options[:using])
6
+ attr_reader :name
7
+
8
+ def initialize(driver_type, **options, &capabilities)
9
+ @driver_type = driver_type
9
10
  @screen_size = options[:screen_size]
10
- @options = options[:options]
11
+ @options = options[:options] || {}
12
+ @name = @options.delete(:name) || driver_type
13
+ @capabilities = capabilities
14
+
15
+ if [:poltergeist, :webkit].include?(driver_type)
16
+ ActiveSupport::Deprecation.warn <<~MSG.squish
17
+ Poltergeist and capybara-webkit are not maintained already.
18
+ Driver registration of :poltergeist or :webkit is deprecated and will be removed in Rails 7.1.
19
+ You can still use :selenium, and also :cuprite is available for alternative to Poltergeist.
20
+ MSG
21
+ end
22
+
23
+ if driver_type == :selenium
24
+ gem "selenium-webdriver", ">= 4.0.0"
25
+ require "selenium/webdriver"
26
+ @browser = Browser.new(options[:using])
27
+ @browser.preload
28
+ else
29
+ @browser = nil
30
+ end
11
31
  end
12
32
 
13
33
  def use
@@ -18,25 +38,29 @@ module ActionDispatch
18
38
 
19
39
  private
20
40
  def registerable?
21
- [:selenium, :poltergeist, :webkit].include?(@name)
41
+ [:selenium, :poltergeist, :webkit, :cuprite, :rack_test].include?(@driver_type)
22
42
  end
23
43
 
24
44
  def register
25
- Capybara.register_driver @name do |app|
26
- case @name
45
+ @browser&.configure(&@capabilities)
46
+
47
+ Capybara.register_driver name do |app|
48
+ case @driver_type
27
49
  when :selenium then register_selenium(app)
28
50
  when :poltergeist then register_poltergeist(app)
29
51
  when :webkit then register_webkit(app)
52
+ when :cuprite then register_cuprite(app)
53
+ when :rack_test then register_rack_test(app)
30
54
  end
31
55
  end
32
56
  end
33
57
 
34
58
  def browser_options
35
- @options.merge(options: @browser.options).compact
59
+ @options.merge(capabilities: @browser.options).compact
36
60
  end
37
61
 
38
62
  def register_selenium(app)
39
- Capybara::Selenium::Driver.new(app, { browser: @browser.type }.merge(browser_options)).tap do |driver|
63
+ Capybara::Selenium::Driver.new(app, browser: @browser.type, **browser_options).tap do |driver|
40
64
  driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
41
65
  end
42
66
  end
@@ -51,8 +75,16 @@ module ActionDispatch
51
75
  end
52
76
  end
53
77
 
78
+ def register_cuprite(app)
79
+ Capybara::Cuprite::Driver.new(app, @options.merge(window_size: @screen_size))
80
+ end
81
+
82
+ def register_rack_test(app)
83
+ Capybara::RackTest::Driver.new(app, respect_data_method: true, **@options)
84
+ end
85
+
54
86
  def setup
55
- Capybara.current_driver = @name
87
+ Capybara.current_driver = name
56
88
  end
57
89
  end
58
90
  end
@@ -9,10 +9,19 @@ module ActionDispatch
9
9
  #
10
10
  # +take_screenshot+ can be used at any point in your system tests to take
11
11
  # a screenshot of the current state. This can be useful for debugging or
12
- # automating visual testing.
12
+ # automating visual testing. You can take multiple screenshots per test
13
+ # to investigate changes at different points during your test. These will be
14
+ # named with a sequential prefix (or 'failed' for failing tests)
13
15
  #
14
16
  # The screenshot will be displayed in your console, if supported.
15
17
  #
18
+ # The default screenshots directory is +tmp/screenshots+ but you can set a different
19
+ # one with +Capybara.save_path+
20
+ #
21
+ # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+ environment variable to
22
+ # save the HTML from the page that is being screenshotted so you can investigate the
23
+ # elements on the page at the time of the screenshot
24
+ #
16
25
  # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
17
26
  # control the output. Possible values are:
18
27
  # * [+simple+ (default)] Only displays the screenshot path.
@@ -20,8 +29,10 @@ module ActionDispatch
20
29
  # * [+inline+] Display the screenshot in the terminal using the
21
30
  # iTerm image protocol (https://iterm2.com/documentation-images.html).
22
31
  # * [+artifact+] Display the screenshot in the terminal, using the terminal
23
- # artifact format (https://buildkite.github.io/terminal/inline-images/).
32
+ # artifact format (https://buildkite.github.io/terminal-to-html/inline-images/).
24
33
  def take_screenshot
34
+ increment_unique
35
+ save_html if save_html?
25
36
  save_image
26
37
  puts display_image
27
38
  end
@@ -29,25 +40,59 @@ module ActionDispatch
29
40
  # Takes a screenshot of the current page in the browser if the test
30
41
  # failed.
31
42
  #
32
- # +take_failed_screenshot+ is included in <tt>application_system_test_case.rb</tt>
33
- # that is generated with the application. To take screenshots when a test
34
- # fails add +take_failed_screenshot+ to the teardown block before clearing
35
- # sessions.
43
+ # +take_failed_screenshot+ is called during system test teardown.
36
44
  def take_failed_screenshot
37
- take_screenshot if failed? && supports_screenshot?
45
+ take_screenshot if failed? && supports_screenshot? && Capybara::Session.instance_created?
38
46
  end
39
47
 
40
48
  private
49
+ attr_accessor :_screenshot_counter
50
+
51
+ def save_html?
52
+ ENV["RAILS_SYSTEM_TESTING_SCREENSHOT_HTML"] == "1"
53
+ end
54
+
55
+ def increment_unique
56
+ @_screenshot_counter ||= 0
57
+ @_screenshot_counter += 1
58
+ end
59
+
60
+ def unique
61
+ failed? ? "failures" : (_screenshot_counter || 0).to_s
62
+ end
63
+
41
64
  def image_name
42
- failed? ? "failures_#{method_name}" : method_name
65
+ sanitized_method_name = method_name.tr("/\\", "--")
66
+ name = "#{unique}_#{sanitized_method_name}"
67
+ name[0...225]
43
68
  end
44
69
 
45
70
  def image_path
46
- @image_path ||= absolute_image_path.relative_path_from(Pathname.pwd).to_s
71
+ absolute_image_path.to_s
72
+ end
73
+
74
+ def html_path
75
+ absolute_html_path.to_s
76
+ end
77
+
78
+ def absolute_path
79
+ Rails.root.join(screenshots_dir, image_name)
80
+ end
81
+
82
+ def screenshots_dir
83
+ Capybara.save_path.presence || "tmp/screenshots"
47
84
  end
48
85
 
49
86
  def absolute_image_path
50
- Rails.root.join("tmp/screenshots/#{image_name}.png")
87
+ "#{absolute_path}.png"
88
+ end
89
+
90
+ def absolute_html_path
91
+ "#{absolute_path}.html"
92
+ end
93
+
94
+ def save_html
95
+ page.save_page(absolute_html_path)
51
96
  end
52
97
 
53
98
  def save_image
@@ -65,7 +110,8 @@ module ActionDispatch
65
110
  end
66
111
 
67
112
  def display_image
68
- message = "[Screenshot]: #{image_path}\n".dup
113
+ message = +"[Screenshot Image]: #{image_path}\n"
114
+ message << +"[Screenshot HTML]: #{html_path}\n" if save_html?
69
115
 
70
116
  case output_type
71
117
  when "artifact"
@@ -80,7 +126,7 @@ module ActionDispatch
80
126
  end
81
127
 
82
128
  def inline_base64(path)
83
- Base64.encode64(path).gsub("\n", "")
129
+ Base64.strict_encode64(path)
84
130
  end
85
131
 
86
132
  def failed?
@@ -4,20 +4,13 @@ module ActionDispatch
4
4
  module SystemTesting
5
5
  module TestHelpers
6
6
  module SetupAndTeardown # :nodoc:
7
- DEFAULT_HOST = "http://127.0.0.1"
8
-
9
- def host!(host)
10
- super
11
- Capybara.app_host = host
12
- end
13
-
14
- def before_setup
15
- host! DEFAULT_HOST
7
+ def before_teardown
8
+ take_failed_screenshot
9
+ ensure
16
10
  super
17
11
  end
18
12
 
19
13
  def after_teardown
20
- take_failed_screenshot
21
14
  Capybara.reset_sessions!
22
15
  ensure
23
16
  super
@@ -35,7 +35,6 @@ module ActionDispatch
35
35
  end
36
36
 
37
37
  private
38
-
39
38
  def code_from_name(name)
40
39
  GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
41
40
  end
@@ -31,15 +31,13 @@ module ActionDispatch
31
31
  message ||= generate_response_message(type)
32
32
 
33
33
  if RESPONSE_PREDICATES.keys.include?(type)
34
- assert @response.send(RESPONSE_PREDICATES[type]), message
34
+ assert @response.public_send(RESPONSE_PREDICATES[type]), message
35
35
  else
36
36
  assert_equal AssertionResponse.new(type).code, @response.response_code, message
37
37
  end
38
38
  end
39
39
 
40
- # Asserts that the redirection options passed in match those of the redirect called in the latest action.
41
- # This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
42
- # match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
40
+ # Asserts that the response is a redirect to a URL matching the given options.
43
41
  #
44
42
  # # Asserts that the redirection was to the "index" action on the WeblogController
45
43
  # assert_redirected_to controller: "weblog", action: "index"
@@ -79,9 +77,8 @@ module ActionDispatch
79
77
  end
80
78
 
81
79
  def generate_response_message(expected, actual = @response.response_code)
82
- "Expected response to be a <#{code_with_name(expected)}>,"\
83
- " but was a <#{code_with_name(actual)}>"
84
- .dup.concat(location_if_redirected).concat(response_body_if_short)
80
+ (+"Expected response to be a <#{code_with_name(expected)}>,"\
81
+ " but was a <#{code_with_name(actual)}>").concat(location_if_redirected).concat(response_body_if_short)
85
82
  end
86
83
 
87
84
  def response_body_if_short