capybara 3.32.2

Sign up to get free protection for your applications and to get access to all the features.
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,496 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'English'
5
+
6
+ class Capybara::Selenium::Driver < Capybara::Driver::Base
7
+ include Capybara::Selenium::Find
8
+
9
+ DEFAULT_OPTIONS = {
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
+ attr_reader :app, :options
16
+
17
+ class << self
18
+ def load_selenium
19
+ require 'selenium-webdriver'
20
+ require 'capybara/selenium/logger_suppressor'
21
+ require 'capybara/selenium/patches/atoms'
22
+ require 'capybara/selenium/patches/is_displayed'
23
+ require 'capybara/selenium/patches/action_pauser'
24
+ if Gem.loaded_specs['selenium-webdriver'].version < Gem::Version.new('3.5.0')
25
+ warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
26
+ end
27
+ rescue LoadError => e
28
+ raise e unless e.message.match?(/selenium-webdriver/)
29
+
30
+ 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."
31
+ end
32
+
33
+ attr_reader :specializations
34
+
35
+ def register_specialization(browser_name, specialization)
36
+ @specializations ||= {}
37
+ @specializations[browser_name] = specialization
38
+ end
39
+ end
40
+
41
+ def browser
42
+ unless @browser
43
+ options[:http_client] ||= begin
44
+ require 'capybara/selenium/patches/persistent_client'
45
+ if options[:timeout]
46
+ ::Capybara::Selenium::PersistentClient.new(read_timeout: options[:timeout])
47
+ else
48
+ ::Capybara::Selenium::PersistentClient.new
49
+ end
50
+ end
51
+ processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
52
+ @browser = Selenium::WebDriver.for(options[:browser], processed_options)
53
+
54
+ specialize_driver
55
+ setup_exit_handler
56
+ end
57
+ @browser
58
+ end
59
+
60
+ def initialize(app, **options)
61
+ self.class.load_selenium
62
+ @app = app
63
+ @browser = nil
64
+ @exit_status = nil
65
+ @frame_handles = Hash.new { |hash, handle| hash[handle] = [] }
66
+ @options = DEFAULT_OPTIONS.merge(options)
67
+ @node_class = ::Capybara::Selenium::Node
68
+ end
69
+
70
+ def visit(path)
71
+ browser.navigate.to(path)
72
+ end
73
+
74
+ def refresh
75
+ browser.navigate.refresh
76
+ end
77
+
78
+ def go_back
79
+ browser.navigate.back
80
+ end
81
+
82
+ def go_forward
83
+ browser.navigate.forward
84
+ end
85
+
86
+ def html
87
+ browser.page_source
88
+ end
89
+
90
+ def title
91
+ browser.title
92
+ end
93
+
94
+ def current_url
95
+ browser.current_url
96
+ end
97
+
98
+ def wait?; true; end
99
+ def needs_server?; true; end
100
+
101
+ def execute_script(script, *args)
102
+ browser.execute_script(script, *native_args(args))
103
+ end
104
+
105
+ def evaluate_script(script, *args)
106
+ result = execute_script("return #{script}", *args)
107
+ unwrap_script_result(result)
108
+ end
109
+
110
+ def evaluate_async_script(script, *args)
111
+ browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time
112
+ result = browser.execute_async_script(script, *native_args(args))
113
+ unwrap_script_result(result)
114
+ end
115
+
116
+ def save_screenshot(path, **_options)
117
+ browser.save_screenshot(path)
118
+ end
119
+
120
+ def reset!
121
+ # Use instance variable directly so we avoid starting the browser just to reset the session
122
+ return unless @browser
123
+
124
+ navigated = false
125
+ timer = Capybara::Helpers.timer(expire_in: 10)
126
+ begin
127
+ # Only trigger a navigation if we haven't done it already, otherwise it
128
+ # can trigger an endless series of unload modals
129
+ reset_browser_state unless navigated
130
+ navigated = true
131
+ # Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
132
+ wait_for_empty_page(timer)
133
+ rescue *unhandled_alert_errors
134
+ # This error is thrown if an unhandled alert is on the page
135
+ # Firefox appears to automatically dismiss this alert, chrome does not
136
+ # We'll try to accept it
137
+ accept_unhandled_reset_alert
138
+ # try cleaning up the browser again
139
+ retry
140
+ end
141
+ end
142
+
143
+ def frame_obscured_at?(x:, y:)
144
+ frame = @frame_handles[current_window_handle].last
145
+ return false unless frame
146
+
147
+ switch_to_frame(:parent)
148
+ begin
149
+ frame.base.obscured?(x: x, y: y)
150
+ ensure
151
+ switch_to_frame(frame)
152
+ end
153
+ end
154
+
155
+ def switch_to_frame(frame)
156
+ handles = @frame_handles[current_window_handle]
157
+ case frame
158
+ when :top
159
+ handles.clear
160
+ browser.switch_to.default_content
161
+ when :parent
162
+ handles.pop
163
+ browser.switch_to.parent_frame
164
+ else
165
+ handles << frame
166
+ browser.switch_to.frame(frame.native)
167
+ end
168
+ end
169
+
170
+ def current_window_handle
171
+ browser.window_handle
172
+ end
173
+
174
+ def window_size(handle)
175
+ within_given_window(handle) do
176
+ size = browser.manage.window.size
177
+ [size.width, size.height]
178
+ end
179
+ end
180
+
181
+ def resize_window_to(handle, width, height)
182
+ within_given_window(handle) do
183
+ browser.manage.window.resize_to(width, height)
184
+ end
185
+ end
186
+
187
+ def maximize_window(handle)
188
+ within_given_window(handle) do
189
+ browser.manage.window.maximize
190
+ end
191
+ sleep 0.1 # work around for https://code.google.com/p/selenium/issues/detail?id=7405
192
+ end
193
+
194
+ def fullscreen_window(handle)
195
+ within_given_window(handle) do
196
+ browser.manage.window.full_screen
197
+ end
198
+ end
199
+
200
+ def close_window(handle)
201
+ raise ArgumentError, 'Not allowed to close the primary window' if handle == window_handles.first
202
+
203
+ within_given_window(handle) do
204
+ browser.close
205
+ end
206
+ end
207
+
208
+ def window_handles
209
+ browser.window_handles
210
+ end
211
+
212
+ def open_new_window(kind = :tab)
213
+ browser.manage.new_window(kind)
214
+ rescue NoMethodError, Selenium::WebDriver::Error::WebDriverError
215
+ # If not supported by the driver or browser default to using JS
216
+ browser.execute_script('window.open();')
217
+ end
218
+
219
+ def switch_to_window(handle)
220
+ browser.switch_to.window handle
221
+ end
222
+
223
+ def accept_modal(_type, **options)
224
+ yield if block_given?
225
+ modal = find_modal(**options)
226
+
227
+ modal.send_keys options[:with] if options[:with]
228
+
229
+ message = modal.text
230
+ modal.accept
231
+ message
232
+ end
233
+
234
+ def dismiss_modal(_type, **options)
235
+ yield if block_given?
236
+ modal = find_modal(**options)
237
+ message = modal.text
238
+ modal.dismiss
239
+ message
240
+ end
241
+
242
+ def quit
243
+ @browser&.quit
244
+ rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
245
+ # Browser must have already gone
246
+ rescue Selenium::WebDriver::Error::UnknownError => e
247
+ unless silenced_unknown_error_message?(e.message) # Most likely already gone
248
+ # probably already gone but not sure - so warn
249
+ warn "Ignoring Selenium UnknownError during driver quit: #{e.message}"
250
+ end
251
+ ensure
252
+ @browser = nil
253
+ end
254
+
255
+ def invalid_element_errors
256
+ @invalid_element_errors ||= begin
257
+ [
258
+ ::Selenium::WebDriver::Error::StaleElementReferenceError,
259
+ ::Selenium::WebDriver::Error::ElementNotInteractableError,
260
+ ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around chromedriver go_back/go_forward race condition
261
+ ::Selenium::WebDriver::Error::ElementClickInterceptedError,
262
+ ::Selenium::WebDriver::Error::NoSuchElementError, # IE
263
+ ::Selenium::WebDriver::Error::InvalidArgumentError # IE
264
+ ].tap do |errors|
265
+ unless selenium_4?
266
+ ::Selenium::WebDriver.logger.suppress_deprecations do
267
+ errors.concat [
268
+ ::Selenium::WebDriver::Error::UnhandledError,
269
+ ::Selenium::WebDriver::Error::ElementNotVisibleError,
270
+ ::Selenium::WebDriver::Error::InvalidElementStateError,
271
+ ::Selenium::WebDriver::Error::ElementNotSelectableError
272
+ ]
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+ def no_such_window_error
280
+ Selenium::WebDriver::Error::NoSuchWindowError
281
+ end
282
+
283
+ private
284
+
285
+ def selenium_4?
286
+ defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)
287
+ end
288
+
289
+ def native_args(args)
290
+ args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
291
+ end
292
+
293
+ def clear_browser_state
294
+ delete_all_cookies
295
+ clear_storage
296
+ rescue *clear_browser_state_errors
297
+ # delete_all_cookies fails when we've previously gone
298
+ # to about:blank, so we rescue this error and do nothing
299
+ # instead.
300
+ end
301
+
302
+ def clear_browser_state_errors
303
+ @clear_browser_state_errors ||= [Selenium::WebDriver::Error::UnknownError]
304
+ end
305
+
306
+ def unhandled_alert_errors
307
+ @unhandled_alert_errors ||= with_legacy_error(
308
+ [Selenium::WebDriver::Error::UnexpectedAlertOpenError],
309
+ 'UnhandledAlertError'
310
+ )
311
+ end
312
+
313
+ def delete_all_cookies
314
+ @browser.manage.delete_all_cookies
315
+ end
316
+
317
+ def clear_storage
318
+ clear_session_storage unless options[:clear_session_storage] == false
319
+ clear_local_storage unless options[:clear_local_storage] == false
320
+ rescue Selenium::WebDriver::Error::JavascriptError
321
+ # session/local storage may not be available if on non-http pages (e.g. about:blank)
322
+ end
323
+
324
+ def clear_session_storage
325
+ if @browser.respond_to? :session_storage
326
+ @browser.session_storage.clear
327
+ else
328
+ begin
329
+ @browser&.execute_script('window.sessionStorage.clear()')
330
+ rescue # rubocop:disable Style/RescueStandardError
331
+ unless options[:clear_session_storage].nil?
332
+ warn 'sessionStorage clear requested but is not supported by this driver'
333
+ end
334
+ end
335
+ end
336
+ end
337
+
338
+ def clear_local_storage
339
+ if @browser.respond_to? :local_storage
340
+ @browser.local_storage.clear
341
+ else
342
+ begin
343
+ @browser&.execute_script('window.localStorage.clear()')
344
+ rescue # rubocop:disable Style/RescueStandardError
345
+ unless options[:clear_local_storage].nil?
346
+ warn 'localStorage clear requested but is not supported by this driver'
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ def navigate_with_accept(url)
353
+ @browser.navigate.to(url)
354
+ sleep 0.1 # slight wait for alert
355
+ @browser.switch_to.alert.accept
356
+ rescue modal_error
357
+ # alert now gone, should mean navigation happened
358
+ end
359
+
360
+ def modal_error
361
+ Selenium::WebDriver::Error::NoSuchAlertError
362
+ end
363
+
364
+ def within_given_window(handle)
365
+ original_handle = current_window_handle
366
+ if handle == original_handle
367
+ yield
368
+ else
369
+ switch_to_window(handle)
370
+ result = yield
371
+ switch_to_window(original_handle)
372
+ result
373
+ end
374
+ end
375
+
376
+ def find_modal(text: nil, **options)
377
+ # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
378
+ # Actual wait time may be longer than specified
379
+ wait = Selenium::WebDriver::Wait.new(
380
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0,
381
+ ignore: modal_error
382
+ )
383
+ begin
384
+ wait.until do
385
+ alert = @browser.switch_to.alert
386
+ regexp = text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(text.to_s))
387
+ matched = alert.text.match?(regexp)
388
+ unless matched
389
+ raise Capybara::ModalNotFound, "Unable to find modal dialog with #{text} - found '#{alert.text}' instead."
390
+ end
391
+
392
+ alert
393
+ end
394
+ rescue *find_modal_errors
395
+ raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
396
+ end
397
+ end
398
+
399
+ def find_modal_errors
400
+ @find_modal_errors ||= with_legacy_error([Selenium::WebDriver::Error::TimeoutError], 'TimeOutError')
401
+ end
402
+
403
+ def with_legacy_error(errors, legacy_error)
404
+ errors.tap do |errs|
405
+ unless selenium_4?
406
+ ::Selenium::WebDriver.logger.suppress_deprecations do
407
+ errs << Selenium::WebDriver::Error.const_get(legacy_error)
408
+ end
409
+ end
410
+ end
411
+ end
412
+
413
+ def silenced_unknown_error_message?(msg)
414
+ silenced_unknown_error_messages.any? { |regex| msg.match? regex }
415
+ end
416
+
417
+ def silenced_unknown_error_messages
418
+ [/Error communicating with the remote browser/]
419
+ end
420
+
421
+ def unwrap_script_result(arg)
422
+ case arg
423
+ when Array
424
+ arg.map { |arr| unwrap_script_result(arr) }
425
+ when Hash
426
+ arg.transform_values! { |value| unwrap_script_result(value) }
427
+ when Selenium::WebDriver::Element
428
+ build_node(arg)
429
+ else
430
+ arg
431
+ end
432
+ end
433
+
434
+ def find_context
435
+ browser
436
+ end
437
+
438
+ def build_node(native_node, initial_cache = {})
439
+ ::Capybara::Selenium::Node.new(self, native_node, initial_cache)
440
+ end
441
+
442
+ def bridge
443
+ browser.send(:bridge)
444
+ end
445
+
446
+ def specialize_driver
447
+ browser_type = browser.browser
448
+ Capybara::Selenium::Driver.specializations.select { |k, _v| k === browser_type }.each_value do |specialization| # rubocop:disable Style/CaseEquality
449
+ extend specialization
450
+ end
451
+ end
452
+
453
+ def setup_exit_handler
454
+ main = Process.pid
455
+ at_exit do
456
+ # Store the exit status of the test run since it goes away after calling the at_exit proc...
457
+ @exit_status = $ERROR_INFO.status if $ERROR_INFO.is_a?(SystemExit)
458
+ quit if Process.pid == main
459
+ exit @exit_status if @exit_status # Force exit with stored status
460
+ end
461
+ end
462
+
463
+ def reset_browser_state
464
+ clear_browser_state
465
+ @browser.navigate.to('about:blank')
466
+ end
467
+
468
+ def wait_for_empty_page(timer)
469
+ until find_xpath('/html/body/*').empty?
470
+ raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if timer.expired?
471
+
472
+ sleep 0.01
473
+
474
+ # It has been observed that it is possible that asynchronous JS code in
475
+ # the application under test can navigate the browser away from about:blank
476
+ # if the timing is just right. Ensure we are still at about:blank...
477
+ @browser.navigate.to('about:blank') unless current_url == 'about:blank'
478
+ end
479
+ end
480
+
481
+ def accept_unhandled_reset_alert
482
+ @browser.switch_to.alert.accept
483
+ sleep 0.25 # allow time for the modal to be handled
484
+ rescue modal_error
485
+ # The alert is now gone.
486
+ # If navigation has not occurred attempt again and accept alert
487
+ # since FF may have dismissed the alert at first attempt.
488
+ navigate_with_accept('about:blank') if current_url != 'about:blank'
489
+ end
490
+ end
491
+
492
+ require 'capybara/selenium/driver_specializations/chrome_driver'
493
+ require 'capybara/selenium/driver_specializations/firefox_driver'
494
+ require 'capybara/selenium/driver_specializations/internet_explorer_driver'
495
+ require 'capybara/selenium/driver_specializations/safari_driver'
496
+ require 'capybara/selenium/driver_specializations/edge_driver'