capybara 3.8.1 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +465 -0
  4. data/License.txt +1 -1
  5. data/README.md +58 -57
  6. data/lib/capybara/config.rb +10 -4
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +2 -2
  9. data/lib/capybara/driver/node.rb +26 -5
  10. data/lib/capybara/dsl.rb +12 -4
  11. data/lib/capybara/helpers.rb +8 -4
  12. data/lib/capybara/minitest/spec.rb +162 -85
  13. data/lib/capybara/minitest.rb +248 -148
  14. data/lib/capybara/node/actions.rb +149 -96
  15. data/lib/capybara/node/base.rb +27 -10
  16. data/lib/capybara/node/document.rb +12 -0
  17. data/lib/capybara/node/document_matchers.rb +9 -5
  18. data/lib/capybara/node/element.rb +254 -109
  19. data/lib/capybara/node/finders.rb +83 -76
  20. data/lib/capybara/node/matchers.rb +279 -141
  21. data/lib/capybara/node/simple.rb +25 -6
  22. data/lib/capybara/queries/ancestor_query.rb +5 -7
  23. data/lib/capybara/queries/base_query.rb +11 -5
  24. data/lib/capybara/queries/current_path_query.rb +3 -3
  25. data/lib/capybara/queries/match_query.rb +1 -0
  26. data/lib/capybara/queries/selector_query.rb +467 -103
  27. data/lib/capybara/queries/sibling_query.rb +5 -4
  28. data/lib/capybara/queries/style_query.rb +6 -2
  29. data/lib/capybara/queries/text_query.rb +17 -3
  30. data/lib/capybara/queries/title_query.rb +2 -2
  31. data/lib/capybara/rack_test/browser.rb +22 -15
  32. data/lib/capybara/rack_test/driver.rb +10 -1
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +33 -28
  35. data/lib/capybara/rack_test/node.rb +74 -6
  36. data/lib/capybara/registration_container.rb +44 -0
  37. data/lib/capybara/registrations/drivers.rb +36 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  39. data/lib/capybara/registrations/servers.rb +44 -0
  40. data/lib/capybara/result.rb +55 -23
  41. data/lib/capybara/rspec/features.rb +4 -4
  42. data/lib/capybara/rspec/matcher_proxies.rb +36 -15
  43. data/lib/capybara/rspec/matchers/base.rb +111 -0
  44. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  45. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  46. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  47. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  48. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  49. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  50. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  51. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  52. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  53. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  54. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  55. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  56. data/lib/capybara/rspec/matchers.rb +117 -311
  57. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  58. data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
  59. data/lib/capybara/selector/css.rb +17 -15
  60. data/lib/capybara/selector/definition/button.rb +52 -0
  61. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  62. data/lib/capybara/selector/definition/css.rb +10 -0
  63. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  64. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  65. data/lib/capybara/selector/definition/element.rb +27 -0
  66. data/lib/capybara/selector/definition/field.rb +40 -0
  67. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  68. data/lib/capybara/selector/definition/file_field.rb +13 -0
  69. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  70. data/lib/capybara/selector/definition/frame.rb +17 -0
  71. data/lib/capybara/selector/definition/id.rb +6 -0
  72. data/lib/capybara/selector/definition/label.rb +62 -0
  73. data/lib/capybara/selector/definition/link.rb +54 -0
  74. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  75. data/lib/capybara/selector/definition/option.rb +27 -0
  76. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  77. data/lib/capybara/selector/definition/select.rb +81 -0
  78. data/lib/capybara/selector/definition/table.rb +109 -0
  79. data/lib/capybara/selector/definition/table_row.rb +21 -0
  80. data/lib/capybara/selector/definition/xpath.rb +5 -0
  81. data/lib/capybara/selector/definition.rb +277 -0
  82. data/lib/capybara/selector/filter.rb +1 -0
  83. data/lib/capybara/selector/filter_set.rb +26 -19
  84. data/lib/capybara/selector/filters/base.rb +24 -5
  85. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  86. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  87. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  88. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  89. data/lib/capybara/selector/selector.rb +73 -367
  90. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  91. data/lib/capybara/selector.rb +221 -480
  92. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  93. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  94. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  95. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  96. data/lib/capybara/selenium/driver.rb +203 -86
  97. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +88 -14
  98. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  99. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  100. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  101. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  102. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  103. data/lib/capybara/selenium/extensions/find.rb +110 -0
  104. data/lib/capybara/selenium/extensions/html5_drag.rb +191 -22
  105. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  106. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  107. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  108. data/lib/capybara/selenium/node.rb +298 -93
  109. data/lib/capybara/selenium/nodes/chrome_node.rb +100 -8
  110. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  111. data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
  112. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  113. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  114. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  115. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  116. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  117. data/lib/capybara/selenium/patches/logs.rb +45 -0
  118. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
  119. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  120. data/lib/capybara/server/animation_disabler.rb +4 -3
  121. data/lib/capybara/server/checker.rb +6 -2
  122. data/lib/capybara/server/middleware.rb +23 -13
  123. data/lib/capybara/server.rb +30 -7
  124. data/lib/capybara/session/config.rb +14 -10
  125. data/lib/capybara/session/matchers.rb +11 -7
  126. data/lib/capybara/session.rb +152 -111
  127. data/lib/capybara/spec/public/offset.js +6 -0
  128. data/lib/capybara/spec/public/test.js +101 -10
  129. data/lib/capybara/spec/session/all_spec.rb +96 -6
  130. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  131. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +29 -0
  132. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  133. data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
  134. data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
  135. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  136. data/lib/capybara/spec/session/attach_file_spec.rb +63 -36
  137. data/lib/capybara/spec/session/check_spec.rb +10 -4
  138. data/lib/capybara/spec/session/choose_spec.rb +8 -2
  139. data/lib/capybara/spec/session/click_button_spec.rb +117 -61
  140. data/lib/capybara/spec/session/click_link_or_button_spec.rb +16 -0
  141. data/lib/capybara/spec/session/click_link_spec.rb +17 -6
  142. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  143. data/lib/capybara/spec/session/evaluate_script_spec.rb +13 -0
  144. data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
  145. data/lib/capybara/spec/session/fill_in_spec.rb +47 -6
  146. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  147. data/lib/capybara/spec/session/find_spec.rb +74 -4
  148. data/lib/capybara/spec/session/first_spec.rb +1 -1
  149. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +13 -1
  150. data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
  151. data/lib/capybara/spec/session/has_all_selectors_spec.rb +1 -1
  152. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  153. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  154. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  155. data/lib/capybara/spec/session/has_css_spec.rb +122 -12
  156. data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
  157. data/lib/capybara/spec/session/has_field_spec.rb +55 -0
  158. data/lib/capybara/spec/session/has_select_spec.rb +34 -6
  159. data/lib/capybara/spec/session/has_selector_spec.rb +11 -4
  160. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  161. data/lib/capybara/spec/session/has_table_spec.rb +166 -0
  162. data/lib/capybara/spec/session/has_text_spec.rb +48 -1
  163. data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
  164. data/lib/capybara/spec/session/html_spec.rb +7 -0
  165. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  166. data/lib/capybara/spec/session/node_spec.rb +643 -18
  167. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  168. data/lib/capybara/spec/session/refresh_spec.rb +4 -0
  169. data/lib/capybara/spec/session/reset_session_spec.rb +23 -8
  170. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  171. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  172. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  173. data/lib/capybara/spec/session/select_spec.rb +10 -10
  174. data/lib/capybara/spec/session/selectors_spec.rb +36 -5
  175. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  176. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  178. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -0
  179. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -0
  180. data/lib/capybara/spec/session/window/window_spec.rb +59 -58
  181. data/lib/capybara/spec/session/window/windows_spec.rb +4 -0
  182. data/lib/capybara/spec/session/within_spec.rb +23 -0
  183. data/lib/capybara/spec/spec_helper.rb +16 -6
  184. data/lib/capybara/spec/test_app.rb +28 -23
  185. data/lib/capybara/spec/views/animated.erb +49 -0
  186. data/lib/capybara/spec/views/form.erb +48 -7
  187. data/lib/capybara/spec/views/frame_child.erb +3 -2
  188. data/lib/capybara/spec/views/frame_one.erb +1 -0
  189. data/lib/capybara/spec/views/obscured.erb +47 -0
  190. data/lib/capybara/spec/views/offset.erb +32 -0
  191. data/lib/capybara/spec/views/react.erb +45 -0
  192. data/lib/capybara/spec/views/scroll.erb +20 -0
  193. data/lib/capybara/spec/views/spatial.erb +31 -0
  194. data/lib/capybara/spec/views/tables.erb +67 -0
  195. data/lib/capybara/spec/views/with_animation.erb +29 -1
  196. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  197. data/lib/capybara/spec/views/with_hover.erb +1 -0
  198. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  199. data/lib/capybara/spec/views/with_html.erb +32 -6
  200. data/lib/capybara/spec/views/with_js.erb +3 -1
  201. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  202. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  203. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  204. data/lib/capybara/version.rb +1 -1
  205. data/lib/capybara/window.rb +11 -11
  206. data/lib/capybara.rb +118 -111
  207. data/spec/basic_node_spec.rb +14 -3
  208. data/spec/capybara_spec.rb +29 -29
  209. data/spec/css_builder_spec.rb +101 -0
  210. data/spec/dsl_spec.rb +46 -21
  211. data/spec/filter_set_spec.rb +5 -5
  212. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  213. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  214. data/spec/minitest_spec.rb +18 -4
  215. data/spec/minitest_spec_spec.rb +59 -44
  216. data/spec/rack_test_spec.rb +117 -89
  217. data/spec/regexp_dissassembler_spec.rb +250 -0
  218. data/spec/result_spec.rb +51 -49
  219. data/spec/rspec/features_spec.rb +3 -0
  220. data/spec/rspec/shared_spec_matchers.rb +112 -97
  221. data/spec/rspec_spec.rb +35 -17
  222. data/spec/sauce_spec_chrome.rb +43 -0
  223. data/spec/selector_spec.rb +244 -28
  224. data/spec/selenium_spec_chrome.rb +125 -54
  225. data/spec/selenium_spec_chrome_remote.rb +26 -12
  226. data/spec/selenium_spec_edge.rb +23 -8
  227. data/spec/selenium_spec_firefox.rb +208 -0
  228. data/spec/selenium_spec_firefox_remote.rb +15 -18
  229. data/spec/selenium_spec_ie.rb +82 -13
  230. data/spec/selenium_spec_safari.rb +148 -0
  231. data/spec/server_spec.rb +118 -77
  232. data/spec/session_spec.rb +19 -3
  233. data/spec/shared_selenium_node.rb +83 -0
  234. data/spec/shared_selenium_session.rb +110 -65
  235. data/spec/spec_helper.rb +57 -9
  236. data/spec/xpath_builder_spec.rb +93 -0
  237. metadata +257 -17
  238. data/lib/capybara/rspec/compound.rb +0 -94
  239. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -49
  240. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -121
  241. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  242. data/spec/selenium_spec_marionette.rb +0 -172
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
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
+
3
7
  Capybara::SpecHelper.spec 'node' do
4
8
  before do
5
9
  @session.visit('/with_html')
@@ -21,9 +25,9 @@ Capybara::SpecHelper.spec 'node' do
21
25
 
22
26
  describe '#query_scope' do
23
27
  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)
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
 
@@ -105,12 +109,24 @@ Capybara::SpecHelper.spec 'node' do
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
111
116
 
112
- it 'should raise if the textarea is readonly' do
113
- expect { @session.first('//textarea[@readonly]').set('changed') }.to raise_error(Capybara::ReadOnlyElementError)
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
114
130
  end
115
131
 
116
132
  it 'should use global default options' do
@@ -179,7 +195,7 @@ Capybara::SpecHelper.spec 'node' do
179
195
 
180
196
  it 'should see a disabled fieldset as disabled' do
181
197
  @session.visit('/form')
182
- expect(@session.find(:css, '#form_disabled_fieldset')).to be_disabled
198
+ expect(@session.find(:xpath, './/fieldset[@id="form_disabled_fieldset"]')).to be_disabled
183
199
  end
184
200
 
185
201
  context 'in a disabled fieldset' do
@@ -217,8 +233,9 @@ Capybara::SpecHelper.spec 'node' do
217
233
  end
218
234
 
219
235
  describe '#visible?' do
236
+ before { Capybara.ignore_hidden_elements = false }
237
+
220
238
  it 'should extract node visibility' do
221
- Capybara.ignore_hidden_elements = false
222
239
  expect(@session.first('//a')).to be_visible
223
240
 
224
241
  expect(@session.find('//div[@id="hidden"]')).not_to be_visible
@@ -228,11 +245,113 @@ Capybara::SpecHelper.spec 'node' do
228
245
  expect(@session.find('//input[@id="hidden_input"]')).not_to be_visible
229
246
  end
230
247
 
248
+ it 'template elements should not be visible' do
249
+ expect(@session.find('//template')).not_to be_visible
250
+ end
251
+
231
252
  it 'should be boolean' do
232
- Capybara.ignore_hidden_elements = false
233
253
  expect(@session.first('//a').visible?).to be true
234
254
  expect(@session.find('//div[@id="hidden"]').visible?).to be false
235
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
282
+ end
283
+
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
236
355
  end
237
356
 
238
357
  describe '#checked?' do
@@ -324,6 +443,241 @@ Capybara::SpecHelper.spec 'node' do
324
443
  link.drag_to target
325
444
  expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
326
445
  end
446
+
447
+ it 'should work with Dragula' do
448
+ @session.visit('/with_dragula')
449
+ @session.within(:css, '#sortable.ready') do
450
+ src = @session.find('div', text: 'Item 1')
451
+ target = @session.find('div', text: 'Item 3')
452
+ src.drag_to target
453
+ expect(@session).to have_content(/Item 2.*Item 1/, normalize_ws: true)
454
+ end
455
+ end
456
+
457
+ it 'should work with jsTree' do
458
+ @session.visit('/with_jstree')
459
+ @session.within(:css, '#container') do
460
+ @session.assert_text(/A.*B.*C/m)
461
+ source = @session.find(:css, '#j1_1_anchor')
462
+ target = @session.find(:css, '#j1_2_anchor')
463
+
464
+ source.drag_to(target)
465
+
466
+ @session.assert_no_text(/A.*B.*C/m)
467
+ @session.assert_text(/B.*C/m)
468
+ end
469
+ end
470
+
471
+ it 'should simulate a single held down modifier key' do
472
+ %I[
473
+ alt
474
+ ctrl
475
+ meta
476
+ shift
477
+ ].each do |modifier_key|
478
+ @session.visit('/with_js')
479
+
480
+ element = @session.find('//div[@id="drag"]')
481
+ target = @session.find('//div[@id="drop"]')
482
+
483
+ element.drag_to(target, drop_modifiers: modifier_key)
484
+ expect(@session).to have_css('div.drag_start', exact_text: 'Dragged!')
485
+ expect(@session).to have_xpath("//div[contains(., 'Dropped!-#{modifier_key}')]")
486
+ end
487
+ end
488
+
489
+ it 'should simulate multiple held down modifier keys' do
490
+ @session.visit('/with_js')
491
+
492
+ element = @session.find('//div[@id="drag"]')
493
+ target = @session.find('//div[@id="drop"]')
494
+
495
+ modifier_keys = %I[alt ctrl meta shift]
496
+
497
+ element.drag_to(target, drop_modifiers: modifier_keys)
498
+ expect(@session).to have_xpath("//div[contains(., 'Dropped!-#{modifier_keys.join('-')}')]")
499
+ end
500
+
501
+ it 'should support key aliases' do
502
+ { control: :ctrl,
503
+ command: :meta,
504
+ cmd: :meta }.each do |(key_alias, key)|
505
+ @session.visit('/with_js')
506
+
507
+ element = @session.find('//div[@id="drag"]')
508
+ target = @session.find('//div[@id="drop"]')
509
+
510
+ element.drag_to(target, drop_modifiers: [key_alias])
511
+ expect(target).to have_text("Dropped!-#{key}", exact: true)
512
+ end
513
+ end
514
+
515
+ context 'HTML5', requires: %i[js html5_drag] do
516
+ it 'should HTML5 drag and drop an object' do
517
+ @session.visit('/with_js')
518
+ element = @session.find('//div[@id="drag_html5"]')
519
+ target = @session.find('//div[@id="drop_html5"]')
520
+ element.drag_to(target)
521
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5")]')
522
+ end
523
+
524
+ it 'should HTML5 drag and drop an object child' do
525
+ @session.visit('/with_js')
526
+ element = @session.find('//div[@id="drag_html5"]/p')
527
+ target = @session.find('//div[@id="drop_html5"]')
528
+ element.drag_to(target)
529
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5")]')
530
+ end
531
+
532
+ it 'should set clientX/Y in dragover events' do
533
+ @session.visit('/with_js')
534
+ element = @session.find('//div[@id="drag_html5"]')
535
+ target = @session.find('//div[@id="drop_html5"]')
536
+ element.drag_to(target)
537
+ expect(@session).to have_css('div.log', text: /DragOver with client position: [1-9]\d*,[1-9]\d*/, count: 2)
538
+ end
539
+
540
+ it 'should preserve clientX/Y from last dragover event' do
541
+ @session.visit('/with_js')
542
+ element = @session.find('//div[@id="drag_html5"]')
543
+ target = @session.find('//div[@id="drop_html5"]')
544
+ element.drag_to(target)
545
+
546
+ conditions = %w[DragLeave Drop DragEnd].map do |text|
547
+ have_css('div.log', text: text)
548
+ end
549
+ expect(@session).to(conditions.reduce { |memo, cond| memo.and(cond) })
550
+
551
+ # The first "DragOver" div is inserted by the last dragover event dispatched
552
+ drag_over_div = @session.first('//div[@class="log" and starts-with(text(), "DragOver")]')
553
+ position = drag_over_div.text.sub('DragOver ', '')
554
+
555
+ expect(@session).to have_css('div.log', text: /DragLeave #{position}/, count: 1)
556
+ expect(@session).to have_css('div.log', text: /Drop #{position}/, count: 1)
557
+ expect(@session).to have_css('div.log', text: /DragEnd #{position}/, count: 1)
558
+ end
559
+
560
+ it 'should not HTML5 drag and drop on a non HTML5 drop element' do
561
+ @session.visit('/with_js')
562
+ element = @session.find('//div[@id="drag_html5"]')
563
+ target = @session.find('//div[@id="drop_html5"]')
564
+ target.execute_script("$(this).removeClass('drop');")
565
+ element.drag_to(target)
566
+ sleep 1
567
+ expect(@session).not_to have_xpath('//div[contains(., "HTML5 Dropped")]')
568
+ end
569
+
570
+ it 'should HTML5 drag and drop when scrolling needed' do
571
+ @session.visit('/with_js')
572
+ element = @session.find('//div[@id="drag_html5_scroll"]')
573
+ target = @session.find('//div[@id="drop_html5_scroll"]')
574
+ element.drag_to(target)
575
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5_scroll")]')
576
+ end
577
+
578
+ it 'should drag HTML5 default draggable elements' do
579
+ @session.visit('/with_js')
580
+ link = @session.find_link('drag_link_html5')
581
+ target = @session.find(:id, 'drop_html5')
582
+ link.drag_to target
583
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
584
+ end
585
+
586
+ it 'should work with SortableJS' do
587
+ @session.visit('/with_sortable_js')
588
+ @session.within(:css, '#sortable') do
589
+ src = @session.find('div', text: 'Item 1')
590
+ target = @session.find('div', text: 'Item 3')
591
+ src.drag_to target
592
+ expect(@session).to have_content(/Item 3.*Item 1/, normalize_ws: true)
593
+ end
594
+ end
595
+
596
+ it 'should drag HTML5 default draggable element child' do
597
+ @session.visit('/with_js')
598
+ source = @session.find_link('drag_link_html5').find(:css, 'p')
599
+ target = @session.find(:id, 'drop_html5')
600
+ source.drag_to target
601
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
602
+ end
603
+
604
+ it 'should simulate a single held down modifier key' do
605
+ %I[alt ctrl meta shift].each do |modifier_key|
606
+ @session.visit('/with_js')
607
+
608
+ element = @session.find('//div[@id="drag_html5"]')
609
+ target = @session.find('//div[@id="drop_html5"]')
610
+
611
+ element.drag_to(target, drop_modifiers: modifier_key)
612
+
613
+ expect(@session).to have_css('div.drag_start', exact_text: 'HTML5 Dragged!')
614
+ expect(@session).to have_xpath("//div[contains(., 'HTML5 Dropped string: text/plain drag_html5-#{modifier_key}')]")
615
+ end
616
+ end
617
+
618
+ it 'should simulate multiple held down modifier keys' do
619
+ @session.visit('/with_js')
620
+
621
+ element = @session.find('//div[@id="drag_html5"]')
622
+ target = @session.find('//div[@id="drop_html5"]')
623
+
624
+ modifier_keys = %I[alt ctrl meta shift]
625
+
626
+ element.drag_to(target, drop_modifiers: modifier_keys)
627
+ expect(@session).to have_xpath("//div[contains(., 'HTML5 Dropped string: text/plain drag_html5-#{modifier_keys.join('-')}')]")
628
+ end
629
+
630
+ it 'should support key aliases' do
631
+ { control: :ctrl,
632
+ command: :meta,
633
+ cmd: :meta }.each do |(key_alias, key)|
634
+ @session.visit('/with_js')
635
+
636
+ element = @session.find('//div[@id="drag_html5"]')
637
+ target = @session.find('//div[@id="drop_html5"]')
638
+
639
+ element.drag_to(target, drop_modifiers: [key_alias])
640
+ expect(target).to have_text(%r{^HTML5 Dropped string: text/plain drag_html5-#{key}$}m, exact: true)
641
+ end
642
+ end
643
+ end
644
+ end
645
+
646
+ describe 'Element#drop', requires: %i[js html5_drag] do
647
+ it 'can drop a file' do
648
+ @session.visit('/with_js')
649
+ target = @session.find('//div[@id="drop_html5"]')
650
+ target.drop(
651
+ with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__)))
652
+ )
653
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
654
+ end
655
+
656
+ it 'can drop multiple files' do
657
+ @session.visit('/with_js')
658
+ target = @session.find('//div[@id="drop_html5"]')
659
+ target.drop(
660
+ with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__))),
661
+ with_os_path_separators(File.expand_path('../fixtures/test_file.txt', File.dirname(__FILE__)))
662
+ )
663
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
664
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: test_file.txt")]')
665
+ end
666
+
667
+ it 'can drop strings' do
668
+ @session.visit('/with_js')
669
+ target = @session.find('//div[@id="drop_html5"]')
670
+ target.drop('text/plain' => 'Some dropped text')
671
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
672
+ end
673
+
674
+ it 'can drop multiple strings' do
675
+ @session.visit('/with_js')
676
+ target = @session.find('//div[@id="drop_html5"]')
677
+ target.drop('text/plain' => 'Some dropped text', 'text/url' => 'http://www.google.com')
678
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
679
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/url http://www.google.com")]')
680
+ end
327
681
  end
328
682
 
329
683
  describe '#hover', requires: [:hover] do
@@ -340,6 +694,15 @@ Capybara::SpecHelper.spec 'node' do
340
694
  @session.find(:css, '.wrapper.scroll_needed').hover
341
695
  expect(@session.find(:css, '.wrapper.scroll_needed .hidden_until_hover', visible: false)).to be_visible
342
696
  end
697
+
698
+ it 'should hover again after following a link and back' do
699
+ @session.visit('/with_hover')
700
+ @session.find(:css, '.wrapper:not(.scroll_needed)').hover
701
+ @session.click_link('Other hover page')
702
+ @session.click_link('Go back')
703
+ @session.find(:css, '.wrapper:not(.scroll_needed)').hover
704
+ expect(@session.find(:css, '.wrapper:not(.scroll_needed) .hidden_until_hover', visible: false)).to be_visible
705
+ end
343
706
  end
344
707
 
345
708
  describe '#click' do
@@ -395,18 +758,113 @@ Capybara::SpecHelper.spec 'node' do
395
758
  @session.visit('with_js')
396
759
  @session.find(:css, '#click-test').click(x: 5, y: 5)
397
760
  link = @session.find(:link, 'has-been-clicked')
398
- locations = link.text.match(/^Has been clicked at (?<x>[\d\.-]+),(?<y>[\d\.-]+)$/)
761
+ locations = link.text.match(/^Has been clicked at (?<x>[\d.-]+),(?<y>[\d.-]+)$/)
399
762
  # 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
400
763
  # integer/float conversions and rounding.
401
764
  expect(locations[:x].to_f).to be_within(1).of(5)
402
765
  expect(locations[:y].to_f).to be_within(1).of(5)
403
766
  end
404
767
 
768
+ it 'should raise error if both x and y values are not passed' do
769
+ @session.visit('with_js')
770
+ el = @session.find(:css, '#click-test')
771
+ expect { el.click(x: 5) }.to raise_error ArgumentError
772
+ expect { el.click(x: nil, y: 3) }.to raise_error ArgumentError
773
+ end
774
+
405
775
  it 'should be able to click a table row', requires: [:js] do
406
776
  @session.visit('/tables')
407
777
  tr = @session.find(:css, '#agent_table tr:first-child').click
408
778
  expect(tr).to have_css('label', text: 'Clicked')
409
779
  end
780
+
781
+ it 'should retry clicking', requires: [:js] do
782
+ @session.visit('/animated')
783
+ obscured = @session.find(:css, '#obscured')
784
+ @session.execute_script <<~JS
785
+ setTimeout(function(){ $('#cover').hide(); }, 700)
786
+ JS
787
+ expect { obscured.click }.not_to raise_error
788
+ end
789
+
790
+ it 'should allow to retry longer', requires: [:js] do
791
+ @session.visit('/animated')
792
+ obscured = @session.find(:css, '#obscured')
793
+ @session.execute_script <<~JS
794
+ setTimeout(function(){ $('#cover').hide(); }, 3000)
795
+ JS
796
+ expect { obscured.click(wait: 4) }.not_to raise_error
797
+ end
798
+
799
+ it 'should not retry clicking when wait is disabled', requires: [:js] do
800
+ @session.visit('/animated')
801
+ obscured = @session.find(:css, '#obscured')
802
+ @session.execute_script <<~JS
803
+ setTimeout(function(){ $('#cover').hide(); }, 2000)
804
+ JS
805
+ expect { obscured.click(wait: 0) }.to(raise_error { |e| expect(e).to be_an_invalid_element_error(@session) })
806
+ end
807
+
808
+ context 'offset', requires: [:js] do
809
+ before do
810
+ @session.visit('/offset')
811
+ end
812
+
813
+ let :clicker do
814
+ @session.find(:id, 'clicker')
815
+ end
816
+
817
+ context 'when w3c_click_offset is false' do
818
+ before do
819
+ Capybara.w3c_click_offset = false
820
+ end
821
+
822
+ it 'should offset from top left of element' do
823
+ clicker.click(x: 10, y: 5)
824
+ expect(@session).to have_text(/clicked at 110,105/)
825
+ end
826
+
827
+ it 'should offset outside the element' do
828
+ clicker.click(x: -15, y: -10)
829
+ expect(@session).to have_text(/clicked at 85,90/)
830
+ end
831
+
832
+ it 'should default to click the middle' do
833
+ clicker.click
834
+ expect(@session).to have_text(/clicked at 150,150/)
835
+ end
836
+ end
837
+
838
+ context 'when w3c_click_offset is true' do
839
+ before do
840
+ Capybara.w3c_click_offset = true
841
+ end
842
+
843
+ it 'should offset from center of element' do
844
+ clicker.click(x: 10, y: 5)
845
+ expect(@session).to have_text(/clicked at 160,155/)
846
+ end
847
+
848
+ it 'should offset outside from center of element' do
849
+ clicker.click(x: -65, y: -60)
850
+ expect(@session).to have_text(/clicked at 85,90/)
851
+ end
852
+
853
+ it 'should default to click the middle' do
854
+ clicker.click
855
+ expect(@session).to have_text(/clicked at 150,150/)
856
+ end
857
+ end
858
+ end
859
+
860
+ context 'delay', requires: [:js] do
861
+ it 'should delay the mouse up' do
862
+ @session.visit('with_js')
863
+ @session.find(:css, '#click-test').click(delay: 2)
864
+ delay = @session.evaluate_script('window.click_delay')
865
+ expect(delay).to be >= 2
866
+ end
867
+ end
410
868
  end
411
869
 
412
870
  describe '#double_click', requires: [:js] do
@@ -426,12 +884,73 @@ Capybara::SpecHelper.spec 'node' do
426
884
  @session.visit('with_js')
427
885
  @session.find(:css, '#click-test').double_click(x: 10, y: 5)
428
886
  link = @session.find(:link, 'has-been-double-clicked')
429
- locations = link.text.match(/^Has been double clicked at (?<x>[\d\.-]+),(?<y>[\d\.-]+)$/)
887
+ locations = link.text.match(/^Has been double clicked at (?<x>[\d.-]+),(?<y>[\d.-]+)$/)
430
888
  # Resulting click location should be very close to 10, 5 relative to top left corner of the element, but may not be exact due
431
889
  # to integer/float conversions and rounding.
432
890
  expect(locations[:x].to_f).to be_within(1).of(10)
433
891
  expect(locations[:y].to_f).to be_within(1).of(5)
434
892
  end
893
+
894
+ it 'should retry clicking', requires: [:js] do
895
+ @session.visit('/animated')
896
+ obscured = @session.find(:css, '#obscured')
897
+ @session.execute_script <<~JS
898
+ setTimeout(function(){ $('#cover').hide(); }, 700)
899
+ JS
900
+ expect { obscured.double_click }.not_to raise_error
901
+ end
902
+
903
+ context 'offset', requires: [:js] do
904
+ before do
905
+ @session.visit('/offset')
906
+ end
907
+
908
+ let :clicker do
909
+ @session.find(:id, 'clicker')
910
+ end
911
+
912
+ context 'when w3c_click_offset is false' do
913
+ before do
914
+ Capybara.w3c_click_offset = false
915
+ end
916
+
917
+ it 'should offset from top left of element' do
918
+ clicker.double_click(x: 10, y: 5)
919
+ expect(@session).to have_text(/clicked at 110,105/)
920
+ end
921
+
922
+ it 'should offset outside the element' do
923
+ clicker.double_click(x: -15, y: -10)
924
+ expect(@session).to have_text(/clicked at 85,90/)
925
+ end
926
+
927
+ it 'should default to click the middle' do
928
+ clicker.double_click
929
+ expect(@session).to have_text(/clicked at 150,150/)
930
+ end
931
+ end
932
+
933
+ context 'when w3c_click_offset is true' do
934
+ before do
935
+ Capybara.w3c_click_offset = true
936
+ end
937
+
938
+ it 'should offset from center of element' do
939
+ clicker.double_click(x: 10, y: 5)
940
+ expect(@session).to have_text(/clicked at 160,155/)
941
+ end
942
+
943
+ it 'should offset outside from center of element' do
944
+ clicker.double_click(x: -65, y: -60)
945
+ expect(@session).to have_text(/clicked at 85,90/)
946
+ end
947
+
948
+ it 'should default to click the middle' do
949
+ clicker.double_click
950
+ expect(@session).to have_text(/clicked at 150,150/)
951
+ end
952
+ end
953
+ end
435
954
  end
436
955
 
437
956
  describe '#right_click', requires: [:js] do
@@ -451,12 +970,82 @@ Capybara::SpecHelper.spec 'node' do
451
970
  @session.visit('with_js')
452
971
  @session.find(:css, '#click-test').right_click(x: 10, y: 10)
453
972
  link = @session.find(:link, 'has-been-right-clicked')
454
- locations = link.text.match(/^Has been right clicked at (?<x>[\d\.-]+),(?<y>[\d\.-]+)$/)
973
+ locations = link.text.match(/^Has been right clicked at (?<x>[\d.-]+),(?<y>[\d.-]+)$/)
455
974
  # Resulting click location should be very close to 10, 10 relative to top left corner of the element, but may not be exact due
456
975
  # to integer/float conversions and rounding
457
976
  expect(locations[:x].to_f).to be_within(1).of(10)
458
977
  expect(locations[:y].to_f).to be_within(1).of(10)
459
978
  end
979
+
980
+ it 'should retry clicking', requires: [:js] do
981
+ @session.visit('/animated')
982
+ obscured = @session.find(:css, '#obscured')
983
+ @session.execute_script <<~JS
984
+ setTimeout(function(){ $('#cover').hide(); }, 700)
985
+ JS
986
+ expect { obscured.right_click }.not_to raise_error
987
+ end
988
+
989
+ context 'offset', requires: [:js] do
990
+ before do
991
+ @session.visit('/offset')
992
+ end
993
+
994
+ let :clicker do
995
+ @session.find(:id, 'clicker')
996
+ end
997
+
998
+ context 'when w3c_click_offset is false' do
999
+ before do
1000
+ Capybara.w3c_click_offset = false
1001
+ end
1002
+
1003
+ it 'should offset from top left of element' do
1004
+ clicker.right_click(x: 10, y: 5)
1005
+ expect(@session).to have_text(/clicked at 110,105/)
1006
+ end
1007
+
1008
+ it 'should offset outside the element' do
1009
+ clicker.right_click(x: -15, y: -10)
1010
+ expect(@session).to have_text(/clicked at 85,90/)
1011
+ end
1012
+
1013
+ it 'should default to click the middle' do
1014
+ clicker.right_click
1015
+ expect(@session).to have_text(/clicked at 150,150/)
1016
+ end
1017
+ end
1018
+
1019
+ context 'when w3c_click_offset is true' do
1020
+ before do
1021
+ Capybara.w3c_click_offset = true
1022
+ end
1023
+
1024
+ it 'should offset from center of element' do
1025
+ clicker.right_click(x: 10, y: 5)
1026
+ expect(@session).to have_text(/clicked at 160,155/)
1027
+ end
1028
+
1029
+ it 'should offset outside from center of element' do
1030
+ clicker.right_click(x: -65, y: -60)
1031
+ expect(@session).to have_text(/clicked at 85,90/)
1032
+ end
1033
+
1034
+ it 'should default to click the middle' do
1035
+ clicker.right_click
1036
+ expect(@session).to have_text(/clicked at 150,150/)
1037
+ end
1038
+ end
1039
+ end
1040
+
1041
+ context 'delay', requires: [:js] do
1042
+ it 'should delay the mouse up' do
1043
+ @session.visit('with_js')
1044
+ @session.find(:css, '#click-test').right_click(delay: 2)
1045
+ delay = @session.evaluate_script('window.right_click_delay')
1046
+ expect(delay).to be >= 2
1047
+ end
1048
+ end
460
1049
  end
461
1050
 
462
1051
  describe '#send_keys', requires: [:send_keys] do
@@ -478,6 +1067,12 @@ Capybara::SpecHelper.spec 'node' do
478
1067
  expect(@session.find(:css, '#address1_city').value).to eq 'Oceanside'
479
1068
  end
480
1069
 
1070
+ it 'should hold modifiers at top level' do
1071
+ @session.visit('/form')
1072
+ @session.find(:css, '#address1_city').send_keys('ocean', :shift, 'side')
1073
+ expect(@session.find(:css, '#address1_city').value).to eq 'oceanSIDE'
1074
+ end
1075
+
481
1076
  it 'should generate key events', requires: %i[send_keys js] do
482
1077
  @session.visit('/with_js')
483
1078
  @session.find(:css, '#with-key-events').send_keys([:shift, 't'], [:shift, 'w'])
@@ -532,6 +1127,18 @@ Capybara::SpecHelper.spec 'node' do
532
1127
  expect(el).to be_instance_of(Capybara::Node::Element)
533
1128
  expect(el).to eq(change)
534
1129
  end
1130
+
1131
+ it 'should support multiple statements via IIFE' do
1132
+ @session.visit('/with_js')
1133
+ change = @session.find(:css, '#change') # ensure page has loaded and element is available
1134
+ res = change.evaluate_script(<<~JS, 3)
1135
+ (function(n){
1136
+ var el = this;
1137
+ return [el, n];
1138
+ }).apply(this, arguments)
1139
+ JS
1140
+ expect(res).to eq [change, 3]
1141
+ end
535
1142
  end
536
1143
 
537
1144
  describe '#evaluate_async_script', requires: %i[js es_args] do
@@ -550,6 +1157,28 @@ Capybara::SpecHelper.spec 'node' do
550
1157
  end
551
1158
 
552
1159
  describe '#reload', requires: [:js] do
1160
+ it 'should reload elements found via ancestor with CSS' do
1161
+ @session.visit('/with_js')
1162
+ node = @session.find(:css, '#reload-me em').ancestor(:css, 'div')
1163
+ node.reload
1164
+ expect(node[:id]).to eq 'reload-me'
1165
+ end
1166
+
1167
+ it 'should reload elements found via ancestor with XPath' do
1168
+ @session.visit('/with_js')
1169
+ node = @session.find(:css, '#reload-me em').ancestor(:xpath, './/div')
1170
+ node.reload
1171
+ expect(node[:id]).to eq 'reload-me'
1172
+ end
1173
+
1174
+ it 'should reload elements found via sibling' do
1175
+ @session.visit('/with_js')
1176
+ node = @session.find(:css, '#the-list li', text: 'Item 1').sibling(:css, 'li')
1177
+ expect(node.text).to eq 'Item 2'
1178
+ node.reload
1179
+ expect(node.text).to eq 'Item 2'
1180
+ end
1181
+
553
1182
  context 'without automatic reload' do
554
1183
  before { Capybara.automatic_reload = false }
555
1184
 
@@ -660,8 +1289,4 @@ Capybara::SpecHelper.spec 'node' do
660
1289
  end)
661
1290
  end
662
1291
  end
663
-
664
- def be_an_invalid_element_error(session)
665
- satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } }
666
- end
667
1292
  end