capybara 3.23.0 → 3.35.3

Sign up to get free protection for your applications and to get access to all the features.
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)