capybara 3.0.0 → 3.40.0

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