actionpack 6.0.5.1 → 6.1.7.1

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +393 -253
  3. data/MIT-LICENSE +1 -2
  4. data/lib/abstract_controller/base.rb +35 -2
  5. data/lib/abstract_controller/callbacks.rb +2 -2
  6. data/lib/abstract_controller/collector.rb +4 -2
  7. data/lib/abstract_controller/helpers.rb +105 -90
  8. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  9. data/lib/abstract_controller/rendering.rb +9 -9
  10. data/lib/abstract_controller/translation.rb +8 -2
  11. data/lib/abstract_controller.rb +1 -0
  12. data/lib/action_controller/api.rb +2 -2
  13. data/lib/action_controller/base.rb +4 -2
  14. data/lib/action_controller/caching.rb +0 -1
  15. data/lib/action_controller/log_subscriber.rb +3 -3
  16. data/lib/action_controller/metal/conditional_get.rb +11 -3
  17. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  18. data/lib/action_controller/metal/cookies.rb +3 -1
  19. data/lib/action_controller/metal/data_streaming.rb +1 -1
  20. data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
  21. data/lib/action_controller/metal/exceptions.rb +33 -0
  22. data/lib/action_controller/metal/head.rb +7 -4
  23. data/lib/action_controller/metal/helpers.rb +11 -1
  24. data/lib/action_controller/metal/http_authentication.rb +5 -2
  25. data/lib/action_controller/metal/implicit_render.rb +1 -1
  26. data/lib/action_controller/metal/instrumentation.rb +11 -9
  27. data/lib/action_controller/metal/live.rb +10 -1
  28. data/lib/action_controller/metal/logging.rb +20 -0
  29. data/lib/action_controller/metal/mime_responds.rb +6 -2
  30. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  31. data/lib/action_controller/metal/params_wrapper.rb +14 -8
  32. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  33. data/lib/action_controller/metal/redirecting.rb +1 -1
  34. data/lib/action_controller/metal/rendering.rb +6 -0
  35. data/lib/action_controller/metal/request_forgery_protection.rb +1 -1
  36. data/lib/action_controller/metal/rescue.rb +1 -1
  37. data/lib/action_controller/metal/strong_parameters.rb +104 -16
  38. data/lib/action_controller/metal.rb +2 -2
  39. data/lib/action_controller/renderer.rb +23 -13
  40. data/lib/action_controller/test_case.rb +65 -56
  41. data/lib/action_controller.rb +2 -3
  42. data/lib/action_dispatch/http/cache.rb +18 -17
  43. data/lib/action_dispatch/http/content_security_policy.rb +6 -1
  44. data/lib/action_dispatch/http/filter_parameters.rb +1 -1
  45. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  46. data/lib/action_dispatch/http/headers.rb +3 -2
  47. data/lib/action_dispatch/http/mime_negotiation.rb +14 -8
  48. data/lib/action_dispatch/http/mime_type.rb +29 -16
  49. data/lib/action_dispatch/http/parameters.rb +1 -19
  50. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  51. data/lib/action_dispatch/http/request.rb +24 -8
  52. data/lib/action_dispatch/http/response.rb +17 -16
  53. data/lib/action_dispatch/http/url.rb +3 -2
  54. data/lib/action_dispatch/journey/formatter.rb +55 -30
  55. data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
  56. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  57. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
  58. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  59. data/lib/action_dispatch/journey/nodes/node.rb +4 -3
  60. data/lib/action_dispatch/journey/parser.rb +13 -13
  61. data/lib/action_dispatch/journey/parser.y +1 -1
  62. data/lib/action_dispatch/journey/path/pattern.rb +13 -18
  63. data/lib/action_dispatch/journey/route.rb +7 -18
  64. data/lib/action_dispatch/journey/router/utils.rb +6 -4
  65. data/lib/action_dispatch/journey/router.rb +26 -30
  66. data/lib/action_dispatch/journey/visitors.rb +1 -1
  67. data/lib/action_dispatch/journey.rb +0 -2
  68. data/lib/action_dispatch/middleware/actionable_exceptions.rb +1 -1
  69. data/lib/action_dispatch/middleware/cookies.rb +89 -46
  70. data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -15
  71. data/lib/action_dispatch/middleware/debug_view.rb +1 -1
  72. data/lib/action_dispatch/middleware/exception_wrapper.rb +28 -16
  73. data/lib/action_dispatch/middleware/host_authorization.rb +63 -14
  74. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  75. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  76. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  77. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  78. data/lib/action_dispatch/middleware/show_exceptions.rb +12 -0
  79. data/lib/action_dispatch/middleware/ssl.rb +12 -7
  80. data/lib/action_dispatch/middleware/stack.rb +19 -1
  81. data/lib/action_dispatch/middleware/static.rb +154 -93
  82. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  83. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  84. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  85. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
  86. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +100 -8
  87. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  88. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +21 -1
  89. data/lib/action_dispatch/railtie.rb +3 -2
  90. data/lib/action_dispatch/request/session.rb +2 -8
  91. data/lib/action_dispatch/request/utils.rb +26 -2
  92. data/lib/action_dispatch/routing/inspector.rb +8 -7
  93. data/lib/action_dispatch/routing/mapper.rb +102 -71
  94. data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
  95. data/lib/action_dispatch/routing/redirection.rb +4 -4
  96. data/lib/action_dispatch/routing/route_set.rb +49 -41
  97. data/lib/action_dispatch/system_test_case.rb +35 -24
  98. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  99. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  100. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  101. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  102. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  103. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  104. data/lib/action_dispatch/testing/assertions.rb +1 -1
  105. data/lib/action_dispatch/testing/integration.rb +40 -29
  106. data/lib/action_dispatch/testing/test_process.rb +32 -4
  107. data/lib/action_dispatch/testing/test_request.rb +3 -3
  108. data/lib/action_dispatch.rb +3 -2
  109. data/lib/action_pack/gem_version.rb +2 -2
  110. data/lib/action_pack.rb +1 -1
  111. metadata +18 -19
  112. data/lib/action_controller/metal/force_ssl.rb +0 -58
  113. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  114. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  115. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  116. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -110,8 +110,10 @@ module ActionDispatch
110
110
  @url_helpers_module.undef_method url_name
111
111
  end
112
112
  routes[key] = route
113
- define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
114
- define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN
113
+
114
+ helper = UrlHelper.create(route, route.defaults, name)
115
+ define_url_helper @path_helpers_module, path_name, helper, PATH
116
+ define_url_helper @url_helpers_module, url_name, helper, UNKNOWN
115
117
 
116
118
  @path_helpers << path_name
117
119
  @url_helpers << url_name
@@ -169,30 +171,30 @@ module ActionDispatch
169
171
  end
170
172
 
171
173
  class UrlHelper
172
- def self.create(route, options, route_name, url_strategy)
174
+ def self.create(route, options, route_name)
173
175
  if optimize_helper?(route)
174
- OptimizedUrlHelper.new(route, options, route_name, url_strategy)
176
+ OptimizedUrlHelper.new(route, options, route_name)
175
177
  else
176
- new route, options, route_name, url_strategy
178
+ new(route, options, route_name)
177
179
  end
178
180
  end
179
181
 
180
182
  def self.optimize_helper?(route)
181
- !route.glob? && route.path.requirements.empty?
183
+ route.path.requirements.empty? && !route.glob?
182
184
  end
183
185
 
184
- attr_reader :url_strategy, :route_name
186
+ attr_reader :route_name
185
187
 
186
188
  class OptimizedUrlHelper < UrlHelper
187
189
  attr_reader :arg_size
188
190
 
189
- def initialize(route, options, route_name, url_strategy)
191
+ def initialize(route, options, route_name)
190
192
  super
191
193
  @required_parts = @route.required_parts
192
194
  @arg_size = @required_parts.size
193
195
  end
194
196
 
195
- def call(t, args, inner_options)
197
+ def call(t, method_name, args, inner_options, url_strategy)
196
198
  if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
197
199
  options = t.url_options.merge @options
198
200
  options[:path] = optimized_helper(args)
@@ -249,15 +251,14 @@ module ActionDispatch
249
251
  end
250
252
  end
251
253
 
252
- def initialize(route, options, route_name, url_strategy)
254
+ def initialize(route, options, route_name)
253
255
  @options = options
254
256
  @segment_keys = route.segment_keys.uniq
255
257
  @route = route
256
- @url_strategy = url_strategy
257
258
  @route_name = route_name
258
259
  end
259
260
 
260
- def call(t, args, inner_options)
261
+ def call(t, method_name, args, inner_options, url_strategy)
261
262
  controller_options = t.url_options
262
263
  options = controller_options.merge @options
263
264
  hash = handle_positional_args(controller_options,
@@ -266,7 +267,7 @@ module ActionDispatch
266
267
  options,
267
268
  @segment_keys)
268
269
 
269
- t._routes.url_for(hash, route_name, url_strategy)
270
+ t._routes.url_for(hash, route_name, url_strategy, method_name)
270
271
  end
271
272
 
272
273
  def handle_positional_args(controller_options, inner_options, args, result, path_params)
@@ -312,8 +313,7 @@ module ActionDispatch
312
313
  #
313
314
  # foo_url(bar, baz, bang, sort_by: 'baz')
314
315
  #
315
- def define_url_helper(mod, route, name, opts, route_key, url_strategy)
316
- helper = UrlHelper.create(route, opts, route_key, url_strategy)
316
+ def define_url_helper(mod, name, helper, url_strategy)
317
317
  mod.define_method(name) do |*args|
318
318
  last = args.last
319
319
  options = \
@@ -323,7 +323,7 @@ module ActionDispatch
323
323
  when ActionController::Parameters
324
324
  args.pop.to_h
325
325
  end
326
- helper.call self, args, options
326
+ helper.call(self, name, args, options, url_strategy)
327
327
  end
328
328
  end
329
329
  end
@@ -334,7 +334,7 @@ module ActionDispatch
334
334
 
335
335
  attr_accessor :formatter, :set, :named_routes, :default_scope, :router
336
336
  attr_accessor :disable_clear_and_finalize, :resources_path_names
337
- attr_accessor :default_url_options
337
+ attr_accessor :default_url_options, :draw_paths
338
338
  attr_reader :env_key, :polymorphic_mappings
339
339
 
340
340
  alias :routes :set
@@ -366,6 +366,7 @@ module ActionDispatch
366
366
  self.named_routes = NamedRouteCollection.new
367
367
  self.resources_path_names = self.class.default_resources_path_names
368
368
  self.default_url_options = {}
369
+ self.draw_paths = []
369
370
 
370
371
  @config = config
371
372
  @append = []
@@ -476,6 +477,14 @@ module ActionDispatch
476
477
  end
477
478
 
478
479
  def url_helpers(supports_path = true)
480
+ if supports_path
481
+ @url_helpers_with_paths ||= generate_url_helpers(true)
482
+ else
483
+ @url_helpers_without_paths ||= generate_url_helpers(false)
484
+ end
485
+ end
486
+
487
+ def generate_url_helpers(supports_path)
479
488
  routes = self
480
489
 
481
490
  Module.new do
@@ -588,14 +597,14 @@ module ActionDispatch
588
597
  if route.segment_keys.include?(:controller)
589
598
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
590
599
  Using a dynamic :controller segment in a route is deprecated and
591
- will be removed in Rails 6.1.
600
+ will be removed in Rails 7.0.
592
601
  MSG
593
602
  end
594
603
 
595
604
  if route.segment_keys.include?(:action)
596
605
  ActiveSupport::Deprecation.warn(<<-MSG.squish)
597
606
  Using a dynamic :action segment in a route is deprecated and
598
- will be removed in Rails 6.1.
607
+ will be removed in Rails 7.0.
599
608
  MSG
600
609
  end
601
610
 
@@ -641,14 +650,6 @@ module ActionDispatch
641
650
  end
642
651
 
643
652
  class Generator
644
- PARAMETERIZE = lambda do |name, value|
645
- if name == :controller
646
- value
647
- else
648
- value.to_param
649
- end
650
- end
651
-
652
653
  attr_reader :options, :recall, :set, :named_route
653
654
 
654
655
  def initialize(named_route, options, recall, set)
@@ -732,10 +733,10 @@ module ActionDispatch
732
733
  end
733
734
  end
734
735
 
735
- # Generates a path from routes, returns [path, params].
736
- # If no route is generated the formatter will raise ActionController::UrlGenerationError
736
+ # Generates a path from routes, returns a RouteWithParams or MissingRoute.
737
+ # MissingRoute will raise ActionController::UrlGenerationError.
737
738
  def generate
738
- @set.formatter.generate(named_route, options, recall, PARAMETERIZE)
739
+ @set.formatter.generate(named_route, options, recall)
739
740
  end
740
741
 
741
742
  def different_controller?
@@ -760,13 +761,18 @@ module ActionDispatch
760
761
  end
761
762
 
762
763
  def generate_extras(options, recall = {})
763
- route_key = options.delete :use_route
764
- path, params = generate(route_key, options, recall)
765
- return path, params.keys
764
+ if recall
765
+ options = options.merge(_recall: recall)
766
+ end
767
+
768
+ route_name = options.delete :use_route
769
+ generator = generate(route_name, options, recall)
770
+ path_info = path_for(options, route_name, [])
771
+ [URI(path_info).path, generator.params.except(:_recall).keys]
766
772
  end
767
773
 
768
- def generate(route_key, options, recall = {})
769
- Generator.new(route_key, options, recall, self).generate
774
+ def generate(route_name, options, recall = {}, method_name = nil)
775
+ Generator.new(route_name, options, recall, self).generate
770
776
  end
771
777
  private :generate
772
778
 
@@ -786,12 +792,12 @@ module ActionDispatch
786
792
  options.delete(:relative_url_root) || relative_url_root
787
793
  end
788
794
 
789
- def path_for(options, route_name = nil)
790
- url_for(options, route_name, PATH)
795
+ def path_for(options, route_name = nil, reserved = RESERVED_OPTIONS)
796
+ url_for(options, route_name, PATH, nil, reserved)
791
797
  end
792
798
 
793
799
  # The +options+ argument must be a hash whose keys are *symbols*.
794
- def url_for(options, route_name = nil, url_strategy = UNKNOWN)
800
+ def url_for(options, route_name = nil, url_strategy = UNKNOWN, method_name = nil, reserved = RESERVED_OPTIONS)
795
801
  options = default_url_options.merge options
796
802
 
797
803
  user = password = nil
@@ -811,9 +817,11 @@ module ActionDispatch
811
817
  end
812
818
 
813
819
  path_options = options.dup
814
- RESERVED_OPTIONS.each { |ro| path_options.delete ro }
820
+ reserved.each { |ro| path_options.delete ro }
815
821
 
816
- path, params = generate(route_name, path_options, recall)
822
+ route_with_params = generate(route_name, path_options, recall)
823
+ path = route_with_params.path(method_name)
824
+ params = route_with_params.params
817
825
 
818
826
  if options.key? :params
819
827
  params.merge! options[:params]
@@ -855,7 +863,7 @@ module ActionDispatch
855
863
  params.each do |key, value|
856
864
  if value.is_a?(String)
857
865
  value = value.dup.force_encoding(Encoding::BINARY)
858
- params[key] = URI.parser.unescape(value)
866
+ params[key] = URI::DEFAULT_PARSER.unescape(value)
859
867
  end
860
868
  end
861
869
  req.path_parameters = params
@@ -1,10 +1,9 @@
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"
7
- require "selenium/webdriver"
8
7
  require "action_controller"
9
8
  require "action_dispatch/system_testing/driver"
10
9
  require "action_dispatch/system_testing/browser"
@@ -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
@@ -116,22 +115,12 @@ module ActionDispatch
116
115
  include SystemTesting::TestHelpers::SetupAndTeardown
117
116
  include SystemTesting::TestHelpers::ScreenshotHelper
118
117
 
118
+ DEFAULT_HOST = "http://127.0.0.1"
119
+
119
120
  def initialize(*) # :nodoc:
120
121
  super
121
122
  self.class.driven_by(:selenium) unless self.class.driver?
122
123
  self.class.driver.use
123
- @proxy_route = if ActionDispatch.test_app
124
- Class.new do
125
- include ActionDispatch.test_app.routes.url_helpers
126
- include ActionDispatch.test_app.routes.mounted_helpers
127
-
128
- def url_options
129
- default_url_options.merge(host: Capybara.app_host)
130
- end
131
- end.new
132
- else
133
- nil
134
- end
135
124
  end
136
125
 
137
126
  def self.start_application # :nodoc:
@@ -170,16 +159,38 @@ module ActionDispatch
170
159
  self.driver = SystemTesting::Driver.new(driver, **driver_options, &capabilities)
171
160
  end
172
161
 
173
- def method_missing(method, *args, &block)
174
- if @proxy_route.respond_to?(method)
175
- @proxy_route.send(method, *args, &block)
176
- else
177
- super
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
169
+
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
180
+
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
178
187
  end
179
- end
180
188
 
181
- ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self)
189
+ def respond_to_missing?(name, include_private = false)
190
+ url_helpers.respond_to?(name)
191
+ end
182
192
  end
183
-
184
- SystemTestCase.start_application
185
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,23 +21,9 @@ 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
29
- end
30
- end
31
-
32
- def capabilities
33
- @option ||=
34
- case type
35
- when :chrome
36
- ::Selenium::WebDriver::Chrome::Options.new
37
- when :firefox
38
- ::Selenium::WebDriver::Firefox::Options.new
39
- end
24
+ def configure
25
+ initialize_options
26
+ yield options if block_given? && options
40
27
  end
41
28
 
42
29
  # driver_path can be configured as a proc. The webdrivers gem uses this
@@ -47,14 +34,14 @@ module ActionDispatch
47
34
  case type
48
35
  when :chrome
49
36
  if ::Selenium::WebDriver::Service.respond_to? :driver_path=
50
- ::Selenium::WebDriver::Chrome::Service.driver_path.try(:call)
37
+ ::Selenium::WebDriver::Chrome::Service.driver_path&.call
51
38
  else
52
39
  # Selenium <= v3.141.0
53
40
  ::Selenium::WebDriver::Chrome.driver_path
54
41
  end
55
42
  when :firefox
56
43
  if ::Selenium::WebDriver::Service.respond_to? :driver_path=
57
- ::Selenium::WebDriver::Firefox::Service.driver_path.try(:call)
44
+ ::Selenium::WebDriver::Firefox::Service.driver_path&.call
58
45
  else
59
46
  # Selenium <= v3.141.0
60
47
  ::Selenium::WebDriver::Firefox.driver_path
@@ -63,17 +50,36 @@ module ActionDispatch
63
50
  end
64
51
 
65
52
  private
66
- def headless_chrome_browser_options
67
- capabilities.add_argument("--headless")
68
- capabilities.add_argument("--disable-gpu") if Gem.win_platform?
53
+ def initialize_options
54
+ @options ||=
55
+ case type
56
+ when :chrome
57
+ ::Selenium::WebDriver::Chrome::Options.new
58
+ when :firefox
59
+ ::Selenium::WebDriver::Firefox::Options.new
60
+ end
61
+ end
69
62
 
70
- capabilities
63
+ def set_default_options
64
+ case name
65
+ when :headless_chrome
66
+ set_headless_chrome_browser_options
67
+ when :headless_firefox
68
+ set_headless_firefox_browser_options
69
+ end
71
70
  end
72
71
 
73
- def headless_firefox_browser_options
74
- capabilities.add_argument("-headless")
72
+ def set_headless_chrome_browser_options
73
+ configure do |capabilities|
74
+ capabilities.add_argument("--headless")
75
+ capabilities.add_argument("--disable-gpu") if Gem.win_platform?
76
+ end
77
+ end
75
78
 
76
- capabilities
79
+ def set_headless_firefox_browser_options
80
+ configure do |capabilities|
81
+ capabilities.add_argument("-headless")
82
+ end
77
83
  end
78
84
  end
79
85
  end
@@ -7,10 +7,13 @@ module ActionDispatch
7
7
  @name = name
8
8
  @browser = Browser.new(options[:using])
9
9
  @screen_size = options[:screen_size]
10
- @options = options[:options]
10
+ @options = options[:options] || {}
11
11
  @capabilities = capabilities
12
12
 
13
- @browser.preload unless name == :rack_test
13
+ if name == :selenium
14
+ require "selenium/webdriver"
15
+ @browser.preload
16
+ end
14
17
  end
15
18
 
16
19
  def use
@@ -25,7 +28,7 @@ module ActionDispatch
25
28
  end
26
29
 
27
30
  def register
28
- define_browser_capabilities(@browser.capabilities)
31
+ @browser.configure(&@capabilities)
29
32
 
30
33
  Capybara.register_driver @name do |app|
31
34
  case @name
@@ -36,10 +39,6 @@ module ActionDispatch
36
39
  end
37
40
  end
38
41
 
39
- def define_browser_capabilities(capabilities)
40
- @capabilities.call(capabilities) if @capabilities
41
- end
42
-
43
42
  def browser_options
44
43
  @options.merge(options: @browser.options).compact
45
44
  end
@@ -9,10 +9,16 @@ 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
+ # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+ environment variable to
19
+ # save the HTML from the page that is being screenhoted so you can investigate the
20
+ # elements on the page at the time of the screenshot
21
+ #
16
22
  # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
17
23
  # control the output. Possible values are:
18
24
  # * [+simple+ (default)] Only displays the screenshot path.
@@ -22,6 +28,8 @@ module ActionDispatch
22
28
  # * [+artifact+] Display the screenshot in the terminal, using the terminal
23
29
  # artifact format (https://buildkite.github.io/terminal-to-html/inline-images/).
24
30
  def take_screenshot
31
+ increment_unique
32
+ save_html if save_html?
25
33
  save_image
26
34
  puts display_image
27
35
  end
@@ -38,17 +46,49 @@ module ActionDispatch
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
- name = method_name[0...225]
43
- failed? ? "failures_#{name}" : name
65
+ sanitized_method_name = method_name.tr("/\\", "--")
66
+ name = "#{unique}_#{sanitized_method_name}"
67
+ name[0...225]
44
68
  end
45
69
 
46
70
  def image_path
47
- @image_path ||= absolute_image_path.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("tmp/screenshots/#{image_name}")
48
80
  end
49
81
 
50
82
  def absolute_image_path
51
- Rails.root.join("tmp/screenshots/#{image_name}.png")
83
+ "#{absolute_path}.png"
84
+ end
85
+
86
+ def absolute_html_path
87
+ "#{absolute_path}.html"
88
+ end
89
+
90
+ def save_html
91
+ page.save_page(absolute_html_path)
52
92
  end
53
93
 
54
94
  def save_image
@@ -66,7 +106,8 @@ module ActionDispatch
66
106
  end
67
107
 
68
108
  def display_image
69
- message = +"[Screenshot]: #{image_path}\n"
109
+ message = +"[Screenshot Image]: #{image_path}\n"
110
+ message << +"[Screenshot HTML]: #{html_path}\n" if save_html?
70
111
 
71
112
  case output_type
72
113
  when "artifact"
@@ -4,15 +4,12 @@ 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
7
  def host!(host)
10
- Capybara.app_host = host
11
- end
8
+ ActiveSupport::Deprecation.warn \
9
+ "ActionDispatch::SystemTestCase#host! is deprecated with no replacement. " \
10
+ "Set Capybara.app_host directly or rely on Capybara's default host."
12
11
 
13
- def before_setup
14
- host! DEFAULT_HOST
15
- super
12
+ Capybara.app_host = host
16
13
  end
17
14
 
18
15
  def before_teardown
@@ -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"
@@ -89,9 +89,8 @@ module ActionDispatch
89
89
  expected_path = uri.path.to_s.empty? ? "/" : uri.path
90
90
  end
91
91
  else
92
- expected_path = "/#{expected_path}" unless expected_path.first == "/"
92
+ expected_path = "/#{expected_path}" unless expected_path.start_with?("/")
93
93
  end
94
- # Load routes.rb if it hasn't been loaded.
95
94
 
96
95
  options = options.clone
97
96
  generated_path, query_string_keys = @routes.generate_extras(options, defaults)
@@ -183,7 +182,7 @@ module ActionDispatch
183
182
  # ROUTES TODO: These assertions should really work in an integration context
184
183
  def method_missing(selector, *args, &block)
185
184
  if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector)
186
- @controller.send(selector, *args, &block)
185
+ @controller.public_send(selector, *args, &block)
187
186
  else
188
187
  super
189
188
  end
@@ -199,7 +198,8 @@ module ActionDispatch
199
198
  method = :get
200
199
  end
201
200
 
202
- request = ActionController::TestRequest.create @controller.class
201
+ controller = @controller if defined?(@controller)
202
+ request = ActionController::TestRequest.create controller&.class
203
203
 
204
204
  if %r{://}.match?(path)
205
205
  fail_on(URI::InvalidURIError, msg) do
@@ -210,7 +210,7 @@ module ActionDispatch
210
210
  request.path = uri.path.to_s.empty? ? "/" : uri.path
211
211
  end
212
212
  else
213
- path = "/#{path}" unless path.first == "/"
213
+ path = "/#{path}" unless path.start_with?("/")
214
214
  request.path = path
215
215
  end
216
216
 
@@ -14,7 +14,7 @@ module ActionDispatch
14
14
  include Rails::Dom::Testing::Assertions
15
15
 
16
16
  def html_document
17
- @html_document ||= if @response.media_type.to_s.end_with?("xml")
17
+ @html_document ||= if @response.media_type&.end_with?("xml")
18
18
  Nokogiri::XML::Document.parse(@response.body)
19
19
  else
20
20
  Nokogiri::HTML::Document.parse(@response.body)