capybara 3.3.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 (308) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +803 -13
  4. data/License.txt +1 -1
  5. data/README.md +257 -84
  6. data/lib/capybara/config.rb +25 -9
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +17 -3
  9. data/lib/capybara/driver/node.rb +31 -6
  10. data/lib/capybara/dsl.rb +9 -7
  11. data/lib/capybara/helpers.rb +31 -7
  12. data/lib/capybara/minitest/spec.rb +180 -88
  13. data/lib/capybara/minitest.rb +262 -149
  14. data/lib/capybara/node/actions.rb +202 -116
  15. data/lib/capybara/node/base.rb +34 -19
  16. data/lib/capybara/node/document.rb +14 -2
  17. data/lib/capybara/node/document_matchers.rb +10 -12
  18. data/lib/capybara/node/element.rb +269 -115
  19. data/lib/capybara/node/finders.rb +99 -77
  20. data/lib/capybara/node/matchers.rb +327 -151
  21. data/lib/capybara/node/simple.rb +48 -13
  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 +8 -9
  25. data/lib/capybara/queries/base_query.rb +23 -16
  26. data/lib/capybara/queries/current_path_query.rb +16 -6
  27. data/lib/capybara/queries/match_query.rb +1 -0
  28. data/lib/capybara/queries/selector_query.rb +587 -130
  29. data/lib/capybara/queries/sibling_query.rb +8 -6
  30. data/lib/capybara/queries/style_query.rb +6 -2
  31. data/lib/capybara/queries/text_query.rb +28 -14
  32. data/lib/capybara/queries/title_query.rb +2 -2
  33. data/lib/capybara/rack_test/browser.rb +92 -25
  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 +68 -41
  37. data/lib/capybara/rack_test/node.rb +106 -39
  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 +75 -52
  44. data/lib/capybara/rspec/features.rb +7 -7
  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 +141 -339
  60. data/lib/capybara/rspec.rb +2 -0
  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 +27 -25
  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 +73 -25
  88. data/lib/capybara/selector/filters/base.rb +24 -5
  89. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  90. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  91. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  92. data/lib/capybara/selector/regexp_disassembler.rb +211 -0
  93. data/lib/capybara/selector/selector.rb +85 -348
  94. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  95. data/lib/capybara/selector.rb +474 -447
  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 +255 -143
  101. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +93 -11
  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 +436 -134
  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 +56 -19
  123. data/lib/capybara/server/checker.rb +9 -3
  124. data/lib/capybara/server/middleware.rb +28 -12
  125. data/lib/capybara/server.rb +33 -10
  126. data/lib/capybara/session/config.rb +34 -10
  127. data/lib/capybara/session/matchers.rb +23 -16
  128. data/lib/capybara/session.rb +230 -170
  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 +121 -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 +127 -40
  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_spec.rb +21 -18
  140. data/lib/capybara/spec/session/assert_selector_spec.rb +52 -58
  141. data/lib/capybara/spec/session/assert_style_spec.rb +7 -7
  142. data/lib/capybara/spec/session/assert_text_spec.rb +74 -50
  143. data/lib/capybara/spec/session/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 +6 -6
  146. data/lib/capybara/spec/session/check_spec.rb +102 -47
  147. data/lib/capybara/spec/session/choose_spec.rb +58 -32
  148. data/lib/capybara/spec/session/click_button_spec.rb +219 -163
  149. data/lib/capybara/spec/session/click_link_or_button_spec.rb +49 -23
  150. data/lib/capybara/spec/session/click_link_spec.rb +77 -54
  151. data/lib/capybara/spec/session/current_scope_spec.rb +8 -8
  152. data/lib/capybara/spec/session/current_url_spec.rb +38 -29
  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_spec.rb +8 -8
  156. data/lib/capybara/spec/session/element/match_css_spec.rb +16 -10
  157. data/lib/capybara/spec/session/element/match_xpath_spec.rb +6 -6
  158. data/lib/capybara/spec/session/element/matches_selector_spec.rb +68 -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 +101 -46
  163. data/lib/capybara/spec/session/find_button_spec.rb +23 -23
  164. data/lib/capybara/spec/session/find_by_id_spec.rb +7 -7
  165. data/lib/capybara/spec/session/find_field_spec.rb +32 -30
  166. data/lib/capybara/spec/session/find_link_spec.rb +31 -21
  167. data/lib/capybara/spec/session/find_spec.rb +244 -141
  168. data/lib/capybara/spec/session/first_spec.rb +43 -43
  169. data/lib/capybara/spec/session/frame/frame_title_spec.rb +5 -5
  170. data/lib/capybara/spec/session/frame/frame_url_spec.rb +5 -5
  171. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +30 -18
  172. data/lib/capybara/spec/session/frame/within_frame_spec.rb +45 -18
  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 +94 -13
  179. data/lib/capybara/spec/session/has_css_spec.rb +272 -132
  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 +44 -4
  184. data/lib/capybara/spec/session/has_none_selectors_spec.rb +31 -31
  185. data/lib/capybara/spec/session/has_select_spec.rb +84 -50
  186. data/lib/capybara/spec/session/has_selector_spec.rb +111 -71
  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_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 +894 -142
  196. data/lib/capybara/spec/session/node_wrapper_spec.rb +10 -7
  197. data/lib/capybara/spec/session/refresh_spec.rb +9 -7
  198. data/lib/capybara/spec/session/reset_session_spec.rb +63 -35
  199. data/lib/capybara/spec/session/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 +2 -2
  202. data/lib/capybara/spec/session/save_page_spec.rb +37 -37
  203. data/lib/capybara/spec/session/save_screenshot_spec.rb +10 -10
  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 +85 -85
  207. data/lib/capybara/spec/session/selectors_spec.rb +49 -18
  208. data/lib/capybara/spec/session/sibling_spec.rb +9 -9
  209. data/lib/capybara/spec/session/text_spec.rb +25 -24
  210. data/lib/capybara/spec/session/title_spec.rb +7 -6
  211. data/lib/capybara/spec/session/uncheck_spec.rb +33 -21
  212. data/lib/capybara/spec/session/unselect_spec.rb +37 -37
  213. data/lib/capybara/spec/session/visit_spec.rb +68 -49
  214. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  215. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -1
  216. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +20 -16
  217. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +6 -2
  218. data/lib/capybara/spec/session/window/window_spec.rb +62 -63
  219. data/lib/capybara/spec/session/window/windows_spec.rb +5 -1
  220. data/lib/capybara/spec/session/window/within_window_spec.rb +14 -14
  221. data/lib/capybara/spec/session/within_spec.rb +79 -42
  222. data/lib/capybara/spec/spec_helper.rb +41 -53
  223. data/lib/capybara/spec/test_app.rb +132 -43
  224. data/lib/capybara/spec/views/animated.erb +49 -0
  225. data/lib/capybara/spec/views/form.erb +139 -42
  226. data/lib/capybara/spec/views/frame_child.erb +4 -3
  227. data/lib/capybara/spec/views/frame_one.erb +2 -1
  228. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  229. data/lib/capybara/spec/views/frame_two.erb +1 -1
  230. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  231. data/lib/capybara/spec/views/layout.erb +10 -0
  232. data/lib/capybara/spec/views/obscured.erb +47 -0
  233. data/lib/capybara/spec/views/offset.erb +33 -0
  234. data/lib/capybara/spec/views/path.erb +2 -2
  235. data/lib/capybara/spec/views/popup_one.erb +1 -1
  236. data/lib/capybara/spec/views/popup_two.erb +1 -1
  237. data/lib/capybara/spec/views/react.erb +45 -0
  238. data/lib/capybara/spec/views/scroll.erb +21 -0
  239. data/lib/capybara/spec/views/spatial.erb +31 -0
  240. data/lib/capybara/spec/views/tables.erb +67 -0
  241. data/lib/capybara/spec/views/with_animation.erb +39 -4
  242. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  243. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  244. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  245. data/lib/capybara/spec/views/with_hover.erb +3 -2
  246. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  247. data/lib/capybara/spec/views/with_html.erb +37 -9
  248. data/lib/capybara/spec/views/with_html5_svg.erb +20 -0
  249. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  250. data/lib/capybara/spec/views/with_js.erb +26 -5
  251. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  252. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  253. data/lib/capybara/spec/views/with_scope.erb +2 -2
  254. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  255. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  256. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  257. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  258. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  259. data/lib/capybara/spec/views/with_windows.erb +1 -1
  260. data/lib/capybara/spec/views/within_frames.erb +1 -1
  261. data/lib/capybara/version.rb +1 -1
  262. data/lib/capybara/window.rb +19 -25
  263. data/lib/capybara.rb +126 -111
  264. data/spec/basic_node_spec.rb +59 -34
  265. data/spec/capybara_spec.rb +56 -44
  266. data/spec/counter_spec.rb +35 -0
  267. data/spec/css_builder_spec.rb +101 -0
  268. data/spec/css_splitter_spec.rb +8 -8
  269. data/spec/dsl_spec.rb +79 -52
  270. data/spec/filter_set_spec.rb +9 -9
  271. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -4
  272. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -4
  273. data/spec/minitest_spec.rb +45 -7
  274. data/spec/minitest_spec_spec.rb +87 -64
  275. data/spec/per_session_config_spec.rb +6 -6
  276. data/spec/rack_test_spec.rb +172 -116
  277. data/spec/regexp_dissassembler_spec.rb +250 -0
  278. data/spec/result_spec.rb +80 -72
  279. data/spec/rspec/features_spec.rb +21 -16
  280. data/spec/rspec/scenarios_spec.rb +10 -6
  281. data/spec/rspec/shared_spec_matchers.rb +407 -365
  282. data/spec/rspec/views_spec.rb +3 -3
  283. data/spec/rspec_matchers_spec.rb +35 -10
  284. data/spec/rspec_spec.rb +63 -41
  285. data/spec/sauce_spec_chrome.rb +43 -0
  286. data/spec/selector_spec.rb +334 -89
  287. data/spec/selenium_spec_chrome.rb +176 -62
  288. data/spec/selenium_spec_chrome_remote.rb +54 -14
  289. data/spec/selenium_spec_edge.rb +41 -8
  290. data/spec/selenium_spec_firefox.rb +228 -0
  291. data/spec/selenium_spec_firefox_remote.rb +94 -0
  292. data/spec/selenium_spec_ie.rb +129 -11
  293. data/spec/selenium_spec_safari.rb +162 -0
  294. data/spec/server_spec.rb +171 -97
  295. data/spec/session_spec.rb +34 -18
  296. data/spec/shared_selenium_node.rb +79 -0
  297. data/spec/shared_selenium_session.rb +344 -80
  298. data/spec/spec_helper.rb +124 -2
  299. data/spec/whitespace_normalizer_spec.rb +54 -0
  300. data/spec/xpath_builder_spec.rb +93 -0
  301. metadata +326 -28
  302. data/lib/capybara/rspec/compound.rb +0 -94
  303. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -31
  304. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -31
  305. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  306. data/lib/capybara/spec/session/source_spec.rb +0 -0
  307. data/lib/capybara/spec/views/with_title.erb +0 -5
  308. data/spec/selenium_spec_marionette.rb +0 -167
@@ -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,70 +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 "#style", requires: [:css] do
55
- it "should return the computed style value" do
58
+ describe '#style', requires: [:css] do
59
+ it 'should return the computed style value' do
56
60
  expect(@session.find(:css, '#first').style('display')).to eq('display' => 'block')
57
61
  expect(@session.find(:css, '#second').style(:display)).to eq('display' => 'inline')
58
62
  end
59
63
 
60
- it "should return multiple style values" do
64
+ it 'should return multiple style values' do
61
65
  expect(@session.find(:css, '#first').style('display', :'line-height')).to eq('display' => 'block', 'line-height' => '25px')
62
66
  end
63
67
  end
64
68
 
65
- describe "#value" do
66
- it "should allow retrieval of the value" do
69
+ describe '#value' do
70
+ it 'should allow retrieval of the value' do
67
71
  expect(@session.find('//textarea[@id="normal"]').value).to eq('banana')
68
72
  end
69
73
 
70
- it "should not swallow extra newlines in textarea" do
74
+ it 'should not swallow extra newlines in textarea' do
71
75
  expect(@session.find('//textarea[@id="additional_newline"]').value).to eq("\nbanana")
72
76
  end
73
77
 
74
- 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
75
79
  @session.find('//textarea[@id="normal"]').set("\nbanana")
76
80
  expect(@session.find('//textarea[@id="normal"]').value).to eq("\nbanana")
77
81
  end
78
82
 
79
- it "return any HTML content in textarea" do
80
- @session.find('//textarea[1]').set("some <em>html</em> here")
81
- 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')
82
86
  end
83
87
 
84
88
  it "defaults to 'on' for checkbox" do
@@ -92,25 +96,37 @@ Capybara::SpecHelper.spec "node" do
92
96
  end
93
97
  end
94
98
 
95
- describe "#set" do
96
- it "should allow assignment of field value" do
99
+ describe '#set' do
100
+ it 'should allow assignment of field value' do
97
101
  expect(@session.first('//input').value).to eq('monkey')
98
102
  @session.first('//input').set('gorilla')
99
103
  expect(@session.first('//input').value).to eq('gorilla')
100
104
  end
101
105
 
102
- 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
103
107
  @session.execute_script("var el = document.getElementById('test_field'); el.focus(); el.setSelectionRange(0, 0);")
104
108
  @session.first('//input').set('')
105
109
  expect(@session.first('//input').value).to eq('')
106
110
  end
107
111
 
108
- it "should raise if the text field is readonly" do
109
- expect { @session.first('//input[@readonly]').set('changed') }.to raise_error(Capybara::ReadOnlyElementError)
110
- end
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
111
125
 
112
- it "should raise if the textarea is readonly" do
113
- expect { @session.first('//textarea[@readonly]').set('changed') }.to raise_error(Capybara::ReadOnlyElementError)
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
114
130
  end
115
131
 
116
132
  it 'should use global default options' do
@@ -121,7 +137,7 @@ Capybara::SpecHelper.spec "node" do
121
137
  expect(element.base).to have_received(:set).with('gorilla', clear: :backspace)
122
138
  end
123
139
 
124
- context "with a contenteditable element", requires: [:js] do
140
+ context 'with a contenteditable element', requires: [:js] do
125
141
  it 'should allow me to change the contents' do
126
142
  @session.visit('/with_js')
127
143
  @session.find(:css, '#existing_content_editable').set('WYSIWYG')
@@ -141,73 +157,97 @@ Capybara::SpecHelper.spec "node" do
141
157
  expect(@session.find(:css, '#existing_content_editable_child_parent').text).to eq("Some content\nWYSIWYG")
142
158
  end
143
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
144
172
  end
145
173
 
146
- describe "#tag_name" do
147
- it "should extract node tag name" do
174
+ describe '#tag_name' do
175
+ it 'should extract node tag name' do
148
176
  expect(@session.all('//a')[0].tag_name).to eq('a')
149
177
  expect(@session.all('//a')[1].tag_name).to eq('a')
150
178
  expect(@session.all('//p')[1].tag_name).to eq('p')
151
179
  end
152
180
  end
153
181
 
154
- describe "#disabled?" do
155
- it "should extract disabled node" do
182
+ describe '#disabled?' do
183
+ it 'should extract disabled node' do
156
184
  @session.visit('/form')
157
185
  expect(@session.find('//input[@id="customer_name"]')).to be_disabled
158
186
  expect(@session.find('//input[@id="customer_email"]')).not_to be_disabled
159
187
  end
160
188
 
161
- it "should see disabled options as disabled" do
189
+ it 'should see disabled options as disabled' do
162
190
  @session.visit('/form')
163
191
  expect(@session.find('//select[@id="form_title"]/option[1]')).not_to be_disabled
164
192
  expect(@session.find('//select[@id="form_title"]/option[@disabled]')).to be_disabled
165
193
  end
166
194
 
167
- it "should see enabled options in disabled select as disabled" do
195
+ it 'should see enabled options in disabled select as disabled' do
168
196
  @session.visit('/form')
169
197
  expect(@session.find('//select[@id="form_disabled_select"]/option')).to be_disabled
170
198
  expect(@session.find('//select[@id="form_disabled_select"]/optgroup/option')).to be_disabled
171
199
  expect(@session.find('//select[@id="form_title"]/option[1]')).not_to be_disabled
172
200
  end
173
201
 
174
- it "should see enabled options in disabled optgroup as disabled" do
202
+ it 'should see enabled options in disabled optgroup as disabled' do
203
+ @session.visit('/form')
204
+ expect(@session.find('//option', text: 'A.B.1')).to be_disabled
205
+ expect(@session.find('//option', text: 'A.2')).not_to be_disabled
206
+ end
207
+
208
+ it 'should see a disabled fieldset as disabled' do
175
209
  @session.visit('/form')
176
- expect(@session.find('//option', text: "A.B.1")).to be_disabled
177
- expect(@session.find('//option', text: "A.2")).not_to be_disabled
210
+ expect(@session.find(:xpath, './/fieldset[@id="form_disabled_fieldset"]')).to be_disabled
178
211
  end
179
212
 
180
- context "in a disabled fieldset" do
213
+ context 'in a disabled fieldset' do
181
214
  # https://html.spec.whatwg.org/#the-fieldset-element
182
- it "should see elements not in first legend as disabled" do
215
+ it 'should see elements not in first legend as disabled' do
183
216
  @session.visit('/form')
184
217
  expect(@session.find('//input[@id="form_disabled_fieldset_child"]')).to be_disabled
185
218
  expect(@session.find('//input[@id="form_disabled_fieldset_second_legend_child"]')).to be_disabled
186
219
  expect(@session.find('//input[@id="form_enabled_fieldset_child"]')).not_to be_disabled
187
220
  end
188
221
 
189
- it "should see elements in first legend as enabled" do
222
+ it 'should see elements in first legend as enabled' do
190
223
  @session.visit('/form')
191
224
  expect(@session.find('//input[@id="form_disabled_fieldset_legend_child"]')).not_to be_disabled
192
225
  end
193
226
 
194
- it "should sees options not in first legend as disabled" do
227
+ it 'should sees options not in first legend as disabled' do
195
228
  @session.visit('/form')
196
229
  expect(@session.find('//option', text: 'Disabled Child Option')).to be_disabled
197
230
  end
198
231
  end
199
232
 
200
- it "should be boolean" do
233
+ it 'should be boolean' do
201
234
  @session.visit('/form')
202
235
  expect(@session.find('//select[@id="form_disabled_select"]/option').disabled?).to be true
203
236
  expect(@session.find('//select[@id="form_disabled_select2"]/option').disabled?).to be true
204
237
  expect(@session.find('//select[@id="form_title"]/option[1]').disabled?).to be false
205
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
206
245
  end
207
246
 
208
- describe "#visible?" do
209
- it "should extract node visibility" do
210
- Capybara.ignore_hidden_elements = false
247
+ describe '#visible?' do
248
+ before { Capybara.ignore_hidden_elements = false }
249
+
250
+ it 'should extract node visibility' do
211
251
  expect(@session.first('//a')).to be_visible
212
252
 
213
253
  expect(@session.find('//div[@id="hidden"]')).not_to be_visible
@@ -217,22 +257,124 @@ Capybara::SpecHelper.spec "node" do
217
257
  expect(@session.find('//input[@id="hidden_input"]')).not_to be_visible
218
258
  end
219
259
 
220
- it "should be boolean" do
221
- 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
222
265
  expect(@session.first('//a').visible?).to be true
223
266
  expect(@session.find('//div[@id="hidden"]').visible?).to be false
224
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
225
294
  end
226
295
 
227
- describe "#checked?" do
228
- it "should extract node checked state" do
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
367
+ end
368
+
369
+ describe '#checked?' do
370
+ it 'should extract node checked state' do
229
371
  @session.visit('/form')
230
372
  expect(@session.find('//input[@id="gender_female"]')).to be_checked
231
373
  expect(@session.find('//input[@id="gender_male"]')).not_to be_checked
232
374
  expect(@session.first('//h1')).not_to be_checked
233
375
  end
234
376
 
235
- it "should be boolean" do
377
+ it 'should be boolean' do
236
378
  @session.visit('/form')
237
379
  expect(@session.find('//input[@id="gender_female"]').checked?).to be true
238
380
  expect(@session.find('//input[@id="gender_male"]').checked?).to be false
@@ -240,15 +382,15 @@ Capybara::SpecHelper.spec "node" do
240
382
  end
241
383
  end
242
384
 
243
- describe "#selected?" do
244
- it "should extract node selected state" do
385
+ describe '#selected?' do
386
+ it 'should extract node selected state' do
245
387
  @session.visit('/form')
246
388
  expect(@session.find('//option[@value="en"]')).to be_selected
247
389
  expect(@session.find('//option[@value="sv"]')).not_to be_selected
248
390
  expect(@session.first('//h1')).not_to be_selected
249
391
  end
250
392
 
251
- it "should be boolean" do
393
+ it 'should be boolean' do
252
394
  @session.visit('/form')
253
395
  expect(@session.find('//option[@value="en"]').selected?).to be true
254
396
  expect(@session.find('//option[@value="sv"]').selected?).to be false
@@ -256,33 +398,44 @@ Capybara::SpecHelper.spec "node" do
256
398
  end
257
399
  end
258
400
 
259
- describe "#==" do
260
- it "preserve object identity" do
261
- expect(@session.find('//h1') == @session.find('//h1')).to be true # rubocop:disable Lint/UselessComparison
262
- 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
263
405
  expect(@session.find('//h1').eql?(@session.find('//h1'))).to be false
264
406
  end
265
407
 
266
- it "returns false for unrelated object" do
267
- 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
268
410
  end
269
411
  end
270
412
 
271
- describe "#path" do
413
+ describe '#path' do
272
414
  # Testing for specific XPaths here doesn't make sense since there
273
415
  # are many that can refer to the same element
274
416
  before do
275
417
  @session.visit('/path')
276
418
  end
277
419
 
278
- it "returns xpath which points to itself" do
420
+ it 'returns xpath which points to itself' do
279
421
  element = @session.find(:link, 'Second Link')
280
422
  expect(@session.find(:xpath, element.path)).to eq(element)
281
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
282
435
  end
283
436
 
284
- describe "#trigger", requires: %i[js trigger] do
285
- 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
286
439
  @session.visit('/with_js')
287
440
  @session.find(:css, '#with_focus_event').trigger(:focus)
288
441
  expect(@session).to have_css('#focus_event_triggered')
@@ -290,52 +443,323 @@ Capybara::SpecHelper.spec "node" do
290
443
  end
291
444
 
292
445
  describe '#drag_to', requires: %i[js drag] do
293
- it "should drag and drop an object" do
446
+ it 'should drag and drop an object' do
294
447
  @session.visit('/with_js')
295
448
  element = @session.find('//div[@id="drag"]')
296
449
  target = @session.find('//div[@id="drop"]')
297
450
  element.drag_to(target)
298
- expect(@session.find('//div[contains(., "Dropped!")]')).not_to be_nil
451
+ expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
299
452
  end
300
453
 
301
- it "should drag and drop if scrolling is needed" do
454
+ it 'should drag and drop if scrolling is needed' do
302
455
  @session.visit('/with_js')
303
456
  element = @session.find('//div[@id="drag_scroll"]')
304
457
  target = @session.find('//div[@id="drop_scroll"]')
305
458
  element.drag_to(target)
306
- 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")]')
307
722
  end
308
723
  end
309
724
 
310
725
  describe '#hover', requires: [:hover] do
311
- it "should allow hovering on an element" do
726
+ it 'should allow hovering on an element' do
312
727
  @session.visit('/with_hover')
313
728
  expect(@session.find(:css, '.wrapper:not(.scroll_needed) .hidden_until_hover', visible: false)).not_to be_visible
314
729
  @session.find(:css, '.wrapper:not(.scroll_needed)').hover
315
730
  expect(@session.find(:css, '.wrapper:not(.scroll_needed) .hidden_until_hover', visible: false)).to be_visible
316
731
  end
317
732
 
318
- 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
319
734
  @session.visit('/with_hover')
320
735
  expect(@session.find(:css, '.wrapper.scroll_needed .hidden_until_hover', visible: false)).not_to be_visible
321
736
  @session.find(:css, '.wrapper.scroll_needed').hover
322
737
  expect(@session.find(:css, '.wrapper.scroll_needed .hidden_until_hover', visible: false)).to be_visible
323
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
324
748
  end
325
749
 
326
750
  describe '#click' do
327
- it "should not follow a link if no href" do
751
+ it 'should not follow a link if no href' do
328
752
  @session.find(:css, '#link_placeholder').click
329
753
  expect(@session.current_url).to match(%r{/with_html$})
330
754
  end
331
755
 
332
- 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
333
757
  @session.find(:css, '#link_blank_href').click
334
758
  sleep 1
335
759
  expect(@session).to have_current_path('/with_html')
336
760
  end
337
761
 
338
- it "should be able to check a checkbox" do
762
+ it 'should be able to check a checkbox' do
339
763
  @session.visit('form')
340
764
  cbox = @session.find(:checkbox, 'form_terms_of_use')
341
765
  expect(cbox).not_to be_checked
@@ -343,7 +767,7 @@ Capybara::SpecHelper.spec "node" do
343
767
  expect(cbox).to be_checked
344
768
  end
345
769
 
346
- it "should be able to uncheck a checkbox" do
770
+ it 'should be able to uncheck a checkbox' do
347
771
  @session.visit('/form')
348
772
  cbox = @session.find(:checkbox, 'form_pets_dog')
349
773
  expect(cbox).to be_checked
@@ -351,7 +775,7 @@ Capybara::SpecHelper.spec "node" do
351
775
  expect(cbox).not_to be_checked
352
776
  end
353
777
 
354
- it "should be able to select a radio button" do
778
+ it 'should be able to select a radio button' do
355
779
  @session.visit('/form')
356
780
  radio = @session.find(:radio_button, 'gender_male')
357
781
  expect(radio).not_to be_checked
@@ -359,182 +783,515 @@ Capybara::SpecHelper.spec "node" do
359
783
  expect(radio).to be_checked
360
784
  end
361
785
 
362
- it "should allow modifiers", requires: [:js] do
786
+ it 'should allow modifiers', requires: [:js] do
363
787
  @session.visit('/with_js')
364
788
  @session.find(:css, '#click-test').click(:shift)
365
789
  expect(@session).to have_link('Has been shift clicked')
366
790
  end
367
791
 
368
- it "should allow multiple modifiers", requires: [:js] do
792
+ it 'should allow multiple modifiers', requires: [:js] do
369
793
  @session.visit('with_js')
370
794
  @session.find(:css, '#click-test').click(:control, :alt, :meta, :shift)
371
795
  # Selenium with Chrome on OSX ctrl-click generates a right click so just verify all keys but not click type
372
- expect(@session).to have_link("alt control meta shift")
796
+ expect(@session).to have_link('alt control meta shift')
373
797
  end
374
798
 
375
- 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
376
801
  @session.visit('with_js')
377
802
  @session.find(:css, '#click-test').click(x: 5, y: 5)
378
803
  link = @session.find(:link, 'has-been-clicked')
379
- 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.-]+)$/)
380
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
381
806
  # integer/float conversions and rounding.
382
807
  expect(locations[:x].to_f).to be_within(1).of(5)
383
808
  expect(locations[:y].to_f).to be_within(1).of(5)
384
809
  end
385
810
 
386
- it "should be able to click a table row", requires: [:js] do
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
387
819
  @session.visit('/tables')
388
820
  tr = @session.find(:css, '#agent_table tr:first-child').click
389
821
  expect(tr).to have_css('label', text: 'Clicked')
390
822
  end
391
- end
392
823
 
393
- describe '#double_click', requires: [:js] do
394
- before do
395
- pending "selenium-webdriver/geckodriver doesn't generate double click event" if marionette_lt?(59, @session)
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) })
396
849
  end
397
850
 
398
- it "should double click an element" do
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
901
+ end
902
+
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
399
915
  @session.visit('/with_js')
400
916
  @session.find(:css, '#click-test').double_click
401
917
  expect(@session.find(:css, '#has-been-double-clicked')).to be_truthy
402
918
  end
403
919
 
404
- it "should allow modifiers", requires: [:js] do
920
+ it 'should allow modifiers', requires: [:js] do
405
921
  @session.visit('/with_js')
406
922
  @session.find(:css, '#click-test').double_click(:alt)
407
923
  expect(@session).to have_link('Has been alt double clicked')
408
924
  end
409
925
 
410
- 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
411
928
  @session.visit('with_js')
412
929
  @session.find(:css, '#click-test').double_click(x: 10, y: 5)
413
930
  link = @session.find(:link, 'has-been-double-clicked')
414
- 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.-]+)$/)
415
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
416
933
  # to integer/float conversions and rounding.
417
934
  expect(locations[:x].to_f).to be_within(1).of(10)
418
935
  expect(locations[:y].to_f).to be_within(1).of(5)
419
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
420
998
  end
421
999
 
422
1000
  describe '#right_click', requires: [:js] do
423
- it "should right click an element" do
1001
+ it 'should right click an element' do
424
1002
  @session.visit('/with_js')
425
1003
  @session.find(:css, '#click-test').right_click
426
1004
  expect(@session.find(:css, '#has-been-right-clicked')).to be_truthy
427
1005
  end
428
1006
 
429
- it "should allow modifiers", requires: [:js] do
1007
+ it 'should allow modifiers', requires: [:js] do
430
1008
  @session.visit('/with_js')
431
1009
  @session.find(:css, '#click-test').right_click(:meta)
432
1010
  expect(@session).to have_link('Has been meta right clicked')
433
1011
  end
434
1012
 
435
- 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
436
1015
  @session.visit('with_js')
437
1016
  @session.find(:css, '#click-test').right_click(x: 10, y: 10)
438
1017
  link = @session.find(:link, 'has-been-right-clicked')
439
- 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.-]+)$/)
440
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
441
1020
  # to integer/float conversions and rounding
442
1021
  expect(locations[:x].to_f).to be_within(1).of(10)
443
1022
  expect(locations[:y].to_f).to be_within(1).of(10)
444
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
445
1094
  end
446
1095
 
447
1096
  describe '#send_keys', requires: [:send_keys] do
448
- it "should send a string of keys to an element" do
1097
+ it 'should send a string of keys to an element' do
449
1098
  @session.visit('/form')
450
1099
  @session.find(:css, '#address1_city').send_keys('Oceanside')
451
1100
  expect(@session.find(:css, '#address1_city').value).to eq 'Oceanside'
452
1101
  end
453
1102
 
454
- it "should send special characters" do
455
- pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" if marionette?(@session)
1103
+ it 'should send special characters' do
456
1104
  @session.visit('/form')
457
1105
  @session.find(:css, '#address1_city').send_keys('Ocean', :space, 'sie', :left, 'd')
458
1106
  expect(@session.find(:css, '#address1_city').value).to eq 'Ocean side'
459
1107
  end
460
1108
 
461
- it "should allow for multiple simultaneous keys" do
462
- pending "selenium-webdriver/geckodriver doesn't support complex sets of characters" if marionette?(@session)
1109
+ it 'should allow for multiple simultaneous keys' do
463
1110
  @session.visit('/form')
464
1111
  @session.find(:css, '#address1_city').send_keys([:shift, 'o'], 'ceanside')
465
1112
  expect(@session.find(:css, '#address1_city').value).to eq 'Oceanside'
466
1113
  end
467
1114
 
468
- it "should generate key events", requires: %i[send_keys js] do
469
- 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
470
1122
  @session.visit('/with_js')
471
1123
  @session.find(:css, '#with-key-events').send_keys([:shift, 't'], [:shift, 'w'])
472
1124
  expect(@session.find(:css, '#key-events-output')).to have_text('keydown:16 keydown:84 keydown:16 keydown:87')
473
1125
  end
474
1126
  end
475
1127
 
476
- describe "#execute_script", requires: %i[js es_args] do
477
- it "should execute the given script in the context of the element and return nothing" do
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
478
1130
  @session.visit('/with_js')
479
1131
  expect(@session.find(:css, '#change').execute_script("this.textContent = 'Funky Doodle'")).to be_nil
480
1132
  expect(@session).to have_css('#change', text: 'Funky Doodle')
481
1133
  end
482
1134
 
483
- it "should pass arguments to the script" do
1135
+ it 'should pass arguments to the script' do
484
1136
  @session.visit('/with_js')
485
- @session.find(:css, '#change').execute_script("this.textContent = arguments[0]", "Doodle Funk")
1137
+ @session.find(:css, '#change').execute_script('this.textContent = arguments[0]', 'Doodle Funk')
486
1138
  expect(@session).to have_css('#change', text: 'Doodle Funk')
487
1139
  end
488
1140
  end
489
1141
 
490
- describe "#evaluate_script", requires: %i[js es_args] do
491
- it "should evaluate the given script in the context of the element and return whatever it produces" do
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
492
1144
  @session.visit('/with_js')
493
1145
  el = @session.find(:css, '#with_change_event')
494
- expect(el.evaluate_script("this.value")).to eq('default value')
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
495
1154
  end
496
1155
 
497
- it "should pass arguments to the script" do
1156
+ it 'should pass arguments to the script' do
498
1157
  @session.visit('/with_js')
499
- @session.find(:css, '#change').evaluate_script("this.textContent = arguments[0]", "Doodle Funk")
1158
+ @session.find(:css, '#change').evaluate_script('this.textContent = arguments[0]', 'Doodle Funk')
500
1159
  expect(@session).to have_css('#change', text: 'Doodle Funk')
501
1160
  end
502
1161
 
503
- it "should pass multiple arguments" do
1162
+ it 'should pass multiple arguments' do
504
1163
  @session.visit('/with_js')
505
1164
  change = @session.find(:css, '#change')
506
- expect(change.evaluate_script("arguments[0] + arguments[1]", 2, 3)).to eq 5
1165
+ expect(change.evaluate_script('arguments[0] + arguments[1]', 2, 3)).to eq 5
507
1166
  end
508
1167
 
509
- it "should support returning elements" do
1168
+ it 'should support returning elements' do
510
1169
  @session.visit('/with_js')
511
1170
  change = @session.find(:css, '#change') # ensure page has loaded and element is available
512
- el = change.evaluate_script("this")
1171
+ el = change.evaluate_script('this')
513
1172
  expect(el).to be_instance_of(Capybara::Node::Element)
514
1173
  expect(el).to eq(change)
515
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
516
1187
  end
517
1188
 
518
- describe "#evaluate_async_script", requires: %i[js es_args] do
519
- it "should evaluate the given script in the context of the element" do
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
520
1191
  @session.visit('/with_js')
521
1192
  el = @session.find(:css, '#with_change_event')
522
- expect(el.evaluate_async_script("arguments[0](this.value)")).to eq('default value')
1193
+ expect(el.evaluate_async_script('arguments[0](this.value)')).to eq('default value')
523
1194
  end
524
1195
 
525
- it "should support returning elements after asynchronous operation" do
1196
+ it 'should support returning elements after asynchronous operation' do
526
1197
  @session.visit('/with_js')
527
1198
  change = @session.find(:css, '#change') # ensure page has loaded and element is available
528
- el = change.evaluate_async_script("var cb = arguments[0]; setTimeout(function(el){ cb(el) }, 100, this)")
1199
+ el = change.evaluate_async_script('var cb = arguments[0]; setTimeout(function(el){ cb(el) }, 100, this)')
529
1200
  expect(el).to eq(change)
530
1201
  end
531
1202
  end
532
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
+
533
1266
  describe '#reload', requires: [:js] do
534
- 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
535
1290
  before { Capybara.automatic_reload = false }
536
1291
 
537
- it "should reload the current context of the node" do
1292
+ after { Capybara.automatic_reload = true }
1293
+
1294
+ it 'should reload the current context of the node' do
538
1295
  @session.visit('/with_js')
539
1296
  node = @session.find(:css, '#reload-me')
540
1297
  @session.click_link('Reload!')
@@ -543,7 +1300,7 @@ Capybara::SpecHelper.spec "node" do
543
1300
  expect(node.text).to eq('has been reloaded')
544
1301
  end
545
1302
 
546
- it "should reload a parent node" do
1303
+ it 'should reload a parent node' do
547
1304
  @session.visit('/with_js')
548
1305
  node = @session.find(:css, '#reload-me').find(:css, 'em')
549
1306
  @session.click_link('Reload!')
@@ -552,7 +1309,7 @@ Capybara::SpecHelper.spec "node" do
552
1309
  expect(node.text).to eq('has been reloaded')
553
1310
  end
554
1311
 
555
- it "should not automatically reload" do
1312
+ it 'should not automatically reload' do
556
1313
  @session.visit('/with_js')
557
1314
  node = @session.find(:css, '#reload-me')
558
1315
  @session.click_link('Reload!')
@@ -563,15 +1320,14 @@ Capybara::SpecHelper.spec "node" do
563
1320
  expect(error).to be_an_invalid_element_error(@session)
564
1321
  end)
565
1322
  end
566
- after { Capybara.automatic_reload = true }
567
1323
  end
568
1324
 
569
- context "with automatic reload" do
1325
+ context 'with automatic reload' do
570
1326
  before do
571
1327
  Capybara.default_max_wait_time = 4
572
1328
  end
573
1329
 
574
- it "should reload the current context of the node automatically" do
1330
+ it 'should reload the current context of the node automatically' do
575
1331
  @session.visit('/with_js')
576
1332
  node = @session.find(:css, '#reload-me')
577
1333
  @session.click_link('Reload!')
@@ -579,7 +1335,7 @@ Capybara::SpecHelper.spec "node" do
579
1335
  expect(node.text).to eq('has been reloaded')
580
1336
  end
581
1337
 
582
- it "should reload a parent node automatically" do
1338
+ it 'should reload a parent node automatically' do
583
1339
  @session.visit('/with_js')
584
1340
  node = @session.find(:css, '#reload-me').find(:css, 'em')
585
1341
  @session.click_link('Reload!')
@@ -587,7 +1343,7 @@ Capybara::SpecHelper.spec "node" do
587
1343
  expect(node.text).to eq('has been reloaded')
588
1344
  end
589
1345
 
590
- it "should reload a node automatically when using find" do
1346
+ it 'should reload a node automatically when using find' do
591
1347
  @session.visit('/with_js')
592
1348
  node = @session.find(:css, '#reload-me')
593
1349
  @session.click_link('Reload!')
@@ -609,9 +1365,9 @@ Capybara::SpecHelper.spec "node" do
609
1365
  end.to(raise_error { |error| expect(error).to be_an_invalid_element_error(@session) })
610
1366
  end
611
1367
 
612
- it "should reload nodes with options" do
1368
+ it 'should reload nodes with options' do
613
1369
  @session.visit('/with_js')
614
- node = @session.find(:css, 'em', text: "reloaded")
1370
+ node = @session.find(:css, 'em', text: 'reloaded')
615
1371
  @session.click_link('Reload!')
616
1372
  sleep(1)
617
1373
  expect(node.text).to eq('has been reloaded')
@@ -619,9 +1375,9 @@ Capybara::SpecHelper.spec "node" do
619
1375
  end
620
1376
  end
621
1377
 
622
- context "when #synchronize raises server errors" do
623
- it "sets an explanatory exception as the cause of server exceptions", requires: %i[server js] do
624
- 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') }
625
1381
  expect do
626
1382
  @session.find(:css, 'span')
627
1383
  end.to(raise_error(TestApp::TestAppError) do |e|
@@ -630,8 +1386,8 @@ Capybara::SpecHelper.spec "node" do
630
1386
  end)
631
1387
  end
632
1388
 
633
- it "sets an explanatory exception as the cause of server exceptions with errors with initializers", requires: %i[server js] do
634
- 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') }
635
1391
  expect do
636
1392
  @session.find(:css, 'span')
637
1393
  end.to(raise_error(TestApp::TestAppOtherError) do |e|
@@ -640,8 +1396,4 @@ Capybara::SpecHelper.spec "node" do
640
1396
  end)
641
1397
  end
642
1398
  end
643
-
644
- def be_an_invalid_element_error(session)
645
- satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } }
646
- end
647
1399
  end