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
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Capybara::SpecHelper.spec "node" do
3
+ # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
4
+ # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
5
+ # using Capybara provided assertions with builtin waiting behavior.
6
+
7
+ Capybara::SpecHelper.spec 'node' do
4
8
  before do
5
9
  @session.visit('/with_html')
6
10
  end
7
11
 
8
- it "should act like a session object" do
12
+ it 'should act like a session object' do
9
13
  @session.visit('/form')
10
14
  @form = @session.find(:css, '#get-form')
11
15
  expect(@form).to have_field('Middle Name')
@@ -15,59 +19,70 @@ Capybara::SpecHelper.spec "node" do
15
19
  expect(extract_results(@session)['middle_name']).to eq('Monkey')
16
20
  end
17
21
 
18
- it "should scope CSS selectors" do
22
+ it 'should scope CSS selectors' do
19
23
  expect(@session.find(:css, '#second')).to have_no_css('h1')
20
24
  end
21
25
 
22
- describe "#query_scope" do
23
- it "should have a reference to the element the query was evaluated on if there is one" do
24
- @node = @session.find(:css, '#first')
25
- expect(@node.query_scope).to eq(@node.session.document)
26
- expect(@node.find(:css, '#foo').query_scope).to eq(@node)
26
+ describe '#query_scope' do
27
+ it 'should have a reference to the element the query was evaluated on if there is one' do
28
+ node = @session.find(:css, '#first')
29
+ expect(node.query_scope).to eq(node.session.document)
30
+ expect(node.find(:css, '#foo').query_scope).to eq(node)
27
31
  end
28
32
  end
29
33
 
30
- describe "#text" do
31
- it "should extract node texts" do
34
+ describe '#text' do
35
+ it 'should extract node texts' do
32
36
  expect(@session.all('//a')[0].text).to eq('labore')
33
37
  expect(@session.all('//a')[1].text).to eq('ullamco')
34
38
  end
35
39
 
36
- it "should return document text on /html selector" do
40
+ it 'should return document text on /html selector' do
37
41
  @session.visit('/with_simple_html')
38
42
  expect(@session.all('/html')[0].text).to eq('Bar')
39
43
  end
40
44
  end
41
45
 
42
- describe "#[]" do
43
- it "should extract node attributes" do
46
+ describe '#[]' do
47
+ it 'should extract node attributes' do
44
48
  expect(@session.all('//a')[0][:class]).to eq('simple')
45
49
  expect(@session.all('//a')[1][:id]).to eq('foo')
46
50
  expect(@session.all('//input')[0][:type]).to eq('text')
47
51
  end
48
52
 
49
- it "should extract boolean node attributes" do
53
+ it 'should extract boolean node attributes' do
50
54
  expect(@session.find('//input[@id="checked_field"]')[:checked]).to be_truthy
51
55
  end
52
56
  end
53
57
 
54
- describe "#value" do
55
- it "should allow retrieval of the value" do
58
+ describe '#style', requires: [:css] do
59
+ it 'should return the computed style value' do
60
+ expect(@session.find(:css, '#first').style('display')).to eq('display' => 'block')
61
+ expect(@session.find(:css, '#second').style(:display)).to eq('display' => 'inline')
62
+ end
63
+
64
+ it 'should return multiple style values' do
65
+ expect(@session.find(:css, '#first').style('display', :'line-height')).to eq('display' => 'block', 'line-height' => '25px')
66
+ end
67
+ end
68
+
69
+ describe '#value' do
70
+ it 'should allow retrieval of the value' do
56
71
  expect(@session.find('//textarea[@id="normal"]').value).to eq('banana')
57
72
  end
58
73
 
59
- it "should not swallow extra newlines in textarea" do
74
+ it 'should not swallow extra newlines in textarea' do
60
75
  expect(@session.find('//textarea[@id="additional_newline"]').value).to eq("\nbanana")
61
76
  end
62
77
 
63
- it "should not swallow leading newlines for set content in textarea" do
78
+ it 'should not swallow leading newlines for set content in textarea' do
64
79
  @session.find('//textarea[@id="normal"]').set("\nbanana")
65
80
  expect(@session.find('//textarea[@id="normal"]').value).to eq("\nbanana")
66
81
  end
67
82
 
68
- it "return any HTML content in textarea" do
69
- @session.find('//textarea[1]').set("some <em>html</em> here")
70
- expect(@session.find('//textarea[1]').value).to eq("some <em>html</em> here")
83
+ it 'return any HTML content in textarea' do
84
+ @session.find('//textarea[1]').set('some <em>html</em> here')
85
+ expect(@session.find('//textarea[1]').value).to eq('some <em>html</em> here')
71
86
  end
72
87
 
73
88
  it "defaults to 'on' for checkbox" do
@@ -81,28 +96,48 @@ Capybara::SpecHelper.spec "node" do
81
96
  end
82
97
  end
83
98
 
84
- describe "#set" do
85
- it "should allow assignment of field value" do
99
+ describe '#set' do
100
+ it 'should allow assignment of field value' do
86
101
  expect(@session.first('//input').value).to eq('monkey')
87
102
  @session.first('//input').set('gorilla')
88
103
  expect(@session.first('//input').value).to eq('gorilla')
89
104
  end
90
105
 
91
- it "should fill the field even if the caret was not at the end", requires: [:js] do
106
+ it 'should fill the field even if the caret was not at the end', requires: [:js] do
92
107
  @session.execute_script("var el = document.getElementById('test_field'); el.focus(); el.setSelectionRange(0, 0);")
93
108
  @session.first('//input').set('')
94
109
  expect(@session.first('//input').value).to eq('')
95
110
  end
96
111
 
97
- it "should raise if the text field is readonly" do
98
- expect { @session.first('//input[@readonly]').set('changed') }.to raise_error(Capybara::ReadOnlyElementError)
112
+ if ENV['CAPYBARA_THOROUGH']
113
+ it 'should raise if the text field is readonly' do
114
+ expect { @session.first('//input[@readonly]').set('changed') }.to raise_error(Capybara::ReadOnlyElementError)
115
+ end
116
+
117
+ it 'should raise if the textarea is readonly' do
118
+ expect { @session.first('//textarea[@readonly]').set('changed') }.to raise_error(Capybara::ReadOnlyElementError)
119
+ end
120
+ else
121
+ it 'should not change if the text field is readonly' do
122
+ @session.first('//input[@readonly]').set('changed')
123
+ expect(@session.first('//input[@readonly]').value).to eq 'should not change'
124
+ end
125
+
126
+ it 'should not change if the textarea is readonly' do
127
+ @session.first('//textarea[@readonly]').set('changed')
128
+ expect(@session.first('//textarea[@readonly]').value).to eq 'textarea should not change'
129
+ end
99
130
  end
100
131
 
101
- it "should raise if the text field is readonly" do
102
- expect { @session.first('//textarea[@readonly]').set('changed') }.to raise_error(Capybara::ReadOnlyElementError)
132
+ it 'should use global default options' do
133
+ Capybara.default_set_options = { clear: :backspace }
134
+ element = @session.first(:fillable_field, type: 'text')
135
+ allow(element.base).to receive(:set)
136
+ element.set('gorilla')
137
+ expect(element.base).to have_received(:set).with('gorilla', clear: :backspace)
103
138
  end
104
139
 
105
- context "with a contenteditable element", requires: [:js] do
140
+ context 'with a contenteditable element', requires: [:js] do
106
141
  it 'should allow me to change the contents' do
107
142
  @session.visit('/with_js')
108
143
  @session.find(:css, '#existing_content_editable').set('WYSIWYG')
@@ -122,72 +157,97 @@ Capybara::SpecHelper.spec "node" do
122
157
  expect(@session.find(:css, '#existing_content_editable_child_parent').text).to eq("Some content\nWYSIWYG")
123
158
  end
124
159
  end
160
+
161
+ it 'should submit single text input forms if ended with \n' do
162
+ @session.visit('/form')
163
+ @session.find(:css, '#single_input').set("my entry\n")
164
+ expect(extract_results(@session)['single_input']).to eq('my entry')
165
+ end
166
+
167
+ it 'should not submit single text input forms if ended with \n and has multiple values' do
168
+ @session.visit('/form')
169
+ @session.find(:css, '#two_input_1').set("my entry\n")
170
+ expect(@session.find(:css, '#two_input_1').value).to eq("my entry\n").or(eq 'my entry')
171
+ end
125
172
  end
126
173
 
127
- describe "#tag_name" do
128
- it "should extract node tag name" do
174
+ describe '#tag_name' do
175
+ it 'should extract node tag name' do
129
176
  expect(@session.all('//a')[0].tag_name).to eq('a')
130
177
  expect(@session.all('//a')[1].tag_name).to eq('a')
131
178
  expect(@session.all('//p')[1].tag_name).to eq('p')
132
179
  end
133
180
  end
134
181
 
135
- describe "#disabled?" do
136
- it "should extract disabled node" do
182
+ describe '#disabled?' do
183
+ it 'should extract disabled node' do
137
184
  @session.visit('/form')
138
185
  expect(@session.find('//input[@id="customer_name"]')).to be_disabled
139
186
  expect(@session.find('//input[@id="customer_email"]')).not_to be_disabled
140
187
  end
141
188
 
142
- it "should see disabled options as disabled" do
189
+ it 'should see disabled options as disabled' do
143
190
  @session.visit('/form')
144
191
  expect(@session.find('//select[@id="form_title"]/option[1]')).not_to be_disabled
145
192
  expect(@session.find('//select[@id="form_title"]/option[@disabled]')).to be_disabled
146
193
  end
147
194
 
148
- it "should see enabled options in disabled select as disabled" do
195
+ it 'should see enabled options in disabled select as disabled' do
149
196
  @session.visit('/form')
150
197
  expect(@session.find('//select[@id="form_disabled_select"]/option')).to be_disabled
151
198
  expect(@session.find('//select[@id="form_disabled_select"]/optgroup/option')).to be_disabled
152
199
  expect(@session.find('//select[@id="form_title"]/option[1]')).not_to be_disabled
153
200
  end
154
201
 
155
- it "should see enabled options in disabled optgroup as disabled" do
202
+ it 'should see enabled options in disabled optgroup as disabled' do
156
203
  @session.visit('/form')
157
- expect(@session.find('//option', text: "A.B.1")).to be_disabled
158
- expect(@session.find('//option', text: "A.2")).not_to be_disabled
204
+ expect(@session.find('//option', text: 'A.B.1')).to be_disabled
205
+ expect(@session.find('//option', text: 'A.2')).not_to be_disabled
159
206
  end
160
207
 
161
- context "in a disabled fieldset" do
208
+ it 'should see a disabled fieldset as disabled' do
209
+ @session.visit('/form')
210
+ expect(@session.find(:xpath, './/fieldset[@id="form_disabled_fieldset"]')).to be_disabled
211
+ end
212
+
213
+ context 'in a disabled fieldset' do
162
214
  # https://html.spec.whatwg.org/#the-fieldset-element
163
- it "should see elements not in first legend as disabled" do
215
+ it 'should see elements not in first legend as disabled' do
164
216
  @session.visit('/form')
165
217
  expect(@session.find('//input[@id="form_disabled_fieldset_child"]')).to be_disabled
166
218
  expect(@session.find('//input[@id="form_disabled_fieldset_second_legend_child"]')).to be_disabled
167
219
  expect(@session.find('//input[@id="form_enabled_fieldset_child"]')).not_to be_disabled
168
220
  end
169
221
 
170
- it "should see elements in first legend as enabled" do
222
+ it 'should see elements in first legend as enabled' do
171
223
  @session.visit('/form')
172
224
  expect(@session.find('//input[@id="form_disabled_fieldset_legend_child"]')).not_to be_disabled
173
225
  end
174
226
 
175
- it "should sees options not in first legend as disabled" do
227
+ it 'should sees options not in first legend as disabled' do
176
228
  @session.visit('/form')
177
229
  expect(@session.find('//option', text: 'Disabled Child Option')).to be_disabled
178
230
  end
179
231
  end
180
- it "should be boolean" do
232
+
233
+ it 'should be boolean' do
181
234
  @session.visit('/form')
182
235
  expect(@session.find('//select[@id="form_disabled_select"]/option').disabled?).to be true
183
236
  expect(@session.find('//select[@id="form_disabled_select2"]/option').disabled?).to be true
184
237
  expect(@session.find('//select[@id="form_title"]/option[1]').disabled?).to be false
185
238
  end
239
+
240
+ it 'should be disabled for all elements that are CSS :disabled' do
241
+ @session.visit('/form')
242
+ # sanity check
243
+ expect(@session.all(:css, ':disabled')).to all(be_disabled)
244
+ end
186
245
  end
187
246
 
188
- describe "#visible?" do
189
- it "should extract node visibility" do
190
- Capybara.ignore_hidden_elements = false
247
+ describe '#visible?' do
248
+ before { Capybara.ignore_hidden_elements = false }
249
+
250
+ it 'should extract node visibility' do
191
251
  expect(@session.first('//a')).to be_visible
192
252
 
193
253
  expect(@session.find('//div[@id="hidden"]')).not_to be_visible
@@ -197,22 +257,124 @@ Capybara::SpecHelper.spec "node" do
197
257
  expect(@session.find('//input[@id="hidden_input"]')).not_to be_visible
198
258
  end
199
259
 
200
- it "should be boolean" do
201
- Capybara.ignore_hidden_elements = false
260
+ it 'template elements should not be visible' do
261
+ expect(@session.find('//template')).not_to be_visible
262
+ end
263
+
264
+ it 'should be boolean' do
202
265
  expect(@session.first('//a').visible?).to be true
203
266
  expect(@session.find('//div[@id="hidden"]').visible?).to be false
204
267
  end
268
+
269
+ it 'closed details > summary elements and descendants should be visible' do
270
+ expect(@session.find(:css, '#closed_details summary')).to be_visible
271
+ expect(@session.find(:css, '#closed_details summary h6')).to be_visible
272
+ end
273
+
274
+ it 'details non-summary descendants should be non-visible when closed' do
275
+ descendants = @session.all(:css, '#closed_details > *:not(summary), #closed_details > *:not(summary) *', minimum: 2)
276
+ expect(descendants).not_to include(be_visible)
277
+ end
278
+
279
+ it 'deatils descendants should be visible when open' do
280
+ descendants = @session.all(:css, '#open_details *')
281
+ expect(descendants).to all(be_visible)
282
+ end
283
+
284
+ it 'works when details is toggled open and closed' do
285
+ @session.find(:css, '#closed_details > summary').click
286
+ expect(@session).to have_css('#closed_details *', visible: :visible, count: 5)
287
+ .and(have_no_css('#closed_details *', visible: :hidden))
288
+
289
+ @session.find(:css, '#closed_details > summary').click
290
+ descendants_css = '#closed_details > *:not(summary), #closed_details > *:not(summary) *'
291
+ expect(@session).to have_no_css(descendants_css, visible: :visible)
292
+ .and(have_css(descendants_css, visible: :hidden, count: 3))
293
+ end
294
+ end
295
+
296
+ describe '#obscured?', requires: [:css] do
297
+ it 'should see non visible elements as obscured' do
298
+ Capybara.ignore_hidden_elements = false
299
+ expect(@session.find('//div[@id="hidden"]')).to be_obscured
300
+ expect(@session.find('//div[@id="hidden_via_ancestor"]')).to be_obscured
301
+ expect(@session.find('//div[@id="hidden_attr"]')).to be_obscured
302
+ expect(@session.find('//a[@id="hidden_attr_via_ancestor"]')).to be_obscured
303
+ expect(@session.find('//input[@id="hidden_input"]')).to be_obscured
304
+ end
305
+
306
+ it 'should see non-overlapped elements as not obscured' do
307
+ @session.visit('/obscured')
308
+ expect(@session.find(:css, '#cover')).not_to be_obscured
309
+ end
310
+
311
+ it 'should see elements only overlapped by descendants as not obscured' do
312
+ expect(@session.first(:css, 'p:not(.para)')).not_to be_obscured
313
+ end
314
+
315
+ it 'should see elements outside the viewport as obscured' do
316
+ @session.visit('/obscured')
317
+ off = @session.find(:css, '#offscreen')
318
+ off_wrapper = @session.find(:css, '#offscreen_wrapper')
319
+ expect(off).to be_obscured
320
+ expect(off_wrapper).to be_obscured
321
+ @session.scroll_to(off_wrapper)
322
+ expect(off_wrapper).not_to be_obscured
323
+ expect(off).to be_obscured
324
+ off_wrapper.scroll_to(off)
325
+ expect(off).not_to be_obscured
326
+ expect(off_wrapper).not_to be_obscured
327
+ end
328
+
329
+ it 'should see overlapped elements as obscured' do
330
+ @session.visit('/obscured')
331
+ expect(@session.find(:css, '#obscured')).to be_obscured
332
+ end
333
+
334
+ it 'should be boolean' do
335
+ Capybara.ignore_hidden_elements = false
336
+ expect(@session.first('//a').obscured?).to be false
337
+ expect(@session.find('//div[@id="hidden"]').obscured?).to be true
338
+ end
339
+
340
+ it 'should work in frames' do
341
+ @session.visit('/obscured')
342
+ frame = @session.find(:css, '#frameOne')
343
+ @session.within_frame(frame) do
344
+ div = @session.find(:css, '#divInFrameOne')
345
+ expect(div).to be_obscured
346
+ @session.scroll_to div
347
+ expect(div).not_to be_obscured
348
+ end
349
+ end
350
+
351
+ it 'should work in nested iframes' do
352
+ @session.visit('/obscured')
353
+ frame = @session.find(:css, '#nestedFrames')
354
+ @session.within_frame(frame) do
355
+ @session.within_frame(:css, '#childFrame') do
356
+ gcframe = @session.find(:css, '#grandchildFrame2')
357
+ @session.within_frame(gcframe) do
358
+ expect(@session.find(:css, '#divInFrameTwo')).to be_obscured
359
+ end
360
+ @session.scroll_to(gcframe)
361
+ @session.within_frame(gcframe) do
362
+ expect(@session.find(:css, '#divInFrameTwo')).not_to be_obscured
363
+ end
364
+ end
365
+ end
366
+ end
205
367
  end
206
368
 
207
- describe "#checked?" do
208
- it "should extract node checked state" do
369
+ describe '#checked?' do
370
+ it 'should extract node checked state' do
209
371
  @session.visit('/form')
210
372
  expect(@session.find('//input[@id="gender_female"]')).to be_checked
211
373
  expect(@session.find('//input[@id="gender_male"]')).not_to be_checked
212
374
  expect(@session.first('//h1')).not_to be_checked
213
375
  end
214
376
 
215
- it "should be boolean" do
377
+ it 'should be boolean' do
216
378
  @session.visit('/form')
217
379
  expect(@session.find('//input[@id="gender_female"]').checked?).to be true
218
380
  expect(@session.find('//input[@id="gender_male"]').checked?).to be false
@@ -220,15 +382,15 @@ Capybara::SpecHelper.spec "node" do
220
382
  end
221
383
  end
222
384
 
223
- describe "#selected?" do
224
- it "should extract node selected state" do
385
+ describe '#selected?' do
386
+ it 'should extract node selected state' do
225
387
  @session.visit('/form')
226
388
  expect(@session.find('//option[@value="en"]')).to be_selected
227
389
  expect(@session.find('//option[@value="sv"]')).not_to be_selected
228
390
  expect(@session.first('//h1')).not_to be_selected
229
391
  end
230
392
 
231
- it "should be boolean" do
393
+ it 'should be boolean' do
232
394
  @session.visit('/form')
233
395
  expect(@session.find('//option[@value="en"]').selected?).to be true
234
396
  expect(@session.find('//option[@value="sv"]').selected?).to be false
@@ -236,33 +398,44 @@ Capybara::SpecHelper.spec "node" do
236
398
  end
237
399
  end
238
400
 
239
- describe "#==" do
240
- it "preserve object identity" do
241
- expect(@session.find('//h1') == @session.find('//h1')).to be true # rubocop:disable Lint/UselessComparison
242
- expect(@session.find('//h1') === @session.find('//h1')).to be true # rubocop:disable Style/CaseEquality, Lint/UselessComparison
401
+ describe '#==' do
402
+ it 'preserve object identity' do
403
+ expect(@session.find('//h1') == @session.find('//h1')).to be true # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
404
+ expect(@session.find('//h1') === @session.find('//h1')).to be true # rubocop:disable Style/CaseEquality, Lint/BinaryOperatorWithIdenticalOperands
243
405
  expect(@session.find('//h1').eql?(@session.find('//h1'))).to be false
244
406
  end
245
407
 
246
- it "returns false for unrelated object" do
247
- expect(@session.find('//h1') == "Not Capybara::Node::Base").to be false
408
+ it 'returns false for unrelated object' do
409
+ expect(@session.find('//h1') == 'Not Capybara::Node::Base').to be false
248
410
  end
249
411
  end
250
412
 
251
- describe "#path" do
413
+ describe '#path' do
252
414
  # Testing for specific XPaths here doesn't make sense since there
253
415
  # are many that can refer to the same element
254
- before :each do
416
+ before do
255
417
  @session.visit('/path')
256
418
  end
257
419
 
258
- it "returns xpath which points to itself" do
420
+ it 'returns xpath which points to itself' do
259
421
  element = @session.find(:link, 'Second Link')
260
422
  expect(@session.find(:xpath, element.path)).to eq(element)
261
423
  end
424
+
425
+ it 'reports when element in shadow dom', requires: [:shadow_dom] do
426
+ @session.visit('/with_js')
427
+ shadow = @session.find(:css, '#shadow')
428
+ element = @session.evaluate_script(<<~JS, shadow)
429
+ (function(root){
430
+ return root.shadowRoot.querySelector('span');
431
+ })(arguments[0])
432
+ JS
433
+ expect(element.path).to eq '(: Shadow DOM element - no XPath :)'
434
+ end
262
435
  end
263
436
 
264
- describe "#trigger", requires: %i[js trigger] do
265
- it "should allow triggering of custom JS events" do
437
+ describe '#trigger', requires: %i[js trigger] do
438
+ it 'should allow triggering of custom JS events' do
266
439
  @session.visit('/with_js')
267
440
  @session.find(:css, '#with_focus_event').trigger(:focus)
268
441
  expect(@session).to have_css('#focus_event_triggered')
@@ -270,52 +443,323 @@ Capybara::SpecHelper.spec "node" do
270
443
  end
271
444
 
272
445
  describe '#drag_to', requires: %i[js drag] do
273
- it "should drag and drop an object" do
446
+ it 'should drag and drop an object' do
274
447
  @session.visit('/with_js')
275
448
  element = @session.find('//div[@id="drag"]')
276
449
  target = @session.find('//div[@id="drop"]')
277
450
  element.drag_to(target)
278
- expect(@session.find('//div[contains(., "Dropped!")]')).not_to be_nil
451
+ expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
279
452
  end
280
453
 
281
- it "should drag and drop if scrolling is needed" do
454
+ it 'should drag and drop if scrolling is needed' do
282
455
  @session.visit('/with_js')
283
456
  element = @session.find('//div[@id="drag_scroll"]')
284
457
  target = @session.find('//div[@id="drop_scroll"]')
285
458
  element.drag_to(target)
286
- expect(@session.find('//div[contains(., "Dropped!")]')).not_to be_nil
459
+ expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
460
+ end
461
+
462
+ it 'should drag a link' do
463
+ @session.visit('/with_js')
464
+ link = @session.find_link('drag_link')
465
+ target = @session.find(:id, 'drop')
466
+ link.drag_to target
467
+ expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
468
+ end
469
+
470
+ it 'should work with Dragula' do
471
+ @session.visit('/with_dragula')
472
+ @session.within(:css, '#sortable.ready') do
473
+ src = @session.find('div', text: 'Item 1')
474
+ target = @session.find('div', text: 'Item 3')
475
+ src.drag_to target
476
+ expect(@session).to have_content(/Item 2.*Item 1/, normalize_ws: true)
477
+ end
478
+ end
479
+
480
+ it 'should work with jsTree' do
481
+ @session.visit('/with_jstree')
482
+ @session.within(:css, '#container') do
483
+ @session.assert_text(/A.*B.*C/m)
484
+ source = @session.find(:css, '#j1_1_anchor')
485
+ target = @session.find(:css, '#j1_2_anchor')
486
+
487
+ source.drag_to(target)
488
+
489
+ @session.assert_no_text(/A.*B.*C/m)
490
+ @session.assert_text(/B.*C/m)
491
+ end
492
+ end
493
+
494
+ it 'should simulate a single held down modifier key' do
495
+ %I[
496
+ alt
497
+ ctrl
498
+ meta
499
+ shift
500
+ ].each do |modifier_key|
501
+ @session.visit('/with_js')
502
+
503
+ element = @session.find('//div[@id="drag"]')
504
+ target = @session.find('//div[@id="drop"]')
505
+
506
+ element.drag_to(target, drop_modifiers: modifier_key)
507
+ expect(@session).to have_css('div.drag_start', exact_text: 'Dragged!')
508
+ expect(@session).to have_xpath("//div[contains(., 'Dropped!-#{modifier_key}')]")
509
+ end
510
+ end
511
+
512
+ it 'should simulate multiple held down modifier keys' do
513
+ @session.visit('/with_js')
514
+
515
+ element = @session.find('//div[@id="drag"]')
516
+ target = @session.find('//div[@id="drop"]')
517
+
518
+ modifier_keys = %I[alt ctrl meta shift]
519
+
520
+ element.drag_to(target, drop_modifiers: modifier_keys)
521
+ expect(@session).to have_xpath("//div[contains(., 'Dropped!-#{modifier_keys.join('-')}')]")
522
+ end
523
+
524
+ it 'should support key aliases' do
525
+ { control: :ctrl,
526
+ command: :meta,
527
+ cmd: :meta }.each do |(key_alias, key)|
528
+ @session.visit('/with_js')
529
+
530
+ element = @session.find('//div[@id="drag"]')
531
+ target = @session.find('//div[@id="drop"]')
532
+
533
+ element.drag_to(target, drop_modifiers: [key_alias])
534
+ expect(target).to have_text("Dropped!-#{key}", exact: true)
535
+ end
536
+ end
537
+
538
+ context 'HTML5', requires: %i[js html5_drag] do
539
+ it 'should HTML5 drag and drop an object' do
540
+ @session.visit('/with_js')
541
+ element = @session.find('//div[@id="drag_html5"]')
542
+ target = @session.find('//div[@id="drop_html5"]')
543
+ element.drag_to(target)
544
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5")]')
545
+ end
546
+
547
+ it 'should HTML5 drag and drop an object child' do
548
+ @session.visit('/with_js')
549
+ element = @session.find('//div[@id="drag_html5"]/p')
550
+ target = @session.find('//div[@id="drop_html5"]')
551
+ element.drag_to(target)
552
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5")]')
553
+ end
554
+
555
+ it 'should set clientX/Y in dragover events' do
556
+ @session.visit('/with_js')
557
+ element = @session.find('//div[@id="drag_html5"]')
558
+ target = @session.find('//div[@id="drop_html5"]')
559
+ element.drag_to(target)
560
+ expect(@session).to have_css('div.log', text: /DragOver with client position: [1-9]\d*,[1-9]\d*/, count: 2)
561
+ end
562
+
563
+ it 'should preserve clientX/Y from last dragover event' do
564
+ @session.visit('/with_js')
565
+ element = @session.find('//div[@id="drag_html5"]')
566
+ target = @session.find('//div[@id="drop_html5"]')
567
+ element.drag_to(target)
568
+
569
+ conditions = %w[DragLeave Drop DragEnd].map do |text|
570
+ have_css('div.log', text: text)
571
+ end
572
+ expect(@session).to(conditions.reduce { |memo, cond| memo.and(cond) })
573
+
574
+ # The first "DragOver" div is inserted by the last dragover event dispatched
575
+ drag_over_div = @session.first('//div[@class="log" and starts-with(text(), "DragOver")]')
576
+ position = drag_over_div.text.sub('DragOver ', '')
577
+
578
+ expect(@session).to have_css('div.log', text: /DragLeave #{position}/, count: 1)
579
+ expect(@session).to have_css('div.log', text: /Drop #{position}/, count: 1)
580
+ expect(@session).to have_css('div.log', text: /DragEnd #{position}/, count: 1)
581
+ end
582
+
583
+ it 'should not HTML5 drag and drop on a non HTML5 drop element' do
584
+ @session.visit('/with_js')
585
+ element = @session.find('//div[@id="drag_html5"]')
586
+ target = @session.find('//div[@id="drop_html5"]')
587
+ target.execute_script("$(this).removeClass('drop');")
588
+ element.drag_to(target)
589
+ sleep 1
590
+ expect(@session).not_to have_xpath('//div[contains(., "HTML5 Dropped")]')
591
+ end
592
+
593
+ it 'should HTML5 drag and drop when scrolling needed' do
594
+ @session.visit('/with_js')
595
+ element = @session.find('//div[@id="drag_html5_scroll"]')
596
+ target = @session.find('//div[@id="drop_html5_scroll"]')
597
+ element.drag_to(target)
598
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5_scroll")]')
599
+ end
600
+
601
+ it 'should drag HTML5 default draggable elements' do
602
+ @session.visit('/with_js')
603
+ link = @session.find_link('drag_link_html5')
604
+ target = @session.find(:id, 'drop_html5')
605
+ link.drag_to target
606
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
607
+ end
608
+
609
+ it 'should work with SortableJS' do
610
+ @session.visit('/with_sortable_js')
611
+ @session.within(:css, '#sortable') do
612
+ src = @session.find('div', text: 'Item 1')
613
+ target = @session.find('div', text: 'Item 3')
614
+ src.drag_to target
615
+ expect(@session).to have_content(/Item 3.*Item 1/, normalize_ws: true)
616
+ end
617
+ end
618
+
619
+ it 'should drag HTML5 default draggable element child' do
620
+ @session.visit('/with_js')
621
+ source = @session.find_link('drag_link_html5').find(:css, 'p')
622
+ target = @session.find(:id, 'drop_html5')
623
+ source.drag_to target
624
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
625
+ end
626
+
627
+ it 'should simulate a single held down modifier key' do
628
+ %I[alt ctrl meta shift].each do |modifier_key|
629
+ @session.visit('/with_js')
630
+
631
+ element = @session.find('//div[@id="drag_html5"]')
632
+ target = @session.find('//div[@id="drop_html5"]')
633
+
634
+ element.drag_to(target, drop_modifiers: modifier_key)
635
+
636
+ expect(@session).to have_css('div.drag_start', exact_text: 'HTML5 Dragged!')
637
+ expect(@session).to have_xpath("//div[contains(., 'HTML5 Dropped string: text/plain drag_html5-#{modifier_key}')]")
638
+ end
639
+ end
640
+
641
+ it 'should simulate multiple held down modifier keys' do
642
+ @session.visit('/with_js')
643
+
644
+ element = @session.find('//div[@id="drag_html5"]')
645
+ target = @session.find('//div[@id="drop_html5"]')
646
+
647
+ modifier_keys = %I[alt ctrl meta shift]
648
+
649
+ element.drag_to(target, drop_modifiers: modifier_keys)
650
+ expect(@session).to have_xpath("//div[contains(., 'HTML5 Dropped string: text/plain drag_html5-#{modifier_keys.join('-')}')]")
651
+ end
652
+
653
+ it 'should support key aliases' do
654
+ { control: :ctrl,
655
+ command: :meta,
656
+ cmd: :meta }.each do |(key_alias, key)|
657
+ @session.visit('/with_js')
658
+
659
+ element = @session.find('//div[@id="drag_html5"]')
660
+ target = @session.find('//div[@id="drop_html5"]')
661
+
662
+ element.drag_to(target, drop_modifiers: [key_alias])
663
+ expect(target).to have_text(%r{^HTML5 Dropped string: text/plain drag_html5-#{key}$}m, exact: true)
664
+ end
665
+ end
666
+
667
+ it 'should trigger a dragenter event, before the first dragover event' do
668
+ @session.visit('/with_js')
669
+ element = @session.find('//div[@id="drag_html5"]')
670
+ target = @session.find('//div[@id="drop_html5"]')
671
+ element.drag_to(target)
672
+
673
+ # Events are listed in reverse chronological order
674
+ expect(@session).to have_text(/DragOver.*DragEnter/m)
675
+ end
676
+ end
677
+ end
678
+
679
+ describe 'Element#drop', requires: %i[js html5_drag] do
680
+ it 'can drop a file' do
681
+ @session.visit('/with_js')
682
+ target = @session.find('//div[@id="drop_html5"]')
683
+ target.drop(
684
+ with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__)))
685
+ )
686
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
687
+ end
688
+
689
+ it 'can drop multiple files' do
690
+ @session.visit('/with_js')
691
+ target = @session.find('//div[@id="drop_html5"]')
692
+ target.drop(
693
+ with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__))),
694
+ with_os_path_separators(File.expand_path('../fixtures/test_file.txt', File.dirname(__FILE__)))
695
+ )
696
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
697
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: test_file.txt")]')
698
+ end
699
+
700
+ it 'can drop strings' do
701
+ @session.visit('/with_js')
702
+ target = @session.find('//div[@id="drop_html5"]')
703
+ target.drop('text/plain' => 'Some dropped text')
704
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
705
+ end
706
+
707
+ it 'can drop a pathname' do
708
+ @session.visit('/with_js')
709
+ target = @session.find('//div[@id="drop_html5"]')
710
+ target.drop(
711
+ Pathname.new(with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__))))
712
+ )
713
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
714
+ end
715
+
716
+ it 'can drop multiple strings' do
717
+ @session.visit('/with_js')
718
+ target = @session.find('//div[@id="drop_html5"]')
719
+ target.drop('text/plain' => 'Some dropped text', 'text/url' => 'http://www.google.com')
720
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
721
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/url http://www.google.com")]')
287
722
  end
288
723
  end
289
724
 
290
725
  describe '#hover', requires: [:hover] do
291
- it "should allow hovering on an element" do
726
+ it 'should allow hovering on an element' do
292
727
  @session.visit('/with_hover')
293
728
  expect(@session.find(:css, '.wrapper:not(.scroll_needed) .hidden_until_hover', visible: false)).not_to be_visible
294
729
  @session.find(:css, '.wrapper:not(.scroll_needed)').hover
295
730
  expect(@session.find(:css, '.wrapper:not(.scroll_needed) .hidden_until_hover', visible: false)).to be_visible
296
731
  end
297
732
 
298
- it "should allow hovering on an element that needs to be scrolled into view" do
733
+ it 'should allow hovering on an element that needs to be scrolled into view' do
299
734
  @session.visit('/with_hover')
300
735
  expect(@session.find(:css, '.wrapper.scroll_needed .hidden_until_hover', visible: false)).not_to be_visible
301
736
  @session.find(:css, '.wrapper.scroll_needed').hover
302
737
  expect(@session.find(:css, '.wrapper.scroll_needed .hidden_until_hover', visible: false)).to be_visible
303
738
  end
739
+
740
+ it 'should hover again after following a link and back' do
741
+ @session.visit('/with_hover')
742
+ @session.find(:css, '.wrapper:not(.scroll_needed)').hover
743
+ @session.click_link('Other hover page')
744
+ @session.click_link('Go back')
745
+ @session.find(:css, '.wrapper:not(.scroll_needed)').hover
746
+ expect(@session.find(:css, '.wrapper:not(.scroll_needed) .hidden_until_hover', visible: false)).to be_visible
747
+ end
304
748
  end
305
749
 
306
750
  describe '#click' do
307
- it "should not follow a link if no href" do
751
+ it 'should not follow a link if no href' do
308
752
  @session.find(:css, '#link_placeholder').click
309
753
  expect(@session.current_url).to match(%r{/with_html$})
310
754
  end
311
755
 
312
- it "should go to the same page if href is blank" do
756
+ it 'should go to the same page if href is blank' do
313
757
  @session.find(:css, '#link_blank_href').click
314
758
  sleep 1
315
759
  expect(@session).to have_current_path('/with_html')
316
760
  end
317
761
 
318
- it "should be able to check a checkbox" do
762
+ it 'should be able to check a checkbox' do
319
763
  @session.visit('form')
320
764
  cbox = @session.find(:checkbox, 'form_terms_of_use')
321
765
  expect(cbox).not_to be_checked
@@ -323,7 +767,7 @@ Capybara::SpecHelper.spec "node" do
323
767
  expect(cbox).to be_checked
324
768
  end
325
769
 
326
- it "should be able to uncheck a checkbox" do
770
+ it 'should be able to uncheck a checkbox' do
327
771
  @session.visit('/form')
328
772
  cbox = @session.find(:checkbox, 'form_pets_dog')
329
773
  expect(cbox).to be_checked
@@ -331,7 +775,7 @@ Capybara::SpecHelper.spec "node" do
331
775
  expect(cbox).not_to be_checked
332
776
  end
333
777
 
334
- it "should be able to select a radio button" do
778
+ it 'should be able to select a radio button' do
335
779
  @session.visit('/form')
336
780
  radio = @session.find(:radio_button, 'gender_male')
337
781
  expect(radio).not_to be_checked
@@ -339,118 +783,515 @@ Capybara::SpecHelper.spec "node" do
339
783
  expect(radio).to be_checked
340
784
  end
341
785
 
342
- it "should allow modifiers", requires: [:js] do
786
+ it 'should allow modifiers', requires: [:js] do
343
787
  @session.visit('/with_js')
344
788
  @session.find(:css, '#click-test').click(:shift)
345
789
  expect(@session).to have_link('Has been shift clicked')
346
790
  end
347
791
 
348
- it "should allow multiple modifiers", requires: [:js] do
792
+ it 'should allow multiple modifiers', requires: [:js] do
349
793
  @session.visit('with_js')
350
794
  @session.find(:css, '#click-test').click(:control, :alt, :meta, :shift)
351
795
  # Selenium with Chrome on OSX ctrl-click generates a right click so just verify all keys but not click type
352
- expect(@session).to have_link("alt control meta shift")
796
+ expect(@session).to have_link('alt control meta shift')
353
797
  end
354
798
 
355
- it "should allow to adjust the click offset", requires: [:js] do
799
+ it 'should allow to adjust the click offset', requires: [:js] do
800
+ Capybara.w3c_click_offset = false
356
801
  @session.visit('with_js')
357
802
  @session.find(:css, '#click-test').click(x: 5, y: 5)
358
803
  link = @session.find(:link, 'has-been-clicked')
359
- locations = link.text.match(/^Has been clicked at (?<x>[\d\.-]+),(?<y>[\d\.-]+)$/)
804
+ locations = link.text.match(/^Has been clicked at (?<x>[\d.-]+),(?<y>[\d.-]+)$/)
360
805
  # Resulting click location should be very close to 0, 0 relative to top left corner of the element, but may not be exact due to
361
806
  # integer/float conversions and rounding.
362
807
  expect(locations[:x].to_f).to be_within(1).of(5)
363
808
  expect(locations[:y].to_f).to be_within(1).of(5)
364
809
  end
365
- end
366
810
 
367
- describe '#double_click', requires: [:js] do
368
- before do
369
- pending "selenium-webdriver/geckodriver doesn't generate double click event" if marionette_lt?(59, @session)
811
+ it 'should raise error if both x and y values are not passed' do
812
+ @session.visit('with_js')
813
+ el = @session.find(:css, '#click-test')
814
+ expect { el.click(x: 5) }.to raise_error ArgumentError
815
+ expect { el.click(x: nil, y: 3) }.to raise_error ArgumentError
816
+ end
817
+
818
+ it 'should be able to click a table row', requires: [:js] do
819
+ @session.visit('/tables')
820
+ tr = @session.find(:css, '#agent_table tr:first-child').click
821
+ expect(tr).to have_css('label', text: 'Clicked')
822
+ end
823
+
824
+ it 'should retry clicking', requires: [:js] do
825
+ @session.visit('/animated')
826
+ obscured = @session.find(:css, '#obscured')
827
+ @session.execute_script <<~JS
828
+ setTimeout(function(){ $('#cover').hide(); }, 700)
829
+ JS
830
+ expect { obscured.click }.not_to raise_error
831
+ end
832
+
833
+ it 'should allow to retry longer', requires: [:js] do
834
+ @session.visit('/animated')
835
+ obscured = @session.find(:css, '#obscured')
836
+ @session.execute_script <<~JS
837
+ setTimeout(function(){ $('#cover').hide(); }, 3000)
838
+ JS
839
+ expect { obscured.click(wait: 4) }.not_to raise_error
840
+ end
841
+
842
+ it 'should not retry clicking when wait is disabled', requires: [:js] do
843
+ @session.visit('/animated')
844
+ obscured = @session.find(:css, '#obscured')
845
+ @session.execute_script <<~JS
846
+ setTimeout(function(){ $('#cover').hide(); }, 2000)
847
+ JS
848
+ expect { obscured.click(wait: 0) }.to(raise_error { |e| expect(e).to be_an_invalid_element_error(@session) })
849
+ end
850
+
851
+ context 'offset', requires: [:js] do
852
+ before do
853
+ @session.visit('/offset')
854
+ end
855
+
856
+ let :clicker do
857
+ @session.find(:id, 'clicker')
858
+ end
859
+
860
+ context 'when w3c_click_offset is false' do
861
+ before do
862
+ Capybara.w3c_click_offset = false
863
+ end
864
+
865
+ it 'should offset from top left of element' do
866
+ clicker.click(x: 10, y: 5)
867
+ expect(@session).to have_text(/clicked at 110,105/)
868
+ end
869
+
870
+ it 'should offset outside the element' do
871
+ clicker.click(x: -15, y: -10)
872
+ expect(@session).to have_text(/clicked at 85,90/)
873
+ end
874
+
875
+ it 'should default to click the middle' do
876
+ clicker.click
877
+ expect(@session).to have_text(/clicked at 150,150/)
878
+ end
879
+ end
880
+
881
+ context 'when w3c_click_offset is true' do
882
+ before do
883
+ Capybara.w3c_click_offset = true
884
+ end
885
+
886
+ it 'should offset from center of element' do
887
+ clicker.click(x: 10, y: 5)
888
+ expect(@session).to have_text(/clicked at 160,155/)
889
+ end
890
+
891
+ it 'should offset outside from center of element' do
892
+ clicker.click(x: -65, y: -60)
893
+ expect(@session).to have_text(/clicked at 85,90/)
894
+ end
895
+
896
+ it 'should default to click the middle' do
897
+ clicker.click
898
+ expect(@session).to have_text(/clicked at 150,150/)
899
+ end
900
+ end
370
901
  end
371
902
 
372
- it "should double click an element" do
903
+ context 'delay', requires: [:js] do
904
+ it 'should delay the mouse up' do
905
+ @session.visit('with_js')
906
+ @session.find(:css, '#click-test').click(delay: 2)
907
+ delay = @session.evaluate_script('window.click_delay')
908
+ expect(delay).to be >= 2
909
+ end
910
+ end
911
+ end
912
+
913
+ describe '#double_click', requires: [:js] do
914
+ it 'should double click an element' do
373
915
  @session.visit('/with_js')
374
916
  @session.find(:css, '#click-test').double_click
375
- expect(@session.find(:css, '#has-been-double-clicked')).to be
917
+ expect(@session.find(:css, '#has-been-double-clicked')).to be_truthy
376
918
  end
377
919
 
378
- it "should allow modifiers", requires: [:js] do
920
+ it 'should allow modifiers', requires: [:js] do
379
921
  @session.visit('/with_js')
380
922
  @session.find(:css, '#click-test').double_click(:alt)
381
923
  expect(@session).to have_link('Has been alt double clicked')
382
924
  end
383
925
 
384
- it "should allow to adjust the offset", requires: [:js] do
926
+ it 'should allow to adjust the offset', requires: [:js] do
927
+ Capybara.w3c_click_offset = false
385
928
  @session.visit('with_js')
386
929
  @session.find(:css, '#click-test').double_click(x: 10, y: 5)
387
930
  link = @session.find(:link, 'has-been-double-clicked')
388
- locations = link.text.match(/^Has been double clicked at (?<x>[\d\.-]+),(?<y>[\d\.-]+)$/)
931
+ locations = link.text.match(/^Has been double clicked at (?<x>[\d.-]+),(?<y>[\d.-]+)$/)
389
932
  # Resulting click location should be very close to 10, 5 relative to top left corner of the element, but may not be exact due
390
933
  # to integer/float conversions and rounding.
391
934
  expect(locations[:x].to_f).to be_within(1).of(10)
392
935
  expect(locations[:y].to_f).to be_within(1).of(5)
393
936
  end
937
+
938
+ it 'should retry clicking', requires: [:js] do
939
+ @session.visit('/animated')
940
+ obscured = @session.find(:css, '#obscured')
941
+ @session.execute_script <<~JS
942
+ setTimeout(function(){ $('#cover').hide(); }, 700)
943
+ JS
944
+ expect { obscured.double_click }.not_to raise_error
945
+ end
946
+
947
+ context 'offset', requires: [:js] do
948
+ before do
949
+ @session.visit('/offset')
950
+ end
951
+
952
+ let :clicker do
953
+ @session.find(:id, 'clicker')
954
+ end
955
+
956
+ context 'when w3c_click_offset is false' do
957
+ before do
958
+ Capybara.w3c_click_offset = false
959
+ end
960
+
961
+ it 'should offset from top left of element' do
962
+ clicker.double_click(x: 10, y: 5)
963
+ expect(@session).to have_text(/clicked at 110,105/)
964
+ end
965
+
966
+ it 'should offset outside the element' do
967
+ clicker.double_click(x: -15, y: -10)
968
+ expect(@session).to have_text(/clicked at 85,90/)
969
+ end
970
+
971
+ it 'should default to click the middle' do
972
+ clicker.double_click
973
+ expect(@session).to have_text(/clicked at 150,150/)
974
+ end
975
+ end
976
+
977
+ context 'when w3c_click_offset is true' do
978
+ before do
979
+ Capybara.w3c_click_offset = true
980
+ end
981
+
982
+ it 'should offset from center of element' do
983
+ clicker.double_click(x: 10, y: 5)
984
+ expect(@session).to have_text(/clicked at 160,155/)
985
+ end
986
+
987
+ it 'should offset outside from center of element' do
988
+ clicker.double_click(x: -65, y: -60)
989
+ expect(@session).to have_text(/clicked at 85,90/)
990
+ end
991
+
992
+ it 'should default to click the middle' do
993
+ clicker.double_click
994
+ expect(@session).to have_text(/clicked at 150,150/)
995
+ end
996
+ end
997
+ end
394
998
  end
395
999
 
396
1000
  describe '#right_click', requires: [:js] do
397
- it "should right click an element" do
1001
+ it 'should right click an element' do
398
1002
  @session.visit('/with_js')
399
1003
  @session.find(:css, '#click-test').right_click
400
- expect(@session.find(:css, '#has-been-right-clicked')).to be
1004
+ expect(@session.find(:css, '#has-been-right-clicked')).to be_truthy
401
1005
  end
402
1006
 
403
- it "should allow modifiers", requires: [:js] do
1007
+ it 'should allow modifiers', requires: [:js] do
404
1008
  @session.visit('/with_js')
405
1009
  @session.find(:css, '#click-test').right_click(:meta)
406
1010
  expect(@session).to have_link('Has been meta right clicked')
407
1011
  end
408
1012
 
409
- it "should allow to adjust the offset", requires: [:js] do
1013
+ it 'should allow to adjust the offset', requires: [:js] do
1014
+ Capybara.w3c_click_offset = false
410
1015
  @session.visit('with_js')
411
1016
  @session.find(:css, '#click-test').right_click(x: 10, y: 10)
412
1017
  link = @session.find(:link, 'has-been-right-clicked')
413
- locations = link.text.match(/^Has been right clicked at (?<x>[\d\.-]+),(?<y>[\d\.-]+)$/)
1018
+ locations = link.text.match(/^Has been right clicked at (?<x>[\d.-]+),(?<y>[\d.-]+)$/)
414
1019
  # Resulting click location should be very close to 10, 10 relative to top left corner of the element, but may not be exact due
415
1020
  # to integer/float conversions and rounding
416
1021
  expect(locations[:x].to_f).to be_within(1).of(10)
417
1022
  expect(locations[:y].to_f).to be_within(1).of(10)
418
1023
  end
1024
+
1025
+ it 'should retry clicking', requires: [:js] do
1026
+ @session.visit('/animated')
1027
+ obscured = @session.find(:css, '#obscured')
1028
+ @session.execute_script <<~JS
1029
+ setTimeout(function(){ $('#cover').hide(); }, 700)
1030
+ JS
1031
+ expect { obscured.right_click }.not_to raise_error
1032
+ end
1033
+
1034
+ context 'offset', requires: [:js] do
1035
+ before do
1036
+ @session.visit('/offset')
1037
+ end
1038
+
1039
+ let :clicker do
1040
+ @session.find(:id, 'clicker')
1041
+ end
1042
+
1043
+ context 'when w3c_click_offset is false' do
1044
+ before do
1045
+ Capybara.w3c_click_offset = false
1046
+ end
1047
+
1048
+ it 'should offset from top left of element' do
1049
+ clicker.right_click(x: 10, y: 5)
1050
+ expect(@session).to have_text(/clicked at 110,105/)
1051
+ end
1052
+
1053
+ it 'should offset outside the element' do
1054
+ clicker.right_click(x: -15, y: -10)
1055
+ expect(@session).to have_text(/clicked at 85,90/)
1056
+ end
1057
+
1058
+ it 'should default to click the middle' do
1059
+ clicker.right_click
1060
+ expect(@session).to have_text(/clicked at 150,150/)
1061
+ end
1062
+ end
1063
+
1064
+ context 'when w3c_click_offset is true' do
1065
+ before do
1066
+ Capybara.w3c_click_offset = true
1067
+ end
1068
+
1069
+ it 'should offset from center of element' do
1070
+ clicker.right_click(x: 10, y: 5)
1071
+ expect(@session).to have_text(/clicked at 160,155/)
1072
+ end
1073
+
1074
+ it 'should offset outside from center of element' do
1075
+ clicker.right_click(x: -65, y: -60)
1076
+ expect(@session).to have_text(/clicked at 85,90/)
1077
+ end
1078
+
1079
+ it 'should default to click the middle' do
1080
+ clicker.right_click
1081
+ expect(@session).to have_text(/clicked at 150,150/)
1082
+ end
1083
+ end
1084
+ end
1085
+
1086
+ context 'delay', requires: [:js] do
1087
+ it 'should delay the mouse up' do
1088
+ @session.visit('with_js')
1089
+ @session.find(:css, '#click-test').right_click(delay: 2)
1090
+ delay = @session.evaluate_script('window.right_click_delay')
1091
+ expect(delay).to be >= 2
1092
+ end
1093
+ end
419
1094
  end
420
1095
 
421
1096
  describe '#send_keys', requires: [:send_keys] do
422
- it "should send a string of keys to an element" do
1097
+ it 'should send a string of keys to an element' do
423
1098
  @session.visit('/form')
424
1099
  @session.find(:css, '#address1_city').send_keys('Oceanside')
425
1100
  expect(@session.find(:css, '#address1_city').value).to eq 'Oceanside'
426
1101
  end
427
1102
 
428
- it "should send special characters" do
429
- pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" if marionette?(@session)
1103
+ it 'should send special characters' do
430
1104
  @session.visit('/form')
431
1105
  @session.find(:css, '#address1_city').send_keys('Ocean', :space, 'sie', :left, 'd')
432
1106
  expect(@session.find(:css, '#address1_city').value).to eq 'Ocean side'
433
1107
  end
434
1108
 
435
- it "should allow for multiple simultaneous keys" do
436
- pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" if marionette?(@session)
1109
+ it 'should allow for multiple simultaneous keys' do
437
1110
  @session.visit('/form')
438
1111
  @session.find(:css, '#address1_city').send_keys([:shift, 'o'], 'ceanside')
439
1112
  expect(@session.find(:css, '#address1_city').value).to eq 'Oceanside'
440
1113
  end
441
1114
 
442
- it "should generate key events", requires: %i[send_keys js] do
443
- pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" if marionette?(@session)
1115
+ it 'should hold modifiers at top level' do
1116
+ @session.visit('/form')
1117
+ @session.find(:css, '#address1_city').send_keys('ocean', :shift, 'side')
1118
+ expect(@session.find(:css, '#address1_city').value).to eq 'oceanSIDE'
1119
+ end
1120
+
1121
+ it 'should generate key events', requires: %i[send_keys js] do
444
1122
  @session.visit('/with_js')
445
1123
  @session.find(:css, '#with-key-events').send_keys([:shift, 't'], [:shift, 'w'])
446
1124
  expect(@session.find(:css, '#key-events-output')).to have_text('keydown:16 keydown:84 keydown:16 keydown:87')
447
1125
  end
448
1126
  end
449
1127
 
1128
+ describe '#execute_script', requires: %i[js es_args] do
1129
+ it 'should execute the given script in the context of the element and return nothing' do
1130
+ @session.visit('/with_js')
1131
+ expect(@session.find(:css, '#change').execute_script("this.textContent = 'Funky Doodle'")).to be_nil
1132
+ expect(@session).to have_css('#change', text: 'Funky Doodle')
1133
+ end
1134
+
1135
+ it 'should pass arguments to the script' do
1136
+ @session.visit('/with_js')
1137
+ @session.find(:css, '#change').execute_script('this.textContent = arguments[0]', 'Doodle Funk')
1138
+ expect(@session).to have_css('#change', text: 'Doodle Funk')
1139
+ end
1140
+ end
1141
+
1142
+ describe '#evaluate_script', requires: %i[js es_args] do
1143
+ it 'should evaluate the given script in the context of the element and return whatever it produces' do
1144
+ @session.visit('/with_js')
1145
+ el = @session.find(:css, '#with_change_event')
1146
+ expect(el.evaluate_script('this.value')).to eq('default value')
1147
+ end
1148
+
1149
+ it 'should ignore leading whitespace' do
1150
+ @session.visit('/with_js')
1151
+ expect(@session.find(:css, '#change').evaluate_script('
1152
+ 2 + 3
1153
+ ')).to eq 5
1154
+ end
1155
+
1156
+ it 'should pass arguments to the script' do
1157
+ @session.visit('/with_js')
1158
+ @session.find(:css, '#change').evaluate_script('this.textContent = arguments[0]', 'Doodle Funk')
1159
+ expect(@session).to have_css('#change', text: 'Doodle Funk')
1160
+ end
1161
+
1162
+ it 'should pass multiple arguments' do
1163
+ @session.visit('/with_js')
1164
+ change = @session.find(:css, '#change')
1165
+ expect(change.evaluate_script('arguments[0] + arguments[1]', 2, 3)).to eq 5
1166
+ end
1167
+
1168
+ it 'should support returning elements' do
1169
+ @session.visit('/with_js')
1170
+ change = @session.find(:css, '#change') # ensure page has loaded and element is available
1171
+ el = change.evaluate_script('this')
1172
+ expect(el).to be_instance_of(Capybara::Node::Element)
1173
+ expect(el).to eq(change)
1174
+ end
1175
+
1176
+ it 'should support multiple statements via IIFE' do
1177
+ @session.visit('/with_js')
1178
+ change = @session.find(:css, '#change') # ensure page has loaded and element is available
1179
+ res = change.evaluate_script(<<~JS, 3)
1180
+ (function(n){
1181
+ var el = this;
1182
+ return [el, n];
1183
+ }).apply(this, arguments)
1184
+ JS
1185
+ expect(res).to eq [change, 3]
1186
+ end
1187
+ end
1188
+
1189
+ describe '#evaluate_async_script', requires: %i[js es_args] do
1190
+ it 'should evaluate the given script in the context of the element' do
1191
+ @session.visit('/with_js')
1192
+ el = @session.find(:css, '#with_change_event')
1193
+ expect(el.evaluate_async_script('arguments[0](this.value)')).to eq('default value')
1194
+ end
1195
+
1196
+ it 'should support returning elements after asynchronous operation' do
1197
+ @session.visit('/with_js')
1198
+ change = @session.find(:css, '#change') # ensure page has loaded and element is available
1199
+ el = change.evaluate_async_script('var cb = arguments[0]; setTimeout(function(el){ cb(el) }, 100, this)')
1200
+ expect(el).to eq(change)
1201
+ end
1202
+ end
1203
+
1204
+ describe '#shadow_root', requires: %i[js] do
1205
+ it 'should get the shadow root' do
1206
+ @session.visit('/with_shadow')
1207
+ expect do
1208
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1209
+ expect(shadow_root).to be_a(Capybara::Node::Element)
1210
+ end.not_to raise_error
1211
+ end
1212
+
1213
+ it 'should find elements inside the shadow dom using CSS' do
1214
+ @session.visit('/with_shadow')
1215
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1216
+ expect(shadow_root).to have_css('#shadow_content', text: 'some text')
1217
+ end
1218
+
1219
+ it 'should find nested shadow roots' do
1220
+ @session.visit('/with_shadow')
1221
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1222
+ nested_shadow_root = shadow_root.find(:css, '#nested_shadow_host').shadow_root
1223
+ expect(nested_shadow_root).to have_css('#nested_shadow_content', text: 'nested text')
1224
+ end
1225
+
1226
+ it 'should click on elements' do
1227
+ @session.visit('/with_shadow')
1228
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1229
+ checkbox = shadow_root.find(:css, 'input[type="checkbox"]')
1230
+ expect(checkbox).not_to be_checked
1231
+ checkbox.click
1232
+ expect(checkbox).to be_checked
1233
+ end
1234
+
1235
+ it 'should use convenience methods once moved to a descendant of the shadow root' do
1236
+ @session.visit('/with_shadow')
1237
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1238
+ descendant = shadow_root.find(:css, '#controls_wrapper')
1239
+ expect do
1240
+ descendant.check('shadow_checkbox')
1241
+ end.not_to raise_error
1242
+ expect(descendant).to have_checked_field('shadow_checkbox')
1243
+ end
1244
+
1245
+ it 'should produce error messages when failing' do
1246
+ @session.visit('/with_shadow')
1247
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1248
+ expect do
1249
+ expect(shadow_root).to have_css('#shadow_content', text: 'Not in the document')
1250
+ end.to raise_error(/tag="ShadowRoot"/)
1251
+ end
1252
+
1253
+ it 'should get visible text' do
1254
+ @session.visit('/with_shadow')
1255
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1256
+ expect(shadow_root).to have_text('some text scroll.html')
1257
+ end
1258
+
1259
+ it 'should get all text' do
1260
+ @session.visit('/with_shadow')
1261
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1262
+ expect(shadow_root).to have_text(:all, 'some text scroll.html')
1263
+ end
1264
+ end
1265
+
450
1266
  describe '#reload', requires: [:js] do
451
- context "without automatic reload" do
1267
+ it 'should reload elements found via ancestor with CSS' do
1268
+ @session.visit('/with_js')
1269
+ node = @session.find(:css, '#reload-me em').ancestor(:css, 'div')
1270
+ node.reload
1271
+ expect(node[:id]).to eq 'reload-me'
1272
+ end
1273
+
1274
+ it 'should reload elements found via ancestor with XPath' do
1275
+ @session.visit('/with_js')
1276
+ node = @session.find(:css, '#reload-me em').ancestor(:xpath, './/div')
1277
+ node.reload
1278
+ expect(node[:id]).to eq 'reload-me'
1279
+ end
1280
+
1281
+ it 'should reload elements found via sibling' do
1282
+ @session.visit('/with_js')
1283
+ node = @session.find(:css, '#the-list li', text: 'Item 1').sibling(:css, 'li')
1284
+ expect(node.text).to eq 'Item 2'
1285
+ node.reload
1286
+ expect(node.text).to eq 'Item 2'
1287
+ end
1288
+
1289
+ context 'without automatic reload' do
452
1290
  before { Capybara.automatic_reload = false }
453
- it "should reload the current context of the node" do
1291
+
1292
+ after { Capybara.automatic_reload = true }
1293
+
1294
+ it 'should reload the current context of the node' do
454
1295
  @session.visit('/with_js')
455
1296
  node = @session.find(:css, '#reload-me')
456
1297
  @session.click_link('Reload!')
@@ -459,7 +1300,7 @@ Capybara::SpecHelper.spec "node" do
459
1300
  expect(node.text).to eq('has been reloaded')
460
1301
  end
461
1302
 
462
- it "should reload a parent node" do
1303
+ it 'should reload a parent node' do
463
1304
  @session.visit('/with_js')
464
1305
  node = @session.find(:css, '#reload-me').find(:css, 'em')
465
1306
  @session.click_link('Reload!')
@@ -468,7 +1309,7 @@ Capybara::SpecHelper.spec "node" do
468
1309
  expect(node.text).to eq('has been reloaded')
469
1310
  end
470
1311
 
471
- it "should not automatically reload" do
1312
+ it 'should not automatically reload' do
472
1313
  @session.visit('/with_js')
473
1314
  node = @session.find(:css, '#reload-me')
474
1315
  @session.click_link('Reload!')
@@ -479,15 +1320,14 @@ Capybara::SpecHelper.spec "node" do
479
1320
  expect(error).to be_an_invalid_element_error(@session)
480
1321
  end)
481
1322
  end
482
- after { Capybara.automatic_reload = true }
483
1323
  end
484
1324
 
485
- context "with automatic reload" do
1325
+ context 'with automatic reload' do
486
1326
  before do
487
1327
  Capybara.default_max_wait_time = 4
488
1328
  end
489
1329
 
490
- it "should reload the current context of the node automatically" do
1330
+ it 'should reload the current context of the node automatically' do
491
1331
  @session.visit('/with_js')
492
1332
  node = @session.find(:css, '#reload-me')
493
1333
  @session.click_link('Reload!')
@@ -495,7 +1335,7 @@ Capybara::SpecHelper.spec "node" do
495
1335
  expect(node.text).to eq('has been reloaded')
496
1336
  end
497
1337
 
498
- it "should reload a parent node automatically" do
1338
+ it 'should reload a parent node automatically' do
499
1339
  @session.visit('/with_js')
500
1340
  node = @session.find(:css, '#reload-me').find(:css, 'em')
501
1341
  @session.click_link('Reload!')
@@ -503,7 +1343,7 @@ Capybara::SpecHelper.spec "node" do
503
1343
  expect(node.text).to eq('has been reloaded')
504
1344
  end
505
1345
 
506
- it "should reload a node automatically when using find" do
1346
+ it 'should reload a node automatically when using find' do
507
1347
  @session.visit('/with_js')
508
1348
  node = @session.find(:css, '#reload-me')
509
1349
  @session.click_link('Reload!')
@@ -525,9 +1365,9 @@ Capybara::SpecHelper.spec "node" do
525
1365
  end.to(raise_error { |error| expect(error).to be_an_invalid_element_error(@session) })
526
1366
  end
527
1367
 
528
- it "should reload nodes with options" do
1368
+ it 'should reload nodes with options' do
529
1369
  @session.visit('/with_js')
530
- node = @session.find(:css, 'em', text: "reloaded")
1370
+ node = @session.find(:css, 'em', text: 'reloaded')
531
1371
  @session.click_link('Reload!')
532
1372
  sleep(1)
533
1373
  expect(node.text).to eq('has been reloaded')
@@ -535,9 +1375,9 @@ Capybara::SpecHelper.spec "node" do
535
1375
  end
536
1376
  end
537
1377
 
538
- context "when #synchronize raises server errors" do
539
- it "sets an explanatory exception as the cause of server exceptions", requires: %i[server js] do
540
- quietly { @session.visit("/error") }
1378
+ context 'when #synchronize raises server errors' do
1379
+ it 'sets an explanatory exception as the cause of server exceptions', requires: %i[server js] do
1380
+ quietly { @session.visit('/error') }
541
1381
  expect do
542
1382
  @session.find(:css, 'span')
543
1383
  end.to(raise_error(TestApp::TestAppError) do |e|
@@ -546,8 +1386,8 @@ Capybara::SpecHelper.spec "node" do
546
1386
  end)
547
1387
  end
548
1388
 
549
- it "sets an explanatory exception as the cause of server exceptions with errors with initializers", requires: %i[server js] do
550
- quietly { @session.visit("/other_error") }
1389
+ it 'sets an explanatory exception as the cause of server exceptions with errors with initializers', requires: %i[server js] do
1390
+ quietly { @session.visit('/other_error') }
551
1391
  expect do
552
1392
  @session.find(:css, 'span')
553
1393
  end.to(raise_error(TestApp::TestAppOtherError) do |e|
@@ -556,8 +1396,4 @@ Capybara::SpecHelper.spec "node" do
556
1396
  end)
557
1397
  end
558
1398
  end
559
-
560
- def be_an_invalid_element_error(session)
561
- satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } }
562
- end
563
1399
  end