capybara 2.7.0 → 3.35.3

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 (318) hide show
  1. checksums.yaml +5 -5
  2. data/.yardopts +1 -0
  3. data/History.md +1147 -11
  4. data/License.txt +1 -1
  5. data/README.md +252 -131
  6. data/lib/capybara/config.rb +92 -0
  7. data/lib/capybara/cucumber.rb +3 -3
  8. data/lib/capybara/driver/base.rb +52 -21
  9. data/lib/capybara/driver/node.rb +48 -14
  10. data/lib/capybara/dsl.rb +16 -9
  11. data/lib/capybara/helpers.rb +72 -81
  12. data/lib/capybara/minitest/spec.rb +267 -0
  13. data/lib/capybara/minitest.rb +385 -0
  14. data/lib/capybara/node/actions.rb +337 -89
  15. data/lib/capybara/node/base.rb +50 -32
  16. data/lib/capybara/node/document.rb +19 -3
  17. data/lib/capybara/node/document_matchers.rb +22 -24
  18. data/lib/capybara/node/element.rb +388 -125
  19. data/lib/capybara/node/finders.rb +231 -121
  20. data/lib/capybara/node/matchers.rb +503 -217
  21. data/lib/capybara/node/simple.rb +64 -27
  22. data/lib/capybara/queries/ancestor_query.rb +27 -0
  23. data/lib/capybara/queries/base_query.rb +87 -11
  24. data/lib/capybara/queries/current_path_query.rb +24 -24
  25. data/lib/capybara/queries/match_query.rb +15 -10
  26. data/lib/capybara/queries/selector_query.rb +675 -81
  27. data/lib/capybara/queries/sibling_query.rb +26 -0
  28. data/lib/capybara/queries/style_query.rb +45 -0
  29. data/lib/capybara/queries/text_query.rb +88 -20
  30. data/lib/capybara/queries/title_query.rb +9 -11
  31. data/lib/capybara/rack_test/browser.rb +63 -39
  32. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  33. data/lib/capybara/rack_test/driver.rb +26 -16
  34. data/lib/capybara/rack_test/errors.rb +6 -0
  35. data/lib/capybara/rack_test/form.rb +73 -58
  36. data/lib/capybara/rack_test/node.rb +187 -67
  37. data/lib/capybara/rails.rb +4 -8
  38. data/lib/capybara/registration_container.rb +44 -0
  39. data/lib/capybara/registrations/drivers.rb +42 -0
  40. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  41. data/lib/capybara/registrations/servers.rb +45 -0
  42. data/lib/capybara/result.rb +142 -14
  43. data/lib/capybara/rspec/features.rb +17 -42
  44. data/lib/capybara/rspec/matcher_proxies.rb +82 -0
  45. data/lib/capybara/rspec/matchers/base.rb +111 -0
  46. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  47. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  48. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  49. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  50. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  51. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  52. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  53. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  54. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  55. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  56. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  57. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  58. data/lib/capybara/rspec/matchers.rb +143 -244
  59. data/lib/capybara/rspec.rb +10 -12
  60. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  61. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  62. data/lib/capybara/selector/css.rb +102 -0
  63. data/lib/capybara/selector/definition/button.rb +63 -0
  64. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  65. data/lib/capybara/selector/definition/css.rb +10 -0
  66. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  67. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  68. data/lib/capybara/selector/definition/element.rb +28 -0
  69. data/lib/capybara/selector/definition/field.rb +40 -0
  70. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  71. data/lib/capybara/selector/definition/file_field.rb +13 -0
  72. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  73. data/lib/capybara/selector/definition/frame.rb +17 -0
  74. data/lib/capybara/selector/definition/id.rb +6 -0
  75. data/lib/capybara/selector/definition/label.rb +62 -0
  76. data/lib/capybara/selector/definition/link.rb +54 -0
  77. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  78. data/lib/capybara/selector/definition/option.rb +27 -0
  79. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  80. data/lib/capybara/selector/definition/select.rb +81 -0
  81. data/lib/capybara/selector/definition/table.rb +109 -0
  82. data/lib/capybara/selector/definition/table_row.rb +21 -0
  83. data/lib/capybara/selector/definition/xpath.rb +5 -0
  84. data/lib/capybara/selector/definition.rb +278 -0
  85. data/lib/capybara/selector/filter.rb +3 -46
  86. data/lib/capybara/selector/filter_set.rb +124 -0
  87. data/lib/capybara/selector/filters/base.rb +77 -0
  88. data/lib/capybara/selector/filters/expression_filter.rb +22 -0
  89. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  90. data/lib/capybara/selector/filters/node_filter.rb +31 -0
  91. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  92. data/lib/capybara/selector/selector.rb +155 -0
  93. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  94. data/lib/capybara/selector.rb +232 -369
  95. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  96. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  98. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  99. data/lib/capybara/selenium/driver.rb +380 -142
  100. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  101. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  102. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  103. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  104. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  105. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  106. data/lib/capybara/selenium/extensions/find.rb +110 -0
  107. data/lib/capybara/selenium/extensions/html5_drag.rb +228 -0
  108. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  109. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  110. data/lib/capybara/selenium/logger_suppressor.rb +40 -0
  111. data/lib/capybara/selenium/node.rb +528 -97
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +137 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +104 -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/action_pauser.rb +26 -0
  118. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  119. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  120. data/lib/capybara/selenium/patches/logs.rb +45 -0
  121. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  122. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  123. data/lib/capybara/server/animation_disabler.rb +63 -0
  124. data/lib/capybara/server/checker.rb +44 -0
  125. data/lib/capybara/server/middleware.rb +71 -0
  126. data/lib/capybara/server.rb +74 -71
  127. data/lib/capybara/session/config.rb +126 -0
  128. data/lib/capybara/session/matchers.rb +44 -27
  129. data/lib/capybara/session.rb +500 -297
  130. data/lib/capybara/spec/fixtures/no_extension +1 -0
  131. data/lib/capybara/spec/public/jquery.js +5 -5
  132. data/lib/capybara/spec/public/offset.js +6 -0
  133. data/lib/capybara/spec/public/test.js +168 -14
  134. data/lib/capybara/spec/session/accept_alert_spec.rb +37 -14
  135. data/lib/capybara/spec/session/accept_confirm_spec.rb +7 -6
  136. data/lib/capybara/spec/session/accept_prompt_spec.rb +38 -10
  137. data/lib/capybara/spec/session/all_spec.rb +179 -59
  138. data/lib/capybara/spec/session/ancestor_spec.rb +88 -0
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +140 -0
  140. data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
  141. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  142. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  143. data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
  144. data/lib/capybara/spec/session/assert_title_spec.rb +93 -0
  145. data/lib/capybara/spec/session/attach_file_spec.rb +154 -48
  146. data/lib/capybara/spec/session/body_spec.rb +12 -13
  147. data/lib/capybara/spec/session/check_spec.rb +168 -41
  148. data/lib/capybara/spec/session/choose_spec.rb +75 -23
  149. data/lib/capybara/spec/session/click_button_spec.rb +243 -175
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +57 -32
  151. data/lib/capybara/spec/session/click_link_spec.rb +100 -53
  152. data/lib/capybara/spec/session/current_scope_spec.rb +11 -10
  153. data/lib/capybara/spec/session/current_url_spec.rb +61 -35
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +7 -7
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +5 -4
  156. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +13 -6
  157. data/lib/capybara/spec/session/element/match_css_spec.rb +21 -7
  158. data/lib/capybara/spec/session/element/match_xpath_spec.rb +9 -7
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +91 -34
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +45 -3
  162. data/lib/capybara/spec/session/execute_script_spec.rb +24 -4
  163. data/lib/capybara/spec/session/fill_in_spec.rb +166 -64
  164. data/lib/capybara/spec/session/find_button_spec.rb +37 -18
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +10 -9
  166. data/lib/capybara/spec/session/find_field_spec.rb +57 -34
  167. data/lib/capybara/spec/session/find_link_spec.rb +47 -10
  168. data/lib/capybara/spec/session/find_spec.rb +290 -144
  169. data/lib/capybara/spec/session/first_spec.rb +91 -48
  170. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  171. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  172. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +116 -0
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +112 -0
  174. data/lib/capybara/spec/session/go_back_spec.rb +3 -2
  175. data/lib/capybara/spec/session/go_forward_spec.rb +3 -2
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  177. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  178. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  179. data/lib/capybara/spec/session/has_button_spec.rb +76 -19
  180. data/lib/capybara/spec/session/has_css_spec.rb +277 -131
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +98 -26
  182. data/lib/capybara/spec/session/has_field_spec.rb +177 -107
  183. data/lib/capybara/spec/session/has_link_spec.rb +13 -12
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +78 -0
  185. data/lib/capybara/spec/session/has_select_spec.rb +191 -95
  186. data/lib/capybara/spec/session/has_selector_spec.rb +128 -64
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +172 -5
  189. data/lib/capybara/spec/session/has_text_spec.rb +126 -60
  190. data/lib/capybara/spec/session/has_title_spec.rb +35 -12
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +74 -53
  192. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +3 -2
  193. data/lib/capybara/spec/session/html_spec.rb +14 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  195. data/lib/capybara/spec/session/node_spec.rb +1028 -131
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  197. data/lib/capybara/spec/session/refresh_spec.rb +34 -0
  198. data/lib/capybara/spec/session/reset_session_spec.rb +75 -34
  199. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +2 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +3 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +11 -15
  202. data/lib/capybara/spec/session/save_page_spec.rb +42 -55
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +16 -14
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  206. data/lib/capybara/spec/session/select_spec.rb +112 -85
  207. data/lib/capybara/spec/session/selectors_spec.rb +71 -8
  208. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  209. data/lib/capybara/spec/session/text_spec.rb +38 -23
  210. data/lib/capybara/spec/session/title_spec.rb +17 -5
  211. data/lib/capybara/spec/session/uncheck_spec.rb +71 -12
  212. data/lib/capybara/spec/session/unselect_spec.rb +44 -43
  213. data/lib/capybara/spec/session/visit_spec.rb +99 -32
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +33 -29
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +5 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +5 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +39 -30
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +17 -10
  219. data/lib/capybara/spec/session/window/window_spec.rb +121 -73
  220. data/lib/capybara/spec/session/window/windows_spec.rb +12 -10
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +52 -82
  222. data/lib/capybara/spec/session/within_spec.rb +76 -43
  223. data/lib/capybara/spec/spec_helper.rb +67 -33
  224. data/lib/capybara/spec/test_app.rb +85 -36
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/buttons.erb +1 -1
  227. data/lib/capybara/spec/views/fieldsets.erb +1 -1
  228. data/lib/capybara/spec/views/form.erb +227 -20
  229. data/lib/capybara/spec/views/frame_child.erb +10 -2
  230. data/lib/capybara/spec/views/frame_one.erb +2 -1
  231. data/lib/capybara/spec/views/frame_parent.erb +2 -2
  232. data/lib/capybara/spec/views/frame_two.erb +1 -1
  233. data/lib/capybara/spec/views/header_links.erb +1 -1
  234. data/lib/capybara/spec/views/host_links.erb +1 -1
  235. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  236. data/lib/capybara/spec/views/obscured.erb +47 -0
  237. data/lib/capybara/spec/views/offset.erb +32 -0
  238. data/lib/capybara/spec/views/path.erb +1 -1
  239. data/lib/capybara/spec/views/popup_one.erb +1 -1
  240. data/lib/capybara/spec/views/popup_two.erb +1 -1
  241. data/lib/capybara/spec/views/postback.erb +1 -1
  242. data/lib/capybara/spec/views/react.erb +45 -0
  243. data/lib/capybara/spec/views/scroll.erb +20 -0
  244. data/lib/capybara/spec/views/spatial.erb +31 -0
  245. data/lib/capybara/spec/views/tables.erb +69 -2
  246. data/lib/capybara/spec/views/with_animation.erb +82 -0
  247. data/lib/capybara/spec/views/with_base_tag.erb +1 -1
  248. data/lib/capybara/spec/views/with_count.erb +1 -1
  249. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  250. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  251. data/lib/capybara/spec/views/with_hover.erb +7 -1
  252. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  253. data/lib/capybara/spec/views/with_html.erb +100 -10
  254. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  255. data/lib/capybara/spec/views/with_html_entities.erb +1 -1
  256. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  257. data/lib/capybara/spec/views/with_js.erb +49 -3
  258. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  259. data/lib/capybara/spec/views/with_namespace.erb +20 -0
  260. data/lib/capybara/spec/views/with_scope.erb +1 -1
  261. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  262. data/lib/capybara/spec/views/with_simple_html.erb +1 -1
  263. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  264. data/lib/capybara/spec/views/with_title.erb +1 -1
  265. data/lib/capybara/spec/views/with_unload_alert.erb +3 -1
  266. data/lib/capybara/spec/views/with_windows.erb +7 -1
  267. data/lib/capybara/spec/views/within_frames.erb +6 -3
  268. data/lib/capybara/version.rb +2 -1
  269. data/lib/capybara/window.rb +39 -21
  270. data/lib/capybara.rb +208 -186
  271. data/spec/basic_node_spec.rb +52 -39
  272. data/spec/capybara_spec.rb +72 -50
  273. data/spec/css_builder_spec.rb +101 -0
  274. data/spec/css_splitter_spec.rb +38 -0
  275. data/spec/dsl_spec.rb +81 -61
  276. data/spec/filter_set_spec.rb +46 -0
  277. data/spec/fixtures/capybara.csv +1 -0
  278. data/spec/fixtures/certificate.pem +25 -0
  279. data/spec/fixtures/key.pem +27 -0
  280. data/spec/fixtures/selenium_driver_rspec_failure.rb +7 -3
  281. data/spec/fixtures/selenium_driver_rspec_success.rb +7 -3
  282. data/spec/minitest_spec.rb +164 -0
  283. data/spec/minitest_spec_spec.rb +162 -0
  284. data/spec/per_session_config_spec.rb +68 -0
  285. data/spec/rack_test_spec.rb +189 -96
  286. data/spec/regexp_dissassembler_spec.rb +250 -0
  287. data/spec/result_spec.rb +143 -13
  288. data/spec/rspec/features_spec.rb +38 -32
  289. data/spec/rspec/scenarios_spec.rb +9 -7
  290. data/spec/rspec/shared_spec_matchers.rb +959 -0
  291. data/spec/rspec/views_spec.rb +9 -3
  292. data/spec/rspec_matchers_spec.rb +62 -0
  293. data/spec/rspec_spec.rb +127 -30
  294. data/spec/sauce_spec_chrome.rb +43 -0
  295. data/spec/selector_spec.rb +458 -37
  296. data/spec/selenium_spec_chrome.rb +196 -9
  297. data/spec/selenium_spec_chrome_remote.rb +100 -0
  298. data/spec/selenium_spec_edge.rb +47 -0
  299. data/spec/selenium_spec_firefox.rb +210 -0
  300. data/spec/selenium_spec_firefox_remote.rb +80 -0
  301. data/spec/selenium_spec_ie.rb +150 -0
  302. data/spec/selenium_spec_safari.rb +148 -0
  303. data/spec/server_spec.rb +200 -101
  304. data/spec/session_spec.rb +91 -0
  305. data/spec/shared_selenium_node.rb +83 -0
  306. data/spec/shared_selenium_session.rb +558 -0
  307. data/spec/spec_helper.rb +94 -2
  308. data/spec/xpath_builder_spec.rb +93 -0
  309. metadata +420 -60
  310. data/lib/capybara/query.rb +0 -7
  311. data/lib/capybara/spec/session/assert_current_path.rb +0 -60
  312. data/lib/capybara/spec/session/assert_selector.rb +0 -148
  313. data/lib/capybara/spec/session/assert_text.rb +0 -196
  314. data/lib/capybara/spec/session/assert_title.rb +0 -70
  315. data/lib/capybara/spec/session/source_spec.rb +0 -0
  316. data/lib/capybara/spec/session/within_frame_spec.rb +0 -53
  317. data/spec/rspec/matchers_spec.rb +0 -827
  318. data/spec/selenium_spec.rb +0 -151
@@ -1,108 +1,179 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Node
4
5
  module Finders
5
-
6
6
  ##
7
7
  #
8
- # Find an {Capybara::Node::Element} based on the given arguments. +find+ will raise an error if the element
8
+ # Find an {Capybara::Node::Element} based on the given arguments. {#find} will raise an error if the element
9
9
  # is not found.
10
10
  #
11
- # @!macro waiting_behavior
12
- # If the driver is capable of executing JavaScript, +$0+ will wait for a set amount of time
13
- # and continuously retry finding the element until either the element is found or the time
14
- # expires. The length of time +find+ will wait is controlled through {Capybara.default_max_wait_time}
15
- # and defaults to 2 seconds.
16
- # @option options [false, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
11
+ # page.find('#foo').find('.bar')
12
+ # page.find(:xpath, './/div[contains(., "bar")]')
13
+ # page.find('li', text: 'Quox').click_link('Delete')
17
14
  #
18
- # +find+ takes the same options as +all+.
15
+ # @param (see #all)
19
16
  #
20
- # page.find('#foo').find('.bar')
21
- # page.find(:xpath, '//div[contains(., "bar")]')
22
- # page.find('li', :text => 'Quox').click_link('Delete')
17
+ # @macro waiting_behavior
18
+ #
19
+ # @!macro system_filters
20
+ # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
21
+ # @option options [String, Boolean] exact_text
22
+ # When String the elements contained text must match exactly, when Boolean controls whether the `text` option must match exactly.
23
+ # Defaults to {Capybara.configure exact_text}.
24
+ # @option options [Boolean] normalize_ws
25
+ # Whether the `text`/`exact_text` options are compared against elment text with whitespace normalized or as returned by the driver.
26
+ # Defaults to {Capybara.configure default_normalize_ws}.
27
+ # @option options [Boolean, Symbol] visible
28
+ # Only find elements with the specified visibility. Defaults to behavior indicated by {Capybara.configure ignore_hidden_elements}.
29
+ # * true - only finds visible elements.
30
+ # * false - finds invisible _and_ visible elements.
31
+ # * :all - same as false; finds visible and invisible elements.
32
+ # * :hidden - only finds invisible elements.
33
+ # * :visible - same as true; only finds visible elements.
34
+ # @option options [Boolean] obscured Only find elements with the specified obscured state:
35
+ # * true - only find elements whose centerpoint is not in the viewport or is obscured by another non-descendant element.
36
+ # * false - only find elements whose centerpoint is in the viewport and is not obscured by other non-descendant elements.
37
+ # @option options [String, Regexp] id Only find elements with an id that matches the value passed
38
+ # @option options [String, Array<String>, Regexp] class Only find elements with matching class/classes.
39
+ # * Absence of a class can be checked by prefixing the class name with `!`
40
+ # * If you need to check for existence of a class name that starts with `!` then prefix with `!!`
41
+ #
42
+ # class:['a', '!b', '!!!c'] # limit to elements with class 'a' and '!c' but not class 'b'
43
+ #
44
+ # @option options [String, Regexp, Hash] style Only find elements with matching style. String and Regexp will be checked against text of the elements `style` attribute, while a Hash will be compared against the elements full style
45
+ # @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially. Defaults to {Capybara.configure exact}.
46
+ # @option options [Symbol] match The matching strategy to use. Defaults to {Capybara.configure match}.
47
+ #
48
+ # @return [Capybara::Node::Element] The found element
49
+ # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
23
50
  #
24
- # @param (see Capybara::Node::Finders#all)
51
+ def find(*args, **options, &optional_filter_block)
52
+ options[:session_options] = session_options
53
+ synced_resolve Capybara::Queries::SelectorQuery.new(*args, **options, &optional_filter_block)
54
+ end
55
+
56
+ ##
57
+ #
58
+ # Find an {Capybara::Node::Element} based on the given arguments that is also an ancestor of the element called on.
59
+ # {#ancestor} will raise an error if the element is not found.
60
+ #
61
+ # {#ancestor} takes the same options as {#find}.
25
62
  #
26
- # @option options [Boolean] match The matching strategy to use.
63
+ # element.ancestor('#foo').find('.bar')
64
+ # element.ancestor(:xpath, './/div[contains(., "bar")]')
65
+ # element.ancestor('ul', text: 'Quox').click_link('Delete')
66
+ #
67
+ # @param (see #find)
68
+ #
69
+ # @macro waiting_behavior
27
70
  #
28
71
  # @return [Capybara::Node::Element] The found element
29
72
  # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
30
73
  #
31
- def find(*args)
32
- query = Capybara::Queries::SelectorQuery.new(*args)
33
- synchronize(query.wait) do
34
- if query.match == :smart or query.match == :prefer_exact
35
- result = query.resolve_for(self, true)
36
- result = query.resolve_for(self, false) if result.size == 0 && !query.exact?
37
- else
38
- result = query.resolve_for(self)
39
- end
40
- if query.match == :one or query.match == :smart and result.size > 1
41
- raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
42
- end
43
- if result.size == 0
44
- raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
45
- end
46
- result.first
47
- end.tap(&:allow_reload!)
74
+ def ancestor(*args, **options, &optional_filter_block)
75
+ options[:session_options] = session_options
76
+ synced_resolve Capybara::Queries::AncestorQuery.new(*args, **options, &optional_filter_block)
48
77
  end
49
78
 
50
79
  ##
51
80
  #
52
- # Find a form field on the page. The field can be found by its name, id or label text.
81
+ # Find an {Capybara::Node::Element} based on the given arguments that is also a sibling of the element called on.
82
+ # {#sibling} will raise an error if the element is not found.
83
+ #
84
+ # {#sibling} takes the same options as {#find}.
85
+ #
86
+ # element.sibling('#foo').find('.bar')
87
+ # element.sibling(:xpath, './/div[contains(., "bar")]')
88
+ # element.sibling('ul', text: 'Quox').click_link('Delete')
89
+ #
90
+ # @param (see #find)
53
91
  #
54
92
  # @macro waiting_behavior
55
93
  #
56
- # @param [String] locator Which field to find
57
- #
58
- # @option options [Boolean] checked Match checked field?
59
- # @option options [Boolean] unchecked Match unchecked field?
60
- # @option options [Boolean, Symbol] disabled (false) Match disabled field?
61
- # * true - only finds a disabled field
62
- # * false - only finds an enabled field
63
- # * :all - finds either an enabled or disabled field
64
- # @option options [Boolean] readonly Match readonly field?
65
- # @option options [String] with Value of field to match on
66
- # @option options [String] type Type of field to match on
94
+ # @return [Capybara::Node::Element] The found element
95
+ # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
96
+ #
97
+ def sibling(*args, **options, &optional_filter_block)
98
+ options[:session_options] = session_options
99
+ synced_resolve Capybara::Queries::SiblingQuery.new(*args, **options, &optional_filter_block)
100
+ end
101
+
102
+ ##
103
+ #
104
+ # Find a form field on the page. The field can be found by its name, id or label text.
105
+ #
106
+ # @overload find_field([locator], **options)
107
+ # @param [String] locator name, id, {Capybara.configure test_id} attribute, placeholder or text of associated label element
108
+ #
109
+ # @macro waiting_behavior
110
+ #
111
+ #
112
+ # @option options [Boolean] checked Match checked field?
113
+ # @option options [Boolean] unchecked Match unchecked field?
114
+ # @option options [Boolean, Symbol] disabled (false) Match disabled field?
115
+ # * true - only finds a disabled field
116
+ # * false - only finds an enabled field
117
+ # * :all - finds either an enabled or disabled field
118
+ # @option options [Boolean] readonly Match readonly field?
119
+ # @option options [String, Regexp] with Value of field to match on
120
+ # @option options [String] type Type of field to match on
121
+ # @option options [Boolean] multiple Match fields that can have multiple values?
122
+ # @option options [String, Regexp] id Match fields that match the id attribute
123
+ # @option options [String] name Match fields that match the name attribute
124
+ # @option options [String] placeholder Match fields that match the placeholder attribute
125
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) passed
67
126
  # @return [Capybara::Node::Element] The found element
68
127
  #
69
- def find_field(locator, options={})
70
- find(:field, locator, options)
128
+ def find_field(locator = nil, **options, &optional_filter_block)
129
+ find(:field, locator, **options, &optional_filter_block)
71
130
  end
72
- alias_method :field_labeled, :find_field
73
131
 
74
132
  ##
75
133
  #
76
134
  # Find a link on the page. The link can be found by its id or text.
77
135
  #
78
- # @macro waiting_behavior
136
+ # @overload find_link([locator], **options)
137
+ # @param [String] locator id, {Capybara.configure test_id} attribute, title, text, or alt of enclosed img element
138
+ #
139
+ # @macro waiting_behavior
79
140
  #
80
- # @param [String] locator Which link to find
81
- # @option options [String,Regexp] href Value to match against the links href
141
+ # @option options [String,Regexp,nil] href Value to match against the links href, if `nil` finds link placeholders (`<a>` elements with no href attribute), if `false` ignores the href
142
+ # @option options [String, Regexp] id Match links with the id provided
143
+ # @option options [String] title Match links with the title provided
144
+ # @option options [String] alt Match links with a contained img element whose alt matches
145
+ # @option options [String, Array<String>, Regexp] class Match links that match the class(es) provided
82
146
  # @return [Capybara::Node::Element] The found element
83
147
  #
84
- def find_link(locator, options={})
85
- find(:link, locator, options)
148
+ def find_link(locator = nil, **options, &optional_filter_block)
149
+ find(:link, locator, **options, &optional_filter_block)
86
150
  end
87
151
 
88
152
  ##
89
153
  #
90
154
  # Find a button on the page.
91
- # This can be any \<input> element of type submit, reset, image, button or it can be a
92
- # \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
93
- # by their text content, and image \<input> elements by their alt attribute
94
-
95
- # @macro waiting_behavior
96
- #
97
- # @param [String] locator Which button to find
98
- # @option options [Boolean, Symbol] disabled (false) Match disabled button?
99
- # * true - only finds a disabled button
100
- # * false - only finds an enabled button
101
- # * :all - finds either an enabled or disabled button
155
+ # This can be any `<input>` element of type submit, reset, image, button or it can be a
156
+ # `<button>` element. All buttons can be found by their id, name, {Capybara.configure test_id} attribute, value, or title.
157
+ # `<button>` elements can also be found by their text content, and image `<input>` elements by their alt attribute.
158
+ #
159
+ # @overload find_button([locator], **options)
160
+ # @param [String] locator id, name, {Capybara.configure test_id} attribute, value, title, text content, alt of image
161
+ #
162
+ # @macro waiting_behavior
163
+ #
164
+ # @option options [Boolean, Symbol] disabled (false) Match disabled button?
165
+ # * true - only finds a disabled button
166
+ # * false - only finds an enabled button
167
+ # * :all - finds either an enabled or disabled button
168
+ # @option options [String, Regexp] id Match buttons with the id provided
169
+ # @option options [String] name Match buttons with the name provided
170
+ # @option options [String] title Match buttons with the title provided
171
+ # @option options [String] value Match buttons with the value provided
172
+ # @option options [String, Array<String>, Regexp] class Match buttons that match the class(es) provided
102
173
  # @return [Capybara::Node::Element] The found element
103
174
  #
104
- def find_button(locator, options={})
105
- find(:button, locator, options)
175
+ def find_button(locator = nil, **options, &optional_filter_block)
176
+ find(:button, locator, **options, &optional_filter_block)
106
177
  end
107
178
 
108
179
  ##
@@ -111,15 +182,16 @@ module Capybara
111
182
  #
112
183
  # @macro waiting_behavior
113
184
  #
114
- # @param [String] id Which element to find
185
+ # @param [String] id id of element
115
186
  #
116
187
  # @return [Capybara::Node::Element] The found element
117
188
  #
118
- def find_by_id(id, options={})
119
- find(:id, id, options)
189
+ def find_by_id(id, **options, &optional_filter_block)
190
+ find(:id, id, **options, &optional_filter_block)
120
191
  end
121
192
 
122
193
  ##
194
+ # @!method all([kind = Capybara.default_selector], locator = nil, **options)
123
195
  #
124
196
  # Find all elements on the page matching the given selector
125
197
  # and options.
@@ -129,60 +201,68 @@ module Capybara
129
201
  # following statements are equivalent:
130
202
  #
131
203
  # page.all(:css, 'a#person_123')
132
- # page.all(:xpath, '//a[@id="person_123"]')
133
- #
204
+ # page.all(:xpath, './/a[@id="person_123"]')
134
205
  #
135
206
  # If the type of selector is left out, Capybara uses
136
- # {Capybara.default_selector}. It's set to :css by default.
207
+ # {Capybara.configure default_selector}. It's set to `:css` by default.
137
208
  #
138
209
  # page.all("a#person_123")
139
210
  #
140
211
  # Capybara.default_selector = :xpath
141
- # page.all('//a[@id="person_123"]')
212
+ # page.all('.//a[@id="person_123"]')
142
213
  #
143
214
  # The set of found elements can further be restricted by specifying
144
215
  # options. It's possible to select elements by their text or visibility:
145
216
  #
146
- # page.all('a', :text => 'Home')
147
- # page.all('#menu li', :visible => true)
148
- #
149
- # By default if no elements are found, an empty array is returned;
150
- # however, expectations can be set on the number of elements to be found which
151
- # will trigger Capybara's waiting behavior for the expectations to match.The
152
- # expectations can be set using
217
+ # page.all('a', text: 'Home')
218
+ # page.all('#menu li', visible: true)
153
219
  #
154
- # page.assert_selector('p#foo', :count => 4)
155
- # page.assert_selector('p#foo', :maximum => 10)
156
- # page.assert_selector('p#foo', :minimum => 1)
157
- # page.assert_selector('p#foo', :between => 1..10)
220
+ # By default Capybara's waiting behavior will wait up to {Capybara.configure default_max_wait_time}
221
+ # seconds for matching elements to be available and then return an empty result if none
222
+ # are available. It is possible to set expectations on the number of results located and
223
+ # Capybara will raise an exception if the number of elements located don't satisfy the
224
+ # specified conditions. The expectations can be set using:
158
225
  #
159
- # See {Capybara::Helpers#matches_count?} for additional information about
160
- # count matching.
226
+ # page.assert_selector('p#foo', count: 4)
227
+ # page.assert_selector('p#foo', maximum: 10)
228
+ # page.assert_selector('p#foo', minimum: 1)
229
+ # page.assert_selector('p#foo', between: 1..10)
161
230
  #
162
- # @overload all([kind], locator, options)
163
- # @param [:css, :xpath] kind The type of selector
164
- # @param [String] locator The selector
165
- # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
166
- # @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
167
- # * true - only finds visible elements.
168
- # * false - finds invisible _and_ visible elements.
169
- # * :all - same as false; finds visible and invisible elements.
170
- # * :hidden - only finds invisible elements.
171
- # * :visible - same as true; only finds visible elements.
172
- # @option options [Integer] count Exact number of matches that are expected to be found
173
- # @option options [Integer] maximum Maximum number of matches that are expected to be found
174
- # @option options [Integer] minimum Minimum number of matches that are expected to be found
175
- # @option options [Range] between Number of matches found must be within the given range
176
- # @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially
177
- # @option options [Integer] wait (Capybara.default_max_wait_time) The time to wait for element count expectations to become true
231
+ # @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.). Defaults to {Capybara.configure default_selector}.
232
+ # @param [String] locator The locator for the specified selector
233
+ # @macro system_filters
234
+ # @macro waiting_behavior
235
+ # @option options [Integer] count Exact number of matches that are expected to be found
236
+ # @option options [Integer] maximum Maximum number of matches that are expected to be found
237
+ # @option options [Integer] minimum Minimum number of matches that are expected to be found
238
+ # @option options [Range] between Number of matches found must be within the given range
239
+ # @option options [Boolean] allow_reload Beta feature - May be removed in any version.
240
+ # When `true` allows elements to be reloaded if they become stale. This is an advanced behavior and should only be used
241
+ # if you fully understand the potential ramifications. The results can be confusing on dynamic pages. Defaults to `false`
242
+ # @overload all([kind = Capybara.default_selector], locator = nil, **options)
243
+ # @overload all([kind = Capybara.default_selector], locator = nil, **options, &filter_block)
244
+ # @yieldparam element [Capybara::Node::Element] The element being considered for inclusion in the results
245
+ # @yieldreturn [Boolean] Should the element be considered in the results?
178
246
  # @return [Capybara::Result] A collection of found elements
179
- #
180
- def all(*args)
181
- query = Capybara::Queries::SelectorQuery.new(*args)
182
- synchronize(query.wait) do
183
- result = query.resolve_for(self)
184
- raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count?
185
- result
247
+ # @raise [Capybara::ExpectationNotMet] The number of elements found doesn't match the specified conditions
248
+ def all(*args, allow_reload: false, **options, &optional_filter_block)
249
+ minimum_specified = options_include_minimum?(options)
250
+ options = { minimum: 1 }.merge(options) unless minimum_specified
251
+ options[:session_options] = session_options
252
+ query = Capybara::Queries::SelectorQuery.new(*args, **options, &optional_filter_block)
253
+ result = nil
254
+ begin
255
+ synchronize(query.wait) do
256
+ result = query.resolve_for(self)
257
+ result.allow_reload! if allow_reload
258
+ raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count?
259
+
260
+ result
261
+ end
262
+ rescue Capybara::ExpectationNotMet
263
+ raise if minimum_specified || (result.compare_count == 1)
264
+
265
+ Result.new([], nil)
186
266
  end
187
267
  end
188
268
  alias_method :find_all, :all
@@ -190,26 +270,56 @@ module Capybara
190
270
  ##
191
271
  #
192
272
  # Find the first element on the page matching the given selector
193
- # and options, or nil if no element matches. By default no waiting
194
- # behavior occurs, however if {Capybara.wait_on_first_by_default} is set to true
195
- # it will trigger Capybara's waiting behavior for a minimum of 1 matching element to be found and
196
- # return the first. Waiting behavior can also be triggered by passing in any of the count
197
- # expectation options.
273
+ # and options. By default {#first} will wait up to {Capybara.configure default_max_wait_time}
274
+ # seconds for matching elements to appear and then raise an error if no matching
275
+ # element is found, or `nil` if the provided count options allow for empty results.
198
276
  #
199
277
  # @overload first([kind], locator, options)
200
- # @param [:css, :xpath] kind The type of selector
278
+ # @param [Symbol] kind The type of selector
201
279
  # @param [String] locator The selector
202
280
  # @param [Hash] options Additional options; see {#all}
203
281
  # @return [Capybara::Node::Element] The found element or nil
282
+ # @raise [Capybara::ElementNotFound] If element(s) matching the provided options can't be found before time expires
204
283
  #
205
- def first(*args)
206
- if Capybara.wait_on_first_by_default
207
- options = if args.last.is_a?(Hash) then args.pop.dup else {} end
208
- args.push({minimum: 1}.merge(options))
209
- end
210
- all(*args).first
211
- rescue Capybara::ExpectationNotMet
212
- nil
284
+ def first(*args, **options, &optional_filter_block)
285
+ options = { minimum: 1 }.merge(options) unless options_include_minimum?(options)
286
+ all(*args, **options, &optional_filter_block).first
287
+ end
288
+
289
+ private
290
+
291
+ def synced_resolve(query)
292
+ synchronize(query.wait) do
293
+ if prefer_exact?(query)
294
+ result = query.resolve_for(self, true)
295
+ result = query.resolve_for(self, false) if result.empty? && query.supports_exact? && !query.exact?
296
+ else
297
+ result = query.resolve_for(self)
298
+ end
299
+
300
+ if ambiguous?(query, result)
301
+ raise Capybara::Ambiguous, "Ambiguous match, found #{result.size} elements matching #{query.applied_description}"
302
+ end
303
+ raise Capybara::ElementNotFound, "Unable to find #{query.applied_description}" if result.empty?
304
+
305
+ result.first
306
+ end.tap(&:allow_reload!)
307
+ end
308
+
309
+ def ambiguous?(query, result)
310
+ %i[one smart].include?(query.match) && (result.size > 1)
311
+ end
312
+
313
+ def prefer_exact?(query)
314
+ %i[smart prefer_exact].include?(query.match)
315
+ end
316
+
317
+ def options_include_minimum?(opts)
318
+ %i[count minimum between].any? { |key| opts.key?(key) }
319
+ end
320
+
321
+ def parent
322
+ first(:xpath, './parent::*', minimum: 0)
213
323
  end
214
324
  end
215
325
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Node
4
5
  module Matchers
5
-
6
6
  ##
7
7
  #
8
- # Checks if a given selector is on the page or current node.
8
+ # Checks if a given selector is on the page or a descendant of the current node.
9
9
  #
10
10
  # page.has_selector?('p#foo')
11
11
  # page.has_selector?(:xpath, './/p[@id="foo"]')
@@ -14,81 +14,68 @@ module Capybara
14
14
  # By default it will check if the expression occurs at least once,
15
15
  # but a different number can be specified.
16
16
  #
17
- # page.has_selector?('p.foo', :count => 4)
17
+ # page.has_selector?('p.foo', count: 4)
18
18
  #
19
19
  # This will check if the expression occurs exactly 4 times.
20
20
  #
21
21
  # It also accepts all options that {Capybara::Node::Finders#all} accepts,
22
- # such as :text and :visible.
22
+ # such as `:text` and `:visible`.
23
23
  #
24
- # page.has_selector?('li', :text => 'Horse', :visible => true)
24
+ # page.has_selector?('li', text: 'Horse', visible: true)
25
25
  #
26
- # has_selector? can also accept XPath expressions generated by the
26
+ # {#has_selector?} can also accept XPath expressions generated by the
27
27
  # XPath gem:
28
28
  #
29
29
  # page.has_selector?(:xpath, XPath.descendant(:p))
30
30
  #
31
31
  # @param (see Capybara::Node::Finders#all)
32
- # @param args
33
- # @option args [Integer] :count (nil) Number of times the text should occur
34
- # @option args [Integer] :minimum (nil) Minimum number of times the text should occur
35
- # @option args [Integer] :maximum (nil) Maximum number of times the text should occur
36
- # @option args [Range] :between (nil) Range of times that should contain number of times text occurs
32
+ # @option options [Integer] :count (nil) Number of matching elements that should exist
33
+ # @option options [Integer] :minimum (nil) Minimum number of matching elements that should exist
34
+ # @option options [Integer] :maximum (nil) Maximum number of matching elements that should exist
35
+ # @option options [Range] :between (nil) Range of number of matching elements that should exist
37
36
  # @return [Boolean] If the expression exists
38
37
  #
39
- def has_selector?(*args)
40
- assert_selector(*args)
41
- rescue Capybara::ExpectationNotMet
42
- return false
38
+ def has_selector?(*args, **options, &optional_filter_block)
39
+ make_predicate(options) { assert_selector(*args, options, &optional_filter_block) }
43
40
  end
44
41
 
45
42
  ##
46
43
  #
47
- # Checks if a given selector is not on the page or current node.
48
- # Usage is identical to Capybara::Node::Matchers#has_selector?
44
+ # Checks if a given selector is not on the page or a descendant of the current node.
45
+ # Usage is identical to {#has_selector?}.
49
46
  #
50
- # @param (see Capybara::Node::Finders#has_selector?)
47
+ # @param (see #has_selector?)
51
48
  # @return [Boolean]
52
49
  #
53
- def has_no_selector?(*args)
54
- assert_no_selector(*args)
55
- rescue Capybara::ExpectationNotMet
56
- return false
50
+ def has_no_selector?(*args, **options, &optional_filter_block)
51
+ make_predicate(options) { assert_no_selector(*args, options, &optional_filter_block) }
57
52
  end
58
53
 
59
54
  ##
60
55
  #
61
- # Checks if the current node matches given selector
62
- # Usage is identical to Capybara::Node::Matchers#has_selector?
56
+ # Checks if a an element has the specified CSS styles.
63
57
  #
64
- # @param (see Capybara::Node::Finders#has_selector?)
65
- # @return [Boolean]
58
+ # element.matches_style?( 'color' => 'rgb(0,0,255)', 'font-size' => /px/ )
66
59
  #
67
- def matches_selector?(*args)
68
- assert_matches_selector(*args)
69
- rescue Capybara::ExpectationNotMet
70
- return false
60
+ # @param styles [Hash]
61
+ # @return [Boolean] If the styles match
62
+ #
63
+ def matches_style?(styles = nil, **options)
64
+ styles, options = options, {} if styles.nil?
65
+ make_predicate(options) { assert_matches_style(styles, **options) }
71
66
  end
72
67
 
73
-
74
68
  ##
69
+ # @deprecated Use {#matches_style?} instead.
75
70
  #
76
- # Checks if the current node does not match given selector
77
- # Usage is identical to Capybara::Node::Matchers#has_selector?
78
- #
79
- # @param (see Capybara::Node::Finders#has_selector?)
80
- # @return [Boolean]
81
- #
82
- def not_matches_selector?(*args)
83
- assert_not_matches_selector(*args)
84
- rescue Capybara::ExpectationNotMet
85
- return false
71
+ def has_style?(styles = nil, **options)
72
+ Capybara::Helpers.warn "DEPRECATED: has_style? is deprecated, please use matches_style? : #{Capybara::Helpers.filter_backtrace(caller)}"
73
+ matches_style?(styles, **options)
86
74
  end
87
75
 
88
-
89
76
  ##
90
77
  #
91
- # Asserts that a given selector is on the page or current node.
78
+ # Asserts that a given selector is on the page or a descendant of the current node.
92
79
  #
93
80
  # page.assert_selector('p#foo')
94
81
  # page.assert_selector(:xpath, './/p[@id="foo"]')
@@ -97,20 +84,20 @@ module Capybara
97
84
  # By default it will check if the expression occurs at least once,
98
85
  # but a different number can be specified.
99
86
  #
100
- # page.assert_selector('p#foo', :count => 4)
87
+ # page.assert_selector('p#foo', count: 4)
101
88
  #
102
89
  # This will check if the expression occurs exactly 4 times. See
103
90
  # {Capybara::Node::Finders#all} for other available result size options.
104
91
  #
105
- # If a :count of 0 is specified, it will behave like {#assert_no_selector};
92
+ # If a `:count` of 0 is specified, it will behave like {#assert_no_selector};
106
93
  # however, use of that method is preferred over this one.
107
94
  #
108
95
  # It also accepts all options that {Capybara::Node::Finders#all} accepts,
109
- # such as :text and :visible.
96
+ # such as `:text` and `:visible`.
110
97
  #
111
- # page.assert_selector('li', :text => 'Horse', :visible => true)
98
+ # page.assert_selector('li', text: 'Horse', visible: true)
112
99
  #
113
- # `assert_selector` can also accept XPath expressions generated by the
100
+ # {#assert_selector} can also accept XPath expressions generated by the
114
101
  # XPath gem:
115
102
  #
116
103
  # page.assert_selector(:xpath, XPath.descendant(:p))
@@ -119,105 +106,157 @@ module Capybara
119
106
  # @option options [Integer] :count (nil) Number of times the expression should occur
120
107
  # @raise [Capybara::ExpectationNotMet] If the selector does not exist
121
108
  #
122
- def assert_selector(*args)
123
- query = Capybara::Queries::SelectorQuery.new(*args)
124
- synchronize(query.wait) do
125
- result = query.resolve_for(self)
126
- matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
127
- unless matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
109
+ def assert_selector(*args, &optional_filter_block)
110
+ _verify_selector_result(args, optional_filter_block) do |result, query|
111
+ unless result.matches_count? && (result.any? || query.expects_none?)
128
112
  raise Capybara::ExpectationNotMet, result.failure_message
129
113
  end
130
114
  end
131
- return true
132
115
  end
133
116
 
134
117
  ##
135
118
  #
136
- # Asserts that a given selector is not on the page or current node.
137
- # Usage is identical to Capybara::Node::Matchers#assert_selector
138
- #
139
- # Query options such as :count, :minimum, :maximum, and :between are
140
- # considered to be an integral part of the selector. This will return
141
- # true, for example, if a page contains 4 anchors but the query expects 5:
119
+ # Asserts that an element has the specified CSS styles.
142
120
  #
143
- # page.assert_no_selector('a', :minimum => 1) # Found, raises Capybara::ExpectationNotMet
144
- # page.assert_no_selector('a', :count => 4) # Found, raises Capybara::ExpectationNotMet
145
- # page.assert_no_selector('a', :count => 5) # Not Found, returns true
121
+ # element.assert_matches_style( 'color' => 'rgb(0,0,255)', 'font-size' => /px/ )
146
122
  #
147
- # @param (see Capybara::Node::Finders#assert_selector)
148
- # @raise [Capybara::ExpectationNotMet] If the selector exists
123
+ # @param styles [Hash]
124
+ # @raise [Capybara::ExpectationNotMet] If the element doesn't have the specified styles
149
125
  #
150
- def assert_no_selector(*args)
151
- query = Capybara::Queries::SelectorQuery.new(*args)
126
+ def assert_matches_style(styles = nil, **options)
127
+ styles, options = options, {} if styles.nil?
128
+ query_args, query_opts = _set_query_session_options(styles, options)
129
+ query = Capybara::Queries::StyleQuery.new(*query_args, **query_opts)
152
130
  synchronize(query.wait) do
153
- result = query.resolve_for(self)
154
- matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
155
- if matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
156
- raise Capybara::ExpectationNotMet, result.negative_failure_message
157
- end
131
+ raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self)
158
132
  end
159
- return true
133
+ true
160
134
  end
161
- alias_method :refute_selector, :assert_no_selector
162
135
 
163
136
  ##
137
+ # @deprecated Use {#assert_matches_style} instead.
164
138
  #
165
- # Asserts that the current_node matches a given selector
139
+ def assert_style(styles = nil, **options)
140
+ warn 'assert_style is deprecated, please use assert_matches_style instead'
141
+ assert_matches_style(styles, **options)
142
+ end
143
+
144
+ # Asserts that all of the provided selectors are present on the given page
145
+ # or descendants of the current node. If options are provided, the assertion
146
+ # will check that each locator is present with those options as well (other than `:wait`).
166
147
  #
167
- # node.assert_matches_selector('p#foo')
168
- # node.assert_matches_selector(:xpath, '//p[@id="foo"]')
169
- # node.assert_matches_selector(:foo)
148
+ # page.assert_all_of_selectors(:custom, 'Tom', 'Joe', visible: all)
149
+ # page.assert_all_of_selectors(:css, '#my_div', 'a.not_clicked')
170
150
  #
171
- # It also accepts all options that {Capybara::Node::Finders#all} accepts,
172
- # such as :text and :visible.
151
+ # It accepts all options that {Capybara::Node::Finders#all} accepts,
152
+ # such as `:text` and `:visible`.
173
153
  #
174
- # node.assert_matches_selector('li', :text => 'Horse', :visible => true)
154
+ # The `:wait` option applies to all of the selectors as a group, so all of the locators must be present
155
+ # within `:wait` (defaults to {Capybara.configure default_max_wait_time}) seconds.
175
156
  #
176
- # @param (see Capybara::Node::Finders#all)
177
- # @raise [Capybara::ExpectationNotMet] If the selector does not match
157
+ # @overload assert_all_of_selectors([kind = Capybara.default_selector], *locators, **options)
178
158
  #
179
- def assert_matches_selector(*args)
180
- query = Capybara::Queries::MatchQuery.new(*args)
181
- synchronize(query.wait) do
182
- result = query.resolve_for(self.parent)
183
- unless result.include? self
184
- raise Capybara::ExpectationNotMet, "Item does not match the provided selector"
159
+ def assert_all_of_selectors(*args, **options, &optional_filter_block)
160
+ _verify_multiple(*args, **options) do |selector, locator, opts|
161
+ assert_selector(selector, locator, opts, &optional_filter_block)
162
+ end
163
+ end
164
+
165
+ # Asserts that none of the provided selectors are present on the given page
166
+ # or descendants of the current node. If options are provided, the assertion
167
+ # will check that each locator is not present with those options as well (other than `:wait`).
168
+ #
169
+ # page.assert_none_of_selectors(:custom, 'Tom', 'Joe', visible: all)
170
+ # page.assert_none_of_selectors(:css, '#my_div', 'a.not_clicked')
171
+ #
172
+ # It accepts all options that {Capybara::Node::Finders#all} accepts,
173
+ # such as `:text` and `:visible`.
174
+ #
175
+ # The `:wait` option applies to all of the selectors as a group, so none of the locators must be present
176
+ # within `:wait` (defaults to {Capybara.configure default_max_wait_time}) seconds.
177
+ #
178
+ # @overload assert_none_of_selectors([kind = Capybara.default_selector], *locators, **options)
179
+ #
180
+ def assert_none_of_selectors(*args, **options, &optional_filter_block)
181
+ _verify_multiple(*args, **options) do |selector, locator, opts|
182
+ assert_no_selector(selector, locator, opts, &optional_filter_block)
183
+ end
184
+ end
185
+
186
+ # Asserts that any of the provided selectors are present on the given page
187
+ # or descendants of the current node. If options are provided, the assertion
188
+ # will check that each locator is present with those options as well (other than `:wait`).
189
+ #
190
+ # page.assert_any_of_selectors(:custom, 'Tom', 'Joe', visible: all)
191
+ # page.assert_any_of_selectors(:css, '#my_div', 'a.not_clicked')
192
+ #
193
+ # It accepts all options that {Capybara::Node::Finders#all} accepts,
194
+ # such as `:text` and `:visible`.
195
+ #
196
+ # The `:wait` option applies to all of the selectors as a group, so any of the locators must be present
197
+ # within `:wait` (defaults to {Capybara.configure default_max_wait_time}) seconds.
198
+ #
199
+ # @overload assert_any_of_selectors([kind = Capybara.default_selector], *locators, **options)
200
+ #
201
+ def assert_any_of_selectors(*args, wait: nil, **options, &optional_filter_block)
202
+ wait = session_options.default_max_wait_time if wait.nil?
203
+ selector = extract_selector(args)
204
+ synchronize(wait) do
205
+ res = args.map do |locator|
206
+ assert_selector(selector, locator, options, &optional_filter_block)
207
+ break nil
208
+ rescue Capybara::ExpectationNotMet => e
209
+ e.message
185
210
  end
211
+ raise Capybara::ExpectationNotMet, res.join(' or ') if res
212
+
213
+ true
186
214
  end
187
- return true
188
215
  end
189
216
 
190
- def assert_not_matches_selector(*args)
191
- query = Capybara::Queries::MatchQuery.new(*args)
192
- synchronize(query.wait) do
193
- result = query.resolve_for(self.parent)
194
- if result.include? self
195
- raise Capybara::ExpectationNotMet, 'Item matched the provided selector'
217
+ ##
218
+ #
219
+ # Asserts that a given selector is not on the page or a descendant of the current node.
220
+ # Usage is identical to {#assert_selector}.
221
+ #
222
+ # Query options such as `:count`, `:minimum`, `:maximum`, and `:between` are
223
+ # considered to be an integral part of the selector. This will return
224
+ # `true`, for example, if a page contains 4 anchors but the query expects 5:
225
+ #
226
+ # page.assert_no_selector('a', minimum: 1) # Found, raises Capybara::ExpectationNotMet
227
+ # page.assert_no_selector('a', count: 4) # Found, raises Capybara::ExpectationNotMet
228
+ # page.assert_no_selector('a', count: 5) # Not Found, returns true
229
+ #
230
+ # @param (see #assert_selector)
231
+ # @raise [Capybara::ExpectationNotMet] If the selector exists
232
+ #
233
+ def assert_no_selector(*args, &optional_filter_block)
234
+ _verify_selector_result(args, optional_filter_block) do |result, query|
235
+ if result.matches_count? && (!result.empty? || query.expects_none?)
236
+ raise Capybara::ExpectationNotMet, result.negative_failure_message
196
237
  end
197
238
  end
198
- return true
199
239
  end
200
- alias_method :refute_matches_selector, :assert_not_matches_selector
201
240
 
202
241
  ##
203
242
  #
204
- # Checks if a given XPath expression is on the page or current node.
243
+ # Checks if a given XPath expression is on the page or a descendant of the current node.
205
244
  #
206
245
  # page.has_xpath?('.//p[@id="foo"]')
207
246
  #
208
247
  # By default it will check if the expression occurs at least once,
209
248
  # but a different number can be specified.
210
249
  #
211
- # page.has_xpath?('.//p[@id="foo"]', :count => 4)
250
+ # page.has_xpath?('.//p[@id="foo"]', count: 4)
212
251
  #
213
252
  # This will check if the expression occurs exactly 4 times.
214
253
  #
215
254
  # It also accepts all options that {Capybara::Node::Finders#all} accepts,
216
- # such as :text and :visible.
255
+ # such as `:text` and `:visible`.
217
256
  #
218
- # page.has_xpath?('.//li', :text => 'Horse', :visible => true)
257
+ # page.has_xpath?('.//li', text: 'Horse', visible: true)
219
258
  #
220
- # has_xpath? can also accept XPath expressions generate by the
259
+ # {#has_xpath?} can also accept XPath expressions generated by the
221
260
  # XPath gem:
222
261
  #
223
262
  # xpath = XPath.generate { |x| x.descendant(:p) }
@@ -228,59 +267,59 @@ module Capybara
228
267
  # @option options [Integer] :count (nil) Number of times the expression should occur
229
268
  # @return [Boolean] If the expression exists
230
269
  #
231
- def has_xpath?(path, options={})
232
- has_selector?(:xpath, path, options)
270
+ def has_xpath?(path, **options, &optional_filter_block)
271
+ has_selector?(:xpath, path, **options, &optional_filter_block)
233
272
  end
234
273
 
235
274
  ##
236
275
  #
237
- # Checks if a given XPath expression is not on the page or current node.
238
- # Usage is identical to Capybara::Node::Matchers#has_xpath?
276
+ # Checks if a given XPath expression is not on the page or a descendant of the current node.
277
+ # Usage is identical to {#has_xpath?}.
239
278
  #
240
- # @param (see Capybara::Node::Finders#has_xpath?)
279
+ # @param (see #has_xpath?)
241
280
  # @return [Boolean]
242
281
  #
243
- def has_no_xpath?(path, options={})
244
- has_no_selector?(:xpath, path, options)
282
+ def has_no_xpath?(path, **options, &optional_filter_block)
283
+ has_no_selector?(:xpath, path, **options, &optional_filter_block)
245
284
  end
246
285
 
247
286
  ##
248
287
  #
249
- # Checks if a given CSS selector is on the page or current node.
288
+ # Checks if a given CSS selector is on the page or a descendant of the current node.
250
289
  #
251
290
  # page.has_css?('p#foo')
252
291
  #
253
292
  # By default it will check if the selector occurs at least once,
254
293
  # but a different number can be specified.
255
294
  #
256
- # page.has_css?('p#foo', :count => 4)
295
+ # page.has_css?('p#foo', count: 4)
257
296
  #
258
297
  # This will check if the selector occurs exactly 4 times.
259
298
  #
260
299
  # It also accepts all options that {Capybara::Node::Finders#all} accepts,
261
- # such as :text and :visible.
300
+ # such as `:text` and `:visible`.
262
301
  #
263
- # page.has_css?('li', :text => 'Horse', :visible => true)
302
+ # page.has_css?('li', text: 'Horse', visible: true)
264
303
  #
265
304
  # @param [String] path A CSS selector
266
305
  # @param options (see Capybara::Node::Finders#all)
267
306
  # @option options [Integer] :count (nil) Number of times the selector should occur
268
307
  # @return [Boolean] If the selector exists
269
308
  #
270
- def has_css?(path, options={})
271
- has_selector?(:css, path, options)
309
+ def has_css?(path, **options, &optional_filter_block)
310
+ has_selector?(:css, path, **options, &optional_filter_block)
272
311
  end
273
312
 
274
313
  ##
275
314
  #
276
- # Checks if a given CSS selector is not on the page or current node.
277
- # Usage is identical to Capybara::Node::Matchers#has_css?
315
+ # Checks if a given CSS selector is not on the page or a descendant of the current node.
316
+ # Usage is identical to {#has_css?}.
278
317
  #
279
- # @param (see Capybara::Node::Finders#has_css?)
318
+ # @param (see #has_css?)
280
319
  # @return [Boolean]
281
320
  #
282
- def has_no_css?(path, options={})
283
- has_no_selector?(:css, path, options)
321
+ def has_no_css?(path, **options, &optional_filter_block)
322
+ has_no_selector?(:css, path, **options, &optional_filter_block)
284
323
  end
285
324
 
286
325
  ##
@@ -289,12 +328,11 @@ module Capybara
289
328
  # text or id.
290
329
  #
291
330
  # @param [String] locator The text or id of a link to check for
292
- # @param options
293
331
  # @option options [String, Regexp] :href The value the href attribute must be
294
332
  # @return [Boolean] Whether it exists
295
333
  #
296
- def has_link?(locator, options={})
297
- has_selector?(:link, locator, options)
334
+ def has_link?(locator = nil, **options, &optional_filter_block)
335
+ has_selector?(:link, locator, **options, &optional_filter_block)
298
336
  end
299
337
 
300
338
  ##
@@ -302,11 +340,11 @@ module Capybara
302
340
  # Checks if the page or current node has no link with the given
303
341
  # text or id.
304
342
  #
305
- # @param (see Capybara::Node::Finders#has_link?)
343
+ # @param (see #has_link?)
306
344
  # @return [Boolean] Whether it doesn't exist
307
345
  #
308
- def has_no_link?(locator, options={})
309
- has_no_selector?(:link, locator, options)
346
+ def has_no_link?(locator = nil, **options, &optional_filter_block)
347
+ has_no_selector?(:link, locator, **options, &optional_filter_block)
310
348
  end
311
349
 
312
350
  ##
@@ -317,8 +355,8 @@ module Capybara
317
355
  # @param [String] locator The text, value or id of a button to check for
318
356
  # @return [Boolean] Whether it exists
319
357
  #
320
- def has_button?(locator, options={})
321
- has_selector?(:button, locator, options)
358
+ def has_button?(locator = nil, **options, &optional_filter_block)
359
+ has_selector?(:button, locator, **options, &optional_filter_block)
322
360
  end
323
361
 
324
362
  ##
@@ -329,8 +367,8 @@ module Capybara
329
367
  # @param [String] locator The text, value or id of a button to check for
330
368
  # @return [Boolean] Whether it doesn't exist
331
369
  #
332
- def has_no_button?(locator, options={})
333
- has_no_selector?(:button, locator, options)
370
+ def has_no_button?(locator = nil, **options, &optional_filter_block)
371
+ has_no_selector?(:button, locator, **options, &optional_filter_block)
334
372
  end
335
373
 
336
374
  ##
@@ -339,90 +377,90 @@ module Capybara
339
377
  # label, name or id.
340
378
  #
341
379
  # For text fields and other textual fields, such as textareas and
342
- # HTML5 email/url/etc. fields, it's possible to specify a :with
380
+ # HTML5 email/url/etc. fields, it's possible to specify a `:with`
343
381
  # option to specify the text the field should contain:
344
382
  #
345
- # page.has_field?('Name', :with => 'Jonas')
383
+ # page.has_field?('Name', with: 'Jonas')
346
384
  #
347
385
  # It is also possible to filter by the field type attribute:
348
386
  #
349
- # page.has_field?('Email', :type => 'email')
387
+ # page.has_field?('Email', type: 'email')
350
388
  #
351
- # Note: 'textarea' and 'select' are valid type values, matching the associated tag names.
389
+ # NOTE: 'textarea' and 'select' are valid type values, matching the associated tag names.
352
390
  #
353
- # @param [String] locator The label, name or id of a field to check for
354
- # @option options [String] :with The text content of the field
355
- # @option options [String] :type The type attribute of the field
356
- # @return [Boolean] Whether it exists
391
+ # @param [String] locator The label, name or id of a field to check for
392
+ # @option options [String, Regexp] :with The text content of the field or a Regexp to match
393
+ # @option options [String] :type The type attribute of the field
394
+ # @return [Boolean] Whether it exists
357
395
  #
358
- def has_field?(locator, options={})
359
- has_selector?(:field, locator, options)
396
+ def has_field?(locator = nil, **options, &optional_filter_block)
397
+ has_selector?(:field, locator, **options, &optional_filter_block)
360
398
  end
361
399
 
362
400
  ##
363
401
  #
364
402
  # Checks if the page or current node has no form field with the given
365
- # label, name or id. See {Capybara::Node::Matchers#has_field?}.
403
+ # label, name or id. See {#has_field?}.
366
404
  #
367
- # @param [String] locator The label, name or id of a field to check for
368
- # @option options [String] :with The text content of the field
369
- # @option options [String] :type The type attribute of the field
370
- # @return [Boolean] Whether it doesn't exist
405
+ # @param [String] locator The label, name or id of a field to check for
406
+ # @option options [String, Regexp] :with The text content of the field or a Regexp to match
407
+ # @option options [String] :type The type attribute of the field
408
+ # @return [Boolean] Whether it doesn't exist
371
409
  #
372
- def has_no_field?(locator, options={})
373
- has_no_selector?(:field, locator, options)
410
+ def has_no_field?(locator = nil, **options, &optional_filter_block)
411
+ has_no_selector?(:field, locator, **options, &optional_filter_block)
374
412
  end
375
413
 
376
414
  ##
377
415
  #
378
416
  # Checks if the page or current node has a radio button or
379
- # checkbox with the given label, value or id, that is currently
417
+ # checkbox with the given label, value, id, or {Capybara.configure test_id} attribute that is currently
380
418
  # checked.
381
419
  #
382
420
  # @param [String] locator The label, name or id of a checked field
383
421
  # @return [Boolean] Whether it exists
384
422
  #
385
- def has_checked_field?(locator, options={})
386
- has_selector?(:field, locator, options.merge(:checked => true))
423
+ def has_checked_field?(locator = nil, **options, &optional_filter_block)
424
+ has_selector?(:field, locator, **options.merge(checked: true), &optional_filter_block)
387
425
  end
388
426
 
389
427
  ##
390
428
  #
391
429
  # Checks if the page or current node has no radio button or
392
- # checkbox with the given label, value or id, that is currently
430
+ # checkbox with the given label, value or id, or {Capybara.configure test_id} attribute that is currently
393
431
  # checked.
394
432
  #
395
433
  # @param [String] locator The label, name or id of a checked field
396
434
  # @return [Boolean] Whether it doesn't exist
397
435
  #
398
- def has_no_checked_field?(locator, options={})
399
- has_no_selector?(:field, locator, options.merge(:checked => true))
436
+ def has_no_checked_field?(locator = nil, **options, &optional_filter_block)
437
+ has_no_selector?(:field, locator, **options.merge(checked: true), &optional_filter_block)
400
438
  end
401
439
 
402
440
  ##
403
441
  #
404
442
  # Checks if the page or current node has a radio button or
405
- # checkbox with the given label, value or id, that is currently
443
+ # checkbox with the given label, value or id, or {Capybara.configure test_id} attribute that is currently
406
444
  # unchecked.
407
445
  #
408
446
  # @param [String] locator The label, name or id of an unchecked field
409
447
  # @return [Boolean] Whether it exists
410
448
  #
411
- def has_unchecked_field?(locator, options={})
412
- has_selector?(:field, locator, options.merge(:unchecked => true))
449
+ def has_unchecked_field?(locator = nil, **options, &optional_filter_block)
450
+ has_selector?(:field, locator, **options.merge(unchecked: true), &optional_filter_block)
413
451
  end
414
452
 
415
453
  ##
416
454
  #
417
455
  # Checks if the page or current node has no radio button or
418
- # checkbox with the given label, value or id, that is currently
456
+ # checkbox with the given label, value or id, or {Capybara.configure test_id} attribute that is currently
419
457
  # unchecked.
420
458
  #
421
459
  # @param [String] locator The label, name or id of an unchecked field
422
460
  # @return [Boolean] Whether it doesn't exist
423
461
  #
424
- def has_no_unchecked_field?(locator, options={})
425
- has_no_selector?(:field, locator, options.merge(:unchecked => true))
462
+ def has_no_unchecked_field?(locator = nil, **options, &optional_filter_block)
463
+ has_no_selector?(:field, locator, **options.merge(unchecked: true), &optional_filter_block)
426
464
  end
427
465
 
428
466
  ##
@@ -432,41 +470,42 @@ module Capybara
432
470
  #
433
471
  # It can be specified which option should currently be selected:
434
472
  #
435
- # page.has_select?('Language', :selected => 'German')
473
+ # page.has_select?('Language', selected: 'German')
436
474
  #
437
475
  # For multiple select boxes, several options may be specified:
438
476
  #
439
- # page.has_select?('Language', :selected => ['English', 'German'])
477
+ # page.has_select?('Language', selected: ['English', 'German'])
440
478
  #
441
479
  # It's also possible to check if the exact set of options exists for
442
480
  # this select box:
443
481
  #
444
- # page.has_select?('Language', :options => ['English', 'German', 'Spanish'])
482
+ # page.has_select?('Language', options: ['English', 'German', 'Spanish'])
445
483
  #
446
484
  # You can also check for a partial set of options:
447
485
  #
448
- # page.has_select?('Language', :with_options => ['English', 'German'])
486
+ # page.has_select?('Language', with_options: ['English', 'German'])
449
487
  #
450
- # @param [String] locator The label, name or id of a select box
451
- # @option options [Array] :options Options which should be contained in this select box
452
- # @option options [Array] :with_options Partial set of options which should be contained in this select box
453
- # @option options [String, Array] :selected Options which should be selected
454
- # @return [Boolean] Whether it exists
488
+ # @param [String] locator The label, name or id of a select box
489
+ # @option options [Array] :options Options which should be contained in this select box
490
+ # @option options [Array] :with_options Partial set of options which should be contained in this select box
491
+ # @option options [String, Array] :selected Options which should be selected
492
+ # @option options [String, Array] :with_selected Partial set of options which should minimally be selected
493
+ # @return [Boolean] Whether it exists
455
494
  #
456
- def has_select?(locator, options={})
457
- has_selector?(:select, locator, options)
495
+ def has_select?(locator = nil, **options, &optional_filter_block)
496
+ has_selector?(:select, locator, **options, &optional_filter_block)
458
497
  end
459
498
 
460
499
  ##
461
500
  #
462
501
  # Checks if the page or current node has no select field with the
463
- # given label, name or id. See {Capybara::Node::Matchers#has_select?}.
502
+ # given label, name or id. See {#has_select?}.
464
503
  #
465
- # @param (see Capybara::Node::Matchers#has_select?)
504
+ # @param (see #has_select?)
466
505
  # @return [Boolean] Whether it doesn't exist
467
506
  #
468
- def has_no_select?(locator, options={})
469
- has_no_selector?(:select, locator, options)
507
+ def has_no_select?(locator = nil, **options, &optional_filter_block)
508
+ has_no_selector?(:select, locator, **options, &optional_filter_block)
470
509
  end
471
510
 
472
511
  ##
@@ -476,23 +515,134 @@ module Capybara
476
515
  #
477
516
  # page.has_table?('People')
478
517
  #
479
- # @param [String] locator The id or caption of a table
480
- # @return [Boolean] Whether it exist
481
- #
482
- def has_table?(locator, options={})
483
- has_selector?(:table, locator, options)
518
+ # @param [String] locator The id or caption of a table
519
+ # @option options [Array<Array<String>>] :rows
520
+ # Text which should be contained in the tables `<td>` elements organized by row (`<td>` visibility is not considered)
521
+ # @option options [Array<Array<String>>, Array<Hash<String,String>>] :with_rows
522
+ # Partial set of text which should be contained in the tables `<td>` elements organized by row (`<td>` visibility is not considered)
523
+ # @option options [Array<Array<String>>] :cols
524
+ # Text which should be contained in the tables `<td>` elements organized by column (`<td>` visibility is not considered)
525
+ # @option options [Array<Array<String>>, Array<Hash<String,String>>] :with_cols
526
+ # Partial set of text which should be contained in the tables `<td>` elements organized by column (`<td>` visibility is not considered)
527
+ # @return [Boolean] Whether it exists
528
+ #
529
+ def has_table?(locator = nil, **options, &optional_filter_block)
530
+ has_selector?(:table, locator, **options, &optional_filter_block)
484
531
  end
485
532
 
486
533
  ##
487
534
  #
488
535
  # Checks if the page or current node has no table with the given id
489
- # or caption. See {Capybara::Node::Matchers#has_table?}.
536
+ # or caption. See {#has_table?}.
490
537
  #
491
- # @param (see Capybara::Node::Matchers#has_table?)
538
+ # @param (see #has_table?)
492
539
  # @return [Boolean] Whether it doesn't exist
493
540
  #
494
- def has_no_table?(locator, options={})
495
- has_no_selector?(:table, locator, options)
541
+ def has_no_table?(locator = nil, **options, &optional_filter_block)
542
+ has_no_selector?(:table, locator, **options, &optional_filter_block)
543
+ end
544
+
545
+ ##
546
+ #
547
+ # Asserts that the current node matches a given selector.
548
+ #
549
+ # node.assert_matches_selector('p#foo')
550
+ # node.assert_matches_selector(:xpath, '//p[@id="foo"]')
551
+ # node.assert_matches_selector(:foo)
552
+ #
553
+ # It also accepts all options that {Capybara::Node::Finders#all} accepts,
554
+ # such as `:text` and `:visible`.
555
+ #
556
+ # node.assert_matches_selector('li', text: 'Horse', visible: true)
557
+ #
558
+ # @param (see Capybara::Node::Finders#all)
559
+ # @raise [Capybara::ExpectationNotMet] If the selector does not match
560
+ #
561
+ def assert_matches_selector(*args, &optional_filter_block)
562
+ _verify_match_result(args, optional_filter_block) do |result|
563
+ raise Capybara::ExpectationNotMet, 'Item does not match the provided selector' unless result.include? self
564
+ end
565
+ end
566
+
567
+ ##
568
+ #
569
+ # Asserts that the current node does not match a given selector.
570
+ # Usage is identical to {#assert_matches_selector}.
571
+ #
572
+ # @param (see #assert_matches_selector)
573
+ # @raise [Capybara::ExpectationNotMet] If the selector matches
574
+ #
575
+ def assert_not_matches_selector(*args, &optional_filter_block)
576
+ _verify_match_result(args, optional_filter_block) do |result|
577
+ raise Capybara::ExpectationNotMet, 'Item matched the provided selector' if result.include? self
578
+ end
579
+ end
580
+
581
+ ##
582
+ #
583
+ # Checks if the current node matches given selector.
584
+ #
585
+ # @param (see #has_selector?)
586
+ # @return [Boolean]
587
+ #
588
+ def matches_selector?(*args, **options, &optional_filter_block)
589
+ make_predicate(options) { assert_matches_selector(*args, options, &optional_filter_block) }
590
+ end
591
+
592
+ ##
593
+ #
594
+ # Checks if the current node matches given XPath expression.
595
+ #
596
+ # @param [String, XPath::Expression] xpath The XPath expression to match against the current code
597
+ # @return [Boolean]
598
+ #
599
+ def matches_xpath?(xpath, **options, &optional_filter_block)
600
+ matches_selector?(:xpath, xpath, **options, &optional_filter_block)
601
+ end
602
+
603
+ ##
604
+ #
605
+ # Checks if the current node matches given CSS selector.
606
+ #
607
+ # @param [String] css The CSS selector to match against the current code
608
+ # @return [Boolean]
609
+ #
610
+ def matches_css?(css, **options, &optional_filter_block)
611
+ matches_selector?(:css, css, **options, &optional_filter_block)
612
+ end
613
+
614
+ ##
615
+ #
616
+ # Checks if the current node does not match given selector.
617
+ # Usage is identical to {#has_selector?}.
618
+ #
619
+ # @param (see #has_selector?)
620
+ # @return [Boolean]
621
+ #
622
+ def not_matches_selector?(*args, **options, &optional_filter_block)
623
+ make_predicate(options) { assert_not_matches_selector(*args, options, &optional_filter_block) }
624
+ end
625
+
626
+ ##
627
+ #
628
+ # Checks if the current node does not match given XPath expression.
629
+ #
630
+ # @param [String, XPath::Expression] xpath The XPath expression to match against the current code
631
+ # @return [Boolean]
632
+ #
633
+ def not_matches_xpath?(xpath, **options, &optional_filter_block)
634
+ not_matches_selector?(:xpath, xpath, **options, &optional_filter_block)
635
+ end
636
+
637
+ ##
638
+ #
639
+ # Checks if the current node does not match given CSS selector.
640
+ #
641
+ # @param [String] css The CSS selector to match against the current code
642
+ # @return [Boolean]
643
+ #
644
+ def not_matches_css?(css, **options, &optional_filter_block)
645
+ not_matches_selector?(:css, css, **options, &optional_filter_block)
496
646
  end
497
647
 
498
648
  ##
@@ -500,34 +650,34 @@ module Capybara
500
650
  # ignoring any HTML tags.
501
651
  #
502
652
  # @!macro text_query_params
503
- # @overload $0(type, text, options = {})
504
- # @param [:all, :visible] type Whether to check for only visible or all text
653
+ # @overload $0(type, text, **options)
654
+ # @param [:all, :visible] type Whether to check for only visible or all text. If this parameter is missing or nil then we use the value of {Capybara.configure ignore_hidden_elements}, which defaults to `true`, corresponding to `:visible`.
505
655
  # @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
506
656
  # @option options [Integer] :count (nil) Number of times the text is expected to occur
507
657
  # @option options [Integer] :minimum (nil) Minimum number of times the text is expected to occur
508
658
  # @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
509
659
  # @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
510
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for text to eq/match given string/regexp argument
511
- # @overload $0(text, options = {})
660
+ # @option options [Numeric] :wait Maximum time that Capybara will wait for text to eq/match given string/regexp argument. Defaults to {Capybara.configure default_max_wait_time}.
661
+ # @option options [Boolean] :exact Whether text must be an exact match or just substring. Defaults to {Capybara.configure exact_text}.
662
+ # @option options [Boolean] :normalize_ws (false) When `true` replace all whitespace with standard spaces and collapse consecutive whitespace to a single space
663
+ # @overload $0(text, **options)
512
664
  # @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
513
665
  # @option options [Integer] :count (nil) Number of times the text is expected to occur
514
666
  # @option options [Integer] :minimum (nil) Minimum number of times the text is expected to occur
515
667
  # @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
516
668
  # @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
517
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for text to eq/match given string/regexp argument
669
+ # @option options [Numeric] :wait Maximum time that Capybara will wait for text to eq/match given string/regexp argument. Defaults to {Capybara.configure default_max_wait_time}.
670
+ # @option options [Boolean] :exact Whether text must be an exact match or just substring. Defaults to {Capybara.configure exact_text}.
671
+ # @option options [Boolean] :normalize_ws (false) When `true` replace all whitespace with standard spaces and collapse consecutive whitespace to a single space
518
672
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
519
673
  # @return [true]
520
674
  #
521
- def assert_text(*args)
522
- query = Capybara::Queries::TextQuery.new(*args)
523
- synchronize(query.wait) do
524
- count = query.resolve_for(self)
525
- matches_count = Capybara::Helpers.matches_count?(count, query.options)
526
- unless matches_count && ((count > 0) || Capybara::Helpers.expects_none?(query.options))
675
+ def assert_text(type_or_text, *args, **opts)
676
+ _verify_text(type_or_text, *args, **opts) do |count, query|
677
+ unless query.matches_count?(count) && (count.positive? || query.expects_none?)
527
678
  raise Capybara::ExpectationNotMet, query.failure_message
528
679
  end
529
680
  end
530
- return true
531
681
  end
532
682
 
533
683
  ##
@@ -538,26 +688,18 @@ module Capybara
538
688
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
539
689
  # @return [true]
540
690
  #
541
- def assert_no_text(*args)
542
- query = Capybara::Queries::TextQuery.new(*args)
543
- synchronize(query.wait) do
544
- count = query.resolve_for(self)
545
- matches_count = Capybara::Helpers.matches_count?(count, query.options)
546
- if matches_count && ((count > 0) || Capybara::Helpers.expects_none?(query.options))
691
+ def assert_no_text(type_or_text, *args, **opts)
692
+ _verify_text(type_or_text, *args, **opts) do |count, query|
693
+ if query.matches_count?(count) && (count.positive? || query.expects_none?)
547
694
  raise Capybara::ExpectationNotMet, query.negative_failure_message
548
695
  end
549
696
  end
550
- return true
551
697
  end
552
698
 
553
699
  ##
554
700
  # Checks if the page or current node has the given text content,
555
701
  # ignoring any HTML tags.
556
702
  #
557
- # Whitespaces are normalized in both node's text and passed text parameter.
558
- # Note that whitespace isn't normalized in passed regexp as normalizing whitespace
559
- # in regexp isn't easy and doesn't seem to be worth it.
560
- #
561
703
  # By default it will check if the text occurs at least once,
562
704
  # but a different number can be specified.
563
705
  #
@@ -568,10 +710,8 @@ module Capybara
568
710
  # @macro text_query_params
569
711
  # @return [Boolean] Whether it exists
570
712
  #
571
- def has_text?(*args)
572
- assert_text(*args)
573
- rescue Capybara::ExpectationNotMet
574
- return false
713
+ def has_text?(*args, **options)
714
+ make_predicate(options) { assert_text(*args, **options) }
575
715
  end
576
716
  alias_method :has_content?, :has_text?
577
717
 
@@ -582,15 +722,161 @@ module Capybara
582
722
  # @macro text_query_params
583
723
  # @return [Boolean] Whether it doesn't exist
584
724
  #
585
- def has_no_text?(*args)
586
- assert_no_text(*args)
587
- rescue Capybara::ExpectationNotMet
588
- return false
725
+ def has_no_text?(*args, **options)
726
+ make_predicate(options) { assert_no_text(*args, **options) }
589
727
  end
590
728
  alias_method :has_no_content?, :has_no_text?
591
729
 
730
+ ##
731
+ #
732
+ # Asserts that a given selector matches an ancestor of the current node.
733
+ #
734
+ # element.assert_ancestor('p#foo')
735
+ #
736
+ # Accepts the same options as {#assert_selector}
737
+ #
738
+ # @param (see Capybara::Node::Finders#find)
739
+ # @raise [Capybara::ExpectationNotMet] If the selector does not exist
740
+ #
741
+ def assert_ancestor(*args, &optional_filter_block)
742
+ _verify_selector_result(args, optional_filter_block, Capybara::Queries::AncestorQuery) do |result, query|
743
+ unless result.matches_count? && (result.any? || query.expects_none?)
744
+ raise Capybara::ExpectationNotMet, result.failure_message
745
+ end
746
+ end
747
+ end
748
+
749
+ def assert_no_ancestor(*args, &optional_filter_block)
750
+ _verify_selector_result(args, optional_filter_block, Capybara::Queries::AncestorQuery) do |result, query|
751
+ if result.matches_count? && (!result.empty? || query.expects_none?)
752
+ raise Capybara::ExpectationNotMet, result.negative_failure_message
753
+ end
754
+ end
755
+ end
756
+
757
+ ##
758
+ #
759
+ # Predicate version of {#assert_ancestor}
760
+ #
761
+ def has_ancestor?(*args, **options, &optional_filter_block)
762
+ make_predicate(options) { assert_ancestor(*args, options, &optional_filter_block) }
763
+ end
764
+
765
+ ##
766
+ #
767
+ # Predicate version of {#assert_no_ancestor}
768
+ #
769
+ def has_no_ancestor?(*args, **options, &optional_filter_block)
770
+ make_predicate(options) { assert_no_ancestor(*args, options, &optional_filter_block) }
771
+ end
772
+
773
+ ##
774
+ #
775
+ # Asserts that a given selector matches a sibling of the current node.
776
+ #
777
+ # element.assert_sibling('p#foo')
778
+ #
779
+ # Accepts the same options as {#assert_selector}
780
+ #
781
+ # @param (see Capybara::Node::Finders#find)
782
+ # @raise [Capybara::ExpectationNotMet] If the selector does not exist
783
+ #
784
+ def assert_sibling(*args, &optional_filter_block)
785
+ _verify_selector_result(args, optional_filter_block, Capybara::Queries::SiblingQuery) do |result, query|
786
+ unless result.matches_count? && (result.any? || query.expects_none?)
787
+ raise Capybara::ExpectationNotMet, result.failure_message
788
+ end
789
+ end
790
+ end
791
+
792
+ def assert_no_sibling(*args, &optional_filter_block)
793
+ _verify_selector_result(args, optional_filter_block, Capybara::Queries::SiblingQuery) do |result, query|
794
+ if result.matches_count? && (!result.empty? || query.expects_none?)
795
+ raise Capybara::ExpectationNotMet, result.negative_failure_message
796
+ end
797
+ end
798
+ end
799
+
800
+ ##
801
+ #
802
+ # Predicate version of {#assert_sibling}
803
+ #
804
+ def has_sibling?(*args, **options, &optional_filter_block)
805
+ make_predicate(options) { assert_sibling(*args, options, &optional_filter_block) }
806
+ end
807
+
808
+ ##
809
+ #
810
+ # Predicate version of {#assert_no_sibling}
811
+ #
812
+ def has_no_sibling?(*args, **options, &optional_filter_block)
813
+ make_predicate(options) { assert_no_sibling(*args, options, &optional_filter_block) }
814
+ end
815
+
592
816
  def ==(other)
593
- self.eql?(other) || (other.respond_to?(:base) && base == other.base)
817
+ eql?(other) || (other.respond_to?(:base) && base == other.base)
818
+ end
819
+
820
+ private
821
+
822
+ def extract_selector(args)
823
+ args.first.is_a?(Symbol) ? args.shift : session_options.default_selector
824
+ end
825
+
826
+ def _verify_multiple(*args, wait: nil, **options)
827
+ wait = session_options.default_max_wait_time if wait.nil?
828
+ selector = extract_selector(args)
829
+ synchronize(wait) do
830
+ args.each { |locator| yield(selector, locator, options) }
831
+ end
832
+ end
833
+
834
+ def _verify_selector_result(query_args, optional_filter_block, query_type = Capybara::Queries::SelectorQuery)
835
+ # query_args, query_opts = if query_args[0].is_a? Symbol
836
+ # a,o = _set_query_session_options(*query_args.slice(2..))
837
+ # [query_args.slice(0..1).concat(a), o]
838
+ # else
839
+ # _set_query_session_options(*query_args)
840
+ # end
841
+ query_args, query_opts = _set_query_session_options(*query_args)
842
+ query = query_type.new(*query_args, **query_opts, &optional_filter_block)
843
+ synchronize(query.wait) do
844
+ yield query.resolve_for(self), query
845
+ end
846
+ true
847
+ end
848
+
849
+ def _verify_match_result(query_args, optional_filter_block)
850
+ query_args, query_opts = _set_query_session_options(*query_args)
851
+ query = Capybara::Queries::MatchQuery.new(*query_args, **query_opts, &optional_filter_block)
852
+ synchronize(query.wait) do
853
+ yield query.resolve_for(parent || session&.document || query_scope)
854
+ end
855
+ true
856
+ end
857
+
858
+ def _verify_text(type = nil, expected_text, **query_options) # rubocop:disable Style/OptionalArguments
859
+ query_options[:session_options] = session_options
860
+ query = Capybara::Queries::TextQuery.new(type, expected_text, **query_options)
861
+ synchronize(query.wait) do
862
+ yield query.resolve_for(self), query
863
+ end
864
+ true
865
+ end
866
+
867
+ def _set_query_session_options(*query_args)
868
+ query_args, query_options = query_args.dup, {}
869
+ # query_options = query_args.pop if query_options.empty? && query_args.last.is_a?(Hash)
870
+ query_options = query_args.pop if query_args.last.is_a?(Hash)
871
+ query_options[:session_options] = session_options
872
+ [query_args, query_options]
873
+ end
874
+
875
+ def make_predicate(options)
876
+ options[:wait] = 0 unless options.key?(:wait) || session_options.predicates_wait
877
+ yield
878
+ rescue Capybara::ExpectationNotMet
879
+ false
594
880
  end
595
881
  end
596
882
  end