capybara 3.0.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +891 -12
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +29 -10
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +22 -4
  9. data/lib/capybara/driver/node.rb +38 -9
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +57 -8
  12. data/lib/capybara/minitest/spec.rb +185 -84
  13. data/lib/capybara/minitest.rb +264 -145
  14. data/lib/capybara/node/actions.rb +248 -124
  15. data/lib/capybara/node/base.rb +35 -20
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +13 -15
  18. data/lib/capybara/node/element.rb +350 -113
  19. data/lib/capybara/node/finders.rb +104 -82
  20. data/lib/capybara/node/matchers.rb +363 -157
  21. data/lib/capybara/node/simple.rb +54 -15
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +9 -10
  25. data/lib/capybara/queries/base_query.rb +25 -18
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +11 -0
  28. data/lib/capybara/queries/selector_query.rb +617 -104
  29. data/lib/capybara/queries/sibling_query.rb +9 -7
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +40 -22
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +106 -31
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +74 -49
  37. data/lib/capybara/rack_test/node.rb +120 -47
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +87 -53
  44. data/lib/capybara/rspec/features.rb +8 -10
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +142 -315
  60. data/lib/capybara/rspec.rb +3 -2
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +85 -8
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +72 -28
  88. data/lib/capybara/selector/filters/base.rb +45 -2
  89. data/lib/capybara/selector/filters/expression_filter.rb +5 -6
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +18 -4
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +89 -200
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -534
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +270 -245
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +460 -170
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +80 -0
  123. data/lib/capybara/server/checker.rb +44 -0
  124. data/lib/capybara/server/middleware.rb +71 -0
  125. data/lib/capybara/server.rb +58 -67
  126. data/lib/capybara/session/config.rb +40 -6
  127. data/lib/capybara/session/matchers.rb +26 -19
  128. data/lib/capybara/session.rb +252 -194
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +126 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +135 -44
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/{assert_current_path.rb → assert_current_path_spec.rb} +20 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  141. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  142. data/lib/capybara/spec/session/{assert_text.rb → assert_text_spec.rb} +76 -52
  143. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +11 -13
  146. data/lib/capybara/spec/session/check_spec.rb +112 -51
  147. data/lib/capybara/spec/session/choose_spec.rb +62 -30
  148. data/lib/capybara/spec/session/click_button_spec.rb +227 -161
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -30
  150. data/lib/capybara/spec/session/click_link_spec.rb +89 -55
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +44 -37
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +8 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +70 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +110 -44
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +8 -8
  165. data/lib/capybara/spec/session/find_field_spec.rb +33 -31
  166. data/lib/capybara/spec/session/find_link_spec.rb +42 -14
  167. data/lib/capybara/spec/session/find_spec.rb +251 -142
  168. data/lib/capybara/spec/session/first_spec.rb +45 -44
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +6 -6
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +6 -6
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +32 -20
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +46 -19
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +98 -12
  179. data/lib/capybara/spec/session/has_css_spec.rb +271 -137
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +46 -6
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +33 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +117 -69
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +958 -122
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +15 -12
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +65 -37
  199. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +5 -4
  202. data/lib/capybara/spec/session/save_page_spec.rb +41 -38
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +13 -11
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +102 -76
  207. data/lib/capybara/spec/session/selectors_spec.rb +51 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +26 -24
  210. data/lib/capybara/spec/session/title_spec.rb +8 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +41 -22
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +79 -53
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +22 -19
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +4 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +4 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +25 -21
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +10 -5
  219. data/lib/capybara/spec/session/window/window_spec.rb +88 -54
  220. data/lib/capybara/spec/session/window/windows_spec.rb +11 -8
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +17 -16
  222. data/lib/capybara/spec/session/within_spec.rb +82 -44
  223. data/lib/capybara/spec/spec_helper.rb +46 -52
  224. data/lib/capybara/spec/test_app.rb +148 -41
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/form.erb +156 -42
  227. data/lib/capybara/spec/views/frame_child.erb +4 -3
  228. data/lib/capybara/spec/views/frame_one.erb +2 -1
  229. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  230. data/lib/capybara/spec/views/frame_two.erb +1 -1
  231. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  232. data/lib/capybara/spec/views/layout.erb +10 -0
  233. data/lib/capybara/spec/views/obscured.erb +47 -0
  234. data/lib/capybara/spec/views/offset.erb +33 -0
  235. data/lib/capybara/spec/views/path.erb +2 -2
  236. data/lib/capybara/spec/views/popup_one.erb +1 -1
  237. data/lib/capybara/spec/views/popup_two.erb +1 -1
  238. data/lib/capybara/spec/views/react.erb +45 -0
  239. data/lib/capybara/spec/views/scroll.erb +21 -0
  240. data/lib/capybara/spec/views/spatial.erb +31 -0
  241. data/lib/capybara/spec/views/tables.erb +68 -1
  242. data/lib/capybara/spec/views/with_animation.erb +81 -0
  243. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  244. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  245. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  246. data/lib/capybara/spec/views/with_hover.erb +3 -2
  247. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  248. data/lib/capybara/spec/views/with_html.erb +46 -11
  249. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  250. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  251. data/lib/capybara/spec/views/with_js.erb +30 -5
  252. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  253. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  254. data/lib/capybara/spec/views/with_scope.erb +2 -2
  255. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  256. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  257. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  258. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  259. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  260. data/lib/capybara/spec/views/with_windows.erb +1 -1
  261. data/lib/capybara/spec/views/within_frames.erb +1 -1
  262. data/lib/capybara/version.rb +1 -1
  263. data/lib/capybara/window.rb +32 -26
  264. data/lib/capybara.rb +128 -104
  265. data/spec/basic_node_spec.rb +59 -34
  266. data/spec/capybara_spec.rb +65 -51
  267. data/spec/counter_spec.rb +35 -0
  268. data/spec/css_builder_spec.rb +101 -0
  269. data/spec/css_splitter_spec.rb +38 -0
  270. data/spec/dsl_spec.rb +84 -55
  271. data/spec/filter_set_spec.rb +24 -7
  272. data/spec/fixtures/certificate.pem +25 -0
  273. data/spec/fixtures/key.pem +27 -0
  274. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -5
  275. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -5
  276. data/spec/minitest_spec.rb +49 -5
  277. data/spec/minitest_spec_spec.rb +92 -62
  278. data/spec/per_session_config_spec.rb +6 -6
  279. data/spec/rack_test_spec.rb +183 -115
  280. data/spec/regexp_dissassembler_spec.rb +250 -0
  281. data/spec/result_spec.rb +99 -39
  282. data/spec/rspec/features_spec.rb +28 -25
  283. data/spec/rspec/scenarios_spec.rb +10 -6
  284. data/spec/rspec/shared_spec_matchers.rb +418 -364
  285. data/spec/rspec/views_spec.rb +4 -3
  286. data/spec/rspec_matchers_spec.rb +35 -10
  287. data/spec/rspec_spec.rb +109 -85
  288. data/spec/sauce_spec_chrome.rb +43 -0
  289. data/spec/selector_spec.rb +392 -62
  290. data/spec/selenium_spec_chrome.rb +183 -41
  291. data/spec/selenium_spec_chrome_remote.rb +96 -0
  292. data/spec/selenium_spec_edge.rb +41 -8
  293. data/spec/selenium_spec_firefox.rb +228 -0
  294. data/spec/selenium_spec_firefox_remote.rb +94 -0
  295. data/spec/selenium_spec_ie.rb +129 -11
  296. data/spec/selenium_spec_safari.rb +162 -0
  297. data/spec/server_spec.rb +192 -81
  298. data/spec/session_spec.rb +52 -16
  299. data/spec/shared_selenium_node.rb +79 -0
  300. data/spec/shared_selenium_session.rb +460 -123
  301. data/spec/spec_helper.rb +124 -2
  302. data/spec/whitespace_normalizer_spec.rb +54 -0
  303. data/spec/xpath_builder_spec.rb +93 -0
  304. metadata +344 -45
  305. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  306. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  307. data/.yard/yard_extensions.rb +0 -78
  308. data/lib/capybara/rspec/compound.rb +0 -90
  309. data/lib/capybara/spec/session/assert_selector.rb +0 -149
  310. data/lib/capybara/spec/session/source_spec.rb +0 -0
  311. data/lib/capybara/spec/views/with_title.erb +0 -5
  312. data/spec/selenium_spec_marionette.rb +0 -143
@@ -3,105 +3,118 @@
3
3
  module Capybara
4
4
  module Node
5
5
  module Actions
6
- ##
7
- #
8
- # Finds a button or link and clicks it. See {Capybara::Node::Actions#click_button} and
9
- # {Capybara::Node::Actions#click_link} for what locator will match against for each type of element
10
6
  # @!macro waiting_behavior
11
- # If the driver is capable of executing JavaScript, +$0+ will wait for a set amount of time
7
+ # If the driver is capable of executing JavaScript, this method will wait for a set amount of time
12
8
  # and continuously retry finding the element until either the element is found or the time
13
- # expires. The length of time +find+ will wait is controlled through {Capybara.default_max_wait_time}
9
+ # expires. The length of time this method will wait is controlled through {Capybara.configure default_max_wait_time}.
14
10
  #
15
- # @option options [false, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
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}.
13
+
14
+ ##
16
15
  #
17
- # @overload click_link_or_button([locator], options)
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
18
  #
19
- # @param [String] locator See {Capybara::Node::Actions#click_button} and {Capybara::Node::Actions#click_link}
19
+ # @overload click_link_or_button([locator], **options)
20
+ # @macro waiting_behavior
21
+ # @param [String] locator See {#click_button} and {#click_link}
20
22
  #
21
23
  # @return [Capybara::Node::Element] The element clicked
22
24
  #
23
25
  def click_link_or_button(locator = nil, **options)
24
- find(:link_or_button, locator, options).click
26
+ find(:link_or_button, locator, **options).click
25
27
  end
26
28
  alias_method :click_on, :click_link_or_button
27
29
 
28
30
  ##
29
31
  #
30
- # 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
31
33
  # alt text inside the link.
32
34
  #
33
- # @macro waiting_behavior
34
- #
35
- # @overload click_link([locator], options)
36
- # @param [String] locator text, id, title or nested image's alt attribute
37
- # @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}
38
39
  #
39
40
  # @return [Capybara::Node::Element] The element clicked
40
41
  def click_link(locator = nil, **options)
41
- find(:link, locator, options).click
42
+ find(:link, locator, **options).click
42
43
  end
43
44
 
44
45
  ##
45
46
  #
46
47
  # Finds a button on the page and clicks it.
47
- # This can be any \<input> element of type submit, reset, image, button or it can be a
48
- # \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
49
- # by their text content, and image \<input> elements by their alt attribute
50
- #
51
- # @macro waiting_behavior
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.
52
51
  #
53
- # @overload click_button([locator], options)
52
+ # @overload click_button([locator], **options)
53
+ # @macro waiting_behavior
54
54
  # @param [String] locator Which button to find
55
- # @param options See {Capybara::Node::Finders#find_button}
55
+ # @param [Hash] options See {Capybara::Node::Finders#find_button}
56
56
  # @return [Capybara::Node::Element] The element clicked
57
57
  def click_button(locator = nil, **options)
58
- find(:button, locator, options).click
58
+ find(:button, locator, **options).click
59
59
  end
60
60
 
61
61
  ##
62
62
  #
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 or label text.
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.
65
66
  #
67
+ # # will fill in a descendant fillable field with name, id, or label text matching 'Name'
66
68
  # page.fill_in 'Name', with: 'Bob'
67
69
  #
70
+ # # will fill in `el` if it's a fillable field
71
+ # el.fill_in with: 'Tom'
68
72
  #
69
- # @overload fill_in([locator], options={})
73
+ #
74
+ # @overload fill_in([locator], with:, **options)
70
75
  # @param [String] locator Which field to fill in
71
76
  # @param [Hash] options
77
+ # @param with: [String] The value to fill in
72
78
  # @macro waiting_behavior
73
- # @option options [String] :with The value to fill in - required
74
- # @option options [Hash] :fill_options Driver specific options regarding how to fill fields
75
- # @option options [String] :currently_with The current value property of the field to fill in
76
- # @option options [Boolean] :multiple Match fields that can have multiple values?
77
- # @option options [String] :id Match fields that match the id attribute
78
- # @option options [String] :name Match fields that match the name attribute
79
- # @option options [String] :placeholder Match fields that match the placeholder attribute
80
- # @option options [String, Array<String>] :class Match fields that match the class(es) provided
81
- #
82
- # @return [Capybara::Node::Element] The element filled_in
83
- def fill_in(locator = nil, with:, fill_options: {}, **options)
84
- options[:with] = options.delete(:currently_with) if options.key?(:currently_with)
85
- find(:fillable_field, locator, options).set(with, fill_options)
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)
86
92
  end
87
93
 
88
94
  # @!macro label_click
89
- # @option options [Boolean] :allow_label_click (Capybara.automatic_label_click) Attempt to click the label to toggle state if element is non-visible.
95
+ # @option options [Boolean, Hash] 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
+ # If set to a Hash it is passed as options to the `click` on the label
90
98
 
91
99
  ##
92
100
  #
93
- # Find a radio button and mark it as checked. The radio button can be found
94
- # via name, id or label text.
101
+ # Find a descendant radio button and mark it as checked. The radio button can be found
102
+ # via name, id, {Capybara.configure test_id} attribute or label text. If no locator is
103
+ # provided this will match against self or a descendant.
95
104
  #
105
+ # # will choose a descendant radio button with a name, id, or label text matching 'Male'
96
106
  # page.choose('Male')
97
107
  #
98
- # @overload choose([locator], options)
108
+ # # will choose `el` if it's a radio button element
109
+ # el.choose()
110
+ #
111
+ # @overload choose([locator], **options)
99
112
  # @param [String] locator Which radio button to choose
100
113
  #
101
- # @option options [String] :option Value of the radio_button to choose
102
- # @option options [String] :id Match fields that match the id attribute
103
- # @option options [String] :name Match fields that match the name attribute
104
- # @option options [String, Array<String>] :class Match fields that match the class(es) provided
114
+ # @option options [String] option Value of the radio_button to choose
115
+ # @option options [String, Regexp] id Match fields that match the id attribute
116
+ # @option options [String] name Match fields that match the name attribute
117
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
105
118
  # @macro waiting_behavior
106
119
  # @macro label_click
107
120
  #
@@ -112,42 +125,52 @@ module Capybara
112
125
 
113
126
  ##
114
127
  #
115
- # Find a check box and mark it as checked. The check box can be found
116
- # via name, id or label text.
128
+ # Find a descendant check box and mark it as checked. The check box can be found
129
+ # via name, id, {Capybara.configure test_id} attribute, or label text. If no locator
130
+ # is provided this will match against self or a descendant.
117
131
  #
132
+ # # will check a descendant checkbox with a name, id, or label text matching 'German'
118
133
  # page.check('German')
119
134
  #
135
+ # # will check `el` if it's a checkbox element
136
+ # el.check()
137
+ #
120
138
  #
121
- # @overload check([locator], options)
139
+ # @overload check([locator], **options)
122
140
  # @param [String] locator Which check box to check
123
141
  #
124
- # @option options [String] :option Value of the checkbox to select
125
- # @option options [String] id Match fields that match the id attribute
142
+ # @option options [String] option Value of the checkbox to select
143
+ # @option options [String, Regexp] id Match fields that match the id attribute
126
144
  # @option options [String] name Match fields that match the name attribute
127
- # @option options [String, Array<String>] :class Match fields that match the class(es) provided
145
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
128
146
  # @macro label_click
129
147
  # @macro waiting_behavior
130
148
  #
131
149
  # @return [Capybara::Node::Element] The element checked or the label clicked
132
- def check(locator, **options)
150
+ def check(locator = nil, **options)
133
151
  _check_with_label(:checkbox, true, locator, **options)
134
152
  end
135
153
 
136
154
  ##
137
155
  #
138
- # Find a check box and mark uncheck it. The check box can be found
139
- # via name, id or label text.
156
+ # Find a descendant check box and uncheck it. The check box can be found
157
+ # via name, id, {Capybara.configure test_id} attribute, or label text. If
158
+ # no locator is provided this will match against self or a descendant.
140
159
  #
160
+ # # will uncheck a descendant checkbox with a name, id, or label text matching 'German'
141
161
  # page.uncheck('German')
142
162
  #
163
+ # # will uncheck `el` if it's a checkbox element
164
+ # el.uncheck()
143
165
  #
144
- # @overload uncheck([locator], options)
166
+ #
167
+ # @overload uncheck([locator], **options)
145
168
  # @param [String] locator Which check box to uncheck
146
169
  #
147
- # @option options [String] :option Value of the checkbox to deselect
148
- # @option options [String] id Match fields that match the id attribute
170
+ # @option options [String] option Value of the checkbox to deselect
171
+ # @option options [String, Regexp] id Match fields that match the id attribute
149
172
  # @option options [String] name Match fields that match the name attribute
150
- # @option options [String, Array<String>] :class Match fields that match the class(es) provided
173
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
151
174
  # @macro label_click
152
175
  # @macro waiting_behavior
153
176
  #
@@ -158,85 +181,168 @@ module Capybara
158
181
 
159
182
  ##
160
183
  #
161
- # If `:from` option is present, `select` finds a select box on the page
162
- # and selects a particular option from it.
184
+ # If `from` option is present, {#select} finds a select box, or text input with associated datalist,
185
+ # on the page and selects a particular option from it.
163
186
  # Otherwise it finds an option inside current scope and selects it.
164
- # If the select box is a multiple select, +select+ can be called multiple times to select more than
187
+ # If the select box is a multiple select, {#select} can be called multiple times to select more than
165
188
  # one option.
166
- # The select box can be found via its name, id or label text. The option can be found by its text.
189
+ # The select box can be found via its name, id, {Capybara.configure test_id} attribute, or label text.
190
+ # The option can be found by its text.
167
191
  #
168
192
  # page.select 'March', from: 'Month'
169
193
  #
170
- # @macro waiting_behavior
194
+ # @overload select(value = nil, from: nil, **options)
195
+ # @macro waiting_behavior
171
196
  #
172
- # @param [String] value Which option to select
173
- # @option options [String] :from The id, name or label of the select box
197
+ # @param value [String] Which option to select
198
+ # @param from [String] The id, {Capybara.configure test_id} attribute, name or label of the select box
174
199
  #
175
200
  # @return [Capybara::Node::Element] The option element selected
176
201
  def select(value = nil, from: nil, **options)
177
- scope = from ? find(:select, from, options) : self
178
- scope.find(:option, value, options).select_option
202
+ raise ArgumentError, 'The :from option does not take an element' if from.is_a? Capybara::Node::Element
203
+
204
+ el = from ? find_select_or_datalist_input(from, options) : self
205
+
206
+ if el.respond_to?(:tag_name) && (el.tag_name == 'input')
207
+ select_datalist_option(el, value)
208
+ else
209
+ el.find(:option, value, **options).select_option
210
+ end
179
211
  end
180
212
 
181
213
  ##
182
214
  #
183
215
  # Find a select box on the page and unselect a particular option from it. If the select
184
- # box is a multiple select, +unselect+ can be called multiple times to unselect more than
185
- # one option. The select box can be found via its name, id or label text.
216
+ # box is a multiple select, {#unselect} can be called multiple times to unselect more than
217
+ # one option. The select box can be found via its name, id, {Capybara.configure test_id} attribute,
218
+ # or label text.
186
219
  #
187
220
  # page.unselect 'March', from: 'Month'
188
221
  #
189
- # @macro waiting_behavior
222
+ # @overload unselect(value = nil, from: nil, **options)
223
+ # @macro waiting_behavior
224
+ #
225
+ # @param value [String] Which option to unselect
226
+ # @param from [String] The id, {Capybara.configure test_id} attribute, name or label of the select box
190
227
  #
191
- # @param [String] value Which option to unselect
192
- # @param [Hash{:from => String}] options The id, name or label of the select box
193
228
  #
194
229
  # @return [Capybara::Node::Element] The option element unselected
195
230
  def unselect(value = nil, from: nil, **options)
196
- scope = from ? find(:select, from, options) : self
197
- scope.find(:option, value, options).unselect_option
231
+ raise ArgumentError, 'The :from option does not take an element' if from.is_a? Capybara::Node::Element
232
+
233
+ scope = from ? find(:select, from, **options) : self
234
+ scope.find(:option, value, **options).unselect_option
198
235
  end
199
236
 
200
237
  ##
201
238
  #
202
- # Find a file field on the page and attach a file given its path. The file field can
203
- # be found via its name, id or label text.
239
+ # Find a descendant file field on the page and attach a file given its path. There are two ways to use
240
+ # {#attach_file}, in the first method the file field can be found via its name, id,
241
+ # {Capybara.configure test_id} attribute, or label text. In the case of the file field being hidden for
242
+ # styling reasons the `make_visible` option can be used to temporarily change the CSS of
243
+ # the file field, attach the file, and then revert the CSS back to original. If no locator is
244
+ # passed this will match self or a descendant.
245
+ # The second method, which is currently in beta and may be changed/removed, involves passing a block
246
+ # which performs whatever actions would trigger the file chooser to appear.
204
247
  #
205
- # page.attach_file(locator, '/path/to/file.png')
248
+ # # will attach file to a descendant file input element that has a name, id, or label_text matching 'My File'
249
+ # page.attach_file('My File', '/path/to/file.png')
206
250
  #
207
- # @macro waiting_behavior
251
+ # # will attach file to el if it's a file input element
252
+ # el.attach_file('/path/to/file.png')
208
253
  #
209
- # @param [String] locator Which field to attach the file to
210
- # @param [String] path The path of the file that will be attached, or an array of paths
254
+ # # will attach file to whatever file input is triggered by the block
255
+ # page.attach_file('/path/to/file.png') do
256
+ # page.find('#upload_button').click
257
+ # end
211
258
  #
212
- # @option options [Symbol] match (Capybara.match) The matching strategy to use (:one, :first, :prefer_exact, :smart).
213
- # @option options [Boolean] exact (Capybara.exact) Match the exact label name/contents or accept a partial match.
214
- # @option options [Boolean] multiple Match field which allows multiple file selection
215
- # @option options [String] id Match fields that match the id attribute
216
- # @option options [String] name Match fields that match the name attribute
217
- # @option options [String, Array<String>] :class Match fields that match the class(es) provided
218
- # @option options [true, Hash] make_visible 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)
259
+ # @overload attach_file([locator], paths, **options)
260
+ # @macro waiting_behavior
219
261
  #
262
+ # @param [String] locator Which field to attach the file to
263
+ # @param [String, Array<String>] paths The path(s) of the file(s) that will be attached
264
+ #
265
+ # @option options [Symbol] match
266
+ # The matching strategy to use (:one, :first, :prefer_exact, :smart). Defaults to {Capybara.configure match}.
267
+ # @option options [Boolean] exact
268
+ # Match the exact label name/contents or accept a partial match. Defaults to {Capybara.configure exact}.
269
+ # @option options [Boolean] multiple Match field which allows multiple file selection
270
+ # @option options [String, Regexp] id Match fields that match the id attribute
271
+ # @option options [String] name Match fields that match the name attribute
272
+ # @option options [String, Array<String>, Regexp] class Match fields that match the class(es) provided
273
+ # @option options [true, Hash] make_visible
274
+ # 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).
275
+ # @overload attach_file(paths, &blk)
276
+ # @param [String, Array<String>] paths The path(s) of the file(s) that will be attached
277
+ # @yield Block whose actions will trigger the system file chooser to be shown
220
278
  # @return [Capybara::Node::Element] The file field element
221
- def attach_file(locator = nil, path, make_visible: nil, **options) # rubocop:disable Style/OptionalArguments
222
- Array(path).each do |p|
223
- raise Capybara::FileNotFound, "cannot attach file, #{p} does not exist" unless File.exist?(p.to_s)
279
+ def attach_file(locator = nil, paths, make_visible: nil, **options) # rubocop:disable Style/OptionalArguments
280
+ if locator && block_given?
281
+ raise ArgumentError, '`#attach_file` does not support passing both a locator and a block'
282
+ end
283
+
284
+ Array(paths).each do |path|
285
+ raise Capybara::FileNotFound, "cannot attach file, #{path} does not exist" unless File.exist?(path.to_s)
286
+ end
287
+ options[:allow_self] = true if locator.nil?
288
+
289
+ if block_given?
290
+ begin
291
+ execute_script CAPTURE_FILE_ELEMENT_SCRIPT
292
+ yield
293
+ file_field = evaluate_script 'window._capybara_clicked_file_input'
294
+ raise ArgumentError, "Capybara was unable to determine the file input you're attaching to" unless file_field
295
+ rescue ::Capybara::NotSupportedByDriverError
296
+ warn 'Block mode of `#attach_file` is not supported by the current driver - ignoring.'
297
+ end
224
298
  end
225
299
  # Allow user to update the CSS style of the file input since they are so often hidden on a page
226
300
  if make_visible
227
- ff = find(:file_field, locator, options.merge(visible: :all))
228
- while_visible(ff, make_visible) { |el| el.set(path) }
301
+ ff = file_field || find(:file_field, locator, **options.merge(visible: :all))
302
+ while_visible(ff, make_visible) { |el| el.set(paths) }
229
303
  else
230
- find(:file_field, locator, options).set(path)
304
+ (file_field || find(:file_field, locator, **options)).set(paths)
231
305
  end
232
306
  end
233
307
 
234
308
  private
235
309
 
310
+ def find_select_or_datalist_input(from, options)
311
+ synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
312
+ find(:select, from, **options)
313
+ rescue Capybara::ElementNotFound => select_error # rubocop:disable Naming/RescuedExceptionsVariableName
314
+ raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
315
+
316
+ begin
317
+ find(:datalist_input, from, **options)
318
+ rescue Capybara::ElementNotFound => dlinput_error
319
+ raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
320
+ end
321
+ end
322
+ end
323
+
324
+ def select_datalist_option(input, value)
325
+ datalist_options = input.evaluate_script(DATALIST_OPTIONS_SCRIPT)
326
+ option = datalist_options.find { |opt| opt.values_at('value', 'label').include?(value) }
327
+ raise ::Capybara::ElementNotFound, %(Unable to find datalist option "#{value}") unless option
328
+
329
+ input.set(option['value'])
330
+ rescue ::Capybara::NotSupportedByDriverError
331
+ # Implement for drivers that don't support JS
332
+ datalist = find(:xpath, XPath.descendant(:datalist)[XPath.attr(:id) == input[:list]], visible: false)
333
+ option = datalist.find(:datalist_option, value, disabled: false)
334
+ input.set(option.value)
335
+ end
336
+
236
337
  def while_visible(element, visible_css)
237
- visible_css = { opacity: 1, display: 'block', visibility: 'visible' } if visible_css == true
338
+ if visible_css == true
339
+ visible_css = { opacity: 1, display: 'block', visibility: 'visible', width: 'auto', height: 'auto' }
340
+ end
238
341
  _update_style(element, visible_css)
239
- raise ExpectationNotMet, "The style changes in :make_visible did not make the file input visible" unless element.visible?
342
+ unless element.visible?
343
+ raise ExpectationNotMet, 'The style changes in :make_visible did not make the file input visible'
344
+ end
345
+
240
346
  begin
241
347
  yield element
242
348
  ensure
@@ -245,52 +351,70 @@ module Capybara
245
351
  end
246
352
 
247
353
  def _update_style(element, style)
248
- session.execute_script(UPDATE_STYLE_SCRIPT, element, style)
354
+ element.execute_script(UPDATE_STYLE_SCRIPT, style)
249
355
  rescue Capybara::NotSupportedByDriverError
250
- warn "The :make_visible option is not supported by the current driver - ignoring"
356
+ warn 'The :make_visible option is not supported by the current driver - ignoring'
251
357
  end
252
358
 
253
359
  def _reset_style(element)
254
- session.execute_script(RESET_STYLE_SCRIPT, element)
255
- rescue # swallow extra errors
360
+ element.execute_script(RESET_STYLE_SCRIPT)
361
+ rescue StandardError # rubocop:disable Lint/SuppressedException swallow extra errors
256
362
  end
257
363
 
258
- def _check_with_label(selector, checked, locator, allow_label_click: session_options.automatic_label_click, **options)
364
+ def _check_with_label(selector, checked, locator,
365
+ allow_label_click: session_options.automatic_label_click, **options)
366
+ options[:allow_self] = true if locator.nil?
259
367
  synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
368
+ el = find(selector, locator, **options)
369
+ el.set(checked)
370
+ rescue StandardError => e
371
+ raise unless allow_label_click && catch_error?(e)
372
+
260
373
  begin
261
- el = find(selector, locator, options)
262
- el.set(checked)
263
- rescue => e
264
- raise unless allow_label_click && catch_error?(e)
265
- begin
266
- el ||= find(selector, locator, options.merge(visible: :all))
267
- res = find(:label, for: el, visible: true).click unless el.checked? == checked
268
- res
269
- rescue # swallow extra errors - raise original
270
- raise e
374
+ el ||= find(selector, locator, **options.merge(visible: :all))
375
+ unless el.checked? == checked
376
+ el.session
377
+ .find(:label, for: el, visible: true, match: :first)
378
+ .click(**(Hash.try_convert(allow_label_click) || {}))
271
379
  end
380
+ rescue StandardError # swallow extra errors - raise original
381
+ raise e
272
382
  end
273
383
  end
274
384
  end
275
385
 
276
- UPDATE_STYLE_SCRIPT = <<-'JS'.freeze
277
- var el = arguments[0];
278
- el.capybara_style_cache = el.style.cssText;
279
- var css = arguments[1];
386
+ UPDATE_STYLE_SCRIPT = <<~JS
387
+ this.capybara_style_cache = this.style.cssText;
388
+ var css = arguments[0];
280
389
  for (var prop in css){
281
390
  if (css.hasOwnProperty(prop)) {
282
- el.style[prop] = css[prop]
391
+ this.style.setProperty(prop, css[prop], "important");
283
392
  }
284
393
  }
285
394
  JS
286
395
 
287
- RESET_STYLE_SCRIPT = <<-'JS'.freeze
288
- var el = arguments[0];
289
- if (el.hasOwnProperty('capybara_style_cache')) {
290
- el.style.cssText = el.capybara_style_cache;
291
- delete el.capybara_style_cache;
396
+ RESET_STYLE_SCRIPT = <<~JS
397
+ if (this.hasOwnProperty('capybara_style_cache')) {
398
+ this.style.cssText = this.capybara_style_cache;
399
+ delete this.capybara_style_cache;
292
400
  }
293
401
  JS
402
+
403
+ DATALIST_OPTIONS_SCRIPT = <<~JS
404
+ Array.prototype.slice.call((this.list||{}).options || []).
405
+ filter(function(el){ return !el.disabled }).
406
+ map(function(el){ return { "value": el.value, "label": el.label} })
407
+ JS
408
+
409
+ CAPTURE_FILE_ELEMENT_SCRIPT = <<~JS
410
+ document.addEventListener('click', function file_catcher(e){
411
+ if (e.target.matches("input[type='file']")) {
412
+ window._capybara_clicked_file_input = e.target;
413
+ this.removeEventListener('click', file_catcher);
414
+ e.preventDefault();
415
+ }
416
+ }, {capture: true})
417
+ JS
294
418
  end
295
419
  end
296
420
  end
@@ -73,36 +73,51 @@ module Capybara
73
73
  # @return [Object] The result of the given block
74
74
  # @raise [Capybara::FrozenInTime] If the return value of `Time.now` appears stuck
75
75
  #
76
- def synchronize(seconds = session_options.default_max_wait_time, errors: nil)
77
- if session.synchronized
76
+ def synchronize(seconds = nil, errors: nil)
77
+ return yield if session.synchronized
78
+
79
+ seconds = session_options.default_max_wait_time if [nil, true].include? seconds
80
+ interval = session_options.default_retry_interval
81
+ session.synchronized = true
82
+ timer = Capybara::Helpers.timer(expire_in: seconds)
83
+ begin
78
84
  yield
79
- else
80
- session.synchronized = true
81
- start_time = Capybara::Helpers.monotonic_time
82
- begin
83
- yield
84
- rescue => e
85
- session.raise_server_error!
86
- raise e unless driver.wait? && catch_error?(e, errors)
87
- raise e if (Capybara::Helpers.monotonic_time - start_time) >= seconds
88
- sleep(0.05)
89
- raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if Capybara::Helpers.monotonic_time == start_time
85
+ rescue StandardError => e
86
+ session.raise_server_error!
87
+ raise e unless catch_error?(e, errors)
88
+
89
+ if driver.wait?
90
+ raise e if timer.expired?
91
+
92
+ sleep interval
93
+ reload if session_options.automatic_reload
94
+ else
95
+ old_base = @base
90
96
  reload if session_options.automatic_reload
91
- retry
92
- ensure
93
- session.synchronized = false
97
+ raise e if old_base == @base
94
98
  end
99
+ retry
100
+ ensure
101
+ session.synchronized = false
95
102
  end
96
103
  end
97
104
 
98
105
  # @api private
99
- def find_css(css)
100
- base.find_css(css)
106
+ def find_css(css, **options)
107
+ if base.method(:find_css).arity == 1
108
+ base.find_css(css)
109
+ else
110
+ base.find_css(css, **options)
111
+ end
101
112
  end
102
113
 
103
114
  # @api private
104
- def find_xpath(xpath)
105
- base.find_xpath(xpath)
115
+ def find_xpath(xpath, **options)
116
+ if base.method(:find_xpath).arity == 1
117
+ base.find_xpath(xpath)
118
+ else
119
+ base.find_xpath(xpath, **options)
120
+ end
106
121
  end
107
122
 
108
123
  # @api private
@@ -20,8 +20,8 @@ module Capybara
20
20
  #
21
21
  # @return [String] The text of the document
22
22
  #
23
- def text(type = nil)
24
- find(:xpath, '/html').text(type)
23
+ def text(type = nil, normalize_ws: false)
24
+ find(:xpath, '/html').text(type, normalize_ws: normalize_ws)
25
25
  end
26
26
 
27
27
  ##
@@ -31,6 +31,18 @@ module Capybara
31
31
  def title
32
32
  session.driver.title
33
33
  end
34
+
35
+ def execute_script(*args)
36
+ find(:xpath, '/html').execute_script(*args)
37
+ end
38
+
39
+ def evaluate_script(*args)
40
+ find(:xpath, '/html').evaluate_script(*args)
41
+ end
42
+
43
+ def scroll_to(*args, quirks: false, **options)
44
+ find(:xpath, quirks ? '//body' : '/html').scroll_to(*args, **options)
45
+ end
34
46
  end
35
47
  end
36
48
  end