capybara 3.32.2

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 (313) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/History.md +1813 -0
  4. data/License.txt +22 -0
  5. data/README.md +1099 -0
  6. data/lib/capybara.rb +511 -0
  7. data/lib/capybara/config.rb +94 -0
  8. data/lib/capybara/cucumber.rb +27 -0
  9. data/lib/capybara/driver/base.rb +170 -0
  10. data/lib/capybara/driver/node.rb +139 -0
  11. data/lib/capybara/dsl.rb +65 -0
  12. data/lib/capybara/helpers.rb +108 -0
  13. data/lib/capybara/minitest.rb +386 -0
  14. data/lib/capybara/minitest/spec.rb +264 -0
  15. data/lib/capybara/node/actions.rb +420 -0
  16. data/lib/capybara/node/base.rb +143 -0
  17. data/lib/capybara/node/document.rb +48 -0
  18. data/lib/capybara/node/document_matchers.rb +67 -0
  19. data/lib/capybara/node/element.rb +606 -0
  20. data/lib/capybara/node/finders.rb +325 -0
  21. data/lib/capybara/node/matchers.rb +883 -0
  22. data/lib/capybara/node/simple.rb +208 -0
  23. data/lib/capybara/queries/ancestor_query.rb +27 -0
  24. data/lib/capybara/queries/base_query.rb +106 -0
  25. data/lib/capybara/queries/current_path_query.rb +51 -0
  26. data/lib/capybara/queries/match_query.rb +26 -0
  27. data/lib/capybara/queries/selector_query.rb +710 -0
  28. data/lib/capybara/queries/sibling_query.rb +26 -0
  29. data/lib/capybara/queries/style_query.rb +45 -0
  30. data/lib/capybara/queries/text_query.rb +110 -0
  31. data/lib/capybara/queries/title_query.rb +39 -0
  32. data/lib/capybara/rack_test/browser.rb +140 -0
  33. data/lib/capybara/rack_test/css_handlers.rb +13 -0
  34. data/lib/capybara/rack_test/driver.rb +109 -0
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +127 -0
  37. data/lib/capybara/rack_test/node.rb +325 -0
  38. data/lib/capybara/rails.rb +16 -0
  39. data/lib/capybara/registrations/drivers.rb +36 -0
  40. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  41. data/lib/capybara/registrations/servers.rb +44 -0
  42. data/lib/capybara/result.rb +190 -0
  43. data/lib/capybara/rspec.rb +29 -0
  44. data/lib/capybara/rspec/features.rb +23 -0
  45. data/lib/capybara/rspec/matcher_proxies.rb +82 -0
  46. data/lib/capybara/rspec/matchers.rb +201 -0
  47. data/lib/capybara/rspec/matchers/base.rb +111 -0
  48. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  49. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  50. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  51. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  52. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  53. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  54. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  55. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  56. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  57. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  58. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  59. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  60. data/lib/capybara/selector.rb +233 -0
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
  63. data/lib/capybara/selector/css.rb +102 -0
  64. data/lib/capybara/selector/definition.rb +276 -0
  65. data/lib/capybara/selector/definition/button.rb +51 -0
  66. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  67. data/lib/capybara/selector/definition/css.rb +10 -0
  68. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  69. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  70. data/lib/capybara/selector/definition/element.rb +27 -0
  71. data/lib/capybara/selector/definition/field.rb +40 -0
  72. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  73. data/lib/capybara/selector/definition/file_field.rb +13 -0
  74. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  75. data/lib/capybara/selector/definition/frame.rb +17 -0
  76. data/lib/capybara/selector/definition/id.rb +6 -0
  77. data/lib/capybara/selector/definition/label.rb +62 -0
  78. data/lib/capybara/selector/definition/link.rb +46 -0
  79. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  80. data/lib/capybara/selector/definition/option.rb +27 -0
  81. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  82. data/lib/capybara/selector/definition/select.rb +81 -0
  83. data/lib/capybara/selector/definition/table.rb +109 -0
  84. data/lib/capybara/selector/definition/table_row.rb +21 -0
  85. data/lib/capybara/selector/definition/xpath.rb +5 -0
  86. data/lib/capybara/selector/filter.rb +5 -0
  87. data/lib/capybara/selector/filter_set.rb +124 -0
  88. data/lib/capybara/selector/filters/base.rb +77 -0
  89. data/lib/capybara/selector/filters/expression_filter.rb +22 -0
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +31 -0
  92. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  93. data/lib/capybara/selector/selector.rb +147 -0
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  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 +496 -0
  100. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +119 -0
  101. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +126 -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 +78 -0
  110. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  111. data/lib/capybara/selenium/node.rb +610 -0
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +119 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +131 -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 +47 -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.rb +126 -0
  124. data/lib/capybara/server/animation_disabler.rb +58 -0
  125. data/lib/capybara/server/checker.rb +44 -0
  126. data/lib/capybara/server/middleware.rb +69 -0
  127. data/lib/capybara/session.rb +942 -0
  128. data/lib/capybara/session/config.rb +124 -0
  129. data/lib/capybara/session/matchers.rb +87 -0
  130. data/lib/capybara/spec/fixtures/another_test_file.txt +1 -0
  131. data/lib/capybara/spec/fixtures/capybara.jpg +3 -0
  132. data/lib/capybara/spec/fixtures/no_extension +1 -0
  133. data/lib/capybara/spec/fixtures/test_file.txt +1 -0
  134. data/lib/capybara/spec/public/jquery-ui.js +13 -0
  135. data/lib/capybara/spec/public/jquery.js +5 -0
  136. data/lib/capybara/spec/public/offset.js +6 -0
  137. data/lib/capybara/spec/public/test.js +268 -0
  138. data/lib/capybara/spec/session/accept_alert_spec.rb +81 -0
  139. data/lib/capybara/spec/session/accept_confirm_spec.rb +32 -0
  140. data/lib/capybara/spec/session/accept_prompt_spec.rb +78 -0
  141. data/lib/capybara/spec/session/all_spec.rb +278 -0
  142. data/lib/capybara/spec/session/ancestor_spec.rb +88 -0
  143. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +140 -0
  144. data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
  145. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  146. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  147. data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
  148. data/lib/capybara/spec/session/assert_title_spec.rb +93 -0
  149. data/lib/capybara/spec/session/attach_file_spec.rb +216 -0
  150. data/lib/capybara/spec/session/body_spec.rb +23 -0
  151. data/lib/capybara/spec/session/check_spec.rb +235 -0
  152. data/lib/capybara/spec/session/choose_spec.rb +121 -0
  153. data/lib/capybara/spec/session/click_button_spec.rb +506 -0
  154. data/lib/capybara/spec/session/click_link_or_button_spec.rb +129 -0
  155. data/lib/capybara/spec/session/click_link_spec.rb +229 -0
  156. data/lib/capybara/spec/session/current_scope_spec.rb +31 -0
  157. data/lib/capybara/spec/session/current_url_spec.rb +115 -0
  158. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +36 -0
  159. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +21 -0
  160. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +38 -0
  161. data/lib/capybara/spec/session/element/match_css_spec.rb +31 -0
  162. data/lib/capybara/spec/session/element/match_xpath_spec.rb +25 -0
  163. data/lib/capybara/spec/session/element/matches_selector_spec.rb +120 -0
  164. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
  165. data/lib/capybara/spec/session/evaluate_script_spec.rb +49 -0
  166. data/lib/capybara/spec/session/execute_script_spec.rb +28 -0
  167. data/lib/capybara/spec/session/fill_in_spec.rb +286 -0
  168. data/lib/capybara/spec/session/find_button_spec.rb +74 -0
  169. data/lib/capybara/spec/session/find_by_id_spec.rb +33 -0
  170. data/lib/capybara/spec/session/find_field_spec.rb +113 -0
  171. data/lib/capybara/spec/session/find_link_spec.rb +70 -0
  172. data/lib/capybara/spec/session/find_spec.rb +531 -0
  173. data/lib/capybara/spec/session/first_spec.rb +156 -0
  174. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  175. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  176. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +116 -0
  177. data/lib/capybara/spec/session/frame/within_frame_spec.rb +112 -0
  178. data/lib/capybara/spec/session/go_back_spec.rb +12 -0
  179. data/lib/capybara/spec/session/go_forward_spec.rb +14 -0
  180. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  181. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  182. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  183. data/lib/capybara/spec/session/has_button_spec.rb +69 -0
  184. data/lib/capybara/spec/session/has_css_spec.rb +374 -0
  185. data/lib/capybara/spec/session/has_current_path_spec.rb +138 -0
  186. data/lib/capybara/spec/session/has_field_spec.rb +349 -0
  187. data/lib/capybara/spec/session/has_link_spec.rb +39 -0
  188. data/lib/capybara/spec/session/has_none_selectors_spec.rb +78 -0
  189. data/lib/capybara/spec/session/has_select_spec.rb +310 -0
  190. data/lib/capybara/spec/session/has_selector_spec.rb +202 -0
  191. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  192. data/lib/capybara/spec/session/has_table_spec.rb +198 -0
  193. data/lib/capybara/spec/session/has_text_spec.rb +394 -0
  194. data/lib/capybara/spec/session/has_title_spec.rb +71 -0
  195. data/lib/capybara/spec/session/has_xpath_spec.rb +149 -0
  196. data/lib/capybara/spec/session/headers_spec.rb +8 -0
  197. data/lib/capybara/spec/session/html_spec.rb +47 -0
  198. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  199. data/lib/capybara/spec/session/node_spec.rb +1292 -0
  200. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  201. data/lib/capybara/spec/session/refresh_spec.rb +33 -0
  202. data/lib/capybara/spec/session/reset_session_spec.rb +148 -0
  203. data/lib/capybara/spec/session/response_code_spec.rb +8 -0
  204. data/lib/capybara/spec/session/save_and_open_page_spec.rb +21 -0
  205. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +43 -0
  206. data/lib/capybara/spec/session/save_page_spec.rb +110 -0
  207. data/lib/capybara/spec/session/save_screenshot_spec.rb +55 -0
  208. data/lib/capybara/spec/session/screenshot_spec.rb +18 -0
  209. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  210. data/lib/capybara/spec/session/select_spec.rb +229 -0
  211. data/lib/capybara/spec/session/selectors_spec.rb +98 -0
  212. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  213. data/lib/capybara/spec/session/source_spec.rb +0 -0
  214. data/lib/capybara/spec/session/text_spec.rb +74 -0
  215. data/lib/capybara/spec/session/title_spec.rb +29 -0
  216. data/lib/capybara/spec/session/uncheck_spec.rb +100 -0
  217. data/lib/capybara/spec/session/unselect_spec.rb +116 -0
  218. data/lib/capybara/spec/session/visit_spec.rb +204 -0
  219. data/lib/capybara/spec/session/window/become_closed_spec.rb +89 -0
  220. data/lib/capybara/spec/session/window/current_window_spec.rb +28 -0
  221. data/lib/capybara/spec/session/window/open_new_window_spec.rb +31 -0
  222. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +132 -0
  223. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +99 -0
  224. data/lib/capybara/spec/session/window/window_spec.rb +203 -0
  225. data/lib/capybara/spec/session/window/windows_spec.rb +34 -0
  226. data/lib/capybara/spec/session/window/within_window_spec.rb +157 -0
  227. data/lib/capybara/spec/session/within_spec.rb +199 -0
  228. data/lib/capybara/spec/spec_helper.rb +134 -0
  229. data/lib/capybara/spec/test_app.rb +226 -0
  230. data/lib/capybara/spec/views/animated.erb +49 -0
  231. data/lib/capybara/spec/views/buttons.erb +5 -0
  232. data/lib/capybara/spec/views/fieldsets.erb +30 -0
  233. data/lib/capybara/spec/views/form.erb +685 -0
  234. data/lib/capybara/spec/views/frame_child.erb +18 -0
  235. data/lib/capybara/spec/views/frame_one.erb +10 -0
  236. data/lib/capybara/spec/views/frame_parent.erb +9 -0
  237. data/lib/capybara/spec/views/frame_two.erb +9 -0
  238. data/lib/capybara/spec/views/header_links.erb +8 -0
  239. data/lib/capybara/spec/views/host_links.erb +13 -0
  240. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  241. data/lib/capybara/spec/views/obscured.erb +47 -0
  242. data/lib/capybara/spec/views/offset.erb +32 -0
  243. data/lib/capybara/spec/views/path.erb +13 -0
  244. data/lib/capybara/spec/views/popup_one.erb +9 -0
  245. data/lib/capybara/spec/views/popup_two.erb +9 -0
  246. data/lib/capybara/spec/views/postback.erb +14 -0
  247. data/lib/capybara/spec/views/react.erb +45 -0
  248. data/lib/capybara/spec/views/scroll.erb +20 -0
  249. data/lib/capybara/spec/views/spatial.erb +31 -0
  250. data/lib/capybara/spec/views/tables.erb +130 -0
  251. data/lib/capybara/spec/views/with_animation.erb +74 -0
  252. data/lib/capybara/spec/views/with_base_tag.erb +11 -0
  253. data/lib/capybara/spec/views/with_count.erb +8 -0
  254. data/lib/capybara/spec/views/with_dragula.erb +22 -0
  255. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  256. data/lib/capybara/spec/views/with_hover.erb +24 -0
  257. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  258. data/lib/capybara/spec/views/with_html.erb +208 -0
  259. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  260. data/lib/capybara/spec/views/with_html_entities.erb +2 -0
  261. data/lib/capybara/spec/views/with_js.erb +160 -0
  262. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  263. data/lib/capybara/spec/views/with_namespace.erb +20 -0
  264. data/lib/capybara/spec/views/with_scope.erb +42 -0
  265. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  266. data/lib/capybara/spec/views/with_simple_html.erb +2 -0
  267. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  268. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  269. data/lib/capybara/spec/views/with_title.erb +5 -0
  270. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  271. data/lib/capybara/spec/views/with_windows.erb +54 -0
  272. data/lib/capybara/spec/views/within_frames.erb +15 -0
  273. data/lib/capybara/version.rb +5 -0
  274. data/lib/capybara/window.rb +146 -0
  275. data/spec/basic_node_spec.rb +154 -0
  276. data/spec/capybara_spec.rb +112 -0
  277. data/spec/css_builder_spec.rb +101 -0
  278. data/spec/css_splitter_spec.rb +38 -0
  279. data/spec/dsl_spec.rb +276 -0
  280. data/spec/filter_set_spec.rb +46 -0
  281. data/spec/fixtures/capybara.csv +1 -0
  282. data/spec/fixtures/certificate.pem +25 -0
  283. data/spec/fixtures/key.pem +27 -0
  284. data/spec/fixtures/selenium_driver_rspec_failure.rb +13 -0
  285. data/spec/fixtures/selenium_driver_rspec_success.rb +13 -0
  286. data/spec/minitest_spec.rb +163 -0
  287. data/spec/minitest_spec_spec.rb +162 -0
  288. data/spec/per_session_config_spec.rb +68 -0
  289. data/spec/rack_test_spec.rb +268 -0
  290. data/spec/regexp_dissassembler_spec.rb +250 -0
  291. data/spec/result_spec.rb +196 -0
  292. data/spec/rspec/features_spec.rb +99 -0
  293. data/spec/rspec/scenarios_spec.rb +19 -0
  294. data/spec/rspec/shared_spec_matchers.rb +947 -0
  295. data/spec/rspec/views_spec.rb +14 -0
  296. data/spec/rspec_matchers_spec.rb +62 -0
  297. data/spec/rspec_spec.rb +145 -0
  298. data/spec/sauce_spec_chrome.rb +43 -0
  299. data/spec/selector_spec.rb +513 -0
  300. data/spec/selenium_spec_chrome.rb +188 -0
  301. data/spec/selenium_spec_chrome_remote.rb +96 -0
  302. data/spec/selenium_spec_edge.rb +47 -0
  303. data/spec/selenium_spec_firefox.rb +208 -0
  304. data/spec/selenium_spec_firefox_remote.rb +80 -0
  305. data/spec/selenium_spec_ie.rb +150 -0
  306. data/spec/selenium_spec_safari.rb +148 -0
  307. data/spec/server_spec.rb +292 -0
  308. data/spec/session_spec.rb +91 -0
  309. data/spec/shared_selenium_node.rb +83 -0
  310. data/spec/shared_selenium_session.rb +476 -0
  311. data/spec/spec_helper.rb +100 -0
  312. data/spec/xpath_builder_spec.rb +93 -0
  313. metadata +753 -0
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Node
5
+ ##
6
+ #
7
+ # A {Capybara::Node::Base} represents either an element on a page through the subclass
8
+ # {Capybara::Node::Element} or a document through {Capybara::Node::Document}.
9
+ #
10
+ # Both types of Node share the same methods, used for interacting with the
11
+ # elements on the page. These methods are divided into three categories,
12
+ # finders, actions and matchers. These are found in the modules
13
+ # {Capybara::Node::Finders}, {Capybara::Node::Actions} and {Capybara::Node::Matchers}
14
+ # respectively.
15
+ #
16
+ # A {Capybara::Session} exposes all methods from {Capybara::Node::Document} directly:
17
+ #
18
+ # session = Capybara::Session.new(:rack_test, my_app)
19
+ # session.visit('/')
20
+ # session.fill_in('Foo', with: 'Bar') # from Capybara::Node::Actions
21
+ # bar = session.find('#bar') # from Capybara::Node::Finders
22
+ # bar.select('Baz', from: 'Quox') # from Capybara::Node::Actions
23
+ # session.has_css?('#foobar') # from Capybara::Node::Matchers
24
+ #
25
+ class Base
26
+ attr_reader :session, :base, :query_scope
27
+
28
+ include Capybara::Node::Finders
29
+ include Capybara::Node::Actions
30
+ include Capybara::Node::Matchers
31
+
32
+ def initialize(session, base)
33
+ @session = session
34
+ @base = base
35
+ end
36
+
37
+ # overridden in subclasses, e.g. Capybara::Node::Element
38
+ def reload
39
+ self
40
+ end
41
+
42
+ ##
43
+ #
44
+ # This method is Capybara's primary defence against asynchronicity
45
+ # problems. It works by attempting to run a given block of code until it
46
+ # succeeds. The exact behaviour of this method depends on a number of
47
+ # factors. Basically there are certain exceptions which, when raised
48
+ # from the block, instead of bubbling up, are caught, and the block is
49
+ # re-run.
50
+ #
51
+ # Certain drivers, such as RackTest, have no support for asynchronous
52
+ # processes, these drivers run the block, and any error raised bubbles up
53
+ # immediately. This allows faster turn around in the case where an
54
+ # expectation fails.
55
+ #
56
+ # Only exceptions that are {Capybara::ElementNotFound} or any subclass
57
+ # thereof cause the block to be rerun. Drivers may specify additional
58
+ # exceptions which also cause reruns. This usually occurs when a node is
59
+ # manipulated which no longer exists on the page. For example, the
60
+ # Selenium driver specifies
61
+ # `Selenium::WebDriver::Error::ObsoleteElementError`.
62
+ #
63
+ # As long as any of these exceptions are thrown, the block is re-run,
64
+ # until a certain amount of time passes. The amount of time defaults to
65
+ # {Capybara.default_max_wait_time} and can be overridden through the `seconds`
66
+ # argument. This time is compared with the system time to see how much
67
+ # time has passed. On rubies/platforms which don't support access to a monotonic process clock
68
+ # if the return value of `Time.now` is stubbed out, Capybara will raise `Capybara::FrozenInTime`.
69
+ #
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 +
72
+ # [Capybara::ElementNotFound]) exception types that cause the block to be rerun
73
+ # @return [Object] The result of the given block
74
+ # @raise [Capybara::FrozenInTime] If the return value of `Time.now` appears stuck
75
+ #
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
+ session.synchronized = true
81
+ timer = Capybara::Helpers.timer(expire_in: seconds)
82
+ begin
83
+ yield
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
+ 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, **options)
108
+ else
109
+ base.find_css(css)
110
+ end
111
+ end
112
+
113
+ # @api private
114
+ def find_xpath(xpath, **options)
115
+ if base.method(:find_xpath).arity != 1
116
+ base.find_xpath(xpath, **options)
117
+ else
118
+ base.find_xpath(xpath)
119
+ end
120
+ end
121
+
122
+ # @api private
123
+ def session_options
124
+ session.config
125
+ end
126
+
127
+ def to_capybara_node
128
+ self
129
+ end
130
+
131
+ protected
132
+
133
+ def catch_error?(error, errors = nil)
134
+ errors ||= (driver.invalid_element_errors + [Capybara::ElementNotFound])
135
+ errors.any? { |type| error.is_a?(type) }
136
+ end
137
+
138
+ def driver
139
+ session.driver
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Node
5
+ ##
6
+ #
7
+ # A {Capybara::Document} represents an HTML document. Any operation
8
+ # performed on it will be performed on the entire document.
9
+ #
10
+ # @see Capybara::Node
11
+ #
12
+ class Document < Base
13
+ include Capybara::Node::DocumentMatchers
14
+
15
+ def inspect
16
+ %(#<Capybara::Document>)
17
+ end
18
+
19
+ ##
20
+ #
21
+ # @return [String] The text of the document
22
+ #
23
+ def text(type = nil, normalize_ws: false)
24
+ find(:xpath, '/html').text(type, normalize_ws: normalize_ws)
25
+ end
26
+
27
+ ##
28
+ #
29
+ # @return [String] The title of the document
30
+ #
31
+ def title
32
+ session.driver.title
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
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Node
5
+ module DocumentMatchers
6
+ ##
7
+ # Asserts that the page has the given title.
8
+ #
9
+ # @!macro title_query_params
10
+ # @overload $0(string, **options)
11
+ # @param string [String] The string that title should include
12
+ # @overload $0(regexp, **options)
13
+ # @param regexp [Regexp] The regexp that title should match to
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
16
+ # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
17
+ # @return [true]
18
+ #
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)
22
+ end
23
+ end
24
+
25
+ ##
26
+ # Asserts that the page doesn't have the given title.
27
+ #
28
+ # @macro title_query_params
29
+ # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
30
+ # @return [true]
31
+ #
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)
35
+ end
36
+ end
37
+
38
+ ##
39
+ # Checks if the page has the given title.
40
+ #
41
+ # @macro title_query_params
42
+ # @return [Boolean]
43
+ #
44
+ def has_title?(title, **options)
45
+ make_predicate(options) { assert_title(title, **options) }
46
+ end
47
+
48
+ ##
49
+ # Checks if the page doesn't have the given title.
50
+ #
51
+ # @macro title_query_params
52
+ # @return [Boolean]
53
+ #
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
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,606 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Node
5
+ ##
6
+ #
7
+ # A {Capybara::Node::Element} represents a single element on the page. It is possible
8
+ # to interact with the contents of this element the same as with a document:
9
+ #
10
+ # session = Capybara::Session.new(:rack_test, my_app)
11
+ #
12
+ # bar = session.find('#bar') # from Capybara::Node::Finders
13
+ # bar.select('Baz', from: 'Quox') # from Capybara::Node::Actions
14
+ #
15
+ # {Capybara::Node::Element} also has access to HTML attributes and other properties of the
16
+ # element:
17
+ #
18
+ # bar.value
19
+ # bar.text
20
+ # bar[:title]
21
+ #
22
+ # @see Capybara::Node
23
+ #
24
+ class Element < Base
25
+ def initialize(session, base, query_scope, query)
26
+ super(session, base)
27
+ @query_scope = query_scope
28
+ @query = query
29
+ @allow_reload = false
30
+ @query_idx = nil
31
+ end
32
+
33
+ def allow_reload!(idx = nil)
34
+ @query_idx = idx
35
+ @allow_reload = true
36
+ end
37
+
38
+ ##
39
+ #
40
+ # @return [Object] The native element from the driver, this allows access to driver specific methods
41
+ #
42
+ def native
43
+ synchronize { base.native }
44
+ end
45
+
46
+ ##
47
+ #
48
+ # Retrieve the text of the element. If {Capybara.configure ignore_hidden_elements}
49
+ # is `true`, which it is by default, then this will return only text
50
+ # which is visible. The exact semantics of this may differ between
51
+ # drivers, but generally any text within elements with `display:none` is
52
+ # ignored. This behaviour can be overridden by passing `:all` to this
53
+ # method.
54
+ #
55
+ # @param type [:all, :visible] Whether to return only visible or all text
56
+ # @return [String] The text of the element
57
+ #
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
62
+ end
63
+
64
+ ##
65
+ #
66
+ # Retrieve the given attribute.
67
+ #
68
+ # element[:title] # => HTML title attribute
69
+ #
70
+ # @param [Symbol] attribute The attribute to retrieve
71
+ # @return [String] The value of the attribute
72
+ #
73
+ def [](attribute)
74
+ synchronize { base[attribute] }
75
+ end
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
+
101
+ ##
102
+ #
103
+ # @return [String] The value of the form element
104
+ #
105
+ def value
106
+ synchronize { base.value }
107
+ end
108
+
109
+ ##
110
+ #
111
+ # Set the value of the form element to the given value.
112
+ #
113
+ # @param [String] value The new value
114
+ # @param [Hash] options Driver specific options for how to set the value. Take default values from {Capybara.configure default_set_options}.
115
+ #
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}"
120
+ end
121
+
122
+ options = session_options.default_set_options.to_h.merge(options)
123
+ synchronize { base.set(value, **options) }
124
+ self
125
+ end
126
+
127
+ ##
128
+ #
129
+ # Select this node if it is an option element inside a select tag.
130
+ #
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
142
+ end
143
+
144
+ ##
145
+ #
146
+ # Unselect this node if it is an option element inside a multiple select tag.
147
+ #
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
153
+ end
154
+
155
+ ##
156
+ #
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
174
+ end
175
+
176
+ ##
177
+ #
178
+ # Right Click the Element.
179
+ #
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
188
+ end
189
+
190
+ ##
191
+ #
192
+ # Double Click the Element.
193
+ #
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
201
+ end
202
+
203
+ ##
204
+ #
205
+ # Send Keystrokes to the Element.
206
+ #
207
+ # @overload send_keys(keys, ...)
208
+ # @param keys [String, Symbol, Array<String,Symbol>]
209
+ #
210
+ # Examples:
211
+ #
212
+ # element.send_keys "foo" #=> value: 'foo'
213
+ # element.send_keys "tet", :left, "s" #=> value: 'test'
214
+ # element.send_keys [:control, 'a'], :space #=> value: ' ' - assuming ctrl-a selects all contents
215
+ #
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
274
+ def send_keys(*args)
275
+ synchronize { base.send_keys(*args) }
276
+ self
277
+ end
278
+
279
+ ##
280
+ #
281
+ # Hover on the Element.
282
+ #
283
+ # @return [Capybara::Node::Element] The element
284
+ def hover
285
+ synchronize { base.hover }
286
+ self
287
+ end
288
+
289
+ ##
290
+ #
291
+ # @return [String] The tag name of the element
292
+ #
293
+ def tag_name
294
+ # Element type is immutable so cache it
295
+ @tag_name ||= initial_cache[:tag_name] || synchronize { base.tag_name }
296
+ end
297
+
298
+ ##
299
+ #
300
+ # Whether or not the element is visible. Not all drivers support CSS, so
301
+ # the result may be inaccurate.
302
+ #
303
+ # @return [Boolean] Whether the element is visible
304
+ #
305
+ def visible?
306
+ synchronize { base.visible? }
307
+ end
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
+
320
+ ##
321
+ #
322
+ # Whether or not the element is checked.
323
+ #
324
+ # @return [Boolean] Whether the element is checked
325
+ #
326
+ def checked?
327
+ synchronize { base.checked? }
328
+ end
329
+
330
+ ##
331
+ #
332
+ # Whether or not the element is selected.
333
+ #
334
+ # @return [Boolean] Whether the element is selected
335
+ #
336
+ def selected?
337
+ synchronize { base.selected? }
338
+ end
339
+
340
+ ##
341
+ #
342
+ # Whether or not the element is disabled.
343
+ #
344
+ # @return [Boolean] Whether the element is disabled
345
+ #
346
+ def disabled?
347
+ synchronize { base.disabled? }
348
+ end
349
+
350
+ ##
351
+ #
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.
373
+ #
374
+ # @return [String] An XPath expression
375
+ #
376
+ def path
377
+ synchronize { base.path }
378
+ end
379
+
380
+ def rect
381
+ synchronize { base.rect }
382
+ end
383
+
384
+ ##
385
+ #
386
+ # Trigger any event on the current element, for example mouseover or focus
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.
390
+ #
391
+ # @param [String] event The name of the event to trigger
392
+ #
393
+ # @return [Capybara::Node::Element] The element
394
+ def trigger(event)
395
+ synchronize { base.trigger(event) }
396
+ self
397
+ end
398
+
399
+ ##
400
+ #
401
+ # Drag the element to the given other element.
402
+ #
403
+ # source = page.find('#foo')
404
+ # target = page.find('#bar')
405
+ # source.drag_to(target)
406
+ #
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 do |arg|
439
+ return arg.to_path if arg.respond_to?(:to_path)
440
+
441
+ arg
442
+ end
443
+ synchronize { base.drop(*options) }
444
+ self
445
+ end
446
+
447
+ ##
448
+ #
449
+ # Scroll the page or element.
450
+ #
451
+ # @overload scroll_to(position, offset: [0,0])
452
+ # Scroll the page or element to its top, bottom or middle.
453
+ # @param [:top, :bottom, :center, :current] position
454
+ # @param [[Integer, Integer]] offset
455
+ #
456
+ # @overload scroll_to(element, align: :top)
457
+ # Scroll the page or current element until the given element is aligned at the top, bottom, or center of it.
458
+ # @param [Capybara::Node::Element] element The element to be scrolled into view
459
+ # @param [:top, :bottom, :center] align Where to align the element being scrolled into view with relation to the current page/element if possible
460
+ #
461
+ # @overload scroll_to(x,y)
462
+ # @param [Integer] x Horizontal scroll offset
463
+ # @param [Integer] y Vertical scroll offset
464
+ #
465
+ # @return [Capybara::Node::Element] The element
466
+ def scroll_to(pos_or_el_or_x, y = nil, align: :top, offset: nil)
467
+ case pos_or_el_or_x
468
+ when Symbol
469
+ synchronize { base.scroll_to(nil, pos_or_el_or_x) } unless pos_or_el_or_x == :current
470
+ when Capybara::Node::Element
471
+ synchronize { base.scroll_to(pos_or_el_or_x.base, align) }
472
+ else
473
+ synchronize { base.scroll_to(nil, nil, [pos_or_el_or_x, y]) }
474
+ end
475
+ synchronize { base.scroll_by(*offset) } unless offset.nil?
476
+ self
477
+ end
478
+
479
+ ##
480
+ #
481
+ # Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
482
+ # complex objects, such as jQuery statements. {#execute_script} should be used over
483
+ # {#evaluate_script} whenever a result is not expected or needed. `this` in the script will refer to the element this is called on.
484
+ #
485
+ # @param [String] script A string of JavaScript to execute
486
+ # @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
487
+ #
488
+ def execute_script(script, *args)
489
+ session.execute_script(<<~JS, self, *args)
490
+ (function (){
491
+ #{script}
492
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
493
+ JS
494
+ end
495
+
496
+ ##
497
+ #
498
+ # Evaluate the given JS in the context of the element and return the result. Be careful when using this with
499
+ # scripts that return complex objects, such as jQuery statements. {#execute_script} might
500
+ # be a better alternative. `this` in the script will refer to the element this is called on.
501
+ #
502
+ # @param [String] script A string of JavaScript to evaluate
503
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
504
+ #
505
+ def evaluate_script(script, *args)
506
+ session.evaluate_script(<<~JS, self, *args)
507
+ (function(){
508
+ return #{script.strip}
509
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
510
+ JS
511
+ end
512
+
513
+ ##
514
+ #
515
+ # Evaluate the given JavaScript in the context of the element and obtain the result from a
516
+ # callback function which will be passed as the last argument to the script. `this` in the
517
+ # script will refer to the element this is called on.
518
+ #
519
+ # @param [String] script A string of JavaScript to evaluate
520
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
521
+ #
522
+ def evaluate_async_script(script, *args)
523
+ session.evaluate_async_script(<<~JS, self, *args)
524
+ (function (){
525
+ #{script}
526
+ }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
527
+ JS
528
+ end
529
+
530
+ ##
531
+ #
532
+ # Toggle the elements background color between white and black for a period of time.
533
+ #
534
+ # @return [Capybara::Node::Element] The element
535
+ def flash
536
+ execute_script(<<~JS, 100)
537
+ async function flash(el, delay){
538
+ var old_bg = el.style.backgroundColor;
539
+ var colors = ["black", "white"];
540
+ for(var i=0; i<20; i++){
541
+ el.style.backgroundColor = colors[i % colors.length];
542
+ await new Promise(resolve => setTimeout(resolve, delay));
543
+ }
544
+ el.style.backgroundColor = old_bg;
545
+ }
546
+ flash(this, arguments[0]);
547
+ JS
548
+
549
+ self
550
+ end
551
+
552
+ # @api private
553
+ def reload
554
+ return self unless @allow_reload
555
+
556
+ begin
557
+ reloaded = @query.resolve_for(query_scope.reload)[@query_idx.to_i]
558
+ @base = reloaded.base if reloaded
559
+ rescue StandardError => e
560
+ raise e unless catch_error?(e)
561
+ end
562
+ self
563
+ end
564
+
565
+ ##
566
+ #
567
+ # A human-readable representation of the element.
568
+ #
569
+ # @return [String] A string representation
570
+ def inspect
571
+ %(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
572
+ rescue NotSupportedByDriverError
573
+ %(#<Capybara::Node::Element tag="#{base.tag_name}">)
574
+ rescue *session.driver.invalid_element_errors
575
+ %(Obsolete #<Capybara::Node::Element>)
576
+ end
577
+
578
+ # @api private
579
+ def initial_cache
580
+ base.respond_to?(:initial_cache) ? base.initial_cache : {}
581
+ end
582
+
583
+ STYLE_SCRIPT = <<~JS
584
+ (function(){
585
+ var s = window.getComputedStyle(this);
586
+ var result = {};
587
+ for (var i = arguments.length; i--; ) {
588
+ var property_name = arguments[i];
589
+ result[property_name] = s.getPropertyValue(property_name);
590
+ }
591
+ return result;
592
+ }).apply(this, arguments)
593
+ JS
594
+
595
+ private
596
+
597
+ def perform_click_action(keys, wait: nil, **options)
598
+ raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ options[:x] ^ options[:y]
599
+
600
+ options[:offset] ||= :center if session_options.w3c_click_offset
601
+ synchronize(wait) { yield keys, options }
602
+ self
603
+ end
604
+ end
605
+ end
606
+ end