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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +416 -255
- data/MIT-LICENSE +1 -2
- data/lib/abstract_controller/base.rb +35 -2
- data/lib/abstract_controller/callbacks.rb +2 -2
- data/lib/abstract_controller/collector.rb +4 -2
- data/lib/abstract_controller/helpers.rb +105 -90
- data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
- data/lib/abstract_controller/rendering.rb +9 -9
- data/lib/abstract_controller/translation.rb +8 -2
- data/lib/abstract_controller.rb +1 -0
- data/lib/action_controller/api.rb +2 -2
- data/lib/action_controller/base.rb +4 -2
- data/lib/action_controller/caching.rb +0 -1
- data/lib/action_controller/log_subscriber.rb +3 -3
- data/lib/action_controller/metal/conditional_get.rb +11 -3
- data/lib/action_controller/metal/content_security_policy.rb +1 -1
- data/lib/action_controller/metal/cookies.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +1 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
- data/lib/action_controller/metal/exceptions.rb +33 -0
- data/lib/action_controller/metal/head.rb +7 -4
- data/lib/action_controller/metal/helpers.rb +11 -1
- data/lib/action_controller/metal/http_authentication.rb +5 -2
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +11 -9
- data/lib/action_controller/metal/live.rb +10 -1
- data/lib/action_controller/metal/logging.rb +20 -0
- data/lib/action_controller/metal/mime_responds.rb +6 -2
- data/lib/action_controller/metal/parameter_encoding.rb +35 -4
- data/lib/action_controller/metal/params_wrapper.rb +14 -8
- data/lib/action_controller/metal/permissions_policy.rb +46 -0
- data/lib/action_controller/metal/redirecting.rb +21 -2
- data/lib/action_controller/metal/rendering.rb +6 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +1 -1
- data/lib/action_controller/metal/rescue.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +104 -16
- data/lib/action_controller/metal.rb +2 -2
- data/lib/action_controller/renderer.rb +23 -13
- data/lib/action_controller/test_case.rb +65 -56
- data/lib/action_controller.rb +2 -3
- data/lib/action_dispatch/http/cache.rb +18 -17
- data/lib/action_dispatch/http/content_security_policy.rb +6 -1
- data/lib/action_dispatch/http/filter_parameters.rb +1 -1
- data/lib/action_dispatch/http/filter_redirect.rb +1 -1
- data/lib/action_dispatch/http/headers.rb +3 -2
- data/lib/action_dispatch/http/mime_negotiation.rb +14 -8
- data/lib/action_dispatch/http/mime_type.rb +29 -16
- data/lib/action_dispatch/http/parameters.rb +1 -19
- data/lib/action_dispatch/http/permissions_policy.rb +173 -0
- data/lib/action_dispatch/http/request.rb +24 -8
- data/lib/action_dispatch/http/response.rb +17 -16
- data/lib/action_dispatch/http/url.rb +3 -2
- data/lib/action_dispatch/journey/formatter.rb +55 -30
- data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
- data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
- data/lib/action_dispatch/journey/nodes/node.rb +4 -3
- data/lib/action_dispatch/journey/parser.rb +13 -13
- data/lib/action_dispatch/journey/parser.y +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +13 -18
- data/lib/action_dispatch/journey/route.rb +7 -18
- data/lib/action_dispatch/journey/router/utils.rb +6 -4
- data/lib/action_dispatch/journey/router.rb +26 -30
- data/lib/action_dispatch/journey/visitors.rb +1 -1
- data/lib/action_dispatch/journey.rb +0 -2
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +1 -1
- data/lib/action_dispatch/middleware/cookies.rb +89 -46
- data/lib/action_dispatch/middleware/debug_exceptions.rb +8 -15
- data/lib/action_dispatch/middleware/debug_view.rb +1 -1
- data/lib/action_dispatch/middleware/exception_wrapper.rb +28 -16
- data/lib/action_dispatch/middleware/host_authorization.rb +63 -14
- data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
- data/lib/action_dispatch/middleware/request_id.rb +4 -5
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
- data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +12 -0
- data/lib/action_dispatch/middleware/ssl.rb +12 -7
- data/lib/action_dispatch/middleware/stack.rb +19 -1
- data/lib/action_dispatch/middleware/static.rb +154 -93
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +100 -8
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +21 -1
- data/lib/action_dispatch/railtie.rb +3 -2
- data/lib/action_dispatch/request/session.rb +2 -8
- data/lib/action_dispatch/request/utils.rb +26 -2
- data/lib/action_dispatch/routing/inspector.rb +8 -7
- data/lib/action_dispatch/routing/mapper.rb +102 -71
- data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
- data/lib/action_dispatch/routing/redirection.rb +4 -4
- data/lib/action_dispatch/routing/route_set.rb +49 -41
- data/lib/action_dispatch/system_test_case.rb +35 -24
- data/lib/action_dispatch/system_testing/browser.rb +33 -27
- data/lib/action_dispatch/system_testing/driver.rb +6 -7
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
- data/lib/action_dispatch/testing/assertions/response.rb +2 -4
- data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +40 -29
- data/lib/action_dispatch/testing/test_process.rb +32 -4
- data/lib/action_dispatch/testing/test_request.rb +3 -3
- data/lib/action_dispatch.rb +3 -2
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +18 -19
- data/lib/action_controller/metal/force_ssl.rb +0 -58
- data/lib/action_dispatch/http/parameter_filter.rb +0 -12
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- 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
|
-
|
114
|
-
|
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
|
174
|
+
def self.create(route, options, route_name)
|
173
175
|
if optimize_helper?(route)
|
174
|
-
OptimizedUrlHelper.new(route, options, route_name
|
176
|
+
OptimizedUrlHelper.new(route, options, route_name)
|
175
177
|
else
|
176
|
-
new
|
178
|
+
new(route, options, route_name)
|
177
179
|
end
|
178
180
|
end
|
179
181
|
|
180
182
|
def self.optimize_helper?(route)
|
181
|
-
|
183
|
+
route.path.requirements.empty? && !route.glob?
|
182
184
|
end
|
183
185
|
|
184
|
-
attr_reader :
|
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
|
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
|
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,
|
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
|
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
|
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
|
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
|
736
|
-
#
|
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
|
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
|
-
|
764
|
-
|
765
|
-
|
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(
|
769
|
-
Generator.new(
|
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
|
-
|
820
|
+
reserved.each { |ro| path_options.delete ro }
|
815
821
|
|
816
|
-
|
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.
|
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", ">=
|
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
|
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
|
-
|
174
|
-
|
175
|
-
@
|
176
|
-
|
177
|
-
|
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
|
-
|
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
|
24
|
-
|
25
|
-
|
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
|
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
|
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
|
67
|
-
|
68
|
-
|
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
|
-
|
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
|
74
|
-
capabilities
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
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.
|
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
|
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.
|
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.
|
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
|
-
|
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.
|
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
|
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)
|