capybara 3.23.0 → 3.35.3

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +264 -11
  3. data/README.md +10 -6
  4. data/lib/capybara.rb +20 -8
  5. data/lib/capybara/config.rb +10 -8
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +4 -0
  8. data/lib/capybara/driver/node.rb +4 -0
  9. data/lib/capybara/dsl.rb +10 -2
  10. data/lib/capybara/helpers.rb +28 -2
  11. data/lib/capybara/minitest.rb +232 -144
  12. data/lib/capybara/minitest/spec.rb +156 -97
  13. data/lib/capybara/node/actions.rb +36 -36
  14. data/lib/capybara/node/base.rb +6 -6
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +3 -3
  17. data/lib/capybara/node/element.rb +77 -33
  18. data/lib/capybara/node/finders.rb +24 -17
  19. data/lib/capybara/node/matchers.rb +79 -64
  20. data/lib/capybara/node/simple.rb +11 -4
  21. data/lib/capybara/queries/ancestor_query.rb +6 -10
  22. data/lib/capybara/queries/base_query.rb +2 -1
  23. data/lib/capybara/queries/current_path_query.rb +14 -4
  24. data/lib/capybara/queries/selector_query.rb +259 -23
  25. data/lib/capybara/queries/sibling_query.rb +5 -11
  26. data/lib/capybara/queries/style_query.rb +1 -1
  27. data/lib/capybara/queries/text_query.rb +13 -1
  28. data/lib/capybara/rack_test/browser.rb +13 -4
  29. data/lib/capybara/rack_test/driver.rb +2 -1
  30. data/lib/capybara/rack_test/form.rb +2 -2
  31. data/lib/capybara/rack_test/node.rb +42 -6
  32. data/lib/capybara/registration_container.rb +44 -0
  33. data/lib/capybara/registrations/drivers.rb +18 -12
  34. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  35. data/lib/capybara/registrations/servers.rb +9 -2
  36. data/lib/capybara/result.rb +39 -19
  37. data/lib/capybara/rspec.rb +2 -0
  38. data/lib/capybara/rspec/matcher_proxies.rb +5 -5
  39. data/lib/capybara/rspec/matchers.rb +97 -74
  40. data/lib/capybara/rspec/matchers/base.rb +19 -6
  41. data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
  42. data/lib/capybara/rspec/matchers/have_ancestor.rb +5 -7
  43. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  44. data/lib/capybara/rspec/matchers/have_selector.rb +15 -10
  45. data/lib/capybara/rspec/matchers/have_sibling.rb +4 -7
  46. data/lib/capybara/rspec/matchers/have_text.rb +4 -7
  47. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  48. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  49. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  50. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  51. data/lib/capybara/selector.rb +46 -19
  52. data/lib/capybara/selector/builders/css_builder.rb +10 -6
  53. data/lib/capybara/selector/builders/xpath_builder.rb +4 -2
  54. data/lib/capybara/selector/css.rb +1 -1
  55. data/lib/capybara/selector/definition.rb +13 -11
  56. data/lib/capybara/selector/definition/button.rb +32 -15
  57. data/lib/capybara/selector/definition/checkbox.rb +2 -2
  58. data/lib/capybara/selector/definition/css.rb +3 -1
  59. data/lib/capybara/selector/definition/datalist_input.rb +2 -2
  60. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  61. data/lib/capybara/selector/definition/element.rb +3 -2
  62. data/lib/capybara/selector/definition/field.rb +1 -1
  63. data/lib/capybara/selector/definition/file_field.rb +1 -1
  64. data/lib/capybara/selector/definition/fillable_field.rb +2 -2
  65. data/lib/capybara/selector/definition/label.rb +5 -3
  66. data/lib/capybara/selector/definition/link.rb +8 -0
  67. data/lib/capybara/selector/definition/option.rb +1 -1
  68. data/lib/capybara/selector/definition/radio_button.rb +2 -2
  69. data/lib/capybara/selector/definition/select.rb +33 -14
  70. data/lib/capybara/selector/definition/table.rb +6 -3
  71. data/lib/capybara/selector/definition/table_row.rb +2 -2
  72. data/lib/capybara/selector/filter_set.rb +13 -11
  73. data/lib/capybara/selector/filters/base.rb +6 -1
  74. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  75. data/lib/capybara/selector/regexp_disassembler.rb +7 -0
  76. data/lib/capybara/selector/selector.rb +13 -3
  77. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  78. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -1
  79. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  80. data/lib/capybara/selenium/atoms/src/isDisplayed.js +10 -10
  81. data/lib/capybara/selenium/driver.rb +86 -24
  82. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +24 -21
  83. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +21 -19
  84. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +17 -1
  85. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +0 -4
  86. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  87. data/lib/capybara/selenium/extensions/find.rb +37 -26
  88. data/lib/capybara/selenium/extensions/html5_drag.rb +55 -11
  89. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  90. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  91. data/lib/capybara/selenium/logger_suppressor.rb +8 -2
  92. data/lib/capybara/selenium/node.rb +160 -40
  93. data/lib/capybara/selenium/nodes/chrome_node.rb +72 -12
  94. data/lib/capybara/selenium/nodes/edge_node.rb +32 -14
  95. data/lib/capybara/selenium/nodes/firefox_node.rb +28 -32
  96. data/lib/capybara/selenium/nodes/safari_node.rb +5 -29
  97. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  98. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  99. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  100. data/lib/capybara/selenium/patches/logs.rb +32 -7
  101. data/lib/capybara/server.rb +19 -3
  102. data/lib/capybara/server/animation_disabler.rb +8 -3
  103. data/lib/capybara/server/checker.rb +1 -1
  104. data/lib/capybara/server/middleware.rb +22 -10
  105. data/lib/capybara/session.rb +66 -40
  106. data/lib/capybara/session/config.rb +11 -3
  107. data/lib/capybara/session/matchers.rb +11 -11
  108. data/lib/capybara/spec/public/offset.js +6 -0
  109. data/lib/capybara/spec/public/test.js +75 -7
  110. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  111. data/lib/capybara/spec/session/all_spec.rb +60 -5
  112. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  113. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  114. data/lib/capybara/spec/session/check_spec.rb +6 -0
  115. data/lib/capybara/spec/session/click_button_spec.rb +16 -0
  116. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  117. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  118. data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
  119. data/lib/capybara/spec/session/find_spec.rb +55 -0
  120. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -0
  121. data/lib/capybara/spec/session/has_button_spec.rb +51 -0
  122. data/lib/capybara/spec/session/has_css_spec.rb +26 -4
  123. data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
  124. data/lib/capybara/spec/session/has_field_spec.rb +34 -0
  125. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  126. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  127. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  128. data/lib/capybara/spec/session/has_text_spec.rb +30 -0
  129. data/lib/capybara/spec/session/html_spec.rb +1 -1
  130. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  131. data/lib/capybara/spec/session/node_spec.rb +394 -9
  132. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  133. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  134. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  135. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -15
  136. data/lib/capybara/spec/session/selectors_spec.rb +16 -3
  137. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  138. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  139. data/lib/capybara/spec/session/window/window_spec.rb +8 -8
  140. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  141. data/lib/capybara/spec/spec_helper.rb +14 -14
  142. data/lib/capybara/spec/test_app.rb +27 -21
  143. data/lib/capybara/spec/views/form.erb +47 -4
  144. data/lib/capybara/spec/views/offset.erb +32 -0
  145. data/lib/capybara/spec/views/spatial.erb +31 -0
  146. data/lib/capybara/spec/views/with_animation.erb +37 -1
  147. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  148. data/lib/capybara/spec/views/with_html.erb +24 -2
  149. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  150. data/lib/capybara/spec/views/with_js.erb +4 -1
  151. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  152. data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
  153. data/lib/capybara/version.rb +1 -1
  154. data/lib/capybara/window.rb +3 -7
  155. data/spec/basic_node_spec.rb +15 -14
  156. data/spec/capybara_spec.rb +28 -28
  157. data/spec/dsl_spec.rb +16 -3
  158. data/spec/filter_set_spec.rb +5 -5
  159. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  160. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  161. data/spec/minitest_spec.rb +3 -2
  162. data/spec/minitest_spec_spec.rb +46 -46
  163. data/spec/rack_test_spec.rb +38 -15
  164. data/spec/regexp_dissassembler_spec.rb +52 -38
  165. data/spec/result_spec.rb +43 -32
  166. data/spec/rspec/features_spec.rb +4 -1
  167. data/spec/rspec/scenarios_spec.rb +4 -0
  168. data/spec/rspec/shared_spec_matchers.rb +68 -56
  169. data/spec/rspec_spec.rb +9 -5
  170. data/spec/selector_spec.rb +32 -17
  171. data/spec/selenium_spec_chrome.rb +78 -11
  172. data/spec/selenium_spec_chrome_remote.rb +23 -6
  173. data/spec/selenium_spec_edge.rb +15 -12
  174. data/spec/selenium_spec_firefox.rb +24 -19
  175. data/spec/selenium_spec_firefox_remote.rb +0 -8
  176. data/spec/selenium_spec_ie.rb +1 -6
  177. data/spec/server_spec.rb +106 -44
  178. data/spec/session_spec.rb +5 -5
  179. data/spec/shared_selenium_node.rb +56 -2
  180. data/spec/shared_selenium_session.rb +122 -15
  181. data/spec/spec_helper.rb +2 -2
  182. metadata +63 -17
  183. data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -143,10 +143,56 @@ Capybara::SpecHelper.spec '#has_no_table?' do
143
143
  ])
144
144
  end
145
145
 
146
- it 'should consider columns' do
147
- expect(@session).to have_no_table('Vertical Headers', with_cols:
148
- [
149
- { 'First Name' => 'Joe' }
150
- ])
146
+ context 'using :with_cols' do
147
+ it 'should consider a single column' do
148
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
149
+ [
150
+ { 'First Name' => 'Joe' }
151
+ ])
152
+ end
153
+
154
+ it 'should be true even if the last column does exist' do
155
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
156
+ [
157
+ {
158
+ 'First Name' => 'What?',
159
+ 'What?' => 'Walpole',
160
+ 'City' => 'Oceanside' # This line makes the example fail
161
+ }
162
+ ])
163
+ end
164
+
165
+ it 'should be true if none of the columns exist' do
166
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
167
+ [
168
+ {
169
+ 'First Name' => 'What?',
170
+ 'What?' => 'Walpole',
171
+ 'City' => 'What?'
172
+ }
173
+ ])
174
+ end
175
+
176
+ it 'should be true if the first column does match' do
177
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
178
+ [
179
+ {
180
+ 'First Name' => 'Thomas',
181
+ 'Last Name' => 'What',
182
+ 'City' => 'What'
183
+ }
184
+ ])
185
+ end
186
+
187
+ it 'should be true if none of the columns match' do
188
+ expect(@session).to have_no_table('Vertical Headers', with_cols:
189
+ [
190
+ {
191
+ 'First Name' => 'What',
192
+ 'Last Name' => 'What',
193
+ 'City' => 'What'
194
+ }
195
+ ])
196
+ end
151
197
  end
152
198
  end
@@ -111,6 +111,30 @@ Capybara::SpecHelper.spec '#has_text?' do
111
111
  expect(@session).not_to have_text(/xxxxyzzz/)
112
112
  end
113
113
 
114
+ context 'with object implementing to_s and to_hash' do
115
+ it 'should work if the object is passed alone' do
116
+ with_to_hash = Class.new do
117
+ def to_s; '42' end
118
+ def to_hash; { value: 'Other hash' } end
119
+ end.new
120
+ @session.visit('/with_html')
121
+ expect(@session).to have_text(with_to_hash)
122
+ end
123
+
124
+ it 'should work if passed with empty options' do
125
+ with_to_hash = Class.new do
126
+ def to_s; '42' end
127
+ def to_hash; { value: 'Other hash' } end
128
+ end.new
129
+ @session.visit('/with_html')
130
+ if RUBY_VERSION >= '2.7'
131
+ expect(@session).to have_text(:visible, with_to_hash, **{})
132
+ else
133
+ expect(@session).to have_text(:visible, with_to_hash, {})
134
+ end
135
+ end
136
+ end
137
+
114
138
  context 'with exact: true option' do
115
139
  it 'should be true if text matches exactly' do
116
140
  @session.visit('/with_html')
@@ -139,6 +163,12 @@ Capybara::SpecHelper.spec '#has_text?' do
139
163
  expect(@session).to have_text(nil)
140
164
  end
141
165
 
166
+ it 'should warn when passed nil' do
167
+ @session.visit('/with_html')
168
+ expect_any_instance_of(Kernel).to receive(:warn).with(/Checking for expected text of nil is confusing/) # rubocop:disable RSpec/AnyInstance
169
+ expect(@session).to have_text(nil)
170
+ end
171
+
142
172
  it 'should wait for text to appear', requires: [:js] do
143
173
  Capybara.using_wait_time(3) do
144
174
  @session.visit('/with_js')
@@ -1,6 +1,6 @@
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
3
+ # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
4
4
  # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
5
5
  # using Capybara provided assertions with builtin waiting behavior.
6
6
 
@@ -28,8 +28,8 @@ Capybara::SpecHelper.spec '#matches_style?', requires: [:css] do
28
28
  output(/have_style is deprecated/).to_stderr
29
29
 
30
30
  el = @session.find(:css, '#first')
31
- allow(el).to receive(:warn).and_return(nil)
31
+ allow(Capybara::Helpers).to receive(:warn).and_return(nil)
32
32
  el.has_style?('display' => /^bl/)
33
- expect(el).to have_received(:warn)
33
+ expect(Capybara::Helpers).to have_received(:warn)
34
34
  end
35
35
  end
@@ -1,6 +1,6 @@
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
3
+ # NOTE: This file uses `sleep` to sync up parts of the tests. This is only implemented like this
4
4
  # because of the methods being tested. In tests using Capybara this type of behavior should be implemented
5
5
  # using Capybara provided assertions with builtin waiting behavior.
6
6
 
@@ -233,8 +233,9 @@ Capybara::SpecHelper.spec 'node' do
233
233
  end
234
234
 
235
235
  describe '#visible?' do
236
+ before { Capybara.ignore_hidden_elements = false }
237
+
236
238
  it 'should extract node visibility' do
237
- Capybara.ignore_hidden_elements = false
238
239
  expect(@session.first('//a')).to be_visible
239
240
 
240
241
  expect(@session.find('//div[@id="hidden"]')).not_to be_visible
@@ -245,15 +246,39 @@ Capybara::SpecHelper.spec 'node' do
245
246
  end
246
247
 
247
248
  it 'template elements should not be visible' do
248
- Capybara.ignore_hidden_elements = false
249
249
  expect(@session.find('//template')).not_to be_visible
250
250
  end
251
251
 
252
252
  it 'should be boolean' do
253
- Capybara.ignore_hidden_elements = false
254
253
  expect(@session.first('//a').visible?).to be true
255
254
  expect(@session.find('//div[@id="hidden"]').visible?).to be false
256
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
257
282
  end
258
283
 
259
284
  describe '#obscured?', requires: [:css] do
@@ -363,8 +388,8 @@ Capybara::SpecHelper.spec 'node' do
363
388
 
364
389
  describe '#==' do
365
390
  it 'preserve object identity' do
366
- expect(@session.find('//h1') == @session.find('//h1')).to be true # rubocop:disable Lint/UselessComparison
367
- expect(@session.find('//h1') === @session.find('//h1')).to be true # rubocop:disable Style/CaseEquality, Lint/UselessComparison
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
368
393
  expect(@session.find('//h1').eql?(@session.find('//h1'))).to be false
369
394
  end
370
395
 
@@ -384,6 +409,17 @@ Capybara::SpecHelper.spec 'node' do
384
409
  element = @session.find(:link, 'Second Link')
385
410
  expect(@session.find(:xpath, element.path)).to eq(element)
386
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
387
423
  end
388
424
 
389
425
  describe '#trigger', requires: %i[js trigger] do
@@ -419,6 +455,74 @@ Capybara::SpecHelper.spec 'node' do
419
455
  expect(@session).to have_xpath('//div[contains(., "Dropped!")]')
420
456
  end
421
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
+
422
526
  context 'HTML5', requires: %i[js html5_drag] do
423
527
  it 'should HTML5 drag and drop an object' do
424
528
  @session.visit('/with_js')
@@ -428,6 +532,14 @@ Capybara::SpecHelper.spec 'node' do
428
532
  expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain drag_html5")]')
429
533
  end
430
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
+
431
543
  it 'should set clientX/Y in dragover events' do
432
544
  @session.visit('/with_js')
433
545
  element = @session.find('//div[@id="drag_html5"]')
@@ -436,6 +548,26 @@ Capybara::SpecHelper.spec 'node' do
436
548
  expect(@session).to have_css('div.log', text: /DragOver with client position: [1-9]\d*,[1-9]\d*/, count: 2)
437
549
  end
438
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
+
439
571
  it 'should not HTML5 drag and drop on a non HTML5 drop element' do
440
572
  @session.visit('/with_js')
441
573
  element = @session.find('//div[@id="drag_html5"]')
@@ -471,6 +603,54 @@ Capybara::SpecHelper.spec 'node' do
471
603
  expect(@session).to have_content(/Item 3.*Item 1/, normalize_ws: true)
472
604
  end
473
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
474
654
  end
475
655
  end
476
656
 
@@ -502,6 +682,15 @@ Capybara::SpecHelper.spec 'node' do
502
682
  expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
503
683
  end
504
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
+
505
694
  it 'can drop multiple strings' do
506
695
  @session.visit('/with_js')
507
696
  target = @session.find('//div[@id="drop_html5"]')
@@ -589,7 +778,7 @@ Capybara::SpecHelper.spec 'node' do
589
778
  @session.visit('with_js')
590
779
  @session.find(:css, '#click-test').click(x: 5, y: 5)
591
780
  link = @session.find(:link, 'has-been-clicked')
592
- 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.-]+)$/)
593
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
594
783
  # integer/float conversions and rounding.
595
784
  expect(locations[:x].to_f).to be_within(1).of(5)
@@ -635,6 +824,67 @@ Capybara::SpecHelper.spec 'node' do
635
824
  JS
636
825
  expect { obscured.click(wait: 0) }.to(raise_error { |e| expect(e).to be_an_invalid_element_error(@session) })
637
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
638
888
  end
639
889
 
640
890
  describe '#double_click', requires: [:js] do
@@ -654,7 +904,7 @@ Capybara::SpecHelper.spec 'node' do
654
904
  @session.visit('with_js')
655
905
  @session.find(:css, '#click-test').double_click(x: 10, y: 5)
656
906
  link = @session.find(:link, 'has-been-double-clicked')
657
- 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.-]+)$/)
658
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
659
909
  # to integer/float conversions and rounding.
660
910
  expect(locations[:x].to_f).to be_within(1).of(10)
@@ -669,6 +919,58 @@ Capybara::SpecHelper.spec 'node' do
669
919
  JS
670
920
  expect { obscured.double_click }.not_to raise_error
671
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
672
974
  end
673
975
 
674
976
  describe '#right_click', requires: [:js] do
@@ -688,7 +990,7 @@ Capybara::SpecHelper.spec 'node' do
688
990
  @session.visit('with_js')
689
991
  @session.find(:css, '#click-test').right_click(x: 10, y: 10)
690
992
  link = @session.find(:link, 'has-been-right-clicked')
691
- 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.-]+)$/)
692
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
693
995
  # to integer/float conversions and rounding
694
996
  expect(locations[:x].to_f).to be_within(1).of(10)
@@ -703,6 +1005,67 @@ Capybara::SpecHelper.spec 'node' do
703
1005
  JS
704
1006
  expect { obscured.right_click }.not_to raise_error
705
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
706
1069
  end
707
1070
 
708
1071
  describe '#send_keys', requires: [:send_keys] do
@@ -814,6 +1177,28 @@ Capybara::SpecHelper.spec 'node' do
814
1177
  end
815
1178
 
816
1179
  describe '#reload', requires: [:js] 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
+
817
1202
  context 'without automatic reload' do
818
1203
  before { Capybara.automatic_reload = false }
819
1204