capybara 3.0.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/History.md +891 -12
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +29 -10
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +22 -4
  9. data/lib/capybara/driver/node.rb +38 -9
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +57 -8
  12. data/lib/capybara/minitest/spec.rb +185 -84
  13. data/lib/capybara/minitest.rb +264 -145
  14. data/lib/capybara/node/actions.rb +248 -124
  15. data/lib/capybara/node/base.rb +35 -20
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +13 -15
  18. data/lib/capybara/node/element.rb +350 -113
  19. data/lib/capybara/node/finders.rb +104 -82
  20. data/lib/capybara/node/matchers.rb +363 -157
  21. data/lib/capybara/node/simple.rb +54 -15
  22. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  23. data/lib/capybara/queries/active_element_query.rb +18 -0
  24. data/lib/capybara/queries/ancestor_query.rb +9 -10
  25. data/lib/capybara/queries/base_query.rb +25 -18
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +11 -0
  28. data/lib/capybara/queries/selector_query.rb +617 -104
  29. data/lib/capybara/queries/sibling_query.rb +9 -7
  30. data/lib/capybara/queries/style_query.rb +45 -0
  31. data/lib/capybara/queries/text_query.rb +40 -22
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +106 -31
  34. data/lib/capybara/rack_test/driver.rb +16 -7
  35. data/lib/capybara/rack_test/errors.rb +6 -0
  36. data/lib/capybara/rack_test/form.rb +74 -49
  37. data/lib/capybara/rack_test/node.rb +120 -47
  38. data/lib/capybara/rails.rb +1 -1
  39. data/lib/capybara/registration_container.rb +41 -0
  40. data/lib/capybara/registrations/drivers.rb +42 -0
  41. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  42. data/lib/capybara/registrations/servers.rb +66 -0
  43. data/lib/capybara/result.rb +87 -53
  44. data/lib/capybara/rspec/features.rb +8 -10
  45. data/lib/capybara/rspec/matcher_proxies.rb +39 -18
  46. data/lib/capybara/rspec/matchers/base.rb +113 -0
  47. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  48. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  49. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  50. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  51. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  52. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  53. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  54. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  55. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  56. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  57. data/lib/capybara/rspec/matchers/match_style.rb +43 -0
  58. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  59. data/lib/capybara/rspec/matchers.rb +142 -315
  60. data/lib/capybara/rspec.rb +3 -2
  61. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  62. data/lib/capybara/selector/builders/xpath_builder.rb +71 -0
  63. data/lib/capybara/selector/css.rb +85 -8
  64. data/lib/capybara/selector/definition/button.rb +68 -0
  65. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  66. data/lib/capybara/selector/definition/css.rb +10 -0
  67. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  68. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  69. data/lib/capybara/selector/definition/element.rb +28 -0
  70. data/lib/capybara/selector/definition/field.rb +40 -0
  71. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  72. data/lib/capybara/selector/definition/file_field.rb +13 -0
  73. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  74. data/lib/capybara/selector/definition/frame.rb +17 -0
  75. data/lib/capybara/selector/definition/id.rb +6 -0
  76. data/lib/capybara/selector/definition/label.rb +62 -0
  77. data/lib/capybara/selector/definition/link.rb +55 -0
  78. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  79. data/lib/capybara/selector/definition/option.rb +27 -0
  80. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  81. data/lib/capybara/selector/definition/select.rb +81 -0
  82. data/lib/capybara/selector/definition/table.rb +109 -0
  83. data/lib/capybara/selector/definition/table_row.rb +21 -0
  84. data/lib/capybara/selector/definition/xpath.rb +5 -0
  85. data/lib/capybara/selector/definition.rb +280 -0
  86. data/lib/capybara/selector/filter.rb +1 -0
  87. data/lib/capybara/selector/filter_set.rb +72 -28
  88. data/lib/capybara/selector/filters/base.rb +45 -2
  89. data/lib/capybara/selector/filters/expression_filter.rb +5 -6
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +18 -4
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +89 -200
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -534
  96. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  97. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  98. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  99. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  100. data/lib/capybara/selenium/driver.rb +270 -245
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +117 -0
  102. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  103. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +84 -0
  104. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  105. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  106. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  107. data/lib/capybara/selenium/extensions/find.rb +110 -0
  108. data/lib/capybara/selenium/extensions/html5_drag.rb +229 -0
  109. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  110. data/lib/capybara/selenium/extensions/scroll.rb +76 -0
  111. data/lib/capybara/selenium/node.rb +460 -170
  112. data/lib/capybara/selenium/nodes/chrome_node.rb +125 -0
  113. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  114. data/lib/capybara/selenium/nodes/firefox_node.rb +136 -0
  115. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  116. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  117. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  118. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  119. data/lib/capybara/selenium/patches/logs.rb +45 -0
  120. data/lib/capybara/selenium/patches/pause_duration_fix.rb +9 -0
  121. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  122. data/lib/capybara/server/animation_disabler.rb +80 -0
  123. data/lib/capybara/server/checker.rb +44 -0
  124. data/lib/capybara/server/middleware.rb +71 -0
  125. data/lib/capybara/server.rb +58 -67
  126. data/lib/capybara/session/config.rb +40 -6
  127. data/lib/capybara/session/matchers.rb +26 -19
  128. data/lib/capybara/session.rb +252 -194
  129. data/lib/capybara/spec/public/jquery.js +5 -5
  130. data/lib/capybara/spec/public/offset.js +6 -0
  131. data/lib/capybara/spec/public/test.js +126 -8
  132. data/lib/capybara/spec/session/accept_alert_spec.rb +11 -11
  133. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -3
  134. data/lib/capybara/spec/session/accept_prompt_spec.rb +9 -10
  135. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  136. data/lib/capybara/spec/session/all_spec.rb +135 -44
  137. data/lib/capybara/spec/session/ancestor_spec.rb +24 -19
  138. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +67 -38
  139. data/lib/capybara/spec/session/{assert_current_path.rb → assert_current_path_spec.rb} +20 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +143 -0
  141. data/lib/capybara/spec/session/assert_style_spec.rb +26 -0
  142. data/lib/capybara/spec/session/{assert_text.rb → assert_text_spec.rb} +76 -52
  143. data/lib/capybara/spec/session/{assert_title.rb → assert_title_spec.rb} +12 -12
  144. data/lib/capybara/spec/session/attach_file_spec.rb +126 -72
  145. data/lib/capybara/spec/session/body_spec.rb +11 -13
  146. data/lib/capybara/spec/session/check_spec.rb +112 -51
  147. data/lib/capybara/spec/session/choose_spec.rb +62 -30
  148. data/lib/capybara/spec/session/click_button_spec.rb +227 -161
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -30
  150. data/lib/capybara/spec/session/click_link_spec.rb +89 -55
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +44 -37
  153. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  154. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +2 -2
  155. data/lib/capybara/spec/session/element/{assert_match_selector.rb → assert_match_selector_spec.rb} +11 -9
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +18 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +8 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +70 -56
  159. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +7 -7
  160. data/lib/capybara/spec/session/evaluate_script_spec.rb +28 -8
  161. data/lib/capybara/spec/session/execute_script_spec.rb +8 -7
  162. data/lib/capybara/spec/session/fill_in_spec.rb +110 -44
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +8 -8
  165. data/lib/capybara/spec/session/find_field_spec.rb +33 -31
  166. data/lib/capybara/spec/session/find_link_spec.rb +42 -14
  167. data/lib/capybara/spec/session/find_spec.rb +251 -142
  168. data/lib/capybara/spec/session/first_spec.rb +45 -44
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +6 -6
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +6 -6
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +32 -20
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +46 -19
  173. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  174. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  175. data/lib/capybara/spec/session/has_all_selectors_spec.rb +23 -23
  176. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  177. data/lib/capybara/spec/session/has_any_selectors_spec.rb +29 -0
  178. data/lib/capybara/spec/session/has_button_spec.rb +98 -12
  179. data/lib/capybara/spec/session/has_css_spec.rb +271 -137
  180. data/lib/capybara/spec/session/has_current_path_spec.rb +50 -35
  181. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  182. data/lib/capybara/spec/session/has_field_spec.rb +137 -58
  183. data/lib/capybara/spec/session/has_link_spec.rb +46 -6
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +33 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +117 -69
  187. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  188. data/lib/capybara/spec/session/has_table_spec.rb +181 -4
  189. data/lib/capybara/spec/session/has_text_spec.rb +101 -53
  190. data/lib/capybara/spec/session/has_title_spec.rb +19 -14
  191. data/lib/capybara/spec/session/has_xpath_spec.rb +56 -38
  192. data/lib/capybara/spec/session/{headers.rb → headers_spec.rb} +1 -1
  193. data/lib/capybara/spec/session/html_spec.rb +13 -6
  194. data/lib/capybara/spec/session/matches_style_spec.rb +37 -0
  195. data/lib/capybara/spec/session/node_spec.rb +958 -122
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +15 -12
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +65 -37
  199. data/lib/capybara/spec/session/{response_code.rb → response_code_spec.rb} +1 -1
  200. data/lib/capybara/spec/session/save_and_open_page_spec.rb +2 -2
  201. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +5 -4
  202. data/lib/capybara/spec/session/save_page_spec.rb +41 -38
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +13 -11
  204. data/lib/capybara/spec/session/screenshot_spec.rb +2 -2
  205. data/lib/capybara/spec/session/scroll_spec.rb +119 -0
  206. data/lib/capybara/spec/session/select_spec.rb +102 -76
  207. data/lib/capybara/spec/session/selectors_spec.rb +51 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +26 -24
  210. data/lib/capybara/spec/session/title_spec.rb +8 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +41 -22
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +79 -53
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +22 -19
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +4 -3
  216. data/lib/capybara/spec/session/window/open_new_window_spec.rb +4 -3
  217. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +25 -21
  218. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +10 -5
  219. data/lib/capybara/spec/session/window/window_spec.rb +88 -54
  220. data/lib/capybara/spec/session/window/windows_spec.rb +11 -8
  221. data/lib/capybara/spec/session/window/within_window_spec.rb +17 -16
  222. data/lib/capybara/spec/session/within_spec.rb +82 -44
  223. data/lib/capybara/spec/spec_helper.rb +46 -52
  224. data/lib/capybara/spec/test_app.rb +148 -41
  225. data/lib/capybara/spec/views/animated.erb +49 -0
  226. data/lib/capybara/spec/views/form.erb +156 -42
  227. data/lib/capybara/spec/views/frame_child.erb +4 -3
  228. data/lib/capybara/spec/views/frame_one.erb +2 -1
  229. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  230. data/lib/capybara/spec/views/frame_two.erb +1 -1
  231. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  232. data/lib/capybara/spec/views/layout.erb +10 -0
  233. data/lib/capybara/spec/views/obscured.erb +47 -0
  234. data/lib/capybara/spec/views/offset.erb +33 -0
  235. data/lib/capybara/spec/views/path.erb +2 -2
  236. data/lib/capybara/spec/views/popup_one.erb +1 -1
  237. data/lib/capybara/spec/views/popup_two.erb +1 -1
  238. data/lib/capybara/spec/views/react.erb +45 -0
  239. data/lib/capybara/spec/views/scroll.erb +21 -0
  240. data/lib/capybara/spec/views/spatial.erb +31 -0
  241. data/lib/capybara/spec/views/tables.erb +68 -1
  242. data/lib/capybara/spec/views/with_animation.erb +81 -0
  243. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  244. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  245. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  246. data/lib/capybara/spec/views/with_hover.erb +3 -2
  247. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  248. data/lib/capybara/spec/views/with_html.erb +46 -11
  249. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  250. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  251. data/lib/capybara/spec/views/with_js.erb +30 -5
  252. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  253. data/lib/capybara/spec/views/with_namespace.erb +21 -0
  254. data/lib/capybara/spec/views/with_scope.erb +2 -2
  255. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  256. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  257. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  258. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  259. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  260. data/lib/capybara/spec/views/with_windows.erb +1 -1
  261. data/lib/capybara/spec/views/within_frames.erb +1 -1
  262. data/lib/capybara/version.rb +1 -1
  263. data/lib/capybara/window.rb +32 -26
  264. data/lib/capybara.rb +128 -104
  265. data/spec/basic_node_spec.rb +59 -34
  266. data/spec/capybara_spec.rb +65 -51
  267. data/spec/counter_spec.rb +35 -0
  268. data/spec/css_builder_spec.rb +101 -0
  269. data/spec/css_splitter_spec.rb +38 -0
  270. data/spec/dsl_spec.rb +84 -55
  271. data/spec/filter_set_spec.rb +24 -7
  272. data/spec/fixtures/certificate.pem +25 -0
  273. data/spec/fixtures/key.pem +27 -0
  274. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -5
  275. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -5
  276. data/spec/minitest_spec.rb +49 -5
  277. data/spec/minitest_spec_spec.rb +92 -62
  278. data/spec/per_session_config_spec.rb +6 -6
  279. data/spec/rack_test_spec.rb +183 -115
  280. data/spec/regexp_dissassembler_spec.rb +250 -0
  281. data/spec/result_spec.rb +99 -39
  282. data/spec/rspec/features_spec.rb +28 -25
  283. data/spec/rspec/scenarios_spec.rb +10 -6
  284. data/spec/rspec/shared_spec_matchers.rb +418 -364
  285. data/spec/rspec/views_spec.rb +4 -3
  286. data/spec/rspec_matchers_spec.rb +35 -10
  287. data/spec/rspec_spec.rb +109 -85
  288. data/spec/sauce_spec_chrome.rb +43 -0
  289. data/spec/selector_spec.rb +392 -62
  290. data/spec/selenium_spec_chrome.rb +183 -41
  291. data/spec/selenium_spec_chrome_remote.rb +96 -0
  292. data/spec/selenium_spec_edge.rb +41 -8
  293. data/spec/selenium_spec_firefox.rb +228 -0
  294. data/spec/selenium_spec_firefox_remote.rb +94 -0
  295. data/spec/selenium_spec_ie.rb +129 -11
  296. data/spec/selenium_spec_safari.rb +162 -0
  297. data/spec/server_spec.rb +192 -81
  298. data/spec/session_spec.rb +52 -16
  299. data/spec/shared_selenium_node.rb +79 -0
  300. data/spec/shared_selenium_session.rb +460 -123
  301. data/spec/spec_helper.rb +124 -2
  302. data/spec/whitespace_normalizer_spec.rb +54 -0
  303. data/spec/xpath_builder_spec.rb +93 -0
  304. metadata +344 -45
  305. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  306. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  307. data/.yard/yard_extensions.rb +0 -78
  308. data/lib/capybara/rspec/compound.rb +0 -90
  309. data/lib/capybara/spec/session/assert_selector.rb +0 -149
  310. data/lib/capybara/spec/session/source_spec.rb +0 -0
  311. data/lib/capybara/spec/views/with_title.erb +0 -5
  312. data/spec/selenium_spec_marionette.rb +0 -143
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/nodes/chrome_node'
4
+ require 'capybara/selenium/patches/logs'
5
+
6
+ module Capybara::Selenium::Driver::ChromeDriver
7
+ def self.extended(base)
8
+ bridge = base.send(:bridge)
9
+ bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:log)
10
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
11
+ base.options[:native_displayed] = false if base.options[:native_displayed].nil?
12
+ end
13
+
14
+ def fullscreen_window(handle)
15
+ within_given_window(handle) do
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.include?('full_screen_window')
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
22
+ end
23
+ end
24
+
25
+ def resize_window_to(handle, width, height)
26
+ super
27
+ rescue Selenium::WebDriver::Error::UnknownError => e
28
+ raise unless e.message.include?('failed to change window state')
29
+
30
+ # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
31
+ # and raises unnecessary error. Wait a bit and try again.
32
+ sleep 0.25
33
+ super
34
+ end
35
+
36
+ def reset!
37
+ # Use instance variable directly so we avoid starting the browser just to reset the session
38
+ return unless @browser
39
+
40
+ switch_to_window(window_handles.first)
41
+ window_handles.slice(1..).each { |win| close_window(win) }
42
+ return super if chromedriver_version < 73
43
+
44
+ timer = Capybara::Helpers.timer(expire_in: 10)
45
+ begin
46
+ clear_storage unless uniform_storage_clear?
47
+ @browser.navigate.to('about:blank')
48
+ wait_for_empty_page(timer)
49
+ rescue *unhandled_alert_errors
50
+ accept_unhandled_reset_alert
51
+ retry
52
+ end
53
+
54
+ execute_cdp('Storage.clearDataForOrigin', origin: '*', storageTypes: storage_types_to_clear)
55
+ end
56
+
57
+ private
58
+
59
+ def storage_types_to_clear
60
+ types = ['cookies']
61
+ types << 'local_storage' if clear_all_storage?
62
+ types.join(',')
63
+ end
64
+
65
+ def clear_all_storage?
66
+ storage_clears.none? false
67
+ end
68
+
69
+ def uniform_storage_clear?
70
+ storage_clears.uniq { |s| s == false }.length <= 1
71
+ end
72
+
73
+ def storage_clears
74
+ options.values_at(:clear_session_storage, :clear_local_storage)
75
+ end
76
+
77
+ def clear_storage
78
+ # Chrome errors if attempt to clear storage on about:blank
79
+ # In W3C mode it crashes chromedriver
80
+ url = current_url
81
+ super unless url.nil? || url.start_with?('about:')
82
+ end
83
+
84
+ def delete_all_cookies
85
+ execute_cdp('Network.clearBrowserCookies')
86
+ rescue *cdp_unsupported_errors
87
+ # If the CDP clear isn't supported do original limited clear
88
+ super
89
+ end
90
+
91
+ def cdp_unsupported_errors
92
+ @cdp_unsupported_errors ||= [Selenium::WebDriver::Error::WebDriverError]
93
+ end
94
+
95
+ def execute_cdp(cmd, params = {})
96
+ if browser.respond_to? :execute_cdp
97
+ browser.execute_cdp(cmd, **params)
98
+ else
99
+ args = { cmd: cmd, params: params }
100
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/goog/cdp/execute", args)
101
+ result['value']
102
+ end
103
+ end
104
+
105
+ def build_node(native_node, initial_cache = {})
106
+ ::Capybara::Selenium::ChromeNode.new(self, native_node, initial_cache)
107
+ end
108
+
109
+ def chromedriver_version
110
+ @chromedriver_version ||= begin
111
+ caps = browser.capabilities
112
+ caps['chrome']&.fetch('chromedriverVersion', nil).to_f
113
+ end
114
+ end
115
+ end
116
+
117
+ Capybara::Selenium::Driver.register_specialization :chrome, Capybara::Selenium::Driver::ChromeDriver
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/nodes/edge_node'
4
+
5
+ module Capybara::Selenium::Driver::EdgeDriver
6
+ def self.extended(base)
7
+ bridge = base.send(:bridge)
8
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
9
+ base.options[:native_displayed] = false if base.options[:native_displayed].nil?
10
+ end
11
+
12
+ def fullscreen_window(handle)
13
+ return super if edgedriver_version < 75
14
+
15
+ within_given_window(handle) do
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.include?('full_screen_window')
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
22
+ end
23
+ end
24
+
25
+ def resize_window_to(handle, width, height)
26
+ super
27
+ rescue Selenium::WebDriver::Error::UnknownError => e
28
+ raise unless e.message.include?('failed to change window state')
29
+
30
+ # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
31
+ # and raises unnecessary error. Wait a bit and try again.
32
+ sleep 0.25
33
+ super
34
+ end
35
+
36
+ def reset!
37
+ return super if edgedriver_version < 75
38
+ # Use instance variable directly so we avoid starting the browser just to reset the session
39
+ return unless @browser
40
+
41
+ switch_to_window(window_handles.first)
42
+ window_handles.slice(1..).each { |win| close_window(win) }
43
+
44
+ timer = Capybara::Helpers.timer(expire_in: 10)
45
+ begin
46
+ clear_storage unless uniform_storage_clear?
47
+ @browser.navigate.to('about:blank')
48
+ wait_for_empty_page(timer)
49
+ rescue *unhandled_alert_errors
50
+ accept_unhandled_reset_alert
51
+ retry
52
+ end
53
+
54
+ execute_cdp('Storage.clearDataForOrigin', origin: '*', storageTypes: storage_types_to_clear)
55
+ end
56
+
57
+ def download_path=(path)
58
+ if @browser.respond_to?(:download_path=)
59
+ @browser.download_path = path
60
+ else
61
+ # Not yet implemented in seleniun-webdriver for edge so do it ourselves
62
+ execute_cdp('Page.setDownloadBehavior', behavior: 'allow', downloadPath: path)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def storage_types_to_clear
69
+ types = ['cookies']
70
+ types << 'local_storage' if clear_all_storage?
71
+ types.join(',')
72
+ end
73
+
74
+ def clear_all_storage?
75
+ storage_clears.none? false
76
+ end
77
+
78
+ def uniform_storage_clear?
79
+ storage_clears.uniq { |s| s == false }.length <= 1
80
+ end
81
+
82
+ def storage_clears
83
+ options.values_at(:clear_session_storage, :clear_local_storage)
84
+ end
85
+
86
+ def clear_storage
87
+ # Edgedriver crashes if attempt to clear storage on about:blank
88
+ url = current_url
89
+ super unless url.nil? || url.start_with?('about:')
90
+ end
91
+
92
+ def delete_all_cookies
93
+ return super if edgedriver_version < 75
94
+
95
+ execute_cdp('Network.clearBrowserCookies')
96
+ rescue *cdp_unsupported_errors
97
+ # If the CDP clear isn't supported do original limited clear
98
+ super
99
+ end
100
+
101
+ def cdp_unsupported_errors
102
+ @cdp_unsupported_errors ||= [Selenium::WebDriver::Error::WebDriverError]
103
+ end
104
+
105
+ def execute_cdp(cmd, params = {})
106
+ if browser.respond_to? :execute_cdp
107
+ browser.execute_cdp(cmd, **params)
108
+ else
109
+ args = { cmd: cmd, params: params }
110
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/ms/cdp/execute", args)
111
+ result['value']
112
+ end
113
+ end
114
+
115
+ def build_node(native_node, initial_cache = {})
116
+ ::Capybara::Selenium::EdgeNode.new(self, native_node, initial_cache)
117
+ end
118
+
119
+ def edgedriver_version
120
+ @edgedriver_version ||= begin
121
+ caps = browser.capabilities
122
+ caps['msedge']&.fetch('msedgedriverVersion', nil).to_f
123
+ end
124
+ end
125
+ end
126
+
127
+ Capybara::Selenium::Driver.register_specialization :edge, Capybara::Selenium::Driver::EdgeDriver
128
+ Capybara::Selenium::Driver.register_specialization :edge_chrome, Capybara::Selenium::Driver::EdgeDriver
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/nodes/firefox_node'
4
+
5
+ module Capybara::Selenium::Driver::FirefoxDriver
6
+ def self.extended(driver)
7
+ driver.extend Capybara::Selenium::Driver::W3CFirefoxDriver
8
+ bridge = driver.send(:bridge)
9
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
10
+ end
11
+ end
12
+
13
+ module Capybara::Selenium::Driver::W3CFirefoxDriver
14
+ class << self
15
+ def extended(driver)
16
+ require 'capybara/selenium/patches/pause_duration_fix' if pause_broken?(driver.browser)
17
+ driver.options[:native_displayed] = false if driver.options[:native_displayed].nil?
18
+ end
19
+
20
+ def pause_broken?(sel_driver)
21
+ sel_driver.capabilities['moz:geckodriverVersion']&.start_with?('0.22.')
22
+ end
23
+ end
24
+
25
+ def resize_window_to(handle, width, height)
26
+ within_given_window(handle) do
27
+ # Don't set the size if already set - See https://github.com/mozilla/geckodriver/issues/643
28
+ if window_size(handle) == [width, height]
29
+ {}
30
+ else
31
+ super
32
+ end
33
+ end
34
+ end
35
+
36
+ def reset!
37
+ # Use instance variable directly so we avoid starting the browser just to reset the session
38
+ return unless @browser
39
+
40
+ if browser_version >= 68
41
+ begin
42
+ # Firefox 68 hangs if we try to switch windows while a modal is visible
43
+ browser.switch_to.alert&.dismiss
44
+ rescue Selenium::WebDriver::Error::NoSuchAlertError
45
+ # Swallow
46
+ end
47
+ end
48
+
49
+ switch_to_window(window_handles.first)
50
+ window_handles.slice(1..).each { |win| close_window(win) }
51
+ super
52
+ end
53
+
54
+ def refresh
55
+ # Accept any "will repost content" confirmation that occurs
56
+ accept_modal :confirm, wait: 0.1 do
57
+ super
58
+ end
59
+ rescue Capybara::ModalNotFound
60
+ # No modal was opened - page has refreshed - ignore
61
+ end
62
+
63
+ def switch_to_frame(frame)
64
+ return super unless frame == :parent
65
+
66
+ # geckodriver/firefox has an issue if the current frame is removed from within it
67
+ # so we have to move to the default_content and iterate back through the frames
68
+ handles = @frame_handles[current_window_handle]
69
+ browser.switch_to.default_content
70
+ handles.tap(&:pop).each { |fh| browser.switch_to.frame(fh.native) }
71
+ end
72
+
73
+ private
74
+
75
+ def build_node(native_node, initial_cache = {})
76
+ ::Capybara::Selenium::FirefoxNode.new(self, native_node, initial_cache)
77
+ end
78
+
79
+ def browser_version
80
+ browser.capabilities[:browser_version].to_f
81
+ end
82
+ end
83
+
84
+ Capybara::Selenium::Driver.register_specialization :firefox, Capybara::Selenium::Driver::FirefoxDriver
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/nodes/ie_node'
4
+
5
+ module Capybara::Selenium::Driver::InternetExplorerDriver
6
+ def switch_to_frame(frame)
7
+ return super unless frame == :parent
8
+
9
+ # iedriverserver has an issue if the current frame is removed from within it
10
+ # so we have to move to the default_content and iterate back through the frames
11
+ handles = @frame_handles[current_window_handle]
12
+ browser.switch_to.default_content
13
+ handles.tap(&:pop).each { |fh| browser.switch_to.frame(fh.native) }
14
+ end
15
+
16
+ private
17
+
18
+ def build_node(native_node, initial_cache = {})
19
+ ::Capybara::Selenium::IENode.new(self, native_node, initial_cache)
20
+ end
21
+ end
22
+
23
+ module Capybara::Selenium
24
+ Driver.register_specialization :ie, Driver::InternetExplorerDriver
25
+ Driver.register_specialization :internet_explorer, Driver::InternetExplorerDriver
26
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/nodes/safari_node'
4
+
5
+ module Capybara::Selenium::Driver::SafariDriver
6
+ def switch_to_frame(frame)
7
+ return super unless frame == :parent
8
+
9
+ # safaridriver/safari has an issue where switch_to_frame(:parent)
10
+ # behaves like switch_to_frame(:top)
11
+ handles = @frame_handles[current_window_handle]
12
+ browser.switch_to.default_content
13
+ handles.tap(&:pop).each { |fh| browser.switch_to.frame(fh.native) }
14
+ end
15
+
16
+ private
17
+
18
+ def build_node(native_node, initial_cache = {})
19
+ ::Capybara::Selenium::SafariNode.new(self, native_node, initial_cache)
20
+ end
21
+ end
22
+
23
+ Capybara::Selenium::Driver.register_specialization(/^(safari|Safari_Technology_Preview)$/,
24
+ Capybara::Selenium::Driver::SafariDriver)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Capybara::Selenium::Node
4
+ module FileInputClickEmulation
5
+ def click(keys = [], **options)
6
+ super
7
+ rescue Selenium::WebDriver::Error::InvalidArgumentError
8
+ return emulate_click if attaching_file? && visible_file_field?
9
+
10
+ raise
11
+ end
12
+
13
+ private
14
+
15
+ def visible_file_field?
16
+ (attrs(:tagName, :type).map { |val| val&.downcase } == %w[input file]) && visible?
17
+ end
18
+
19
+ def attaching_file?
20
+ caller_locations.any? { |cl| cl.base_label == 'attach_file' }
21
+ end
22
+
23
+ def emulate_click
24
+ driver.execute_script(<<~JS, self)
25
+ arguments[0].dispatchEvent(
26
+ new MouseEvent('click', {
27
+ view: window,
28
+ bubbles: true,
29
+ cancelable: true
30
+ }));
31
+ JS
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Selenium
5
+ module Find
6
+ def find_xpath(selector, uses_visibility: false, styles: nil, position: false, **_options)
7
+ find_by(:xpath, selector, uses_visibility: uses_visibility, texts: [], styles: styles, position: position)
8
+ end
9
+
10
+ def find_css(selector, uses_visibility: false, texts: [], styles: nil, position: false, **_options)
11
+ find_by(:css, selector, uses_visibility: uses_visibility, texts: texts, styles: styles, position: position)
12
+ end
13
+
14
+ private
15
+
16
+ def find_by(format, selector, uses_visibility:, texts:, styles:, position:)
17
+ els = find_context.find_elements(format, selector)
18
+ hints = []
19
+
20
+ if (els.size > 2) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
21
+ els = filter_by_text(els, texts) unless texts.empty?
22
+ hints = gather_hints(els, uses_visibility: uses_visibility, styles: styles, position: position)
23
+ end
24
+ els.map.with_index { |el, idx| build_node(el, hints[idx] || {}) }
25
+ end
26
+
27
+ def gather_hints(elements, uses_visibility:, styles:, position:)
28
+ hints_js, functions = build_hints_js(uses_visibility, styles, position)
29
+ return [] unless functions.any?
30
+
31
+ (es_context.execute_script(hints_js, elements) || []).map! do |results|
32
+ hint = {}
33
+ hint[:style] = results.pop if functions.include?(:style_func)
34
+ hint[:position] = results.pop if functions.include?(:position_func)
35
+ hint[:visible] = results.pop if functions.include?(:vis_func)
36
+ hint
37
+ end
38
+ rescue ::Selenium::WebDriver::Error::StaleElementReferenceError,
39
+ ::Capybara::NotSupportedByDriverError
40
+ # warn 'Unexpected Stale Element Error - skipping optimization'
41
+ []
42
+ end
43
+
44
+ def filter_by_text(elements, texts)
45
+ es_context.execute_script <<~JS, elements, texts
46
+ var texts = arguments[1];
47
+ return arguments[0].filter(function(el){
48
+ var content = el.textContent.toLowerCase();
49
+ return texts.every(function(txt){ return content.indexOf(txt.toLowerCase()) != -1 });
50
+ })
51
+ JS
52
+ end
53
+
54
+ def build_hints_js(uses_visibility, styles, position)
55
+ functions = []
56
+ hints_js = +''
57
+
58
+ if uses_visibility && !is_displayed_atom.empty?
59
+ hints_js << <<~VISIBILITY_JS
60
+ var vis_func = #{is_displayed_atom};
61
+ VISIBILITY_JS
62
+ functions << :vis_func
63
+ end
64
+
65
+ if position
66
+ hints_js << <<~POSITION_JS
67
+ var position_func = function(el){
68
+ return el.getBoundingClientRect();
69
+ };
70
+ POSITION_JS
71
+ functions << :position_func
72
+ end
73
+
74
+ if styles.is_a? Hash
75
+ hints_js << <<~STYLE_JS
76
+ var style_func = function(el){
77
+ var el_styles = window.getComputedStyle(el);
78
+ return #{styles.keys.map(&:to_s)}.reduce(function(res, style){
79
+ res[style] = el_styles[style];
80
+ return res;
81
+ }, {});
82
+ };
83
+ STYLE_JS
84
+ functions << :style_func
85
+ end
86
+
87
+ hints_js << <<~EACH_JS
88
+ return arguments[0].map(function(el){
89
+ return [#{functions.join(',')}].map(function(fn){ return fn.call(null, el) });
90
+ });
91
+ EACH_JS
92
+
93
+ [hints_js, functions]
94
+ end
95
+
96
+ def es_context
97
+ respond_to?(:execute_script) ? self : driver
98
+ end
99
+
100
+ def is_displayed_atom # rubocop:disable Naming/PredicateName
101
+ @@is_displayed_atom ||= begin # rubocop:disable Style/ClassVars
102
+ browser.send(:bridge).send(:read_atom, 'isDisplayed')
103
+ rescue StandardError
104
+ # If the atom doesn't exist or other error
105
+ ''
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end