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
@@ -1,183 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Style/AsciiComments
4
-
5
- require 'capybara/selector/filter_set'
6
- require 'capybara/selector/css'
7
- require 'capybara/selector/regexp_disassembler'
8
- require 'capybara/selector/builders/xpath_builder'
9
- require 'capybara/selector/builders/css_builder'
10
-
11
3
  module Capybara
12
- #
13
- # ## Built-in Selectors
14
- #
15
- # * **:xpath** - Select elements by XPath expression
16
- # * Locator: An XPath expression
17
- #
18
- # * **:css** - Select elements by CSS selector
19
- # * Locator: A CSS selector
20
- #
21
- # * **:id** - Select element by id
22
- # * Locator: (String, Regexp, XPath::Expression) The id of the element to match
23
- #
24
- # * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
25
- # * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
26
- # * Filters:
27
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
28
- # * :name (String) — Matches the name attribute
29
- # * :placeholder (String) — Matches the placeholder attribute
30
- # * :type (String) — Matches the type attribute of the field or element type for 'textarea' and 'select'
31
- # * :readonly (Boolean)
32
- # * :with (String) — Matches the current value of the field
33
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
34
- # * :checked (Boolean) — Match checked fields?
35
- # * :unchecked (Boolean) — Match unchecked fields?
36
- # * :disabled (Boolean) — Match disabled field?
37
- # * :multiple (Boolean) — Match fields that accept multiple values
38
- # * :style (String, Regexp, Hash)
39
- #
40
- # * **:fieldset** - Select fieldset elements
41
- # * Locator: Matches id or contents of wrapped legend
42
- # * Filters:
43
- # * :id (String, Regexp, XPath::Expression) — Matches id attribute
44
- # * :legend (String) — Matches contents of wrapped legend
45
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
46
- # * :style (String, Regexp, Hash)
47
- #
48
- # * **:link** - Find links ( <a> elements with an href attribute )
49
- # * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
50
- # * Filters:
51
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
52
- # * :title (String) — Matches the title attribute
53
- # * :alt (String) — Matches the alt attribute of a contained img element
54
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
55
- # * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
56
- # * :style (String, Regexp, Hash)
57
- #
58
- # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
59
- # * Locator: Matches the id, Capybara.test_id attribute, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
60
- # * Filters:
61
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
62
- # * :title (String) — Matches the title attribute
63
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
64
- # * :value (String) — Matches the value of an input button
65
- # * :type
66
- # * :style (String, Regexp, Hash)
67
- #
68
- # * **:link_or_button** - Find links or buttons
69
- # * Locator: See :link and :button selectors
70
- #
71
- # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
72
- # * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
73
- # * Filters:
74
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
75
- # * :name (String) — Matches the name attribute
76
- # * :placeholder (String) — Matches the placeholder attribute
77
- # * :with (String) — Matches the current value of the field
78
- # * :type (String) — Matches the type attribute of the field or element type for 'textarea'
79
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
80
- # * :disabled (Boolean) — Match disabled field?
81
- # * :multiple (Boolean) — Match fields that accept multiple values
82
- # * :style (String, Regexp, Hash)
83
- #
84
- # * **:radio_button** - Find radio buttons
85
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
86
- # * Filters:
87
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
88
- # * :name (String) — Matches the name attribute
89
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
90
- # * :checked (Boolean) — Match checked fields?
91
- # * :unchecked (Boolean) — Match unchecked fields?
92
- # * :disabled (Boolean) — Match disabled field?
93
- # * :option (String) — Match the value
94
- # * :style (String, Regexp, Hash)
95
- #
96
- # * **:checkbox** - Find checkboxes
97
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
98
- # * Filters:
99
- # * *:id (String, Regexp, XPath::Expression) — Matches the id attribute
100
- # * *:name (String) — Matches the name attribute
101
- # * *:class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
102
- # * *:checked (Boolean) — Match checked fields?
103
- # * *:unchecked (Boolean) — Match unchecked fields?
104
- # * *:disabled (Boolean) — Match disabled field?
105
- # * *:option (String) — Match the value
106
- # * :style (String, Regexp, Hash)
107
- #
108
- # * **:select** - Find select elements
109
- # * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
110
- # * Filters:
111
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
112
- # * :name (String) — Matches the name attribute
113
- # * :placeholder (String) — Matches the placeholder attribute
114
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
115
- # * :disabled (Boolean) — Match disabled field?
116
- # * :multiple (Boolean) — Match fields that accept multiple values
117
- # * :options (Array<String>) — Exact match options
118
- # * :with_options (Array<String>) — Partial match options
119
- # * :selected (String, Array<String>) — Match the selection(s)
120
- # * :with_selected (String, Array<String>) — Partial match the selection(s)
121
- # * :style (String, Regexp, Hash)
122
- #
123
- # * **:option** - Find option elements
124
- # * Locator: Match text of option
125
- # * Filters:
126
- # * :disabled (Boolean) — Match disabled option
127
- # * :selected (Boolean) — Match selected option
128
- #
129
- # * **:datalist_input**
130
- # * Locator:
131
- # * Filters:
132
- # * :disabled
133
- # * :name
134
- # * :placeholder
135
- #
136
- # * **:datalist_option**
137
- # * Locator:
138
- #
139
- # * **:file_field** - Find file input elements
140
- # * Locator: Match id, Capybara.test_id attribute, name, or associated label text
141
- # * Filters:
142
- # * :id (String, Regexp, XPath::Expression) — Matches the id attribute
143
- # * :name (String) — Matches the name attribute
144
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
145
- # * :disabled (Boolean) — Match disabled field?
146
- # * :multiple (Boolean) — Match field that accepts multiple values
147
- # * :style (String, Regexp, Hash)
148
- #
149
- # * **:label** - Find label elements
150
- # * Locator: Match id or text contents
151
- # * Filters:
152
- # * :for (Element, String) — The element or id of the element associated with the label
153
- #
154
- # * **:table** - Find table elements
155
- # * Locator: id or caption text of table
156
- # * Filters:
157
- # * :id (String, Regexp, XPath::Expression) — Match id attribute of table
158
- # * :caption (String) — Match text of associated caption
159
- # * :class ((String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
160
- # * :style (String, Regexp, Hash)
161
- #
162
- # * **:frame** - Find frame/iframe elements
163
- # * Locator: Match id or name
164
- # * Filters:
165
- # * :id (String, Regexp, XPath::Expression) — Match id attribute
166
- # * :name (String) — Match name attribute
167
- # * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
168
- # * :style (String, Regexp, Hash)
169
- #
170
- # * **:element**
171
- # * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
172
- # * Filters: Matches on any element attribute
173
- #
174
- class Selector
175
- attr_reader :name, :format
176
- extend Forwardable
177
-
4
+ class Selector < SimpleDelegator
178
5
  class << self
179
6
  def all
180
- @selectors ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
7
+ @definitions ||= {} # rubocop:disable Naming/MemoizedInstanceVariableName
181
8
  end
182
9
 
183
10
  def [](name)
@@ -185,7 +12,7 @@ module Capybara
185
12
  end
186
13
 
187
14
  def add(name, **options, &block)
188
- all[name.to_sym] = Capybara::Selector.new(name.to_sym, **options, &block)
15
+ all[name.to_sym] = Definition.new(name.to_sym, **options, &block)
189
16
  end
190
17
 
191
18
  def update(name, &block)
@@ -201,226 +28,67 @@ module Capybara
201
28
  end
202
29
  end
203
30
 
204
- def initialize(name, locator_type: nil, raw_locator: false, &block)
205
- @name = name
206
- @filter_set = FilterSet.add(name) {}
207
- @match = nil
208
- @label = nil
209
- @failure_message = nil
210
- @format = nil
211
- @expression = nil
212
- @expression_filters = {}
213
- @locator_filter = nil
214
- @default_visibility = nil
215
- @locator_type = locator_type
216
- @raw_locator = raw_locator
217
- @config = {
218
- enable_aria_label: false,
219
- test_id: nil
220
- }
221
- instance_eval(&block)
222
- end
223
-
224
- def custom_filters
225
- warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
226
- node_filters.merge(expression_filters).freeze
227
- end
31
+ attr_reader :errors
228
32
 
229
- def node_filters
230
- @filter_set.node_filters
33
+ def initialize(definition, config:, format:)
34
+ definition = self.class[definition] unless definition.is_a? Definition
35
+ super(definition)
36
+ @definition = definition
37
+ @config = config
38
+ @format = format
39
+ @errors = []
231
40
  end
232
41
 
233
- def expression_filters
234
- @filter_set.expression_filters
42
+ def format
43
+ @format || @definition.default_format
235
44
  end
45
+ alias_method :current_format, :format
236
46
 
237
- ##
238
- #
239
- # Define a selector by an xpath expression
240
- #
241
- # @overload xpath(*expression_filters, &block)
242
- # @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
243
- # @yield [locator, options] The block to use to generate the XPath expression
244
- # @yieldparam [String] locator The locator string passed to the query
245
- # @yieldparam [Hash] options The options hash passed to the query
246
- # @yieldreturn [#to_xpath, #to_s] An object that can produce an xpath expression
247
- #
248
- # @overload xpath()
249
- # @return [#call] The block that will be called to generate the XPath expression
250
- #
251
- def xpath(*allowed_filters, &block)
252
- expression(:xpath, allowed_filters, &block)
253
- end
254
-
255
- ##
256
- #
257
- # Define a selector by a CSS selector
258
- #
259
- # @overload css(*expression_filters, &block)
260
- # @param [Array<Symbol>] expression_filters ([]) Names of filters that can be implemented via this CSS selector
261
- # @yield [locator, options] The block to use to generate the CSS selector
262
- # @yieldparam [String] locator The locator string passed to the query
263
- # @yieldparam [Hash] options The options hash passed to the query
264
- # @yieldreturn [#to_s] An object that can produce a CSS selector
265
- #
266
- # @overload css()
267
- # @return [#call] The block that will be called to generate the CSS selector
268
- #
269
- def css(*allowed_filters, &block)
270
- expression(:css, allowed_filters, &block)
47
+ def enable_aria_label
48
+ @config[:enable_aria_label]
271
49
  end
272
50
 
273
- ##
274
- #
275
- # Automatic selector detection
276
- #
277
- # @yield [locator] This block takes the passed in locator string and returns whether or not it matches the selector
278
- # @yieldparam [String], locator The locator string used to determin if it matches the selector
279
- # @yieldreturn [Boolean] Whether this selector matches the locator string
280
- # @return [#call] The block that will be used to detect selector match
281
- #
282
- def match(&block)
283
- @match = block if block
284
- @match
51
+ def enable_aria_role
52
+ @config[:enable_aria_role]
285
53
  end
286
54
 
287
- ##
288
- #
289
- # Set/get a descriptive label for the selector
290
- #
291
- # @overload label(label)
292
- # @param [String] label A descriptive label for this selector - used in error messages
293
- # @overload label()
294
- # @return [String] The currently set label
295
- #
296
- def label(label = nil)
297
- @label = label if label
298
- @label
55
+ def test_id
56
+ @config[:test_id]
299
57
  end
300
58
 
301
- ##
302
- #
303
- # Description of the selector
304
- #
305
- # @!method description(options)
306
- # @param [Hash] options The options of the query used to generate the description
307
- # @return [String] Description of the selector when used with the options passed
308
- def_delegator :@filter_set, :description
309
-
310
- def call(locator, selector_config: {}, **options)
311
- @config.merge! selector_config
59
+ def call(locator, **options)
312
60
  if format
313
- @expression.call(locator, options)
61
+ raise ArgumentError, "Selector #{@name} does not support #{format}" unless expressions.key?(format)
62
+
63
+ instance_exec(locator, **options, &expressions[format])
314
64
  else
315
65
  warn 'Selector has no format'
316
66
  end
317
67
  ensure
318
- warn "Locator #{locator.inspect} must #{locator_description}. This will raise an error in a future version of Capybara." unless locator_valid?(locator)
319
- end
320
-
321
- ##
322
- #
323
- # Should this selector be used for the passed in locator
324
- #
325
- # This is used by the automatic selector selection mechanism when no selector type is passed to a selector query
326
- #
327
- # @param [String] locator The locator passed to the query
328
- # @return [Boolean] Whether or not to use this selector
329
- #
330
- def match?(locator)
331
- @match&.call(locator)
332
- end
333
-
334
- ##
335
- #
336
- # Define a node filter for use with this selector
337
- #
338
- # @!method node_filter(name, *types, options={}, &block)
339
- # @param [Symbol, Regexp] name The filter name
340
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
341
- # @param [Hash] options ({}) Options of the filter
342
- # @option options [Array<>] :valid_values Valid values for this filter
343
- # @option options :default The default value of the filter (if any)
344
- # @option options :skip_if Value of the filter that will cause it to be skipped
345
- # @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.
346
- #
347
- # If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
348
- # is passed for the name the block should accept | node, option_name, option_value |. In either case
349
- # the block should return `true` if the node passes the filer or `false` if it doesn't
350
-
351
- # @!method filter
352
- # See {Selector#node_filter}
353
-
354
- ##
355
- #
356
- # Define an expression filter for use with this selector
357
- #
358
- # @!method expression_filter(name, *types, matcher: nil, **options, &block)
359
- # @param [Symbol, Regexp] name The filter name
360
- # @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
361
- # @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
362
- # @param [Hash] options ({}) Options of the filter
363
- # @option options [Array<>] :valid_values Valid values for this filter
364
- # @option options :default The default value of the filter (if any)
365
- # @option options :skip_if Value of the filter that will cause it to be skipped
366
- # @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.
367
- #
368
- # If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
369
- # is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
370
- # the block should return the modified expression
371
-
372
- def_delegators :@filter_set, :node_filter, :expression_filter, :filter
373
-
374
- def locator_filter(*types, **options, &block)
375
- types.each { |type| options[type] = true }
376
- @locator_filter = Filters::LocatorFilter.new(block, options) if block
377
- @locator_filter
378
- end
379
-
380
- def filter_set(name, filters_to_use = nil)
381
- @filter_set.import(name, filters_to_use)
382
- end
383
-
384
- def_delegator :@filter_set, :describe
385
-
386
- def describe_expression_filters(&block)
387
- if block_given?
388
- describe(:expression_filters, &block)
389
- else
390
- describe(:expression_filters) do |**options|
391
- describe_all_expression_filters(options)
392
- end
68
+ unless locator_valid?(locator)
69
+ Capybara::Helpers.warn(
70
+ "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. " \
71
+ 'This will raise an error in a future version of Capybara. ' \
72
+ "Called from: #{Capybara::Helpers.filter_backtrace(caller)}"
73
+ )
393
74
  end
394
75
  end
395
76
 
396
- def describe_node_filters(&block)
397
- describe(:node_filters, &block)
398
- end
399
-
400
- ##
401
- #
402
- # Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
403
- # If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
404
- #
405
- # @param [Symbol] default_visibility Only find elements with the specified visibility:
406
- # * :all - finds visible and invisible elements.
407
- # * :hidden - only finds invisible elements.
408
- # * :visible - only finds visible elements.
409
- def visible(default_visibility = nil, &block)
410
- @default_visibility = block || default_visibility
77
+ def add_error(error_msg)
78
+ errors << error_msg
411
79
  end
412
80
 
413
- def default_visibility(fallback = Capybara.ignore_hidden_elements, options = {})
414
- vis = if @default_visibility&.respond_to?(:call)
415
- @default_visibility.call(options)
416
- else
417
- @default_visibility
418
- end
419
- vis.nil? ? fallback : vis
81
+ def expression_for(name, locator, config: @config, format: current_format, **options)
82
+ Selector.new(name, config: config, format: format).call(locator, **options)
420
83
  end
421
84
 
422
- def add_error(error_msg)
423
- errors << error_msg
85
+ # @api private
86
+ def with_filter_errors(errors)
87
+ old_errors = @errors
88
+ @errors = errors
89
+ yield
90
+ ensure
91
+ @errors = old_errors
424
92
  end
425
93
 
426
94
  # @api private
@@ -431,39 +99,12 @@ module Capybara
431
99
  when :xpath
432
100
  Capybara::Selector::XPathBuilder
433
101
  else
434
- raise NotImplementedError, "No builder exists for selector of type #{format}"
102
+ raise NotImplementedError, "No builder exists for selector of type #{default_format}"
435
103
  end.new(expr)
436
104
  end
437
105
 
438
- # @api private
439
- def with_filter_errors(errors)
440
- Thread.current["capybara_#{object_id}_errors"] = errors
441
- yield
442
- ensure
443
- Thread.current["capybara_#{object_id}_errors"] = nil
444
- end
445
-
446
- # @api private
447
- def raw_locator?
448
- !!@raw_locator
449
- end
450
-
451
106
  private
452
107
 
453
- def locator_types
454
- return nil unless @locator_type
455
-
456
- Array(@locator_type)
457
- end
458
-
459
- def locator_valid?(locator)
460
- return true unless locator && locator_types
461
-
462
- locator_types&.any? do |type_or_method|
463
- type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
464
- end
465
- end
466
-
467
108
  def locator_description
468
109
  locator_types.group_by { |lt| lt.is_a? Symbol }.map do |symbol, types_or_methods|
469
110
  if symbol
@@ -474,16 +115,12 @@ module Capybara
474
115
  end.join(' or ')
475
116
  end
476
117
 
477
- def errors
478
- Thread.current["capybara_#{object_id}_errors"] || []
479
- end
480
-
481
- def enable_aria_label
482
- @config[:enable_aria_label]
483
- end
118
+ def locator_valid?(locator)
119
+ return true unless locator && locator_types
484
120
 
485
- def test_id
486
- @config[:test_id]
121
+ locator_types&.any? do |type_or_method|
122
+ type_or_method.is_a?(Symbol) ? locator.respond_to?(type_or_method) : type_or_method === locator # rubocop:disable Style/CaseEquality
123
+ end
487
124
  end
488
125
 
489
126
  def locate_field(xpath, locator, **_options)
@@ -499,23 +136,11 @@ module Capybara
499
136
  attr_matchers |= XPath.attr(test_id) == locator if test_id
500
137
 
501
138
  locate_xpath = locate_xpath[attr_matchers]
502
- locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
139
+ locate_xpath + locate_label(locator).descendant(xpath)
503
140
  end
504
141
 
505
- def describe_all_expression_filters(**opts)
506
- expression_filters.map do |ef_name, ef|
507
- if ef.matcher?
508
- handled_custom_keys(ef, opts.keys).map { |key| " with #{ef_name}[#{key} => #{opts[key]}]" }.join
509
- elsif opts.key?(ef_name)
510
- " with #{ef_name} #{opts[ef_name]}"
511
- end
512
- end.join
513
- end
514
-
515
- def handled_custom_keys(filter, keys)
516
- keys.select do |key|
517
- filter.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
518
- end
142
+ def locate_label(locator)
143
+ XPath.descendant(:label)[XPath.string.n.is(locator)]
519
144
  end
520
145
 
521
146
  def find_by_attr(attribute, value)
@@ -530,20 +155,5 @@ module Capybara
530
155
  def find_by_class_attr(classes)
531
156
  Array(classes).map { |klass| XPath.attr(:class).contains_word(klass) }.reduce(:&)
532
157
  end
533
-
534
- def parameter_names(block)
535
- block.parameters.select { |(type, _name)| %i[key keyreq].include? type }.map { |(_type, name)| name }
536
- end
537
-
538
- def expression(type, allowed_filters, &block)
539
- if block
540
- @format, @expression = type, block
541
- allowed_filters = parameter_names(block) if allowed_filters.empty?
542
- allowed_filters.flatten.each { |ef| expression_filters[ef] = Filters::IdentityExpressionFilter.new(ef) }
543
- end
544
- format == type ? @expression : nil
545
- end
546
158
  end
547
159
  end
548
-
549
- # rubocop:enable Style/AsciiComments
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XPath
4
+ class Renderer
5
+ def join(*expressions)
6
+ expressions.join('/')
7
+ end
8
+ end
9
+ end
10
+
11
+ module XPath
12
+ module DSL
13
+ def join(*expressions)
14
+ XPath::Expression.new(:join, *[self, expressions].flatten)
15
+ end
16
+ end
17
+ end