capybara 2.7.0 → 3.35.3

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