capybara 3.36.0 → 3.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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