capybara 3.35.3 → 3.36.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +31 -1
  3. data/README.md +5 -1
  4. data/lib/capybara/config.rb +16 -4
  5. data/lib/capybara/driver/base.rb +4 -0
  6. data/lib/capybara/driver/node.rb +1 -1
  7. data/lib/capybara/helpers.rb +2 -11
  8. data/lib/capybara/node/actions.rb +10 -5
  9. data/lib/capybara/node/document.rb +2 -2
  10. data/lib/capybara/node/element.rb +1 -1
  11. data/lib/capybara/node/finders.rb +1 -1
  12. data/lib/capybara/node/simple.rb +5 -1
  13. data/lib/capybara/queries/active_element_query.rb +18 -0
  14. data/lib/capybara/queries/ancestor_query.rb +2 -1
  15. data/lib/capybara/queries/current_path_query.rb +1 -1
  16. data/lib/capybara/queries/selector_query.rb +14 -1
  17. data/lib/capybara/queries/sibling_query.rb +2 -1
  18. data/lib/capybara/rack_test/node.rb +9 -6
  19. data/lib/capybara/registrations/drivers.rb +3 -3
  20. data/lib/capybara/rspec/matcher_proxies.rb +3 -3
  21. data/lib/capybara/rspec/matchers/have_selector.rb +1 -1
  22. data/lib/capybara/selector/builders/css_builder.rb +1 -1
  23. data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
  24. data/lib/capybara/selector/css.rb +1 -1
  25. data/lib/capybara/selector/definition/button.rb +9 -4
  26. data/lib/capybara/selector/definition/checkbox.rb +1 -1
  27. data/lib/capybara/selector/definition/file_field.rb +1 -1
  28. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  29. data/lib/capybara/selector/definition/radio_button.rb +1 -1
  30. data/lib/capybara/selector/definition.rb +2 -1
  31. data/lib/capybara/selector/filter_set.rb +4 -6
  32. data/lib/capybara/selector.rb +1 -0
  33. data/lib/capybara/selenium/driver.rb +19 -9
  34. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
  35. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +1 -1
  36. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  37. data/lib/capybara/selenium/node.rb +10 -8
  38. data/lib/capybara/selenium/nodes/chrome_node.rb +1 -1
  39. data/lib/capybara/selenium/nodes/edge_node.rb +1 -1
  40. data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
  41. data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
  42. data/lib/capybara/server/animation_disabler.rb +15 -9
  43. data/lib/capybara/session.rb +12 -2
  44. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  45. data/lib/capybara/spec/session/all_spec.rb +4 -6
  46. data/lib/capybara/spec/session/check_spec.rb +9 -0
  47. data/lib/capybara/spec/session/choose_spec.rb +6 -0
  48. data/lib/capybara/spec/session/has_any_selectors_spec.rb +4 -0
  49. data/lib/capybara/spec/session/has_button_spec.rb +24 -0
  50. data/lib/capybara/spec/session/has_field_spec.rb +24 -0
  51. data/lib/capybara/spec/session/has_link_spec.rb +24 -0
  52. data/lib/capybara/spec/session/has_text_spec.rb +1 -1
  53. data/lib/capybara/spec/session/node_spec.rb +1 -1
  54. data/lib/capybara/spec/session/scroll_spec.rb +4 -4
  55. data/lib/capybara/spec/spec_helper.rb +4 -3
  56. data/lib/capybara/spec/test_app.rb +17 -8
  57. data/lib/capybara/spec/views/animated.erb +1 -1
  58. data/lib/capybara/spec/views/form.erb +11 -3
  59. data/lib/capybara/spec/views/frame_child.erb +1 -1
  60. data/lib/capybara/spec/views/frame_one.erb +1 -1
  61. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  62. data/lib/capybara/spec/views/frame_two.erb +1 -1
  63. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  64. data/lib/capybara/spec/views/layout.erb +10 -0
  65. data/lib/capybara/spec/views/obscured.erb +1 -1
  66. data/lib/capybara/spec/views/offset.erb +2 -1
  67. data/lib/capybara/spec/views/path.erb +2 -2
  68. data/lib/capybara/spec/views/popup_one.erb +1 -1
  69. data/lib/capybara/spec/views/popup_two.erb +1 -1
  70. data/lib/capybara/spec/views/react.erb +2 -2
  71. data/lib/capybara/spec/views/scroll.erb +2 -1
  72. data/lib/capybara/spec/views/spatial.erb +1 -1
  73. data/lib/capybara/spec/views/with_animation.erb +2 -3
  74. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  75. data/lib/capybara/spec/views/with_dragula.erb +2 -2
  76. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  77. data/lib/capybara/spec/views/with_hover.erb +2 -2
  78. data/lib/capybara/spec/views/with_html.erb +1 -1
  79. data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
  80. data/lib/capybara/spec/views/with_js.erb +2 -3
  81. data/lib/capybara/spec/views/with_jstree.erb +1 -1
  82. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  83. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  84. data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
  85. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  86. data/lib/capybara/spec/views/with_windows.erb +1 -1
  87. data/lib/capybara/spec/views/within_frames.erb +1 -1
  88. data/lib/capybara/version.rb +1 -1
  89. data/lib/capybara.rb +18 -22
  90. data/spec/basic_node_spec.rb +16 -3
  91. data/spec/dsl_spec.rb +1 -1
  92. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  93. data/spec/rack_test_spec.rb +14 -10
  94. data/spec/result_spec.rb +5 -6
  95. data/spec/rspec/features_spec.rb +1 -1
  96. data/spec/rspec/shared_spec_matchers.rb +2 -2
  97. data/spec/selenium_spec_chrome.rb +8 -9
  98. data/spec/selenium_spec_chrome_remote.rb +9 -8
  99. data/spec/selenium_spec_firefox.rb +4 -3
  100. data/spec/selenium_spec_firefox_remote.rb +2 -2
  101. data/spec/selenium_spec_ie.rb +3 -6
  102. data/spec/selenium_spec_safari.rb +27 -19
  103. data/spec/shared_selenium_node.rb +0 -4
  104. data/spec/shared_selenium_session.rb +11 -8
  105. metadata +35 -14
  106. data/lib/capybara/spec/views/with_title.erb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 516861530f59d3d9713daba7379838146575b920f787ac2c3f5c4bbffc6bf44d
4
- data.tar.gz: 85b3fb81bf05e43122c2eef320a7a2dbd500033723ebbc04ebc414b0a42d0881
3
+ metadata.gz: fd7f81974b21d47d35c99dff02f878607128c99c80b18b1fef54680797978898
4
+ data.tar.gz: ee6659925b7ddaaa0220ad0af81ae65a34e96094583308c160902470e7019128
5
5
  SHA512:
6
- metadata.gz: 7f745dac92b73afa56f43a2a509052c7fc5d1efa458863b0b3f05507c664847d6f67720a2c2a680e21126888e63a6eaf6a0e9be7d1a4df9d6aaa9ec61d832e29
7
- data.tar.gz: 53532156dd31631879a0a8fda93126f259a5cd856a6cec82e6283f9f96e1cc2d98b788e4414ffee5756024d14819450d358529aa90d4c948618e82b878342ebd
6
+ metadata.gz: ad7d2ea4d2612770e801443e61b23a45c0b9936fa4387170ffbbf051706dbc8aee5111b8eefb2d6fabb3042b479dc19d26248923d84da3e78d12dc090aca1e2d
7
+ data.tar.gz: 8868469dc6ddcb6c802f4a7b76df9455ff561d00ad78e3cab75e5f02e1b71b74402700c081864368d446304acb6fac2fed5cb79df117d309d4a0a278c95e35d8
data/History.md CHANGED
@@ -1,7 +1,38 @@
1
+ # Version 3.36.0
2
+ Release date: 2021-10-24
3
+
4
+ ### Changed
5
+
6
+ * Ruby 2.6.0+ is now required
7
+ * Minimum selenium-webdriver supported is now 3.142.7
8
+
9
+ ### Added
10
+
11
+ * Support for selenium-webdriver 4.x
12
+ * `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
14
+ * `Session#active_element` returns the element with focus - Not supported by the `RackTest` driver [Sean Doyle]
15
+ * Support `focused:` filter for finding interactive elements - Not supported by the `RackTest` driver [Sean Doyle]
16
+
17
+ ### Fixed
18
+
19
+ * Sibling and ancestor queries now work with Simple::Node - Issue #2452
20
+ * rack_test correctly ignores readonly attribute on specific input element types
21
+ * `Node#all_text` always returns a string - Issue #2477
22
+ * `have_any_of_selectors` negated match - Issue #2473
23
+ * `Document#scroll_to` fixed for standards behavior - pass quirks: true if you need the older behavior [Eric Anderson]
24
+ * Use capture on attach file event listener for better React compatibility [Jeff Way]
25
+ * Animation disabler produces valid HTML [Javi Martin]
26
+
27
+ ### Removed
28
+
29
+ * References to non-w3c mode in drivers/tests. Non-w3c mode is obsolete and no one should be using it anymore. Capybara hasn't been testing/supporting it in a while
30
+
1
31
  # Version 3.35.3
2
32
  Release date: 2021-01-29
3
33
 
4
34
  ### Fixed
35
+
5
36
  * Just a release to have the correct dates in the History.md in released gem
6
37
 
7
38
  # Version 3.35.2
@@ -1898,4 +1929,3 @@ too many users, please read the release notes carefully!
1898
1929
  * clicking links where the image's alt attribute contains the text is now possible
1899
1930
  * within_fieldset and within_table work when the default selector is CSS
1900
1931
  * boolean attributes work the same across drivers (return true/false)
1901
-
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.5.0 or later. To install, add this line to your
77
+ Capybara requires Ruby 2.6.0 or later. To install, add this line to your
78
78
  `Gemfile` and run `bundle install`:
79
79
 
80
80
  ```ruby
@@ -151,6 +151,8 @@ RSpec](https://relishapp.com/rspec/rspec-rails/v/4-0/docs/directory-structure))
151
151
  and if you have your Capybara specs in a different directory, then tag the
152
152
  example groups with `type: :feature` or `type: :system` depending on which type of test you're writing.
153
153
 
154
+ If you are using Rails system specs please see [their documentation](https://relishapp.com/rspec/rspec-rails/docs/system-specs/system-spec#system-specs-driven-by-selenium-chrome-headless) for selecting the driver you wish to use.
155
+
154
156
  If you are not using Rails, tag all the example groups in which you want to use
155
157
  Capybara with `type: :feature`.
156
158
 
@@ -258,6 +260,8 @@ end
258
260
 
259
261
  ## <a name="using-capybara-with-minitest"></a>Using Capybara with Minitest
260
262
 
263
+ * If you are using Rails system tests please see their documentation for information on selecting the driver you wish to use.
264
+
261
265
  * If you are using Rails, but not using Rails system tests, add the following code in your `test_helper.rb`
262
266
  file to make Capybara available in all test cases deriving from
263
267
  `ActionDispatch::IntegrationTest`:
@@ -7,10 +7,12 @@ module Capybara
7
7
  class Config
8
8
  extend Forwardable
9
9
 
10
- OPTIONS = %i[app reuse_server threadsafe server default_driver javascript_driver allow_gumbo].freeze
10
+ OPTIONS = %i[
11
+ app reuse_server threadsafe server default_driver javascript_driver use_html5_parsing allow_gumbo
12
+ ].freeze
11
13
 
12
- attr_accessor :app, :allow_gumbo
13
- attr_reader :reuse_server, :threadsafe, :session_options
14
+ attr_accessor :app, :use_html5_parsing
15
+ attr_reader :reuse_server, :threadsafe, :session_options # rubocop:disable Style/BisectedAttrAccessor
14
16
  attr_writer :default_driver, :javascript_driver
15
17
 
16
18
  SessionConfig::OPTIONS.each do |method|
@@ -22,7 +24,7 @@ module Capybara
22
24
  @javascript_driver = nil
23
25
  end
24
26
 
25
- attr_writer :reuse_server
27
+ attr_writer :reuse_server # rubocop:disable Style/BisectedAttrAccessor
26
28
 
27
29
  def threadsafe=(bool)
28
30
  if (bool != threadsafe) && Session.instance_created?
@@ -88,5 +90,15 @@ module Capybara
88
90
  end
89
91
  @deprecation_notified[method] = true
90
92
  end
93
+
94
+ def allow_gumbo=(val)
95
+ deprecate('allow_gumbo=', 'use_html5_parsing=')
96
+ self.use_html5_parsing = val
97
+ end
98
+
99
+ def allow_gumbo
100
+ deprecate('allow_gumbo', 'use_html5_parsing')
101
+ use_html5_parsing
102
+ end
91
103
  end
92
104
  end
@@ -63,6 +63,10 @@ class Capybara::Driver::Base
63
63
  raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#send_keys'
64
64
  end
65
65
 
66
+ def active_element
67
+ raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#active_element'
68
+ end
69
+
66
70
  ##
67
71
  #
68
72
  # @param frame [Capybara::Node::Element, :parent, :top] The iframe element to switch to
@@ -132,7 +132,7 @@ module Capybara
132
132
  end
133
133
 
134
134
  def ==(other)
135
- raise NotSupportedByDriverError, 'Capybara::Driver::Node#=='
135
+ eql?(other) || (other.respond_to?(:native) && native == other.native)
136
136
  end
137
137
  end
138
138
  end
@@ -75,23 +75,14 @@ module Capybara
75
75
 
76
76
  filter = %r{lib/capybara/|lib/rspec/|lib/minitest/}
77
77
  new_trace = trace.take_while { |line| line !~ filter }
78
- new_trace = trace.reject { |line| line =~ filter } if new_trace.empty?
78
+ new_trace = trace.grep_v(filter) if new_trace.empty?
79
79
  new_trace = trace.dup if new_trace.empty?
80
80
 
81
81
  new_trace.first.split(/:in /, 2).first
82
82
  end
83
83
 
84
84
  def warn(message, uplevel: 1)
85
- return Kernel.warn(message, uplevel: uplevel) if RUBY_VERSION >= '2.6'
86
-
87
- # TODO: Remove when we drop support for Ruby 2.5
88
- # Workaround for emulating `warn '...', uplevel: n` in Ruby 2.5 or lower.
89
- if (match = /^(?<file>.+?):(?<line>\d+)(?::in `.*')?/.match(caller[uplevel]))
90
- location = [match[:file], match[:line]].join(':')
91
- Kernel.warn "#{location}: #{message}"
92
- else
93
- Kernel.warn message
94
- end
85
+ Kernel.warn(message, uplevel: uplevel)
95
86
  end
96
87
 
97
88
  if defined?(Process::CLOCK_MONOTONIC)
@@ -92,8 +92,9 @@ module Capybara
92
92
  end
93
93
 
94
94
  # @!macro label_click
95
- # @option options [Boolean] allow_label_click
95
+ # @option options [Boolean, Hash] allow_label_click
96
96
  # Attempt to click the label to toggle state if element is non-visible. Defaults to {Capybara.configure automatic_label_click}.
97
+ # If set to a Hash it is passed as options to the `click` on the label
97
98
 
98
99
  ##
99
100
  #
@@ -277,7 +278,7 @@ module Capybara
277
278
  # @return [Capybara::Node::Element] The file field element
278
279
  def attach_file(locator = nil, paths, make_visible: nil, **options) # rubocop:disable Style/OptionalArguments
279
280
  if locator && block_given?
280
- raise ArgumentError, '``#attach_file` does not support passing both a locator and a block'
281
+ raise ArgumentError, '`#attach_file` does not support passing both a locator and a block'
281
282
  end
282
283
 
283
284
  Array(paths).each do |path|
@@ -314,7 +315,7 @@ module Capybara
314
315
 
315
316
  begin
316
317
  find(:datalist_input, from, **options)
317
- rescue Capybara::ElementNotFound => dlinput_error # rubocop:disable Naming/RescuedExceptionsVariableName
318
+ rescue Capybara::ElementNotFound => dlinput_error
318
319
  raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
319
320
  end
320
321
  end
@@ -371,7 +372,11 @@ module Capybara
371
372
 
372
373
  begin
373
374
  el ||= find(selector, locator, **options.merge(visible: :all))
374
- el.session.find(:label, for: el, visible: true, match: :first).click unless el.checked? == checked
375
+ unless el.checked? == checked
376
+ el.session
377
+ .find(:label, for: el, visible: true, match: :first)
378
+ .click(**(Hash.try_convert(allow_label_click) || {}))
379
+ end
375
380
  rescue StandardError # swallow extra errors - raise original
376
381
  raise e
377
382
  end
@@ -408,7 +413,7 @@ module Capybara
408
413
  this.removeEventListener('click', file_catcher);
409
414
  e.preventDefault();
410
415
  }
411
- })
416
+ }, {capture: true})
412
417
  JS
413
418
  end
414
419
  end
@@ -40,8 +40,8 @@ module Capybara
40
40
  find(:xpath, '/html').evaluate_script(*args)
41
41
  end
42
42
 
43
- def scroll_to(*args, **options)
44
- find(:xpath, '//body').scroll_to(*args, **options)
43
+ def scroll_to(*args, quirks: false, **options)
44
+ find(:xpath, quirks ? '//body' : '/html').scroll_to(*args, **options)
45
45
  end
46
46
  end
47
47
  end
@@ -550,7 +550,7 @@ module Capybara
550
550
  return self unless @allow_reload
551
551
 
552
552
  begin
553
- reloaded = @query.resolve_for(query_scope.reload)[@query_idx.to_i]
553
+ reloaded = @query.resolve_for(query_scope ? query_scope.reload : session)[@query_idx.to_i]
554
554
  @base = reloaded.base if reloaded
555
555
  rescue StandardError => e
556
556
  raise e unless catch_error?(e)
@@ -22,7 +22,7 @@ module Capybara
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
25
- # Whether the `text`/`exact_text` options are compared against elment text with whitespace normalized or as returned by the driver.
25
+ # Whether the `text`/`exact_text` options are compared against element text with whitespace normalized or as returned by the driver.
26
26
  # Defaults to {Capybara.configure default_normalize_ws}.
27
27
  # @option options [Boolean, Symbol] visible
28
28
  # Only find elements with the specified visibility. Defaults to behavior indicated by {Capybara.configure ignore_hidden_elements}.
@@ -110,7 +110,7 @@ module Capybara
110
110
  # No need for an xpath if only checking the current element
111
111
  !(native.key?('hidden') ||
112
112
  /display:\s?none/.match?(native[:style] || '') ||
113
- %w[script head].include?(tag_name))
113
+ %w[script head style].include?(tag_name))
114
114
  end
115
115
  end
116
116
 
@@ -191,6 +191,10 @@ module Capybara
191
191
  {}
192
192
  end
193
193
 
194
+ def ==(other)
195
+ eql?(other) || (other.respond_to?(:native) && native == other.native)
196
+ end
197
+
194
198
  private
195
199
 
196
200
  def option_value(option)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ # @api private
5
+ module Queries
6
+ class ActiveElementQuery < BaseQuery
7
+ def initialize(**options)
8
+ @options = options
9
+ super(@options)
10
+ end
11
+
12
+ def resolve_for(session)
13
+ node = session.driver.active_element
14
+ [Capybara::Node::Element.new(session, node, nil, self)]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -8,7 +8,8 @@ module Capybara
8
8
  @child_node = node
9
9
 
10
10
  node.synchronize do
11
- match_results = super(node.session.current_scope, exact)
11
+ scope = node.respond_to?(:session) ? node.session.current_scope : node.find(:xpath, '/*')
12
+ match_results = super(scope, exact)
12
13
  ancestors = node.find_xpath(XPath.ancestor.to_s)
13
14
  .map(&method(:to_element))
14
15
  .select { |el| match_results.include?(el) }
@@ -19,7 +19,7 @@ module Capybara
19
19
 
20
20
  def resolves_for?(session)
21
21
  uri = ::Addressable::URI.parse(session.current_url)
22
- @actual_path = (options[:ignore_query] ? uri&.omit(:query) : uri).yield_self do |u|
22
+ @actual_path = (options[:ignore_query] ? uri&.omit(:query) : uri).then do |u|
23
23
  options[:url] ? u&.to_s : u&.request_uri
24
24
  end
25
25
 
@@ -9,7 +9,7 @@ module Capybara
9
9
 
10
10
  SPATIAL_KEYS = %i[above below left_of right_of near].freeze
11
11
  VALID_KEYS = SPATIAL_KEYS + COUNT_KEYS +
12
- %i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set]
12
+ %i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set focused]
13
13
  VALID_MATCH = %i[first smart prefer_exact one].freeze
14
14
 
15
15
  def initialize(*args,
@@ -73,6 +73,8 @@ module Capybara
73
73
 
74
74
  desc << " with id #{options[:id]}" if options[:id]
75
75
  desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
76
+ desc << ' that is focused' if options[:focused]
77
+ desc << ' that is not focused' if options[:focused] == false
76
78
 
77
79
  desc << case options[:style]
78
80
  when String
@@ -371,6 +373,10 @@ module Capybara
371
373
  options.key?(:style) && !custom_keys.include?(:style)
372
374
  end
373
375
 
376
+ def use_default_focused_filter?
377
+ options.key?(:focused) && !custom_keys.include?(:focused)
378
+ end
379
+
374
380
  def use_spatial_filter?
375
381
  options.values_at(*SPATIAL_KEYS).compact.any?
376
382
  end
@@ -435,6 +441,7 @@ module Capybara
435
441
  matches_id_filter?(node) &&
436
442
  matches_class_filter?(node) &&
437
443
  matches_style_filter?(node) &&
444
+ matches_focused_filter?(node) &&
438
445
  matches_text_filter?(node) &&
439
446
  matches_exact_text_filter?(node)
440
447
  end
@@ -494,6 +501,12 @@ module Capybara
494
501
  end
495
502
  end
496
503
 
504
+ def matches_focused_filter?(node)
505
+ return true unless use_default_focused_filter?
506
+
507
+ (node == node.session.active_element) == options[:focused]
508
+ end
509
+
497
510
  def need_to_process_classes?
498
511
  case options[:class]
499
512
  when Regexp then true
@@ -7,7 +7,8 @@ module Capybara
7
7
  def resolve_for(node, exact = nil)
8
8
  @sibling_node = node
9
9
  node.synchronize do
10
- match_results = super(node.session.current_scope, exact)
10
+ scope = node.respond_to?(:session) ? node.session.current_scope : node.find(:xpath, '/*')
11
+ match_results = super(scope, exact)
11
12
  siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
12
13
  .map(&method(:to_element))
13
14
  .select { |el| match_results.include?(el) }
@@ -108,6 +108,13 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
108
108
  end
109
109
  end
110
110
 
111
+ def readonly?
112
+ # readonly attribute not valid on these input types
113
+ return false if input_field? && %w[hidden range color checkbox radio file submit image reset button].include?(type)
114
+
115
+ super
116
+ end
117
+
111
118
  def path
112
119
  native.path
113
120
  end
@@ -139,10 +146,6 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
139
146
  end
140
147
  end
141
148
 
142
- def ==(other)
143
- native == other.native
144
- end
145
-
146
149
  protected
147
150
 
148
151
  # @api private
@@ -159,7 +162,7 @@ protected
159
162
  end.join || ''
160
163
  text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
161
164
  text
162
- else
165
+ else # rubocop:disable Lint/DuplicateBranch
163
166
  ''
164
167
  end
165
168
  end
@@ -213,7 +216,7 @@ private
213
216
  min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
214
217
  value = value.to_f
215
218
  value = value.clamp(min, max)
216
- value = ((value - min) / step).round * step + min
219
+ value = (((value - min) / step).round * step) + min
217
220
  native['value'] = value.clamp(min, max)
218
221
  end
219
222
 
@@ -14,7 +14,7 @@ Capybara.register_driver :selenium_headless do |app|
14
14
  browser_options = ::Selenium::WebDriver::Firefox::Options.new.tap do |opts|
15
15
  opts.add_argument '-headless'
16
16
  end
17
- Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
17
+ Capybara::Selenium::Driver.new(app, **{ :browser => :firefox, options_key => browser_options })
18
18
  end
19
19
 
20
20
  Capybara.register_driver :selenium_chrome do |app|
@@ -25,7 +25,7 @@ Capybara.register_driver :selenium_chrome do |app|
25
25
  opts.add_argument('--disable-site-isolation-trials')
26
26
  end
27
27
 
28
- Capybara::Selenium::Driver.new(app, **Hash[:browser => :chrome, options_key => browser_options])
28
+ Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
29
29
  end
30
30
 
31
31
  Capybara.register_driver :selenium_chrome_headless do |app|
@@ -38,5 +38,5 @@ Capybara.register_driver :selenium_chrome_headless do |app|
38
38
  opts.add_argument('--disable-site-isolation-trials')
39
39
  end
40
40
 
41
- Capybara::Selenium::Driver.new(app, **Hash[:browser => :chrome, options_key => browser_options])
41
+ Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
42
42
  end
@@ -23,7 +23,7 @@ end
23
23
  if RUBY_ENGINE == 'jruby'
24
24
  # :nocov:
25
25
  module Capybara::DSL
26
- class <<self
26
+ class << self
27
27
  remove_method :included
28
28
 
29
29
  def included(base)
@@ -55,7 +55,7 @@ else
55
55
  end
56
56
 
57
57
  def self.prepended(base)
58
- class <<base
58
+ class << base
59
59
  prepend ClassMethods
60
60
  end
61
61
  end
@@ -70,7 +70,7 @@ else
70
70
  end
71
71
 
72
72
  def self.prepended(base)
73
- class <<base
73
+ class << base
74
74
  prepend ClassMethods
75
75
  end
76
76
  end
@@ -64,7 +64,7 @@ module Capybara
64
64
  el.assert_any_of_selectors(*@args, **session_query_options, &@filter_block)
65
65
  end
66
66
 
67
- def does_not_match?(_actual)
67
+ def does_not_match?(el)
68
68
  el.assert_none_of_selectors(*@args, **session_query_options, &@filter_block)
69
69
  end
70
70
 
@@ -76,7 +76,7 @@ module Capybara
76
76
  else
77
77
  cls = Array(classes).reject { |c| c.is_a? Regexp }.group_by { |cl| cl.match?(/^!(?!!!)/) }
78
78
  [(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
79
- cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
79
+ cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..))})" }).join]
80
80
  end
81
81
  end
82
82
  end
@@ -51,7 +51,7 @@ module Capybara
51
51
  else
52
52
  Array(classes).reject { |c| c.is_a? Regexp }.map do |klass|
53
53
  if klass.match?(/^!(?!!!)/)
54
- !XPath.attr(:class).contains_word(klass.slice(1..-1))
54
+ !XPath.attr(:class).contains_word(klass.slice(1..))
55
55
  else
56
56
  XPath.attr(:class).contains_word(klass.sub(/^!!/, ''))
57
57
  end
@@ -43,7 +43,7 @@ module Capybara
43
43
  when '"', "'"
44
44
  selector << parse_string(char, str)
45
45
  when '\\'
46
- selector << char + str.getc
46
+ selector << (char + str.getc)
47
47
  when ','
48
48
  selectors << selector.strip
49
49
  selector.clear
@@ -14,16 +14,17 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
14
14
  XPath.string.n.is(locator) |
15
15
  XPath.descendant(:img)[XPath.attr(:alt).is(locator)]
16
16
 
17
- input_btn_xpath = input_btn_xpath[locator_matchers] + locate_label(locator).descendant(input_btn_xpath)
18
- btn_xpath = btn_xpath[btn_matchers] + locate_label(locator).descendant(btn_xpath)
17
+ label_contains_xpath = locate_label(locator).descendant[labellable_elements]
18
+ input_btn_xpath = input_btn_xpath[locator_matchers]
19
+ btn_xpath = btn_xpath[btn_matchers]
19
20
  aria_btn_xpath = aria_btn_xpath[btn_matchers]
20
21
 
21
22
  alt_matches = XPath.attr(:alt).is(locator)
22
23
  alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
23
- image_btn_xpath = image_btn_xpath[alt_matches] + locate_label(locator).descendant(image_btn_xpath)
24
+ image_btn_xpath = image_btn_xpath[alt_matches]
24
25
  end
25
26
 
26
- btn_xpaths = [input_btn_xpath, btn_xpath, image_btn_xpath]
27
+ btn_xpaths = [input_btn_xpath, btn_xpath, image_btn_xpath, label_contains_xpath].compact
27
28
  btn_xpaths << aria_btn_xpath if enable_aria_role
28
29
 
29
30
  %i[value title type].inject(btn_xpaths.inject(&:union)) do |memo, ef|
@@ -60,4 +61,8 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
60
61
  (XPath.attr(config.test_id) == locator if config.test_id)
61
62
  ].compact.inject(&:|)
62
63
  end
64
+
65
+ def labellable_elements
66
+ (XPath.self(:input) & XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')) | XPath.self(:button)
67
+ end
63
68
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Capybara.add_selector(:checkbox, locator_type: [String, Symbol]) do
4
4
  xpath do |locator, allow_self: nil, **options|
5
- xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
5
+ xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input)[
6
6
  XPath.attr(:type) == 'checkbox'
7
7
  ]
8
8
  locate_field(xpath, locator, **options)
@@ -3,7 +3,7 @@
3
3
  Capybara.add_selector(:file_field, locator_type: [String, Symbol]) do
4
4
  label 'file field'
5
5
  xpath do |locator, allow_self: nil, **options|
6
- xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
6
+ xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input)[
7
7
  XPath.attr(:type) == 'file'
8
8
  ]
9
9
  locate_field(xpath, locator, **options)
@@ -3,7 +3,7 @@
3
3
  Capybara.add_selector(:fillable_field, locator_type: [String, Symbol]) do
4
4
  label 'field'
5
5
  xpath do |locator, allow_self: nil, **options|
6
- xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input, :textarea)[
6
+ xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input, :textarea)[
7
7
  !XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')
8
8
  ]
9
9
  locate_field(xpath, locator, **options)
@@ -3,7 +3,7 @@
3
3
  Capybara.add_selector(:radio_button, locator_type: [String, Symbol]) do
4
4
  label 'radio button'
5
5
  xpath do |locator, allow_self: nil, **options|
6
- xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
6
+ xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input)[
7
7
  XPath.attr(:type) == 'radio'
8
8
  ]
9
9
  locate_field(xpath, locator, **options)
@@ -260,7 +260,8 @@ module Capybara
260
260
 
261
261
  def parameter_names(block)
262
262
  key_types = %i[key keyreq]
263
- block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_type, name)| name }
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
265
  end
265
266
 
266
267
  def expression(type, allowed_filters, &block)
@@ -101,13 +101,11 @@ module Capybara
101
101
  private
102
102
 
103
103
  def options_with_defaults(options)
104
- options = options.dup
105
- [expression_filters, node_filters].each do |filters|
106
- filters.select { |_n, filter| filter.default? }.each do |name, filter|
107
- options[name] = filter.default unless options.key?(name)
108
- end
104
+ expression_filters.chain(node_filters)
105
+ .select { |_n, filter| filter.default? }
106
+ .each_with_object(options.dup) do |(name, filter), opts|
107
+ opts[name] = filter.default unless opts.key?(name)
109
108
  end
110
- options
111
109
  end
112
110
 
113
111
  def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
@@ -14,6 +14,7 @@ require 'capybara/selector/definition'
14
14
  # * :left_of (Element) - Match elements left of the passed element on the page
15
15
  # * :right_of (Element) - Match elements right of the passed element on the page
16
16
  # * :near (Element) - Match elements near (within 50px) the passed element on the page
17
+ # * :focused (Boolean) - Match elements with focus (requires driver support)
17
18
  #
18
19
  # ### Built-in Selectors
19
20
  #