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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Node
4
-
5
5
  ##
6
6
  #
7
7
  # A {Capybara::Node::Base} represents either an element on a page through the subclass
@@ -17,13 +17,13 @@ module Capybara
17
17
  #
18
18
  # session = Capybara::Session.new(:rack_test, my_app)
19
19
  # session.visit('/')
20
- # session.fill_in('Foo', :with => 'Bar') # from Capybara::Node::Actions
20
+ # session.fill_in('Foo', with: 'Bar') # from Capybara::Node::Actions
21
21
  # bar = session.find('#bar') # from Capybara::Node::Finders
22
- # bar.select('Baz', :from => 'Quox') # from Capybara::Node::Actions
22
+ # bar.select('Baz', from: 'Quox') # from Capybara::Node::Actions
23
23
  # session.has_css?('#foobar') # from Capybara::Node::Matchers
24
24
  #
25
25
  class Base
26
- attr_reader :session, :base, :parent
26
+ attr_reader :session, :base, :query_scope
27
27
 
28
28
  include Capybara::Node::Finders
29
29
  include Capybara::Node::Actions
@@ -67,54 +67,72 @@ module Capybara
67
67
  # time has passed. On rubies/platforms which don't support access to a monotonic process clock
68
68
  # if the return value of `Time.now` is stubbed out, Capybara will raise `Capybara::FrozenInTime`.
69
69
  #
70
- # @param [Integer] seconds Number of seconds to retry this block
71
- # @param options [Hash]
72
- # @option options [Array<Exception>] :errors (driver.invalid_element_errors +
70
+ # @param [Integer] seconds (current sessions default_max_wait_time) Maximum number of seconds to retry this block
71
+ # @param [Array<Exception>] errors (driver.invalid_element_errors +
73
72
  # [Capybara::ElementNotFound]) exception types that cause the block to be rerun
74
73
  # @return [Object] The result of the given block
75
74
  # @raise [Capybara::FrozenInTime] If the return value of `Time.now` appears stuck
76
75
  #
77
- def synchronize(seconds=Capybara.default_max_wait_time, options = {})
78
- start_time = Capybara::Helpers.monotonic_time
76
+ def synchronize(seconds = nil, errors: nil)
77
+ return yield if session.synchronized
79
78
 
80
- if session.synchronized
79
+ seconds = session_options.default_max_wait_time if [nil, true].include? seconds
80
+ session.synchronized = true
81
+ timer = Capybara::Helpers.timer(expire_in: seconds)
82
+ begin
81
83
  yield
82
- else
83
- session.synchronized = true
84
- begin
85
- yield
86
- rescue => e
87
- session.raise_server_error!
88
- raise e unless driver.wait?
89
- raise e unless catch_error?(e, options[:errors])
90
- raise e if (Capybara::Helpers.monotonic_time - start_time) >= seconds
91
- sleep(0.05)
92
- 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
93
- reload if Capybara.automatic_reload
94
- retry
95
- ensure
96
- session.synchronized = false
84
+ rescue StandardError => e
85
+ session.raise_server_error!
86
+ raise e unless catch_error?(e, errors)
87
+
88
+ if driver.wait?
89
+ raise e if timer.expired?
90
+
91
+ sleep(0.01)
92
+ reload if session_options.automatic_reload
93
+ else
94
+ old_base = @base
95
+ reload if session_options.automatic_reload
96
+ raise e if old_base == @base
97
97
  end
98
+ retry
99
+ ensure
100
+ session.synchronized = false
101
+ end
102
+ end
103
+
104
+ # @api private
105
+ def find_css(css, **options)
106
+ if base.method(:find_css).arity == 1
107
+ base.find_css(css)
108
+ else
109
+ base.find_css(css, **options)
98
110
  end
99
111
  end
100
112
 
101
113
  # @api private
102
- def find_css(css)
103
- base.find_css(css)
114
+ def find_xpath(xpath, **options)
115
+ if base.method(:find_xpath).arity == 1
116
+ base.find_xpath(xpath)
117
+ else
118
+ base.find_xpath(xpath, **options)
119
+ end
104
120
  end
105
121
 
106
122
  # @api private
107
- def find_xpath(xpath)
108
- base.find_xpath(xpath)
123
+ def session_options
124
+ session.config
125
+ end
126
+
127
+ def to_capybara_node
128
+ self
109
129
  end
110
130
 
111
131
  protected
112
132
 
113
133
  def catch_error?(error, errors = nil)
114
134
  errors ||= (driver.invalid_element_errors + [Capybara::ElementNotFound])
115
- errors.any? do |type|
116
- error.is_a?(type)
117
- end
135
+ errors.any? { |type| error.is_a?(type) }
118
136
  end
119
137
 
120
138
  def driver
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Node
4
-
5
5
  ##
6
6
  #
7
7
  # A {Capybara::Document} represents an HTML document. Any operation
@@ -20,13 +20,29 @@ 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
+ ##
28
+ #
29
+ # @return [String] The title of the document
30
+ #
27
31
  def title
28
32
  session.driver.title
29
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, **options)
44
+ find(:xpath, '//body').scroll_to(*args, **options)
45
+ end
30
46
  end
31
47
  end
32
48
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Node
4
5
  module DocumentMatchers
@@ -6,22 +7,19 @@ module Capybara
6
7
  # Asserts that the page has the given title.
7
8
  #
8
9
  # @!macro title_query_params
9
- # @overload $0(string, options = {})
10
+ # @overload $0(string, **options)
10
11
  # @param string [String] The string that title should include
11
- # @overload $0(regexp, options = {})
12
+ # @overload $0(regexp, **options)
12
13
  # @param regexp [Regexp] The regexp that title should match to
13
14
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for title to eq/match given string/regexp argument
15
+ # @option options [Boolean] :exact (false) When passed a string should the match be exact or just substring
14
16
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
15
17
  # @return [true]
16
18
  #
17
- def assert_title(title, options = {})
18
- query = Capybara::Queries::TitleQuery.new(title, options)
19
- synchronize(query.wait) do
20
- unless query.resolves_for?(self)
21
- raise Capybara::ExpectationNotMet, query.failure_message
22
- end
19
+ def assert_title(title, **options)
20
+ _verify_title(title, options) do |query|
21
+ raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self)
23
22
  end
24
- return true
25
23
  end
26
24
 
27
25
  ##
@@ -31,14 +29,10 @@ module Capybara
31
29
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
32
30
  # @return [true]
33
31
  #
34
- def assert_no_title(title, options = {})
35
- query = Capybara::Queries::TitleQuery.new(title, options)
36
- synchronize(query.wait) do
37
- if query.resolves_for?(self)
38
- raise Capybara::ExpectationNotMet, query.negative_failure_message
39
- end
32
+ def assert_no_title(title, **options)
33
+ _verify_title(title, options) do |query|
34
+ raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self)
40
35
  end
41
- return true
42
36
  end
43
37
 
44
38
  ##
@@ -47,10 +41,8 @@ module Capybara
47
41
  # @macro title_query_params
48
42
  # @return [Boolean]
49
43
  #
50
- def has_title?(title, options = {})
51
- assert_title(title, options)
52
- rescue Capybara::ExpectationNotMet
53
- return false
44
+ def has_title?(title, **options)
45
+ make_predicate(options) { assert_title(title, **options) }
54
46
  end
55
47
 
56
48
  ##
@@ -59,10 +51,16 @@ module Capybara
59
51
  # @macro title_query_params
60
52
  # @return [Boolean]
61
53
  #
62
- def has_no_title?(title, options = {})
63
- assert_no_title(title, options)
64
- rescue Capybara::ExpectationNotMet
65
- return false
54
+ def has_no_title?(title, **options)
55
+ make_predicate(options) { assert_no_title(title, **options) }
56
+ end
57
+
58
+ private
59
+
60
+ def _verify_title(title, options)
61
+ query = Capybara::Queries::TitleQuery.new(title, **options)
62
+ synchronize(query.wait) { yield(query) }
63
+ true
66
64
  end
67
65
  end
68
66
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Node
4
-
5
5
  ##
6
6
  #
7
7
  # A {Capybara::Node::Element} represents a single element on the page. It is possible
@@ -10,7 +10,7 @@ module Capybara
10
10
  # session = Capybara::Session.new(:rack_test, my_app)
11
11
  #
12
12
  # bar = session.find('#bar') # from Capybara::Node::Finders
13
- # bar.select('Baz', :from => 'Quox') # from Capybara::Node::Actions
13
+ # bar.select('Baz', from: 'Quox') # from Capybara::Node::Actions
14
14
  #
15
15
  # {Capybara::Node::Element} also has access to HTML attributes and other properties of the
16
16
  # element:
@@ -22,14 +22,16 @@ module Capybara
22
22
  # @see Capybara::Node
23
23
  #
24
24
  class Element < Base
25
-
26
- def initialize(session, base, parent, query)
25
+ def initialize(session, base, query_scope, query)
27
26
  super(session, base)
28
- @parent = parent
27
+ @query_scope = query_scope
29
28
  @query = query
29
+ @allow_reload = false
30
+ @query_idx = nil
30
31
  end
31
32
 
32
- def allow_reload!
33
+ def allow_reload!(idx = nil)
34
+ @query_idx = idx
33
35
  @allow_reload = true
34
36
  end
35
37
 
@@ -43,30 +45,25 @@ module Capybara
43
45
 
44
46
  ##
45
47
  #
46
- # Retrieve the text of the element. If `Capybara.ignore_hidden_elements`
48
+ # Retrieve the text of the element. If {Capybara.configure ignore_hidden_elements}
47
49
  # is `true`, which it is by default, then this will return only text
48
50
  # which is visible. The exact semantics of this may differ between
49
51
  # drivers, but generally any text within elements with `display:none` is
50
52
  # ignored. This behaviour can be overridden by passing `:all` to this
51
53
  # method.
52
54
  #
53
- # @param [:all, :visible] type Whether to return only visible or all text
55
+ # @param type [:all, :visible] Whether to return only visible or all text
54
56
  # @return [String] The text of the element
55
57
  #
56
- def text(type=nil)
57
- type ||= :all unless Capybara.ignore_hidden_elements or Capybara.visible_text_only
58
- synchronize do
59
- if type == :all
60
- base.all_text
61
- else
62
- base.visible_text
63
- end
64
- end
58
+ def text(type = nil, normalize_ws: false)
59
+ type ||= :all unless session_options.ignore_hidden_elements || session_options.visible_text_only
60
+ txt = synchronize { type == :all ? base.all_text : base.visible_text }
61
+ normalize_ws ? txt.gsub(/[[:space:]]+/, ' ').strip : txt
65
62
  end
66
63
 
67
64
  ##
68
65
  #
69
- # Retrieve the given attribute
66
+ # Retrieve the given attribute.
70
67
  #
71
68
  # element[:title] # => HTML title attribute
72
69
  #
@@ -77,6 +74,30 @@ module Capybara
77
74
  synchronize { base[attribute] }
78
75
  end
79
76
 
77
+ ##
78
+ #
79
+ # Retrieve the given CSS styles.
80
+ #
81
+ # element.style('color', 'font-size') # => Computed values of CSS 'color' and 'font-size' styles
82
+ #
83
+ # @param [Array<String>] styles Names of the desired CSS properties
84
+ # @return [Hash] Hash of the CSS property names to computed values
85
+ #
86
+ def style(*styles)
87
+ styles = styles.flatten.map(&:to_s)
88
+ raise ArgumentError, 'You must specify at least one CSS style' if styles.empty?
89
+
90
+ begin
91
+ synchronize { base.style(styles) }
92
+ rescue NotImplementedError => e
93
+ begin
94
+ evaluate_script(STYLE_SCRIPT, *styles)
95
+ rescue Capybara::NotSupportedByDriverError
96
+ raise e
97
+ end
98
+ end
99
+ end
100
+
80
101
  ##
81
102
  #
82
103
  # @return [String] The value of the form element
@@ -90,147 +111,179 @@ module Capybara
90
111
  # Set the value of the form element to the given value.
91
112
  #
92
113
  # @param [String] value The new value
93
- # @param [Hash{}] options Driver specific options for how to set the value
114
+ # @param [Hash] options Driver specific options for how to set the value. Take default values from {Capybara.configure default_set_options}.
94
115
  #
95
- def set(value, options={})
96
- options ||= {}
97
-
98
- driver_supports_options = (base.method(:set).arity != 1)
99
-
100
- unless options.empty? || driver_supports_options
101
- warn "Options passed to Capybara::Node#set but the driver doesn't support them"
116
+ # @return [Capybara::Node::Element] The element
117
+ def set(value, **options)
118
+ if ENV['CAPYBARA_THOROUGH'] && readonly?
119
+ raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}"
102
120
  end
103
121
 
104
- synchronize do
105
- if driver_supports_options
106
- base.set(value, options)
107
- else
108
- base.set(value)
109
- end
110
- end
122
+ options = session_options.default_set_options.to_h.merge(options)
123
+ synchronize { base.set(value, **options) }
124
+ self
111
125
  end
112
126
 
113
127
  ##
114
128
  #
115
- # Select this node if is an option element inside a select tag
129
+ # Select this node if it is an option element inside a select tag.
116
130
  #
117
- def select_option
118
- warn "Attempt to select disabled option: #{value || text}" if disabled?
119
- synchronize { base.select_option }
131
+ # @!macro action_waiting_behavior
132
+ # If the driver dynamic pages (JS) and the element is currently non-interactable, this method will
133
+ # continuously retry the action until either the element becomes interactable or the maximum
134
+ # wait time expires.
135
+ #
136
+ # @param [false, Numeric] wait
137
+ # Maximum time to wait for the action to succeed. Defaults to {Capybara.configure default_max_wait_time}.
138
+ # @return [Capybara::Node::Element] The element
139
+ def select_option(wait: nil)
140
+ synchronize(wait) { base.select_option }
141
+ self
120
142
  end
121
143
 
122
144
  ##
123
145
  #
124
- # Unselect this node if is an option element inside a multiple select tag
146
+ # Unselect this node if it is an option element inside a multiple select tag.
125
147
  #
126
- def unselect_option
127
- synchronize { base.unselect_option }
148
+ # @macro action_waiting_behavior
149
+ # @return [Capybara::Node::Element] The element
150
+ def unselect_option(wait: nil)
151
+ synchronize(wait) { base.unselect_option }
152
+ self
128
153
  end
129
154
 
130
155
  ##
131
156
  #
132
- # Click the Element
133
- #
134
- def click
135
- synchronize { base.click }
157
+ # Click the Element.
158
+ #
159
+ # @macro action_waiting_behavior
160
+ # @!macro click_modifiers
161
+ # Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element.
162
+ # @overload $0(*modifier_keys, wait: nil, **offset)
163
+ # @param *modifier_keys [:alt, :control, :meta, :shift] ([]) Keys to be held down when clicking
164
+ # @option options [Integer] x X coordinate to offset the click location. If {Capybara.configure w3c_click_offset} is `true` the
165
+ # offset will be from the element center, otherwise it will be from the top left corner of the element
166
+ # @option options [Integer] y Y coordinate to offset the click location. If {Capybara.configure w3c_click_offset} is `true` the
167
+ # offset will be from the element center, otherwise it will be from the top left corner of the element
168
+ # @option options [Float] delay Delay between the mouse down and mouse up events in seconds (0)
169
+ # @return [Capybara::Node::Element] The element
170
+ def click(*keys, **options)
171
+ perform_click_action(keys, **options) do |k, opts|
172
+ base.click(k, **opts)
173
+ end
136
174
  end
137
175
 
138
176
  ##
139
177
  #
140
- # Right Click the Element
178
+ # Right Click the Element.
141
179
  #
142
- def right_click
143
- synchronize { base.right_click }
180
+ # @macro action_waiting_behavior
181
+ # @macro click_modifiers
182
+ # @option options [Float] delay Delay between the mouse down and mouse up events in seconds (0)
183
+ # @return [Capybara::Node::Element] The element
184
+ def right_click(*keys, **options)
185
+ perform_click_action(keys, **options) do |k, opts|
186
+ base.right_click(k, **opts)
187
+ end
144
188
  end
145
189
 
146
190
  ##
147
191
  #
148
- # Double Click the Element
192
+ # Double Click the Element.
149
193
  #
150
- def double_click
151
- synchronize { base.double_click }
194
+ # @macro action_waiting_behavior
195
+ # @macro click_modifiers
196
+ # @return [Capybara::Node::Element] The element
197
+ def double_click(*keys, **options)
198
+ perform_click_action(keys, **options) do |k, opts|
199
+ base.double_click(k, **opts)
200
+ end
152
201
  end
153
202
 
154
203
  ##
155
204
  #
156
- # Send Keystrokes to the Element
205
+ # Send Keystrokes to the Element.
157
206
  #
158
207
  # @overload send_keys(keys, ...)
159
- # @param [String, Symbol, Array<String,Symbol>] keys
208
+ # @param keys [String, Symbol, Array<String,Symbol>]
160
209
  #
161
210
  # Examples:
162
211
  #
163
212
  # element.send_keys "foo" #=> value: 'foo'
164
- # element.send_keys "tet", :left, "s" #=> value: 'test'
213
+ # element.send_keys "tet", :left, "s" #=> value: 'test'
165
214
  # element.send_keys [:control, 'a'], :space #=> value: ' ' - assuming ctrl-a selects all contents
166
215
  #
167
- # Symbols supported for keys
168
- # :cancel
169
- # :help
170
- # :backspace
171
- # :tab
172
- # :clear
173
- # :return
174
- # :enter
175
- # :shift
176
- # :control
177
- # :alt
178
- # :pause
179
- # :escape
180
- # :space
181
- # :page_up
182
- # :page_down
183
- # :end
184
- # :home
185
- # :left
186
- # :up
187
- # :right
188
- # :down
189
- # :insert
190
- # :delete
191
- # :semicolon
192
- # :equals
193
- # :numpad0
194
- # :numpad1
195
- # :numpad2
196
- # :numpad3
197
- # :numpad4
198
- # :numpad5
199
- # :numpad6
200
- # :numpad7
201
- # :numpad8
202
- # :numpad9
203
- # :multiply - numeric keypad *
204
- # :add - numeric keypad +
205
- # :separator - numeric keypad 'separator' key ??
206
- # :subtract - numeric keypad -
207
- # :decimal - numeric keypad .
208
- # :divide - numeric keypad /
209
- # :f1
210
- # :f2
211
- # :f3
212
- # :f4
213
- # :f5
214
- # :f6
215
- # :f7
216
- # :f8
217
- # :f9
218
- # :f10
219
- # :f11
220
- # :f12
221
- # :meta
222
- # :command - alias of :meta
223
- #
216
+ # Symbols supported for keys:
217
+ # * :cancel
218
+ # * :help
219
+ # * :backspace
220
+ # * :tab
221
+ # * :clear
222
+ # * :return
223
+ # * :enter
224
+ # * :shift
225
+ # * :control
226
+ # * :alt
227
+ # * :pause
228
+ # * :escape
229
+ # * :space
230
+ # * :page_up
231
+ # * :page_down
232
+ # * :end
233
+ # * :home
234
+ # * :left
235
+ # * :up
236
+ # * :right
237
+ # * :down
238
+ # * :insert
239
+ # * :delete
240
+ # * :semicolon
241
+ # * :equals
242
+ # * :numpad0
243
+ # * :numpad1
244
+ # * :numpad2
245
+ # * :numpad3
246
+ # * :numpad4
247
+ # * :numpad5
248
+ # * :numpad6
249
+ # * :numpad7
250
+ # * :numpad8
251
+ # * :numpad9
252
+ # * :multiply - numeric keypad *
253
+ # * :add - numeric keypad +
254
+ # * :separator - numeric keypad 'separator' key ??
255
+ # * :subtract - numeric keypad -
256
+ # * :decimal - numeric keypad .
257
+ # * :divide - numeric keypad /
258
+ # * :f1
259
+ # * :f2
260
+ # * :f3
261
+ # * :f4
262
+ # * :f5
263
+ # * :f6
264
+ # * :f7
265
+ # * :f8
266
+ # * :f9
267
+ # * :f10
268
+ # * :f11
269
+ # * :f12
270
+ # * :meta
271
+ # * :command - alias of :meta
272
+ #
273
+ # @return [Capybara::Node::Element] The element
224
274
  def send_keys(*args)
225
275
  synchronize { base.send_keys(*args) }
276
+ self
226
277
  end
227
278
 
228
279
  ##
229
280
  #
230
- # Hover on the Element
281
+ # Hover on the Element.
231
282
  #
283
+ # @return [Capybara::Node::Element] The element
232
284
  def hover
233
285
  synchronize { base.hover }
286
+ self
234
287
  end
235
288
 
236
289
  ##
@@ -238,7 +291,8 @@ module Capybara
238
291
  # @return [String] The tag name of the element
239
292
  #
240
293
  def tag_name
241
- synchronize { base.tag_name }
294
+ # Element type is immutable so cache it
295
+ @tag_name ||= initial_cache[:tag_name] || synchronize { base.tag_name }
242
296
  end
243
297
 
244
298
  ##
@@ -252,6 +306,17 @@ module Capybara
252
306
  synchronize { base.visible? }
253
307
  end
254
308
 
309
+ ##
310
+ #
311
+ # Whether or not the element is currently in the viewport and it (or descendants)
312
+ # would be considered clickable at the elements center point.
313
+ #
314
+ # @return [Boolean] Whether the elements center is obscured.
315
+ #
316
+ def obscured?
317
+ synchronize { base.obscured? }
318
+ end
319
+
255
320
  ##
256
321
  #
257
322
  # Whether or not the element is checked.
@@ -284,7 +349,27 @@ module Capybara
284
349
 
285
350
  ##
286
351
  #
287
- # An XPath expression describing where on the page the element can be found
352
+ # Whether or not the element is readonly.
353
+ #
354
+ # @return [Boolean] Whether the element is readonly
355
+ #
356
+ def readonly?
357
+ synchronize { base.readonly? }
358
+ end
359
+
360
+ ##
361
+ #
362
+ # Whether or not the element supports multiple results.
363
+ #
364
+ # @return [Boolean] Whether the element supports multiple results.
365
+ #
366
+ def multiple?
367
+ synchronize { base.multiple? }
368
+ end
369
+
370
+ ##
371
+ #
372
+ # An XPath expression describing where on the page the element can be found.
288
373
  #
289
374
  # @return [String] An XPath expression
290
375
  #
@@ -292,15 +377,23 @@ module Capybara
292
377
  synchronize { base.path }
293
378
  end
294
379
 
380
+ def rect
381
+ synchronize { base.rect }
382
+ end
383
+
295
384
  ##
296
385
  #
297
386
  # Trigger any event on the current element, for example mouseover or focus
298
- # events. Does not work in Selenium.
387
+ # events. Not supported with the Selenium driver, and SHOULDN'T BE USED IN TESTING unless you
388
+ # fully understand why you're using it, that it can allow actions a user could never
389
+ # perform, and that it may completely invalidate your test.
299
390
  #
300
391
  # @param [String] event The name of the event to trigger
301
392
  #
393
+ # @return [Capybara::Node::Element] The element
302
394
  def trigger(event)
303
395
  synchronize { base.trigger(event) }
396
+ self
304
397
  end
305
398
 
306
399
  ##
@@ -312,27 +405,197 @@ module Capybara
312
405
  # source.drag_to(target)
313
406
  #
314
407
  # @param [Capybara::Node::Element] node The element to drag to
408
+ # @param [Hash] options Driver specific options for dragging. May not be supported by all drivers.
409
+ # @option options [Numeric] :delay (0.05) When using Chrome/Firefox with Selenium and HTML5 dragging this is the number
410
+ # of seconds between each stage of the drag.
411
+ # @option options [Boolean] :html5 When using Chrome/Firefox with Selenium enables to force the use of HTML5
412
+ # (true) or legacy (false) dragging. If not specified the driver will attempt to
413
+ # detect the correct method to use.
414
+ # @option options [Array<Symbol>,Symbol] :drop_modifiers Modifier keys which should be held while the dragged element is dropped.
415
+ #
416
+ #
417
+ # @return [Capybara::Node::Element] The dragged element
418
+ def drag_to(node, **options)
419
+ synchronize { base.drag_to(node.base, **options) }
420
+ self
421
+ end
422
+
423
+ ##
424
+ #
425
+ # Drop items on the current element.
426
+ #
427
+ # target = page.find('#foo')
428
+ # target.drop('/some/path/file.csv')
429
+ #
430
+ # @overload drop(path, ...)
431
+ # @param [String, #to_path] path Location of the file to drop on the element
432
+ #
433
+ # @overload drop(strings, ...)
434
+ # @param [Hash] strings A hash of type to data to be dropped - `{ "text/url" => "https://www.google.com" }`
435
+ #
436
+ # @return [Capybara::Node::Element] The element
437
+ def drop(*args)
438
+ options = args.map { |arg| arg.respond_to?(:to_path) ? arg.to_path : arg }
439
+ synchronize { base.drop(*options) }
440
+ self
441
+ end
442
+
443
+ ##
444
+ #
445
+ # Scroll the page or element.
446
+ #
447
+ # @overload scroll_to(position, offset: [0,0])
448
+ # Scroll the page or element to its top, bottom or middle.
449
+ # @param [:top, :bottom, :center, :current] position
450
+ # @param [[Integer, Integer]] offset
451
+ #
452
+ # @overload scroll_to(element, align: :top)
453
+ # Scroll the page or current element until the given element is aligned at the top, bottom, or center of it.
454
+ # @param [Capybara::Node::Element] element The element to be scrolled into view
455
+ # @param [:top, :bottom, :center] align Where to align the element being scrolled into view with relation to the current page/element if possible
456
+ #
457
+ # @overload scroll_to(x,y)
458
+ # @param [Integer] x Horizontal scroll offset
459
+ # @param [Integer] y Vertical scroll offset
460
+ #
461
+ # @return [Capybara::Node::Element] The element
462
+ def scroll_to(pos_or_el_or_x, y = nil, align: :top, offset: nil)
463
+ case pos_or_el_or_x
464
+ when Symbol
465
+ synchronize { base.scroll_to(nil, pos_or_el_or_x) } unless pos_or_el_or_x == :current
466
+ when Capybara::Node::Element
467
+ synchronize { base.scroll_to(pos_or_el_or_x.base, align) }
468
+ else
469
+ synchronize { base.scroll_to(nil, nil, [pos_or_el_or_x, y]) }
470
+ end
471
+ synchronize { base.scroll_by(*offset) } unless offset.nil?
472
+ self
473
+ end
474
+
475
+ ##
476
+ #
477
+ # Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
478
+ # complex objects, such as jQuery statements. {#execute_script} should be used over
479
+ # {#evaluate_script} whenever a result is not expected or needed. `this` in the script will refer to the element this is called on.
480
+ #
481
+ # @param [String] script A string of JavaScript to execute
482
+ # @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
483
+ #
484
+ def execute_script(script, *args)
485
+ session.execute_script(<<~JS, self, *args)
486
+ (function (){
487
+ #{script}
488
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
489
+ JS
490
+ end
491
+
492
+ ##
493
+ #
494
+ # Evaluate the given JS in the context of the element and return the result. Be careful when using this with
495
+ # scripts that return complex objects, such as jQuery statements. {#execute_script} might
496
+ # be a better alternative. `this` in the script will refer to the element this is called on.
497
+ #
498
+ # @param [String] script A string of JavaScript to evaluate
499
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
315
500
  #
316
- def drag_to(node)
317
- synchronize { base.drag_to(node.base) }
501
+ def evaluate_script(script, *args)
502
+ session.evaluate_script(<<~JS, self, *args)
503
+ (function(){
504
+ return #{script.strip}
505
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
506
+ JS
318
507
  end
319
508
 
509
+ ##
510
+ #
511
+ # Evaluate the given JavaScript in the context of the element and obtain the result from a
512
+ # callback function which will be passed as the last argument to the script. `this` in the
513
+ # script will refer to the element this is called on.
514
+ #
515
+ # @param [String] script A string of JavaScript to evaluate
516
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
517
+ #
518
+ def evaluate_async_script(script, *args)
519
+ session.evaluate_async_script(<<~JS, self, *args)
520
+ (function (){
521
+ #{script}
522
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
523
+ JS
524
+ end
525
+
526
+ ##
527
+ #
528
+ # Toggle the elements background color between white and black for a period of time.
529
+ #
530
+ # @return [Capybara::Node::Element] The element
531
+ def flash
532
+ execute_script(<<~JS, 100)
533
+ async function flash(el, delay){
534
+ var old_bg = el.style.backgroundColor;
535
+ var colors = ["black", "white"];
536
+ for(var i=0; i<20; i++){
537
+ el.style.backgroundColor = colors[i % colors.length];
538
+ await new Promise(resolve => setTimeout(resolve, delay));
539
+ }
540
+ el.style.backgroundColor = old_bg;
541
+ }
542
+ flash(this, arguments[0]);
543
+ JS
544
+
545
+ self
546
+ end
547
+
548
+ # @api private
320
549
  def reload
321
- if @allow_reload
322
- begin
323
- reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
324
- @base = reloaded.base if reloaded
325
- rescue => e
326
- raise e unless catch_error?(e)
327
- end
550
+ return self unless @allow_reload
551
+
552
+ begin
553
+ reloaded = @query.resolve_for(query_scope.reload)[@query_idx.to_i]
554
+ @base = reloaded.base if reloaded
555
+ rescue StandardError => e
556
+ raise e unless catch_error?(e)
328
557
  end
329
558
  self
330
559
  end
331
560
 
561
+ ##
562
+ #
563
+ # A human-readable representation of the element.
564
+ #
565
+ # @return [String] A string representation
332
566
  def inspect
333
- %(#<Capybara::Node::Element tag="#{tag_name}" path="#{path}">)
567
+ %(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
334
568
  rescue NotSupportedByDriverError
335
- %(#<Capybara::Node::Element tag="#{tag_name}">)
569
+ %(#<Capybara::Node::Element tag="#{base.tag_name}">)
570
+ rescue *session.driver.invalid_element_errors
571
+ %(Obsolete #<Capybara::Node::Element>)
572
+ end
573
+
574
+ # @api private
575
+ def initial_cache
576
+ base.respond_to?(:initial_cache) ? base.initial_cache : {}
577
+ end
578
+
579
+ STYLE_SCRIPT = <<~JS
580
+ (function(){
581
+ var s = window.getComputedStyle(this);
582
+ var result = {};
583
+ for (var i = arguments.length; i--; ) {
584
+ var property_name = arguments[i];
585
+ result[property_name] = s.getPropertyValue(property_name);
586
+ }
587
+ return result;
588
+ }).apply(this, arguments)
589
+ JS
590
+
591
+ private
592
+
593
+ def perform_click_action(keys, wait: nil, **options)
594
+ raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ options[:x] ^ options[:y]
595
+
596
+ options[:offset] ||= :center if session_options.w3c_click_offset
597
+ synchronize(wait) { yield keys, options }
598
+ self
336
599
  end
337
600
  end
338
601
  end