capybara 3.13.2 → 3.40.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 (260) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +587 -16
  4. data/README.md +240 -90
  5. data/lib/capybara/config.rb +24 -11
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +8 -0
  8. data/lib/capybara/driver/node.rb +20 -4
  9. data/lib/capybara/dsl.rb +5 -3
  10. data/lib/capybara/helpers.rb +25 -4
  11. data/lib/capybara/minitest/spec.rb +174 -90
  12. data/lib/capybara/minitest.rb +256 -142
  13. data/lib/capybara/node/actions.rb +123 -77
  14. data/lib/capybara/node/base.rb +20 -12
  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 +223 -117
  18. data/lib/capybara/node/finders.rb +81 -71
  19. data/lib/capybara/node/matchers.rb +271 -134
  20. data/lib/capybara/node/simple.rb +18 -5
  21. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  22. data/lib/capybara/queries/active_element_query.rb +18 -0
  23. data/lib/capybara/queries/ancestor_query.rb +8 -9
  24. data/lib/capybara/queries/base_query.rb +3 -2
  25. data/lib/capybara/queries/current_path_query.rb +15 -5
  26. data/lib/capybara/queries/selector_query.rb +364 -54
  27. data/lib/capybara/queries/sibling_query.rb +8 -6
  28. data/lib/capybara/queries/style_query.rb +2 -2
  29. data/lib/capybara/queries/text_query.rb +13 -1
  30. data/lib/capybara/queries/title_query.rb +1 -1
  31. data/lib/capybara/rack_test/browser.rb +76 -11
  32. data/lib/capybara/rack_test/driver.rb +10 -5
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +31 -9
  35. data/lib/capybara/rack_test/node.rb +74 -23
  36. data/lib/capybara/registration_container.rb +41 -0
  37. data/lib/capybara/registrations/drivers.rb +42 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  39. data/lib/capybara/registrations/servers.rb +66 -0
  40. data/lib/capybara/result.rb +44 -20
  41. data/lib/capybara/rspec/matcher_proxies.rb +13 -11
  42. data/lib/capybara/rspec/matchers/base.rb +31 -16
  43. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  44. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  45. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  46. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  47. data/lib/capybara/rspec/matchers/have_selector.rb +21 -21
  48. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  49. data/lib/capybara/rspec/matchers/have_text.rb +4 -4
  50. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  51. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  52. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  53. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  54. data/lib/capybara/rspec/matchers.rb +111 -68
  55. data/lib/capybara/rspec.rb +2 -0
  56. data/lib/capybara/selector/builders/css_builder.rb +11 -7
  57. data/lib/capybara/selector/builders/xpath_builder.rb +5 -3
  58. data/lib/capybara/selector/css.rb +11 -9
  59. data/lib/capybara/selector/definition/button.rb +68 -0
  60. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  61. data/lib/capybara/selector/definition/css.rb +10 -0
  62. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  63. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  64. data/lib/capybara/selector/definition/element.rb +28 -0
  65. data/lib/capybara/selector/definition/field.rb +40 -0
  66. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  67. data/lib/capybara/selector/definition/file_field.rb +13 -0
  68. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  69. data/lib/capybara/selector/definition/frame.rb +17 -0
  70. data/lib/capybara/selector/definition/id.rb +6 -0
  71. data/lib/capybara/selector/definition/label.rb +62 -0
  72. data/lib/capybara/selector/definition/link.rb +55 -0
  73. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  74. data/lib/capybara/selector/definition/option.rb +27 -0
  75. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  76. data/lib/capybara/selector/definition/select.rb +81 -0
  77. data/lib/capybara/selector/definition/table.rb +109 -0
  78. data/lib/capybara/selector/definition/table_row.rb +21 -0
  79. data/lib/capybara/selector/definition/xpath.rb +5 -0
  80. data/lib/capybara/selector/definition.rb +280 -0
  81. data/lib/capybara/selector/filter_set.rb +19 -18
  82. data/lib/capybara/selector/filters/base.rb +11 -2
  83. data/lib/capybara/selector/filters/locator_filter.rb +13 -3
  84. data/lib/capybara/selector/regexp_disassembler.rb +11 -7
  85. data/lib/capybara/selector/selector.rb +50 -440
  86. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  87. data/lib/capybara/selector.rb +473 -482
  88. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  89. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  90. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  91. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  92. data/lib/capybara/selenium/driver.rb +174 -62
  93. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +74 -18
  94. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  95. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +37 -3
  96. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
  97. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  98. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  99. data/lib/capybara/selenium/extensions/find.rb +68 -45
  100. data/lib/capybara/selenium/extensions/html5_drag.rb +192 -22
  101. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  102. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  103. data/lib/capybara/selenium/node.rb +268 -72
  104. data/lib/capybara/selenium/nodes/chrome_node.rb +105 -9
  105. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  106. data/lib/capybara/selenium/nodes/firefox_node.rb +51 -61
  107. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  108. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  109. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  110. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  111. data/lib/capybara/selenium/patches/logs.rb +45 -0
  112. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  113. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  114. data/lib/capybara/server/animation_disabler.rb +43 -21
  115. data/lib/capybara/server/checker.rb +6 -2
  116. data/lib/capybara/server/middleware.rb +25 -13
  117. data/lib/capybara/server.rb +20 -4
  118. data/lib/capybara/session/config.rb +15 -11
  119. data/lib/capybara/session/matchers.rb +11 -11
  120. data/lib/capybara/session.rb +162 -131
  121. data/lib/capybara/spec/public/offset.js +6 -0
  122. data/lib/capybara/spec/public/test.js +105 -6
  123. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  124. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  125. data/lib/capybara/spec/session/all_spec.rb +89 -15
  126. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  127. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  128. data/lib/capybara/spec/session/assert_text_spec.rb +26 -22
  129. data/lib/capybara/spec/session/attach_file_spec.rb +64 -31
  130. data/lib/capybara/spec/session/check_spec.rb +26 -4
  131. data/lib/capybara/spec/session/choose_spec.rb +14 -2
  132. data/lib/capybara/spec/session/click_button_spec.rb +109 -61
  133. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  134. data/lib/capybara/spec/session/click_link_spec.rb +23 -1
  135. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  136. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  137. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  138. data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
  139. data/lib/capybara/spec/session/fill_in_spec.rb +46 -5
  140. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  141. data/lib/capybara/spec/session/find_spec.rb +80 -7
  142. data/lib/capybara/spec/session/first_spec.rb +2 -2
  143. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
  144. data/lib/capybara/spec/session/frame/within_frame_spec.rb +14 -1
  145. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  146. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  147. data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
  148. data/lib/capybara/spec/session/has_button_spec.rb +81 -0
  149. data/lib/capybara/spec/session/has_css_spec.rb +45 -8
  150. data/lib/capybara/spec/session/has_current_path_spec.rb +22 -7
  151. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  152. data/lib/capybara/spec/session/has_field_spec.rb +59 -1
  153. data/lib/capybara/spec/session/has_link_spec.rb +40 -0
  154. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  155. data/lib/capybara/spec/session/has_select_spec.rb +42 -8
  156. data/lib/capybara/spec/session/has_selector_spec.rb +19 -4
  157. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  158. data/lib/capybara/spec/session/has_table_spec.rb +177 -0
  159. data/lib/capybara/spec/session/has_text_spec.rb +31 -3
  160. data/lib/capybara/spec/session/html_spec.rb +1 -1
  161. data/lib/capybara/spec/session/matches_style_spec.rb +6 -4
  162. data/lib/capybara/spec/session/node_spec.rb +697 -23
  163. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  164. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  165. data/lib/capybara/spec/session/reset_session_spec.rb +21 -7
  166. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  167. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  168. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  169. data/lib/capybara/spec/session/scroll_spec.rb +9 -7
  170. data/lib/capybara/spec/session/select_spec.rb +5 -10
  171. data/lib/capybara/spec/session/selectors_spec.rb +24 -3
  172. data/lib/capybara/spec/session/uncheck_spec.rb +3 -3
  173. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  174. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  175. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  176. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  178. data/lib/capybara/spec/session/window/window_spec.rb +54 -57
  179. data/lib/capybara/spec/session/window/windows_spec.rb +2 -2
  180. data/lib/capybara/spec/session/within_spec.rb +36 -0
  181. data/lib/capybara/spec/spec_helper.rb +30 -19
  182. data/lib/capybara/spec/test_app.rb +122 -34
  183. data/lib/capybara/spec/views/animated.erb +49 -0
  184. data/lib/capybara/spec/views/form.erb +86 -8
  185. data/lib/capybara/spec/views/frame_child.erb +3 -2
  186. data/lib/capybara/spec/views/frame_one.erb +2 -1
  187. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  188. data/lib/capybara/spec/views/frame_two.erb +1 -1
  189. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  190. data/lib/capybara/spec/views/layout.erb +10 -0
  191. data/lib/capybara/spec/views/obscured.erb +10 -10
  192. data/lib/capybara/spec/views/offset.erb +33 -0
  193. data/lib/capybara/spec/views/path.erb +2 -2
  194. data/lib/capybara/spec/views/popup_one.erb +1 -1
  195. data/lib/capybara/spec/views/popup_two.erb +1 -1
  196. data/lib/capybara/spec/views/react.erb +45 -0
  197. data/lib/capybara/spec/views/scroll.erb +2 -1
  198. data/lib/capybara/spec/views/spatial.erb +31 -0
  199. data/lib/capybara/spec/views/tables.erb +67 -0
  200. data/lib/capybara/spec/views/with_animation.erb +39 -4
  201. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  202. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  203. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  204. data/lib/capybara/spec/views/with_hover.erb +3 -2
  205. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  206. data/lib/capybara/spec/views/with_html.erb +34 -6
  207. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  208. data/lib/capybara/spec/views/with_js.erb +7 -4
  209. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  210. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  211. data/lib/capybara/spec/views/with_scope.erb +2 -2
  212. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  213. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  214. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  215. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  216. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  217. data/lib/capybara/spec/views/with_windows.erb +1 -1
  218. data/lib/capybara/spec/views/within_frames.erb +1 -1
  219. data/lib/capybara/version.rb +1 -1
  220. data/lib/capybara/window.rb +14 -18
  221. data/lib/capybara.rb +91 -126
  222. data/spec/basic_node_spec.rb +30 -16
  223. data/spec/capybara_spec.rb +40 -28
  224. data/spec/counter_spec.rb +35 -0
  225. data/spec/css_builder_spec.rb +3 -1
  226. data/spec/css_splitter_spec.rb +1 -1
  227. data/spec/dsl_spec.rb +33 -22
  228. data/spec/filter_set_spec.rb +5 -5
  229. data/spec/fixtures/selenium_driver_rspec_failure.rb +3 -3
  230. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -3
  231. data/spec/minitest_spec.rb +24 -2
  232. data/spec/minitest_spec_spec.rb +60 -45
  233. data/spec/per_session_config_spec.rb +1 -1
  234. data/spec/rack_test_spec.rb +131 -98
  235. data/spec/regexp_dissassembler_spec.rb +53 -39
  236. data/spec/result_spec.rb +68 -66
  237. data/spec/rspec/features_spec.rb +9 -4
  238. data/spec/rspec/scenarios_spec.rb +6 -2
  239. data/spec/rspec/shared_spec_matchers.rb +137 -98
  240. data/spec/rspec_matchers_spec.rb +25 -0
  241. data/spec/rspec_spec.rb +23 -21
  242. data/spec/sauce_spec_chrome.rb +43 -0
  243. data/spec/selector_spec.rb +77 -21
  244. data/spec/selenium_spec_chrome.rb +141 -39
  245. data/spec/selenium_spec_chrome_remote.rb +32 -17
  246. data/spec/selenium_spec_edge.rb +36 -8
  247. data/spec/selenium_spec_firefox.rb +110 -68
  248. data/spec/selenium_spec_firefox_remote.rb +22 -15
  249. data/spec/selenium_spec_ie.rb +29 -22
  250. data/spec/selenium_spec_safari.rb +162 -0
  251. data/spec/server_spec.rb +153 -81
  252. data/spec/session_spec.rb +11 -4
  253. data/spec/shared_selenium_node.rb +79 -0
  254. data/spec/shared_selenium_session.rb +179 -74
  255. data/spec/spec_helper.rb +80 -5
  256. data/spec/whitespace_normalizer_spec.rb +54 -0
  257. data/spec/xpath_builder_spec.rb +3 -1
  258. metadata +218 -30
  259. data/lib/capybara/spec/session/source_spec.rb +0 -0
  260. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selector/filter_set'
4
+ require 'capybara/selector/css'
5
+ require 'capybara/selector/regexp_disassembler'
6
+ require 'capybara/selector/builders/xpath_builder'
7
+ require 'capybara/selector/builders/css_builder'
8
+
9
+ module Capybara
10
+ class Selector
11
+ class Definition
12
+ attr_reader :name, :expressions
13
+
14
+ extend Forwardable
15
+
16
+ def initialize(name, locator_type: nil, raw_locator: false, supports_exact: nil, &block)
17
+ @name = name
18
+ @filter_set = Capybara::Selector::FilterSet.add(name)
19
+ @match = nil
20
+ @label = nil
21
+ @failure_message = nil
22
+ @expressions = {}
23
+ @expression_filters = {}
24
+ @locator_filter = nil
25
+ @default_visibility = nil
26
+ @locator_type = locator_type
27
+ @raw_locator = raw_locator
28
+ @supports_exact = supports_exact
29
+ instance_eval(&block)
30
+ end
31
+
32
+ def custom_filters
33
+ warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
34
+ node_filters.merge(expression_filters).freeze
35
+ end
36
+
37
+ def node_filters
38
+ @filter_set.node_filters
39
+ end
40
+
41
+ def expression_filters
42
+ @filter_set.expression_filters
43
+ end
44
+
45
+ ##
46
+ #
47
+ # Define a selector by an xpath expression
48
+ #
49
+ # @overload xpath(*expression_filters, &block)
50
+ # @param [Array<Symbol>] expression_filters ([]) Names of filters that are implemented via this expression, if not specified the names of any keyword parameters in the block will be used
51
+ # @yield [locator, options] The block to use to generate the XPath expression
52
+ # @yieldparam [String] locator The locator string passed to the query
53
+ # @yieldparam [Hash] options The options hash passed to the query
54
+ # @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression
55
+ #
56
+ # @overload xpath()
57
+ # @return [#call] The block that will be called to generate the XPath expression
58
+ #
59
+ def xpath(*allowed_filters, &block)
60
+ expression(:xpath, allowed_filters, &block)
61
+ end
62
+
63
+ ##
64
+ #
65
+ # Define a selector by a CSS selector
66
+ #
67
+ # @overload css(*expression_filters, &block)
68
+ # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector
69
+ # @yield [locator, options] The block to use to generate the CSS selector
70
+ # @yieldparam [String] locator The locator string passed to the query
71
+ # @yieldparam [Hash] options The options hash passed to the query
72
+ # @yieldreturn [#to_s] An object that can produce a CSS selector
73
+ #
74
+ # @overload css()
75
+ # @return [#call] The block that will be called to generate the CSS selector
76
+ #
77
+ def css(*allowed_filters, &block)
78
+ expression(:css, allowed_filters, &block)
79
+ end
80
+
81
+ ##
82
+ #
83
+ # Automatic selector detection
84
+ #
85
+ # @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
86
+ # @yieldparam [String], locator The locator string used to determine if it matches the selector
87
+ # @yieldreturn [Boolean] Whether this selector matches the locator string
88
+ # @return [#call] The block that will be used to detect selector match
89
+ #
90
+ def match(&block)
91
+ @match = block if block
92
+ @match
93
+ end
94
+
95
+ ##
96
+ #
97
+ # Set/get a descriptive label for the selector
98
+ #
99
+ # @overload label(label)
100
+ # @param [String] label A descriptive label for this selector - used in error messages
101
+ # @overload label()
102
+ # @return [String] The currently set label
103
+ #
104
+ def label(label = nil)
105
+ @label = label if label
106
+ @label
107
+ end
108
+
109
+ ##
110
+ #
111
+ # Description of the selector
112
+ #
113
+ # @!method description(options)
114
+ # @param [Hash] options The options of the query used to generate the description
115
+ # @return [String] Description of the selector when used with the options passed
116
+ def_delegator :@filter_set, :description
117
+
118
+ ##
119
+ #
120
+ # Should this selector be used for the passed in locator
121
+ #
122
+ # This is used by the automatic selector selection mechanism when no selector type is passed to a selector query
123
+ #
124
+ # @param [String] locator The locator passed to the query
125
+ # @return [Boolean] Whether or not to use this selector
126
+ #
127
+ def match?(locator)
128
+ @match&.call(locator)
129
+ end
130
+
131
+ ##
132
+ #
133
+ # Define a node filter for use with this selector
134
+ #
135
+ # @!method node_filter(name, *types, options={}, &block)
136
+ # @param [Symbol, Regexp] name The filter name
137
+ # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
138
+ # @param [Hash] options ({}) Options of the filter
139
+ # @option options [Array<>] :valid_values Valid values for this filter
140
+ # @option options :default The default value of the filter (if any)
141
+ # @option options :skip_if Value of the filter that will cause it to be skipped
142
+ # @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
143
+ #
144
+ # If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
145
+ # is passed for the name the block should accept | node, option_name, option_value |. In either case
146
+ # the block should return `true` if the node passes the filer or `false` if it doesn't
147
+
148
+ ##
149
+ #
150
+ # Define an expression filter for use with this selector
151
+ #
152
+ # @!method expression_filter(name, *types, matcher: nil, **options, &block)
153
+ # @param [Symbol, Regexp] name The filter name
154
+ # @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
155
+ # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
156
+ # @param [Hash] options ({}) Options of the filter
157
+ # @option options [Array<>] :valid_values Valid values for this filter
158
+ # @option options :default The default value of the filter (if any)
159
+ # @option options :skip_if Value of the filter that will cause it to be skipped
160
+ # @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
161
+ #
162
+ # If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
163
+ # is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
164
+ # the block should return the modified expression
165
+
166
+ def_delegators :@filter_set, :node_filter, :expression_filter, :filter
167
+
168
+ def locator_filter(*types, **options, &block)
169
+ types.each { |type| options[type] = true }
170
+ @locator_filter = Capybara::Selector::Filters::LocatorFilter.new(block, **options) if block
171
+ @locator_filter
172
+ end
173
+
174
+ def filter_set(name, filters_to_use = nil)
175
+ @filter_set.import(name, filters_to_use)
176
+ end
177
+
178
+ def_delegator :@filter_set, :describe
179
+
180
+ def describe_expression_filters(&block)
181
+ if block
182
+ describe(:expression_filters, &block)
183
+ else
184
+ describe(:expression_filters) do |**options|
185
+ describe_all_expression_filters(**options)
186
+ end
187
+ end
188
+ end
189
+
190
+ def describe_all_expression_filters(**opts)
191
+ expression_filters.map do |ef_name, ef|
192
+ if ef.matcher?
193
+ handled_custom_options(ef, opts).map { |option, value| " with #{ef_name}[#{option} => #{value}]" }.join
194
+ elsif opts.key?(ef_name)
195
+ " with #{ef_name} #{opts[ef_name]}"
196
+ end
197
+ end.join
198
+ end
199
+
200
+ def describe_node_filters(&block)
201
+ describe(:node_filters, &block)
202
+ end
203
+
204
+ ##
205
+ #
206
+ # Set the default visibility mode that should be used if no visible option is passed when using the selector.
207
+ # If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
208
+ #
209
+ # @param [Symbol] default_visibility Only find elements with the specified visibility:
210
+ # * :all - finds visible and invisible elements.
211
+ # * :hidden - only finds invisible elements.
212
+ # * :visible - only finds visible elements.
213
+ def visible(default_visibility = nil, &block)
214
+ @default_visibility = block || default_visibility
215
+ end
216
+
217
+ def default_visibility(fallback = Capybara.ignore_hidden_elements, options = {})
218
+ vis = if @default_visibility.respond_to?(:call)
219
+ @default_visibility.call(options)
220
+ else
221
+ @default_visibility
222
+ end
223
+ vis.nil? ? fallback : vis
224
+ end
225
+
226
+ # @api private
227
+ def raw_locator?
228
+ !!@raw_locator
229
+ end
230
+
231
+ # @api private
232
+ def supports_exact?
233
+ @supports_exact
234
+ end
235
+
236
+ def default_format
237
+ return nil if @expressions.keys.empty?
238
+
239
+ if @expressions.size == 1
240
+ @expressions.keys.first
241
+ else
242
+ :xpath
243
+ end
244
+ end
245
+
246
+ # @api private
247
+ def locator_types
248
+ return nil unless @locator_type
249
+
250
+ Array(@locator_type)
251
+ end
252
+
253
+ private
254
+
255
+ def handled_custom_options(filter, options)
256
+ options.select do |option, _|
257
+ filter.handles_option?(option) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(option)
258
+ end
259
+ end
260
+
261
+ def parameter_names(block)
262
+ key_types = %i[key keyreq]
263
+ # user filter_map when we drop dupport for 2.6
264
+ # block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_, name)| name }
265
+ block.parameters.filter_map { |(type, name)| name if key_types.include? type }
266
+ end
267
+
268
+ def expression(type, allowed_filters, &block)
269
+ if block
270
+ @expressions[type] = block
271
+ allowed_filters = parameter_names(block) if allowed_filters.empty?
272
+ allowed_filters.flatten.each do |ef|
273
+ expression_filters[ef] = Capybara::Selector::Filters::IdentityExpressionFilter.new(ef)
274
+ end
275
+ end
276
+ @expressions[type]
277
+ end
278
+ end
279
+ end
280
+ end
@@ -12,16 +12,18 @@ module Capybara
12
12
  @node_filters = {}
13
13
  @expression_filters = {}
14
14
  @descriptions = Hash.new { |hsh, key| hsh[key] = [] }
15
- instance_eval(&block)
15
+ instance_eval(&block) if block
16
16
  end
17
17
 
18
- def node_filter(name, *types_and_options, &block)
19
- add_filter(name, Filters::NodeFilter, *types_and_options, &block)
18
+ def node_filter(names, *types, **options, &block)
19
+ Array(names).each do |name|
20
+ add_filter(name, Filters::NodeFilter, *types, **options, &block)
21
+ end
20
22
  end
21
23
  alias_method :filter, :node_filter
22
24
 
23
- def expression_filter(name, *types_and_options, &block)
24
- 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)
25
27
  end
26
28
 
27
29
  def describe(what = nil, &block)
@@ -40,14 +42,14 @@ module Capybara
40
42
  def description(node_filters: true, expression_filters: true, **options)
41
43
  opts = options_with_defaults(options)
42
44
  description = +''
43
- description << undeclared_descriptions.map { |desc| desc.call(opts).to_s }.join
44
- description << expression_filter_descriptions.map { |desc| desc.call(opts).to_s }.join if expression_filters
45
- 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
46
48
  description
47
49
  end
48
50
 
49
51
  def descriptions
50
- warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
52
+ Capybara::Helpers.warn 'DEPRECATED: FilterSet#descriptions is deprecated without replacement'
51
53
  [undeclared_descriptions, node_filter_descriptions, expression_filter_descriptions].flatten
52
54
  end
53
55
 
@@ -99,20 +101,19 @@ module Capybara
99
101
  private
100
102
 
101
103
  def options_with_defaults(options)
102
- options = options.dup
103
- [expression_filters, node_filters].each do |filters|
104
- filters.select { |_n, filter| filter.default? }.each do |name, filter|
105
- options[name] = filter.default unless options.key?(name)
106
- end
107
- end
108
- options
104
+ expression_filters
105
+ .chain(node_filters)
106
+ .filter_map { |name, filter| [name, filter.default] if filter.default? }
107
+ .to_h.merge!(options)
109
108
  end
110
109
 
111
110
  def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
112
111
  types.each { |type| options[type] = true }
113
- raise 'ArgumentError', ':default option is not supported for filters with a :matcher option' if matcher && options[:default]
112
+ if matcher && options[:default]
113
+ raise 'ArgumentError', ':default option is not supported for filters with a :matcher option'
114
+ end
114
115
 
115
- filter = filter_class.new(name, matcher, block, options)
116
+ filter = filter_class.new(name, matcher, block, **options)
116
117
  (filter_class <= Filters::ExpressionFilter ? @expression_filters : @node_filters)[name] = filter
117
118
  end
118
119
  end
@@ -24,6 +24,10 @@ module Capybara
24
24
  @options.key?(:skip_if) && value == @options[:skip_if]
25
25
  end
26
26
 
27
+ def format
28
+ @options[:format]
29
+ end
30
+
27
31
  def matcher?
28
32
  !@matcher.nil?
29
33
  end
@@ -34,7 +38,7 @@ module Capybara
34
38
 
35
39
  def handles_option?(option_name)
36
40
  if matcher?
37
- option_name =~ @matcher
41
+ @matcher.match? option_name
38
42
  else
39
43
  @name == option_name
40
44
  end
@@ -44,7 +48,12 @@ module Capybara
44
48
 
45
49
  def apply(subject, name, value, skip_value, ctx)
46
50
  return skip_value if skip?(value)
47
- 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
48
57
 
49
58
  if @block.arity == 2
50
59
  filter_context(ctx).instance_exec(subject, value, &@block)
@@ -7,11 +7,21 @@ 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
- def matches?(node, value, context = nil)
14
- super(node, nil, value, context)
13
+ def matches?(node, value, context = nil, exact:)
14
+ apply(node, value, true, context, exact: exact, format: context&.default_format)
15
+ rescue Capybara::ElementNotFound
16
+ false
17
+ end
18
+
19
+ private
20
+
21
+ def apply(subject, value, skip_value, ctx, **options)
22
+ return skip_value if skip?(value)
23
+
24
+ filter_context(ctx).instance_exec(subject, value, **options, &@block)
15
25
  end
16
26
  end
17
27
  end
@@ -69,11 +69,8 @@ module Capybara
69
69
  suffixes = [[]]
70
70
  strs.reverse_each do |str|
71
71
  if str.is_a? Set
72
- prefixes = str.each_with_object([]) { |s, memo| memo.concat combine(s) }
73
-
74
- result = []
75
- prefixes.product(suffixes) { |pair| result << pair.flatten(1) }
76
- suffixes = result
72
+ prefixes = str.flat_map { |s| combine(s) }
73
+ suffixes = prefixes.product(suffixes).map { |pair| pair.flatten(1) }
77
74
  else
78
75
  suffixes.each { |arr| arr.unshift str }
79
76
  end
@@ -100,6 +97,8 @@ module Capybara
100
97
  def extract_strings(process_alternatives)
101
98
  strings = []
102
99
  each do |exp|
100
+ next if exp.ignore?
101
+
103
102
  next strings.push(nil) if exp.optional? && !process_alternatives
104
103
 
105
104
  next strings.push(exp.alternative_strings) if exp.alternation? && process_alternatives
@@ -159,6 +158,11 @@ module Capybara
159
158
  alts.all?(&:any?) ? Set.new(alts) : nil
160
159
  end
161
160
 
161
+ def ignore?
162
+ [Regexp::Expression::Assertion::NegativeLookahead,
163
+ Regexp::Expression::Assertion::NegativeLookbehind].any? { |klass| @exp.is_a? klass }
164
+ end
165
+
162
166
  private
163
167
 
164
168
  def indeterminate?
@@ -166,11 +170,11 @@ module Capybara
166
170
  end
167
171
 
168
172
  def min_repeat
169
- @exp.quantifier&.min || 1
173
+ @exp.repetitions.begin
170
174
  end
171
175
 
172
176
  def max_repeat
173
- @exp.quantifier&.max || 1
177
+ @exp.repetitions.end
174
178
  end
175
179
 
176
180
  def fixed_repeat?