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,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Capybara::Selenium::Node
4
+ module Html5Drag
5
+ # Implement methods to emulate HTML5 drag and drop
6
+
7
+ def drag_to(element, html5: nil, delay: 0.05, drop_modifiers: [])
8
+ drop_modifiers = Array(drop_modifiers)
9
+
10
+ driver.execute_script MOUSEDOWN_TRACKER
11
+ scroll_if_needed { browser_action.click_and_hold(native).perform }
12
+ html5 = !driver.evaluate_script(LEGACY_DRAG_CHECK, self) if html5.nil?
13
+ if html5
14
+ perform_html5_drag(element, delay, drop_modifiers)
15
+ else
16
+ perform_legacy_drag(element, drop_modifiers)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def perform_legacy_drag(element, drop_modifiers)
23
+ element.scroll_if_needed do
24
+ # browser_action.move_to(element.native).release.perform
25
+ keys_down = modifiers_down(browser_action, drop_modifiers)
26
+ keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers)
27
+ keys_up.perform
28
+ end
29
+ end
30
+
31
+ def perform_html5_drag(element, delay, drop_modifiers)
32
+ driver.evaluate_async_script HTML5_DRAG_DROP_SCRIPT, self, element, delay * 1000, normalize_keys(drop_modifiers)
33
+ browser_action.release.perform
34
+ end
35
+
36
+ def html5_drop(*args)
37
+ if args[0].is_a? String
38
+ input = driver.evaluate_script ATTACH_FILE
39
+ input.set_file(args)
40
+ driver.execute_script DROP_FILE, self, input
41
+ else
42
+ items = args.each_with_object([]) do |arg, arr|
43
+ arg.each_with_object(arr) do |(type, data), arr_|
44
+ arr_ << { type: type, data: data }
45
+ end
46
+ end
47
+ driver.execute_script DROP_STRING, items, self
48
+ end
49
+ end
50
+
51
+ DROP_STRING = <<~JS
52
+ var strings = arguments[0],
53
+ el = arguments[1],
54
+ dt = new DataTransfer(),
55
+ opts = { cancelable: true, bubbles: true, dataTransfer: dt };
56
+ for (var i=0; i < strings.length; i++){
57
+ if (dt.items) {
58
+ dt.items.add(strings[i]['data'], strings[i]['type']);
59
+ } else {
60
+ dt.setData(strings[i]['type'], strings[i]['data']);
61
+ }
62
+ }
63
+ var dropEvent = new DragEvent('drop', opts);
64
+ el.dispatchEvent(dropEvent);
65
+ JS
66
+
67
+ DROP_FILE = <<~JS
68
+ var el = arguments[0],
69
+ input = arguments[1],
70
+ files = input.files,
71
+ dt = new DataTransfer(),
72
+ opts = { cancelable: true, bubbles: true, dataTransfer: dt };
73
+ input.parentElement.removeChild(input);
74
+ if (dt.items){
75
+ for (var i=0; i<files.length; i++){
76
+ dt.items.add(files[i]);
77
+ }
78
+ } else {
79
+ Object.defineProperty(dt, "files", {
80
+ value: files,
81
+ writable: false
82
+ });
83
+ }
84
+ var dropEvent = new DragEvent('drop', opts);
85
+ el.dispatchEvent(dropEvent);
86
+ JS
87
+
88
+ ATTACH_FILE = <<~JS
89
+ (function(){
90
+ var input = document.createElement('INPUT');
91
+ input.type = "file";
92
+ input.id = "_capybara_drop_file";
93
+ input.multiple = true;
94
+ document.body.appendChild(input);
95
+ return input;
96
+ })()
97
+ JS
98
+
99
+ MOUSEDOWN_TRACKER = <<~JS
100
+ window.capybara_mousedown_prevented = null;
101
+ document.addEventListener('mousedown', ev => {
102
+ window.capybara_mousedown_prevented = ev.defaultPrevented;
103
+ }, { once: true, passive: true })
104
+ JS
105
+
106
+ LEGACY_DRAG_CHECK = <<~JS
107
+ (function(el){
108
+ if ([true, null].indexOf(window.capybara_mousedown_prevented) >= 0){
109
+ return true;
110
+ }
111
+
112
+ do {
113
+ if (el.draggable) return false;
114
+ } while (el = el.parentElement );
115
+ return true;
116
+ })(arguments[0])
117
+ JS
118
+
119
+ HTML5_DRAG_DROP_SCRIPT = <<~JS
120
+ function rectCenter(rect){
121
+ return new DOMPoint(
122
+ (rect.left + rect.right)/2,
123
+ (rect.top + rect.bottom)/2
124
+ );
125
+ }
126
+
127
+ function pointOnRect(pt, rect) {
128
+ var rectPt = rectCenter(rect);
129
+ var slope = (rectPt.y - pt.y) / (rectPt.x - pt.x);
130
+
131
+ if (pt.x <= rectPt.x) { // left side
132
+ var minXy = slope * (rect.left - pt.x) + pt.y;
133
+ if (rect.top <= minXy && minXy <= rect.bottom)
134
+ return new DOMPoint(rect.left, minXy);
135
+ }
136
+
137
+ if (pt.x >= rectPt.x) { // right side
138
+ var maxXy = slope * (rect.right - pt.x) + pt.y;
139
+ if (rect.top <= maxXy && maxXy <= rect.bottom)
140
+ return new DOMPoint(rect.right, maxXy);
141
+ }
142
+
143
+ if (pt.y <= rectPt.y) { // top side
144
+ var minYx = (rectPt.top - pt.y) / slope + pt.x;
145
+ if (rect.left <= minYx && minYx <= rect.right)
146
+ return new DOMPoint(minYx, rect.top);
147
+ }
148
+
149
+ if (pt.y >= rectPt.y) { // bottom side
150
+ var maxYx = (rect.bottom - pt.y) / slope + pt.x;
151
+ if (rect.left <= maxYx && maxYx <= rect.right)
152
+ return new DOMPoint(maxYx, rect.bottom);
153
+ }
154
+
155
+ return new DOMPoint(pt.x,pt.y);
156
+ }
157
+
158
+ function dragEnterTarget() {
159
+ target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
160
+ var targetRect = target.getBoundingClientRect();
161
+ var sourceCenter = rectCenter(source.getBoundingClientRect());
162
+
163
+ for (var i = 0; i < drop_modifier_keys.length; i++) {
164
+ key = drop_modifier_keys[i];
165
+ if (key == "control"){
166
+ key = "ctrl"
167
+ }
168
+ opts[key + 'Key'] = true;
169
+ }
170
+
171
+ // fire 2 dragover events to simulate dragging with a direction
172
+ var entryPoint = pointOnRect(sourceCenter, targetRect)
173
+ var dragOverOpts = Object.assign({clientX: entryPoint.x, clientY: entryPoint.y}, opts);
174
+ var dragOverEvent = new DragEvent('dragover', dragOverOpts);
175
+ target.dispatchEvent(dragOverEvent);
176
+ window.setTimeout(dragOnTarget, step_delay);
177
+ }
178
+
179
+ function dragOnTarget() {
180
+ var targetCenter = rectCenter(target.getBoundingClientRect());
181
+ var dragOverOpts = Object.assign({clientX: targetCenter.x, clientY: targetCenter.y}, opts);
182
+ var dragOverEvent = new DragEvent('dragover', dragOverOpts);
183
+ target.dispatchEvent(dragOverEvent);
184
+ window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented, dragOverOpts);
185
+ }
186
+
187
+ function dragLeave(drop, dragOverOpts) {
188
+ var dragLeaveOptions = Object.assign({}, opts, dragOverOpts);
189
+ var dragLeaveEvent = new DragEvent('dragleave', dragLeaveOptions);
190
+ target.dispatchEvent(dragLeaveEvent);
191
+ if (drop) {
192
+ var dropEvent = new DragEvent('drop', dragLeaveOptions);
193
+ target.dispatchEvent(dropEvent);
194
+ }
195
+ var dragEndEvent = new DragEvent('dragend', dragLeaveOptions);
196
+ source.dispatchEvent(dragEndEvent);
197
+ callback.call(true);
198
+ }
199
+
200
+ var source = arguments[0],
201
+ target = arguments[1],
202
+ step_delay = arguments[2],
203
+ drop_modifier_keys = arguments[3],
204
+ callback = arguments[4];
205
+
206
+ var dt = new DataTransfer();
207
+ var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
208
+
209
+ while (source && !source.draggable) {
210
+ source = source.parentElement;
211
+ }
212
+
213
+ if (source.tagName == 'A'){
214
+ dt.setData('text/uri-list', source.href);
215
+ dt.setData('text', source.href);
216
+ }
217
+ if (source.tagName == 'IMG'){
218
+ dt.setData('text/uri-list', source.src);
219
+ dt.setData('text', source.src);
220
+ }
221
+
222
+ var dragEvent = new DragEvent('dragstart', opts);
223
+ source.dispatchEvent(dragEvent);
224
+
225
+ window.setTimeout(dragEnterTarget, step_delay);
226
+ JS
227
+ end
228
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Capybara::Selenium::Node
4
+ #
5
+ # @api private
6
+ #
7
+ class ModifierKeysStack
8
+ def initialize
9
+ @stack = []
10
+ end
11
+
12
+ def include?(key)
13
+ @stack.flatten.include?(key)
14
+ end
15
+
16
+ def press(key)
17
+ @stack.last.push(key)
18
+ end
19
+
20
+ def push
21
+ @stack.push []
22
+ end
23
+
24
+ def pop
25
+ @stack.pop
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Selenium
5
+ module Scroll
6
+ def scroll_by(x, y)
7
+ driver.execute_script <<~JS, self, x, y
8
+ var el = arguments[0];
9
+ if (el.scrollBy){
10
+ el.scrollBy(arguments[1], arguments[2]);
11
+ } else {
12
+ el.scrollTop = el.scrollTop + arguments[2];
13
+ el.scrollLeft = el.scrollLeft + arguments[1];
14
+ }
15
+ JS
16
+ end
17
+
18
+ def scroll_to(element, location, position = nil)
19
+ # location, element = element, nil if element.is_a? Symbol
20
+ if element.is_a? Capybara::Selenium::Node
21
+ scroll_element_to_location(element, location)
22
+ elsif location.is_a? Symbol
23
+ scroll_to_location(location)
24
+ else
25
+ scroll_to_coords(*position)
26
+ end
27
+ self
28
+ end
29
+
30
+ private
31
+
32
+ def scroll_element_to_location(element, location)
33
+ scroll_opts = case location
34
+ when :top
35
+ 'true'
36
+ when :bottom
37
+ 'false'
38
+ when :center
39
+ "{behavior: 'instant', block: 'center'}"
40
+ else
41
+ raise ArgumentError, "Invalid scroll_to location: #{location}"
42
+ end
43
+ driver.execute_script <<~JS, element
44
+ arguments[0].scrollIntoView(#{scroll_opts})
45
+ JS
46
+ end
47
+
48
+ def scroll_to_location(location)
49
+ scroll_y = case location
50
+ when :top
51
+ '0'
52
+ when :bottom
53
+ 'arguments[0].scrollHeight'
54
+ when :center
55
+ '(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
56
+ end
57
+ driver.execute_script <<~JS, self
58
+ if (arguments[0].scrollTo){
59
+ arguments[0].scrollTo(0, #{scroll_y});
60
+ } else {
61
+ arguments[0].scrollTop = #{scroll_y};
62
+ }
63
+ JS
64
+ end
65
+
66
+ def scroll_to_coords(x, y)
67
+ driver.execute_script <<~JS, self, x, y
68
+ if (arguments[0].scrollTo){
69
+ arguments[0].scrollTo(arguments[1], arguments[2]);
70
+ } else {
71
+ arguments[0].scrollTop = arguments[2];
72
+ arguments[0].scrollLeft = arguments[1];
73
+ }
74
+ JS
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Selenium
5
+ module DeprecationSuppressor
6
+ def initialize(*)
7
+ @suppress_for_capybara = false
8
+ super
9
+ end
10
+
11
+ def deprecate(*)
12
+ super unless @suppress_for_capybara
13
+ end
14
+
15
+ def suppress_deprecations
16
+ prev_suppress_for_capybara, @suppress_for_capybara = @suppress_for_capybara, true
17
+ yield
18
+ ensure
19
+ @suppress_for_capybara = prev_suppress_for_capybara
20
+ end
21
+ end
22
+
23
+ module ErrorSuppressor
24
+ def for_code(*)
25
+ ::Selenium::WebDriver.logger.suppress_deprecations do
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Selenium::WebDriver::Logger.prepend Capybara::Selenium::DeprecationSuppressor
34
+ Selenium::WebDriver::Error.singleton_class.prepend Capybara::Selenium::ErrorSuppressor
@@ -0,0 +1,610 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Selenium specific implementation of the Capybara::Driver::Node API
4
+
5
+ require 'capybara/selenium/extensions/find'
6
+ require 'capybara/selenium/extensions/scroll'
7
+
8
+ class Capybara::Selenium::Node < Capybara::Driver::Node
9
+ include Capybara::Selenium::Find
10
+ include Capybara::Selenium::Scroll
11
+
12
+ def visible_text
13
+ native.text
14
+ end
15
+
16
+ def all_text
17
+ text = driver.evaluate_script('arguments[0].textContent', self)
18
+ text.gsub(/[\u200b\u200e\u200f]/, '')
19
+ .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
20
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
22
+ .tr("\u00a0", ' ')
23
+ end
24
+
25
+ def [](name)
26
+ native.attribute(name.to_s)
27
+ rescue Selenium::WebDriver::Error::WebDriverError
28
+ nil
29
+ end
30
+
31
+ def value
32
+ if tag_name == 'select' && multiple?
33
+ native.find_elements(:css, 'option:checked').map { |el| el[:value] || el.text }
34
+ else
35
+ native[:value]
36
+ end
37
+ end
38
+
39
+ def style(styles)
40
+ styles.each_with_object({}) do |style, result|
41
+ result[style] = native.css_value(style)
42
+ end
43
+ end
44
+
45
+ ##
46
+ #
47
+ # Set the value of the form element to the given value.
48
+ #
49
+ # @param [String] value The new value
50
+ # @param [Hash{}] options Driver specific options for how to set the value
51
+ # @option options [Symbol,Array] :clear (nil) The method used to clear the previous value <br/>
52
+ # nil => clear via javascript <br/>
53
+ # :none => append the new value to the existing value <br/>
54
+ # :backspace => send backspace keystrokes to clear the field <br/>
55
+ # Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
56
+ def set(value, **options)
57
+ if value.is_a?(Array) && !multiple?
58
+ raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
59
+ end
60
+
61
+ tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
62
+ @tag_name ||= tag_name
63
+
64
+ case tag_name
65
+ when 'input'
66
+ case type
67
+ when 'radio'
68
+ click
69
+ when 'checkbox'
70
+ click if value ^ checked?
71
+ when 'file'
72
+ set_file(value)
73
+ when 'date'
74
+ set_date(value)
75
+ when 'time'
76
+ set_time(value)
77
+ when 'datetime-local'
78
+ set_datetime_local(value)
79
+ when 'color'
80
+ set_color(value)
81
+ when 'range'
82
+ set_range(value)
83
+ else
84
+ set_text(value, **options)
85
+ end
86
+ when 'textarea'
87
+ set_text(value, **options)
88
+ else
89
+ set_content_editable(value)
90
+ end
91
+ end
92
+
93
+ def select_option
94
+ click unless selected? || disabled?
95
+ end
96
+
97
+ def unselect_option
98
+ raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
99
+
100
+ click if selected?
101
+ end
102
+
103
+ def click(keys = [], **options)
104
+ click_options = ClickOptions.new(keys, options)
105
+ return native.click if click_options.empty?
106
+
107
+ perform_with_options(click_options) do |action|
108
+ target = click_options.coords? ? nil : native
109
+ if click_options.delay.zero?
110
+ action.click(target)
111
+ else
112
+ action.click_and_hold(target)
113
+ if w3c?
114
+ action.pause(action.pointer_inputs.first, click_options.delay)
115
+ else
116
+ action.pause(click_options.delay)
117
+ end
118
+ action.release
119
+ end
120
+ end
121
+ rescue StandardError => e
122
+ if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
123
+ e.message.match?(/Other element would receive the click/)
124
+ scroll_to_center
125
+ end
126
+
127
+ raise e
128
+ end
129
+
130
+ def right_click(keys = [], **options)
131
+ click_options = ClickOptions.new(keys, options)
132
+ perform_with_options(click_options) do |action|
133
+ target = click_options.coords? ? nil : native
134
+ if click_options.delay.zero?
135
+ action.context_click(target)
136
+ elsif w3c?
137
+ action.move_to(target) if target
138
+ action.pointer_down(:right)
139
+ .pause(action.pointer_inputs.first, click_options.delay)
140
+ .pointer_up(:right)
141
+ else
142
+ raise ArgumentError, 'Delay is not supported when right clicking with legacy (non-w3c) selenium driver'
143
+ end
144
+ end
145
+ end
146
+
147
+ def double_click(keys = [], **options)
148
+ click_options = ClickOptions.new(keys, options)
149
+ raise ArgumentError, "double_click doesn't support a delay option" unless click_options.delay.zero?
150
+
151
+ perform_with_options(click_options) do |action|
152
+ click_options.coords? ? action.double_click : action.double_click(native)
153
+ end
154
+ end
155
+
156
+ def send_keys(*args)
157
+ native.send_keys(*args)
158
+ end
159
+
160
+ def hover
161
+ scroll_if_needed { browser_action.move_to(native).perform }
162
+ end
163
+
164
+ def drag_to(element, drop_modifiers: [], **)
165
+ drop_modifiers = Array(drop_modifiers)
166
+ # Due to W3C spec compliance - The Actions API no longer scrolls to elements when necessary
167
+ # which means Seleniums `drag_and_drop` is now broken - do it manually
168
+ scroll_if_needed { browser_action.click_and_hold(native).perform }
169
+ # element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
170
+ element.scroll_if_needed do
171
+ keys_down = modifiers_down(browser_action, drop_modifiers)
172
+ keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers)
173
+ keys_up.perform
174
+ end
175
+ end
176
+
177
+ def drop(*_)
178
+ raise NotImplementedError, 'Out of browser drop emulation is not implemented for the current browser'
179
+ end
180
+
181
+ def tag_name
182
+ @tag_name ||= native.tag_name.downcase
183
+ end
184
+
185
+ def visible?; boolean_attr(native.displayed?); end
186
+ def readonly?; boolean_attr(self[:readonly]); end
187
+ def multiple?; boolean_attr(self[:multiple]); end
188
+ def selected?; boolean_attr(native.selected?); end
189
+ alias :checked? :selected?
190
+
191
+ def disabled?
192
+ return true unless native.enabled?
193
+
194
+ # WebDriver only defines `disabled?` for form controls but fieldset makes sense too
195
+ find_xpath('self::fieldset/ancestor-or-self::fieldset[@disabled]').any?
196
+ end
197
+
198
+ def content_editable?
199
+ native.attribute('isContentEditable') == 'true'
200
+ end
201
+
202
+ def ==(other)
203
+ native == other.native
204
+ end
205
+
206
+ def path
207
+ driver.evaluate_script GET_XPATH_SCRIPT, self
208
+ end
209
+
210
+ def obscured?(x: nil, y: nil)
211
+ res = driver.evaluate_script(OBSCURED_OR_OFFSET_SCRIPT, self, x, y)
212
+ return true if res == true
213
+
214
+ driver.frame_obscured_at?(x: res['x'], y: res['y'])
215
+ end
216
+
217
+ def rect
218
+ native.rect
219
+ end
220
+
221
+ protected
222
+
223
+ def scroll_if_needed
224
+ yield
225
+ rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
226
+ scroll_to_center
227
+ yield
228
+ end
229
+
230
+ def scroll_to_center
231
+ script = <<-'JS'
232
+ try {
233
+ arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
234
+ } catch(e) {
235
+ arguments[0].scrollIntoView(true);
236
+ }
237
+ JS
238
+ begin
239
+ driver.execute_script(script, self)
240
+ rescue StandardError
241
+ # Swallow error if scrollIntoView with options isn't supported
242
+ end
243
+ end
244
+
245
+ private
246
+
247
+ def sibling_index(parent, node, selector)
248
+ siblings = parent.find_xpath(selector)
249
+ case siblings.size
250
+ when 0
251
+ '[ERROR]' # IE doesn't support full XPath (namespace-uri, etc)
252
+ when 1
253
+ '' # index not necessary when only one matching element
254
+ else
255
+ idx = siblings.index(node)
256
+ # Element may not be found in the siblings if it has gone away
257
+ idx.nil? ? '[ERROR]' : "[#{idx + 1}]"
258
+ end
259
+ end
260
+
261
+ def boolean_attr(val)
262
+ val && (val != 'false')
263
+ end
264
+
265
+ # a reference to the select node if this is an option node
266
+ def select_node
267
+ find_xpath(XPath.ancestor(:select)[1]).first
268
+ end
269
+
270
+ def set_text(value, clear: nil, rapid: nil, **_unused)
271
+ value = value.to_s
272
+ if value.empty? && clear.nil?
273
+ native.clear
274
+ elsif clear == :backspace
275
+ # Clear field by sending the correct number of backspace keys.
276
+ backspaces = [:backspace] * self.value.to_s.length
277
+ send_keys(*([:end] + backspaces + [value]))
278
+ elsif clear.is_a? Array
279
+ send_keys(*clear, value)
280
+ else
281
+ driver.execute_script 'arguments[0].select()', self unless clear == :none
282
+ if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
283
+ send_keys(value[0..3])
284
+ driver.execute_script RAPID_SET_TEXT, self, value[0...-3]
285
+ send_keys(value[-3..-1])
286
+ else
287
+ send_keys(value)
288
+ end
289
+ end
290
+ end
291
+
292
+ def auto_rapid_set_length
293
+ 30
294
+ end
295
+
296
+ def perform_with_options(click_options, &block)
297
+ raise ArgumentError, 'A block must be provided' unless block
298
+
299
+ scroll_if_needed do
300
+ action_with_modifiers(click_options) do |action|
301
+ if block_given?
302
+ yield action
303
+ else
304
+ click_options.coords? ? action.click : action.click(native)
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ def set_date(value) # rubocop:disable Naming/AccessorMethodName
311
+ value = SettableValue.new(value)
312
+ return set_text(value) unless value.dateable?
313
+
314
+ # TODO: this would be better if locale can be detected and correct keystrokes sent
315
+ update_value_js(value.to_date_str)
316
+ end
317
+
318
+ def set_time(value) # rubocop:disable Naming/AccessorMethodName
319
+ value = SettableValue.new(value)
320
+ return set_text(value) unless value.timeable?
321
+
322
+ # TODO: this would be better if locale can be detected and correct keystrokes sent
323
+ update_value_js(value.to_time_str)
324
+ end
325
+
326
+ def set_datetime_local(value) # rubocop:disable Naming/AccessorMethodName
327
+ value = SettableValue.new(value)
328
+ return set_text(value) unless value.timeable?
329
+
330
+ # TODO: this would be better if locale can be detected and correct keystrokes sent
331
+ update_value_js(value.to_datetime_str)
332
+ end
333
+
334
+ def set_color(value) # rubocop:disable Naming/AccessorMethodName
335
+ update_value_js(value)
336
+ end
337
+
338
+ def set_range(value) # rubocop:disable Naming/AccessorMethodName
339
+ update_value_js(value)
340
+ end
341
+
342
+ def update_value_js(value)
343
+ driver.execute_script(<<-JS, self, value)
344
+ if (arguments[0].readOnly) { return };
345
+ if (document.activeElement !== arguments[0]){
346
+ arguments[0].focus();
347
+ }
348
+ if (arguments[0].value != arguments[1]) {
349
+ arguments[0].value = arguments[1]
350
+ arguments[0].dispatchEvent(new InputEvent('input'));
351
+ arguments[0].dispatchEvent(new Event('change', { bubbles: true }));
352
+ }
353
+ JS
354
+ end
355
+
356
+ def set_file(value) # rubocop:disable Naming/AccessorMethodName
357
+ with_file_detector do
358
+ path_names = value.to_s.empty? ? [] : value
359
+ file_names = Array(path_names).map do |pn|
360
+ Pathname.new(pn).absolute? ? pn : File.expand_path(pn)
361
+ end.join("\n")
362
+ native.send_keys(file_names)
363
+ end
364
+ end
365
+
366
+ def with_file_detector
367
+ if driver.options[:browser] == :remote &&
368
+ bridge.respond_to?(:file_detector) &&
369
+ bridge.file_detector.nil?
370
+ begin
371
+ bridge.file_detector = lambda do |(fn, *)|
372
+ str = fn.to_s
373
+ str if File.exist?(str)
374
+ end
375
+ yield
376
+ ensure
377
+ bridge.file_detector = nil
378
+ end
379
+ else
380
+ yield
381
+ end
382
+ end
383
+
384
+ def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName
385
+ # Ensure we are focused on the element
386
+ click
387
+
388
+ editable = driver.execute_script <<-JS, self
389
+ if (arguments[0].isContentEditable) {
390
+ var range = document.createRange();
391
+ var sel = window.getSelection();
392
+ arguments[0].focus();
393
+ range.selectNodeContents(arguments[0]);
394
+ sel.removeAllRanges();
395
+ sel.addRange(range);
396
+ return true;
397
+ }
398
+ return false;
399
+ JS
400
+
401
+ # The action api has a speed problem but both chrome and firefox 58 raise errors
402
+ # if we use the faster direct send_keys. For now just send_keys to the element
403
+ # we've already focused.
404
+ # native.send_keys(value.to_s)
405
+ browser_action.send_keys(value.to_s).perform if editable
406
+ end
407
+
408
+ def action_with_modifiers(click_options)
409
+ actions = browser_action.tap do |acts|
410
+ if click_options.center_offset? && click_options.coords?
411
+ acts.move_to(native).move_by(*click_options.coords)
412
+ else
413
+ acts.move_to(native, *click_options.coords)
414
+ end
415
+ end
416
+ modifiers_down(actions, click_options.keys)
417
+ yield actions
418
+ modifiers_up(actions, click_options.keys)
419
+ actions.perform
420
+ ensure
421
+ act = browser_action
422
+ act.release_actions if act.respond_to?(:release_actions)
423
+ end
424
+
425
+ def modifiers_down(actions, keys)
426
+ each_key(keys) { |key| actions.key_down(key) }
427
+ actions
428
+ end
429
+
430
+ def modifiers_up(actions, keys)
431
+ each_key(keys) { |key| actions.key_up(key) }
432
+ actions
433
+ end
434
+
435
+ def browser
436
+ driver.browser
437
+ end
438
+
439
+ def bridge
440
+ browser.send(:bridge)
441
+ end
442
+
443
+ def browser_action
444
+ browser.action
445
+ end
446
+
447
+ def capabilities
448
+ browser.capabilities
449
+ end
450
+
451
+ def w3c?
452
+ (defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) ||
453
+ capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
454
+ end
455
+
456
+ def normalize_keys(keys)
457
+ keys.map do |key|
458
+ case key
459
+ when :ctrl then :control
460
+ when :command, :cmd then :meta
461
+ else
462
+ key
463
+ end
464
+ end
465
+ end
466
+
467
+ def each_key(keys)
468
+ normalize_keys(keys).each { |key| yield(key) }
469
+ end
470
+
471
+ def find_context
472
+ native
473
+ end
474
+
475
+ def build_node(native_node, initial_cache = {})
476
+ self.class.new(driver, native_node, initial_cache)
477
+ end
478
+
479
+ def attrs(*attr_names)
480
+ return attr_names.map { |name| self[name.to_s] } if ENV['CAPYBARA_THOROUGH']
481
+
482
+ driver.evaluate_script <<~'JS', self, attr_names.map(&:to_s)
483
+ (function(el, names){
484
+ return names.map(function(name){
485
+ return el[name]
486
+ });
487
+ })(arguments[0], arguments[1]);
488
+ JS
489
+ end
490
+
491
+ GET_XPATH_SCRIPT = <<~'JS'
492
+ (function(el, xml){
493
+ var xpath = '';
494
+ var pos, tempitem2;
495
+
496
+ while(el !== xml.documentElement) {
497
+ pos = 0;
498
+ tempitem2 = el;
499
+ while(tempitem2) {
500
+ if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name
501
+ pos += 1;
502
+ }
503
+ tempitem2 = tempitem2.previousSibling;
504
+ }
505
+
506
+ if (el.namespaceURI != xml.documentElement.namespaceURI) {
507
+ xpath = "*[local-name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
508
+ } else {
509
+ xpath = el.nodeName.toUpperCase()+"["+pos+"]/"+xpath;
510
+ }
511
+
512
+ el = el.parentNode;
513
+ }
514
+ xpath = '/'+xml.documentElement.nodeName.toUpperCase()+'/'+xpath;
515
+ xpath = xpath.replace(/\/$/, '');
516
+ return xpath;
517
+ })(arguments[0], document)
518
+ JS
519
+
520
+ OBSCURED_OR_OFFSET_SCRIPT = <<~'JS'
521
+ (function(el, x, y) {
522
+ var box = el.getBoundingClientRect();
523
+ if (x == null) x = box.width/2;
524
+ if (y == null) y = box.height/2 ;
525
+
526
+ var px = box.left + x,
527
+ py = box.top + y,
528
+ e = document.elementFromPoint(px, py);
529
+
530
+ if (!el.contains(e))
531
+ return true;
532
+
533
+ return { x: px, y: py };
534
+ })(arguments[0], arguments[1], arguments[2])
535
+ JS
536
+
537
+ RAPID_SET_TEXT = <<~'JS'
538
+ (function(el, value) {
539
+ if (el.maxLength && el.maxLength != -1){
540
+ value = value.slice(0, el.maxLength);
541
+ }
542
+ el.value = value;
543
+ })(arguments[0], arguments[1])
544
+ JS
545
+
546
+ # SettableValue encapsulates time/date field formatting
547
+ class SettableValue
548
+ attr_reader :value
549
+
550
+ def initialize(value)
551
+ @value = value
552
+ end
553
+
554
+ def to_s
555
+ value.to_s
556
+ end
557
+
558
+ def dateable?
559
+ !value.is_a?(String) && value.respond_to?(:to_date)
560
+ end
561
+
562
+ def to_date_str
563
+ value.to_date.iso8601
564
+ end
565
+
566
+ def timeable?
567
+ !value.is_a?(String) && value.respond_to?(:to_time)
568
+ end
569
+
570
+ def to_time_str
571
+ value.to_time.strftime('%H:%M')
572
+ end
573
+
574
+ def to_datetime_str
575
+ value.to_time.strftime('%Y-%m-%dT%H:%M')
576
+ end
577
+ end
578
+ private_constant :SettableValue
579
+
580
+ # ClickOptions encapsulates click option logic
581
+ class ClickOptions
582
+ attr_reader :keys, :options
583
+
584
+ def initialize(keys, options)
585
+ @keys = keys
586
+ @options = options
587
+ end
588
+
589
+ def coords?
590
+ options[:x] && options[:y]
591
+ end
592
+
593
+ def coords
594
+ [options[:x], options[:y]]
595
+ end
596
+
597
+ def center_offset?
598
+ options[:offset] == :center
599
+ end
600
+
601
+ def empty?
602
+ keys.empty? && !coords? && delay.zero?
603
+ end
604
+
605
+ def delay
606
+ options[:delay] || 0
607
+ end
608
+ end
609
+ private_constant :ClickOptions
610
+ end