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,167 +1,415 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Node
4
5
  module Actions
6
+ # @!macro waiting_behavior
7
+ # If the driver is capable of executing JavaScript, this method will wait for a set amount of time
8
+ # and continuously retry finding the element until either the element is found or the time
9
+ # expires. The length of time this method will wait is controlled through {Capybara.configure default_max_wait_time}.
10
+ #
11
+ # @option options [false, true, Numeric] wait
12
+ # Maximum time to wait for matching element to appear. Defaults to {Capybara.configure default_max_wait_time}.
5
13
 
6
14
  ##
7
15
  #
8
- # Finds a button or link by id, text or value and clicks it. Also looks at image
9
- # alt text inside the link.
16
+ # Finds a button or link and clicks it. See {#click_button} and
17
+ # {#click_link} for what locator will match against for each type of element.
18
+ #
19
+ # @overload click_link_or_button([locator], **options)
20
+ # @macro waiting_behavior
21
+ # @param [String] locator See {#click_button} and {#click_link}
10
22
  #
11
- # @param [String] locator Text, id or value of link or button
23
+ # @return [Capybara::Node::Element] The element clicked
12
24
  #
13
- def click_link_or_button(locator, options={})
14
- find(:link_or_button, locator, options).click
25
+ def click_link_or_button(locator = nil, **options)
26
+ find(:link_or_button, locator, **options).click
15
27
  end
16
28
  alias_method :click_on, :click_link_or_button
17
29
 
18
30
  ##
19
31
  #
20
- # Finds a link by id, text or title and clicks it. Also looks at image
32
+ # Finds a link by id, {Capybara.configure test_id} attribute, text or title and clicks it. Also looks at image
21
33
  # alt text inside the link.
22
34
  #
23
- # @param [String] locator text, id, title or nested image's alt attribute
24
- # @param options See {Capybara::Node::Finders#find_link}
35
+ # @overload click_link([locator], **options)
36
+ # @macro waiting_behavior
37
+ # @param [String] locator text, id, {Capybara.configure test_id} attribute, title or nested image's alt attribute
38
+ # @param [Hash] options See {Capybara::Node::Finders#find_link}
25
39
  #
26
- def click_link(locator, options={})
27
- find(:link, locator, options).click
40
+ # @return [Capybara::Node::Element] The element clicked
41
+ def click_link(locator = nil, **options)
42
+ find(:link, locator, **options).click
28
43
  end
29
44
 
30
45
  ##
31
46
  #
32
47
  # Finds a button on the page and clicks it.
33
- # This can be any \<input> element of type submit, reset, image, button or it can be a
34
- # \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
35
- # by their text content, and image \<input> elements by their alt attribute
36
- #
37
- # @param [String] locator Which button to find
38
- # @param options See {Capybara::Node::Finders#find_button}
39
- def click_button(locator, options={})
40
- find(:button, locator, options).click
48
+ # This can be any `<input>` element of type submit, reset, image, button or it can be a
49
+ # `<button>` element. All buttons can be found by their id, name, {Capybara.configure test_id} attribute, value, or title. `<button>` elements can also be found
50
+ # by their text content, and image `<input>` elements by their alt attribute.
51
+ #
52
+ # @overload click_button([locator], **options)
53
+ # @macro waiting_behavior
54
+ # @param [String] locator Which button to find
55
+ # @param [Hash] options See {Capybara::Node::Finders#find_button}
56
+ # @return [Capybara::Node::Element] The element clicked
57
+ def click_button(locator = nil, **options)
58
+ find(:button, locator, **options).click
41
59
  end
42
60
 
43
61
  ##
44
62
  #
45
- # Locate a text field or text area and fill it in with the given text
46
- # The field can be found via its name, id or label text.
47
- #
48
- # page.fill_in 'Name', :with => 'Bob'
49
- #
50
- # @param [String] locator Which field to fill in
51
- # @param [Hash] options
52
- # @option options [String] :with The value to fill in - required
53
- # @option options [Hash] :fill_options Driver specific options regarding how to fill fields
54
- #
55
- def fill_in(locator, options={})
56
- raise "Must pass a hash containing 'with'" if not options.is_a?(Hash) or not options.has_key?(:with)
57
- with = options.delete(:with)
58
- fill_options = options.delete(:fill_options)
59
- find(:fillable_field, locator, options).set(with, fill_options)
63
+ # Locate a text field or text area and fill it in with the given text.
64
+ # The field can be found via its name, id, {Capybara.configure test_id} attribute, placeholder, or label text.
65
+ # If no locator is provided this will operate on self or a descendant.
66
+ #
67
+ # # will fill in a descendant fillable field with name, id, or label text matching 'Name'
68
+ # page.fill_in 'Name', with: 'Bob'
69
+ #
70
+ # # will fill in `el` if it's a fillable field
71
+ # el.fill_in with: 'Tom'
72
+ #
73
+ #
74
+ # @overload fill_in([locator], with:, **options)
75
+ # @param [String] locator Which field to fill in
76
+ # @param [Hash] options
77
+ # @param with: [String] The value to fill in
78
+ # @macro waiting_behavior
79
+ # @option options [String] currently_with The current value property of the field to fill in
80
+ # @option options [Boolean] multiple Match fields that can have multiple values?
81
+ # @option options [String, Regexp] id Match fields that match the id attribute
82
+ # @option options [String] name Match fields that match the name attribute
83
+ # @option options [String] placeholder Match fields that match the placeholder attribute
84
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
85
+ # @option options [Hash] fill_options Driver specific options regarding how to fill fields (Defaults come from {Capybara.configure default_set_options})
86
+ #
87
+ # @return [Capybara::Node::Element] The element filled in
88
+ def fill_in(locator = nil, with:, currently_with: nil, fill_options: {}, **find_options)
89
+ find_options[:with] = currently_with if currently_with
90
+ find_options[:allow_self] = true if locator.nil?
91
+ find(:fillable_field, locator, **find_options).set(with, **fill_options)
60
92
  end
61
93
 
94
+ # @!macro label_click
95
+ # @option options [Boolean] allow_label_click
96
+ # Attempt to click the label to toggle state if element is non-visible. Defaults to {Capybara.configure automatic_label_click}.
97
+
62
98
  ##
63
99
  #
64
- # Find a radio button and mark it as checked. The radio button can be found
65
- # via name, id or label text.
100
+ # Find a descendant radio button and mark it as checked. The radio button can be found
101
+ # via name, id, {Capybara.configure test_id} attribute or label text. If no locator is
102
+ # provided this will match against self or a descendant.
66
103
  #
104
+ # # will choose a descendant radio button with a name, id, or label text matching 'Male'
67
105
  # page.choose('Male')
68
106
  #
69
- # @param [String] locator Which radio button to choose
107
+ # # will choose `el` if it's a radio button element
108
+ # el.choose()
109
+ #
110
+ # @overload choose([locator], **options)
111
+ # @param [String] locator Which radio button to choose
70
112
  #
71
- def choose(locator, options={})
72
- find(:radio_button, locator, options).set(true)
113
+ # @option options [String] option Value of the radio_button to choose
114
+ # @option options [String, Regexp] id Match fields that match the id attribute
115
+ # @option options [String] name Match fields that match the name attribute
116
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
117
+ # @macro waiting_behavior
118
+ # @macro label_click
119
+ #
120
+ # @return [Capybara::Node::Element] The element chosen or the label clicked
121
+ def choose(locator = nil, **options)
122
+ _check_with_label(:radio_button, true, locator, **options)
73
123
  end
74
124
 
75
125
  ##
76
126
  #
77
- # Find a check box and mark it as checked. The check box can be found
78
- # via name, id or label text.
127
+ # Find a descendant check box and mark it as checked. The check box can be found
128
+ # via name, id, {Capybara.configure test_id} attribute, or label text. If no locator
129
+ # is provided this will match against self or a descendant.
79
130
  #
131
+ # # will check a descendant checkbox with a name, id, or label text matching 'German'
80
132
  # page.check('German')
81
133
  #
82
- # @param [String] locator Which check box to check
134
+ # # will check `el` if it's a checkbox element
135
+ # el.check()
136
+ #
83
137
  #
84
- def check(locator, options={})
85
- find(:checkbox, locator, options).set(true)
138
+ # @overload check([locator], **options)
139
+ # @param [String] locator Which check box to check
140
+ #
141
+ # @option options [String] option Value of the checkbox to select
142
+ # @option options [String, Regexp] id Match fields that match the id attribute
143
+ # @option options [String] name Match fields that match the name attribute
144
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
145
+ # @macro label_click
146
+ # @macro waiting_behavior
147
+ #
148
+ # @return [Capybara::Node::Element] The element checked or the label clicked
149
+ def check(locator = nil, **options)
150
+ _check_with_label(:checkbox, true, locator, **options)
86
151
  end
87
152
 
88
153
  ##
89
154
  #
90
- # Find a check box and mark uncheck it. The check box can be found
91
- # via name, id or label text.
155
+ # Find a descendant check box and uncheck it. The check box can be found
156
+ # via name, id, {Capybara.configure test_id} attribute, or label text. If
157
+ # no locator is provided this will match against self or a descendant.
92
158
  #
159
+ # # will uncheck a descendant checkbox with a name, id, or label text matching 'German'
93
160
  # page.uncheck('German')
94
161
  #
95
- # @param [String] locator Which check box to uncheck
162
+ # # will uncheck `el` if it's a checkbox element
163
+ # el.uncheck()
164
+ #
165
+ #
166
+ # @overload uncheck([locator], **options)
167
+ # @param [String] locator Which check box to uncheck
96
168
  #
97
- def uncheck(locator, options={})
98
- find(:checkbox, locator, options).set(false)
169
+ # @option options [String] option Value of the checkbox to deselect
170
+ # @option options [String, Regexp] id Match fields that match the id attribute
171
+ # @option options [String] name Match fields that match the name attribute
172
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
173
+ # @macro label_click
174
+ # @macro waiting_behavior
175
+ #
176
+ # @return [Capybara::Node::Element] The element unchecked or the label clicked
177
+ def uncheck(locator = nil, **options)
178
+ _check_with_label(:checkbox, false, locator, **options)
99
179
  end
100
180
 
101
181
  ##
102
182
  #
103
- # If `:from` option is present, `select` finds a select box on the page
104
- # and selects a particular option from it.
183
+ # If `from` option is present, {#select} finds a select box, or text input with associated datalist,
184
+ # on the page and selects a particular option from it.
105
185
  # Otherwise it finds an option inside current scope and selects it.
106
- # If the select box is a multiple select, +select+ can be called multiple times to select more than
186
+ # If the select box is a multiple select, {#select} can be called multiple times to select more than
107
187
  # one option.
108
- # The select box can be found via its name, id or label text. The option can be found by its text.
188
+ # The select box can be found via its name, id, {Capybara.configure test_id} attribute, or label text.
189
+ # The option can be found by its text.
190
+ #
191
+ # page.select 'March', from: 'Month'
109
192
  #
110
- # page.select 'March', :from => 'Month'
193
+ # @overload select(value = nil, from: nil, **options)
194
+ # @macro waiting_behavior
111
195
  #
112
- # @param [String] value Which option to select
113
- # @option options [String] :from The id, name or label of the select box
196
+ # @param value [String] Which option to select
197
+ # @param from [String] The id, {Capybara.configure test_id} attribute, name or label of the select box
114
198
  #
115
- def select(value, options={})
116
- if options.has_key?(:from)
117
- from = options.delete(:from)
118
- find(:select, from, options).find(:option, value, options).select_option
199
+ # @return [Capybara::Node::Element] The option element selected
200
+ def select(value = nil, from: nil, **options)
201
+ raise ArgumentError, 'The :from option does not take an element' if from.is_a? Capybara::Node::Element
202
+
203
+ el = from ? find_select_or_datalist_input(from, options) : self
204
+
205
+ if el.respond_to?(:tag_name) && (el.tag_name == 'input')
206
+ select_datalist_option(el, value)
119
207
  else
120
- find(:option, value, options).select_option
208
+ el.find(:option, value, **options).select_option
121
209
  end
122
210
  end
123
211
 
124
212
  ##
125
213
  #
126
214
  # Find a select box on the page and unselect a particular option from it. If the select
127
- # box is a multiple select, +unselect+ can be called multiple times to unselect more than
128
- # one option. The select box can be found via its name, id or label text.
215
+ # box is a multiple select, {#unselect} can be called multiple times to unselect more than
216
+ # one option. The select box can be found via its name, id, {Capybara.configure test_id} attribute,
217
+ # or label text.
129
218
  #
130
- # page.unselect 'March', :from => 'Month'
219
+ # page.unselect 'March', from: 'Month'
131
220
  #
132
- # @param [String] value Which option to unselect
133
- # @param [Hash{:from => String}] options The id, name or label of the select box
221
+ # @overload unselect(value = nil, from: nil, **options)
222
+ # @macro waiting_behavior
134
223
  #
135
- def unselect(value, options={})
136
- if options.has_key?(:from)
137
- from = options.delete(:from)
138
- find(:select, from, options).find(:option, value, options).unselect_option
139
- else
140
- find(:option, value, options).unselect_option
141
- end
224
+ # @param value [String] Which option to unselect
225
+ # @param from [String] The id, {Capybara.configure test_id} attribute, name or label of the select box
226
+ #
227
+ #
228
+ # @return [Capybara::Node::Element] The option element unselected
229
+ def unselect(value = nil, from: nil, **options)
230
+ raise ArgumentError, 'The :from option does not take an element' if from.is_a? Capybara::Node::Element
231
+
232
+ scope = from ? find(:select, from, **options) : self
233
+ scope.find(:option, value, **options).unselect_option
142
234
  end
143
235
 
144
236
  ##
145
237
  #
146
- # Find a file field on the page and attach a file given its path. The file field can
147
- # be found via its name, id or label text.
148
- #
149
- # page.attach_file(locator, '/path/to/file.png')
150
- #
151
- # @param [String] locator Which field to attach the file to
152
- # @param [String] path The path of the file that will be attached, or an array of paths
153
- #
154
- # @option options [Symbol] match (Capybara.match) The matching strategy to use (:one, :first, :prefer_exact, :smart).
155
- # @option options [Boolean] exact (Capybara.exact) Match the exact label name/contents or accept a partial match.
156
- # @option options [Fixnum] wait (Capybara.default_max_wait_time) If using a Javascript driver, maximum number of seconds during which the element will be searched for.
157
- # @option options [Boolean] multiple Match field which allows multiple file selection
158
- #
159
- def attach_file(locator, path, options={})
160
- Array(path).each do |p|
161
- raise Capybara::FileNotFound, "cannot attach file, #{p} does not exist" unless File.exist?(p.to_s)
238
+ # Find a descendant file field on the page and attach a file given its path. There are two ways to use
239
+ # {#attach_file}, in the first method the file field can be found via its name, id,
240
+ # {Capybara.configure test_id} attribute, or label text. In the case of the file field being hidden for
241
+ # styling reasons the `make_visible` option can be used to temporarily change the CSS of
242
+ # the file field, attach the file, and then revert the CSS back to original. If no locator is
243
+ # passed this will match self or a descendant.
244
+ # The second method, which is currently in beta and may be changed/removed, involves passing a block
245
+ # which performs whatever actions would trigger the file chooser to appear.
246
+ #
247
+ # # will attach file to a descendant file input element that has a name, id, or label_text matching 'My File'
248
+ # page.attach_file('My File', '/path/to/file.png')
249
+ #
250
+ # # will attach file to el if it's a file input element
251
+ # el.attach_file('/path/to/file.png')
252
+ #
253
+ # # will attach file to whatever file input is triggered by the block
254
+ # page.attach_file('/path/to/file.png') do
255
+ # page.find('#upload_button').click
256
+ # end
257
+ #
258
+ # @overload attach_file([locator], paths, **options)
259
+ # @macro waiting_behavior
260
+ #
261
+ # @param [String] locator Which field to attach the file to
262
+ # @param [String, Array<String>] paths The path(s) of the file(s) that will be attached
263
+ #
264
+ # @option options [Symbol] match
265
+ # The matching strategy to use (:one, :first, :prefer_exact, :smart). Defaults to {Capybara.configure match}.
266
+ # @option options [Boolean] exact
267
+ # Match the exact label name/contents or accept a partial match. Defaults to {Capybara.configure exact}.
268
+ # @option options [Boolean] multiple Match field which allows multiple file selection
269
+ # @option options [String, Regexp] id Match fields that match the id attribute
270
+ # @option options [String] name Match fields that match the name attribute
271
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
272
+ # @option options [true, Hash] make_visible
273
+ # A Hash of CSS styles to change before attempting to attach the file, if `true`, `{ opacity: 1, display: 'block', visibility: 'visible' }` is used (may not be supported by all drivers).
274
+ # @overload attach_file(paths, &blk)
275
+ # @param [String, Array<String>] paths The path(s) of the file(s) that will be attached
276
+ # @yield Block whose actions will trigger the system file chooser to be shown
277
+ # @return [Capybara::Node::Element] The file field element
278
+ def attach_file(locator = nil, paths, make_visible: nil, **options) # rubocop:disable Style/OptionalArguments
279
+ if locator && block_given?
280
+ raise ArgumentError, '``#attach_file` does not support passing both a locator and a block'
281
+ end
282
+
283
+ Array(paths).each do |path|
284
+ raise Capybara::FileNotFound, "cannot attach file, #{path} does not exist" unless File.exist?(path.to_s)
162
285
  end
163
- find(:file_field, locator, options).set(path)
286
+ options[:allow_self] = true if locator.nil?
287
+
288
+ if block_given?
289
+ begin
290
+ execute_script CAPTURE_FILE_ELEMENT_SCRIPT
291
+ yield
292
+ file_field = evaluate_script 'window._capybara_clicked_file_input'
293
+ raise ArgumentError, "Capybara was unable to determine the file input you're attaching to" unless file_field
294
+ rescue ::Capybara::NotSupportedByDriverError
295
+ warn 'Block mode of `#attach_file` is not supported by the current driver - ignoring.'
296
+ end
297
+ end
298
+ # Allow user to update the CSS style of the file input since they are so often hidden on a page
299
+ if make_visible
300
+ ff = file_field || find(:file_field, locator, **options.merge(visible: :all))
301
+ while_visible(ff, make_visible) { |el| el.set(paths) }
302
+ else
303
+ (file_field || find(:file_field, locator, **options)).set(paths)
304
+ end
305
+ end
306
+
307
+ private
308
+
309
+ def find_select_or_datalist_input(from, options)
310
+ synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
311
+ find(:select, from, **options)
312
+ rescue Capybara::ElementNotFound => select_error # rubocop:disable Naming/RescuedExceptionsVariableName
313
+ raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
314
+
315
+ begin
316
+ find(:datalist_input, from, **options)
317
+ rescue Capybara::ElementNotFound => dlinput_error # rubocop:disable Naming/RescuedExceptionsVariableName
318
+ raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
319
+ end
320
+ end
321
+ end
322
+
323
+ def select_datalist_option(input, value)
324
+ datalist_options = input.evaluate_script(DATALIST_OPTIONS_SCRIPT)
325
+ option = datalist_options.find { |opt| opt.values_at('value', 'label').include?(value) }
326
+ raise ::Capybara::ElementNotFound, %(Unable to find datalist option "#{value}") unless option
327
+
328
+ input.set(option['value'])
329
+ rescue ::Capybara::NotSupportedByDriverError
330
+ # Implement for drivers that don't support JS
331
+ datalist = find(:xpath, XPath.descendant(:datalist)[XPath.attr(:id) == input[:list]], visible: false)
332
+ option = datalist.find(:datalist_option, value, disabled: false)
333
+ input.set(option.value)
334
+ end
335
+
336
+ def while_visible(element, visible_css)
337
+ if visible_css == true
338
+ visible_css = { opacity: 1, display: 'block', visibility: 'visible', width: 'auto', height: 'auto' }
339
+ end
340
+ _update_style(element, visible_css)
341
+ unless element.visible?
342
+ raise ExpectationNotMet, 'The style changes in :make_visible did not make the file input visible'
343
+ end
344
+
345
+ begin
346
+ yield element
347
+ ensure
348
+ _reset_style(element)
349
+ end
350
+ end
351
+
352
+ def _update_style(element, style)
353
+ element.execute_script(UPDATE_STYLE_SCRIPT, style)
354
+ rescue Capybara::NotSupportedByDriverError
355
+ warn 'The :make_visible option is not supported by the current driver - ignoring'
356
+ end
357
+
358
+ def _reset_style(element)
359
+ element.execute_script(RESET_STYLE_SCRIPT)
360
+ rescue StandardError # rubocop:disable Lint/SuppressedException swallow extra errors
164
361
  end
362
+
363
+ def _check_with_label(selector, checked, locator,
364
+ allow_label_click: session_options.automatic_label_click, **options)
365
+ options[:allow_self] = true if locator.nil?
366
+ synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
367
+ el = find(selector, locator, **options)
368
+ el.set(checked)
369
+ rescue StandardError => e
370
+ raise unless allow_label_click && catch_error?(e)
371
+
372
+ begin
373
+ el ||= find(selector, locator, **options.merge(visible: :all))
374
+ el.session.find(:label, for: el, visible: true, match: :first).click unless el.checked? == checked
375
+ rescue StandardError # swallow extra errors - raise original
376
+ raise e
377
+ end
378
+ end
379
+ end
380
+
381
+ UPDATE_STYLE_SCRIPT = <<~'JS'
382
+ this.capybara_style_cache = this.style.cssText;
383
+ var css = arguments[0];
384
+ for (var prop in css){
385
+ if (css.hasOwnProperty(prop)) {
386
+ this.style.setProperty(prop, css[prop], "important");
387
+ }
388
+ }
389
+ JS
390
+
391
+ RESET_STYLE_SCRIPT = <<~'JS'
392
+ if (this.hasOwnProperty('capybara_style_cache')) {
393
+ this.style.cssText = this.capybara_style_cache;
394
+ delete this.capybara_style_cache;
395
+ }
396
+ JS
397
+
398
+ DATALIST_OPTIONS_SCRIPT = <<~'JS'
399
+ Array.prototype.slice.call((this.list||{}).options || []).
400
+ filter(function(el){ return !el.disabled }).
401
+ map(function(el){ return { "value": el.value, "label": el.label} })
402
+ JS
403
+
404
+ CAPTURE_FILE_ELEMENT_SCRIPT = <<~'JS'
405
+ document.addEventListener('click', function file_catcher(e){
406
+ if (e.target.matches("input[type='file']")) {
407
+ window._capybara_clicked_file_input = e.target;
408
+ this.removeEventListener('click', file_catcher);
409
+ e.preventDefault();
410
+ }
411
+ })
412
+ JS
165
413
  end
166
414
  end
167
415
  end