capybara 3.3.0 → 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 (308) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +803 -13
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +25 -9
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +17 -3
  9. data/lib/capybara/driver/node.rb +31 -6
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +31 -7
  12. data/lib/capybara/minitest/spec.rb +180 -88
  13. data/lib/capybara/minitest.rb +262 -149
  14. data/lib/capybara/node/actions.rb +202 -116
  15. data/lib/capybara/node/base.rb +34 -19
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +10 -12
  18. data/lib/capybara/node/element.rb +269 -115
  19. data/lib/capybara/node/finders.rb +99 -77
  20. data/lib/capybara/node/matchers.rb +327 -151
  21. data/lib/capybara/node/simple.rb +48 -13
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +8 -9
  25. data/lib/capybara/queries/base_query.rb +23 -16
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +1 -0
  28. data/lib/capybara/queries/selector_query.rb +587 -130
  29. data/lib/capybara/queries/sibling_query.rb +8 -6
  30. data/lib/capybara/queries/style_query.rb +6 -2
  31. data/lib/capybara/queries/text_query.rb +28 -14
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +92 -25
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +68 -41
  37. data/lib/capybara/rack_test/node.rb +106 -39
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +75 -52
  44. data/lib/capybara/rspec/features.rb +7 -7
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +141 -339
  60. data/lib/capybara/rspec.rb +2 -0
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +27 -25
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +73 -25
  88. data/lib/capybara/selector/filters/base.rb +24 -5
  89. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +85 -348
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -447
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +255 -143
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +93 -11
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +436 -134
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +56 -19
  123. data/lib/capybara/server/checker.rb +9 -3
  124. data/lib/capybara/server/middleware.rb +28 -12
  125. data/lib/capybara/server.rb +33 -10
  126. data/lib/capybara/session/config.rb +34 -10
  127. data/lib/capybara/session/matchers.rb +23 -16
  128. data/lib/capybara/session.rb +230 -170
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +121 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +127 -40
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/assert_current_path_spec.rb +21 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +52 -58
  141. data/lib/capybara/spec/session/assert_style_spec.rb +7 -7
  142. data/lib/capybara/spec/session/assert_text_spec.rb +74 -50
  143. data/lib/capybara/spec/session/assert_title_spec.rb +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +6 -6
  146. data/lib/capybara/spec/session/check_spec.rb +102 -47
  147. data/lib/capybara/spec/session/choose_spec.rb +58 -32
  148. data/lib/capybara/spec/session/click_button_spec.rb +219 -163
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -23
  150. data/lib/capybara/spec/session/click_link_spec.rb +77 -54
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +38 -29
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +8 -8
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +16 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +68 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +101 -46
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
  165. data/lib/capybara/spec/session/find_field_spec.rb +32 -30
  166. data/lib/capybara/spec/session/find_link_spec.rb +31 -21
  167. data/lib/capybara/spec/session/find_spec.rb +244 -141
  168. data/lib/capybara/spec/session/first_spec.rb +43 -43
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +30 -18
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +45 -18
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +94 -13
  179. data/lib/capybara/spec/session/has_css_spec.rb +272 -132
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +44 -4
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +31 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +111 -71
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/headers_spec.rb +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +894 -142
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +63 -35
  199. data/lib/capybara/spec/session/response_code_spec.rb +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  202. data/lib/capybara/spec/session/save_page_spec.rb +37 -37
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +10 -10
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +85 -85
  207. data/lib/capybara/spec/session/selectors_spec.rb +49 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +25 -24
  210. data/lib/capybara/spec/session/title_spec.rb +7 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +33 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +68 -49
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
  216. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +20 -16
  217. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +6 -2
  218. data/lib/capybara/spec/session/window/window_spec.rb +62 -63
  219. data/lib/capybara/spec/session/window/windows_spec.rb +5 -1
  220. data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
  221. data/lib/capybara/spec/session/within_spec.rb +79 -42
  222. data/lib/capybara/spec/spec_helper.rb +41 -53
  223. data/lib/capybara/spec/test_app.rb +132 -43
  224. data/lib/capybara/spec/views/animated.erb +49 -0
  225. data/lib/capybara/spec/views/form.erb +139 -42
  226. data/lib/capybara/spec/views/frame_child.erb +4 -3
  227. data/lib/capybara/spec/views/frame_one.erb +2 -1
  228. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  229. data/lib/capybara/spec/views/frame_two.erb +1 -1
  230. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  231. data/lib/capybara/spec/views/layout.erb +10 -0
  232. data/lib/capybara/spec/views/obscured.erb +47 -0
  233. data/lib/capybara/spec/views/offset.erb +33 -0
  234. data/lib/capybara/spec/views/path.erb +2 -2
  235. data/lib/capybara/spec/views/popup_one.erb +1 -1
  236. data/lib/capybara/spec/views/popup_two.erb +1 -1
  237. data/lib/capybara/spec/views/react.erb +45 -0
  238. data/lib/capybara/spec/views/scroll.erb +21 -0
  239. data/lib/capybara/spec/views/spatial.erb +31 -0
  240. data/lib/capybara/spec/views/tables.erb +67 -0
  241. data/lib/capybara/spec/views/with_animation.erb +39 -4
  242. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  243. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  244. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  245. data/lib/capybara/spec/views/with_hover.erb +3 -2
  246. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  247. data/lib/capybara/spec/views/with_html.erb +37 -9
  248. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  249. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  250. data/lib/capybara/spec/views/with_js.erb +26 -5
  251. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  252. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  253. data/lib/capybara/spec/views/with_scope.erb +2 -2
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  256. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  257. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  258. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  259. data/lib/capybara/spec/views/with_windows.erb +1 -1
  260. data/lib/capybara/spec/views/within_frames.erb +1 -1
  261. data/lib/capybara/version.rb +1 -1
  262. data/lib/capybara/window.rb +19 -25
  263. data/lib/capybara.rb +126 -111
  264. data/spec/basic_node_spec.rb +59 -34
  265. data/spec/capybara_spec.rb +56 -44
  266. data/spec/counter_spec.rb +35 -0
  267. data/spec/css_builder_spec.rb +101 -0
  268. data/spec/css_splitter_spec.rb +8 -8
  269. data/spec/dsl_spec.rb +79 -52
  270. data/spec/filter_set_spec.rb +9 -9
  271. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
  272. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
  273. data/spec/minitest_spec.rb +45 -7
  274. data/spec/minitest_spec_spec.rb +87 -64
  275. data/spec/per_session_config_spec.rb +6 -6
  276. data/spec/rack_test_spec.rb +172 -116
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +80 -72
  279. data/spec/rspec/features_spec.rb +21 -16
  280. data/spec/rspec/scenarios_spec.rb +10 -6
  281. data/spec/rspec/shared_spec_matchers.rb +407 -365
  282. data/spec/rspec/views_spec.rb +3 -3
  283. data/spec/rspec_matchers_spec.rb +35 -10
  284. data/spec/rspec_spec.rb +63 -41
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +334 -89
  287. data/spec/selenium_spec_chrome.rb +176 -62
  288. data/spec/selenium_spec_chrome_remote.rb +54 -14
  289. data/spec/selenium_spec_edge.rb +41 -8
  290. data/spec/selenium_spec_firefox.rb +228 -0
  291. data/spec/selenium_spec_firefox_remote.rb +94 -0
  292. data/spec/selenium_spec_ie.rb +129 -11
  293. data/spec/selenium_spec_safari.rb +162 -0
  294. data/spec/server_spec.rb +171 -97
  295. data/spec/session_spec.rb +34 -18
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +344 -80
  298. data/spec/spec_helper.rb +124 -2
  299. data/spec/whitespace_normalizer_spec.rb +54 -0
  300. data/spec/xpath_builder_spec.rb +93 -0
  301. metadata +326 -28
  302. data/lib/capybara/rspec/compound.rb +0 -94
  303. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -31
  304. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -31
  305. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  306. data/lib/capybara/spec/session/source_spec.rb +0 -0
  307. data/lib/capybara/spec/views/with_title.erb +0 -5
  308. data/spec/selenium_spec_marionette.rb +0 -167
@@ -1,467 +1,494 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'capybara/selector/xpath_extensions'
3
4
  require 'capybara/selector/selector'
5
+ require 'capybara/selector/definition'
6
+
7
+ #
8
+ # All Selectors below support the listed selector specific filters in addition to the following system-wide filters
9
+ # * :id (String, Regexp, XPath::Expression) - Matches the id attribute
10
+ # * :class (String, Array<String | Regexp>, Regexp, XPath::Expression) - Matches the class(es) provided
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
17
+ # * :focused (Boolean) - Match elements with focus (requires driver support)
18
+ #
19
+ # ### Built-in Selectors
20
+ #
21
+ # * **:xpath** - Select elements by XPath expression
22
+ # * Locator: An XPath expression
23
+ #
24
+ # ```ruby
25
+ # page.html # => '<input>'
26
+ #
27
+ # page.find :xpath, './/input'
28
+ # ```
29
+ #
30
+ # * **:css** - Select elements by CSS selector
31
+ # * Locator: A CSS selector
32
+ #
33
+ # ```ruby
34
+ # page.html # => '<input>'
35
+ #
36
+ # page.find :css, 'input'
37
+ # ```
38
+ #
39
+ # * **:id** - Select element by id
40
+ # * Locator: (String, Regexp, XPath::Expression) The id of the element to match
41
+ #
42
+ # ```ruby
43
+ # page.html # => '<input id="field">'
44
+ #
45
+ # page.find :id, 'content'
46
+ # ```
47
+ #
48
+ # * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
49
+ # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or
50
+ # associated label text
51
+ # * Filters:
52
+ # * :name (String, Regexp) - Matches the name attribute
53
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
54
+ # * :type (String) - Matches the type attribute of the field or element type for 'textarea' and 'select'
55
+ # * :readonly (Boolean) - Match on the element being readonly
56
+ # * :with (String, Regexp) - Matches the current value of the field
57
+ # * :checked (Boolean) - Match checked fields?
58
+ # * :unchecked (Boolean) - Match unchecked fields?
59
+ # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
60
+ # * :multiple (Boolean) - Match fields that accept multiple values
61
+ # * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
62
+ # * :validation_message (String, Regexp) - Matches the elements current validationMessage
63
+ #
64
+ # ```ruby
65
+ # page.html # => '<label for="article_title">Title</label>
66
+ # <input id="article_title" name="article[title]" value="Hello world">'
67
+ #
68
+ # page.find :field, 'article_title'
69
+ # page.find :field, 'article[title]'
70
+ # page.find :field, 'Title'
71
+ # page.find :field, 'Title', type: 'text', with: 'Hello world'
72
+ # ```
73
+ #
74
+ # * **:fieldset** - Select fieldset elements
75
+ # * Locator: Matches id, {Capybara.configure test_id}, or contents of wrapped legend
76
+ # * Filters:
77
+ # * :legend (String) - Matches contents of wrapped legend
78
+ # * :disabled (Boolean) - Match disabled fieldset?
79
+ #
80
+ # ```ruby
81
+ # page.html # => '<fieldset disabled>
82
+ # <legend>Fields (disabled)</legend>
83
+ # </fieldset>'
84
+ #
85
+ # page.find :fieldset, 'Fields (disabled)', disabled: true
86
+ # ```
87
+ #
88
+ # * **:link** - Find links (`<a>` elements with an href attribute)
89
+ # * Locator: Matches the id, {Capybara.configure test_id}, or title attributes, or the string content of the link,
90
+ # or the alt attribute of a contained img element. By default this selector requires a link to have an href attribute.
91
+ # * Filters:
92
+ # * :title (String) - Matches the title attribute
93
+ # * :alt (String) - Matches the alt attribute of a contained img element
94
+ # * :href (String, Regexp, nil, false) - Matches the normalized href of the link, if nil will find `<a>` elements with no href attribute, if false ignores href presence
95
+ #
96
+ # ```ruby
97
+ # page.html # => '<a href="/">Home</a>'
98
+ #
99
+ # page.find :link, 'Home', href: '/'
100
+ #
101
+ # page.html # => '<a href="/"><img src="/logo.png" alt="The logo"></a>'
102
+ #
103
+ # page.find :link, 'The logo', href: '/'
104
+ # page.find :link, alt: 'The logo', href: '/'
105
+ # ```
106
+ #
107
+ # * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
108
+ # * 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
109
+ # * Filters:
110
+ # * :name (String, Regexp) - Matches the name attribute
111
+ # * :title (String) - Matches the title attribute
112
+ # * :value (String) - Matches the value of an input button
113
+ # * :type (String) - Matches the type attribute
114
+ # * :disabled (Boolean, :all) - Match disabled buttons (Default: false)
115
+ #
116
+ # ```ruby
117
+ # page.html # => '<button>Submit</button>'
118
+ #
119
+ # page.find :button, 'Submit'
120
+ #
121
+ # page.html # => '<button name="article[state]" value="draft">Save as draft</button>'
122
+ #
123
+ # page.find :button, 'Save as draft', name: 'article[state]', value: 'draft'
124
+ # ```
125
+ #
126
+ # * **:link_or_button** - Find links or buttons
127
+ # * Locator: See :link and :button selectors
128
+ # * Filters:
129
+ # * :disabled (Boolean, :all) - Match disabled buttons? (Default: false)
130
+ #
131
+ # ```ruby
132
+ # page.html # => '<a href="/">Home</a>'
133
+ #
134
+ # page.find :link_or_button, 'Home'
135
+ #
136
+ # page.html # => '<button>Submit</button>'
137
+ #
138
+ # page.find :link_or_button, 'Submit'
139
+ # ```
140
+ #
141
+ # * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
142
+ # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
143
+ # * Filters:
144
+ # * :name (String, Regexp) - Matches the name attribute
145
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
146
+ # * :with (String, Regexp) - Matches the current value of the field
147
+ # * :type (String) - Matches the type attribute of the field or element type for 'textarea'
148
+ # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
149
+ # * :multiple (Boolean) - Match fields that accept multiple values
150
+ # * :valid (Boolean) - Match fields that are valid/invalid according to HTML5 form validation
151
+ # * :validation_message (String, Regexp) - Matches the elements current validationMessage
152
+ #
153
+ # ```ruby
154
+ # page.html # => '<label for="article_body">Body</label>
155
+ # <textarea id="article_body" name="article[body]"></textarea>'
156
+ #
157
+ # page.find :fillable_field, 'article_body'
158
+ # page.find :fillable_field, 'article[body]'
159
+ # page.find :fillable_field, 'Body'
160
+ # page.find :field, 'Body', type: 'textarea'
161
+ # ```
162
+ #
163
+ # * **:radio_button** - Find radio buttons
164
+ # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
165
+ # * Filters:
166
+ # * :name (String, Regexp) - Matches the name attribute
167
+ # * :checked (Boolean) - Match checked fields?
168
+ # * :unchecked (Boolean) - Match unchecked fields?
169
+ # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
170
+ # * :option (String, Regexp) - Match the current value
171
+ # * :with - Alias of :option
172
+ #
173
+ # ```ruby
174
+ # page.html # => '<input type="radio" id="article_state_published" name="article[state]" value="published" checked>
175
+ # <label for="article_state_published">Published</label>
176
+ # <input type="radio" id="article_state_draft" name="article[state]" value="draft">
177
+ # <label for="article_state_draft">Draft</label>'
178
+ #
179
+ # page.find :radio_button, 'article_state_published'
180
+ # page.find :radio_button, 'article[state]', option: 'published'
181
+ # page.find :radio_button, 'Published', checked: true
182
+ # page.find :radio_button, 'Draft', unchecked: true
183
+ # ```
184
+ #
185
+ # * **:checkbox** - Find checkboxes
186
+ # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
187
+ # * Filters:
188
+ # * :name (String, Regexp) - Matches the name attribute
189
+ # * :checked (Boolean) - Match checked fields?
190
+ # * :unchecked (Boolean) - Match unchecked fields?
191
+ # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
192
+ # * :with (String, Regexp) - Match the current value
193
+ # * :option - Alias of :with
194
+ #
195
+ # ```ruby
196
+ # page.html # => '<input type="checkbox" id="registration_terms" name="registration[terms]" value="true">
197
+ # <label for="registration_terms">I agree to terms and conditions</label>'
198
+ #
199
+ # page.find :checkbox, 'registration_terms'
200
+ # page.find :checkbox, 'registration[terms]'
201
+ # page.find :checkbox, 'I agree to terms and conditions', unchecked: true
202
+ # ```
203
+ #
204
+ # * **:select** - Find select elements
205
+ # * Locator: Match id, {Capybara.configure test_id} attribute, name, placeholder, or associated label text
206
+ # * Filters:
207
+ # * :name (String, Regexp) - Matches the name attribute
208
+ # * :placeholder (String, Placeholder) - Matches the placeholder attribute
209
+ # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
210
+ # * :multiple (Boolean) - Match fields that accept multiple values
211
+ # * :options (Array<String>) - Exact match options
212
+ # * :enabled_options (Array<String>) - Exact match enabled options
213
+ # * :disabled_options (Array<String>) - Exact match disabled options
214
+ # * :with_options (Array<String>) - Partial match options
215
+ # * :selected (String, Array<String>) - Match the selection(s)
216
+ # * :with_selected (String, Array<String>) - Partial match the selection(s)
217
+ #
218
+ # ```ruby
219
+ # page.html # => '<label for="article_category">Category</label>
220
+ # <select id="article_category" name="article[category]">
221
+ # <option value="General" checked></option>
222
+ # <option value="Other"></option>
223
+ # </select>'
224
+ #
225
+ # page.find :select, 'article_category'
226
+ # page.find :select, 'article[category]'
227
+ # page.find :select, 'Category'
228
+ # page.find :select, 'Category', selected: 'General'
229
+ # page.find :select, with_options: ['General']
230
+ # page.find :select, with_options: ['Other']
231
+ # page.find :select, options: ['General', 'Other']
232
+ # page.find :select, options: ['General'] # => raises Capybara::ElementNotFound
233
+ # ```
234
+ #
235
+ # * **:option** - Find option elements
236
+ # * Locator: Match text of option
237
+ # * Filters:
238
+ # * :disabled (Boolean) - Match disabled option
239
+ # * :selected (Boolean) - Match selected option
240
+ #
241
+ # ```ruby
242
+ # page.html # => '<option value="General" checked></option>
243
+ # <option value="Disabled" disabled></option>
244
+ # <option value="Other"></option>'
245
+ #
246
+ # page.find :option, 'General'
247
+ # page.find :option, 'General', selected: true
248
+ # page.find :option, 'Disabled', disabled: true
249
+ # page.find :option, 'Other', selected: false
250
+ # ```
251
+ #
252
+ # * **:datalist_input** - Find input field with datalist completion
253
+ # * Locator: Matches against the id, {Capybara.configure test_id} attribute, name,
254
+ # placeholder, or associated label text
255
+ # * Filters:
256
+ # * :name (String, Regexp) - Matches the name attribute
257
+ # * :placeholder (String, Regexp) - Matches the placeholder attribute
258
+ # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
259
+ # * :options (Array<String>) - Exact match options
260
+ # * :with_options (Array<String>) - Partial match options
261
+ #
262
+ # ```ruby
263
+ # page.html # => '<label for="ice_cream_flavor">Flavor</label>
264
+ # <input list="ice_cream_flavors" id="ice_cream_flavor" name="ice_cream[flavor]">
265
+ # <datalist id="ice_cream_flavors">
266
+ # <option value="Chocolate"></option>
267
+ # <option value="Strawberry"></option>
268
+ # <option value="Vanilla"></option>
269
+ # </datalist>'
270
+ #
271
+ # page.find :datalist_input, 'ice_cream_flavor'
272
+ # page.find :datalist_input, 'ice_cream[flavor]'
273
+ # page.find :datalist_input, 'Flavor'
274
+ # page.find :datalist_input, with_options: ['Chocolate', 'Strawberry']
275
+ # page.find :datalist_input, options: ['Chocolate', 'Strawberry', 'Vanilla']
276
+ # page.find :datalist_input, options: ['Chocolate'] # => raises Capybara::ElementNotFound
277
+ # ```
278
+ #
279
+ # * **:datalist_option** - Find datalist option
280
+ # * Locator: Match text or value of option
281
+ # * Filters:
282
+ # * :disabled (Boolean) - Match disabled option
283
+ #
284
+ # ```ruby
285
+ # page.html # => '<datalist>
286
+ # <option value="Chocolate"></option>
287
+ # <option value="Strawberry"></option>
288
+ # <option value="Vanilla"></option>
289
+ # <option value="Forbidden" disabled></option>
290
+ # </datalist>'
291
+ #
292
+ # page.find :datalist_option, 'Chocolate'
293
+ # page.find :datalist_option, 'Strawberry'
294
+ # page.find :datalist_option, 'Vanilla'
295
+ # page.find :datalist_option, 'Forbidden', disabled: true
296
+ # ```
297
+ #
298
+ # * **:file_field** - Find file input elements
299
+ # * Locator: Match id, {Capybara.configure test_id} attribute, name, or associated label text
300
+ # * Filters:
301
+ # * :name (String, Regexp) - Matches the name attribute
302
+ # * :disabled (Boolean, :all) - Match disabled field? (Default: false)
303
+ # * :multiple (Boolean) - Match field that accepts multiple values
304
+ #
305
+ # ```ruby
306
+ # page.html # => '<label for="article_banner_image">Banner Image</label>
307
+ # <input type="file" id="article_banner_image" name="article[banner_image]">'
308
+ #
309
+ # page.find :file_field, 'article_banner_image'
310
+ # page.find :file_field, 'article[banner_image]'
311
+ # page.find :file_field, 'Banner Image'
312
+ # page.find :file_field, 'Banner Image', name: 'article[banner_image]'
313
+ # page.find :field, 'Banner Image', type: 'file'
314
+ # ```
315
+ #
316
+ # * **:label** - Find label elements
317
+ # * Locator: Match id, {Capybara.configure test_id}, or text contents
318
+ # * Filters:
319
+ # * :for (Element, String, Regexp) - The element or id of the element associated with the label
320
+ #
321
+ # ```ruby
322
+ # page.html # => '<label for="article_title">Title</label>
323
+ # <input id="article_title" name="article[title]">'
324
+ #
325
+ # page.find :label, 'Title'
326
+ # page.find :label, 'Title', for: 'article_title'
327
+ # page.find :label, 'Title', for: page.find('article[title]')
328
+ # ```
329
+ #
330
+ # * **:table** - Find table elements
331
+ # * Locator: id, {Capybara.configure test_id}, or caption text of table
332
+ # * Filters:
333
+ # * :caption (String) - Match text of associated caption
334
+ # * :with_rows (Array<Array<String>>, Array<Hash<String, String>>) - Partial match `<td>` data - visibility of `<td>` elements is not considered
335
+ # * :rows (Array<Array<String>>) - Match all `<td>`s - visibility of `<td>` elements is not considered
336
+ # * :with_cols (Array<Array<String>>, Array<Hash<String, String>>) - Partial match `<td>` data - visibility of `<td>` elements is not considered
337
+ # * :cols (Array<Array<String>>) - Match all `<td>`s - visibility of `<td>` elements is not considered
338
+ #
339
+ # ```ruby
340
+ # page.html # => '<table>
341
+ # <caption>A table</caption>
342
+ # <tr>
343
+ # <th>A</th>
344
+ # <th>B</th>
345
+ # </tr>
346
+ # <tr>
347
+ # <td>1</td>
348
+ # <td>2</td>
349
+ # </tr>
350
+ # <tr>
351
+ # <td>3</td>
352
+ # <td>4</td>
353
+ # </tr>
354
+ # </table>'
355
+ #
356
+ # page.find :table, 'A table'
357
+ # page.find :table, with_rows: [
358
+ # { 'A' => '1', 'B' => '2' },
359
+ # { 'A' => '3', 'B' => '4' },
360
+ # ]
361
+ # page.find :table, with_rows: [
362
+ # ['1', '2'],
363
+ # ['3', '4'],
364
+ # ]
365
+ # page.find :table, rows: [
366
+ # { 'A' => '1', 'B' => '2' },
367
+ # { 'A' => '3', 'B' => '4' },
368
+ # ]
369
+ # page.find :table, rows: [
370
+ # ['1', '2'],
371
+ # ['3', '4'],
372
+ # ]
373
+ # page.find :table, rows: [ ['1', '2'] ] # => raises Capybara::ElementNotFound
374
+ # ```
375
+ #
376
+ # * **:table_row** - Find table row
377
+ # * Locator: Array<String>, Hash<String, String> table row `<td>` contents - visibility of `<td>` elements is not considered
378
+ #
379
+ # ```ruby
380
+ # page.html # => '<table>
381
+ # <tr>
382
+ # <th>A</th>
383
+ # <th>B</th>
384
+ # </tr>
385
+ # <tr>
386
+ # <td>1</td>
387
+ # <td>2</td>
388
+ # </tr>
389
+ # <tr>
390
+ # <td>3</td>
391
+ # <td>4</td>
392
+ # </tr>
393
+ # </table>'
394
+ #
395
+ # page.find :table_row, 'A' => '1', 'B' => '2'
396
+ # page.find :table_row, 'A' => '3', 'B' => '4'
397
+ # ```
398
+ #
399
+ # * **:frame** - Find frame/iframe elements
400
+ # * Locator: Match id, {Capybara.configure test_id} attribute, or name
401
+ # * Filters:
402
+ # * :name (String) - Match name attribute
403
+ #
404
+ # ```ruby
405
+ # page.html # => '<iframe id="embed_frame" name="embed" src="https://example.com/embed"></iframe>'
406
+ #
407
+ # page.find :frame, 'embed_frame'
408
+ # page.find :frame, 'embed'
409
+ # page.find :frame, name: 'embed'
410
+ # ```
411
+ #
412
+ # * **:element**
413
+ # * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
414
+ # * Filters:
415
+ # * :\<any> (String, Regexp) - Match on any specified element attribute
416
+ #
417
+ # ```ruby
418
+ # page.html # => '<button type="button" role="menuitemcheckbox" aria-checked="true">Check me</button>
419
+ #
420
+ # page.find :element, 'button'
421
+ # page.find :element, type: 'button', text: 'Check me'
422
+ # page.find :element, role: 'menuitemcheckbox'
423
+ # page.find :element, role: /checkbox/, 'aria-checked': 'true'
424
+ # ```
425
+ #
426
+ class Capybara::Selector; end # rubocop:disable Lint/EmptyClass
427
+
4
428
  Capybara::Selector::FilterSet.add(:_field) do
5
429
  node_filter(:checked, :boolean) { |node, value| !(value ^ node.checked?) }
6
430
  node_filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
7
431
  node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
8
- node_filter(:multiple, :boolean) { |node, value| !(value ^ node.multiple?) }
9
-
10
- expression_filter(:name) { |xpath, val| xpath[XPath.attr(:name) == val] }
11
- expression_filter(:placeholder) { |xpath, val| xpath[XPath.attr(:placeholder) == val] }
12
-
13
- describe do |checked: nil, unchecked: nil, disabled: nil, multiple: nil, **_options|
14
- desc, states = +"", []
15
- states << 'checked' if checked || (unchecked == false)
16
- states << 'not checked' if unchecked || (checked == false)
17
- states << 'disabled' if disabled == true
18
- states << 'not disabled' if disabled == false
19
- desc << " that is #{states.join(' and ')}" unless states.empty?
20
- desc << " with the multiple attribute" if multiple == true
21
- desc << " without the multiple attribute" if multiple == false
22
- desc
23
- end
24
- end
25
-
26
- # rubocop:disable Metrics/BlockLength
27
-
28
- Capybara.add_selector(:xpath) do
29
- xpath { |xpath| xpath }
30
- end
31
-
32
- Capybara.add_selector(:css) do
33
- css { |css| css }
34
- end
35
-
36
- Capybara.add_selector(:id) do
37
- xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
38
- end
39
-
40
- Capybara.add_selector(:field) do
41
- xpath do |locator, **options|
42
- xpath = XPath.descendant(:input, :textarea, :select)[!XPath.attr(:type).one_of('submit', 'image', 'hidden')]
43
- locate_field(xpath, locator, options)
44
- end
45
-
46
- expression_filter(:type) do |expr, type|
47
- type = type.to_s
48
- if %w[textarea select].include?(type)
49
- expr.self(type.to_sym)
50
- else
51
- expr[XPath.attr(:type) == type]
52
- end
53
- end
54
-
55
- filter_set(:_field) # checked/unchecked/disabled/multiple/name/placeholder
56
-
57
- node_filter(:readonly, :boolean) { |node, value| !(value ^ node.readonly?) }
58
- node_filter(:with) do |node, with|
59
- with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
60
- end
61
- describe do |type: nil, **options|
62
- desc = +""
63
- (expression_filters.keys - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.key?(ef) }
64
- desc << " of type #{type.inspect}" if type
65
- desc << " with value #{options[:with].to_s.inspect}" if options.key?(:with)
66
- desc
67
- end
68
- end
69
-
70
- Capybara.add_selector(:fieldset) do
71
- xpath(:legend) do |locator, legend: nil, **_options|
72
- xpath = XPath.descendant(:fieldset)
73
- xpath = xpath[(XPath.attr(:id) == locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
74
- xpath = xpath[XPath.child(:legend)[XPath.string.n.is(legend)]] if legend
75
- xpath
76
- end
77
- end
78
-
79
- Capybara.add_selector(:link) do
80
- xpath(:title, :alt) do |locator, href: true, enable_aria_label: false, alt: nil, title: nil, **_options|
81
- xpath = XPath.descendant(:a)
82
- xpath = xpath[
83
- case href
84
- when nil, false
85
- !XPath.attr(:href)
86
- when true
87
- XPath.attr(:href)
88
- when Regexp
89
- nil # needs to be handled in filter
90
- else
91
- XPath.attr(:href) == href.to_s
92
- end
93
- ]
94
-
95
- unless locator.nil?
96
- locator = locator.to_s
97
- matchers = [XPath.attr(:id) == locator,
98
- XPath.string.n.is(locator),
99
- XPath.attr(:title).is(locator),
100
- XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
101
- matchers << XPath.attr(:'aria-label').is(locator) if enable_aria_label
102
- xpath = xpath[matchers.reduce(:|)]
103
- end
104
-
105
- xpath = xpath[find_by_attr(:title, title)]
106
- xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt) == alt]] if alt
107
- xpath
108
- end
109
-
110
- node_filter(:href) do |node, href|
111
- # If not a Regexp it's been handled in the main XPath
112
- href.is_a?(Regexp) ? node[:href].match(href) : true
113
- end
114
-
115
- expression_filter(:download, valid_values: [true, false, String]) do |expr, download|
116
- mod = case download
117
- when true then XPath.attr(:download)
118
- when false then !XPath.attr(:download)
119
- when String then XPath.attr(:download) == download
120
- end
121
- expr[mod]
122
- end
123
-
124
- describe do |**options|
125
- desc = +""
126
- desc << " with href #{options[:href].inspect}" if options[:href]
127
- desc << " with no href attribute" if options.fetch(:href, true).nil?
128
- end
129
- end
130
-
131
- Capybara.add_selector(:button) do
132
- xpath(:value, :title, :type) do |locator, enable_aria_label: false, **options|
133
- input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
134
- btn_xpath = XPath.descendant(:button)
135
- image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type) == 'image']
136
-
137
- unless locator.nil?
138
- locator = locator.to_s
139
- locator_matches = XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)
140
- locator_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
141
-
142
- input_btn_xpath = input_btn_xpath[locator_matches]
143
-
144
- btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
145
-
146
- alt_matches = XPath.attr(:alt).is(locator)
147
- alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
148
- image_btn_xpath = image_btn_xpath[alt_matches]
149
- end
150
-
151
- res_xpath = input_btn_xpath.union(btn_xpath).union(image_btn_xpath)
152
-
153
- res_xpath = expression_filters.keys.inject(res_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
154
-
155
- res_xpath
156
- end
157
-
158
- node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
159
-
160
- describe do |disabled: nil, **options|
161
- desc = +""
162
- desc << " that is disabled" if disabled == true
163
- desc << describe_all_expression_filters(options)
164
- desc
165
- end
166
- end
167
-
168
- Capybara.add_selector(:link_or_button) do
169
- label "link or button"
170
- xpath do |locator, **options|
171
- self.class.all.values_at(:link, :button).map { |selector| selector.xpath.call(locator, options) }.reduce(:union)
172
- end
173
-
174
- node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == "a" || !(value ^ node.disabled?) }
175
-
176
- describe { |disabled: nil, **_options| " that is disabled" if disabled == true }
177
- end
178
-
179
- Capybara.add_selector(:fillable_field) do
180
- label "field"
181
-
182
- xpath do |locator, **options|
183
- xpath = XPath.descendant(:input, :textarea)[!XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
184
- locate_field(xpath, locator, options)
185
- end
186
-
187
- expression_filter(:type) do |expr, type|
188
- type = type.to_s
189
- if ['textarea'].include?(type)
190
- expr.self(type.to_sym)
191
- else
192
- expr[XPath.attr(:type) == type]
193
- end
194
- end
195
-
196
- filter_set(:_field, %i[disabled multiple name placeholder])
197
-
198
- node_filter(:with) do |node, with|
199
- with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
200
- end
201
-
202
- describe do |options|
203
- desc = +""
204
- desc << describe_all_expression_filters(options)
205
- desc << " with value #{options[:with].to_s.inspect}" if options.key?(:with)
206
- desc
207
- end
208
- end
209
-
210
- Capybara.add_selector(:radio_button) do
211
- label "radio button"
212
-
213
- xpath do |locator, **options|
214
- xpath = XPath.descendant(:input)[XPath.attr(:type) == 'radio']
215
- locate_field(xpath, locator, options)
216
- end
217
-
218
- filter_set(:_field, %i[checked unchecked disabled name])
219
-
220
- node_filter(:option) { |node, value| node.value == value.to_s }
221
-
222
- describe do |option: nil, **options|
223
- desc = +""
224
- desc << " with value #{option.inspect}" if option
225
- desc << describe_all_expression_filters(options)
226
- desc
227
- end
228
- end
229
-
230
- Capybara.add_selector(:checkbox) do
231
- xpath do |locator, **options|
232
- xpath = XPath.descendant(:input)[XPath.attr(:type) == 'checkbox']
233
- locate_field(xpath, locator, options)
234
- end
235
-
236
- filter_set(:_field, %i[checked unchecked disabled name])
237
-
238
- node_filter(:option) { |node, value| node.value == value.to_s }
239
-
240
- describe do |option: nil, **options|
241
- desc = +""
242
- desc << " with value #{option.inspect}" if option
243
- desc << describe_all_expression_filters(options)
244
- desc
245
- end
246
- end
247
-
248
- Capybara.add_selector(:select) do
249
- label "select box"
250
-
251
- xpath do |locator, **options|
252
- xpath = XPath.descendant(:select)
253
- locate_field(xpath, locator, options)
254
- end
255
-
256
- filter_set(:_field, %i[disabled multiple name placeholder])
257
-
258
- node_filter(:options) do |node, options|
259
- actual = if node.visible?
260
- node.all(:xpath, './/option', wait: false).map(&:text)
261
- else
262
- node.all(:xpath, './/option', visible: false, wait: false).map { |option| option.text(:all) }
263
- end
264
- options.sort == actual.sort
265
- end
266
-
267
- expression_filter(:with_options) do |expr, options|
268
- options.inject(expr) do |xpath, option|
269
- xpath[Capybara::Selector.all[:option].call(option)]
270
- end
271
- end
272
-
273
- node_filter(:selected) do |node, selected|
274
- actual = node.all(:xpath, './/option', visible: false, wait: false).select(&:selected?).map { |option| option.text(:all) }
275
- Array(selected).sort == actual.sort
276
- end
277
-
278
- node_filter(:with_selected) do |node, selected|
279
- actual = node.all(:xpath, './/option', visible: false, wait: false).select(&:selected?).map { |option| option.text(:all) }
280
- (Array(selected) - actual).empty?
281
- end
282
-
283
- describe do |options: nil, with_options: nil, selected: nil, with_selected: nil, **opts|
284
- desc = +""
285
- desc << " with options #{options.inspect}" if options
286
- desc << " with at least options #{with_options.inspect}" if with_options
287
- desc << " with #{selected.inspect} selected" if selected
288
- desc << " with at least #{with_selected.inspect} selected" if with_selected
289
- desc << describe_all_expression_filters(opts)
290
- desc
291
- end
292
- end
293
-
294
- Capybara.add_selector(:datalist_input) do
295
- label "input box with datalist completion"
296
-
297
- xpath do |locator, **options|
298
- xpath = XPath.descendant(:input)[XPath.attr(:list)]
299
- locate_field(xpath, locator, options)
300
- end
301
-
302
- filter_set(:_field, %i[disabled name placeholder])
303
-
304
- node_filter(:options) do |node, options|
305
- actual = node.find("//datalist[@id=#{node[:list]}]", visible: :all).all(:datalist_option, wait: false).map(&:value)
306
- options.sort == actual.sort
307
- end
308
-
309
- expression_filter(:with_options) do |expr, options|
310
- options.inject(expr) do |xpath, option|
311
- xpath[XPath.attr(:list) == XPath.anywhere(:datalist)[Capybara::Selector.all[:datalist_option].call(option)].attr(:id)]
432
+ node_filter(:valid, :boolean) { |node, value| node.evaluate_script('this.validity.valid') == value }
433
+ node_filter(:name) { |node, value| !value.is_a?(Regexp) || value.match?(node[:name]) }
434
+ node_filter(:placeholder) { |node, value| !value.is_a?(Regexp) || value.match?(node[:placeholder]) }
435
+ node_filter(:validation_message) do |node, msg|
436
+ vm = node[:validationMessage]
437
+ (msg.is_a?(Regexp) ? msg.match?(vm) : vm == msg.to_s).tap do |res|
438
+ add_error("Expected validation message to be #{msg.inspect} but was #{vm}") unless res
312
439
  end
313
440
  end
314
441
 
315
- describe do |options: nil, with_options: nil, **opts|
316
- desc = +""
317
- desc << " with options #{options.inspect}" if options
318
- desc << " with at least options #{with_options.inspect}" if with_options
319
- desc << describe_all_expression_filters(opts)
320
- desc
321
- end
322
- end
323
-
324
- Capybara.add_selector(:option) do
325
- xpath do |locator|
326
- xpath = XPath.descendant(:option)
327
- xpath = xpath[XPath.string.n.is(locator.to_s)] unless locator.nil?
328
- xpath
442
+ expression_filter(:name) do |xpath, val|
443
+ builder(xpath).add_attribute_conditions(name: val)
329
444
  end
330
-
331
- node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
332
- node_filter(:selected, :boolean) { |node, value| !(value ^ node.selected?) }
333
-
334
- describe do |**options|
335
- desc = +""
336
- desc << " that is#{' not' unless options[:disabled]} disabled" if options.key?(:disabled)
337
- desc << " that is#{' not' unless options[:selected]} selected" if options.key?(:selected)
338
- desc
339
- end
340
- end
341
-
342
- Capybara.add_selector(:datalist_option) do
343
- label "datalist option"
344
- visible(:all)
345
-
346
- xpath do |locator|
347
- xpath = XPath.descendant(:option)
348
- xpath = xpath[XPath.string.n.is(locator.to_s) | (XPath.attr(:value) == locator.to_s)] unless locator.nil?
349
- xpath
445
+ expression_filter(:placeholder) do |xpath, val|
446
+ builder(xpath).add_attribute_conditions(placeholder: val)
350
447
  end
448
+ expression_filter(:disabled) { |xpath, val| val ? xpath : xpath[~XPath.attr(:disabled)] }
449
+ expression_filter(:multiple) { |xpath, val| xpath[val ? XPath.attr(:multiple) : ~XPath.attr(:multiple)] }
351
450
 
352
- node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
353
-
354
- describe do |**options|
355
- desc = +""
356
- desc << " that is#{' not' unless options[:disabled]} disabled" if options.key?(:disabled)
357
- desc
358
- end
359
- end
360
-
361
- Capybara.add_selector(:file_field) do
362
- label "file field"
363
- xpath do |locator, options|
364
- xpath = XPath.descendant(:input)[XPath.attr(:type) == 'file']
365
- locate_field(xpath, locator, options)
366
- end
367
-
368
- filter_set(:_field, %i[disabled multiple name])
369
-
370
- describe do |**options|
371
- desc = +""
372
- desc << describe_all_expression_filters(options)
373
- desc
374
- end
375
- end
376
-
377
- Capybara.add_selector(:label) do
378
- label "label"
379
- xpath(:for) do |locator, options|
380
- xpath = XPath.descendant(:label)
381
- xpath = xpath[XPath.string.n.is(locator.to_s) | (XPath.attr(:id) == locator.to_s)] unless locator.nil?
382
- if options.key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
383
- with_attr = XPath.attr(:for) == options[:for].to_s
384
- labelable_elements = %i[button input keygen meter output progress select textarea]
385
- wrapped = !XPath.attr(:for) &
386
- XPath.descendant(*labelable_elements)[XPath.attr(:id) == options[:for].to_s]
387
- xpath = xpath[with_attr | wrapped]
388
- end
389
- xpath
390
- end
391
-
392
- node_filter(:for) do |node, field_or_value|
393
- if field_or_value.is_a? Capybara::Node::Element
394
- if node[:for]
395
- field_or_value[:id] == node[:for]
396
- else
397
- field_or_value.find_xpath('./ancestor::label[1]').include? node.base
398
- end
399
- else
400
- true # Non element values were handled through the expression filter
401
- end
402
- end
403
-
404
- describe do |**options|
405
- desc = +""
406
- desc << " for #{options[:for]}" if options[:for]
407
- desc
408
- end
409
- end
410
-
411
- Capybara.add_selector(:table) do
412
- xpath(:caption) do |locator, caption: nil, **_options|
413
- xpath = XPath.descendant(:table)
414
- xpath = xpath[(XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
415
- xpath = xpath[XPath.descendant(:caption) == caption] if caption
416
- xpath
417
- end
418
-
419
- describe do |caption: nil, **_options|
420
- desc = +""
421
- desc << " with caption #{caption}" if caption
422
- desc
423
- end
424
- end
425
-
426
- Capybara.add_selector(:frame) do
427
- xpath(:name) do |locator, **options|
428
- xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
429
- xpath = xpath[(XPath.attr(:id) == locator.to_s) | (XPath.attr(:name) == locator.to_s)] unless locator.nil?
430
- xpath = expression_filters.keys.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
431
- xpath
432
- end
433
-
434
- describe do |name: nil, **_options|
435
- desc = +""
451
+ describe(:expression_filters) do |name: nil, placeholder: nil, disabled: nil, multiple: nil, **|
452
+ desc = +''
453
+ desc << ' that is not disabled' if disabled == false
436
454
  desc << " with name #{name}" if name
455
+ desc << " with placeholder #{placeholder}" if placeholder
456
+ desc << ' with the multiple attribute' if multiple == true
457
+ desc << ' without the multiple attribute' if multiple == false
437
458
  desc
438
459
  end
439
- end
440
-
441
- Capybara.add_selector(:element) do
442
- xpath do |locator, **_options|
443
- XPath.descendant((locator || '@').to_sym)
444
- end
445
460
 
446
- expression_filter(:attributes, matcher: /.+/) do |xpath, name, val|
447
- case val
448
- when Regexp
449
- xpath
450
- when XPath::Expression
451
- xpath[XPath.attr(name)[val]]
452
- else
453
- xpath[XPath.attr(name.to_sym) == val]
454
- end
455
- end
456
-
457
- node_filter(:attributes, matcher: /.+/) do |node, name, val|
458
- val.is_a?(Regexp) ? node[name] =~ val : true
459
- end
460
-
461
- describe do |**options|
462
- desc = +""
463
- desc << describe_all_expression_filters(options)
461
+ describe(:node_filters) do |checked: nil, unchecked: nil, disabled: nil, valid: nil, validation_message: nil, **|
462
+ desc, states = +'', []
463
+ states << 'checked' if checked || (unchecked == false)
464
+ states << 'not checked' if unchecked || (checked == false)
465
+ states << 'disabled' if disabled == true
466
+ desc << " that is #{states.join(' and ')}" unless states.empty?
467
+ desc << ' that is valid' if valid == true
468
+ desc << ' that is invalid' if valid == false
469
+ desc << " with validation message #{validation_message.to_s.inspect}" if validation_message
464
470
  desc
465
471
  end
466
472
  end
467
- # rubocop:enable Metrics/BlockLength
473
+
474
+ require 'capybara/selector/definition/xpath'
475
+ require 'capybara/selector/definition/css'
476
+ require 'capybara/selector/definition/id'
477
+ require 'capybara/selector/definition/field'
478
+ require 'capybara/selector/definition/fieldset'
479
+ require 'capybara/selector/definition/link'
480
+ require 'capybara/selector/definition/button'
481
+ require 'capybara/selector/definition/link_or_button'
482
+ require 'capybara/selector/definition/fillable_field'
483
+ require 'capybara/selector/definition/radio_button'
484
+ require 'capybara/selector/definition/checkbox'
485
+ require 'capybara/selector/definition/select'
486
+ require 'capybara/selector/definition/datalist_input'
487
+ require 'capybara/selector/definition/option'
488
+ require 'capybara/selector/definition/datalist_option'
489
+ require 'capybara/selector/definition/file_field'
490
+ require 'capybara/selector/definition/label'
491
+ require 'capybara/selector/definition/table'
492
+ require 'capybara/selector/definition/table_row'
493
+ require 'capybara/selector/definition/frame'
494
+ require 'capybara/selector/definition/element'