capybara 3.36.0 → 3.37.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +20 -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/minitest/spec.rb +2 -2
  7. data/lib/capybara/node/element.rb +12 -1
  8. data/lib/capybara/node/finders.rb +1 -1
  9. data/lib/capybara/queries/selector_query.rb +20 -7
  10. data/lib/capybara/rack_test/browser.rb +41 -6
  11. data/lib/capybara/rack_test/driver.rb +4 -4
  12. data/lib/capybara/rack_test/node.rb +1 -1
  13. data/lib/capybara/registration_container.rb +0 -3
  14. data/lib/capybara/rspec/matchers/have_selector.rb +4 -4
  15. data/lib/capybara/rspec/matchers.rb +14 -14
  16. data/lib/capybara/selector/definition.rb +2 -1
  17. data/lib/capybara/selenium/driver.rb +6 -2
  18. data/lib/capybara/selenium/node.rb +12 -0
  19. data/lib/capybara/server/animation_disabler.rb +29 -17
  20. data/lib/capybara/session/config.rb +1 -1
  21. data/lib/capybara/session.rb +8 -21
  22. data/lib/capybara/spec/session/all_spec.rb +5 -7
  23. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  24. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  25. data/lib/capybara/spec/session/has_field_spec.rb +1 -1
  26. data/lib/capybara/spec/session/has_select_spec.rb +4 -4
  27. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  28. data/lib/capybara/spec/session/has_text_spec.rb +1 -5
  29. data/lib/capybara/spec/session/node_spec.rb +42 -0
  30. data/lib/capybara/spec/session/visit_spec.rb +14 -0
  31. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  32. data/lib/capybara/spec/test_app.rb +33 -0
  33. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  34. data/lib/capybara/version.rb +1 -1
  35. data/lib/capybara/window.rb +1 -1
  36. data/lib/capybara.rb +1 -0
  37. data/spec/dsl_spec.rb +2 -2
  38. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  39. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  40. data/spec/rack_test_spec.rb +6 -0
  41. data/spec/result_spec.rb +27 -29
  42. data/spec/rspec/features_spec.rb +2 -2
  43. data/spec/rspec/scenarios_spec.rb +1 -1
  44. data/spec/sauce_spec_chrome.rb +3 -3
  45. data/spec/selector_spec.rb +1 -1
  46. data/spec/selenium_spec_chrome.rb +1 -1
  47. data/spec/selenium_spec_firefox.rb +5 -1
  48. data/spec/selenium_spec_safari.rb +5 -1
  49. data/spec/server_spec.rb +5 -5
  50. data/spec/shared_selenium_session.rb +9 -2
  51. data/spec/spec_helper.rb +1 -1
  52. 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: 125837a4db6ae6c9b12ab2339f5daa673397ef9cdb9054689fb1d155a5d6e62f
4
+ data.tar.gz: 445b064e8ae642678457cbf2739a1a6124b7d35dd6aaf295d3d9edce4f6ee8f3
5
5
  SHA512:
6
- metadata.gz: ad7d2ea4d2612770e801443e61b23a45c0b9936fa4387170ffbbf051706dbc8aee5111b8eefb2d6fabb3042b479dc19d26248923d84da3e78d12dc090aca1e2d
7
- data.tar.gz: 8868469dc6ddcb6c802f4a7b76df9455ff561d00ad78e3cab75e5f02e1b71b74402700c081864368d446304acb6fac2fed5cb79df117d309d4a0a278c95e35d8
6
+ metadata.gz: 95154994ad904eae4c9eeb73605bcc273daef240a0cbf241afa8cf1bcfef1ca2de52ecd159fe551ee2d863a0a9a07a2fb9a3beab659f334ec5782f50c5af4ba1
7
+ data.tar.gz: 600e6610c5c48ac5619d1c4cf2ddfc83b86d5315ea259c7d8a6476863a14d4a209bcec0c6334dab5f3ebefac5eaeb1ccaa8a263114a5838f7a49a8483fb44538
data/History.md CHANGED
@@ -1,3 +1,22 @@
1
+ # Version 3.37.0
2
+ Release date: 2022-05-07
3
+
4
+ ### Changed
5
+
6
+ * Ruby 2.7.0+ is now required
7
+
8
+ ### Added
9
+
10
+ * [Beta] CSP nonces inserted into animation disabler additions - Issue #2542
11
+ * Support `<base>` element in rack-test driver - ISsue #2544
12
+ * [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.
13
+ * Regexp now supported for `exact_text` finder option
14
+
15
+ ### Fixed
16
+
17
+ * Fragments in referer headers in rack-test driver - Issue #2525
18
+ * Selenium v4.1 deprecation notice
19
+
1
20
  # Version 3.36.0
2
21
  Release date: 2021-10-24
3
22
 
@@ -10,7 +29,7 @@ Release date: 2021-10-24
10
29
 
11
30
  * Support for selenium-webdriver 4.x
12
31
  * `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
32
+ * Deprecated `allow_gumbo=` in favor of `use_html5_parsing=` to enable use of Nokogiri::HTML5 when available
14
33
  * `Session#active_element` returns the element with focus - Not supported by the `RackTest` driver [Sean Doyle]
15
34
  * Support `focused:` filter for finding interactive elements - Not supported by the `RackTest` driver [Sean Doyle]
16
35
 
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
 
@@ -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
@@ -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
@@ -33,13 +33,13 @@ class Capybara::RackTest::Browser
33
33
  path = request_path if path.nil? || path.empty?
34
34
  uri = build_uri(path)
35
35
  uri.query = '' if method.to_s.casecmp('get').zero?
36
- process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
36
+ process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => referer_url)
37
37
  end
38
38
 
39
39
  def follow(method, path, **attributes)
40
40
  return if fragment_or_script?(path)
41
41
 
42
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
42
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
43
43
  end
44
44
 
45
45
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
@@ -73,14 +73,18 @@ class Capybara::RackTest::Browser
73
73
  end
74
74
 
75
75
  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?('/')
76
+ uri = URI.parse(path)
77
+ base_uri = base_relative_uri_for(uri)
80
78
 
79
+ uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')
80
+
81
+ if base_uri.absolute?
82
+ base_uri.merge(uri)
83
+ else
81
84
  uri.scheme ||= @current_scheme
82
85
  uri.host ||= @current_host
83
86
  uri.port ||= @current_port unless uri.default_port == @current_port
87
+ uri
84
88
  end
85
89
  end
86
90
 
@@ -125,6 +129,25 @@ class Capybara::RackTest::Browser
125
129
 
126
130
  protected
127
131
 
132
+ def base_href
133
+ find(:css, 'head > base').first&.[](:href).to_s
134
+ end
135
+
136
+ def base_relative_uri_for(uri)
137
+ base_uri = URI.parse(base_href)
138
+ current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
139
+ c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
140
+ c.path = '/' if c.path.empty?
141
+ end
142
+
143
+ if [current_uri, base_uri].any?(&:absolute?)
144
+ current_uri.merge(base_uri)
145
+ else
146
+ base_uri.path = current_uri.path if base_uri.path.empty?
147
+ base_uri
148
+ end
149
+ end
150
+
128
151
  def build_rack_mock_session
129
152
  reset_host! unless current_host
130
153
  Rack::MockSession.new(app, current_host)
@@ -136,9 +159,21 @@ protected
136
159
  '/'
137
160
  end
138
161
 
162
+ def safe_last_request
163
+ last_request
164
+ rescue Rack::Test::Error
165
+ nil
166
+ end
167
+
139
168
  private
140
169
 
141
170
  def fragment_or_script?(path)
142
171
  path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
143
172
  end
173
+
174
+ def referer_url
175
+ build_uri(last_request.url).to_s
176
+ rescue Rack::Test::Error
177
+ ''
178
+ end
144
179
  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)
@@ -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
  #
@@ -765,33 +765,20 @@ module Capybara
765
765
  end
766
766
 
767
767
  NODE_METHODS.each do |method|
768
- if RUBY_VERSION >= '2.7'
769
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
770
- def #{method}(...)
771
- @touched = true
772
- current_scope.#{method}(...)
773
- end
774
- METHOD
775
- else
776
- define_method method do |*args, &block|
768
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
769
+ def #{method}(...)
777
770
  @touched = true
778
- current_scope.send(method, *args, &block)
771
+ current_scope.#{method}(...)
779
772
  end
780
- end
773
+ METHOD
781
774
  end
782
775
 
783
776
  DOCUMENT_METHODS.each do |method|
784
- if RUBY_VERSION >= '2.7'
785
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
786
- def #{method}(...)
787
- document.#{method}(...)
788
- end
789
- METHOD
790
- else
791
- define_method method do |*args, &block|
792
- document.send(method, *args, &block)
777
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
778
+ def #{method}(...)
779
+ document.#{method}(...)
793
780
  end
794
- end
781
+ METHOD
795
782
  end
796
783
 
797
784
  def inspect
@@ -132,7 +132,7 @@ Capybara::SpecHelper.spec '#all' do
132
132
  it 'should use the sessions ignore_hidden_elements', psc: true do
133
133
  Capybara.ignore_hidden_elements = true
134
134
  @session.config.ignore_hidden_elements = false
135
- expect(Capybara.ignore_hidden_elements).to eq(true)
135
+ expect(Capybara.ignore_hidden_elements).to be(true)
136
136
  expect(@session.all(:css, 'a.simple').size).to eq(2)
137
137
  @session.config.ignore_hidden_elements = true
138
138
  expect(@session.all(:css, 'a.simple').size).to eq(1)
@@ -208,12 +208,10 @@ Capybara::SpecHelper.spec '#all' do
208
208
  expect { @session.all(:css, 'h1, p', between: 5..) }.to raise_error(Capybara::ExpectationNotMet)
209
209
  end
210
210
 
211
- eval <<~TEST, binding, __FILE__, __LINE__ + 1 if RUBY_VERSION.to_f > 2.6
212
- it'treats a beginless range as maximum' do
213
- expect { @session.all(:css, 'h1, p', between: ..7) }.not_to raise_error
214
- expect { @session.all(:css, 'h1, p', between: ..3) }.to raise_error(Capybara::ExpectationNotMet)
215
- end
216
- TEST
211
+ it 'treats a beginless range as maximum' do
212
+ expect { @session.all(:css, 'h1, p', between: ..7) }.not_to raise_error
213
+ expect { @session.all(:css, 'h1, p', between: ..3) }.to raise_error(Capybara::ExpectationNotMet)
214
+ end
217
215
  end
218
216
 
219
217
  context 'with multiple count filters' do