capybara 3.3.0 → 3.40.0

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