actionpack 7.0.4 → 7.1.3.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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +397 -269
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  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 +75 -28
  10. data/lib/abstract_controller/railties/routes_helpers.rb +1 -16
  11. data/lib/abstract_controller/rendering.rb +12 -14
  12. data/lib/abstract_controller/translation.rb +9 -6
  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 +6 -4
  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/basic_implicit_render.rb +3 -1
  22. data/lib/action_controller/metal/conditional_get.rb +121 -123
  23. data/lib/action_controller/metal/content_security_policy.rb +5 -5
  24. data/lib/action_controller/metal/data_streaming.rb +20 -18
  25. data/lib/action_controller/metal/default_headers.rb +2 -0
  26. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -0
  28. data/lib/action_controller/metal/exceptions.rb +8 -0
  29. data/lib/action_controller/metal/head.rb +9 -7
  30. data/lib/action_controller/metal/helpers.rb +3 -14
  31. data/lib/action_controller/metal/http_authentication.rb +17 -8
  32. data/lib/action_controller/metal/implicit_render.rb +5 -3
  33. data/lib/action_controller/metal/instrumentation.rb +8 -1
  34. data/lib/action_controller/metal/live.rb +25 -1
  35. data/lib/action_controller/metal/mime_responds.rb +2 -2
  36. data/lib/action_controller/metal/params_wrapper.rb +4 -2
  37. data/lib/action_controller/metal/permissions_policy.rb +2 -2
  38. data/lib/action_controller/metal/redirecting.rb +29 -8
  39. data/lib/action_controller/metal/renderers.rb +4 -4
  40. data/lib/action_controller/metal/rendering.rb +114 -9
  41. data/lib/action_controller/metal/request_forgery_protection.rb +144 -53
  42. data/lib/action_controller/metal/rescue.rb +6 -3
  43. data/lib/action_controller/metal/streaming.rb +71 -31
  44. data/lib/action_controller/metal/strong_parameters.rb +158 -101
  45. data/lib/action_controller/metal/url_for.rb +9 -4
  46. data/lib/action_controller/metal.rb +79 -21
  47. data/lib/action_controller/railtie.rb +24 -10
  48. data/lib/action_controller/renderer.rb +99 -85
  49. data/lib/action_controller/test_case.rb +15 -5
  50. data/lib/action_controller.rb +8 -1
  51. data/lib/action_dispatch/constants.rb +32 -0
  52. data/lib/action_dispatch/deprecator.rb +7 -0
  53. data/lib/action_dispatch/http/cache.rb +9 -11
  54. data/lib/action_dispatch/http/content_security_policy.rb +14 -9
  55. data/lib/action_dispatch/http/filter_parameters.rb +14 -28
  56. data/lib/action_dispatch/http/headers.rb +3 -1
  57. data/lib/action_dispatch/http/mime_negotiation.rb +22 -22
  58. data/lib/action_dispatch/http/mime_type.rb +35 -12
  59. data/lib/action_dispatch/http/mime_types.rb +3 -1
  60. data/lib/action_dispatch/http/parameters.rb +1 -1
  61. data/lib/action_dispatch/http/permissions_policy.rb +38 -23
  62. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  63. data/lib/action_dispatch/http/request.rb +63 -30
  64. data/lib/action_dispatch/http/response.rb +80 -63
  65. data/lib/action_dispatch/http/upload.rb +15 -2
  66. data/lib/action_dispatch/journey/formatter.rb +8 -2
  67. data/lib/action_dispatch/journey/path/pattern.rb +14 -14
  68. data/lib/action_dispatch/journey/route.rb +3 -2
  69. data/lib/action_dispatch/journey/router.rb +9 -8
  70. data/lib/action_dispatch/journey/routes.rb +2 -2
  71. data/lib/action_dispatch/log_subscriber.rb +23 -0
  72. data/lib/action_dispatch/middleware/actionable_exceptions.rb +5 -6
  73. data/lib/action_dispatch/middleware/assume_ssl.rb +24 -0
  74. data/lib/action_dispatch/middleware/callbacks.rb +2 -0
  75. data/lib/action_dispatch/middleware/cookies.rb +108 -117
  76. data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -25
  77. data/lib/action_dispatch/middleware/debug_locks.rb +4 -1
  78. data/lib/action_dispatch/middleware/debug_view.rb +7 -2
  79. data/lib/action_dispatch/middleware/exception_wrapper.rb +186 -27
  80. data/lib/action_dispatch/middleware/executor.rb +1 -1
  81. data/lib/action_dispatch/middleware/flash.rb +7 -0
  82. data/lib/action_dispatch/middleware/host_authorization.rb +18 -8
  83. data/lib/action_dispatch/middleware/public_exceptions.rb +5 -3
  84. data/lib/action_dispatch/middleware/reloader.rb +7 -5
  85. data/lib/action_dispatch/middleware/remote_ip.rb +21 -20
  86. data/lib/action_dispatch/middleware/request_id.rb +4 -2
  87. data/lib/action_dispatch/middleware/server_timing.rb +4 -4
  88. data/lib/action_dispatch/middleware/session/abstract_store.rb +5 -0
  89. data/lib/action_dispatch/middleware/session/cache_store.rb +2 -0
  90. data/lib/action_dispatch/middleware/session/cookie_store.rb +11 -5
  91. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +3 -1
  92. data/lib/action_dispatch/middleware/show_exceptions.rb +25 -18
  93. data/lib/action_dispatch/middleware/ssl.rb +18 -6
  94. data/lib/action_dispatch/middleware/stack.rb +7 -2
  95. data/lib/action_dispatch/middleware/static.rb +14 -10
  96. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  97. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  98. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  99. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -3
  100. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -3
  101. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  102. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  103. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  105. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  107. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  108. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  109. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  110. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  111. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -41
  112. data/lib/action_dispatch/railtie.rb +14 -4
  113. data/lib/action_dispatch/request/session.rb +16 -6
  114. data/lib/action_dispatch/request/utils.rb +8 -3
  115. data/lib/action_dispatch/routing/inspector.rb +54 -6
  116. data/lib/action_dispatch/routing/mapper.rb +58 -24
  117. data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -0
  118. data/lib/action_dispatch/routing/redirection.rb +15 -6
  119. data/lib/action_dispatch/routing/route_set.rb +52 -22
  120. data/lib/action_dispatch/routing/routes_proxy.rb +10 -15
  121. data/lib/action_dispatch/routing/url_for.rb +26 -22
  122. data/lib/action_dispatch/routing.rb +7 -7
  123. data/lib/action_dispatch/system_test_case.rb +3 -3
  124. data/lib/action_dispatch/system_testing/browser.rb +20 -19
  125. data/lib/action_dispatch/system_testing/driver.rb +14 -22
  126. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +27 -16
  127. data/lib/action_dispatch/testing/assertion_response.rb +1 -1
  128. data/lib/action_dispatch/testing/assertions/response.rb +14 -7
  129. data/lib/action_dispatch/testing/assertions/routing.rb +67 -28
  130. data/lib/action_dispatch/testing/assertions.rb +3 -1
  131. data/lib/action_dispatch/testing/integration.rb +27 -17
  132. data/lib/action_dispatch/testing/request_encoder.rb +4 -1
  133. data/lib/action_dispatch/testing/test_process.rb +4 -3
  134. data/lib/action_dispatch/testing/test_request.rb +1 -1
  135. data/lib/action_dispatch/testing/test_response.rb +23 -9
  136. data/lib/action_dispatch.rb +37 -4
  137. data/lib/action_pack/gem_version.rb +4 -4
  138. data/lib/action_pack/version.rb +1 -1
  139. data/lib/action_pack.rb +1 -1
  140. metadata +65 -29
@@ -18,10 +18,19 @@ module ActionDispatch
18
18
  def redirect?; true; end
19
19
 
20
20
  def call(env)
21
- serve Request.new env
21
+ ActiveSupport::Notifications.instrument("redirect.action_dispatch") do |payload|
22
+ request = Request.new(env)
23
+ response = build_response(request)
24
+
25
+ payload[:status] = @status
26
+ payload[:location] = response.headers["Location"]
27
+ payload[:request] = request
28
+
29
+ response.to_a
30
+ end
22
31
  end
23
32
 
24
- def serve(req)
33
+ def build_response(req)
25
34
  uri = URI.parse(path(req.path_parameters, req))
26
35
 
27
36
  unless uri.host
@@ -38,15 +47,15 @@ module ActionDispatch
38
47
 
39
48
  req.commit_flash
40
49
 
41
- body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>)
50
+ body = ""
42
51
 
43
52
  headers = {
44
53
  "Location" => uri.to_s,
45
- "Content-Type" => "text/html",
54
+ "Content-Type" => "text/html; charset=#{ActionDispatch::Response.default_charset}",
46
55
  "Content-Length" => body.length.to_s
47
56
  }
48
57
 
49
- [ status, headers, [body] ]
58
+ ActionDispatch::Response.new(status, headers, body)
50
59
  end
51
60
 
52
61
  def path(params, request)
@@ -59,7 +68,7 @@ module ActionDispatch
59
68
 
60
69
  private
61
70
  def relative_path?(path)
62
- path && !path.empty? && path[0] != "/"
71
+ path && !path.empty? && !path.start_with?("/")
63
72
  end
64
73
 
65
74
  def escape(params)
@@ -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,20 +2,22 @@
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.
8
10
  #
9
- # See ActionDispatch::Routing for general information about routing and routes.rb.
11
+ # See ActionDispatch::Routing for general information about routing and <tt>config/routes.rb</tt>.
10
12
  #
11
13
  # <b>Tip:</b> If you need to generate URLs from your models or some other place,
12
- # then ActionController::UrlFor is what you're looking for. Read on for
14
+ # then ActionDispatch::Routing::UrlFor is what you're looking for. Read on for
13
15
  # an introduction. In general, this module should not be included on its own,
14
- # as it is usually included by url_helpers (as in Rails.application.routes.url_helpers).
16
+ # as it is usually included by +url_helpers+ (as in <tt>Rails.application.routes.url_helpers</tt>).
15
17
  #
16
18
  # == URL generation from parameters
17
19
  #
18
- # As you may know, some functions, such as ActionController::Base#url_for
20
+ # As you may know, some functions, such as <tt>ActionController::Base#url_for</tt>
19
21
  # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set
20
22
  # of parameters. For example, you've probably had the chance to write code
21
23
  # like this in one of your views:
@@ -24,12 +26,12 @@ module ActionDispatch
24
26
  # action: 'new', message: 'Welcome!') %>
25
27
  # # => <a href="/users/new?message=Welcome%21">Click here</a>
26
28
  #
27
- # link_to, and all other functions that require URL generation functionality,
28
- # actually use ActionController::UrlFor under the hood. And in particular,
29
- # they use the ActionController::UrlFor#url_for method. One can generate
29
+ # +link_to+, and all other functions that require URL generation functionality,
30
+ # actually use ActionDispatch::Routing::UrlFor under the hood. And in particular,
31
+ # they use the ActionDispatch::Routing::UrlFor#url_for method. One can generate
30
32
  # the same path as the above example by using the following code:
31
33
  #
32
- # include UrlFor
34
+ # include ActionDispatch::Routing::UrlFor
33
35
  # url_for(controller: 'users',
34
36
  # action: 'new',
35
37
  # message: 'Welcome!',
@@ -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
  #
@@ -48,17 +50,17 @@ module ActionDispatch
48
50
  # host: 'www.example.com')
49
51
  # # => "http://www.example.com/users/new?message=Welcome%21"
50
52
  #
51
- # By default, all controllers and views have access to a special version of url_for,
52
- # that already knows what the current hostname is. So if you use url_for in your
53
+ # By default, all controllers and views have access to a special version of +url_for+,
54
+ # that already knows what the current hostname is. So if you use +url_for+ in your
53
55
  # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt>
54
56
  # argument.
55
57
  #
56
- # For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
57
- # So within mailers, you only have to type +url_for+ instead of 'ActionController::UrlFor#url_for'
58
- # in full. However, mailers don't have hostname information, and you still have to provide
59
- # the +:host+ argument or set the default host that will be used in all mailers using the
60
- # configuration option +config.action_mailer.default_url_options+. For more information on
61
- # url_for in mailers read the ActionMailer#Base documentation.
58
+ # For convenience, mailers also include ActionDispatch::Routing::UrlFor. So
59
+ # within mailers, you can use url_for. However, mailers cannot access
60
+ # incoming web requests in order to derive hostname information, so you have
61
+ # to provide the +:host+ option or set the default host using
62
+ # +default_url_options+. For more information on url_for in mailers see the
63
+ # ActionMailer::Base documentation.
62
64
  #
63
65
  #
64
66
  # == URL generation for named routes
@@ -72,7 +74,7 @@ module ActionDispatch
72
74
  # This generates, among other things, the method <tt>users_path</tt>. By default,
73
75
  # this method is accessible from your controllers, views, and mailers. If you need
74
76
  # to access this auto-generated method from other places (such as a model), then
75
- # you can do that by including Rails.application.routes.url_helpers in your class:
77
+ # you can do that by including <tt>Rails.application.routes.url_helpers</tt> in your class:
76
78
  #
77
79
  # class User < ActiveRecord::Base
78
80
  # include Rails.application.routes.url_helpers
@@ -115,11 +117,11 @@ module ActionDispatch
115
117
  default_url_options
116
118
  end
117
119
 
118
- # Generate a URL based on the options provided, default_url_options, and the
119
- # routes defined in routes.rb. The following options are supported:
120
+ # Generate a URL based on the options provided, +default_url_options+, and the
121
+ # routes defined in <tt>config/routes.rb</tt>. The following options are supported:
120
122
  #
121
123
  # * <tt>:only_path</tt> - If true, the relative URL is returned. Defaults to +false+.
122
- # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'.
124
+ # * <tt>:protocol</tt> - The protocol to connect to. Defaults to <tt>"http"</tt>.
123
125
  # * <tt>:host</tt> - Specifies the host the link should be targeted at.
124
126
  # If <tt>:only_path</tt> is false, this option must be
125
127
  # provided either explicitly, or via +default_url_options+.
@@ -134,7 +136,9 @@ 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.
137
- # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
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.
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
  #
140
144
  # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
@@ -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. 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.
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,16 +39,15 @@ 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
57
48
 
58
49
  def browser_options
59
- @options.merge(capabilities: @browser.options).compact
50
+ @options.merge(options: @browser.options).compact
60
51
  end
61
52
 
62
53
  def register_selenium(app)
@@ -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)