capybara 2.18.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +945 -12
  4. data/License.txt +1 -1
  5. data/README.md +264 -90
  6. data/lib/capybara/config.rb +29 -57
  7. data/lib/capybara/cucumber.rb +2 -3
  8. data/lib/capybara/driver/base.rb +35 -18
  9. data/lib/capybara/driver/node.rb +40 -10
  10. data/lib/capybara/dsl.rb +10 -7
  11. data/lib/capybara/helpers.rb +70 -31
  12. data/lib/capybara/minitest/spec.rb +185 -83
  13. data/lib/capybara/minitest.rb +232 -112
  14. data/lib/capybara/node/actions.rb +274 -171
  15. data/lib/capybara/node/base.rb +42 -34
  16. data/lib/capybara/node/document.rb +15 -3
  17. data/lib/capybara/node/document_matchers.rb +19 -21
  18. data/lib/capybara/node/element.rb +362 -135
  19. data/lib/capybara/node/finders.rb +151 -137
  20. data/lib/capybara/node/matchers.rb +394 -209
  21. data/lib/capybara/node/simple.rb +59 -26
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +12 -9
  25. data/lib/capybara/queries/base_query.rb +39 -28
  26. data/lib/capybara/queries/current_path_query.rb +21 -27
  27. data/lib/capybara/queries/match_query.rb +14 -7
  28. data/lib/capybara/queries/selector_query.rb +659 -149
  29. data/lib/capybara/queries/sibling_query.rb +11 -9
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +56 -38
  32. data/lib/capybara/queries/title_query.rb +8 -11
  33. data/lib/capybara/rack_test/browser.rb +114 -42
  34. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  35. data/lib/capybara/rack_test/driver.rb +22 -17
  36. data/lib/capybara/rack_test/errors.rb +6 -0
  37. data/lib/capybara/rack_test/form.rb +93 -58
  38. data/lib/capybara/rack_test/node.rb +184 -81
  39. data/lib/capybara/rails.rb +3 -7
  40. data/lib/capybara/registration_container.rb +41 -0
  41. data/lib/capybara/registrations/drivers.rb +42 -0
  42. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  43. data/lib/capybara/registrations/servers.rb +66 -0
  44. data/lib/capybara/result.rb +97 -63
  45. data/lib/capybara/rspec/features.rb +17 -50
  46. data/lib/capybara/rspec/matcher_proxies.rb +52 -15
  47. data/lib/capybara/rspec/matchers/base.rb +113 -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 +69 -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 +43 -0
  59. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  60. data/lib/capybara/rspec/matchers.rb +146 -310
  61. data/lib/capybara/rspec.rb +7 -11
  62. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  63. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  64. data/lib/capybara/selector/css.rb +85 -13
  65. data/lib/capybara/selector/definition/button.rb +68 -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 +28 -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 +55 -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/definition.rb +280 -0
  87. data/lib/capybara/selector/filter.rb +2 -17
  88. data/lib/capybara/selector/filter_set.rb +80 -33
  89. data/lib/capybara/selector/filters/base.rb +50 -6
  90. data/lib/capybara/selector/filters/expression_filter.rb +8 -26
  91. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  92. data/lib/capybara/selector/filters/node_filter.rb +16 -12
  93. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  94. data/lib/capybara/selector/selector.rb +93 -210
  95. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  96. data/lib/capybara/selector.rb +475 -523
  97. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  99. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  100. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  101. data/lib/capybara/selenium/driver.rb +298 -267
  102. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  103. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  104. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  105. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  106. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  107. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  108. data/lib/capybara/selenium/extensions/find.rb +110 -0
  109. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  110. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  111. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  112. data/lib/capybara/selenium/node.rb +517 -145
  113. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  114. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  115. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  116. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  117. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  118. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  119. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  120. data/lib/capybara/selenium/patches/logs.rb +45 -0
  121. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  122. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  123. data/lib/capybara/server/animation_disabler.rb +80 -0
  124. data/lib/capybara/server/checker.rb +44 -0
  125. data/lib/capybara/server/middleware.rb +71 -0
  126. data/lib/capybara/server.rb +59 -67
  127. data/lib/capybara/session/config.rb +81 -67
  128. data/lib/capybara/session/matchers.rb +28 -20
  129. data/lib/capybara/session.rb +337 -365
  130. data/lib/capybara/spec/public/jquery.js +5 -5
  131. data/lib/capybara/spec/public/offset.js +6 -0
  132. data/lib/capybara/spec/public/test.js +151 -12
  133. data/lib/capybara/spec/session/accept_alert_spec.rb +12 -11
  134. data/lib/capybara/spec/session/accept_confirm_spec.rb +6 -5
  135. data/lib/capybara/spec/session/accept_prompt_spec.rb +10 -10
  136. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  137. data/lib/capybara/spec/session/all_spec.rb +161 -57
  138. data/lib/capybara/spec/session/ancestor_spec.rb +27 -24
  139. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +68 -38
  140. data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
  141. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  142. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  143. data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
  144. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +22 -12
  145. data/lib/capybara/spec/session/attach_file_spec.rb +144 -69
  146. data/lib/capybara/spec/session/body_spec.rb +12 -13
  147. data/lib/capybara/spec/session/check_spec.rb +117 -55
  148. data/lib/capybara/spec/session/choose_spec.rb +64 -31
  149. data/lib/capybara/spec/session/click_button_spec.rb +231 -173
  150. data/lib/capybara/spec/session/click_link_or_button_spec.rb +55 -35
  151. data/lib/capybara/spec/session/click_link_spec.rb +93 -58
  152. data/lib/capybara/spec/session/current_scope_spec.rb +12 -11
  153. data/lib/capybara/spec/session/current_url_spec.rb +57 -39
  154. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +4 -4
  155. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +3 -2
  156. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  157. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  158. data/lib/capybara/spec/session/element/match_xpath_spec.rb +9 -7
  159. data/lib/capybara/spec/session/element/matches_selector_spec.rb +71 -57
  160. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +8 -7
  161. data/lib/capybara/spec/session/evaluate_script_spec.rb +29 -8
  162. data/lib/capybara/spec/session/execute_script_spec.rb +10 -8
  163. data/lib/capybara/spec/session/fill_in_spec.rb +134 -43
  164. data/lib/capybara/spec/session/find_button_spec.rb +25 -24
  165. data/lib/capybara/spec/session/find_by_id_spec.rb +10 -9
  166. data/lib/capybara/spec/session/find_field_spec.rb +37 -41
  167. data/lib/capybara/spec/session/find_link_spec.rb +46 -17
  168. data/lib/capybara/spec/session/find_spec.rb +260 -145
  169. data/lib/capybara/spec/session/first_spec.rb +80 -52
  170. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  171. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  172. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +33 -20
  173. data/lib/capybara/spec/session/frame/within_frame_spec.rb +52 -32
  174. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  175. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  176. data/lib/capybara/spec/session/has_all_selectors_spec.rb +31 -31
  177. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  178. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  179. data/lib/capybara/spec/session/has_button_spec.rb +100 -13
  180. data/lib/capybara/spec/session/has_css_spec.rb +272 -137
  181. data/lib/capybara/spec/session/has_current_path_spec.rb +60 -61
  182. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  183. data/lib/capybara/spec/session/has_field_spec.rb +139 -59
  184. data/lib/capybara/spec/session/has_link_spec.rb +47 -6
  185. data/lib/capybara/spec/session/has_none_selectors_spec.rb +42 -40
  186. data/lib/capybara/spec/session/has_select_spec.rb +107 -72
  187. data/lib/capybara/spec/session/has_selector_spec.rb +120 -71
  188. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  189. data/lib/capybara/spec/session/has_table_spec.rb +183 -5
  190. data/lib/capybara/spec/session/has_text_spec.rb +106 -62
  191. data/lib/capybara/spec/session/has_title_spec.rb +20 -14
  192. data/lib/capybara/spec/session/has_xpath_spec.rb +57 -38
  193. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +3 -2
  194. data/lib/capybara/spec/session/html_spec.rb +14 -6
  195. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  196. data/lib/capybara/spec/session/node_spec.rb +1024 -153
  197. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  198. data/lib/capybara/spec/session/refresh_spec.rb +12 -6
  199. data/lib/capybara/spec/session/reset_session_spec.rb +82 -35
  200. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +2 -1
  201. data/lib/capybara/spec/session/save_and_open_page_spec.rb +3 -2
  202. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +8 -12
  203. data/lib/capybara/spec/session/save_page_spec.rb +42 -55
  204. data/lib/capybara/spec/session/save_screenshot_spec.rb +16 -14
  205. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  206. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  207. data/lib/capybara/spec/session/select_spec.rb +107 -81
  208. data/lib/capybara/spec/session/selectors_spec.rb +52 -19
  209. data/lib/capybara/spec/session/sibling_spec.rb +10 -10
  210. data/lib/capybara/spec/session/text_spec.rb +37 -21
  211. data/lib/capybara/spec/session/title_spec.rb +17 -5
  212. data/lib/capybara/spec/session/uncheck_spec.rb +43 -23
  213. data/lib/capybara/spec/session/unselect_spec.rb +39 -38
  214. data/lib/capybara/spec/session/visit_spec.rb +85 -53
  215. data/lib/capybara/spec/session/window/become_closed_spec.rb +24 -20
  216. data/lib/capybara/spec/session/window/current_window_spec.rb +5 -3
  217. data/lib/capybara/spec/session/window/open_new_window_spec.rb +5 -3
  218. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +27 -22
  219. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +12 -6
  220. data/lib/capybara/spec/session/window/window_spec.rb +97 -63
  221. data/lib/capybara/spec/session/window/windows_spec.rb +12 -10
  222. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -86
  223. data/lib/capybara/spec/session/within_spec.rb +83 -44
  224. data/lib/capybara/spec/spec_helper.rb +54 -44
  225. data/lib/capybara/spec/test_app.rb +158 -43
  226. data/lib/capybara/spec/views/animated.erb +49 -0
  227. data/lib/capybara/spec/views/form.erb +163 -42
  228. data/lib/capybara/spec/views/frame_child.erb +4 -3
  229. data/lib/capybara/spec/views/frame_one.erb +2 -1
  230. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  231. data/lib/capybara/spec/views/frame_two.erb +1 -1
  232. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  233. data/lib/capybara/spec/views/layout.erb +10 -0
  234. data/lib/capybara/spec/views/obscured.erb +47 -0
  235. data/lib/capybara/spec/views/offset.erb +33 -0
  236. data/lib/capybara/spec/views/path.erb +2 -2
  237. data/lib/capybara/spec/views/popup_one.erb +1 -1
  238. data/lib/capybara/spec/views/popup_two.erb +1 -1
  239. data/lib/capybara/spec/views/react.erb +45 -0
  240. data/lib/capybara/spec/views/scroll.erb +21 -0
  241. data/lib/capybara/spec/views/spatial.erb +31 -0
  242. data/lib/capybara/spec/views/tables.erb +68 -1
  243. data/lib/capybara/spec/views/with_animation.erb +81 -0
  244. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  245. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  246. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  247. data/lib/capybara/spec/views/with_hover.erb +3 -2
  248. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  249. data/lib/capybara/spec/views/with_html.erb +69 -12
  250. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  251. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  252. data/lib/capybara/spec/views/with_js.erb +30 -5
  253. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  254. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  255. data/lib/capybara/spec/views/with_scope.erb +2 -2
  256. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  257. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  258. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  259. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  260. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  261. data/lib/capybara/spec/views/with_windows.erb +1 -1
  262. data/lib/capybara/spec/views/within_frames.erb +5 -2
  263. data/lib/capybara/version.rb +2 -1
  264. data/lib/capybara/window.rb +36 -34
  265. data/lib/capybara.rb +134 -107
  266. data/spec/basic_node_spec.rb +60 -34
  267. data/spec/capybara_spec.rb +63 -88
  268. data/spec/counter_spec.rb +35 -0
  269. data/spec/css_builder_spec.rb +101 -0
  270. data/spec/css_splitter_spec.rb +38 -0
  271. data/spec/dsl_spec.rb +85 -64
  272. data/spec/filter_set_spec.rb +27 -9
  273. data/spec/fixtures/certificate.pem +25 -0
  274. data/spec/fixtures/key.pem +27 -0
  275. data/spec/fixtures/selenium_driver_rspec_failure.rb +6 -5
  276. data/spec/fixtures/selenium_driver_rspec_success.rb +6 -5
  277. data/spec/minitest_spec.rb +52 -7
  278. data/spec/minitest_spec_spec.rb +94 -63
  279. data/spec/per_session_config_spec.rb +14 -13
  280. data/spec/rack_test_spec.rb +194 -125
  281. data/spec/regexp_dissassembler_spec.rb +250 -0
  282. data/spec/result_spec.rb +111 -50
  283. data/spec/rspec/features_spec.rb +37 -31
  284. data/spec/rspec/scenarios_spec.rb +10 -8
  285. data/spec/rspec/shared_spec_matchers.rb +473 -422
  286. data/spec/rspec/views_spec.rb +5 -3
  287. data/spec/rspec_matchers_spec.rb +52 -11
  288. data/spec/rspec_spec.rb +109 -89
  289. data/spec/sauce_spec_chrome.rb +43 -0
  290. data/spec/selector_spec.rb +397 -68
  291. data/spec/selenium_spec_chrome.rb +187 -40
  292. data/spec/selenium_spec_chrome_remote.rb +96 -0
  293. data/spec/selenium_spec_edge.rb +60 -0
  294. data/spec/selenium_spec_firefox.rb +201 -41
  295. data/spec/selenium_spec_firefox_remote.rb +94 -0
  296. data/spec/selenium_spec_ie.rb +149 -0
  297. data/spec/selenium_spec_safari.rb +162 -0
  298. data/spec/server_spec.rb +213 -102
  299. data/spec/session_spec.rb +53 -16
  300. data/spec/shared_selenium_node.rb +79 -0
  301. data/spec/shared_selenium_session.rb +473 -122
  302. data/spec/spec_helper.rb +126 -7
  303. data/spec/whitespace_normalizer_spec.rb +54 -0
  304. data/spec/xpath_builder_spec.rb +93 -0
  305. metadata +355 -73
  306. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  307. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  308. data/.yard/yard_extensions.rb +0 -78
  309. data/lib/capybara/query.rb +0 -7
  310. data/lib/capybara/rspec/compound.rb +0 -95
  311. data/lib/capybara/spec/session/assert_current_path.rb +0 -72
  312. data/lib/capybara/spec/session/assert_selector.rb +0 -148
  313. data/lib/capybara/spec/session/assert_text.rb +0 -234
  314. data/lib/capybara/spec/session/source_spec.rb +0 -0
  315. data/lib/capybara/spec/views/with_title.erb +0 -5
  316. data/spec/selenium_spec_marionette.rb +0 -127
@@ -1,48 +1,94 @@
1
1
  # frozen_string_literal: true
2
- require "uri"
2
+
3
+ require 'uri'
4
+ require 'English'
3
5
 
4
6
  class Capybara::Selenium::Driver < Capybara::Driver::Base
7
+ include Capybara::Selenium::Find
5
8
 
6
9
  DEFAULT_OPTIONS = {
7
- :browser => :firefox,
8
- clear_local_storage: false,
9
- clear_session_storage: false
10
- }
11
- SPECIAL_OPTIONS = [:browser, :clear_local_storage, :clear_session_storage]
10
+ browser: :firefox,
11
+ clear_local_storage: nil,
12
+ clear_session_storage: nil
13
+ }.freeze
14
+ SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
15
+ CAPS_VERSION = Gem::Requirement.new('< 4.8.0')
12
16
 
13
17
  attr_reader :app, :options
14
18
 
19
+ class << self
20
+ attr_reader :selenium_webdriver_version
21
+
22
+ def load_selenium
23
+ require 'selenium-webdriver'
24
+ require 'capybara/selenium/patches/atoms'
25
+ require 'capybara/selenium/patches/is_displayed'
26
+
27
+ # Look up the version of `selenium-webdriver` to
28
+ # see if it's a version we support.
29
+ #
30
+ # By default, we use Gem.loaded_specs to determine
31
+ # the version number. However, in some cases, such
32
+ # as when loading `selenium-webdriver` outside of
33
+ # Rubygems, we fall back to referencing
34
+ # Selenium::WebDriver::VERSION. Ideally we'd
35
+ # use the constant in all cases, but earlier versions
36
+ # of `selenium-webdriver` didn't provide the constant.
37
+ @selenium_webdriver_version =
38
+ if Gem.loaded_specs['selenium-webdriver']
39
+ Gem.loaded_specs['selenium-webdriver'].version
40
+ else
41
+ Gem::Version.new(Selenium::WebDriver::VERSION)
42
+ end
43
+
44
+ unless Gem::Requirement.new('>= 4.8').satisfied_by? @selenium_webdriver_version
45
+ warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade to 4.8+."
46
+ end
47
+
48
+ @selenium_webdriver_version
49
+ rescue LoadError => e
50
+ raise e unless e.message.include?('selenium-webdriver')
51
+
52
+ raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
53
+ end
54
+
55
+ attr_reader :specializations
56
+
57
+ def register_specialization(browser_name, specialization)
58
+ @specializations ||= {}
59
+ @specializations[browser_name] = specialization
60
+ end
61
+ end
62
+
15
63
  def browser
16
64
  unless @browser
17
- if firefox?
18
- options[:desired_capabilities] ||= {}
19
- options[:desired_capabilities].merge!({ unexpectedAlertBehaviour: "ignore" })
65
+ options[:http_client] ||= begin
66
+ require 'capybara/selenium/patches/persistent_client'
67
+ if options[:timeout]
68
+ ::Capybara::Selenium::PersistentClient.new(read_timeout: options[:timeout])
69
+ else
70
+ ::Capybara::Selenium::PersistentClient.new
71
+ end
20
72
  end
73
+ processed_options = options.except(*SPECIAL_OPTIONS)
21
74
 
22
- @processed_options = options.reject { |key,_val| SPECIAL_OPTIONS.include?(key) }
23
- @browser = Selenium::WebDriver.for(options[:browser], @processed_options)
24
-
25
- @w3c = ((defined?(Selenium::WebDriver::Remote::W3CCapabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3CCapabilities)) ||
26
- (defined?(Selenium::WebDriver::Remote::W3C::Capabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3C::Capabilities)))
27
- main = Process.pid
28
- at_exit do
29
- # Store the exit status of the test run since it goes away after calling the at_exit proc...
30
- @exit_status = $!.status if $!.is_a?(SystemExit)
31
- quit if Process.pid == main
32
- exit @exit_status if @exit_status # Force exit with stored status
33
- end
75
+ @browser = Selenium::WebDriver.for(options[:browser], processed_options)
76
+
77
+ specialize_driver
78
+ setup_exit_handler
34
79
  end
35
80
  @browser
36
81
  end
37
82
 
38
- def initialize(app, options={})
39
- load_selenium
40
- @session = nil
83
+ def initialize(app, **options)
84
+ super()
85
+ self.class.load_selenium
41
86
  @app = app
42
87
  @browser = nil
43
88
  @exit_status = nil
44
- @frame_handles = {}
89
+ @frame_handles = Hash.new { |hash, handle| hash[handle] = [] }
45
90
  @options = DEFAULT_OPTIONS.merge(options)
91
+ @node_class = ::Capybara::Selenium::Node
46
92
  end
47
93
 
48
94
  def visit(path)
@@ -50,10 +96,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
50
96
  end
51
97
 
52
98
  def refresh
53
- accept_modal(nil, wait: 0.1) do
54
- browser.navigate.refresh
55
- end
56
- rescue Capybara::ModalNotFound
99
+ browser.navigate.refresh
57
100
  end
58
101
 
59
102
  def go_back
@@ -66,6 +109,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
66
109
 
67
110
  def html
68
111
  browser.page_source
112
+ rescue Selenium::WebDriver::Error::JavascriptError => e
113
+ raise unless e.message.include?('documentElement is null')
69
114
  end
70
115
 
71
116
  def title
@@ -76,19 +121,11 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
76
121
  browser.current_url
77
122
  end
78
123
 
79
- def find_xpath(selector)
80
- browser.find_elements(:xpath, selector).map { |node| Capybara::Selenium::Node.new(self, node) }
81
- end
82
-
83
- def find_css(selector)
84
- browser.find_elements(:css, selector).map { |node| Capybara::Selenium::Node.new(self, node) }
85
- end
86
-
87
124
  def wait?; true; end
88
125
  def needs_server?; true; end
89
126
 
90
127
  def execute_script(script, *args)
91
- browser.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
128
+ browser.execute_script(script, *native_args(args))
92
129
  end
93
130
 
94
131
  def evaluate_script(script, *args)
@@ -98,83 +135,69 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
98
135
 
99
136
  def evaluate_async_script(script, *args)
100
137
  browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time
101
- result = browser.execute_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
138
+ result = browser.execute_async_script(script, *native_args(args))
102
139
  unwrap_script_result(result)
103
140
  end
104
141
 
105
- def save_screenshot(path, _options={})
106
- browser.save_screenshot(path)
142
+ def active_element
143
+ build_node(native_active_element)
144
+ end
145
+
146
+ def send_keys(*args)
147
+ # Should this call the specialized nodes rather than native???
148
+ native_active_element.send_keys(*args)
149
+ end
150
+
151
+ def save_screenshot(path, **options)
152
+ browser.save_screenshot(path, **options)
107
153
  end
108
154
 
109
155
  def reset!
110
156
  # Use instance variable directly so we avoid starting the browser just to reset the session
111
- if @browser
112
- navigated = false
113
- start_time = Capybara::Helpers.monotonic_time
114
- begin
115
- if !navigated
116
- # Only trigger a navigation if we haven't done it already, otherwise it
117
- # can trigger an endless series of unload modals
118
- begin
119
- @browser.manage.delete_all_cookies
120
- if options[:clear_session_storage]
121
- if @browser.respond_to? :session_storage
122
- @browser.session_storage.clear
123
- else
124
- warn "sessionStorage clear requested but is not available for this driver"
125
- end
126
- end
127
- if options[:clear_local_storage]
128
- if @browser.respond_to? :local_storage
129
- @browser.local_storage.clear
130
- else
131
- warn "localStorage clear requested but is not available for this driver"
132
- end
133
- end
134
- rescue Selenium::WebDriver::Error::UnhandledError
135
- # delete_all_cookies fails when we've previously gone
136
- # to about:blank, so we rescue this error and do nothing
137
- # instead.
138
- end
139
- @browser.navigate.to("about:blank")
140
- end
141
- navigated = true
157
+ return unless @browser
142
158
 
143
- #Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
144
- until find_xpath("/html/body/*").empty? do
145
- raise Capybara::ExpectationNotMet.new('Timed out waiting for Selenium session reset') if (Capybara::Helpers.monotonic_time - start_time) >= 10
146
- sleep 0.05
147
- end
148
- rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
149
- # This error is thrown if an unhandled alert is on the page
150
- # Firefox appears to automatically dismiss this alert, chrome does not
151
- # We'll try to accept it
152
- begin
153
- @browser.switch_to.alert.accept
154
- sleep 0.25 # allow time for the modal to be handled
155
- rescue modal_error
156
- # The alert is now gone - nothing to do
157
- end
158
- # try cleaning up the browser again
159
- retry
160
- end
159
+ navigated = false
160
+ timer = Capybara::Helpers.timer(expire_in: 10)
161
+ begin
162
+ # Only trigger a navigation if we haven't done it already, otherwise it
163
+ # can trigger an endless series of unload modals
164
+ reset_browser_state unless navigated
165
+ navigated = true
166
+ # Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
167
+ wait_for_empty_page(timer)
168
+ rescue *unhandled_alert_errors
169
+ # This error is thrown if an unhandled alert is on the page
170
+ # Firefox appears to automatically dismiss this alert, chrome does not
171
+ # We'll try to accept it
172
+ accept_unhandled_reset_alert
173
+ # try cleaning up the browser again
174
+ retry
175
+ end
176
+ end
177
+
178
+ def frame_obscured_at?(x:, y:)
179
+ frame = @frame_handles[current_window_handle].last
180
+ return false unless frame
181
+
182
+ switch_to_frame(:parent)
183
+ begin
184
+ frame.base.obscured?(x: x, y: y)
185
+ ensure
186
+ switch_to_frame(frame)
161
187
  end
162
188
  end
163
189
 
164
190
  def switch_to_frame(frame)
191
+ handles = @frame_handles[current_window_handle]
165
192
  case frame
166
193
  when :top
167
- @frame_handles[browser.window_handle] = []
194
+ handles.clear
168
195
  browser.switch_to.default_content
169
196
  when :parent
170
- # would love to use browser.switch_to.parent_frame here
171
- # but it has an issue if the current frame is removed from within it
172
- @frame_handles[browser.window_handle].pop
173
- browser.switch_to.default_content
174
- @frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
197
+ handles.pop
198
+ browser.switch_to.parent_frame
175
199
  else
176
- @frame_handles[browser.window_handle] ||= []
177
- @frame_handles[browser.window_handle] << frame.native
200
+ handles << frame
178
201
  browser.switch_to.frame(frame.native)
179
202
  end
180
203
  end
@@ -192,12 +215,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
192
215
 
193
216
  def resize_window_to(handle, width, height)
194
217
  within_given_window(handle) do
195
- # Don't set the size if already set - See https://github.com/mozilla/geckodriver/issues/643
196
- if marionette? && (window_size(handle) == [width, height])
197
- {}
198
- else
199
- browser.manage.window.resize_to(width, height)
200
- end
218
+ browser.manage.window.resize_to(width, height)
201
219
  end
202
220
  end
203
221
 
@@ -208,7 +226,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
208
226
  sleep 0.1 # work around for https://code.google.com/p/selenium/issues/detail?id=7405
209
227
  end
210
228
 
229
+ def fullscreen_window(handle)
230
+ within_given_window(handle) do
231
+ browser.manage.window.full_screen
232
+ end
233
+ end
234
+
211
235
  def close_window(handle)
236
+ raise ArgumentError, 'Not allowed to close the primary window' if handle == window_handles.first
237
+
212
238
  within_given_window(handle) do
213
239
  browser.close
214
240
  end
@@ -218,7 +244,16 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
218
244
  browser.window_handles
219
245
  end
220
246
 
221
- def open_new_window
247
+ def open_new_window(kind = :tab)
248
+ if browser.switch_to.respond_to?(:new_window)
249
+ handle = current_window_handle
250
+ browser.switch_to.new_window(kind)
251
+ switch_to_window(handle)
252
+ else
253
+ browser.manage.new_window(kind)
254
+ end
255
+ rescue NoMethodError, Selenium::WebDriver::Error::WebDriverError
256
+ # If not supported by the driver or browser default to using JS
222
257
  browser.execute_script('window.open();')
223
258
  end
224
259
 
@@ -226,14 +261,9 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
226
261
  browser.switch_to.window handle
227
262
  end
228
263
 
229
- def within_window(locator)
230
- handle = find_window(locator)
231
- browser.switch_to.window(handle) { yield }
232
- end
233
-
234
- def accept_modal(_type, options={})
264
+ def accept_modal(_type, **options)
235
265
  yield if block_given?
236
- modal = find_modal(options)
266
+ modal = find_modal(**options)
237
267
 
238
268
  modal.send_keys options[:with] if options[:with]
239
269
 
@@ -242,17 +272,18 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
242
272
  message
243
273
  end
244
274
 
245
- def dismiss_modal(_type, options={})
275
+ def dismiss_modal(_type, **options)
246
276
  yield if block_given?
247
- modal = find_modal(options)
277
+ modal = find_modal(**options)
248
278
  message = modal.text
249
279
  modal.dismiss
250
280
  message
251
281
  end
252
282
 
253
283
  def quit
254
- @browser.quit if @browser
255
- rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
284
+ @browser&.quit
285
+ rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED,
286
+ Selenium::WebDriver::Error::InvalidSessionIdError
256
287
  # Browser must have already gone
257
288
  rescue Selenium::WebDriver::Error::UnknownError => e
258
289
  unless silenced_unknown_error_message?(e.message) # Most likely already gone
@@ -264,129 +295,105 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
264
295
  end
265
296
 
266
297
  def invalid_element_errors
267
- [::Selenium::WebDriver::Error::StaleElementReferenceError,
268
- ::Selenium::WebDriver::Error::UnhandledError,
269
- ::Selenium::WebDriver::Error::ElementNotVisibleError,
270
- ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
271
- ::Selenium::WebDriver::Error::ElementNotInteractableError,
272
- ::Selenium::WebDriver::Error::ElementClickInterceptedError,
273
- ::Selenium::WebDriver::Error::InvalidElementStateError,
274
- ::Selenium::WebDriver::Error::ElementNotSelectableError,
275
- ]
298
+ @invalid_element_errors ||=
299
+ [
300
+ ::Selenium::WebDriver::Error::StaleElementReferenceError,
301
+ ::Selenium::WebDriver::Error::ElementNotInteractableError,
302
+ ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around chromedriver go_back/go_forward race condition
303
+ ::Selenium::WebDriver::Error::ElementClickInterceptedError,
304
+ ::Selenium::WebDriver::Error::NoSuchElementError, # IE
305
+ ::Selenium::WebDriver::Error::InvalidArgumentError # IE
306
+ ].tap do |errors|
307
+ if defined?(::Selenium::WebDriver::Error::DetachedShadowRootError)
308
+ errors.push(::Selenium::WebDriver::Error::DetachedShadowRootError)
309
+ end
310
+ end
276
311
  end
277
312
 
278
313
  def no_such_window_error
279
314
  Selenium::WebDriver::Error::NoSuchWindowError
280
315
  end
281
316
 
282
- # @api private
283
- def marionette?
284
- firefox? && browser && @w3c
285
- end
317
+ private
286
318
 
287
- # @api private
288
- def firefox?
289
- browser_name == "firefox"
319
+ def native_args(args)
320
+ args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
290
321
  end
291
322
 
292
- # @api private
293
- def chrome?
294
- browser_name == "chrome"
323
+ def native_active_element
324
+ browser.switch_to.active_element
295
325
  end
296
326
 
297
- # @deprecated This method is being removed
298
- def browser_initialized?
299
- super && !@browser.nil?
327
+ def clear_browser_state
328
+ delete_all_cookies
329
+ clear_storage
330
+ rescue *clear_browser_state_errors
331
+ # delete_all_cookies fails when we've previously gone
332
+ # to about:blank, so we rescue this error and do nothing
333
+ # instead.
300
334
  end
301
335
 
302
- private
336
+ def clear_browser_state_errors
337
+ @clear_browser_state_errors ||= [Selenium::WebDriver::Error::UnknownError]
338
+ end
303
339
 
304
- # @api private
305
- def browser_name
306
- options[:browser].to_s
340
+ def unhandled_alert_errors
341
+ @unhandled_alert_errors ||= [Selenium::WebDriver::Error::UnexpectedAlertOpenError]
307
342
  end
308
343
 
309
- def modal_error
310
- if defined?(Selenium::WebDriver::Error::NoSuchAlertError)
311
- Selenium::WebDriver::Error::NoSuchAlertError
312
- else
313
- Selenium::WebDriver::Error::NoAlertPresentError
314
- end
344
+ def delete_all_cookies
345
+ @browser.manage.delete_all_cookies
315
346
  end
316
347
 
317
- def find_window(locator)
318
- handles = browser.window_handles
319
- return locator if handles.include? locator
348
+ def clear_storage
349
+ clear_session_storage unless options[:clear_session_storage] == false
350
+ clear_local_storage unless options[:clear_local_storage] == false
351
+ rescue Selenium::WebDriver::Error::JavascriptError
352
+ # session/local storage may not be available if on non-http pages (e.g. about:blank)
353
+ end
320
354
 
321
- original_handle = browser.window_handle
322
- handles.each do |handle|
323
- switch_to_window(handle)
324
- if (locator == browser.execute_script("return window.name") ||
325
- browser.title.include?(locator) ||
326
- browser.current_url.include?(locator))
327
- switch_to_window(original_handle)
328
- return handle
355
+ def clear_session_storage
356
+ if @browser.respond_to? :session_storage
357
+ @browser.session_storage.clear
358
+ else
359
+ begin
360
+ @browser&.execute_script('window.sessionStorage.clear()')
361
+ rescue # rubocop:disable Style/RescueStandardError
362
+ unless options[:clear_session_storage].nil?
363
+ warn 'sessionStorage clear requested but is not supported by this driver'
364
+ end
329
365
  end
330
366
  end
331
- raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
332
367
  end
333
368
 
334
- def insert_modal_handlers(accept, response_text)
335
- prompt_response = if accept
336
- if response_text.nil?
337
- "default_text"
338
- else
339
- "'#{response_text.gsub("\\", "\\\\\\").gsub("'", "\\\\'")}'"
340
- end
369
+ def clear_local_storage
370
+ if @browser.respond_to? :local_storage
371
+ @browser.local_storage.clear
341
372
  else
342
- 'null'
373
+ begin
374
+ @browser&.execute_script('window.localStorage.clear()')
375
+ rescue # rubocop:disable Style/RescueStandardError
376
+ unless options[:clear_local_storage].nil?
377
+ warn 'localStorage clear requested but is not supported by this driver'
378
+ end
379
+ end
343
380
  end
381
+ end
382
+
383
+ def navigate_with_accept(url)
384
+ @browser.navigate.to(url)
385
+ sleep 0.1 # slight wait for alert
386
+ @browser.switch_to.alert.accept
387
+ rescue modal_error
388
+ # alert now gone, should mean navigation happened
389
+ end
344
390
 
345
- script = <<-JS
346
- if (typeof window.capybara === 'undefined') {
347
- window.capybara = {
348
- modal_handlers: [],
349
- current_modal_status: function() {
350
- return [this.modal_handlers[0].called, this.modal_handlers[0].modal_text];
351
- },
352
- add_handler: function(handler) {
353
- this.modal_handlers.unshift(handler);
354
- },
355
- remove_handler: function(handler) {
356
- window.alert = handler.alert;
357
- window.confirm = handler.confirm;
358
- window.prompt = handler.prompt;
359
- },
360
- handler_called: function(handler, str) {
361
- handler.called = true;
362
- handler.modal_text = str;
363
- this.remove_handler(handler);
364
- }
365
- };
366
- };
367
-
368
- var modal_handler = {
369
- prompt: window.prompt,
370
- confirm: window.confirm,
371
- alert: window.alert,
372
- called: false
373
- }
374
- window.capybara.add_handler(modal_handler);
375
-
376
- window.alert = window.confirm = function(str = "") {
377
- window.capybara.handler_called(modal_handler, str.toString());
378
- return #{accept ? 'true' : 'false'};
379
- }
380
- window.prompt = function(str = "", default_text = "") {
381
- window.capybara.handler_called(modal_handler, str.toString());
382
- return #{prompt_response};
383
- }
384
- JS
385
- execute_script script
391
+ def modal_error
392
+ Selenium::WebDriver::Error::NoSuchAlertError
386
393
  end
387
394
 
388
395
  def within_given_window(handle)
389
- original_handle = self.current_window_handle
396
+ original_handle = current_window_handle
390
397
  if handle == original_handle
391
398
  yield
392
399
  else
@@ -397,90 +404,114 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
397
404
  end
398
405
  end
399
406
 
400
- def find_modal(options={})
407
+ def find_modal(text: nil, **options)
401
408
  # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
402
409
  # Actual wait time may be longer than specified
403
410
  wait = Selenium::WebDriver::Wait.new(
404
- timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
405
- ignore: modal_error)
411
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0,
412
+ ignore: modal_error
413
+ )
406
414
  begin
407
415
  wait.until do
408
416
  alert = @browser.switch_to.alert
409
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
410
- alert.text.match(regexp) ? alert : nil
417
+ regexp = text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(text.to_s))
418
+ matched = alert.text.match?(regexp)
419
+ unless matched
420
+ raise Capybara::ModalNotFound, "Unable to find modal dialog with #{text} - found '#{alert.text}' instead."
421
+ end
422
+
423
+ alert
411
424
  end
412
- rescue Selenium::WebDriver::Error::TimeOutError
413
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
425
+ rescue *find_modal_errors
426
+ raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
414
427
  end
415
428
  end
416
429
 
417
- def find_headless_modal(options={})
418
- # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
419
- # Actual wait time may be longer than specified
420
- wait = Selenium::WebDriver::Wait.new(
421
- timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
422
- ignore: modal_error)
423
- begin
424
- wait.until do
425
- called, alert_text = evaluate_script('window.capybara && window.capybara.current_modal_status()')
426
- if called
427
- execute_script('window.capybara && window.capybara.modal_handlers.shift()')
428
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
429
- if alert_text.match(regexp)
430
- alert_text
431
- else
432
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
433
- end
434
- elsif called.nil?
435
- # page changed so modal_handler data has gone away
436
- warn "Can't verify modal text when page change occurs - ignoring" if options[:text]
437
- ""
438
- else
439
- nil
440
- end
441
- end
442
- rescue Selenium::WebDriver::Error::TimeOutError
443
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
444
- end
430
+ def find_modal_errors
431
+ @find_modal_errors ||= [Selenium::WebDriver::Error::TimeoutError]
445
432
  end
446
433
 
447
434
  def silenced_unknown_error_message?(msg)
448
- silenced_unknown_error_messages.any? { |r| msg =~ r }
435
+ silenced_unknown_error_messages.any? { |regex| msg.match? regex }
449
436
  end
450
437
 
451
438
  def silenced_unknown_error_messages
452
- [ /Error communicating with the remote browser/ ]
439
+ [/Error communicating with the remote browser/]
453
440
  end
454
441
 
455
442
  def unwrap_script_result(arg)
456
443
  case arg
457
444
  when Array
458
- arg.map { |e| unwrap_script_result(e) }
445
+ arg.map { |arr| unwrap_script_result(arr) }
459
446
  when Hash
460
- arg.each { |k, v| arg[k] = unwrap_script_result(v) }
461
- when Selenium::WebDriver::Element
462
- Capybara::Selenium::Node.new(self, arg)
447
+ arg.transform_values! { |value| unwrap_script_result(value) }
448
+ when Selenium::WebDriver::Element, Selenium::WebDriver::ShadowRoot
449
+ build_node(arg)
463
450
  else
464
451
  arg
465
452
  end
466
453
  end
467
454
 
468
- def load_selenium
469
- begin
470
- require 'selenium-webdriver'
471
- # Fix for selenium-webdriver 3.4.0 which misnamed these
472
- if !defined?(::Selenium::WebDriver::Error::ElementNotInteractableError)
473
- ::Selenium::WebDriver::Error.const_set('ElementNotInteractableError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
474
- end
475
- if !defined?(::Selenium::WebDriver::Error::ElementClickInterceptedError)
476
- ::Selenium::WebDriver::Error.const_set('ElementClickInterceptedError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
477
- end
478
- rescue LoadError => e
479
- if e.message =~ /selenium-webdriver/
480
- raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
481
- else
482
- raise e
483
- end
455
+ def find_context
456
+ browser
457
+ end
458
+
459
+ def build_node(native_node, initial_cache = {})
460
+ ::Capybara::Selenium::Node.new(self, native_node, initial_cache)
461
+ end
462
+
463
+ def bridge
464
+ browser.send(:bridge)
465
+ end
466
+
467
+ def specialize_driver
468
+ browser_type = browser.browser
469
+ Capybara::Selenium::Driver.specializations.select { |k, _v| k === browser_type }.each_value do |specialization| # rubocop:disable Style/CaseEquality
470
+ extend specialization
484
471
  end
485
472
  end
473
+
474
+ def setup_exit_handler
475
+ main = Process.pid
476
+ at_exit do
477
+ # Store the exit status of the test run since it goes away after calling the at_exit proc...
478
+ @exit_status = $ERROR_INFO.status if $ERROR_INFO.is_a?(SystemExit)
479
+ quit if Process.pid == main
480
+ exit @exit_status if @exit_status # Force exit with stored status
481
+ end
482
+ end
483
+
484
+ def reset_browser_state
485
+ clear_browser_state
486
+ @browser.navigate.to('about:blank')
487
+ end
488
+
489
+ def wait_for_empty_page(timer)
490
+ until find_xpath('/html/body/*').empty?
491
+ raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if timer.expired?
492
+
493
+ sleep 0.01
494
+
495
+ # It has been observed that it is possible that asynchronous JS code in
496
+ # the application under test can navigate the browser away from about:blank
497
+ # if the timing is just right. Ensure we are still at about:blank...
498
+ @browser.navigate.to('about:blank') unless current_url == 'about:blank'
499
+ end
500
+ end
501
+
502
+ def accept_unhandled_reset_alert
503
+ @browser.switch_to.alert.accept
504
+ sleep 0.25 # allow time for the modal to be handled
505
+ rescue modal_error
506
+ # The alert is now gone.
507
+ # If navigation has not occurred attempt again and accept alert
508
+ # since FF may have dismissed the alert at first attempt.
509
+ navigate_with_accept('about:blank') if current_url != 'about:blank'
510
+ end
486
511
  end
512
+
513
+ require 'capybara/selenium/driver_specializations/chrome_driver'
514
+ require 'capybara/selenium/driver_specializations/firefox_driver'
515
+ require 'capybara/selenium/driver_specializations/internet_explorer_driver'
516
+ require 'capybara/selenium/driver_specializations/safari_driver'
517
+ require 'capybara/selenium/driver_specializations/edge_driver'