capybara 3.29.0 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +91 -1
  3. data/README.md +10 -3
  4. data/lib/capybara.rb +17 -7
  5. data/lib/capybara/config.rb +7 -3
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/dsl.rb +10 -2
  8. data/lib/capybara/helpers.rb +3 -1
  9. data/lib/capybara/minitest.rb +232 -144
  10. data/lib/capybara/minitest/spec.rb +153 -97
  11. data/lib/capybara/node/actions.rb +35 -35
  12. data/lib/capybara/node/document.rb +2 -2
  13. data/lib/capybara/node/document_matchers.rb +3 -3
  14. data/lib/capybara/node/element.rb +23 -16
  15. data/lib/capybara/node/finders.rb +17 -11
  16. data/lib/capybara/node/matchers.rb +64 -51
  17. data/lib/capybara/node/simple.rb +4 -2
  18. data/lib/capybara/queries/ancestor_query.rb +1 -1
  19. data/lib/capybara/queries/base_query.rb +2 -1
  20. data/lib/capybara/queries/selector_query.rb +25 -5
  21. data/lib/capybara/queries/sibling_query.rb +1 -1
  22. data/lib/capybara/queries/style_query.rb +1 -1
  23. data/lib/capybara/queries/text_query.rb +6 -0
  24. data/lib/capybara/rack_test/browser.rb +7 -2
  25. data/lib/capybara/rack_test/driver.rb +1 -1
  26. data/lib/capybara/rack_test/form.rb +1 -1
  27. data/lib/capybara/rack_test/node.rb +34 -9
  28. data/lib/capybara/registration_container.rb +44 -0
  29. data/lib/capybara/registrations/servers.rb +1 -1
  30. data/lib/capybara/result.rb +29 -5
  31. data/lib/capybara/rspec/matcher_proxies.rb +4 -4
  32. data/lib/capybara/rspec/matchers.rb +27 -27
  33. data/lib/capybara/rspec/matchers/base.rb +12 -6
  34. data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
  35. data/lib/capybara/rspec/matchers/have_ancestor.rb +4 -3
  36. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  37. data/lib/capybara/rspec/matchers/have_selector.rb +15 -7
  38. data/lib/capybara/rspec/matchers/have_sibling.rb +3 -3
  39. data/lib/capybara/rspec/matchers/have_text.rb +3 -3
  40. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  41. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  42. data/lib/capybara/rspec/matchers/match_style.rb +2 -2
  43. data/lib/capybara/rspec/matchers/spatial_sugar.rb +2 -1
  44. data/lib/capybara/selector.rb +34 -17
  45. data/lib/capybara/selector/css.rb +1 -1
  46. data/lib/capybara/selector/definition.rb +7 -6
  47. data/lib/capybara/selector/definition/button.rb +8 -2
  48. data/lib/capybara/selector/definition/checkbox.rb +2 -2
  49. data/lib/capybara/selector/definition/css.rb +3 -1
  50. data/lib/capybara/selector/definition/datalist_input.rb +1 -1
  51. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  52. data/lib/capybara/selector/definition/element.rb +1 -1
  53. data/lib/capybara/selector/definition/field.rb +1 -1
  54. data/lib/capybara/selector/definition/file_field.rb +1 -1
  55. data/lib/capybara/selector/definition/fillable_field.rb +2 -2
  56. data/lib/capybara/selector/definition/label.rb +4 -2
  57. data/lib/capybara/selector/definition/link.rb +8 -0
  58. data/lib/capybara/selector/definition/radio_button.rb +2 -2
  59. data/lib/capybara/selector/definition/select.rb +32 -13
  60. data/lib/capybara/selector/definition/table.rb +6 -3
  61. data/lib/capybara/selector/filter_set.rb +11 -9
  62. data/lib/capybara/selector/filters/base.rb +6 -1
  63. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  64. data/lib/capybara/selector/selector.rb +8 -2
  65. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  66. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  67. data/lib/capybara/selenium/driver.rb +22 -11
  68. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +8 -10
  69. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +7 -9
  70. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
  71. data/lib/capybara/selenium/extensions/html5_drag.rb +30 -13
  72. data/lib/capybara/selenium/node.rb +97 -18
  73. data/lib/capybara/selenium/nodes/chrome_node.rb +11 -14
  74. data/lib/capybara/selenium/nodes/edge_node.rb +4 -2
  75. data/lib/capybara/selenium/nodes/firefox_node.rb +4 -4
  76. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  77. data/lib/capybara/selenium/patches/logs.rb +3 -5
  78. data/lib/capybara/server.rb +15 -3
  79. data/lib/capybara/server/checker.rb +1 -1
  80. data/lib/capybara/server/middleware.rb +20 -10
  81. data/lib/capybara/session.rb +43 -26
  82. data/lib/capybara/session/config.rb +9 -3
  83. data/lib/capybara/session/matchers.rb +6 -6
  84. data/lib/capybara/spec/public/test.js +69 -6
  85. data/lib/capybara/spec/session/all_spec.rb +60 -5
  86. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  87. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  88. data/lib/capybara/spec/session/click_button_spec.rb +16 -0
  89. data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
  90. data/lib/capybara/spec/session/find_spec.rb +31 -8
  91. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  92. data/lib/capybara/spec/session/has_css_spec.rb +12 -9
  93. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  94. data/lib/capybara/spec/session/has_field_spec.rb +16 -0
  95. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  96. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  97. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  98. data/lib/capybara/spec/session/has_text_spec.rb +35 -0
  99. data/lib/capybara/spec/session/node_spec.rb +160 -29
  100. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  101. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  102. data/lib/capybara/spec/session/selectors_spec.rb +15 -2
  103. data/lib/capybara/spec/session/window/window_spec.rb +7 -7
  104. data/lib/capybara/spec/spec_helper.rb +2 -2
  105. data/lib/capybara/spec/test_app.rb +14 -18
  106. data/lib/capybara/spec/views/form.erb +18 -2
  107. data/lib/capybara/spec/views/with_dragula.erb +3 -1
  108. data/lib/capybara/spec/views/with_html.erb +2 -2
  109. data/lib/capybara/spec/views/with_js.erb +1 -0
  110. data/lib/capybara/version.rb +1 -1
  111. data/spec/capybara_spec.rb +1 -1
  112. data/spec/dsl_spec.rb +16 -3
  113. data/spec/minitest_spec.rb +1 -1
  114. data/spec/minitest_spec_spec.rb +46 -46
  115. data/spec/rack_test_spec.rb +13 -1
  116. data/spec/regexp_dissassembler_spec.rb +40 -36
  117. data/spec/result_spec.rb +43 -32
  118. data/spec/rspec/features_spec.rb +1 -0
  119. data/spec/rspec/shared_spec_matchers.rb +68 -56
  120. data/spec/rspec_spec.rb +4 -4
  121. data/spec/selector_spec.rb +1 -1
  122. data/spec/selenium_spec_chrome.rb +9 -6
  123. data/spec/selenium_spec_chrome_remote.rb +2 -0
  124. data/spec/selenium_spec_firefox.rb +7 -2
  125. data/spec/server_spec.rb +65 -31
  126. data/spec/session_spec.rb +1 -1
  127. data/spec/shared_selenium_node.rb +21 -3
  128. data/spec/shared_selenium_session.rb +33 -14
  129. data/spec/spec_helper.rb +1 -1
  130. metadata +6 -4
@@ -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)
@@ -10,6 +10,7 @@ module Capybara
10
10
  class Selector
11
11
  class Definition
12
12
  attr_reader :name, :expressions
13
+
13
14
  extend Forwardable
14
15
 
15
16
  def initialize(name, locator_type: nil, raw_locator: false, supports_exact: nil, &block)
@@ -166,7 +167,7 @@ module Capybara
166
167
 
167
168
  def locator_filter(*types, **options, &block)
168
169
  types.each { |type| options[type] = true }
169
- @locator_filter = Capybara::Selector::Filters::LocatorFilter.new(block, options) if block
170
+ @locator_filter = Capybara::Selector::Filters::LocatorFilter.new(block, **options) if block
170
171
  @locator_filter
171
172
  end
172
173
 
@@ -181,7 +182,7 @@ module Capybara
181
182
  describe(:expression_filters, &block)
182
183
  else
183
184
  describe(:expression_filters) do |**options|
184
- describe_all_expression_filters(options)
185
+ describe_all_expression_filters(**options)
185
186
  end
186
187
  end
187
188
  end
@@ -189,7 +190,7 @@ module Capybara
189
190
  def describe_all_expression_filters(**opts)
190
191
  expression_filters.map do |ef_name, ef|
191
192
  if ef.matcher?
192
- handled_custom_keys(ef, opts.keys).map { |key| " with #{ef_name}[#{key} => #{opts[key]}]" }.join
193
+ handled_custom_options(ef, opts).map { |option, value| " with #{ef_name}[#{option} => #{value}]" }.join
193
194
  elsif opts.key?(ef_name)
194
195
  " with #{ef_name} #{opts[ef_name]}"
195
196
  end
@@ -251,9 +252,9 @@ module Capybara
251
252
 
252
253
  private
253
254
 
254
- def handled_custom_keys(filter, keys)
255
- keys.select do |key|
256
- filter.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
255
+ def handled_custom_options(filter, options)
256
+ options.select do |option, _|
257
+ filter.handles_option?(option) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(option)
257
258
  end
258
259
  end
259
260
 
@@ -4,6 +4,7 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
4
4
  xpath(:value, :title, :type, :name) do |locator, **options|
5
5
  input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
6
6
  btn_xpath = XPath.descendant(:button)
7
+ btn_xpath += XPath.descendant[XPath.attr(:role).equals('button')] if enable_aria_role
7
8
  image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type) == 'image']
8
9
 
9
10
  unless locator.nil?
@@ -26,7 +27,7 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
26
27
  image_btn_xpath = image_btn_xpath[alt_matches]
27
28
  end
28
29
 
29
- %i[value title type name].inject(input_btn_xpath.union(btn_xpath).union(image_btn_xpath)) do |memo, ef|
30
+ %i[value title type].inject(input_btn_xpath.union(btn_xpath).union(image_btn_xpath)) do |memo, ef|
30
31
  memo[find_by_attr(ef, options[ef])]
31
32
  end
32
33
  end
@@ -34,10 +35,15 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
34
35
  node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
35
36
  expression_filter(:disabled) { |xpath, val| val ? xpath : xpath[~XPath.attr(:disabled)] }
36
37
 
38
+ node_filter(:name) { |node, value| !value.is_a?(Regexp) || value.match?(node[:name]) }
39
+ expression_filter(:name) do |xpath, val|
40
+ builder(xpath).add_attribute_conditions(name: val)
41
+ end
42
+
37
43
  describe_expression_filters do |disabled: nil, **options|
38
44
  desc = +''
39
45
  desc << ' that is not disabled' if disabled == false
40
- desc << describe_all_expression_filters(options)
46
+ desc << describe_all_expression_filters(**options)
41
47
  end
42
48
 
43
49
  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|
@@ -18,7 +18,7 @@ Capybara.add_selector(:fillable_field, locator_type: [String, Symbol]) do
18
18
  end
19
19
  end
20
20
 
21
- filter_set(:_field, %i[disabled multiple name placeholder valid])
21
+ filter_set(:_field, %i[disabled multiple name placeholder valid validation_message])
22
22
 
23
23
  node_filter(:with) do |node, with|
24
24
  val = node.value
@@ -2,7 +2,7 @@
2
2
 
3
3
  Capybara.add_selector(:label, locator_type: [String, Symbol]) do
4
4
  label 'label'
5
- xpath(:for) do |locator, options|
5
+ xpath(:for) do |locator, **options|
6
6
  xpath = XPath.descendant(:label)
7
7
  unless locator.nil?
8
8
  locator_matchers = XPath.string.n.is(locator.to_s) | (XPath.attr(:id) == locator.to_s)
@@ -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)
@@ -5,6 +5,13 @@ Capybara.add_selector(:link, locator_type: [String, Symbol]) do
5
5
  xpath = XPath.descendant(:a)
6
6
  xpath = builder(xpath).add_attribute_conditions(href: href) unless href == false
7
7
 
8
+ if enable_aria_role
9
+ role_path = XPath.descendant[XPath.attr(:role).equals('link')]
10
+ role_path = builder(role_path).add_attribute_conditions(href: href) unless [true, false].include? href
11
+
12
+ xpath += role_path
13
+ end
14
+
8
15
  unless locator.nil?
9
16
  locator = locator.to_s
10
17
  matchers = [XPath.attr(:id) == locator,
@@ -18,6 +25,7 @@ Capybara.add_selector(:link, locator_type: [String, Symbol]) do
18
25
 
19
26
  xpath = xpath[find_by_attr(:title, title)]
20
27
  xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt) == alt]] if alt
28
+
21
29
  xpath
22
30
  end
23
31
 
@@ -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,22 +5,32 @@ 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])
12
12
 
13
13
  node_filter(:options) do |node, options|
14
- actual = if node.visible?
15
- node.all(:xpath, './/option', wait: false).map(&:text)
16
- else
17
- node.all(:xpath, './/option', visible: false, wait: false).map { |option| option.text(:all) }
18
- end
14
+ actual = options_text(node)
19
15
  (options.sort == actual.sort).tap do |res|
20
16
  add_error("Expected options #{options.inspect} found #{actual.inspect}") unless res
21
17
  end
22
18
  end
23
19
 
20
+ node_filter(:enabled_options) do |node, options|
21
+ actual = options_text(node) { |o| !o.disabled? }
22
+ (options.sort == actual.sort).tap do |res|
23
+ add_error("Expected enabled options #{options.inspect} found #{actual.inspect}") unless res
24
+ end
25
+ end
26
+
27
+ node_filter(:disabled_options) do |node, options|
28
+ actual = options_text(node, &:disabled?)
29
+ (options.sort == actual.sort).tap do |res|
30
+ add_error("Expected disabled options #{options.inspect} found #{actual.inspect}") unless res
31
+ end
32
+ end
33
+
24
34
  expression_filter(:with_options) do |expr, options|
25
35
  options.inject(expr) do |xpath, option|
26
36
  xpath[expression_for(:option, option)]
@@ -28,18 +38,14 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
28
38
  end
29
39
 
30
40
  node_filter(:selected) do |node, selected|
31
- actual = node.all(:xpath, './/option', visible: false, wait: false)
32
- .select(&:selected?)
33
- .map { |option| option.text(:all) }
41
+ actual = options_text(node, visible: false, &:selected?)
34
42
  (Array(selected).sort == actual.sort).tap do |res|
35
43
  add_error("Expected #{selected.inspect} to be selected found #{actual.inspect}") unless res
36
44
  end
37
45
  end
38
46
 
39
47
  node_filter(:with_selected) do |node, selected|
40
- actual = node.all(:xpath, './/option', visible: false, wait: false)
41
- .select(&:selected?)
42
- .map { |option| option.text(:all) }
48
+ actual = options_text(node, visible: false, &:selected?)
43
49
  (Array(selected) - actual).empty?.tap do |res|
44
50
  add_error("Expected at least #{selected.inspect} to be selected found #{actual.inspect}") unless res
45
51
  end
@@ -51,12 +57,25 @@ Capybara.add_selector(:select, locator_type: [String, Symbol]) do
51
57
  desc
52
58
  end
53
59
 
54
- describe_node_filters do |options: nil, selected: nil, with_selected: nil, disabled: nil, **|
60
+ describe_node_filters do |
61
+ options: nil, disabled_options: nil, enabled_options: nil,
62
+ selected: nil, with_selected: nil,
63
+ disabled: nil, **|
55
64
  desc = +''
56
65
  desc << " with options #{options.inspect}" if options
66
+ desc << " with disabled options #{disabled_options.inspect}}" if disabled_options
67
+ desc << " with enabled options #{enabled_options.inspect}" if enabled_options
57
68
  desc << " with #{selected.inspect} selected" if selected
58
69
  desc << " with at least #{with_selected.inspect} selected" if with_selected
59
70
  desc << ' which is disabled' if disabled
60
71
  desc
61
72
  end
73
+
74
+ def options_text(node, **opts, &filter_block)
75
+ opts[:wait] = false
76
+ opts[:visible] = false unless node.visible?
77
+ node.all(:xpath, './/option', **opts, &filter_block).map do |o|
78
+ o.text((:all if opts[:visible] == false))
79
+ end
80
+ end
62
81
  end
@@ -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]
@@ -40,7 +43,7 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
40
43
  end
41
44
 
42
45
  expression_filter(:cols, valid_values: [Array]) do |xpath, cols|
43
- raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all? { |col| col.is_a? Array }
46
+ raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all?(Array)
44
47
 
45
48
  rows = cols.transpose
46
49
  col_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
@@ -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
@@ -48,7 +48,12 @@ module Capybara
48
48
 
49
49
  def apply(subject, name, value, skip_value, ctx)
50
50
  return skip_value if skip?(value)
51
- raise ArgumentError, "Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}#{" : #{@name}" if @name.is_a?(Regexp)}" unless valid_value?(value)
51
+
52
+ unless valid_value?(value)
53
+ raise ArgumentError,
54
+ "Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}" \
55
+ "#{" : #{name}" if @name.is_a?(Regexp)}"
56
+ end
52
57
 
53
58
  if @block.arity == 2
54
59
  filter_context(ctx).instance_exec(subject, value, &@block)
@@ -7,7 +7,7 @@ module Capybara
7
7
  module Filters
8
8
  class LocatorFilter < NodeFilter
9
9
  def initialize(block, **options)
10
- super(nil, nil, block, options)
10
+ super(nil, nil, block, **options)
11
11
  end
12
12
 
13
13
  def matches?(node, value, context = nil, exact:)
@@ -48,6 +48,10 @@ module Capybara
48
48
  @config[:enable_aria_label]
49
49
  end
50
50
 
51
+ def enable_aria_role
52
+ @config[:enable_aria_role]
53
+ end
54
+
51
55
  def test_id
52
56
  @config[:test_id]
53
57
  end
@@ -56,12 +60,14 @@ module Capybara
56
60
  if format
57
61
  raise ArgumentError, "Selector #{@name} does not support #{format}" unless expressions.key?(format)
58
62
 
59
- instance_exec(locator, options, &expressions[format])
63
+ instance_exec(locator, **options, &expressions[format])
60
64
  else
61
65
  warn 'Selector has no format'
62
66
  end
63
67
  ensure
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)
68
+ unless locator_valid?(locator)
69
+ warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara."
70
+ end
65
71
  end
66
72
 
67
73
  def add_error(error_msg)