actionpack 7.0.8 → 7.1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +360 -353
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/base.rb +20 -11
  6. data/lib/abstract_controller/caching/fragments.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +31 -6
  8. data/lib/abstract_controller/deprecator.rb +7 -0
  9. data/lib/abstract_controller/helpers.rb +61 -18
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +3 -3
  12. data/lib/abstract_controller/translation.rb +7 -4
  13. data/lib/abstract_controller/url_for.rb +2 -0
  14. data/lib/abstract_controller.rb +6 -0
  15. data/lib/action_controller/api.rb +5 -3
  16. data/lib/action_controller/base.rb +3 -17
  17. data/lib/action_controller/caching.rb +2 -0
  18. data/lib/action_controller/deprecator.rb +7 -0
  19. data/lib/action_controller/form_builder.rb +2 -0
  20. data/lib/action_controller/log_subscriber.rb +16 -4
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +2 -0
  23. data/lib/action_controller/metal/default_headers.rb +2 -0
  24. data/lib/action_controller/metal/etag_with_flash.rb +2 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  26. data/lib/action_controller/metal/exceptions.rb +8 -0
  27. data/lib/action_controller/metal/head.rb +8 -6
  28. data/lib/action_controller/metal/helpers.rb +3 -14
  29. data/lib/action_controller/metal/http_authentication.rb +17 -8
  30. data/lib/action_controller/metal/implicit_render.rb +5 -3
  31. data/lib/action_controller/metal/instrumentation.rb +8 -1
  32. data/lib/action_controller/metal/live.rb +24 -0
  33. data/lib/action_controller/metal/mime_responds.rb +2 -2
  34. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  35. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  36. data/lib/action_controller/metal/redirecting.rb +7 -7
  37. data/lib/action_controller/metal/renderers.rb +2 -2
  38. data/lib/action_controller/metal/rendering.rb +0 -7
  39. data/lib/action_controller/metal/request_forgery_protection.rb +139 -50
  40. data/lib/action_controller/metal/rescue.rb +2 -0
  41. data/lib/action_controller/metal/streaming.rb +70 -30
  42. data/lib/action_controller/metal/strong_parameters.rb +132 -52
  43. data/lib/action_controller/metal/url_for.rb +7 -0
  44. data/lib/action_controller/metal.rb +79 -21
  45. data/lib/action_controller/railtie.rb +22 -9
  46. data/lib/action_controller/renderer.rb +98 -65
  47. data/lib/action_controller/test_case.rb +15 -5
  48. data/lib/action_controller.rb +8 -1
  49. data/lib/action_dispatch/constants.rb +32 -0
  50. data/lib/action_dispatch/deprecator.rb +7 -0
  51. data/lib/action_dispatch/http/cache.rb +1 -3
  52. data/lib/action_dispatch/http/content_security_policy.rb +9 -8
  53. data/lib/action_dispatch/http/filter_parameters.rb +11 -5
  54. data/lib/action_dispatch/http/headers.rb +2 -0
  55. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  56. data/lib/action_dispatch/http/mime_type.rb +35 -12
  57. data/lib/action_dispatch/http/mime_types.rb +3 -1
  58. data/lib/action_dispatch/http/parameters.rb +1 -1
  59. data/lib/action_dispatch/http/permissions_policy.rb +40 -18
  60. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  61. data/lib/action_dispatch/http/request.rb +48 -14
  62. data/lib/action_dispatch/http/response.rb +80 -59
  63. data/lib/action_dispatch/http/upload.rb +2 -0
  64. data/lib/action_dispatch/journey/formatter.rb +8 -2
  65. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  66. data/lib/action_dispatch/journey/route.rb +3 -2
  67. data/lib/action_dispatch/journey/router.rb +9 -8
  68. data/lib/action_dispatch/journey/routes.rb +2 -2
  69. data/lib/action_dispatch/log_subscriber.rb +23 -0
  70. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  71. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  72. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  73. data/lib/action_dispatch/middleware/cookies.rb +81 -98
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  75. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  76. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  77. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  78. data/lib/action_dispatch/middleware/executor.rb +1 -1
  79. data/lib/action_dispatch/middleware/flash.rb +7 -0
  80. data/lib/action_dispatch/middleware/host_authorization.rb +6 -3
  81. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  82. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  83. data/lib/action_dispatch/middleware/remote_ip.rb +17 -16
  84. data/lib/action_dispatch/middleware/request_id.rb +2 -0
  85. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +19 -15
  91. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  92. data/lib/action_dispatch/middleware/stack.rb +7 -2
  93. data/lib/action_dispatch/middleware/static.rb +12 -8
  94. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  95. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  96. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  98. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  99. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  101. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  103. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  104. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  105. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  107. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +46 -37
  108. data/lib/action_dispatch/railtie.rb +14 -4
  109. data/lib/action_dispatch/request/session.rb +16 -6
  110. data/lib/action_dispatch/request/utils.rb +8 -3
  111. data/lib/action_dispatch/routing/inspector.rb +54 -6
  112. data/lib/action_dispatch/routing/mapper.rb +35 -24
  113. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  114. data/lib/action_dispatch/routing/redirection.rb +15 -6
  115. data/lib/action_dispatch/routing/route_set.rb +52 -22
  116. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  117. data/lib/action_dispatch/routing/url_for.rb +5 -1
  118. data/lib/action_dispatch/routing.rb +7 -7
  119. data/lib/action_dispatch/system_test_case.rb +3 -3
  120. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  121. data/lib/action_dispatch/system_testing/driver.rb +13 -21
  122. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  123. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  124. data/lib/action_dispatch/testing/assertions/response.rb +13 -6
  125. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  126. data/lib/action_dispatch/testing/assertions.rb +3 -1
  127. data/lib/action_dispatch/testing/integration.rb +27 -17
  128. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  129. data/lib/action_dispatch/testing/test_process.rb +4 -3
  130. data/lib/action_dispatch/testing/test_request.rb +1 -1
  131. data/lib/action_dispatch/testing/test_response.rb +23 -9
  132. data/lib/action_dispatch.rb +37 -4
  133. data/lib/action_pack/gem_version.rb +4 -4
  134. data/lib/action_pack/version.rb +1 -1
  135. data/lib/action_pack.rb +1 -1
  136. metadata +64 -28
@@ -34,20 +34,20 @@ module ActionDispatch
34
34
  if @raise_on_name_error
35
35
  raise
36
36
  else
37
- [404, { "X-Cascade" => "pass" }, []]
37
+ [404, { Constants::X_CASCADE => "pass" }, []]
38
38
  end
39
39
  end
40
40
 
41
- private
42
- def controller(req)
43
- req.controller_class
44
- rescue NameError => e
45
- raise ActionController::RoutingError, e.message, e.backtrace
46
- end
41
+ private
42
+ def controller(req)
43
+ req.controller_class
44
+ rescue NameError => e
45
+ raise ActionController::RoutingError, e.message, e.backtrace
46
+ end
47
47
 
48
- def dispatch(controller, action, req, res)
49
- controller.dispatch(action, req, res)
50
- end
48
+ def dispatch(controller, action, req, res)
49
+ controller.dispatch(action, req, res)
50
+ end
51
51
  end
52
52
 
53
53
  class StaticDispatcher < Dispatcher
@@ -282,7 +282,7 @@ module ActionDispatch
282
282
 
283
283
  if args.size < path_params_size
284
284
  path_params -= controller_options.keys
285
- path_params -= result.keys
285
+ path_params -= (result[:path_params] || {}).merge(result).keys
286
286
  else
287
287
  path_params = path_params.dup
288
288
  end
@@ -375,6 +375,7 @@ module ActionDispatch
375
375
  @disable_clear_and_finalize = false
376
376
  @finalized = false
377
377
  @env_key = "ROUTES_#{object_id}_SCRIPT_NAME"
378
+ @default_env = nil
378
379
 
379
380
  @set = Journey::Routes.new
380
381
  @router = Journey::Router.new @set
@@ -405,6 +406,25 @@ module ActionDispatch
405
406
  end
406
407
  private :make_request
407
408
 
409
+ def default_env
410
+ if default_url_options != @default_env&.[]("action_dispatch.routes.default_url_options")
411
+ url_options = default_url_options.dup.freeze
412
+ uri = URI(ActionDispatch::Http::URL.full_url_for(host: "example.org", **url_options))
413
+
414
+ @default_env = {
415
+ "action_dispatch.routes" => self,
416
+ "action_dispatch.routes.default_url_options" => url_options,
417
+ "HTTPS" => uri.scheme == "https" ? "on" : "off",
418
+ "rack.url_scheme" => uri.scheme,
419
+ "HTTP_HOST" => uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}",
420
+ "SCRIPT_NAME" => uri.path.chomp("/"),
421
+ "rack.input" => "",
422
+ }.freeze
423
+ end
424
+
425
+ @default_env
426
+ end
427
+
408
428
  def draw(&block)
409
429
  clear! unless @disable_clear_and_finalize
410
430
  eval_block(block)
@@ -574,6 +594,20 @@ module ActionDispatch
574
594
  end
575
595
 
576
596
  private :_generate_paths_by_default
597
+
598
+ # If the module is included more than once (for example, in a subclass
599
+ # of an ancestor that includes the module), ensure that the `_routes`
600
+ # singleton and instance methods return the desired route set by
601
+ # including a new copy of the module (recursively if necessary). Note
602
+ # that this method is called for each inclusion, whereas the above
603
+ # `included` block is run only for the initial inclusion of each copy.
604
+ def self.included(base)
605
+ super
606
+ if base.respond_to?(:_routes) && !base._routes.equal?(@_proxy._routes)
607
+ @dup_for_reinclude ||= self.dup
608
+ base.include @dup_for_reinclude
609
+ end
610
+ end
577
611
  end
578
612
  end
579
613
 
@@ -596,16 +630,16 @@ module ActionDispatch
596
630
  named_routes[name] = route if name
597
631
 
598
632
  if route.segment_keys.include?(:controller)
599
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
633
+ ActionDispatch.deprecator.warn(<<-MSG.squish)
600
634
  Using a dynamic :controller segment in a route is deprecated and
601
- will be removed in Rails 7.1.
635
+ will be removed in Rails 7.2.
602
636
  MSG
603
637
  end
604
638
 
605
639
  if route.segment_keys.include?(:action)
606
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
640
+ ActionDispatch.deprecator.warn(<<-MSG.squish)
607
641
  Using a dynamic :action segment in a route is deprecated and
608
- will be removed in Rails 7.1.
642
+ will be removed in Rails 7.2.
609
643
  MSG
610
644
  end
611
645
 
@@ -779,18 +813,14 @@ module ActionDispatch
779
813
 
780
814
  RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
781
815
  :trailing_slash, :anchor, :params, :only_path, :script_name,
782
- :original_script_name, :relative_url_root]
816
+ :original_script_name]
783
817
 
784
818
  def optimize_routes_generation?
785
819
  default_url_options.empty?
786
820
  end
787
821
 
788
822
  def find_script_name(options)
789
- options.delete(:script_name) || find_relative_url_root(options) || ""
790
- end
791
-
792
- def find_relative_url_root(options)
793
- options.delete(:relative_url_root) || relative_url_root
823
+ options.delete(:script_name) || relative_url_root || ""
794
824
  end
795
825
 
796
826
  def path_for(options, route_name = nil, reserved = RESERVED_OPTIONS)
@@ -854,7 +884,7 @@ module ActionDispatch
854
884
 
855
885
  def recognize_path(path, environment = {})
856
886
  method = (environment[:method] || "GET").to_s.upcase
857
- path = Journey::Router::Utils.normalize_path(path) unless %r{://}.match?(path)
887
+ path = Journey::Router::Utils.normalize_path(path) unless path&.include?("://")
858
888
  extras = environment[:extras] || {}
859
889
 
860
890
  begin
@@ -29,23 +29,18 @@ module ActionDispatch
29
29
 
30
30
  def method_missing(method, *args)
31
31
  if @helpers.respond_to?(method)
32
- self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
33
- def #{method}(*args)
34
- options = args.extract_options!
35
- options = url_options.merge((options || {}).symbolize_keys)
32
+ options = args.extract_options!
33
+ options = url_options.merge((options || {}).symbolize_keys)
36
34
 
37
- if @script_namer
38
- options[:script_name] = merge_script_names(
39
- options[:script_name],
40
- @script_namer.call(options)
41
- )
42
- end
35
+ if @script_namer
36
+ options[:script_name] = merge_script_names(
37
+ options[:script_name],
38
+ @script_namer.call(options)
39
+ )
40
+ end
43
41
 
44
- args << options
45
- @helpers.#{method}(*args)
46
- end
47
- RUBY
48
- public_send(method, *args)
42
+ args << options
43
+ @helpers.public_send(method, *args)
49
44
  else
50
45
  super
51
46
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActionDispatch
4
4
  module Routing
5
+ # = Action Dispatch Routing \UrlFor
6
+ #
5
7
  # In <tt>config/routes.rb</tt> you define URL-to-controller mappings, but the reverse
6
8
  # is also possible: a URL can be generated from one of your routing definitions.
7
9
  # URL generation functionality is centralized in this module.
@@ -37,7 +39,7 @@ module ActionDispatch
37
39
  # # => "/users/new?message=Welcome%21"
38
40
  #
39
41
  # Notice the <tt>only_path: true</tt> part. This is because UrlFor has no
40
- # information about the website hostname that your Rails app is serving. So if you
42
+ # information about the website hostname that your \Rails app is serving. So if you
41
43
  # want to include the hostname as well, then you must also pass the <tt>:host</tt>
42
44
  # argument:
43
45
  #
@@ -134,6 +136,8 @@ module ActionDispatch
134
136
  # * <tt>:port</tt> - Optionally specify the port to connect to.
135
137
  # * <tt>:anchor</tt> - An anchor name to be appended to the path.
136
138
  # * <tt>:params</tt> - The query parameters to be appended to the path.
139
+ # * <tt>:path_params</tt> - The query parameters that will only be used
140
+ # for the named dynamic segments of path. If unused, they will be discarded.
137
141
  # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in <tt>"/archive/2009/"</tt>.
138
142
  # * <tt>:script_name</tt> - Specifies application path relative to domain root. If provided, prepends application path.
139
143
  #
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/string/filters"
4
-
5
3
  module ActionDispatch
6
4
  # The routing module provides URL rewriting in native Ruby. It's a way to
7
5
  # redirect incoming requests to controllers and actions. This replaces
@@ -119,9 +117,9 @@ module ActionDispatch
119
117
  #
120
118
  # # In config/routes.rb
121
119
  # controller :blog do
122
- # get 'blog/show', to: :list
123
- # get 'blog/delete', to: :delete
124
- # get 'blog/edit', to: :edit
120
+ # get 'blog/show' => :list
121
+ # get 'blog/delete' => :delete
122
+ # get 'blog/edit' => :edit
125
123
  # end
126
124
  #
127
125
  # # provides named routes for show, delete, and edit
@@ -240,7 +238,7 @@ module ActionDispatch
240
238
  #
241
239
  # == View a list of all your routes
242
240
  #
243
- # rails routes
241
+ # $ bin/rails routes
244
242
  #
245
243
  # Target a specific controller with <tt>-c</tt>, or grep routes
246
244
  # using <tt>-g</tt>. Useful in conjunction with <tt>--expanded</tt>
@@ -250,7 +248,9 @@ module ActionDispatch
250
248
 
251
249
  autoload :Mapper
252
250
  autoload :RouteSet
253
- autoload :RoutesProxy
251
+ eager_autoload do
252
+ autoload :RoutesProxy
253
+ end
254
254
  autoload :UrlFor
255
255
  autoload :PolymorphicRoutes
256
256
 
@@ -51,7 +51,7 @@ module ActionDispatch
51
51
  # driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
52
52
  # end
53
53
  #
54
- # By default, <tt>ActionDispatch::SystemTestCase</tt> is driven by the
54
+ # By default, +ActionDispatch::SystemTestCase+ is driven by the
55
55
  # Selenium driver, with the Chrome browser, and a browser size of 1400x1400.
56
56
  #
57
57
  # Changing the driver configuration options is easy. Let's say you want to use
@@ -106,8 +106,8 @@ module ActionDispatch
106
106
  # end
107
107
  # end
108
108
  #
109
- # Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara
110
- # and Rails, any driver that is supported by Capybara is supported by system
109
+ # Because +ActionDispatch::SystemTestCase+ is a shim between Capybara
110
+ # and \Rails, any driver that is supported by Capybara is supported by system
111
111
  # tests as long as you include the required gems and files.
112
112
  class SystemTestCase < ActiveSupport::TestCase
113
113
  include Capybara::DSL
@@ -3,7 +3,7 @@
3
3
  module ActionDispatch
4
4
  module SystemTesting
5
5
  class Browser # :nodoc:
6
- attr_reader :name, :options
6
+ attr_reader :name
7
7
 
8
8
  def initialize(name)
9
9
  @name = name
@@ -21,35 +21,32 @@ module ActionDispatch
21
21
  end
22
22
  end
23
23
 
24
+ def options
25
+ @options ||=
26
+ case type
27
+ when :chrome
28
+ ::Selenium::WebDriver::Chrome::Options.new
29
+ when :firefox
30
+ ::Selenium::WebDriver::Firefox::Options.new
31
+ end
32
+ end
33
+
24
34
  def configure
25
- initialize_options
26
- yield options if block_given? && options
35
+ yield options if block_given?
27
36
  end
28
37
 
29
- # driver_path can be configured as a proc.
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.
38
+ # driver_path is lazily initialized by default. Eagerly set it to
39
+ # avoid race conditions when using parallel tests.
33
40
  def preload
34
41
  case type
35
42
  when :chrome
36
- ::Selenium::WebDriver::Chrome::Service.driver_path&.call
43
+ resolve_driver_path(::Selenium::WebDriver::Chrome)
37
44
  when :firefox
38
- ::Selenium::WebDriver::Firefox::Service.driver_path&.call
45
+ resolve_driver_path(::Selenium::WebDriver::Firefox)
39
46
  end
40
47
  end
41
48
 
42
49
  private
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
52
-
53
50
  def set_default_options
54
51
  case name
55
52
  when :headless_chrome
@@ -71,6 +68,10 @@ module ActionDispatch
71
68
  capabilities.add_argument("-headless")
72
69
  end
73
70
  end
71
+
72
+ def resolve_driver_path(namespace)
73
+ namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.path(options, namespace::Service)
74
+ end
74
75
  end
75
76
  end
76
77
  end
@@ -12,14 +12,6 @@ module ActionDispatch
12
12
  @name = @options.delete(:name) || driver_type
13
13
  @capabilities = capabilities
14
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
15
  if driver_type == :selenium
24
16
  gem "selenium-webdriver", ">= 4.0.0"
25
17
  require "selenium/webdriver"
@@ -38,7 +30,7 @@ module ActionDispatch
38
30
 
39
31
  private
40
32
  def registerable?
41
- [:selenium, :poltergeist, :webkit, :cuprite, :rack_test].include?(@driver_type)
33
+ [:selenium, :cuprite, :rack_test, :playwright].include?(@driver_type)
42
34
  end
43
35
 
44
36
  def register
@@ -47,10 +39,9 @@ module ActionDispatch
47
39
  Capybara.register_driver name do |app|
48
40
  case @driver_type
49
41
  when :selenium then register_selenium(app)
50
- when :poltergeist then register_poltergeist(app)
51
- when :webkit then register_webkit(app)
52
42
  when :cuprite then register_cuprite(app)
53
43
  when :rack_test then register_rack_test(app)
44
+ when :playwright then register_playwright(app)
54
45
  end
55
46
  end
56
47
  end
@@ -65,16 +56,6 @@ module ActionDispatch
65
56
  end
66
57
  end
67
58
 
68
- def register_poltergeist(app)
69
- Capybara::Poltergeist::Driver.new(app, @options.merge(window_size: @screen_size))
70
- end
71
-
72
- def register_webkit(app)
73
- Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash.merge(@options)).tap do |driver|
74
- driver.resize_window_to(driver.current_window_handle, *@screen_size)
75
- end
76
- end
77
-
78
59
  def register_cuprite(app)
79
60
  Capybara::Cuprite::Driver.new(app, @options.merge(window_size: @screen_size))
80
61
  end
@@ -83,6 +64,17 @@ module ActionDispatch
83
64
  Capybara::RackTest::Driver.new(app, respect_data_method: true, **@options)
84
65
  end
85
66
 
67
+ def register_playwright(app)
68
+ screen = { width: @screen_size[0], height: @screen_size[1] } if @screen_size
69
+ options = {
70
+ screen: screen,
71
+ viewport: screen,
72
+ **@options
73
+ }.compact
74
+
75
+ Capybara::Playwright::Driver.new(app, **options)
76
+ end
77
+
86
78
  def setup
87
79
  Capybara.current_driver = name
88
80
  end
@@ -13,28 +13,28 @@ module ActionDispatch
13
13
  # to investigate changes at different points during your test. These will be
14
14
  # named with a sequential prefix (or 'failed' for failing tests)
15
15
  #
16
- # The screenshot will be displayed in your console, if supported.
17
- #
18
16
  # The default screenshots directory is +tmp/screenshots+ but you can set a different
19
17
  # one with +Capybara.save_path+
20
18
  #
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
19
+ # You can use the +html+ argument or set the +RAILS_SYSTEM_TESTING_SCREENSHOT_HTML+
20
+ # environment variable to save the HTML from the page that is being screenshotted
21
+ # so you can investigate the elements on the page at the time of the screenshot
24
22
  #
25
- # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
26
- # control the output. Possible values are:
23
+ # You can use the +screenshot+ argument or set the +RAILS_SYSTEM_TESTING_SCREENSHOT+
24
+ # environment variable to control the output. Possible values are:
27
25
  # * [+simple+ (default)] Only displays the screenshot path.
28
26
  # This is the default value.
29
27
  # * [+inline+] Display the screenshot in the terminal using the
30
28
  # iTerm image protocol (https://iterm2.com/documentation-images.html).
31
29
  # * [+artifact+] Display the screenshot in the terminal, using the terminal
32
30
  # artifact format (https://buildkite.github.io/terminal-to-html/inline-images/).
33
- def take_screenshot
31
+ def take_screenshot(html: false, screenshot: nil)
32
+ showing_html = html || html_from_env?
33
+
34
34
  increment_unique
35
- save_html if save_html?
35
+ save_html if showing_html
36
36
  save_image
37
- puts display_image
37
+ show display_image(html: showing_html, screenshot_output: screenshot)
38
38
  end
39
39
 
40
40
  # Takes a screenshot of the current page in the browser if the test
@@ -42,13 +42,16 @@ module ActionDispatch
42
42
  #
43
43
  # +take_failed_screenshot+ is called during system test teardown.
44
44
  def take_failed_screenshot
45
- take_screenshot if failed? && supports_screenshot? && Capybara::Session.instance_created?
45
+ return unless failed? && supports_screenshot? && Capybara::Session.instance_created?
46
+
47
+ take_screenshot
48
+ metadata[:failure_screenshot_path] = relative_image_path if Minitest::Runnable.method_defined?(:metadata)
46
49
  end
47
50
 
48
51
  private
49
52
  attr_accessor :_screenshot_counter
50
53
 
51
- def save_html?
54
+ def html_from_env?
52
55
  ENV["RAILS_SYSTEM_TESTING_SCREENSHOT_HTML"] == "1"
53
56
  end
54
57
 
@@ -62,7 +65,7 @@ module ActionDispatch
62
65
  end
63
66
 
64
67
  def image_name
65
- sanitized_method_name = method_name.tr("/\\", "--")
68
+ sanitized_method_name = method_name.gsub(/[^\w]+/, "-")
66
69
  name = "#{unique}_#{sanitized_method_name}"
67
70
  name[0...225]
68
71
  end
@@ -87,6 +90,10 @@ module ActionDispatch
87
90
  "#{absolute_path}.png"
88
91
  end
89
92
 
93
+ def relative_image_path
94
+ "#{absolute_path.relative_path_from(Rails.root)}.png"
95
+ end
96
+
90
97
  def absolute_html_path
91
98
  "#{absolute_path}.html"
92
99
  end
@@ -109,11 +116,15 @@ module ActionDispatch
109
116
  output_type
110
117
  end
111
118
 
112
- def display_image
119
+ def show(img)
120
+ puts img
121
+ end
122
+
123
+ def display_image(html:, screenshot_output:)
113
124
  message = +"[Screenshot Image]: #{image_path}\n"
114
- message << +"[Screenshot HTML]: #{html_path}\n" if save_html?
125
+ message << +"[Screenshot HTML]: #{html_path}\n" if html
115
126
 
116
- case output_type
127
+ case screenshot_output || output_type
117
128
  when "artifact"
118
129
  message << "\e]1338;url=artifact://#{absolute_image_path}\a\n"
119
130
  when "inline"
@@ -36,7 +36,7 @@ module ActionDispatch
36
36
 
37
37
  private
38
38
  def code_from_name(name)
39
- GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
39
+ GENERIC_RESPONSE_CODES[name] || Rack::Utils.status_code(name)
40
40
  end
41
41
 
42
42
  def name_from_code(code)
@@ -30,7 +30,7 @@ module ActionDispatch
30
30
  def assert_response(type, message = nil)
31
31
  message ||= generate_response_message(type)
32
32
 
33
- if RESPONSE_PREDICATES.keys.include?(type)
33
+ if RESPONSE_PREDICATES.key?(type)
34
34
  assert @response.public_send(RESPONSE_PREDICATES[type]), message
35
35
  else
36
36
  assert_equal AssertionResponse.new(type).code, @response.response_code, message
@@ -50,12 +50,19 @@ module ActionDispatch
50
50
  #
51
51
  # # Asserts that the redirection matches the regular expression
52
52
  # assert_redirected_to %r(\Ahttp://example.org)
53
- def assert_redirected_to(options = {}, message = nil)
54
- assert_response(:redirect, message)
55
- return true if options === @response.location
53
+ #
54
+ # # Asserts that the redirection has the HTTP status code 301 (Moved
55
+ # # Permanently).
56
+ # assert_redirected_to "/some/path", status: :moved_permanently
57
+ def assert_redirected_to(url_options = {}, options = {}, message = nil)
58
+ options, message = {}, options unless options.is_a?(Hash)
59
+
60
+ status = options[:status] || :redirect
61
+ assert_response(status, message)
62
+ return true if url_options === @response.location
56
63
 
57
64
  redirect_is = normalize_argument_to_redirection(@response.location)
58
- redirect_expected = normalize_argument_to_redirection(options)
65
+ redirect_expected = normalize_argument_to_redirection(url_options)
59
66
 
60
67
  message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
61
68
  assert_operator redirect_expected, :===, redirect_is, message
@@ -93,7 +100,7 @@ module ActionDispatch
93
100
  end
94
101
 
95
102
  def code_with_name(code_or_name)
96
- if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym)
103
+ if RESPONSE_PREDICATES.value?("#{code_or_name}?".to_sym)
97
104
  code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
98
105
  end
99
106