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