actionpack 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +416 -255
  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 +21 -2
  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 +3 -3
  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)