capybara 3.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/History.md +1813 -0
  4. data/License.txt +22 -0
  5. data/README.md +1099 -0
  6. data/lib/capybara.rb +511 -0
  7. data/lib/capybara/config.rb +94 -0
  8. data/lib/capybara/cucumber.rb +27 -0
  9. data/lib/capybara/driver/base.rb +170 -0
  10. data/lib/capybara/driver/node.rb +139 -0
  11. data/lib/capybara/dsl.rb +65 -0
  12. data/lib/capybara/helpers.rb +108 -0
  13. data/lib/capybara/minitest.rb +386 -0
  14. data/lib/capybara/minitest/spec.rb +264 -0
  15. data/lib/capybara/node/actions.rb +420 -0
  16. data/lib/capybara/node/base.rb +143 -0
  17. data/lib/capybara/node/document.rb +48 -0
  18. data/lib/capybara/node/document_matchers.rb +67 -0
  19. data/lib/capybara/node/element.rb +606 -0
  20. data/lib/capybara/node/finders.rb +325 -0
  21. data/lib/capybara/node/matchers.rb +883 -0
  22. data/lib/capybara/node/simple.rb +208 -0
  23. data/lib/capybara/queries/ancestor_query.rb +27 -0
  24. data/lib/capybara/queries/base_query.rb +106 -0
  25. data/lib/capybara/queries/current_path_query.rb +51 -0
  26. data/lib/capybara/queries/match_query.rb +26 -0
  27. data/lib/capybara/queries/selector_query.rb +710 -0
  28. data/lib/capybara/queries/sibling_query.rb +26 -0
  29. data/lib/capybara/queries/style_query.rb +45 -0
  30. data/lib/capybara/queries/text_query.rb +110 -0
  31. data/lib/capybara/queries/title_query.rb +39 -0
  32. data/lib/capybara/rack_test/browser.rb +140 -0
  33. data/lib/capybara/rack_test/css_handlers.rb +13 -0
  34. data/lib/capybara/rack_test/driver.rb +109 -0
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +127 -0
  37. data/lib/capybara/rack_test/node.rb +325 -0
  38. data/lib/capybara/rails.rb +16 -0
  39. data/lib/capybara/registrations/drivers.rb +36 -0
  40. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  41. data/lib/capybara/registrations/servers.rb +44 -0
  42. data/lib/capybara/result.rb +190 -0
  43. data/lib/capybara/rspec.rb +29 -0
  44. data/lib/capybara/rspec/features.rb +23 -0
  45. data/lib/capybara/rspec/matcher_proxies.rb +82 -0
  46. data/lib/capybara/rspec/matchers.rb +201 -0
  47. data/lib/capybara/rspec/matchers/base.rb +111 -0
  48. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  49. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  50. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  51. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  52. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  53. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  54. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  55. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  56. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  57. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  58. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  59. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  60. data/lib/capybara/selector.rb +233 -0
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
  63. data/lib/capybara/selector/css.rb +102 -0
  64. data/lib/capybara/selector/definition.rb +276 -0
  65. data/lib/capybara/selector/definition/button.rb +51 -0
  66. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  67. data/lib/capybara/selector/definition/css.rb +10 -0
  68. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  69. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  70. data/lib/capybara/selector/definition/element.rb +27 -0
  71. data/lib/capybara/selector/definition/field.rb +40 -0
  72. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  73. data/lib/capybara/selector/definition/file_field.rb +13 -0
  74. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  75. data/lib/capybara/selector/definition/frame.rb +17 -0
  76. data/lib/capybara/selector/definition/id.rb +6 -0
  77. data/lib/capybara/selector/definition/label.rb +62 -0
  78. data/lib/capybara/selector/definition/link.rb +46 -0
  79. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  80. data/lib/capybara/selector/definition/option.rb +27 -0
  81. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  82. data/lib/capybara/selector/definition/select.rb +81 -0
  83. data/lib/capybara/selector/definition/table.rb +109 -0
  84. data/lib/capybara/selector/definition/table_row.rb +21 -0
  85. data/lib/capybara/selector/definition/xpath.rb +5 -0
  86. data/lib/capybara/selector/filter.rb +5 -0
  87. data/lib/capybara/selector/filter_set.rb +124 -0
  88. data/lib/capybara/selector/filters/base.rb +77 -0
  89. data/lib/capybara/selector/filters/expression_filter.rb +22 -0
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +31 -0
  92. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  93. data/lib/capybara/selector/selector.rb +147 -0
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  96. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  98. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  99. data/lib/capybara/selenium/driver.rb +496 -0
  100. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +119 -0
  101. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +126 -0
  102. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  103. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  104. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  105. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  106. data/lib/capybara/selenium/extensions/find.rb +110 -0
  107. data/lib/capybara/selenium/extensions/html5_drag.rb +228 -0
  108. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  109. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  110. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  111. data/lib/capybara/selenium/node.rb +610 -0
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +119 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  118. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  119. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  120. data/lib/capybara/selenium/patches/logs.rb +47 -0
  121. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  122. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  123. data/lib/capybara/server.rb +126 -0
  124. data/lib/capybara/server/animation_disabler.rb +58 -0
  125. data/lib/capybara/server/checker.rb +44 -0
  126. data/lib/capybara/server/middleware.rb +69 -0
  127. data/lib/capybara/session.rb +942 -0
  128. data/lib/capybara/session/config.rb +124 -0
  129. data/lib/capybara/session/matchers.rb +87 -0
  130. data/lib/capybara/spec/fixtures/another_test_file.txt +1 -0
  131. data/lib/capybara/spec/fixtures/capybara.jpg +3 -0
  132. data/lib/capybara/spec/fixtures/no_extension +1 -0
  133. data/lib/capybara/spec/fixtures/test_file.txt +1 -0
  134. data/lib/capybara/spec/public/jquery-ui.js +13 -0
  135. data/lib/capybara/spec/public/jquery.js +5 -0
  136. data/lib/capybara/spec/public/offset.js +6 -0
  137. data/lib/capybara/spec/public/test.js +268 -0
  138. data/lib/capybara/spec/session/accept_alert_spec.rb +81 -0
  139. data/lib/capybara/spec/session/accept_confirm_spec.rb +32 -0
  140. data/lib/capybara/spec/session/accept_prompt_spec.rb +78 -0
  141. data/lib/capybara/spec/session/all_spec.rb +278 -0
  142. data/lib/capybara/spec/session/ancestor_spec.rb +88 -0
  143. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +140 -0
  144. data/lib/capybara/spec/session/assert_current_path_spec.rb +75 -0
  145. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  146. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  147. data/lib/capybara/spec/session/assert_text_spec.rb +258 -0
  148. data/lib/capybara/spec/session/assert_title_spec.rb +93 -0
  149. data/lib/capybara/spec/session/attach_file_spec.rb +216 -0
  150. data/lib/capybara/spec/session/body_spec.rb +23 -0
  151. data/lib/capybara/spec/session/check_spec.rb +235 -0
  152. data/lib/capybara/spec/session/choose_spec.rb +121 -0
  153. data/lib/capybara/spec/session/click_button_spec.rb +506 -0
  154. data/lib/capybara/spec/session/click_link_or_button_spec.rb +129 -0
  155. data/lib/capybara/spec/session/click_link_spec.rb +229 -0
  156. data/lib/capybara/spec/session/current_scope_spec.rb +31 -0
  157. data/lib/capybara/spec/session/current_url_spec.rb +115 -0
  158. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +36 -0
  159. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +21 -0
  160. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +38 -0
  161. data/lib/capybara/spec/session/element/match_css_spec.rb +31 -0
  162. data/lib/capybara/spec/session/element/match_xpath_spec.rb +25 -0
  163. data/lib/capybara/spec/session/element/matches_selector_spec.rb +120 -0
  164. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
  165. data/lib/capybara/spec/session/evaluate_script_spec.rb +49 -0
  166. data/lib/capybara/spec/session/execute_script_spec.rb +28 -0
  167. data/lib/capybara/spec/session/fill_in_spec.rb +286 -0
  168. data/lib/capybara/spec/session/find_button_spec.rb +74 -0
  169. data/lib/capybara/spec/session/find_by_id_spec.rb +33 -0
  170. data/lib/capybara/spec/session/find_field_spec.rb +113 -0
  171. data/lib/capybara/spec/session/find_link_spec.rb +70 -0
  172. data/lib/capybara/spec/session/find_spec.rb +531 -0
  173. data/lib/capybara/spec/session/first_spec.rb +156 -0
  174. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  175. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  176. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +116 -0
  177. data/lib/capybara/spec/session/frame/within_frame_spec.rb +112 -0
  178. data/lib/capybara/spec/session/go_back_spec.rb +12 -0
  179. data/lib/capybara/spec/session/go_forward_spec.rb +14 -0
  180. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  181. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  182. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  183. data/lib/capybara/spec/session/has_button_spec.rb +69 -0
  184. data/lib/capybara/spec/session/has_css_spec.rb +374 -0
  185. data/lib/capybara/spec/session/has_current_path_spec.rb +138 -0
  186. data/lib/capybara/spec/session/has_field_spec.rb +349 -0
  187. data/lib/capybara/spec/session/has_link_spec.rb +39 -0
  188. data/lib/capybara/spec/session/has_none_selectors_spec.rb +78 -0
  189. data/lib/capybara/spec/session/has_select_spec.rb +310 -0
  190. data/lib/capybara/spec/session/has_selector_spec.rb +202 -0
  191. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  192. data/lib/capybara/spec/session/has_table_spec.rb +198 -0
  193. data/lib/capybara/spec/session/has_text_spec.rb +394 -0
  194. data/lib/capybara/spec/session/has_title_spec.rb +71 -0
  195. data/lib/capybara/spec/session/has_xpath_spec.rb +149 -0
  196. data/lib/capybara/spec/session/headers_spec.rb +8 -0
  197. data/lib/capybara/spec/session/html_spec.rb +47 -0
  198. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  199. data/lib/capybara/spec/session/node_spec.rb +1292 -0
  200. data/lib/capybara/spec/session/node_wrapper_spec.rb +39 -0
  201. data/lib/capybara/spec/session/refresh_spec.rb +33 -0
  202. data/lib/capybara/spec/session/reset_session_spec.rb +148 -0
  203. data/lib/capybara/spec/session/response_code_spec.rb +8 -0
  204. data/lib/capybara/spec/session/save_and_open_page_spec.rb +21 -0
  205. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +43 -0
  206. data/lib/capybara/spec/session/save_page_spec.rb +110 -0
  207. data/lib/capybara/spec/session/save_screenshot_spec.rb +55 -0
  208. data/lib/capybara/spec/session/screenshot_spec.rb +18 -0
  209. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  210. data/lib/capybara/spec/session/select_spec.rb +229 -0
  211. data/lib/capybara/spec/session/selectors_spec.rb +98 -0
  212. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  213. data/lib/capybara/spec/session/source_spec.rb +0 -0
  214. data/lib/capybara/spec/session/text_spec.rb +74 -0
  215. data/lib/capybara/spec/session/title_spec.rb +29 -0
  216. data/lib/capybara/spec/session/uncheck_spec.rb +100 -0
  217. data/lib/capybara/spec/session/unselect_spec.rb +116 -0
  218. data/lib/capybara/spec/session/visit_spec.rb +204 -0
  219. data/lib/capybara/spec/session/window/become_closed_spec.rb +89 -0
  220. data/lib/capybara/spec/session/window/current_window_spec.rb +28 -0
  221. data/lib/capybara/spec/session/window/open_new_window_spec.rb +31 -0
  222. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +132 -0
  223. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +99 -0
  224. data/lib/capybara/spec/session/window/window_spec.rb +203 -0
  225. data/lib/capybara/spec/session/window/windows_spec.rb +34 -0
  226. data/lib/capybara/spec/session/window/within_window_spec.rb +157 -0
  227. data/lib/capybara/spec/session/within_spec.rb +199 -0
  228. data/lib/capybara/spec/spec_helper.rb +134 -0
  229. data/lib/capybara/spec/test_app.rb +226 -0
  230. data/lib/capybara/spec/views/animated.erb +49 -0
  231. data/lib/capybara/spec/views/buttons.erb +5 -0
  232. data/lib/capybara/spec/views/fieldsets.erb +30 -0
  233. data/lib/capybara/spec/views/form.erb +685 -0
  234. data/lib/capybara/spec/views/frame_child.erb +18 -0
  235. data/lib/capybara/spec/views/frame_one.erb +10 -0
  236. data/lib/capybara/spec/views/frame_parent.erb +9 -0
  237. data/lib/capybara/spec/views/frame_two.erb +9 -0
  238. data/lib/capybara/spec/views/header_links.erb +8 -0
  239. data/lib/capybara/spec/views/host_links.erb +13 -0
  240. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  241. data/lib/capybara/spec/views/obscured.erb +47 -0
  242. data/lib/capybara/spec/views/offset.erb +32 -0
  243. data/lib/capybara/spec/views/path.erb +13 -0
  244. data/lib/capybara/spec/views/popup_one.erb +9 -0
  245. data/lib/capybara/spec/views/popup_two.erb +9 -0
  246. data/lib/capybara/spec/views/postback.erb +14 -0
  247. data/lib/capybara/spec/views/react.erb +45 -0
  248. data/lib/capybara/spec/views/scroll.erb +20 -0
  249. data/lib/capybara/spec/views/spatial.erb +31 -0
  250. data/lib/capybara/spec/views/tables.erb +130 -0
  251. data/lib/capybara/spec/views/with_animation.erb +74 -0
  252. data/lib/capybara/spec/views/with_base_tag.erb +11 -0
  253. data/lib/capybara/spec/views/with_count.erb +8 -0
  254. data/lib/capybara/spec/views/with_dragula.erb +22 -0
  255. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  256. data/lib/capybara/spec/views/with_hover.erb +24 -0
  257. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  258. data/lib/capybara/spec/views/with_html.erb +208 -0
  259. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  260. data/lib/capybara/spec/views/with_html_entities.erb +2 -0
  261. data/lib/capybara/spec/views/with_js.erb +160 -0
  262. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  263. data/lib/capybara/spec/views/with_namespace.erb +20 -0
  264. data/lib/capybara/spec/views/with_scope.erb +42 -0
  265. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  266. data/lib/capybara/spec/views/with_simple_html.erb +2 -0
  267. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  268. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  269. data/lib/capybara/spec/views/with_title.erb +5 -0
  270. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  271. data/lib/capybara/spec/views/with_windows.erb +54 -0
  272. data/lib/capybara/spec/views/within_frames.erb +15 -0
  273. data/lib/capybara/version.rb +5 -0
  274. data/lib/capybara/window.rb +146 -0
  275. data/spec/basic_node_spec.rb +154 -0
  276. data/spec/capybara_spec.rb +112 -0
  277. data/spec/css_builder_spec.rb +101 -0
  278. data/spec/css_splitter_spec.rb +38 -0
  279. data/spec/dsl_spec.rb +276 -0
  280. data/spec/filter_set_spec.rb +46 -0
  281. data/spec/fixtures/capybara.csv +1 -0
  282. data/spec/fixtures/certificate.pem +25 -0
  283. data/spec/fixtures/key.pem +27 -0
  284. data/spec/fixtures/selenium_driver_rspec_failure.rb +13 -0
  285. data/spec/fixtures/selenium_driver_rspec_success.rb +13 -0
  286. data/spec/minitest_spec.rb +163 -0
  287. data/spec/minitest_spec_spec.rb +162 -0
  288. data/spec/per_session_config_spec.rb +68 -0
  289. data/spec/rack_test_spec.rb +268 -0
  290. data/spec/regexp_dissassembler_spec.rb +250 -0
  291. data/spec/result_spec.rb +196 -0
  292. data/spec/rspec/features_spec.rb +99 -0
  293. data/spec/rspec/scenarios_spec.rb +19 -0
  294. data/spec/rspec/shared_spec_matchers.rb +947 -0
  295. data/spec/rspec/views_spec.rb +14 -0
  296. data/spec/rspec_matchers_spec.rb +62 -0
  297. data/spec/rspec_spec.rb +145 -0
  298. data/spec/sauce_spec_chrome.rb +43 -0
  299. data/spec/selector_spec.rb +513 -0
  300. data/spec/selenium_spec_chrome.rb +188 -0
  301. data/spec/selenium_spec_chrome_remote.rb +96 -0
  302. data/spec/selenium_spec_edge.rb +47 -0
  303. data/spec/selenium_spec_firefox.rb +208 -0
  304. data/spec/selenium_spec_firefox_remote.rb +80 -0
  305. data/spec/selenium_spec_ie.rb +150 -0
  306. data/spec/selenium_spec_safari.rb +148 -0
  307. data/spec/server_spec.rb +292 -0
  308. data/spec/session_spec.rb +91 -0
  309. data/spec/shared_selenium_node.rb +83 -0
  310. data/spec/shared_selenium_session.rb +476 -0
  311. data/spec/spec_helper.rb +100 -0
  312. data/spec/xpath_builder_spec.rb +93 -0
  313. metadata +753 -0
@@ -0,0 +1,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'