capybara 3.12.0 → 3.13.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +13 -0
  3. data/README.md +13 -3
  4. data/lib/capybara.rb +8 -4
  5. data/lib/capybara/config.rb +3 -1
  6. data/lib/capybara/driver/base.rb +2 -2
  7. data/lib/capybara/driver/node.rb +11 -2
  8. data/lib/capybara/minitest.rb +3 -3
  9. data/lib/capybara/minitest/spec.rb +10 -3
  10. data/lib/capybara/node/actions.rb +4 -0
  11. data/lib/capybara/node/base.rb +13 -5
  12. data/lib/capybara/node/document.rb +12 -0
  13. data/lib/capybara/node/element.rb +37 -0
  14. data/lib/capybara/node/finders.rb +1 -0
  15. data/lib/capybara/node/matchers.rb +19 -4
  16. data/lib/capybara/node/simple.rb +7 -2
  17. data/lib/capybara/queries/selector_query.rb +93 -9
  18. data/lib/capybara/rspec/matchers.rb +11 -3
  19. data/lib/capybara/rspec/matchers/become_closed.rb +1 -1
  20. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  21. data/lib/capybara/selector.rb +30 -30
  22. data/lib/capybara/selector/selector.rb +47 -3
  23. data/lib/capybara/selenium/driver.rb +12 -11
  24. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +3 -3
  25. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
  26. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +13 -0
  27. data/lib/capybara/selenium/extensions/find.rb +81 -0
  28. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  29. data/lib/capybara/selenium/node.rb +52 -28
  30. data/lib/capybara/session.rb +13 -1
  31. data/lib/capybara/spec/public/test.js +1 -0
  32. data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
  33. data/lib/capybara/spec/session/attach_file_spec.rb +6 -6
  34. data/lib/capybara/spec/session/click_button_spec.rb +1 -1
  35. data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -0
  36. data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
  37. data/lib/capybara/spec/session/fill_in_spec.rb +7 -1
  38. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  39. data/lib/capybara/spec/session/find_spec.rb +11 -0
  40. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +0 -1
  41. data/lib/capybara/spec/session/has_css_spec.rb +32 -0
  42. data/lib/capybara/spec/session/has_select_spec.rb +2 -2
  43. data/lib/capybara/spec/session/has_selector_spec.rb +7 -0
  44. data/lib/capybara/spec/session/has_text_spec.rb +1 -1
  45. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  46. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  47. data/lib/capybara/spec/session/select_spec.rb +5 -0
  48. data/lib/capybara/spec/spec_helper.rb +1 -0
  49. data/lib/capybara/spec/views/obscured.erb +1 -1
  50. data/lib/capybara/spec/views/scroll.erb +20 -0
  51. data/lib/capybara/spec/views/with_html.erb +1 -1
  52. data/lib/capybara/version.rb +1 -1
  53. data/lib/capybara/window.rb +1 -1
  54. data/spec/basic_node_spec.rb +11 -0
  55. data/spec/dsl_spec.rb +1 -1
  56. data/spec/minitest_spec.rb +2 -2
  57. data/spec/minitest_spec_spec.rb +1 -1
  58. data/spec/rack_test_spec.rb +1 -0
  59. data/spec/result_spec.rb +2 -2
  60. data/spec/selector_spec.rb +11 -1
  61. data/spec/selenium_spec_firefox.rb +1 -1
  62. data/spec/selenium_spec_ie.rb +70 -9
  63. data/spec/session_spec.rb +9 -0
  64. data/spec/shared_selenium_session.rb +4 -3
  65. data/spec/spec_helper.rb +2 -0
  66. metadata +38 -6
  67. data/lib/capybara/rspec/matchers/have_style.rb +0 -23
  68. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
@@ -4,7 +4,7 @@ module Capybara
4
4
  module Queries
5
5
  class SelectorQuery < Queries::BaseQuery
6
6
  attr_reader :expression, :selector, :locator, :options
7
- VALID_KEYS = COUNT_KEYS + %i[text id class visible exact exact_text normalize_ws match wait filter_set]
7
+ VALID_KEYS = COUNT_KEYS + %i[text id class style visible exact exact_text normalize_ws match wait filter_set]
8
8
  VALID_MATCH = %i[first smart prefer_exact one].freeze
9
9
 
10
10
  def initialize(*args,
@@ -14,6 +14,7 @@ module Capybara
14
14
  **options,
15
15
  &filter_block)
16
16
  @resolved_node = nil
17
+ @resolved_count = 0
17
18
  @options = options.dup
18
19
  super(@options)
19
20
  self.session_options = session_options
@@ -50,9 +51,24 @@ module Capybara
50
51
  end
51
52
  desc << " with id #{options[:id]}" if options[:id]
52
53
  desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
54
+ desc << case options[:style]
55
+ when String
56
+ " with style attribute #{options[:style].inspect}"
57
+ when Regexp
58
+ " with style attribute matching #{options[:style].inspect}"
59
+ when Hash
60
+ " with styles #{options[:style].inspect}"
61
+ else ''
62
+ end
53
63
  desc << selector.description(node_filters: show_for[:node], **options)
54
64
  desc << ' that also matches the custom filter block' if @filter_block && show_for[:node]
55
65
  desc << " within #{@resolved_node.inspect}" if describe_within?
66
+ if locator.is_a?(String) && locator.start_with?('#', './/', '//')
67
+ unless selector.raw_locator?
68
+ desc << "\nNote: It appears you may be passing a CSS selector or XPath expression rather than a locator. " \
69
+ "Please see the documentation for acceptable locator values.\n\n"
70
+ end
71
+ end
56
72
  desc
57
73
  end
58
74
 
@@ -91,7 +107,9 @@ module Capybara
91
107
  exact = exact? if exact.nil?
92
108
  expr = apply_expression_filters(@expression)
93
109
  expr = exact ? expr.to_xpath(:exact) : expr.to_s if expr.respond_to?(:to_xpath)
94
- filtered_expression(expr)
110
+ expr = filtered_expression(expr)
111
+ expr = "(#{expr})[#{xpath_text_conditions}]" if try_text_match_in_expression?
112
+ expr
95
113
  end
96
114
 
97
115
  def css
@@ -102,6 +120,7 @@ module Capybara
102
120
  def resolve_for(node, exact = nil)
103
121
  applied_filters.clear
104
122
  @resolved_node = node
123
+ @resolved_count += 1
105
124
  node.synchronize do
106
125
  children = find_nodes_by_selector_format(node, exact).map(&method(:to_element))
107
126
  Capybara::Result.new(children, self)
@@ -123,6 +142,26 @@ module Capybara
123
142
 
124
143
  private
125
144
 
145
+ def text_fragments
146
+ text = (options[:text] || options[:exact_text])
147
+ text.is_a?(String) ? text.split : []
148
+ end
149
+
150
+ def xpath_text_conditions
151
+ (options[:text] || options[:exact_text]).split.map { |txt| XPath.contains(txt) }.reduce(&:&)
152
+ end
153
+
154
+ def try_text_match_in_expression?
155
+ first_try? &&
156
+ (options[:text] || options[:exact_text]).is_a?(String) &&
157
+ @resolved_node&.respond_to?(:session) &&
158
+ @resolved_node.session.driver.wait?
159
+ end
160
+
161
+ def first_try?
162
+ @resolved_count == 1
163
+ end
164
+
126
165
  def show_for_stage(only_applied)
127
166
  lambda do |stage = :any|
128
167
  !only_applied || (stage == :any ? applied_filters.any? : applied_filters.include?(stage))
@@ -141,10 +180,25 @@ module Capybara
141
180
  end
142
181
 
143
182
  def find_nodes_by_selector_format(node, exact)
183
+ hints = {}
184
+ hints[:uses_visibility] = true unless visible == :all
185
+ hints[:texts] = text_fragments unless selector.format == :xpath
186
+ hints[:styles] = options[:style] if use_default_style_filter?
187
+
144
188
  if selector.format == :css
145
- node.find_css(css)
189
+ if node.method(:find_css).arity != 1
190
+ node.find_css(css, **hints)
191
+ else
192
+ node.find_css(css)
193
+ end
194
+ elsif selector.format == :xpath
195
+ if node.method(:find_xpath).arity != 1
196
+ node.find_xpath(xpath(exact), **hints)
197
+ else
198
+ node.find_xpath(xpath(exact))
199
+ end
146
200
  else
147
- node.find_xpath(xpath(exact))
201
+ raise ArgumentError, "Unknown format: #{selector.format}"
148
202
  end
149
203
  end
150
204
 
@@ -237,6 +291,7 @@ module Capybara
237
291
  conditions = {}
238
292
  conditions[:id] = options[:id] if use_default_id_filter?
239
293
  conditions[:class] = options[:class] if use_default_class_filter?
294
+ conditions[:style] = options[:style] if use_default_style_filter? && !options[:style].is_a?(Hash)
240
295
  builder(expr).add_attribute_conditions(conditions)
241
296
  end
242
297
 
@@ -248,6 +303,10 @@ module Capybara
248
303
  options.key?(:class) && !custom_keys.include?(:class)
249
304
  end
250
305
 
306
+ def use_default_style_filter?
307
+ options.key?(:style) && !custom_keys.include?(:style)
308
+ end
309
+
251
310
  def apply_expression_filters(expression)
252
311
  unapplied_options = options.keys - valid_keys
253
312
  expression_filters.inject(expression) do |expr, (name, ef)|
@@ -298,11 +357,12 @@ module Capybara
298
357
  def matches_system_filters?(node)
299
358
  applied_filters << :system
300
359
 
301
- matches_id_filter?(node) &&
360
+ matches_visible_filter?(node) &&
361
+ matches_id_filter?(node) &&
302
362
  matches_class_filter?(node) &&
363
+ matches_style_filter?(node) &&
303
364
  matches_text_filter?(node) &&
304
- matches_exact_text_filter?(node) &&
305
- matches_visible_filter?(node)
365
+ matches_exact_text_filter?(node)
306
366
  end
307
367
 
308
368
  def matches_id_filter?(node)
@@ -317,6 +377,28 @@ module Capybara
317
377
  node[:class] =~ options[:class]
318
378
  end
319
379
 
380
+ def matches_style_filter?(node)
381
+ case options[:style]
382
+ when String, nil
383
+ true
384
+ when Regexp
385
+ node[:style] =~ options[:style]
386
+ when Hash
387
+ matches_style?(node, options[:style])
388
+ end
389
+ end
390
+
391
+ def matches_style?(node, styles)
392
+ @actual_styles = node.initial_cache[:style] || node.style(*styles.keys)
393
+ styles.all? do |style, value|
394
+ if value.is_a? Regexp
395
+ @actual_styles[style.to_s] =~ value
396
+ else
397
+ @actual_styles[style.to_s] == value
398
+ end
399
+ end
400
+ end
401
+
320
402
  def matches_text_filter?(node)
321
403
  value = options[:text]
322
404
  return true unless value
@@ -334,8 +416,10 @@ module Capybara
334
416
 
335
417
  def matches_visible_filter?(node)
336
418
  case visible
337
- when :visible then node.visible?
338
- when :hidden then !node.visible?
419
+ when :visible then
420
+ node.initial_cache[:visible] || (node.initial_cache[:visible].nil? && node.visible?)
421
+ when :hidden then
422
+ (node.initial_cache[:visible] == false) || (node.initial_cache[:visbile].nil? && !node.visible?)
339
423
  else true
340
424
  end
341
425
  end
@@ -3,7 +3,7 @@
3
3
  require 'capybara/rspec/matchers/have_selector'
4
4
  require 'capybara/rspec/matchers/match_selector'
5
5
  require 'capybara/rspec/matchers/have_current_path'
6
- require 'capybara/rspec/matchers/have_style'
6
+ require 'capybara/rspec/matchers/match_style'
7
7
  require 'capybara/rspec/matchers/have_text'
8
8
  require 'capybara/rspec/matchers/have_title'
9
9
  require 'capybara/rspec/matchers/become_closed'
@@ -124,9 +124,17 @@ module Capybara
124
124
  end
125
125
 
126
126
  # RSpec matcher for element style
127
- # See {Capybara::Node::Matchers#has_style?}
127
+ # See {Capybara::Node::Matchers#matches_style?}
128
+ def match_style(styles, **options)
129
+ Matchers::MatchStyle.new(styles, options)
130
+ end
131
+
132
+ ##
133
+ # @deprecated
134
+ #
128
135
  def have_style(styles, **options)
129
- Matchers::HaveStyle.new(styles, options)
136
+ warn 'DEPRECATED: have_style is deprecated, please use match_style'
137
+ match_style(styles, **options)
130
138
  end
131
139
 
132
140
  %w[selector css xpath text title current_path link button
@@ -15,7 +15,7 @@ module Capybara
15
15
  while window.exists?
16
16
  return false if timer.expired?
17
17
 
18
- sleep 0.05
18
+ sleep 0.01
19
19
  end
20
20
  true
21
21
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/rspec/matchers/base'
4
+
5
+ module Capybara
6
+ module RSpecMatchers
7
+ module Matchers
8
+ class MatchStyle < WrappedElementMatcher
9
+ def element_matches?(el)
10
+ el.assert_matches_style(*@args)
11
+ end
12
+
13
+ def does_not_match?(_actual)
14
+ raise ArgumentError, 'The match_style matcher does not support use with not_to/should_not'
15
+ end
16
+
17
+ def description
18
+ 'match style'
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ module Capybara
26
+ module RSpecMatchers
27
+ module Matchers
28
+ ##
29
+ # @deprecated
30
+ class HaveStyle < MatchStyle
31
+ def initialize(*args, &filter_block)
32
+ warn 'HaveStyle matcher is deprecated, please use the MatchStyle matcher instead'
33
+ super
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -25,24 +25,24 @@ end
25
25
 
26
26
  # rubocop:disable Metrics/BlockLength
27
27
 
28
- Capybara.add_selector(:xpath) do
28
+ Capybara.add_selector(:xpath, locator_type: [:to_xpath, String], raw_locator: true) do
29
29
  xpath { |xpath| xpath }
30
30
  end
31
31
 
32
- Capybara.add_selector(:css) do
32
+ Capybara.add_selector(:css, locator_type: [String, Symbol], raw_locator: true) do
33
33
  css { |css| css }
34
34
  end
35
35
 
36
- Capybara.add_selector(:id) do
36
+ Capybara.add_selector(:id, locator_type: [String, Symbol, Regexp]) do
37
37
  xpath { |id| builder(XPath.descendant).add_attribute_conditions(id: id) }
38
38
  locator_filter { |node, id| id.is_a?(Regexp) ? node[:id] =~ id : true }
39
39
  end
40
40
 
41
- Capybara.add_selector(:field) do
42
- visible { |options| :hidden if options[:type] == 'hidden' }
41
+ Capybara.add_selector(:field, locator_type: [String, Symbol]) do
42
+ visible { |options| :hidden if options[:type].to_s == 'hidden' }
43
43
  xpath do |locator, **options|
44
44
  invalid_types = %w[submit image]
45
- invalid_types << 'hidden' unless options[:type] == 'hidden'
45
+ invalid_types << 'hidden' unless options[:type].to_s == 'hidden'
46
46
  xpath = XPath.descendant(:input, :textarea, :select)[!XPath.attr(:type).one_of(*invalid_types)]
47
47
  locate_field(xpath, locator, options)
48
48
  end
@@ -78,7 +78,7 @@ Capybara.add_selector(:field) do
78
78
  end
79
79
  end
80
80
 
81
- Capybara.add_selector(:fieldset) do
81
+ Capybara.add_selector(:fieldset, locator_type: [String, Symbol]) do
82
82
  xpath do |locator, legend: nil, **|
83
83
  locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]
84
84
  locator_matchers |= XPath.attr(test_id) == locator.to_s if test_id
@@ -90,7 +90,7 @@ Capybara.add_selector(:fieldset) do
90
90
  node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
91
91
  end
92
92
 
93
- Capybara.add_selector(:link) do
93
+ Capybara.add_selector(:link, locator_type: [String, Symbol]) do
94
94
  xpath do |locator, href: true, alt: nil, title: nil, **|
95
95
  xpath = builder(XPath.descendant(:a)).add_attribute_conditions(href: href)
96
96
 
@@ -132,7 +132,7 @@ Capybara.add_selector(:link) do
132
132
  end
133
133
  end
134
134
 
135
- Capybara.add_selector(:button) do
135
+ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
136
136
  xpath(:value, :title, :type) do |locator, **options|
137
137
  input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
138
138
  btn_xpath = XPath.descendant(:button)
@@ -166,7 +166,7 @@ Capybara.add_selector(:button) do
166
166
  end
167
167
  end
168
168
 
169
- Capybara.add_selector(:link_or_button) do
169
+ Capybara.add_selector(:link_or_button, locator_type: [String, Symbol]) do
170
170
  label 'link or button'
171
171
  xpath do |locator, **options|
172
172
  self.class.all.values_at(:link, :button).map do |selector|
@@ -181,9 +181,8 @@ Capybara.add_selector(:link_or_button) do
181
181
  end
182
182
  end
183
183
 
184
- Capybara.add_selector(:fillable_field) do
184
+ Capybara.add_selector(:fillable_field, locator_type: [String, Symbol]) do
185
185
  label 'field'
186
-
187
186
  xpath do |locator, allow_self: nil, **options|
188
187
  xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input, :textarea)[
189
188
  !XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')
@@ -215,9 +214,8 @@ Capybara.add_selector(:fillable_field) do
215
214
  end
216
215
  end
217
216
 
218
- Capybara.add_selector(:radio_button) do
217
+ Capybara.add_selector(:radio_button, locator_type: [String, Symbol]) do
219
218
  label 'radio button'
220
-
221
219
  xpath do |locator, allow_self: nil, **options|
222
220
  xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
223
221
  XPath.attr(:type) == 'radio'
@@ -240,7 +238,7 @@ Capybara.add_selector(:radio_button) do
240
238
  end
241
239
  end
242
240
 
243
- Capybara.add_selector(:checkbox) do
241
+ Capybara.add_selector(:checkbox, locator_type: [String, Symbol]) do
244
242
  xpath do |locator, allow_self: nil, **options|
245
243
  xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
246
244
  XPath.attr(:type) == 'checkbox'
@@ -263,7 +261,7 @@ Capybara.add_selector(:checkbox) do
263
261
  end
264
262
  end
265
263
 
266
- Capybara.add_selector(:select) do
264
+ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
267
265
  label 'select box'
268
266
 
269
267
  xpath do |locator, **options|
@@ -320,7 +318,7 @@ Capybara.add_selector(:select) do
320
318
  end
321
319
  end
322
320
 
323
- Capybara.add_selector(:datalist_input) do
321
+ Capybara.add_selector(:datalist_input, locator_type: [String, Symbol]) do
324
322
  label 'input box with datalist completion'
325
323
 
326
324
  xpath do |locator, **options|
@@ -355,7 +353,7 @@ Capybara.add_selector(:datalist_input) do
355
353
  end
356
354
  end
357
355
 
358
- Capybara.add_selector(:option) do
356
+ Capybara.add_selector(:option, locator_type: [String, Symbol]) do
359
357
  xpath do |locator|
360
358
  xpath = XPath.descendant(:option)
361
359
  xpath = xpath[XPath.string.n.is(locator.to_s)] unless locator.nil?
@@ -373,7 +371,7 @@ Capybara.add_selector(:option) do
373
371
  end
374
372
  end
375
373
 
376
- Capybara.add_selector(:datalist_option) do
374
+ Capybara.add_selector(:datalist_option, locator_type: [String, Symbol]) do
377
375
  label 'datalist option'
378
376
  visible(:all)
379
377
 
@@ -390,7 +388,7 @@ Capybara.add_selector(:datalist_option) do
390
388
  end
391
389
  end
392
390
 
393
- Capybara.add_selector(:file_field) do
391
+ Capybara.add_selector(:file_field, locator_type: [String, Symbol]) do
394
392
  label 'file field'
395
393
  xpath do |locator, allow_self: nil, **options|
396
394
  xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
@@ -404,7 +402,7 @@ Capybara.add_selector(:file_field) do
404
402
  describe_expression_filters
405
403
  end
406
404
 
407
- Capybara.add_selector(:label) do
405
+ Capybara.add_selector(:label, locator_type: [String, Symbol]) do
408
406
  label 'label'
409
407
  xpath(:for) do |locator, options|
410
408
  xpath = XPath.descendant(:label)
@@ -413,12 +411,14 @@ Capybara.add_selector(:label) do
413
411
  locator_matchers |= XPath.attr(test_id) == locator if test_id
414
412
  xpath = xpath[locator_matchers]
415
413
  end
416
- if options.key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
417
- with_attr = XPath.attr(:for) == options[:for].to_s
418
- labelable_elements = %i[button input keygen meter output progress select textarea]
419
- wrapped = !XPath.attr(:for) &
420
- XPath.descendant(*labelable_elements)[XPath.attr(:id) == options[:for].to_s]
421
- xpath = xpath[with_attr | wrapped]
414
+ if options.key?(:for)
415
+ if (for_option = options[:for].is_a?(Capybara::Node::Element) ? options[:for][:id] : options[:for])
416
+ with_attr = XPath.attr(:for) == for_option.to_s
417
+ labelable_elements = %i[button input keygen meter output progress select textarea]
418
+ wrapped = !XPath.attr(:for) &
419
+ XPath.descendant(*labelable_elements)[XPath.attr(:id) == for_option.to_s]
420
+ xpath = xpath[with_attr | wrapped]
421
+ end
422
422
  end
423
423
  xpath
424
424
  end
@@ -442,7 +442,7 @@ Capybara.add_selector(:label) do
442
442
  end
443
443
  end
444
444
 
445
- Capybara.add_selector(:table) do
445
+ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
446
446
  xpath do |locator, caption: nil, **|
447
447
  xpath = XPath.descendant(:table)
448
448
  unless locator.nil?
@@ -459,7 +459,7 @@ Capybara.add_selector(:table) do
459
459
  end
460
460
  end
461
461
 
462
- Capybara.add_selector(:frame) do
462
+ Capybara.add_selector(:frame, locator_type: [String, Symbol]) do
463
463
  xpath do |locator, name: nil, **|
464
464
  xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
465
465
  unless locator.nil?
@@ -475,7 +475,7 @@ Capybara.add_selector(:frame) do
475
475
  end
476
476
  end
477
477
 
478
- Capybara.add_selector(:element) do
478
+ Capybara.add_selector(:element, locator_type: [String, Symbol]) do
479
479
  xpath do |locator, **|
480
480
  XPath.descendant.where(locator ? XPath.local_name == locator.to_s : nil)
481
481
  end