capybara 3.18.0 → 3.19.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +16 -0
  3. data/README.md +14 -44
  4. data/lib/capybara/node/actions.rb +2 -2
  5. data/lib/capybara/node/element.rb +3 -5
  6. data/lib/capybara/queries/selector_query.rb +30 -11
  7. data/lib/capybara/rack_test/node.rb +1 -1
  8. data/lib/capybara/result.rb +2 -0
  9. data/lib/capybara/rspec/matcher_proxies.rb +2 -0
  10. data/lib/capybara/rspec/matchers/base.rb +2 -2
  11. data/lib/capybara/rspec/matchers/count_sugar.rb +36 -0
  12. data/lib/capybara/rspec/matchers/have_selector.rb +3 -0
  13. data/lib/capybara/rspec/matchers/have_text.rb +3 -0
  14. data/lib/capybara/selector.rb +196 -599
  15. data/lib/capybara/selector/css.rb +2 -0
  16. data/lib/capybara/selector/definition.rb +276 -0
  17. data/lib/capybara/selector/definition/button.rb +46 -0
  18. data/lib/capybara/selector/definition/checkbox.rb +23 -0
  19. data/lib/capybara/selector/definition/css.rb +5 -0
  20. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  21. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  22. data/lib/capybara/selector/definition/element.rb +27 -0
  23. data/lib/capybara/selector/definition/field.rb +40 -0
  24. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  25. data/lib/capybara/selector/definition/file_field.rb +13 -0
  26. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  27. data/lib/capybara/selector/definition/frame.rb +17 -0
  28. data/lib/capybara/selector/definition/id.rb +6 -0
  29. data/lib/capybara/selector/definition/label.rb +43 -0
  30. data/lib/capybara/selector/definition/link.rb +45 -0
  31. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  32. data/lib/capybara/selector/definition/option.rb +27 -0
  33. data/lib/capybara/selector/definition/radio_button.rb +24 -0
  34. data/lib/capybara/selector/definition/select.rb +62 -0
  35. data/lib/capybara/selector/definition/table.rb +106 -0
  36. data/lib/capybara/selector/definition/table_row.rb +21 -0
  37. data/lib/capybara/selector/definition/xpath.rb +5 -0
  38. data/lib/capybara/selector/filters/base.rb +4 -0
  39. data/lib/capybara/selector/filters/locator_filter.rb +12 -2
  40. data/lib/capybara/selector/selector.rb +40 -452
  41. data/lib/capybara/selenium/driver.rb +4 -10
  42. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +3 -9
  43. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +8 -0
  44. data/lib/capybara/selenium/extensions/find.rb +1 -1
  45. data/lib/capybara/selenium/logger_suppressor.rb +5 -0
  46. data/lib/capybara/selenium/node.rb +19 -13
  47. data/lib/capybara/selenium/nodes/chrome_node.rb +30 -0
  48. data/lib/capybara/selenium/nodes/firefox_node.rb +14 -12
  49. data/lib/capybara/selenium/nodes/ie_node.rb +11 -0
  50. data/lib/capybara/selenium/nodes/safari_node.rb +7 -12
  51. data/lib/capybara/server/checker.rb +7 -3
  52. data/lib/capybara/session.rb +2 -2
  53. data/lib/capybara/spec/session/all_spec.rb +1 -1
  54. data/lib/capybara/spec/session/find_spec.rb +1 -1
  55. data/lib/capybara/spec/session/first_spec.rb +1 -1
  56. data/lib/capybara/spec/session/has_css_spec.rb +7 -0
  57. data/lib/capybara/spec/session/has_text_spec.rb +6 -0
  58. data/lib/capybara/spec/session/save_screenshot_spec.rb +11 -0
  59. data/lib/capybara/spec/session/select_spec.rb +0 -5
  60. data/lib/capybara/spec/test_app.rb +8 -3
  61. data/lib/capybara/version.rb +1 -1
  62. data/lib/capybara/window.rb +1 -1
  63. data/spec/minitest_spec_spec.rb +1 -0
  64. data/spec/selector_spec.rb +12 -6
  65. data/spec/selenium_spec_firefox.rb +0 -3
  66. data/spec/selenium_spec_firefox_remote.rb +0 -3
  67. data/spec/selenium_spec_ie.rb +3 -1
  68. data/spec/server_spec.rb +1 -1
  69. data/spec/shared_selenium_session.rb +1 -1
  70. data/spec/spec_helper.rb +9 -2
  71. metadata +54 -2
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:table_row, locator_type: [Array, Hash]) do
4
+ xpath do |locator|
5
+ xpath = XPath.descendant(:tr)
6
+ if locator.is_a? Hash
7
+ locator.reduce(xpath) do |xp, (header, cell)|
8
+ header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
9
+ cell_xp = XPath.descendant(:td)[
10
+ XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
11
+ ]
12
+ xp[cell_xp]
13
+ end
14
+ else
15
+ initial_td = XPath.descendant(:td)[XPath.string.n.is(locator.shift)]
16
+ tds = locator.reverse.map { |cell| XPath.following_sibling(:td)[XPath.string.n.is(cell)] }
17
+ .reduce { |xp, cell| xp[cell] }
18
+ xpath[initial_td[tds]]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.add_selector(:xpath, locator_type: [:to_xpath, String], raw_locator: true) do
4
+ xpath { |xpath| xpath }
5
+ end
@@ -24,6 +24,10 @@ module Capybara
24
24
  @options.key?(:skip_if) && value == @options[:skip_if]
25
25
  end
26
26
 
27
+ def format
28
+ @options[:format]
29
+ end
30
+
27
31
  def matcher?
28
32
  !@matcher.nil?
29
33
  end
@@ -10,8 +10,18 @@ module Capybara
10
10
  super(nil, nil, block, options)
11
11
  end
12
12
 
13
- def matches?(node, value, context = nil)
14
- super(node, nil, value, context)
13
+ def matches?(node, value, context = nil, exact:)
14
+ apply(node, value, true, context, exact: exact, format: context&.default_format)
15
+ rescue Capybara::ElementNotFound
16
+ false
17
+ end
18
+
19
+ private
20
+
21
+ def apply(subject, value, skip_value, ctx, **options)
22
+ return skip_value if skip?(value)
23
+
24
+ filter_context(ctx).instance_exec(subject, value, **options, &@block)
15
25
  end
16
26
  end
17
27
  end
@@ -1,191 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Style/AsciiComments
4
-
5
- require 'capybara/selector/filter_set'
6
- require 'capybara/selector/css'
7
- require 'capybara/selector/regexp_disassembler'
8
- require 'capybara/selector/builders/xpath_builder'
9
- require 'capybara/selector/builders/css_builder'
10
-
11
3
  module Capybara
12
- #
13
- # ## Built-in Selectors
14
- #
15
- # * **:xpath** - Select elements by XPath expression
16
- # * Locator: An XPath expression
17
- #
18
- # * **:css** - Select elements by CSS selector
19
- # * Locator: A CSS selector
20
- #
21
- # * **:id** - Select element by id
22
- # * Locator: (String, Regexp, XPath::Expression) The id of the element to match
23
- #
24
- # * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
25
- # * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
26
- # * Filters:
27
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
28
- # * :name (String) — Matches the name attribute
29
- # * :placeholder (String) — Matches the placeholder attribute
30
- # * :type (String) — Matches the type attribute of the field or element type for 'textarea' and 'select'
31
- # * :readonly (Boolean)
32
- # * :with (String) — Matches the current value of the field
33
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
34
- # * :checked (Boolean) — Match checked fields?
35
- # * :unchecked (Boolean) — Match unchecked fields?
36
- # * :disabled (Boolean) — Match disabled field?
37
- # * :multiple (Boolean) — Match fields that accept multiple values
38
- # * :style (String, Regexp, Hash)
39
- #
40
- # * **:fieldset** - Select fieldset elements
41
- # * Locator: Matches id or contents of wrapped legend
42
- # * Filters:
43
- # * :id (String, Regexp, XPath::Expression) — Matches id attribute
44
- # * :legend (String) — Matches contents of wrapped legend
45
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
46
- # * :style (String, Regexp, Hash)
47
- #
48
- # * **:link** - Find links ( <a> elements with an href attribute )
49
- # * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
50
- # * Filters:
51
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
52
- # * :title (String) — Matches the title attribute
53
- # * :alt (String) — Matches the alt attribute of a contained img element
54
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
55
- # * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
56
- # * :style (String, Regexp, Hash)
57
- #
58
- # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
59
- # * Locator: Matches the id, Capybara.test_id attribute, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
60
- # * Filters:
61
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
62
- # * :name (String) - Matches the name attribute
63
- # * :title (String) — Matches the title attribute
64
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
65
- # * :value (String) — Matches the value of an input button
66
- # * :type
67
- # * :style (String, Regexp, Hash)
68
- #
69
- # * **:link_or_button** - Find links or buttons
70
- # * Locator: See :link and :button selectors
71
- #
72
- # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
73
- # * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
74
- # * Filters:
75
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
76
- # * :name (String) — Matches the name attribute
77
- # * :placeholder (String) — Matches the placeholder attribute
78
- # * :with (String) — Matches the current value of the field
79
- # * :type (String) — Matches the type attribute of the field or element type for 'textarea'
80
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
81
- # * :disabled (Boolean) — Match disabled field?
82
- # * :multiple (Boolean) — Match fields that accept multiple values
83
- # * :style (String, Regexp, Hash)
84
- #
85
- # * **:radio_button** - Find radio buttons
86
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
87
- # * Filters:
88
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
89
- # * :name (String) — Matches the name attribute
90
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
91
- # * :checked (Boolean) — Match checked fields?
92
- # * :unchecked (Boolean) — Match unchecked fields?
93
- # * :disabled (Boolean) — Match disabled field?
94
- # * :option (String) — Match the value
95
- # * :style (String, Regexp, Hash)
96
- #
97
- # * **:checkbox** - Find checkboxes
98
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
99
- # * Filters:
100
- # * *:id (String, Regexp, XPath::Expression) — Matches the id attribute
101
- # * *:name (String) — Matches the name attribute
102
- # * *:class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
103
- # * *:checked (Boolean) — Match checked fields?
104
- # * *:unchecked (Boolean) — Match unchecked fields?
105
- # * *:disabled (Boolean) — Match disabled field?
106
- # * *:option (String) — Match the value
107
- # * :style (String, Regexp, Hash)
108
- #
109
- # * **:select** - Find select elements
110
- # * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
111
- # * Filters:
112
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
113
- # * :name (String) — Matches the name attribute
114
- # * :placeholder (String) — Matches the placeholder attribute
115
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
116
- # * :disabled (Boolean) — Match disabled field?
117
- # * :multiple (Boolean) — Match fields that accept multiple values
118
- # * :options (Array<String>) — Exact match options
119
- # * :with_options (Array<String>) — Partial match options
120
- # * :selected (String, Array<String>) — Match the selection(s)
121
- # * :with_selected (String, Array<String>) — Partial match the selection(s)
122
- # * :style (String, Regexp, Hash)
123
- #
124
- # * **:option** - Find option elements
125
- # * Locator: Match text of option
126
- # * Filters:
127
- # * :disabled (Boolean) — Match disabled option
128
- # * :selected (Boolean) — Match selected option
129
- #
130
- # * **:datalist_input**
131
- # * Locator:
132
- # * Filters:
133
- # * :disabled
134
- # * :name
135
- # * :placeholder
136
- #
137
- # * **:datalist_option**
138
- # * Locator:
139
- #
140
- # * **:file_field** - Find file input elements
141
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
142
- # * Filters:
143
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
144
- # * :name (String) — Matches the name attribute
145
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
146
- # * :disabled (Boolean) — Match disabled field?
147
- # * :multiple (Boolean) — Match field that accepts multiple values
148
- # * :style (String, Regexp, Hash)
149
- #
150
- # * **:label** - Find label elements
151
- # * Locator: Match id or text contents
152
- # * Filters:
153
- # * :for (Element, String) — The element or id of the element associated with the label
154
- #
155
- # * **:table** - Find table elements
156
- # * Locator: id or caption text of table
157
- # * Filters:
158
- # * :id (String, Regexp, XPath::Expression) — Match id attribute of table
159
- # * :caption (String) — Match text of associated caption
160
- # * :class ((String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
161
- # * :style (String, Regexp, Hash)
162
- # * :with_rows (Array<Array<String>>, Array<Hash<String, String>>) - Partial match <td> data - visibility of <td> elements is not considered
163
- # * :rows (Array<Array<String>>) — Match all <td>s - visibility of <td> elements is not considered
164
- # * :with_cols (Array<Array<String>>, Array<Hash<String, String>>) - Partial match <td> data - visibility of <td> elements is not considered
165
- # * :cols (Array<Array<String>>) — Match all <td>s - visibility of <td> elements is not considered
166
- #
167
- # * **:table_row** - Find table row
168
- # * Locator: Array<String>, Hash<String,String> table row <td> contents - visibility of <td> elements is not considered
169
- #
170
- # * **:frame** - Find frame/iframe elements
171
- # * Locator: Match id or name
172
- # * Filters:
173
- # * :id (String, Regexp, XPath::Expression) — Match id attribute
174
- # * :name (String) — Match name attribute
175
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
176
- # * :style (String, Regexp, Hash)
177
- #
178
- # * **:element**
179
- # * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
180
- # * Filters: Matches on any element attribute
181
- #
182
- class Selector
183
- attr_reader :name, :format
184
- extend Forwardable
185
-
4
+ class Selector < SimpleDelegator
186
5
  class << self
187
6
  def all
188
- @selectors ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
7
+ @definitions ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
189
8
  end
190
9
 
191
10
  def [](name)
@@ -193,7 +12,7 @@ module Capybara
193
12
  end
194
13
 
195
14
  def add(name, **options, &block)
196
- all[name.to_sym] = Capybara::Selector.new(name.to_sym, **options, &block)
15
+ all[name.to_sym] = Definition.new(name.to_sym, **options, &block)
197
16
  end
198
17
 
199
18
  def update(name, &block)
@@ -209,226 +28,57 @@ module Capybara
209
28
  end
210
29
  end
211
30
 
212
- def initialize(name, locator_type: nil, raw_locator: false, &block)
213
- @name = name
214
- @filter_set = FilterSet.add(name) {}
215
- @match = nil
216
- @label = nil
217
- @failure_message = nil
218
- @format = nil
219
- @expression = nil
220
- @expression_filters = {}
221
- @locator_filter = nil
222
- @default_visibility = nil
223
- @locator_type = locator_type
224
- @raw_locator = raw_locator
225
- @config = {
226
- enable_aria_label: false,
227
- test_id: nil
228
- }
229
- instance_eval(&block)
230
- end
231
-
232
- def custom_filters
233
- warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
234
- node_filters.merge(expression_filters).freeze
235
- end
31
+ attr_reader :errors
236
32
 
237
- def node_filters
238
- @filter_set.node_filters
33
+ def initialize(definition, config:, format:)
34
+ definition = self.class[definition] unless definition.is_a? Definition
35
+ super(definition)
36
+ @definition = definition
37
+ @config = config
38
+ @format = format
39
+ @errors = []
239
40
  end
240
41
 
241
- def expression_filters
242
- @filter_set.expression_filters
42
+ def format
43
+ @format || @definition.default_format
243
44
  end
45
+ alias_method :current_format, :format
244
46
 
245
- ##
246
- #
247
- # Define a selector by an xpath expression
248
- #
249
- # @overload xpath(*expression_filters, &block)
250
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that are implemented via this expression, if not specified the names of any keyword parameters in the block will be used
251
- # @yield [locator, options] The block to use to generate the XPath expression
252
- # @yieldparam [String] locator The locator string passed to the query
253
- # @yieldparam [Hash] options The options hash passed to the query
254
- # @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression
255
- #
256
- # @overload xpath()
257
- # @return [#call] The block that will be called to generate the XPath expression
258
- #
259
- def xpath(*allowed_filters, &block)
260
- expression(:xpath, allowed_filters, &block)
261
- end
262
-
263
- ##
264
- #
265
- # Define a selector by a CSS selector
266
- #
267
- # @overload css(*expression_filters, &block)
268
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector
269
- # @yield [locator, options] The block to use to generate the CSS selector
270
- # @yieldparam [String] locator The locator string passed to the query
271
- # @yieldparam [Hash] options The options hash passed to the query
272
- # @yieldreturn [#to_s] An object that can produce a CSS selector
273
- #
274
- # @overload css()
275
- # @return [#call] The block that will be called to generate the CSS selector
276
- #
277
- def css(*allowed_filters, &block)
278
- expression(:css, allowed_filters, &block)
279
- end
280
-
281
- ##
282
- #
283
- # Automatic selector detection
284
- #
285
- # @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
286
- # @yieldparam [String], locator The locator string used to determin if it matches the selector
287
- # @yieldreturn [Boolean] Whether this selector matches the locator string
288
- # @return [#call] The block that will be used to detect selector match
289
- #
290
- def match(&block)
291
- @match = block if block
292
- @match
47
+ def enable_aria_label
48
+ @config[:enable_aria_label]
293
49
  end
294
50
 
295
- ##
296
- #
297
- # Set/get a descriptive label for the selector
298
- #
299
- # @overload label(label)
300
- # @param [String] label A descriptive label for this selector - used in error messages
301
- # @overload label()
302
- # @return [String] The currently set label
303
- #
304
- def label(label = nil)
305
- @label = label if label
306
- @label
51
+ def test_id
52
+ @config[:test_id]
307
53
  end
308
54
 
309
- ##
310
- #
311
- # Description of the selector
312
- #
313
- # @!method description(options)
314
- # @param [Hash] options The options of the query used to generate the description
315
- # @return [String] Description of the selector when used with the options passed
316
- def_delegator :@filter_set, :description
317
-
318
- def call(locator, selector_config: {}, **options)
319
- @config.merge! selector_config
55
+ def call(locator, **options)
320
56
  if format
321
- @expression.call(locator, options)
57
+ raise ArgumentError, "Selector #{@name} does not support #{format}" unless expressions.key?(format)
58
+
59
+ instance_exec(locator, options, &expressions[format])
322
60
  else
323
61
  warn 'Selector has no format'
324
62
  end
325
63
  ensure
326
- warn "Locator #{locator.inspect} must #{locator_description}. This will raise an error in a future version of Capybara." unless locator_valid?(locator)
327
- end
328
-
329
- ##
330
- #
331
- # Should this selector be used for the passed in locator
332
- #
333
- # This is used by the automatic selector selection mechanism when no selector type is passed to a selector query
334
- #
335
- # @param [String] locator The locator passed to the query
336
- # @return [Boolean] Whether or not to use this selector
337
- #
338
- def match?(locator)
339
- @match&.call(locator)
340
- end
341
-
342
- ##
343
- #
344
- # Define a node filter for use with this selector
345
- #
346
- # @!method node_filter(name, *types, options={}, &block)
347
- # @param [Symbol, Regexp] name The filter name
348
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
349
- # @param [Hash] options ({}) Options of the filter
350
- # @option options [Array<>] :valid_values Valid values for this filter
351
- # @option options :default The default value of the filter (if any)
352
- # @option options :skip_if Value of the filter that will cause it to be skipped
353
- # @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
354
- #
355
- # If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
356
- # is passed for the name the block should accept | node, option_name, option_value |. In either case
357
- # the block should return `true` if the node passes the filer or `false` if it doesn't
358
-
359
- # @!method filter
360
- # See {Selector#node_filter}
361
-
362
- ##
363
- #
364
- # Define an expression filter for use with this selector
365
- #
366
- # @!method expression_filter(name, *types, matcher: nil, **options, &block)
367
- # @param [Symbol, Regexp] name The filter name
368
- # @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
369
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
370
- # @param [Hash] options ({}) Options of the filter
371
- # @option options [Array<>] :valid_values Valid values for this filter
372
- # @option options :default The default value of the filter (if any)
373
- # @option options :skip_if Value of the filter that will cause it to be skipped
374
- # @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
375
- #
376
- # If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
377
- # is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
378
- # the block should return the modified expression
379
-
380
- def_delegators :@filter_set, :node_filter, :expression_filter, :filter
381
-
382
- def locator_filter(*types, **options, &block)
383
- types.each { |type| options[type] = true }
384
- @locator_filter = Filters::LocatorFilter.new(block, options) if block
385
- @locator_filter
386
- end
387
-
388
- def filter_set(name, filters_to_use = nil)
389
- @filter_set.import(name, filters_to_use)
390
- end
391
-
392
- def_delegator :@filter_set, :describe
393
-
394
- def describe_expression_filters(&block)
395
- if block_given?
396
- describe(:expression_filters, &block)
397
- else
398
- describe(:expression_filters) do |**options|
399
- describe_all_expression_filters(options)
400
- end
401
- end
402
- end
403
-
404
- def describe_node_filters(&block)
405
- describe(:node_filters, &block)
64
+ warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara." unless locator_valid?(locator)
406
65
  end
407
66
 
408
- ##
409
- #
410
- # Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
411
- # If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
412
- #
413
- # @param [Symbol] default_visibility Only find elements with the specified visibility:
414
- # * :all - finds visible and invisible elements.
415
- # * :hidden - only finds invisible elements.
416
- # * :visible - only finds visible elements.
417
- def visible(default_visibility = nil, &block)
418
- @default_visibility = block || default_visibility
67
+ def add_error(error_msg)
68
+ errors << error_msg
419
69
  end
420
70
 
421
- def default_visibility(fallback = Capybara.ignore_hidden_elements, options = {})
422
- vis = if @default_visibility&.respond_to?(:call)
423
- @default_visibility.call(options)
424
- else
425
- @default_visibility
426
- end
427
- vis.nil? ? fallback : vis
71
+ def expression_for(name, locator, config: @config, format: current_format, **options)
72
+ Selector.new(name, config: config, format: format).call(locator, **options)
428
73
  end
429
74
 
430
- def add_error(error_msg)
431
- errors << error_msg
75
+ # @api private
76
+ def with_filter_errors(errors)
77
+ old_errors = @errors
78
+ @errors = errors
79
+ yield
80
+ ensure
81
+ @errors = old_errors
432
82
  end
433
83
 
434
84
  # @api private
@@ -439,39 +89,12 @@ module Capybara
439
89
  when :xpath
440
90
  Capybara::Selector::XPathBuilder
441
91
  else
442
- raise NotImplementedError, "No builder exists for selector of type #{format}"
92
+ raise NotImplementedError, "No builder exists for selector of type #{default_format}"
443
93
  end.new(expr)
444
94
  end
445
95
 
446
- # @api private
447
- def with_filter_errors(errors)
448
- Thread.current["capybara_#{object_id}_errors"] = errors
449
- yield
450
- ensure
451
- Thread.current["capybara_#{object_id}_errors"] = nil
452
- end
453
-
454
- # @api private
455
- def raw_locator?
456
- !!@raw_locator
457
- end
458
-
459
96
  private
460
97
 
461
- def locator_types
462
- return nil unless @locator_type
463
-
464
- Array(@locator_type)
465
- end
466
-
467
- def locator_valid?(locator)
468
- return true unless locator && locator_types
469
-
470
- locator_types&.any? do |type_or_method|
471
- type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
472
- end
473
- end
474
-
475
98
  def locator_description
476
99
  locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods|
477
100
  if symbol
@@ -482,16 +105,12 @@ module Capybara
482
105
  end.join(' or ')
483
106
  end
484
107
 
485
- def errors
486
- Thread.current["capybara_#{object_id}_errors"] || []
487
- end
488
-
489
- def enable_aria_label
490
- @config[:enable_aria_label]
491
- end
108
+ def locator_valid?(locator)
109
+ return true unless locator && locator_types
492
110
 
493
- def test_id
494
- @config[:test_id]
111
+ locator_types&.any? do |type_or_method|
112
+ type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
113
+ end
495
114
  end
496
115
 
497
116
  def locate_field(xpath, locator, **_options)
@@ -510,22 +129,6 @@ module Capybara
510
129
  locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
511
130
  end
512
131
 
513
- def describe_all_expression_filters(**opts)
514
- expression_filters.map do |ef_name, ef|
515
- if ef.matcher?
516
- handled_custom_keys(ef, opts.keys).map { |key| " with #{ef_name}[#{key} => #{opts[key]}]" }.join
517
- elsif opts.key?(ef_name)
518
- " with #{ef_name} #{opts[ef_name]}"
519
- end
520
- end.join
521
- end
522
-
523
- def handled_custom_keys(filter, keys)
524
- keys.select do |key|
525
- filter.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
526
- end
527
- end
528
-
529
132
  def find_by_attr(attribute, value)
530
133
  finder_name = "find_by_#{attribute}_attr"
531
134
  if respond_to?(finder_name, true)
@@ -538,20 +141,5 @@ module Capybara
538
141
  def find_by_class_attr(classes)
539
142
  Array(classes).map { |klass| XPath.attr(:class).contains_word(klass) }.reduce(:&)
540
143
  end
541
-
542
- def parameter_names(block)
543
- block.parameters.select { |(type, _name)| %i[key keyreq].include? type }.map { |(_type, name)| name }
544
- end
545
-
546
- def expression(type, allowed_filters, &block)
547
- if block
548
- @format, @expression = type, block
549
- allowed_filters = parameter_names(block) if allowed_filters.empty?
550
- allowed_filters.flatten.each { |ef| expression_filters[ef] = Filters::IdentityExpressionFilter.new(ef) }
551
- end
552
- format == type ? @expression : nil
553
- end
554
144
  end
555
145
  end
556
-
557
- # rubocop:enable Style/AsciiComments