capybara 3.3.0 → 3.40.0

Sign up to get free protection for your applications and to get access to all the features.
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