capybara 3.36.0 → 3.37.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +27 -1
  3. data/README.md +1 -1
  4. data/lib/capybara/driver/node.rb +4 -0
  5. data/lib/capybara/dsl.rb +4 -10
  6. data/lib/capybara/helpers.rb +1 -1
  7. data/lib/capybara/minitest/spec.rb +2 -2
  8. data/lib/capybara/node/element.rb +12 -1
  9. data/lib/capybara/node/finders.rb +8 -1
  10. data/lib/capybara/queries/selector_query.rb +20 -7
  11. data/lib/capybara/rack_test/browser.rb +56 -7
  12. data/lib/capybara/rack_test/driver.rb +4 -4
  13. data/lib/capybara/rack_test/node.rb +1 -1
  14. data/lib/capybara/registration_container.rb +0 -3
  15. data/lib/capybara/rspec/matchers/have_selector.rb +4 -4
  16. data/lib/capybara/rspec/matchers.rb +14 -14
  17. data/lib/capybara/selector/definition.rb +2 -1
  18. data/lib/capybara/selector/selector.rb +5 -1
  19. data/lib/capybara/selenium/driver.rb +6 -2
  20. data/lib/capybara/selenium/node.rb +12 -0
  21. data/lib/capybara/server/animation_disabler.rb +29 -17
  22. data/lib/capybara/session/config.rb +1 -1
  23. data/lib/capybara/session.rb +8 -21
  24. data/lib/capybara/spec/session/all_spec.rb +5 -7
  25. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  26. data/lib/capybara/spec/session/find_spec.rb +6 -0
  27. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  28. data/lib/capybara/spec/session/has_field_spec.rb +1 -1
  29. data/lib/capybara/spec/session/has_link_spec.rb +6 -0
  30. data/lib/capybara/spec/session/has_select_spec.rb +4 -4
  31. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  32. data/lib/capybara/spec/session/has_text_spec.rb +1 -5
  33. data/lib/capybara/spec/session/node_spec.rb +42 -0
  34. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  35. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  36. data/lib/capybara/spec/test_app.rb +49 -0
  37. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  38. data/lib/capybara/version.rb +1 -1
  39. data/lib/capybara/window.rb +1 -1
  40. data/lib/capybara.rb +1 -0
  41. data/spec/dsl_spec.rb +2 -2
  42. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  43. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  44. data/spec/rack_test_spec.rb +6 -0
  45. data/spec/result_spec.rb +27 -29
  46. data/spec/rspec/features_spec.rb +2 -2
  47. data/spec/rspec/scenarios_spec.rb +1 -1
  48. data/spec/sauce_spec_chrome.rb +3 -3
  49. data/spec/selector_spec.rb +2 -2
  50. data/spec/selenium_spec_chrome.rb +1 -1
  51. data/spec/selenium_spec_firefox.rb +5 -1
  52. data/spec/selenium_spec_safari.rb +5 -1
  53. data/spec/server_spec.rb +5 -5
  54. data/spec/shared_selenium_session.rb +9 -2
  55. data/spec/spec_helper.rb +1 -1
  56. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd7f81974b21d47d35c99dff02f878607128c99c80b18b1fef54680797978898
4
- data.tar.gz: ee6659925b7ddaaa0220ad0af81ae65a34e96094583308c160902470e7019128
3
+ metadata.gz: c7283f19c4364fa2c19379e8da670105ebf59754937fc80117d975cd89959b87
4
+ data.tar.gz: b4fe7210ee210667602e9ec0a9882a1a34d8a64ceddae4df888aba0dd6974940
5
5
  SHA512:
6
- metadata.gz: ad7d2ea4d2612770e801443e61b23a45c0b9936fa4387170ffbbf051706dbc8aee5111b8eefb2d6fabb3042b479dc19d26248923d84da3e78d12dc090aca1e2d
7
- data.tar.gz: 8868469dc6ddcb6c802f4a7b76df9455ff561d00ad78e3cab75e5f02e1b71b74402700c081864368d446304acb6fac2fed5cb79df117d309d4a0a278c95e35d8
6
+ metadata.gz: 71de08c12fdd479453fa743e0a7ef858e72ca96d9a32d2ecb0b1721b365a1f1e494309432b52536dcac5669af78fe50e60b1a9f886f0b08a2ac2fd378abfc52e
7
+ data.tar.gz: 91a2e75a15768ef02e8c9b9f1400c4ee1dd2e1e2562b4733dc520afddc4c71be962fcd5c0379481fa65938d0776cfe5434c75f5faf0e512246abca48b06b0c00
data/History.md CHANGED
@@ -1,3 +1,29 @@
1
+ # Version 3.37.1
2
+ Relesae date: 2022-05-09
3
+
4
+ ### Fixed
5
+
6
+ * Regression in rack-test visit - Issue #2548
7
+
8
+ # Version 3.37.0
9
+ Release date: 2022-05-07
10
+
11
+ ### Changed
12
+
13
+ * Ruby 2.7.0+ is now required
14
+
15
+ ### Added
16
+
17
+ * [Beta] CSP nonces inserted into animation disabler additions - Issue #2542
18
+ * Support `<base>` element in rack-test driver - ISsue #2544
19
+ * [Beta] `Element#shadow_root` support. Requires selenium-webdriver 4.1+. Only currently supported with Chrome when using the selenium driver. Note: only CSS can be used to find elements from the shadow root. Therefore you won't be able to use most Capybara helper methods (`fill_in`, `click_link`, `find_field`, etc) directly from the shadow root since those locators are built using XPath. If you first locate a descendant from the shadow root using CSS then you should be able to use all the Capybara methods from there.
20
+ * Regexp now supported for `exact_text` finder option
21
+
22
+ ### Fixed
23
+
24
+ * Fragments in referer headers in rack-test driver - Issue #2525
25
+ * Selenium v4.1 deprecation notice
26
+
1
27
  # Version 3.36.0
2
28
  Release date: 2021-10-24
3
29
 
@@ -10,7 +36,7 @@ Release date: 2021-10-24
10
36
 
11
37
  * Support for selenium-webdriver 4.x
12
38
  * `allow_label_click` accepts click options to be used when clicking an associated label
13
- * Deprecated `allow_gumbo=` in favor of `use_html5_parsing=` to enable use of Nokogiri::HTL5 when available
39
+ * Deprecated `allow_gumbo=` in favor of `use_html5_parsing=` to enable use of Nokogiri::HTML5 when available
14
40
  * `Session#active_element` returns the element with focus - Not supported by the `RackTest` driver [Sean Doyle]
15
41
  * Support `focused:` filter for finding interactive elements - Not supported by the `RackTest` driver [Sean Doyle]
16
42
 
data/README.md CHANGED
@@ -74,7 +74,7 @@ GitHub): http://groups.google.com/group/ruby-capybara
74
74
 
75
75
  ## <a name="setup"></a>Setup
76
76
 
77
- Capybara requires Ruby 2.6.0 or later. To install, add this line to your
77
+ Capybara requires Ruby 2.7.0 or later. To install, add this line to your
78
78
  `Gemfile` and run `bundle install`:
79
79
 
80
80
  ```ruby
@@ -125,6 +125,10 @@ module Capybara
125
125
  raise NotSupportedByDriverError, 'Capybara::Driver::Node#trigger'
126
126
  end
127
127
 
128
+ def shadow_root
129
+ raise NotSupportedByDriverError, 'Capybara::Driver::Node#shadow_root'
130
+ end
131
+
128
132
  def inspect
129
133
  %(#<#{self.class} tag="#{tag_name}" path="#{path}">)
130
134
  rescue NotSupportedByDriverError
data/lib/capybara/dsl.rb CHANGED
@@ -47,17 +47,11 @@ module Capybara
47
47
  end
48
48
 
49
49
  Session::DSL_METHODS.each do |method|
50
- if RUBY_VERSION >= '2.7'
51
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
52
- def #{method}(...)
53
- page.method("#{method}").call(...)
54
- end
55
- METHOD
56
- else
57
- define_method method do |*args, &block|
58
- page.send method, *args, &block
50
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
51
+ def #{method}(...)
52
+ page.method("#{method}").call(...)
59
53
  end
60
- end
54
+ METHOD
61
55
  end
62
56
  end
63
57
 
@@ -73,7 +73,7 @@ module Capybara
73
73
  def filter_backtrace(trace)
74
74
  return 'No backtrace' unless trace
75
75
 
76
- filter = %r{lib/capybara/|lib/rspec/|lib/minitest/}
76
+ filter = %r{lib/capybara/|lib/rspec/|lib/minitest/|delegate.rb}
77
77
  new_trace = trace.take_while { |line| line !~ filter }
78
78
  new_trace = trace.grep_v(filter) if new_trace.empty?
79
79
  new_trace = trace.dup if new_trace.empty?
@@ -246,9 +246,9 @@ module Capybara
246
246
 
247
247
  ##
248
248
  # @deprecated
249
- def must_have_style(*args, **kw_args, &block)
249
+ def must_have_style(...)
250
250
  warn 'must_have_style is deprecated, please use must_match_style'
251
- must_match_style(*args, **kw_args, &block)
251
+ must_match_style(...)
252
252
  end
253
253
  end
254
254
  end
@@ -115,7 +115,7 @@ module Capybara
115
115
  #
116
116
  # @return [Capybara::Node::Element] The element
117
117
  def set(value, **options)
118
- if ENV['CAPYBARA_THOROUGH'] && readonly?
118
+ if ENV.fetch('CAPYBARA_THOROUGH', nil) && readonly?
119
119
  raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}"
120
120
  end
121
121
 
@@ -472,6 +472,17 @@ module Capybara
472
472
  self
473
473
  end
474
474
 
475
+ ##
476
+ #
477
+ # Return the shadow_root for the current element
478
+ #
479
+ # @return [Capybara::Node::Element] The shadow root
480
+
481
+ def shadow_root
482
+ root = synchronize { base.shadow_root }
483
+ root && Capybara::Node::Element.new(session, root, nil, nil)
484
+ end
485
+
475
486
  ##
476
487
  #
477
488
  # Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
@@ -18,7 +18,7 @@ module Capybara
18
18
  #
19
19
  # @!macro system_filters
20
20
  # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
21
- # @option options [String, Boolean] exact_text
21
+ # @option options [String, Regexp, String] exact_text
22
22
  # When String the elements contained text must match exactly, when Boolean controls whether the `text` option must match exactly.
23
23
  # Defaults to {Capybara.configure exact_text}.
24
24
  # @option options [Boolean] normalize_ws
@@ -50,6 +50,13 @@ module Capybara
50
50
  #
51
51
  def find(*args, **options, &optional_filter_block)
52
52
  options[:session_options] = session_options
53
+ count_options = options.slice(*Capybara::Queries::BaseQuery::COUNT_KEYS)
54
+ unless count_options.empty?
55
+ Capybara::Helpers.warn(
56
+ "'find' does not support count options (#{count_options}) ignoring. " \
57
+ "Called from: #{Capybara::Helpers.filter_backtrace(caller)}"
58
+ )
59
+ end
53
60
  synced_resolve Capybara::Queries::SelectorQuery.new(*args, **options, &optional_filter_block)
54
61
  end
55
62
 
@@ -27,6 +27,12 @@ module Capybara
27
27
  @order = order
28
28
  @filter_cache = Hash.new { |hsh, key| hsh[key] = {} }
29
29
 
30
+ if @options[:text].is_a?(Regexp) && [true, false].include?(@options[:exact_text])
31
+ Capybara::Helpers.warn(
32
+ "Boolean 'exact_text' option is not supported when 'text' option is a Regexp - ignoring"
33
+ )
34
+ end
35
+
30
36
  super(@options)
31
37
  self.session_options = session_options
32
38
 
@@ -541,16 +547,19 @@ module Capybara
541
547
  def matches_text_filter?(node)
542
548
  value = options[:text]
543
549
  return true unless value
544
- return matches_text_exactly?(node, value) if exact_text == true
550
+ return matches_text_exactly?(node, value) if exact_text == true && !value.is_a?(Regexp)
545
551
 
546
552
  regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
547
553
  matches_text_regexp?(node, regexp)
548
554
  end
549
555
 
550
556
  def matches_exact_text_filter?(node)
551
- return true unless exact_text.is_a?(String)
552
-
553
- matches_text_exactly?(node, exact_text)
557
+ case exact_text
558
+ when String, Regexp
559
+ matches_text_exactly?(node, exact_text)
560
+ else
561
+ true
562
+ end
554
563
  end
555
564
 
556
565
  def matches_visibility_filters?(node)
@@ -578,17 +587,21 @@ module Capybara
578
587
 
579
588
  def matches_text_exactly?(node, value)
580
589
  regexp = value.is_a?(Regexp) ? value : /\A#{Regexp.escape(value.to_s)}\z/
581
- matches_text_regexp?(node, regexp)
590
+ matches_text_regexp(node, regexp).then { |m| m&.pre_match == '' && m&.post_match == '' }
582
591
  end
583
592
 
584
593
  def normalize_ws
585
594
  options.fetch(:normalize_ws, session_options.default_normalize_ws)
586
595
  end
587
596
 
588
- def matches_text_regexp?(node, regexp)
597
+ def matches_text_regexp(node, regexp)
589
598
  text_visible = visible
590
599
  text_visible = :all if text_visible == :hidden
591
- node.text(text_visible, normalize_ws: normalize_ws).match?(regexp)
600
+ node.text(text_visible, normalize_ws: normalize_ws).match(regexp)
601
+ end
602
+
603
+ def matches_text_regexp?(node, regexp)
604
+ !matches_text_regexp(node, regexp).nil?
592
605
  end
593
606
 
594
607
  def default_visibility
@@ -20,6 +20,8 @@ class Capybara::RackTest::Browser
20
20
  end
21
21
 
22
22
  def visit(path, **attributes)
23
+ @new_visit_request = true
24
+ reset_cache!
23
25
  reset_host!
24
26
  process_and_follow_redirects(:get, path, attributes)
25
27
  end
@@ -33,19 +35,18 @@ class Capybara::RackTest::Browser
33
35
  path = request_path if path.nil? || path.empty?
34
36
  uri = build_uri(path)
35
37
  uri.query = '' if method.to_s.casecmp('get').zero?
36
- process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
38
+ process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => referer_url)
37
39
  end
38
40
 
39
41
  def follow(method, path, **attributes)
40
42
  return if fragment_or_script?(path)
41
43
 
42
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
44
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
43
45
  end
44
46
 
45
47
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
46
48
  @current_fragment = build_uri(path).fragment
47
49
  process(method, path, attributes, env)
48
-
49
50
  return unless driver.follow_redirects?
50
51
 
51
52
  driver.redirect_limit.times do
@@ -69,18 +70,23 @@ class Capybara::RackTest::Browser
69
70
  @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
70
71
  @current_fragment = new_uri.fragment || @current_fragment
71
72
  reset_cache!
73
+ @new_visit_request = false
72
74
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
73
75
  end
74
76
 
75
77
  def build_uri(path)
76
- URI.parse(path).tap do |uri|
77
- uri.path = request_path if path.empty? || path.start_with?('?')
78
- uri.path = '/' if uri.path.empty?
79
- uri.path = request_path.sub(%r{/[^/]*$}, '/') + uri.path unless uri.path.start_with?('/')
78
+ uri = URI.parse(path)
79
+ base_uri = base_relative_uri_for(uri)
80
80
 
81
+ uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')
82
+
83
+ if base_uri.absolute?
84
+ base_uri.merge(uri)
85
+ else
81
86
  uri.scheme ||= @current_scheme
82
87
  uri.host ||= @current_host
83
88
  uri.port ||= @current_port unless uri.default_port == @current_port
89
+ uri
84
90
  end
85
91
  end
86
92
 
@@ -123,8 +129,39 @@ class Capybara::RackTest::Browser
123
129
  dom.title
124
130
  end
125
131
 
132
+ def last_request
133
+ raise Rack::Test::Error if @new_visit_request
134
+
135
+ super
136
+ end
137
+
138
+ def last_response
139
+ raise Rack::Test::Error if @new_visit_request
140
+
141
+ super
142
+ end
143
+
126
144
  protected
127
145
 
146
+ def base_href
147
+ find(:css, 'head > base').first&.[](:href).to_s
148
+ end
149
+
150
+ def base_relative_uri_for(uri)
151
+ base_uri = URI.parse(base_href)
152
+ current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
153
+ c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
154
+ c.path = '/' if c.path.empty?
155
+ end
156
+
157
+ if [current_uri, base_uri].any?(&:absolute?)
158
+ current_uri.merge(base_uri)
159
+ else
160
+ base_uri.path = current_uri.path if base_uri.path.empty?
161
+ base_uri
162
+ end
163
+ end
164
+
128
165
  def build_rack_mock_session
129
166
  reset_host! unless current_host
130
167
  Rack::MockSession.new(app, current_host)
@@ -136,9 +173,21 @@ protected
136
173
  '/'
137
174
  end
138
175
 
176
+ def safe_last_request
177
+ last_request
178
+ rescue Rack::Test::Error
179
+ nil
180
+ end
181
+
139
182
  private
140
183
 
141
184
  def fragment_or_script?(path)
142
185
  path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
143
186
  end
187
+
188
+ def referer_url
189
+ build_uri(last_request.url).to_s
190
+ rescue Rack::Test::Error
191
+ ''
192
+ end
144
193
  end
@@ -98,10 +98,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
98
98
  @browser = nil
99
99
  end
100
100
 
101
- def get(*args, &block); browser.get(*args, &block); end
102
- def post(*args, &block); browser.post(*args, &block); end
103
- def put(*args, &block); browser.put(*args, &block); end
104
- def delete(*args, &block); browser.delete(*args, &block); end
101
+ def get(...); browser.get(...); end
102
+ def post(...); browser.post(...); end
103
+ def put(...); browser.put(...); end
104
+ def delete(...); browser.delete(...); end
105
105
  def header(key, value); browser.header(key, value); end
106
106
 
107
107
  def invalid_element_errors
@@ -244,7 +244,7 @@ private
244
244
  end
245
245
 
246
246
  def follow_link
247
- method = self['data-method'] if driver.options[:respect_data_method]
247
+ method = self['data-method'] || self['data-turbo-method'] if driver.options[:respect_data_method]
248
248
  method ||= :get
249
249
  driver.follow(method, self[:href].to_s)
250
250
  end
@@ -19,9 +19,6 @@ module Capybara
19
19
  def method_missing(method_name, *args, **options, &block)
20
20
  if @registered.respond_to?(method_name)
21
21
  Capybara::Helpers.warn "DEPRECATED: Calling '#{method_name}' on the drivers/servers container is deprecated without replacement"
22
- # RUBY 2.6 will send an empty hash rather than nothing with **options so fix that
23
- return @registered.public_send(method_name, *args, &block) if options.empty?
24
-
25
22
  return @registered.public_send(method_name, *args, **options, &block)
26
23
  end
27
24
  super
@@ -8,10 +8,10 @@ module Capybara
8
8
  class HaveSelector < CountableWrappedElementMatcher
9
9
  def initialize(*args, **kw_args, &filter_block)
10
10
  super
11
- if (RUBY_VERSION >= '2.7') && (@args.size < 2) && @kw_args.keys.any?(String) # rubocop:disable Style/GuardClause
12
- @args.push(@kw_args)
13
- @kw_args = {}
14
- end
11
+ return unless (@args.size < 2) && @kw_args.keys.any?(String)
12
+
13
+ @args.push(@kw_args)
14
+ @kw_args = {}
15
15
  end
16
16
 
17
17
  def element_matches?(el)
@@ -15,36 +15,36 @@ module Capybara
15
15
  # RSpec matcher for whether the element(s) matching a given selector exist.
16
16
  #
17
17
  # @see Capybara::Node::Matchers#assert_selector
18
- def have_selector(*args, **kw_args, &optional_filter_block)
19
- Matchers::HaveSelector.new(*args, **kw_args, &optional_filter_block)
18
+ def have_selector(...)
19
+ Matchers::HaveSelector.new(...)
20
20
  end
21
21
 
22
22
  # RSpec matcher for whether the element(s) matching a group of selectors exist.
23
23
  #
24
24
  # @see Capybara::Node::Matchers#assert_all_of_selectors
25
- def have_all_of_selectors(*args, **kw_args, &optional_filter_block)
26
- Matchers::HaveAllSelectors.new(*args, **kw_args, &optional_filter_block)
25
+ def have_all_of_selectors(...)
26
+ Matchers::HaveAllSelectors.new(...)
27
27
  end
28
28
 
29
29
  # RSpec matcher for whether no element(s) matching a group of selectors exist.
30
30
  #
31
31
  # @see Capybara::Node::Matchers#assert_none_of_selectors
32
- def have_none_of_selectors(*args, **kw_args, &optional_filter_block)
33
- Matchers::HaveNoSelectors.new(*args, **kw_args, &optional_filter_block)
32
+ def have_none_of_selectors(...)
33
+ Matchers::HaveNoSelectors.new(...)
34
34
  end
35
35
 
36
36
  # RSpec matcher for whether the element(s) matching any of a group of selectors exist.
37
37
  #
38
38
  # @see Capybara::Node::Matchers#assert_any_of_selectors
39
- def have_any_of_selectors(*args, **kw_args, &optional_filter_block)
40
- Matchers::HaveAnySelectors.new(*args, **kw_args, &optional_filter_block)
39
+ def have_any_of_selectors(...)
40
+ Matchers::HaveAnySelectors.new(...)
41
41
  end
42
42
 
43
43
  # RSpec matcher for whether the current element matches a given selector.
44
44
  #
45
45
  # @see Capybara::Node::Matchers#assert_matches_selector
46
- def match_selector(*args, **kw_args, &optional_filter_block)
47
- Matchers::MatchSelector.new(*args, **kw_args, &optional_filter_block)
46
+ def match_selector(...)
47
+ Matchers::MatchSelector.new(...)
48
48
  end
49
49
 
50
50
  %i[css xpath].each do |selector|
@@ -177,15 +177,15 @@ module Capybara
177
177
  # RSpec matcher for whether sibling element(s) matching a given selector exist.
178
178
  #
179
179
  # @see Capybara::Node::Matchers#assert_sibling
180
- def have_sibling(*args, **kw_args, &optional_filter_block)
181
- Matchers::HaveSibling.new(*args, **kw_args, &optional_filter_block)
180
+ def have_sibling(...)
181
+ Matchers::HaveSibling.new(...)
182
182
  end
183
183
 
184
184
  # RSpec matcher for whether ancestor element(s) matching a given selector exist.
185
185
  #
186
186
  # @see Capybara::Node::Matchers#assert_ancestor
187
- def have_ancestor(*args, **kw_args, &optional_filter_block)
188
- Matchers::HaveAncestor.new(*args, **kw_args, &optional_filter_block)
187
+ def have_ancestor(...)
188
+ Matchers::HaveAncestor.new(...)
189
189
  end
190
190
 
191
191
  ##
@@ -261,7 +261,8 @@ module Capybara
261
261
  def parameter_names(block)
262
262
  key_types = %i[key keyreq]
263
263
  # user filter_map when we drop dupport for 2.6
264
- block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_, name)| name }
264
+ # block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_, name)| name }
265
+ block.parameters.filter_map { |(type, name)| name if key_types.include? type }
265
266
  end
266
267
 
267
268
  def expression(type, allowed_filters, &block)
@@ -66,7 +66,11 @@ module Capybara
66
66
  end
67
67
  ensure
68
68
  unless locator_valid?(locator)
69
- warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara."
69
+ Capybara::Helpers.warn(
70
+ "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. " \
71
+ 'This will raise an error in a future version of Capybara. ' \
72
+ "Called from: #{Capybara::Helpers.filter_backtrace(caller)}"
73
+ )
70
74
  end
71
75
  end
72
76
 
@@ -12,7 +12,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
12
12
  clear_session_storage: nil
13
13
  }.freeze
14
14
  SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
15
- CAPS_VERSION = Gem::Requirement.new('~> 4.0.0.alpha6')
15
+ CAPS_VERSION = Gem::Requirement.new('>= 4.0.0.alpha6')
16
16
 
17
17
  attr_reader :app, :options
18
18
 
@@ -473,12 +473,16 @@ private
473
473
  end
474
474
 
475
475
  def unwrap_script_result(arg)
476
+ # TODO: move into the case when we drop support for Selenium < 4.1
477
+ element_types = [Selenium::WebDriver::Element]
478
+ element_types.push(Selenium::WebDriver::ShadowRoot) if defined?(Selenium::WebDriver::ShadowRoot)
479
+
476
480
  case arg
477
481
  when Array
478
482
  arg.map { |arr| unwrap_script_result(arr) }
479
483
  when Hash
480
484
  arg.transform_values! { |value| unwrap_script_result(value) }
481
- when Selenium::WebDriver::Element
485
+ when *element_types
482
486
  build_node(arg)
483
487
  else
484
488
  arg
@@ -53,6 +53,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
53
53
  # :none => append the new value to the existing value <br/>
54
54
  # :backspace => send backspace keystrokes to clear the field <br/>
55
55
  # Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
56
+ # @option options [Boolean] :rapid (nil) Whether setting text inputs should use a faster &quot;rapid&quot; mode<br/>
57
+ # nil => Text inputs with length greater than 30 characters will be set using a faster driver script mode<br/>
58
+ # true => Rapid mode will be used regardless of input length<br/>
59
+ # false => Sends keys via conventional mode. This may be required to avoid losing key-presses if you have certain
60
+ # Javascript interactions on form inputs<br/>
56
61
  def set(value, **options)
57
62
  if value.is_a?(Array) && !multiple?
58
63
  raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
@@ -214,6 +219,13 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
214
219
  native.rect
215
220
  end
216
221
 
222
+ def shadow_root
223
+ raise_error 'You must be using Selenium 4.1+ for shadow_root support' unless native.respond_to? :shadow_root
224
+
225
+ root = native.shadow_root
226
+ root && build_node(native.shadow_root)
227
+ end
228
+
217
229
  protected
218
230
 
219
231
  def scroll_if_needed
@@ -26,9 +26,10 @@ module Capybara
26
26
  @status, @headers, @body = @app.call(env)
27
27
  return [@status, @headers, @body] unless html_content?
28
28
 
29
+ nonces = directive_nonces.transform_values { |nonce| "nonce=\"#{nonce}\"" if nonce && !nonce.empty? }
29
30
  response = Rack::Response.new([], @status, @headers)
30
31
 
31
- @body.each { |html| response.write insert_disable(html) }
32
+ @body.each { |html| response.write insert_disable(html, nonces) }
32
33
  @body.close if @body.respond_to?(:close)
33
34
 
34
35
  response.finish
@@ -42,28 +43,39 @@ module Capybara
42
43
  /html/.match?(@headers['Content-Type'])
43
44
  end
44
45
 
45
- def insert_disable(html)
46
- html.sub(%r{(</head>)}, "#{disable_css_markup}\\1").sub(%r{(</body>)}, "#{disable_js_markup}\\1")
46
+ def insert_disable(html, nonces)
47
+ html.sub(%r{(</head>)}, "<style #{nonces['style-src']}>#{disable_css_markup}</style>\\1")
48
+ .sub(%r{(</body>)}, "<script #{nonces['script-src']}>#{disable_js_markup}</script>\\1")
47
49
  end
48
50
 
49
- DISABLE_CSS_MARKUP_TEMPLATE = <<~HTML
50
- <style>
51
- %<selector>s, %<selector>s::before, %<selector>s::after {
52
- transition: none !important;
53
- animation-duration: 0s !important;
54
- animation-delay: 0s !important;
55
- scroll-behavior: auto !important;
56
- }
57
- </style>
58
- HTML
51
+ def directive_nonces
52
+ @headers.fetch('Content-Security-Policy', '')
53
+ .split(';')
54
+ .map(&:split)
55
+ .to_h do |s|
56
+ [
57
+ s[0], s[1..].filter_map do |value|
58
+ /^'nonce-(?<nonce>.+)'/ =~ value
59
+ nonce
60
+ end[0]
61
+ ]
62
+ end
63
+ end
64
+
65
+ DISABLE_CSS_MARKUP_TEMPLATE = <<~CSS
66
+ %<selector>s, %<selector>s::before, %<selector>s::after {
67
+ transition: none !important;
68
+ animation-duration: 0s !important;
69
+ animation-delay: 0s !important;
70
+ scroll-behavior: auto !important;
71
+ }
72
+ CSS
59
73
 
60
- DISABLE_JS_MARKUP_TEMPLATE = <<~HTML
61
- <script>
74
+ DISABLE_JS_MARKUP_TEMPLATE = <<~SCRIPT
62
75
  //<![CDATA[
63
76
  (typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
64
77
  //]]>
65
- </script>
66
- HTML
78
+ SCRIPT
67
79
  end
68
80
  end
69
81
  end
@@ -100,7 +100,7 @@ module Capybara
100
100
  remove_method :test_id=
101
101
  ##
102
102
  #
103
- # Set an attribue to be optionally matched against the locator for builtin selector types.
103
+ # Set an attribute to be optionally matched against the locator for builtin selector types.
104
104
  # This attribute will be checked by builtin selector types whenever id would normally be checked.
105
105
  # If `nil` then it will be ignored.
106
106
  #