capybara 3.29.0 → 3.30.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +19 -1
  3. data/README.md +1 -1
  4. data/lib/capybara/config.rb +7 -3
  5. data/lib/capybara/helpers.rb +3 -1
  6. data/lib/capybara/node/actions.rb +23 -19
  7. data/lib/capybara/node/document.rb +2 -2
  8. data/lib/capybara/node/document_matchers.rb +3 -3
  9. data/lib/capybara/node/element.rb +10 -8
  10. data/lib/capybara/node/finders.rb +12 -10
  11. data/lib/capybara/node/matchers.rb +39 -33
  12. data/lib/capybara/node/simple.rb +3 -1
  13. data/lib/capybara/queries/ancestor_query.rb +1 -1
  14. data/lib/capybara/queries/selector_query.rb +17 -4
  15. data/lib/capybara/queries/sibling_query.rb +1 -1
  16. data/lib/capybara/rack_test/browser.rb +4 -1
  17. data/lib/capybara/rack_test/driver.rb +1 -1
  18. data/lib/capybara/rack_test/form.rb +1 -1
  19. data/lib/capybara/selector.rb +22 -16
  20. data/lib/capybara/selector/css.rb +1 -1
  21. data/lib/capybara/selector/definition.rb +2 -2
  22. data/lib/capybara/selector/definition/button.rb +7 -2
  23. data/lib/capybara/selector/definition/checkbox.rb +2 -2
  24. data/lib/capybara/selector/definition/css.rb +3 -1
  25. data/lib/capybara/selector/definition/datalist_input.rb +1 -1
  26. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  27. data/lib/capybara/selector/definition/element.rb +1 -1
  28. data/lib/capybara/selector/definition/field.rb +1 -1
  29. data/lib/capybara/selector/definition/file_field.rb +1 -1
  30. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  31. data/lib/capybara/selector/definition/label.rb +3 -1
  32. data/lib/capybara/selector/definition/radio_button.rb +2 -2
  33. data/lib/capybara/selector/definition/select.rb +1 -1
  34. data/lib/capybara/selector/definition/table.rb +5 -2
  35. data/lib/capybara/selector/filter_set.rb +11 -9
  36. data/lib/capybara/selector/filters/base.rb +6 -1
  37. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  38. data/lib/capybara/selector/selector.rb +4 -2
  39. data/lib/capybara/selenium/driver.rb +19 -11
  40. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
  41. data/lib/capybara/selenium/extensions/html5_drag.rb +6 -5
  42. data/lib/capybara/selenium/node.rb +6 -4
  43. data/lib/capybara/selenium/nodes/chrome_node.rb +7 -3
  44. data/lib/capybara/selenium/nodes/edge_node.rb +3 -1
  45. data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
  46. data/lib/capybara/server.rb +15 -3
  47. data/lib/capybara/server/checker.rb +1 -1
  48. data/lib/capybara/server/middleware.rb +20 -10
  49. data/lib/capybara/session.rb +10 -8
  50. data/lib/capybara/session/config.rb +6 -2
  51. data/lib/capybara/session/matchers.rb +6 -6
  52. data/lib/capybara/spec/public/test.js +11 -0
  53. data/lib/capybara/spec/session/all_spec.rb +15 -0
  54. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  55. data/lib/capybara/spec/session/assert_text_spec.rb +4 -0
  56. data/lib/capybara/spec/session/click_button_spec.rb +5 -0
  57. data/lib/capybara/spec/session/find_spec.rb +20 -0
  58. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  59. data/lib/capybara/spec/session/has_text_spec.rb +31 -0
  60. data/lib/capybara/spec/session/node_spec.rb +15 -0
  61. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  62. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  63. data/lib/capybara/spec/session/selectors_spec.rb +15 -2
  64. data/lib/capybara/spec/views/form.erb +5 -0
  65. data/lib/capybara/version.rb +1 -1
  66. data/spec/dsl_spec.rb +2 -2
  67. data/spec/minitest_spec_spec.rb +46 -46
  68. data/spec/regexp_dissassembler_spec.rb +45 -37
  69. data/spec/result_spec.rb +3 -3
  70. data/spec/rspec/features_spec.rb +1 -0
  71. data/spec/rspec/shared_spec_matchers.rb +3 -3
  72. data/spec/rspec_spec.rb +4 -4
  73. data/spec/selenium_spec_chrome.rb +3 -3
  74. data/spec/selenium_spec_firefox.rb +7 -2
  75. data/spec/server_spec.rb +42 -0
  76. data/spec/session_spec.rb +1 -1
  77. data/spec/shared_selenium_node.rb +3 -3
  78. data/spec/shared_selenium_session.rb +8 -7
  79. metadata +3 -3
@@ -108,7 +108,9 @@ module Capybara
108
108
  !find_xpath(VISIBILITY_XPATH)
109
109
  else
110
110
  # No need for an xpath if only checking the current element
111
- !(native.key?('hidden') || (/display:\s?none/.match? native[:style]) || %w[script head].include?(tag_name))
111
+ !(native.key?('hidden') ||
112
+ /display:\s?none/.match?(native[:style] || '') ||
113
+ %w[script head].include?(tag_name))
112
114
  end
113
115
  end
114
116
 
@@ -12,7 +12,7 @@ module Capybara
12
12
  ancestors = node.find_xpath(XPath.ancestor.to_s)
13
13
  .map(&method(:to_element))
14
14
  .select { |el| match_results.include?(el) }
15
- Capybara::Result.new(ancestors, self)
15
+ Capybara::Result.new(ordered_results(ancestors), self)
16
16
  end
17
17
  end
18
18
 
@@ -16,11 +16,13 @@ module Capybara
16
16
  enable_aria_label: session_options.enable_aria_label,
17
17
  test_id: session_options.test_id,
18
18
  selector_format: nil,
19
+ order: nil,
19
20
  **options,
20
21
  &filter_block)
21
22
  @resolved_node = nil
22
23
  @resolved_count = 0
23
24
  @options = options.dup
25
+ @order = order
24
26
  @filter_cache = Hash.new { |hsh, key| hsh[key] = {} }
25
27
 
26
28
  super(@options)
@@ -37,7 +39,7 @@ module Capybara
37
39
 
38
40
  raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
39
41
 
40
- @expression = selector.call(@locator, @options)
42
+ @expression = selector.call(@locator, **@options)
41
43
 
42
44
  warn_exact_usage
43
45
 
@@ -77,7 +79,9 @@ module Capybara
77
79
  end
78
80
 
79
81
  %i[above below left_of right_of near].each do |spatial_filter|
80
- desc << " #{spatial_filter} #{options[spatial_filter] rescue '<ERROR>'}" if options[spatial_filter] && show_for[:spatial] # rubocop:disable Style/RescueModifier
82
+ if options[spatial_filter] && show_for[:spatial]
83
+ desc << " #{spatial_filter} #{options[spatial_filter] rescue '<ERROR>'}" # rubocop:disable Style/RescueModifier
84
+ end
81
85
  end
82
86
 
83
87
  desc << selector.description(node_filters: show_for[:node], **options)
@@ -148,7 +152,7 @@ module Capybara
148
152
 
149
153
  node.synchronize do
150
154
  children = find_nodes_by_selector_format(node, exact).map(&method(:to_element))
151
- Capybara::Result.new(children, self)
155
+ Capybara::Result.new(ordered_results(children), self)
152
156
  end
153
157
  end
154
158
 
@@ -311,6 +315,15 @@ module Capybara
311
315
  filters
312
316
  end
313
317
 
318
+ def ordered_results(results)
319
+ case @order
320
+ when :reverse
321
+ results.reverse
322
+ else
323
+ results
324
+ end
325
+ end
326
+
314
327
  def custom_keys
315
328
  @custom_keys ||= node_filters.keys + expression_filters.keys
316
329
  end
@@ -338,7 +351,7 @@ module Capybara
338
351
  conditions[:id] = options[:id] if use_default_id_filter?
339
352
  conditions[:class] = options[:class] if use_default_class_filter?
340
353
  conditions[:style] = options[:style] if use_default_style_filter? && !options[:style].is_a?(Hash)
341
- builder(expr).add_attribute_conditions(conditions)
354
+ builder(expr).add_attribute_conditions(**conditions)
342
355
  end
343
356
 
344
357
  def use_default_id_filter?
@@ -11,7 +11,7 @@ module Capybara
11
11
  siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
12
12
  .map(&method(:to_element))
13
13
  .select { |el| match_results.include?(el) }
14
- Capybara::Result.new(siblings, self)
14
+ Capybara::Result.new(ordered_results(siblings), self)
15
15
  end
16
16
  end
17
17
 
@@ -53,7 +53,10 @@ class Capybara::RackTest::Browser
53
53
  end
54
54
  end
55
55
  end
56
- raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
56
+
57
+ if last_response.redirect? # rubocop:disable Style/GuardClause
58
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
59
+ end
57
60
  end
58
61
 
59
62
  def process(method, path, attributes = {}, env = {})
@@ -42,7 +42,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
42
42
  end
43
43
 
44
44
  def visit(path, **attributes)
45
- browser.visit(path, attributes)
45
+ browser.visit(path, **attributes)
46
46
  end
47
47
 
48
48
  def refresh
@@ -56,7 +56,7 @@ private
56
56
  end
57
57
 
58
58
  def request_method
59
- /post/i.match?(self[:method]) ? :post : :get
59
+ /post/i.match?(self[:method] || '') ? :post : :get
60
60
  end
61
61
 
62
62
  def merge_param!(params, key, value)
@@ -30,8 +30,8 @@ require 'capybara/selector/definition'
30
30
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or
31
31
  # associated label text
32
32
  # * Filters:
33
- # * :name (String) - Matches the name attribute
34
- # * :placeholder (String) - Matches the placeholder attribute
33
+ # * :name (String, Regexp) - Matches the name attribute
34
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
35
35
  # * :type (String) - Matches the type attribute of the field or element type for 'textarea' and 'select'
36
36
  # * :readonly (Boolean) - Match on the element being readonly
37
37
  # * :with (String, Regexp) - Matches the current value of the field
@@ -58,7 +58,7 @@ require 'capybara/selector/definition'
58
58
  # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
59
59
  # * Locator: Matches the id, {Capybara.configure test_id} attribute, name, 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
60
  # * Filters:
61
- # * :name (String) - Matches the name attribute
61
+ # * :name (String, Regexp) - Matches the name attribute
62
62
  # * :title (String) - Matches the title attribute
63
63
  # * :value (String) - Matches the value of an input button
64
64
  # * :type (String) - Matches the type attribute
@@ -72,8 +72,8 @@ require 'capybara/selector/definition'
72
72
  # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
73
73
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
74
74
  # * Filters:
75
- # * :name (String) - Matches the name attribute
76
- # * :placeholder (String) - Matches the placeholder attribute
75
+ # * :name (String, Regexp) - Matches the name attribute
76
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
77
77
  # * :with (String, Regexp) - Matches the current value of the field
78
78
  # * :type (String) - Matches the type attribute of the field or element type for 'textarea'
79
79
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
@@ -83,7 +83,7 @@ require 'capybara/selector/definition'
83
83
  # * **:radio_button** - Find radio buttons
84
84
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
85
85
  # * Filters:
86
- # * :name (String) - Matches the name attribute
86
+ # * :name (String, Regexp) - Matches the name attribute
87
87
  # * :checked (Boolean) - Match checked fields?
88
88
  # * :unchecked (Boolean) - Match unchecked fields?
89
89
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
@@ -93,18 +93,18 @@ require 'capybara/selector/definition'
93
93
  # * **:checkbox** - Find checkboxes
94
94
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
95
95
  # * Filters:
96
- # * :name (String) - Matches the name attribute
96
+ # * :name (String, Regexp) - Matches the name attribute
97
97
  # * :checked (Boolean) - Match checked fields?
98
98
  # * :unchecked (Boolean) - Match unchecked fields?
99
99
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
100
- # * :option (String, Regexp) - Match the current value
101
- # * :with - Alias of :option
100
+ # * :with (String, Regexp) - Match the current value
101
+ # * :option - Alias of :with
102
102
  #
103
103
  # * **:select** - Find select elements
104
104
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
105
105
  # * Filters:
106
- # * :name (String) - Matches the name attribute
107
- # * :placeholder (String) - Matches the placeholder attribute
106
+ # * :name (String, Regexp) - Matches the name attribute
107
+ # * :placeholder (String, Placeholder) - Matches the placeholder attribute
108
108
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
109
109
  # * :multiple (Boolean) - Match fields that accept multiple values
110
110
  # * :options (Array<String>) - Exact match options
@@ -122,8 +122,8 @@ require 'capybara/selector/definition'
122
122
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name,
123
123
  # placeholder, or associated label text
124
124
  # * Filters:
125
- # * :name (String) - Matches the name attribute
126
- # * :placeholder (String) - Matches the placeholder attribute
125
+ # * :name (String, Regexp) - Matches the name attribute
126
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
127
127
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
128
128
  # * :options (Array<String>) - Exact match options
129
129
  # * :with_options (Array<String>) - Partial match options
@@ -136,7 +136,7 @@ require 'capybara/selector/definition'
136
136
  # * **:file_field** - Find file input elements
137
137
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
138
138
  # * Filters:
139
- # * :name (String) - Matches the name attribute
139
+ # * :name (String, Regexp) - Matches the name attribute
140
140
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
141
141
  # * :multiple (Boolean) - Match field that accepts multiple values
142
142
  #
@@ -174,9 +174,15 @@ Capybara::Selector::FilterSet.add(:_field) do
174
174
  node_filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
175
175
  node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
176
176
  node_filter(:valid, :boolean) { |node, value| node.evaluate_script('this.validity.valid') == value }
177
+ node_filter(:name) { |node, value| !value.is_a?(Regexp) || value.match?(node[:name]) }
178
+ node_filter(:placeholder) { |node, value| !value.is_a?(Regexp) || value.match?(node[:placeholder]) }
177
179
 
178
- expression_filter(:name) { |xpath, val| xpath[XPath.attr(:name) == val] }
179
- expression_filter(:placeholder) { |xpath, val| xpath[XPath.attr(:placeholder) == val] }
180
+ expression_filter(:name) do |xpath, val|
181
+ builder(xpath).add_attribute_conditions(name: val)
182
+ end
183
+ expression_filter(:placeholder) do |xpath, val|
184
+ builder(xpath).add_attribute_conditions(placeholder: val)
185
+ end
180
186
  expression_filter(:disabled) { |xpath, val| val ? xpath : xpath[~XPath.attr(:disabled)] }
181
187
  expression_filter(:multiple) { |xpath, val| xpath[val ? XPath.attr(:multiple) : ~XPath.attr(:multiple)] }
182
188
 
@@ -15,7 +15,7 @@ module Capybara
15
15
  end
16
16
 
17
17
  def self.escape_char(char)
18
- char.match?(%r{[ -/:-~]}) ? "\\#{char}" : format('\\%06x', char.ord)
18
+ char.match?(%r{[ -/:-~]}) ? "\\#{char}" : format('\\%06<hex>x', hex: char.ord)
19
19
  end
20
20
 
21
21
  def self.split(css)
@@ -166,7 +166,7 @@ module Capybara
166
166
 
167
167
  def locator_filter(*types, **options, &block)
168
168
  types.each { |type| options[type] = true }
169
- @locator_filter = Capybara::Selector::Filters::LocatorFilter.new(block, options) if block
169
+ @locator_filter = Capybara::Selector::Filters::LocatorFilter.new(block, **options) if block
170
170
  @locator_filter
171
171
  end
172
172
 
@@ -181,7 +181,7 @@ module Capybara
181
181
  describe(:expression_filters, &block)
182
182
  else
183
183
  describe(:expression_filters) do |**options|
184
- describe_all_expression_filters(options)
184
+ describe_all_expression_filters(**options)
185
185
  end
186
186
  end
187
187
  end
@@ -26,7 +26,7 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
26
26
  image_btn_xpath = image_btn_xpath[alt_matches]
27
27
  end
28
28
 
29
- %i[value title type name].inject(input_btn_xpath.union(btn_xpath).union(image_btn_xpath)) do |memo, ef|
29
+ %i[value title type].inject(input_btn_xpath.union(btn_xpath).union(image_btn_xpath)) do |memo, ef|
30
30
  memo[find_by_attr(ef, options[ef])]
31
31
  end
32
32
  end
@@ -34,10 +34,15 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
34
34
  node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
35
35
  expression_filter(:disabled) { |xpath, val| val ? xpath : xpath[~XPath.attr(:disabled)] }
36
36
 
37
+ node_filter(:name) { |node, value| !value.is_a?(Regexp) || value.match?(node[:name]) }
38
+ expression_filter(:name) do |xpath, val|
39
+ builder(xpath).add_attribute_conditions(name: val)
40
+ end
41
+
37
42
  describe_expression_filters do |disabled: nil, **options|
38
43
  desc = +''
39
44
  desc << ' that is not disabled' if disabled == false
40
- desc << describe_all_expression_filters(options)
45
+ desc << describe_all_expression_filters(**options)
41
46
  end
42
47
 
43
48
  describe_node_filters do |disabled: nil, **|
@@ -5,7 +5,7 @@ Capybara.add_selector(:checkbox, locator_type: [String, Symbol]) do
5
5
  xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
6
6
  XPath.attr(:type) == 'checkbox'
7
7
  ]
8
- locate_field(xpath, locator, options)
8
+ locate_field(xpath, locator, **options)
9
9
  end
10
10
 
11
11
  filter_set(:_field, %i[checked unchecked disabled name])
@@ -20,7 +20,7 @@ Capybara.add_selector(:checkbox, locator_type: [String, Symbol]) do
20
20
  describe_node_filters do |option: nil, with: nil, **|
21
21
  desc = +''
22
22
  desc << " with value #{option.inspect}" if option
23
- desc << " with value #{with.inspec}" if with
23
+ desc << " with value #{with.inspect}" if with
24
24
  desc
25
25
  end
26
26
  end
@@ -2,7 +2,9 @@
2
2
 
3
3
  Capybara.add_selector(:css, locator_type: [String, Symbol], raw_locator: true) do
4
4
  css do |css|
5
- warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead." if css.is_a? Symbol
5
+ if css.is_a? Symbol
6
+ warn "DEPRECATED: Passing a symbol (#{css.inspect}) as the CSS locator is deprecated - please pass a string instead."
7
+ end
6
8
  css
7
9
  end
8
10
  end
@@ -5,7 +5,7 @@ Capybara.add_selector(:datalist_input, locator_type: [String, Symbol]) do
5
5
 
6
6
  xpath do |locator, **options|
7
7
  xpath = XPath.descendant(:input)[XPath.attr(:list)]
8
- locate_field(xpath, locator, options)
8
+ locate_field(xpath, locator, **options)
9
9
  end
10
10
 
11
11
  filter_set(:_field, %i[disabled name placeholder])
@@ -16,7 +16,7 @@ Capybara.add_selector(:datalist_option, locator_type: [String, Symbol]) do
16
16
  describe_expression_filters do |disabled: nil, **options|
17
17
  desc = +''
18
18
  desc << ' that is not disabled' if disabled == false
19
- desc << describe_all_expression_filters(options)
19
+ desc << describe_all_expression_filters(**options)
20
20
  end
21
21
 
22
22
  describe_node_filters do |**options|
@@ -19,7 +19,7 @@ Capybara.add_selector(:element, locator_type: [String, Symbol]) do
19
19
 
20
20
  describe_expression_filters do |**options|
21
21
  booleans, values = options.partition { |_k, v| [true, false].include? v }.map(&:to_h)
22
- desc = describe_all_expression_filters(values)
22
+ desc = describe_all_expression_filters(**values)
23
23
  desc + booleans.map do |k, v|
24
24
  v ? " with #{k} attribute" : "without #{k} attribute"
25
25
  end.join
@@ -7,7 +7,7 @@ Capybara.add_selector(:field, locator_type: [String, Symbol]) do
7
7
  invalid_types = %w[submit image]
8
8
  invalid_types << 'hidden' unless options[:type].to_s == 'hidden'
9
9
  xpath = XPath.descendant(:input, :textarea, :select)[!XPath.attr(:type).one_of(*invalid_types)]
10
- locate_field(xpath, locator, options)
10
+ locate_field(xpath, locator, **options)
11
11
  end
12
12
 
13
13
  expression_filter(:type) do |expr, type|
@@ -6,7 +6,7 @@ Capybara.add_selector(:file_field, locator_type: [String, Symbol]) do
6
6
  xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
7
7
  XPath.attr(:type) == 'file'
8
8
  ]
9
- locate_field(xpath, locator, options)
9
+ locate_field(xpath, locator, **options)
10
10
  end
11
11
 
12
12
  filter_set(:_field, %i[disabled multiple name])
@@ -6,7 +6,7 @@ Capybara.add_selector(:fillable_field, locator_type: [String, Symbol]) do
6
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
- locate_field(xpath, locator, options)
9
+ locate_field(xpath, locator, **options)
10
10
  end
11
11
 
12
12
  expression_filter(:type) do |expr, type|
@@ -10,7 +10,9 @@ Capybara.add_selector(:label, locator_type: [String, Symbol]) do
10
10
  xpath = xpath[locator_matchers]
11
11
  end
12
12
  if options.key?(:for)
13
- if (for_option = options[:for].is_a?(Capybara::Node::Element) ? options[:for][:id] : options[:for])
13
+ for_option = options[:for]
14
+ for_option = for_option[:id] if for_option.is_a?(Capybara::Node::Element)
15
+ if for_option && (for_option != '')
14
16
  with_attr = builder(XPath.self).add_attribute_conditions(for: for_option)
15
17
  wrapped = !XPath.attr(:for) &
16
18
  builder(XPath.self.descendant(*labelable_elements)).add_attribute_conditions(id: for_option)
@@ -6,7 +6,7 @@ Capybara.add_selector(:radio_button, locator_type: [String, Symbol]) do
6
6
  xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
7
7
  XPath.attr(:type) == 'radio'
8
8
  ]
9
- locate_field(xpath, locator, options)
9
+ locate_field(xpath, locator, **options)
10
10
  end
11
11
 
12
12
  filter_set(:_field, %i[checked unchecked disabled name])
@@ -21,7 +21,7 @@ Capybara.add_selector(:radio_button, locator_type: [String, Symbol]) do
21
21
  describe_node_filters do |option: nil, with: nil, **|
22
22
  desc = +''
23
23
  desc << " with value #{option.inspect}" if option
24
- desc << " with value #{with.inspec}" if with
24
+ desc << " with value #{with.inspect}" if with
25
25
  desc
26
26
  end
27
27
  end
@@ -5,7 +5,7 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
5
5
 
6
6
  xpath do |locator, **options|
7
7
  xpath = XPath.descendant(:select)
8
- locate_field(xpath, locator, options)
8
+ locate_field(xpath, locator, **options)
9
9
  end
10
10
 
11
11
  filter_set(:_field, %i[disabled multiple name placeholder])
@@ -19,7 +19,10 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
19
19
  header = XPath.descendant(:th)[XPath.string.n.is(header)]
20
20
  td = XPath.descendant(:tr)[header].descendant(:td)
21
21
  cell_condition = XPath.string.n.is(cell_str)
22
- cell_condition &= prev_col_position?(XPath.ancestor(:table)[1].join(xp)) if xp
22
+ if xp
23
+ prev_cell = XPath.ancestor(:table)[1].join(xp)
24
+ cell_condition &= (prev_cell & prev_col_position?(prev_cell))
25
+ end
23
26
  td[cell_condition]
24
27
  end
25
28
  else
@@ -28,7 +31,7 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
28
31
 
29
32
  if prev_cell
30
33
  prev_cell = XPath.ancestor(:tr)[1].preceding_sibling(:tr).join(prev_cell)
31
- cell_condition &= prev_col_position?(prev_cell)
34
+ cell_condition &= (prev_cell & prev_col_position?(prev_cell))
32
35
  end
33
36
 
34
37
  XPath.descendant(:td)[cell_condition]
@@ -15,15 +15,15 @@ module Capybara
15
15
  instance_eval(&block)
16
16
  end
17
17
 
18
- def node_filter(names, *types_and_options, &block)
18
+ def node_filter(names, *types, **options, &block)
19
19
  Array(names).each do |name|
20
- add_filter(name, Filters::NodeFilter, *types_and_options, &block)
20
+ add_filter(name, Filters::NodeFilter, *types, **options, &block)
21
21
  end
22
22
  end
23
23
  alias_method :filter, :node_filter
24
24
 
25
- def expression_filter(name, *types_and_options, &block)
26
- add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
25
+ def expression_filter(name, *types, **options, &block)
26
+ add_filter(name, Filters::ExpressionFilter, *types, **options, &block)
27
27
  end
28
28
 
29
29
  def describe(what = nil, &block)
@@ -42,9 +42,9 @@ module Capybara
42
42
  def description(node_filters: true, expression_filters: true, **options)
43
43
  opts = options_with_defaults(options)
44
44
  description = +''
45
- description << undeclared_descriptions.map { |desc| desc.call(opts).to_s }.join
46
- description << expression_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if expression_filters
47
- description << node_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if node_filters
45
+ description << undeclared_descriptions.map { |desc| desc.call(**opts).to_s }.join
46
+ description << expression_filter_descriptions.map { |desc| desc.call(**opts).to_s }.join if expression_filters
47
+ description << node_filter_descriptions.map { |desc| desc.call(**opts).to_s }.join if node_filters
48
48
  description
49
49
  end
50
50
 
@@ -112,9 +112,11 @@ module Capybara
112
112
 
113
113
  def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
114
114
  types.each { |type| options[type] = true }
115
- raise 'ArgumentError', ':default option is not supported for filters with a :matcher option' if matcher && options[:default]
115
+ if matcher && options[:default]
116
+ raise 'ArgumentError', ':default option is not supported for filters with a :matcher option'
117
+ end
116
118
 
117
- filter = filter_class.new(name, matcher, block, options)
119
+ filter = filter_class.new(name, matcher, block, **options)
118
120
  (filter_class <= Filters::ExpressionFilter ? @expression_filters : @node_filters)[name] = filter
119
121
  end
120
122
  end