capybara 3.23.0 → 3.35.3

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +264 -11
  3. data/README.md +10 -6
  4. data/lib/capybara.rb +20 -8
  5. data/lib/capybara/config.rb +10 -8
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +4 -0
  8. data/lib/capybara/driver/node.rb +4 -0
  9. data/lib/capybara/dsl.rb +10 -2
  10. data/lib/capybara/helpers.rb +28 -2
  11. data/lib/capybara/minitest.rb +232 -144
  12. data/lib/capybara/minitest/spec.rb +156 -97
  13. data/lib/capybara/node/actions.rb +36 -36
  14. data/lib/capybara/node/base.rb +6 -6
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +3 -3
  17. data/lib/capybara/node/element.rb +77 -33
  18. data/lib/capybara/node/finders.rb +24 -17
  19. data/lib/capybara/node/matchers.rb +79 -64
  20. data/lib/capybara/node/simple.rb +11 -4
  21. data/lib/capybara/queries/ancestor_query.rb +6 -10
  22. data/lib/capybara/queries/base_query.rb +2 -1
  23. data/lib/capybara/queries/current_path_query.rb +14 -4
  24. data/lib/capybara/queries/selector_query.rb +259 -23
  25. data/lib/capybara/queries/sibling_query.rb +5 -11
  26. data/lib/capybara/queries/style_query.rb +1 -1
  27. data/lib/capybara/queries/text_query.rb +13 -1
  28. data/lib/capybara/rack_test/browser.rb +13 -4
  29. data/lib/capybara/rack_test/driver.rb +2 -1
  30. data/lib/capybara/rack_test/form.rb +2 -2
  31. data/lib/capybara/rack_test/node.rb +42 -6
  32. data/lib/capybara/registration_container.rb +44 -0
  33. data/lib/capybara/registrations/drivers.rb +18 -12
  34. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  35. data/lib/capybara/registrations/servers.rb +9 -2
  36. data/lib/capybara/result.rb +39 -19
  37. data/lib/capybara/rspec.rb +2 -0
  38. data/lib/capybara/rspec/matcher_proxies.rb +5 -5
  39. data/lib/capybara/rspec/matchers.rb +97 -74
  40. data/lib/capybara/rspec/matchers/base.rb +19 -6
  41. data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
  42. data/lib/capybara/rspec/matchers/have_ancestor.rb +5 -7
  43. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  44. data/lib/capybara/rspec/matchers/have_selector.rb +15 -10
  45. data/lib/capybara/rspec/matchers/have_sibling.rb +4 -7
  46. data/lib/capybara/rspec/matchers/have_text.rb +4 -7
  47. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  48. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  49. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  50. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  51. data/lib/capybara/selector.rb +46 -19
  52. data/lib/capybara/selector/builders/css_builder.rb +10 -6
  53. data/lib/capybara/selector/builders/xpath_builder.rb +4 -2
  54. data/lib/capybara/selector/css.rb +1 -1
  55. data/lib/capybara/selector/definition.rb +13 -11
  56. data/lib/capybara/selector/definition/button.rb +32 -15
  57. data/lib/capybara/selector/definition/checkbox.rb +2 -2
  58. data/lib/capybara/selector/definition/css.rb +3 -1
  59. data/lib/capybara/selector/definition/datalist_input.rb +2 -2
  60. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  61. data/lib/capybara/selector/definition/element.rb +3 -2
  62. data/lib/capybara/selector/definition/field.rb +1 -1
  63. data/lib/capybara/selector/definition/file_field.rb +1 -1
  64. data/lib/capybara/selector/definition/fillable_field.rb +2 -2
  65. data/lib/capybara/selector/definition/label.rb +5 -3
  66. data/lib/capybara/selector/definition/link.rb +8 -0
  67. data/lib/capybara/selector/definition/option.rb +1 -1
  68. data/lib/capybara/selector/definition/radio_button.rb +2 -2
  69. data/lib/capybara/selector/definition/select.rb +33 -14
  70. data/lib/capybara/selector/definition/table.rb +6 -3
  71. data/lib/capybara/selector/definition/table_row.rb +2 -2
  72. data/lib/capybara/selector/filter_set.rb +13 -11
  73. data/lib/capybara/selector/filters/base.rb +6 -1
  74. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  75. data/lib/capybara/selector/regexp_disassembler.rb +7 -0
  76. data/lib/capybara/selector/selector.rb +13 -3
  77. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  78. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -1
  79. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  80. data/lib/capybara/selenium/atoms/src/isDisplayed.js +10 -10
  81. data/lib/capybara/selenium/driver.rb +86 -24
  82. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +24 -21
  83. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +21 -19
  84. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +17 -1
  85. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +0 -4
  86. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  87. data/lib/capybara/selenium/extensions/find.rb +37 -26
  88. data/lib/capybara/selenium/extensions/html5_drag.rb +55 -11
  89. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  90. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  91. data/lib/capybara/selenium/logger_suppressor.rb +8 -2
  92. data/lib/capybara/selenium/node.rb +160 -40
  93. data/lib/capybara/selenium/nodes/chrome_node.rb +72 -12
  94. data/lib/capybara/selenium/nodes/edge_node.rb +32 -14
  95. data/lib/capybara/selenium/nodes/firefox_node.rb +28 -32
  96. data/lib/capybara/selenium/nodes/safari_node.rb +5 -29
  97. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  98. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  99. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  100. data/lib/capybara/selenium/patches/logs.rb +32 -7
  101. data/lib/capybara/server.rb +19 -3
  102. data/lib/capybara/server/animation_disabler.rb +8 -3
  103. data/lib/capybara/server/checker.rb +1 -1
  104. data/lib/capybara/server/middleware.rb +22 -10
  105. data/lib/capybara/session.rb +66 -40
  106. data/lib/capybara/session/config.rb +11 -3
  107. data/lib/capybara/session/matchers.rb +11 -11
  108. data/lib/capybara/spec/public/offset.js +6 -0
  109. data/lib/capybara/spec/public/test.js +75 -7
  110. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  111. data/lib/capybara/spec/session/all_spec.rb +60 -5
  112. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  113. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  114. data/lib/capybara/spec/session/check_spec.rb +6 -0
  115. data/lib/capybara/spec/session/click_button_spec.rb +16 -0
  116. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  117. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  118. data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
  119. data/lib/capybara/spec/session/find_spec.rb +55 -0
  120. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -0
  121. data/lib/capybara/spec/session/has_button_spec.rb +51 -0
  122. data/lib/capybara/spec/session/has_css_spec.rb +26 -4
  123. data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
  124. data/lib/capybara/spec/session/has_field_spec.rb +34 -0
  125. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  126. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  127. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  128. data/lib/capybara/spec/session/has_text_spec.rb +30 -0
  129. data/lib/capybara/spec/session/html_spec.rb +1 -1
  130. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  131. data/lib/capybara/spec/session/node_spec.rb +394 -9
  132. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  133. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  134. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  135. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -15
  136. data/lib/capybara/spec/session/selectors_spec.rb +16 -3
  137. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  138. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  139. data/lib/capybara/spec/session/window/window_spec.rb +8 -8
  140. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  141. data/lib/capybara/spec/spec_helper.rb +14 -14
  142. data/lib/capybara/spec/test_app.rb +27 -21
  143. data/lib/capybara/spec/views/form.erb +47 -4
  144. data/lib/capybara/spec/views/offset.erb +32 -0
  145. data/lib/capybara/spec/views/spatial.erb +31 -0
  146. data/lib/capybara/spec/views/with_animation.erb +37 -1
  147. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  148. data/lib/capybara/spec/views/with_html.erb +24 -2
  149. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  150. data/lib/capybara/spec/views/with_js.erb +4 -1
  151. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  152. data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
  153. data/lib/capybara/version.rb +1 -1
  154. data/lib/capybara/window.rb +3 -7
  155. data/spec/basic_node_spec.rb +15 -14
  156. data/spec/capybara_spec.rb +28 -28
  157. data/spec/dsl_spec.rb +16 -3
  158. data/spec/filter_set_spec.rb +5 -5
  159. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  160. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  161. data/spec/minitest_spec.rb +3 -2
  162. data/spec/minitest_spec_spec.rb +46 -46
  163. data/spec/rack_test_spec.rb +38 -15
  164. data/spec/regexp_dissassembler_spec.rb +52 -38
  165. data/spec/result_spec.rb +43 -32
  166. data/spec/rspec/features_spec.rb +4 -1
  167. data/spec/rspec/scenarios_spec.rb +4 -0
  168. data/spec/rspec/shared_spec_matchers.rb +68 -56
  169. data/spec/rspec_spec.rb +9 -5
  170. data/spec/selector_spec.rb +32 -17
  171. data/spec/selenium_spec_chrome.rb +78 -11
  172. data/spec/selenium_spec_chrome_remote.rb +23 -6
  173. data/spec/selenium_spec_edge.rb +15 -12
  174. data/spec/selenium_spec_firefox.rb +24 -19
  175. data/spec/selenium_spec_firefox_remote.rb +0 -8
  176. data/spec/selenium_spec_ie.rb +1 -6
  177. data/spec/server_spec.rb +106 -44
  178. data/spec/session_spec.rb +5 -5
  179. data/spec/shared_selenium_node.rb +56 -2
  180. data/spec/shared_selenium_session.rb +122 -15
  181. data/spec/spec_helper.rb +2 -2
  182. metadata +63 -17
  183. data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -1,20 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'capybara/rspec/matchers/base'
4
- require 'capybara/rspec/matchers/count_sugar'
5
4
 
6
5
  module Capybara
7
6
  module RSpecMatchers
8
7
  module Matchers
9
- class HaveSibling < WrappedElementMatcher
10
- include CountSugar
11
-
8
+ class HaveSibling < CountableWrappedElementMatcher
12
9
  def element_matches?(el)
13
- el.assert_sibling(*@args, &@filter_block)
10
+ el.assert_sibling(*@args, **session_query_options, &@filter_block)
14
11
  end
15
12
 
16
13
  def element_does_not_match?(el)
17
- el.assert_no_sibling(*@args, &@filter_block)
14
+ el.assert_no_sibling(*@args, **session_query_options, &@filter_block)
18
15
  end
19
16
 
20
17
  def description
@@ -22,7 +19,7 @@ module Capybara
22
19
  end
23
20
 
24
21
  def query
25
- @query ||= Capybara::Queries::SiblingQuery.new(*session_query_args, &@filter_block)
22
+ @query ||= Capybara::Queries::SiblingQuery.new(*session_query_args, **session_query_options, &@filter_block)
26
23
  end
27
24
  end
28
25
  end
@@ -1,24 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'capybara/rspec/matchers/base'
4
- require 'capybara/rspec/matchers/count_sugar'
5
4
 
6
5
  module Capybara
7
6
  module RSpecMatchers
8
7
  module Matchers
9
- class HaveText < WrappedElementMatcher
10
- include CountSugar
11
-
8
+ class HaveText < CountableWrappedElementMatcher
12
9
  def element_matches?(el)
13
- el.assert_text(*@args)
10
+ el.assert_text(*@args, **@kw_args)
14
11
  end
15
12
 
16
13
  def element_does_not_match?(el)
17
- el.assert_no_text(*@args)
14
+ el.assert_no_text(*@args, **@kw_args)
18
15
  end
19
16
 
20
17
  def description
21
- "text #{format(text)}"
18
+ "have text #{format(text)}"
22
19
  end
23
20
 
24
21
  def format(content)
@@ -7,11 +7,11 @@ module Capybara
7
7
  module Matchers
8
8
  class HaveTitle < WrappedElementMatcher
9
9
  def element_matches?(el)
10
- el.assert_title(*@args)
10
+ el.assert_title(*@args, **@kw_args)
11
11
  end
12
12
 
13
13
  def element_does_not_match?(el)
14
- el.assert_no_title(*@args)
14
+ el.assert_no_title(*@args, **@kw_args)
15
15
  end
16
16
 
17
17
  def description
@@ -7,11 +7,11 @@ module Capybara
7
7
  module Matchers
8
8
  class MatchSelector < HaveSelector
9
9
  def element_matches?(el)
10
- el.assert_matches_selector(*@args, &@filter_block)
10
+ el.assert_matches_selector(*@args, **session_query_options, &@filter_block)
11
11
  end
12
12
 
13
13
  def element_does_not_match?(el)
14
- el.assert_not_matches_selector(*@args, &@filter_block)
14
+ el.assert_not_matches_selector(*@args, **session_query_options, &@filter_block)
15
15
  end
16
16
 
17
17
  def description
@@ -19,7 +19,7 @@ module Capybara
19
19
  end
20
20
 
21
21
  def query
22
- @query ||= Capybara::Queries::MatchQuery.new(*session_query_args, &@filter_block)
22
+ @query ||= Capybara::Queries::MatchQuery.new(*session_query_args, **session_query_options, &@filter_block)
23
23
  end
24
24
  end
25
25
  end
@@ -6,8 +6,13 @@ module Capybara
6
6
  module RSpecMatchers
7
7
  module Matchers
8
8
  class MatchStyle < WrappedElementMatcher
9
+ def initialize(styles = nil, **kw_args, &filter_block)
10
+ styles, kw_args = kw_args, {} if styles.nil?
11
+ super(styles, **kw_args, &filter_block)
12
+ end
13
+
9
14
  def element_matches?(el)
10
- el.assert_matches_style(*@args)
15
+ el.assert_matches_style(*@args, **@kw_args)
11
16
  end
12
17
 
13
18
  def does_not_match?(_actual)
@@ -28,7 +33,7 @@ module Capybara
28
33
  ##
29
34
  # @deprecated
30
35
  class HaveStyle < MatchStyle
31
- def initialize(*args, &filter_block)
36
+ def initialize(*args, **kw_args, &filter_block)
32
37
  warn 'HaveStyle matcher is deprecated, please use the MatchStyle matcher instead'
33
38
  super
34
39
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module RSpecMatchers
5
+ module SpatialSugar
6
+ def above(el)
7
+ options[:above] = el
8
+ self
9
+ end
10
+
11
+ def below(el)
12
+ options[:below] = el
13
+ self
14
+ end
15
+
16
+ def left_of(el)
17
+ options[:left_of] = el
18
+ self
19
+ end
20
+
21
+ def right_of(el)
22
+ options[:right_of] = el
23
+ self
24
+ end
25
+
26
+ def near(el)
27
+ options[:near] = el
28
+ self
29
+ end
30
+
31
+ private
32
+
33
+ def options
34
+ # (@args.last.is_a?(Hash) ? @args : @args.push({})).last
35
+ @kw_args
36
+ end
37
+ end
38
+ end
39
+ end
@@ -7,8 +7,13 @@ require 'capybara/selector/definition'
7
7
  #
8
8
  # All Selectors below support the listed selector specific filters in addition to the following system-wide filters
9
9
  # * :id (String, Regexp, XPath::Expression) - Matches the id attribute
10
- # * :class (String, Array<String>, Regexp, XPath::Expression) - Matches the class(es) provided
10
+ # * :class (String, Array<String | Regexp>, Regexp, XPath::Expression) - Matches the class(es) provided
11
11
  # * :style (String, Regexp, Hash<String, String>) - Match on elements style
12
+ # * :above (Element) - Match elements above the passed element on the page
13
+ # * :below (Element) - Match elements below the passed element on the page
14
+ # * :left_of (Element) - Match elements left of the passed element on the page
15
+ # * :right_of (Element) - Match elements right of the passed element on the page
16
+ # * :near (Element) - Match elements near (within 50px) the passed element on the page
12
17
  #
13
18
  # ### Built-in Selectors
14
19
  #
@@ -25,8 +30,8 @@ require 'capybara/selector/definition'
25
30
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or
26
31
  # associated label text
27
32
  # * Filters:
28
- # * :name (String) - Matches the name attribute
29
- # * :placeholder (String) - Matches the placeholder attribute
33
+ # * :name (String, Regexp) - Matches the name attribute
34
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
30
35
  # * :type (String) - Matches the type attribute of the field or element type for 'textarea' and 'select'
31
36
  # * :readonly (Boolean) - Match on the element being readonly
32
37
  # * :with (String, Regexp) - Matches the current value of the field
@@ -34,6 +39,8 @@ require 'capybara/selector/definition'
34
39
  # * :unchecked (Boolean) - Match unchecked fields?
35
40
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
36
41
  # * :multiple (Boolean) - Match fields that accept multiple values
42
+ # * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
43
+ # * :validation_message (String, Regexp) - Matches the elements current validationMessage
37
44
  #
38
45
  # * **:fieldset** - Select fieldset elements
39
46
  # * Locator: Matches id, {Capybara.configure test_id}, or contents of wrapped legend
@@ -52,7 +59,7 @@ require 'capybara/selector/definition'
52
59
  # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
53
60
  # * 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
54
61
  # * Filters:
55
- # * :name (String) - Matches the name attribute
62
+ # * :name (String, Regexp) - Matches the name attribute
56
63
  # * :title (String) - Matches the title attribute
57
64
  # * :value (String) - Matches the value of an input button
58
65
  # * :type (String) - Matches the type attribute
@@ -66,17 +73,19 @@ require 'capybara/selector/definition'
66
73
  # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
67
74
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
68
75
  # * Filters:
69
- # * :name (String) - Matches the name attribute
70
- # * :placeholder (String) - Matches the placeholder attribute
76
+ # * :name (String, Regexp) - Matches the name attribute
77
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
71
78
  # * :with (String, Regexp) - Matches the current value of the field
72
79
  # * :type (String) - Matches the type attribute of the field or element type for 'textarea'
73
80
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
74
81
  # * :multiple (Boolean) - Match fields that accept multiple values
82
+ # * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
83
+ # * :validation_message (String, Regexp) - Matches the elements current validationMessage
75
84
  #
76
85
  # * **:radio_button** - Find radio buttons
77
86
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
78
87
  # * Filters:
79
- # * :name (String) - Matches the name attribute
88
+ # * :name (String, Regexp) - Matches the name attribute
80
89
  # * :checked (Boolean) - Match checked fields?
81
90
  # * :unchecked (Boolean) - Match unchecked fields?
82
91
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
@@ -86,21 +95,23 @@ require 'capybara/selector/definition'
86
95
  # * **:checkbox** - Find checkboxes
87
96
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
88
97
  # * Filters:
89
- # * :name (String) - Matches the name attribute
98
+ # * :name (String, Regexp) - Matches the name attribute
90
99
  # * :checked (Boolean) - Match checked fields?
91
100
  # * :unchecked (Boolean) - Match unchecked fields?
92
101
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
93
- # * :option (String, Regexp) - Match the current value
94
- # * :with - Alias of :option
102
+ # * :with (String, Regexp) - Match the current value
103
+ # * :option - Alias of :with
95
104
  #
96
105
  # * **:select** - Find select elements
97
106
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
98
107
  # * Filters:
99
- # * :name (String) - Matches the name attribute
100
- # * :placeholder (String) - Matches the placeholder attribute
108
+ # * :name (String, Regexp) - Matches the name attribute
109
+ # * :placeholder (String, Placeholder) - Matches the placeholder attribute
101
110
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
102
111
  # * :multiple (Boolean) - Match fields that accept multiple values
103
112
  # * :options (Array<String>) - Exact match options
113
+ # * :enabled_options (Array<String>) - Exact match enabled options
114
+ # * :disabled_options (Array<String>) - Exact match disabled options
104
115
  # * :with_options (Array<String>) - Partial match options
105
116
  # * :selected (String, Array<String>) - Match the selection(s)
106
117
  # * :with_selected (String, Array<String>) - Partial match the selection(s)
@@ -115,8 +126,8 @@ require 'capybara/selector/definition'
115
126
  # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name,
116
127
  # placeholder, or associated label text
117
128
  # * Filters:
118
- # * :name (String) - Matches the name attribute
119
- # * :placeholder (String) - Matches the placeholder attribute
129
+ # * :name (String, Regexp) - Matches the name attribute
130
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
120
131
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
121
132
  # * :options (Array<String>) - Exact match options
122
133
  # * :with_options (Array<String>) - Partial match options
@@ -129,7 +140,7 @@ require 'capybara/selector/definition'
129
140
  # * **:file_field** - Find file input elements
130
141
  # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
131
142
  # * Filters:
132
- # * :name (String) - Matches the name attribute
143
+ # * :name (String, Regexp) - Matches the name attribute
133
144
  # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
134
145
  # * :multiple (Boolean) - Match field that accepts multiple values
135
146
  #
@@ -160,15 +171,28 @@ require 'capybara/selector/definition'
160
171
  # * Filters:
161
172
  # * :\<any> (String, Regexp) - Match on any specified element attribute
162
173
  #
163
- class Capybara::Selector; end
174
+ class Capybara::Selector; end # rubocop:disable Lint/EmptyClass
164
175
 
165
176
  Capybara::Selector::FilterSet.add(:_field) do
166
177
  node_filter(:checked, :boolean) { |node, value| !(value ^ node.checked?) }
167
178
  node_filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
168
179
  node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
180
+ node_filter(:valid, :boolean) { |node, value| node.evaluate_script('this.validity.valid') == value }
181
+ node_filter(:name) { |node, value| !value.is_a?(Regexp) || value.match?(node[:name]) }
182
+ node_filter(:placeholder) { |node, value| !value.is_a?(Regexp) || value.match?(node[:placeholder]) }
183
+ node_filter(:validation_message) do |node, msg|
184
+ vm = node[:validationMessage]
185
+ (msg.is_a?(Regexp) ? msg.match?(vm) : vm == msg.to_s).tap do |res|
186
+ add_error("Expected validation message to be #{msg.inspect} but was #{vm}") unless res
187
+ end
188
+ end
169
189
 
170
- expression_filter(:name) { |xpath, val| xpath[XPath.attr(:name) == val] }
171
- expression_filter(:placeholder) { |xpath, val| xpath[XPath.attr(:placeholder) == val] }
190
+ expression_filter(:name) do |xpath, val|
191
+ builder(xpath).add_attribute_conditions(name: val)
192
+ end
193
+ expression_filter(:placeholder) do |xpath, val|
194
+ builder(xpath).add_attribute_conditions(placeholder: val)
195
+ end
172
196
  expression_filter(:disabled) { |xpath, val| val ? xpath : xpath[~XPath.attr(:disabled)] }
173
197
  expression_filter(:multiple) { |xpath, val| xpath[val ? XPath.attr(:multiple) : ~XPath.attr(:multiple)] }
174
198
 
@@ -182,12 +206,15 @@ Capybara::Selector::FilterSet.add(:_field) do
182
206
  desc
183
207
  end
184
208
 
185
- describe(:node_filters) do |checked: nil, unchecked: nil, disabled: nil, **|
209
+ describe(:node_filters) do |checked: nil, unchecked: nil, disabled: nil, valid: nil, validation_message: nil, **|
186
210
  desc, states = +'', []
187
211
  states << 'checked' if checked || (unchecked == false)
188
212
  states << 'not checked' if unchecked || (checked == false)
189
213
  states << 'disabled' if disabled == true
190
214
  desc << " that is #{states.join(' and ')}" unless states.empty?
215
+ desc << ' that is valid' if valid == true
216
+ desc << ' that is invalid' if valid == false
217
+ desc << " with validation message #{validation_message.to_s.inspect}" if validation_message
191
218
  desc
192
219
  end
193
220
  end
@@ -17,11 +17,7 @@ module Capybara
17
17
  conditions = if name == :class
18
18
  class_conditions(value)
19
19
  elsif value.is_a? Regexp
20
- Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
21
- strs.map do |str|
22
- "[#{name}*='#{str}'#{' i' if value.casefold?}]"
23
- end.join
24
- end
20
+ regexp_conditions(name, value)
25
21
  else
26
22
  [attribute_conditions(name => value)]
27
23
  end
@@ -36,6 +32,14 @@ module Capybara
36
32
 
37
33
  private
38
34
 
35
+ def regexp_conditions(name, value)
36
+ Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
37
+ strs.map do |str|
38
+ "[#{name}*='#{str}'#{' i' if value.casefold?}]"
39
+ end.join
40
+ end
41
+ end
42
+
39
43
  def attribute_conditions(attributes)
40
44
  attributes.map do |attribute, value|
41
45
  case value
@@ -70,7 +74,7 @@ module Capybara
70
74
  end.join
71
75
  end
72
76
  else
73
- cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') }
77
+ cls = Array(classes).reject { |c| c.is_a? Regexp }.group_by { |cl| cl.match?(/^!(?!!!)/) }
74
78
  [(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
75
79
  cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
76
80
  end
@@ -15,6 +15,8 @@ module Capybara
15
15
  def add_attribute_conditions(**conditions)
16
16
  @expression = conditions.inject(expression) do |xp, (name, value)|
17
17
  conditions = name == :class ? class_conditions(value) : attribute_conditions(name => value)
18
+ return xp if conditions.nil?
19
+
18
20
  if xp.is_a? XPath::Expression
19
21
  xp[conditions]
20
22
  else
@@ -47,8 +49,8 @@ module Capybara
47
49
  when XPath::Expression, Regexp
48
50
  attribute_conditions(class: classes)
49
51
  else
50
- Array(classes).map do |klass|
51
- if klass.start_with?('!') && !klass.start_with?('!!!')
52
+ Array(classes).reject { |c| c.is_a? Regexp }.map do |klass|
53
+ if klass.match?(/^!(?!!!)/)
52
54
  !XPath.attr(:class).contains_word(klass.slice(1..-1))
53
55
  else
54
56
  XPath.attr(:class).contains_word(klass.sub(/^!!/, ''))
@@ -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,11 +10,12 @@ 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)
16
17
  @name = name
17
- @filter_set = Capybara::Selector::FilterSet.add(name) {}
18
+ @filter_set = Capybara::Selector::FilterSet.add(name)
18
19
  @match = nil
19
20
  @label = nil
20
21
  @failure_message = nil
@@ -82,7 +83,7 @@ module Capybara
82
83
  # Automatic selector detection
83
84
  #
84
85
  # @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
85
- # @yieldparam [String], locator The locator string used to determin if it matches the selector
86
+ # @yieldparam [String], locator The locator string used to determine if it matches the selector
86
87
  # @yieldreturn [Boolean] Whether this selector matches the locator string
87
88
  # @return [#call] The block that will be used to detect selector match
88
89
  #
@@ -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
 
@@ -177,11 +178,11 @@ module Capybara
177
178
  def_delegator :@filter_set, :describe
178
179
 
179
180
  def describe_expression_filters(&block)
180
- if block_given?
181
+ if block
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
@@ -214,7 +215,7 @@ module Capybara
214
215
  end
215
216
 
216
217
  def default_visibility(fallback = Capybara.ignore_hidden_elements, options = {})
217
- vis = if @default_visibility&.respond_to?(:call)
218
+ vis = if @default_visibility.respond_to?(:call)
218
219
  @default_visibility.call(options)
219
220
  else
220
221
  @default_visibility
@@ -251,14 +252,15 @@ 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
 
260
261
  def parameter_names(block)
261
- block.parameters.select { |(type, _name)| %i[key keyreq].include? type }.map { |(_type, name)| name }
262
+ key_types = %i[key keyreq]
263
+ block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_type, name)| name }
262
264
  end
263
265
 
264
266
  def expression(type, allowed_filters, &block)