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
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/extensions/html5_drag'
4
+ require 'capybara/selenium/extensions/file_input_click_emulation'
5
+
6
+ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
7
+ include Html5Drag
8
+ include FileInputClickEmulation
9
+
10
+ def set_text(value, clear: nil, **_unused)
11
+ super.tap do
12
+ # React doesn't see the chromedriver element clear
13
+ send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
14
+ end
15
+ end
16
+
17
+ def set_file(value) # rubocop:disable Naming/AccessorMethodName
18
+ # In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
19
+ if browser_version >= 75.0
20
+ driver.execute_script(<<~JS, self)
21
+ if (arguments[0].multiple && arguments[0].files.length){
22
+ arguments[0].value = null;
23
+ }
24
+ JS
25
+ end
26
+ super
27
+ rescue *file_errors => e
28
+ if e.message.match?(/File not found : .+\n.+/m)
29
+ raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload"
30
+ end
31
+
32
+ raise
33
+ end
34
+
35
+ def drop(*args)
36
+ html5_drop(*args)
37
+ end
38
+
39
+ def click(*, **)
40
+ super
41
+ rescue ::Selenium::WebDriver::Error::ElementClickInterceptedError
42
+ raise
43
+ rescue ::Selenium::WebDriver::Error::WebDriverError => e
44
+ # chromedriver 74 (at least on mac) raises the wrong error for this
45
+ if e.message.include?('element click intercepted')
46
+ raise ::Selenium::WebDriver::Error::ElementClickInterceptedError, e.message
47
+ end
48
+
49
+ raise
50
+ end
51
+
52
+ def disabled?
53
+ driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
54
+ end
55
+
56
+ def select_option
57
+ # To optimize to only one check and then click
58
+ selected_or_disabled = driver.evaluate_script(<<~JS, self)
59
+ arguments[0].matches(':disabled, select:disabled *, :checked')
60
+ JS
61
+ click unless selected_or_disabled
62
+ end
63
+
64
+ def visible?
65
+ return super unless native_displayed?
66
+
67
+ begin
68
+ bridge.send(:execute, :is_element_displayed, id: native.ref)
69
+ rescue Selenium::WebDriver::Error::UnknownCommandError
70
+ # If the is_element_displayed command is unknown, no point in trying again
71
+ driver.options[:native_displayed] = false
72
+ super
73
+ end
74
+ end
75
+
76
+ def send_keys(*args)
77
+ args.chunk { |inp| inp.is_a?(String) && inp.match?(/\p{Emoji Presentation}/) }
78
+ .each do |contains_emoji, inputs|
79
+ if contains_emoji
80
+ inputs.join.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
81
+ .each do |emoji, clusters|
82
+ if emoji
83
+ driver.send(:execute_cdp, 'Input.insertText', text: clusters.join)
84
+ else
85
+ super(clusters.join)
86
+ end
87
+ end
88
+ else
89
+ super(*inputs)
90
+ end
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def perform_legacy_drag(element, drop_modifiers)
97
+ return super if chromedriver_fixed_actions_key_state? || !w3c? || element.obscured?
98
+
99
+ raise ArgumentError, 'Modifier keys are not supported while dragging in this version of Chrome.' unless drop_modifiers.empty?
100
+
101
+ # W3C Chrome/chromedriver < 77 doesn't maintain mouse button state across actions API performs
102
+ # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2981
103
+ browser_action.release.perform
104
+ browser_action.click_and_hold(native).move_to(element.native).release.perform
105
+ end
106
+
107
+ def file_errors
108
+ @file_errors = ::Selenium::WebDriver.logger.suppress_deprecations do
109
+ [::Selenium::WebDriver::Error::ExpectedError]
110
+ end
111
+ end
112
+
113
+ def browser_version(to_float: true)
114
+ caps = capabilities
115
+ ver = (caps[:browser_version] || caps[:version])
116
+ ver = ver.to_f if to_float
117
+ ver
118
+ end
119
+
120
+ def chromedriver_fixed_actions_key_state?
121
+ Gem::Requirement.new('>= 76.0.3809.68').satisfied_by?(chromedriver_version)
122
+ end
123
+
124
+ def chromedriver_supports_displayed_endpoint?
125
+ Gem::Requirement.new('>= 76.0.3809.25').satisfied_by?(chromedriver_version)
126
+ end
127
+
128
+ def chromedriver_version
129
+ Gem::Version.new(capabilities['chrome']['chromedriverVersion'].split(' ')[0]) # rubocop:disable Style/RedundantArgument
130
+ end
131
+
132
+ def native_displayed?
133
+ (driver.options[:native_displayed] != false) &&
134
+ (w3c? && chromedriver_supports_displayed_endpoint?) &&
135
+ (!ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS'])
136
+ end
137
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/extensions/html5_drag'
4
+
5
+ class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
6
+ include Html5Drag
7
+
8
+ def set_text(value, clear: nil, **_unused)
9
+ return super unless chrome_edge?
10
+
11
+ super.tap do
12
+ # React doesn't see the chromedriver element clear
13
+ send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
14
+ end
15
+ end
16
+
17
+ def set_file(value) # rubocop:disable Naming/AccessorMethodName
18
+ # In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
19
+ if chrome_edge?
20
+ driver.execute_script(<<~JS, self)
21
+ if (arguments[0].multiple && arguments[0].files.length){
22
+ arguments[0].value = null;
23
+ }
24
+ JS
25
+ end
26
+ super
27
+ rescue *file_errors => e
28
+ if e.message.match?(/File not found : .+\n.+/m)
29
+ raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload"
30
+ end
31
+
32
+ raise
33
+ end
34
+
35
+ def drop(*args)
36
+ return super unless chrome_edge?
37
+
38
+ html5_drop(*args)
39
+ end
40
+
41
+ def click(*)
42
+ super
43
+ rescue Selenium::WebDriver::Error::InvalidArgumentError => e
44
+ tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
45
+ if tag_name == 'input' && type == 'file'
46
+ raise Selenium::WebDriver::Error::InvalidArgumentError, "EdgeChrome can't click on file inputs.\n#{e.message}"
47
+ end
48
+
49
+ raise
50
+ end
51
+
52
+ def disabled?
53
+ return super unless chrome_edge?
54
+
55
+ driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
56
+ end
57
+
58
+ def select_option
59
+ return super unless chrome_edge?
60
+
61
+ # To optimize to only one check and then click
62
+ selected_or_disabled = driver.evaluate_script(<<~JS, self)
63
+ arguments[0].matches(':disabled, select:disabled *, :checked')
64
+ JS
65
+ click unless selected_or_disabled
66
+ end
67
+
68
+ def visible?
69
+ return super unless chrome_edge? && native_displayed?
70
+
71
+ begin
72
+ bridge.send(:execute, :is_element_displayed, id: native.ref)
73
+ rescue Selenium::WebDriver::Error::UnknownCommandError
74
+ # If the is_element_displayed command is unknown, no point in trying again
75
+ driver.options[:native_displayed] = false
76
+ super
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def file_errors
83
+ @file_errors = ::Selenium::WebDriver.logger.suppress_deprecations do
84
+ [::Selenium::WebDriver::Error::ExpectedError]
85
+ end
86
+ end
87
+
88
+ def browser_version
89
+ @browser_version ||= begin
90
+ caps = driver.browser.capabilities
91
+ (caps[:browser_version] || caps[:version]).to_f
92
+ end
93
+ end
94
+
95
+ def chrome_edge?
96
+ browser_version >= 75
97
+ end
98
+
99
+ def native_displayed?
100
+ (driver.options[:native_displayed] != false) &&
101
+ # chromedriver_supports_displayed_endpoint? &&
102
+ (!ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS'])
103
+ end
104
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/extensions/html5_drag'
4
+ require 'capybara/selenium/extensions/file_input_click_emulation'
5
+
6
+ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
7
+ include Html5Drag
8
+ include FileInputClickEmulation
9
+
10
+ def click(keys = [], **options)
11
+ super
12
+ rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
13
+ if tag_name == 'tr'
14
+ warn 'You are attempting to click a table row which has issues in geckodriver/marionette - '\
15
+ 'see https://github.com/mozilla/geckodriver/issues/1228. Your test should probably be '\
16
+ 'clicking on a table cell like a user would. Clicking the first cell in the row instead.'
17
+ return find_css('th:first-child,td:first-child')[0].click(keys, **options)
18
+ end
19
+ raise
20
+ end
21
+
22
+ def disabled?
23
+ driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
24
+ end
25
+
26
+ def set_file(value) # rubocop:disable Naming/AccessorMethodName
27
+ # By default files are appended so we have to clear here if its multiple and already set
28
+ driver.execute_script(<<~JS, self)
29
+ if (arguments[0].multiple && arguments[0].files.length){
30
+ arguments[0].value = null;
31
+ }
32
+ JS
33
+ return super if browser_version >= 62.0
34
+
35
+ # Workaround lack of support for multiple upload by uploading one at a time
36
+ path_names = value.to_s.empty? ? [] : Array(value)
37
+ if (fd = bridge.file_detector) && !driver.browser.respond_to?(:upload)
38
+ path_names.map! { |path| upload(fd.call([path])) || path }
39
+ end
40
+ path_names.each { |path| native.send_keys(path) }
41
+ end
42
+
43
+ def focused?
44
+ driver.evaluate_script('arguments[0] == document.activeElement', self)
45
+ end
46
+
47
+ def send_keys(*args)
48
+ # https://github.com/mozilla/geckodriver/issues/846
49
+ return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?(Array)
50
+
51
+ native.click unless focused?
52
+
53
+ _send_keys(args).perform
54
+ end
55
+
56
+ def drop(*args)
57
+ html5_drop(*args)
58
+ end
59
+
60
+ def hover
61
+ return super unless browser_version >= 65.0
62
+
63
+ # work around issue 2156 - https://github.com/teamcapybara/capybara/issues/2156
64
+ scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
65
+ end
66
+
67
+ def select_option
68
+ # To optimize to only one check and then click
69
+ selected_or_disabled = driver.evaluate_script(<<~JS, self)
70
+ arguments[0].matches(':disabled, select:disabled *, :checked')
71
+ JS
72
+ click unless selected_or_disabled
73
+ end
74
+
75
+ def visible?
76
+ return super unless native_displayed?
77
+
78
+ begin
79
+ bridge.send(:execute, :is_element_displayed, id: native.ref)
80
+ rescue Selenium::WebDriver::Error::UnknownCommandError
81
+ # If the is_element_displayed command is unknown, no point in trying again
82
+ driver.options[:native_displayed] = false
83
+ super
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def native_displayed?
90
+ (driver.options[:native_displayed] != false) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
91
+ end
92
+
93
+ def perform_with_options(click_options)
94
+ # Firefox/marionette has an issue clicking with offset near viewport edge
95
+ # scroll element to middle just in case
96
+ scroll_to_center if click_options.coords?
97
+ super
98
+ end
99
+
100
+ def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
101
+ case keys
102
+ when :control, :left_control, :right_control,
103
+ :alt, :left_alt, :right_alt,
104
+ :shift, :left_shift, :right_shift,
105
+ :meta, :left_meta, :right_meta,
106
+ :command
107
+ down_keys.press(keys)
108
+ actions.key_down(keys)
109
+ when String
110
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1405370
111
+ keys = keys.upcase if (browser_version < 64.0) && down_keys&.include?(:shift)
112
+ actions.send_keys(keys)
113
+ when Symbol
114
+ actions.send_keys(keys)
115
+ when Array
116
+ down_keys.push
117
+ keys.each { |sub_keys| _send_keys(sub_keys, actions, down_keys) }
118
+ down_keys.pop.reverse_each { |key| actions.key_up(key) }
119
+ else
120
+ raise ArgumentError, 'Unknown keys type'
121
+ end
122
+ actions
123
+ end
124
+
125
+ def upload(local_file)
126
+ return nil unless local_file
127
+ raise ArgumentError, "You may only upload files: #{local_file.inspect}" unless File.file?(local_file)
128
+
129
+ file = ::Selenium::WebDriver::Zipper.zip_file(local_file)
130
+ bridge.http.call(:post, "session/#{bridge.session_id}/file", file: file)['value']
131
+ end
132
+
133
+ def browser_version
134
+ driver.browser.capabilities[:browser_version].to_f
135
+ end
136
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/selenium/extensions/html5_drag'
4
+
5
+ class Capybara::Selenium::IENode < Capybara::Selenium::Node
6
+ def disabled?
7
+ # super
8
+ # optimize to one script call
9
+ driver.evaluate_script <<~JS.delete("\n"), self
10
+ arguments[0].msMatchesSelector('
11
+ :disabled,
12
+ select:disabled *,
13
+ optgroup:disabled *,
14
+ fieldset[disabled],
15
+ fieldset[disabled] > *:not(legend),
16
+ fieldset[disabled] > *:not(legend) *,
17
+ fieldset[disabled] > legend:nth-of-type(n+2),
18
+ fieldset[disabled] > legend:nth-of-type(n+2) *
19
+ ')
20
+ JS
21
+ end
22
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'capybara/selenium/extensions/html5_drag'
4
+ require 'capybara/selenium/extensions/modifier_keys_stack'
5
+
6
+ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
7
+ # include Html5Drag
8
+
9
+ def click(keys = [], **options)
10
+ # driver.execute_script('arguments[0].scrollIntoViewIfNeeded({block: "center"})', self)
11
+ super
12
+ rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
13
+ if tag_name == 'tr'
14
+ warn 'You are attempting to click a table row which has issues in safaridriver - '\
15
+ 'Your test should probably be clicking on a table cell like a user would. '\
16
+ 'Clicking the first cell in the row instead.'
17
+ return find_css('th:first-child,td:first-child')[0].click(keys, options)
18
+ end
19
+ raise
20
+ rescue ::Selenium::WebDriver::Error::WebDriverError => e
21
+ raise unless e.instance_of? ::Selenium::WebDriver::Error::WebDriverError
22
+
23
+ # Safari doesn't return a specific error here - assume it's an ElementNotInteractableError
24
+ raise ::Selenium::WebDriver::Error::ElementNotInteractableError,
25
+ 'Non distinct error raised in #click, translated to ElementNotInteractableError for retry'
26
+ end
27
+
28
+ def select_option
29
+ # To optimize to only one check and then click
30
+ selected_or_disabled = driver.execute_script(<<~JS, self)
31
+ arguments[0].closest('select').scrollIntoView();
32
+ return arguments[0].matches(':disabled, select:disabled *, :checked');
33
+ JS
34
+ click unless selected_or_disabled
35
+ end
36
+
37
+ def unselect_option
38
+ driver.execute_script("arguments[0].closest('select').scrollIntoView()", self)
39
+ super
40
+ end
41
+
42
+ def visible_text
43
+ return '' unless visible?
44
+
45
+ vis_text = driver.execute_script('return arguments[0].innerText', self)
46
+ vis_text.squeeze(' ')
47
+ .gsub(/[\ \n]*\n[\ \n]*/, "\n")
48
+ .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
49
+ .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
50
+ .tr("\u00a0", ' ')
51
+ end
52
+
53
+ def disabled?
54
+ driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
55
+ end
56
+
57
+ def set_file(value) # rubocop:disable Naming/AccessorMethodName
58
+ # By default files are appended so we have to clear here if its multiple and already set
59
+ native.clear if multiple? && driver.evaluate_script('arguments[0].files', self).any?
60
+ super
61
+ end
62
+
63
+ def send_keys(*args)
64
+ if args.none? { |arg| arg.is_a?(Array) || (arg.is_a?(Symbol) && MODIFIER_KEYS.include?(arg)) }
65
+ return super(*args.map { |arg| arg == :space ? ' ' : arg })
66
+ end
67
+
68
+ native.click
69
+ _send_keys(args).perform
70
+ end
71
+
72
+ def set_text(value, clear: nil, **_unused)
73
+ value = value.to_s
74
+ if clear == :backspace
75
+ # Clear field by sending the correct number of backspace keys.
76
+ backspaces = [:backspace] * self.value.to_s.length
77
+ send_keys(*([[:control, 'e']] + backspaces + [value]))
78
+ else
79
+ super.tap do
80
+ # React doesn't see the safaridriver element clear
81
+ send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
82
+ end
83
+ end
84
+ end
85
+
86
+ def hover
87
+ # Workaround issue where hover would sometimes fail - possibly due to mouse not having moved
88
+ scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
89
+ end
90
+
91
+ private
92
+
93
+ def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
94
+ case keys
95
+ when *MODIFIER_KEYS
96
+ down_keys.press(keys)
97
+ actions.key_down(keys)
98
+ when String
99
+ keys = keys.upcase if down_keys&.include?(:shift)
100
+ actions.send_keys(keys)
101
+ when Symbol
102
+ actions.send_keys(keys)
103
+ when Array
104
+ down_keys.push
105
+ keys.each { |sub_keys| _send_keys(sub_keys, actions, down_keys) }
106
+ down_keys.pop.reverse_each { |key| actions.key_up(key) }
107
+ else
108
+ raise ArgumentError, 'Unknown keys type'
109
+ end
110
+ actions
111
+ end
112
+
113
+ MODIFIER_KEYS = %i[control left_control right_control
114
+ alt left_alt right_alt
115
+ shift left_shift right_shift
116
+ meta left_meta right_meta
117
+ command].freeze
118
+ end