capybara 3.31.0 → 3.32.1

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +22 -0
  3. data/README.md +1 -1
  4. data/lib/capybara/minitest.rb +213 -138
  5. data/lib/capybara/minitest/spec.rb +153 -97
  6. data/lib/capybara/node/element.rb +2 -0
  7. data/lib/capybara/rack_test/browser.rb +3 -1
  8. data/lib/capybara/result.rb +1 -0
  9. data/lib/capybara/rspec/matcher_proxies.rb +4 -4
  10. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  11. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  12. data/lib/capybara/selenium/driver.rb +5 -4
  13. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
  14. data/lib/capybara/selenium/node.rb +68 -9
  15. data/lib/capybara/selenium/nodes/chrome_node.rb +0 -9
  16. data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
  17. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  18. data/lib/capybara/spec/public/test.js +11 -0
  19. data/lib/capybara/spec/session/fill_in_spec.rb +9 -0
  20. data/lib/capybara/spec/session/find_spec.rb +11 -8
  21. data/lib/capybara/spec/session/has_css_spec.rb +9 -6
  22. data/lib/capybara/spec/session/node_spec.rb +48 -21
  23. data/lib/capybara/spec/session/window/window_spec.rb +7 -7
  24. data/lib/capybara/spec/spec_helper.rb +1 -2
  25. data/lib/capybara/spec/views/form.erb +5 -0
  26. data/lib/capybara/spec/views/with_html.erb +2 -2
  27. data/lib/capybara/version.rb +1 -1
  28. data/spec/rack_test_spec.rb +12 -0
  29. data/spec/regexp_dissassembler_spec.rb +0 -4
  30. data/spec/result_spec.rb +37 -14
  31. data/spec/selenium_spec_chrome.rb +4 -0
  32. data/spec/selenium_spec_chrome_remote.rb +2 -0
  33. data/spec/shared_selenium_node.rb +18 -0
  34. data/spec/shared_selenium_session.rb +15 -6
  35. metadata +3 -2
@@ -46,7 +46,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
46
46
  begin
47
47
  # Firefox 68 hangs if we try to switch windows while a modal is visible
48
48
  browser.switch_to.alert&.dismiss
49
- rescue Selenium::WebDriver::Error::NoSuchAlertError # rubocop:disable Lint/SuppressedException
49
+ rescue Selenium::WebDriver::Error::NoSuchAlertError
50
50
  # Swallow
51
51
  end
52
52
  end
@@ -61,7 +61,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
61
61
  accept_modal :confirm, wait: 0.1 do
62
62
  super
63
63
  end
64
- rescue Capybara::ModalNotFound # rubocop:disable Lint/SuppressedException
64
+ rescue Capybara::ModalNotFound
65
65
  # No modal was opened - page has refreshed - ignore
66
66
  end
67
67
 
@@ -104,7 +104,20 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
104
104
  click_options = ClickOptions.new(keys, options)
105
105
  return native.click if click_options.empty?
106
106
 
107
- click_with_options(click_options)
107
+ perform_with_options(click_options) do |action|
108
+ target = click_options.coords? ? nil : native
109
+ if click_options.delay.zero?
110
+ action.click(target)
111
+ else
112
+ action.click_and_hold(target)
113
+ if w3c?
114
+ action.pause(action.pointer_inputs.first, click_options.delay)
115
+ else
116
+ action.pause(click_options.delay)
117
+ end
118
+ action.release
119
+ end
120
+ end
108
121
  rescue StandardError => e
109
122
  if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
110
123
  e.message.match?(/Other element would receive the click/)
@@ -116,14 +129,26 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
116
129
 
117
130
  def right_click(keys = [], **options)
118
131
  click_options = ClickOptions.new(keys, options)
119
- click_with_options(click_options) do |action|
120
- click_options.coords? ? action.context_click : action.context_click(native)
132
+ perform_with_options(click_options) do |action|
133
+ target = click_options.coords? ? nil : native
134
+ if click_options.delay.zero?
135
+ action.context_click(target)
136
+ elsif w3c?
137
+ action.move_to(target) if target
138
+ action.pointer_down(:right)
139
+ .pause(action.pointer_inputs.first, click_options.delay)
140
+ .pointer_up(:right)
141
+ else
142
+ raise ArgumentError, 'Delay is not supported when right clicking with legacy (non-w3c) selenium driver'
143
+ end
121
144
  end
122
145
  end
123
146
 
124
147
  def double_click(keys = [], **options)
125
148
  click_options = ClickOptions.new(keys, options)
126
- click_with_options(click_options) do |action|
149
+ raise ArgumentError, "double_click doesn't support a delay option" unless click_options.delay.zero?
150
+
151
+ perform_with_options(click_options) do |action|
127
152
  click_options.coords? ? action.double_click : action.double_click(native)
128
153
  end
129
154
  end
@@ -212,7 +237,7 @@ protected
212
237
  JS
213
238
  begin
214
239
  driver.execute_script(script, self)
215
- rescue StandardError # rubocop:disable Lint/SuppressedException
240
+ rescue StandardError
216
241
  # Swallow error if scrollIntoView with options isn't supported
217
242
  end
218
243
  end
@@ -242,7 +267,7 @@ private
242
267
  find_xpath(XPath.ancestor(:select)[1]).first
243
268
  end
244
269
 
245
- def set_text(value, clear: nil, **_unused)
270
+ def set_text(value, clear: nil, rapid: nil, **_unused)
246
271
  value = value.to_s
247
272
  if value.empty? && clear.nil?
248
273
  native.clear
@@ -254,11 +279,23 @@ private
254
279
  send_keys(*clear, value)
255
280
  else
256
281
  driver.execute_script 'arguments[0].select()', self unless clear == :none
257
- send_keys(value)
282
+ if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
283
+ send_keys(value[0..3])
284
+ driver.execute_script RAPID_SET_TEXT, self, value[0...-3]
285
+ send_keys(value[-3..-1])
286
+ else
287
+ send_keys(value)
288
+ end
258
289
  end
259
290
  end
260
291
 
261
- def click_with_options(click_options)
292
+ def auto_rapid_set_length
293
+ 30
294
+ end
295
+
296
+ def perform_with_options(click_options, &block)
297
+ raise ArgumentError, 'A block must be provided' unless block
298
+
262
299
  scroll_if_needed do
263
300
  action_with_modifiers(click_options) do |action|
264
301
  if block_given?
@@ -407,6 +444,15 @@ private
407
444
  browser.action
408
445
  end
409
446
 
447
+ def capabilities
448
+ browser.capabilities
449
+ end
450
+
451
+ def w3c?
452
+ (defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) ||
453
+ capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
454
+ end
455
+
410
456
  def normalize_keys(keys)
411
457
  keys.map do |key|
412
458
  case key
@@ -488,6 +534,15 @@ private
488
534
  })(arguments[0], arguments[1], arguments[2])
489
535
  JS
490
536
 
537
+ RAPID_SET_TEXT = <<~'JS'
538
+ (function(el, value) {
539
+ if (el.maxLength && el.maxLength != -1){
540
+ value = value.slice(0, el.maxLength);
541
+ }
542
+ el.value = value;
543
+ })(arguments[0], arguments[1])
544
+ JS
545
+
491
546
  # SettableValue encapsulates time/date field formatting
492
547
  class SettableValue
493
548
  attr_reader :value
@@ -544,7 +599,11 @@ private
544
599
  end
545
600
 
546
601
  def empty?
547
- keys.empty? && !coords?
602
+ keys.empty? && !coords? && delay.zero?
603
+ end
604
+
605
+ def delay
606
+ options[:delay] || 0
548
607
  end
549
608
  end
550
609
  private_constant :ClickOptions
@@ -92,11 +92,6 @@ private
92
92
  end
93
93
  end
94
94
 
95
- def w3c?
96
- (defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) ||
97
- capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
98
- end
99
-
100
95
  def browser_version(to_float = true)
101
96
  caps = capabilities
102
97
  ver = (caps[:browser_version] || caps[:version])
@@ -116,10 +111,6 @@ private
116
111
  capabilities['chrome']['chromedriverVersion'].split(' ')[0]
117
112
  end
118
113
 
119
- def capabilities
120
- driver.browser.capabilities
121
- end
122
-
123
114
  def native_displayed?
124
115
  (driver.options[:native_displayed] != false) &&
125
116
  (w3c? && chromedriver_supports_displayed_endpoint?) &&
@@ -85,7 +85,7 @@ private
85
85
  (driver.options[:native_displayed] != false) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
86
86
  end
87
87
 
88
- def click_with_options(click_options)
88
+ def perform_with_options(click_options)
89
89
  # Firefox/marionette has an issue clicking with offset near viewport edge
90
90
  # scroll element to middle just in case
91
91
  scroll_to_center if click_options.coords?
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPauser
4
+ def initialize(mouse, keyboard)
5
+ super
6
+ @devices[:pauser] = Pauser.new
7
+ end
8
+
9
+ def pause(duration)
10
+ @actions << [:pauser, :pause, [duration]]
11
+ self
12
+ end
13
+
14
+ class Pauser
15
+ def pause(duration)
16
+ sleep duration
17
+ end
18
+ end
19
+
20
+ private_constant :Pauser
21
+ end
22
+
23
+ if defined?(::Selenium::WebDriver::VERSION) && (::Selenium::WebDriver::VERSION.to_f < 4) &&
24
+ defined?(::Selenium::WebDriver::ActionBuilder)
25
+ ::Selenium::WebDriver::ActionBuilder.prepend(ActionPauser)
26
+ end
@@ -153,6 +153,7 @@ $(function() {
153
153
  });
154
154
  $('#click-test').on({
155
155
  click: function(e) {
156
+ window.click_delay = ((new Date().getTime()) - window.mouse_down_time)/1000.0;
156
157
  var desc = "";
157
158
  if (e.altKey) desc += 'alt ';
158
159
  if (e.ctrlKey) desc += 'control ';
@@ -179,6 +180,16 @@ $(function() {
179
180
  if (e.shiftKey) desc += 'shift ';
180
181
  var pos = this.getBoundingClientRect();
181
182
  $(this).after('<a id="has-been-right-clicked" href="#">Has been ' + desc + 'right clicked at ' + (e.clientX - pos.left) + ',' + (e.clientY - pos.top) + '</a>');
183
+ },
184
+ mousedown: function(e) {
185
+ window.click_delay = undefined;
186
+ window.right_click_delay = undefined;
187
+ window.mouse_down_time = new Date().getTime();
188
+ },
189
+ mouseup: function(e) {
190
+ if (e.button == 2){
191
+ window.right_click_delay = ((new Date().getTime()) - window.mouse_down_time)/1000.0;
192
+ }
182
193
  }
183
194
  });
184
195
  $('#open-alert').click(function() {
@@ -47,6 +47,15 @@ Capybara::SpecHelper.spec '#fill_in' do
47
47
  expect(extract_results(@session)['description']).to eq('Texty text')
48
48
  end
49
49
 
50
+ it 'should fill in a textarea in a reasonable time by default' do
51
+ textarea = @session.find(:fillable_field, 'form[description]')
52
+ value = 'a' * 4000
53
+ start = Time.now
54
+ textarea.fill_in(with: value)
55
+ expect(Time.now.to_f).to be_within(0.25).of start.to_f
56
+ expect(textarea.value).to eq value
57
+ end
58
+
50
59
  it 'should fill in a password field by id' do
51
60
  @session.fill_in('form_password', with: 'supasikrit')
52
61
  @session.click_button('awesome')
@@ -451,32 +451,35 @@ Capybara::SpecHelper.spec '#find' do
451
451
  context 'with spatial filters', requires: [:spatial] do
452
452
  before do
453
453
  @session.visit('/spatial')
454
- @center = @session.find(:css, 'div.center')
454
+ end
455
+
456
+ let :center do
457
+ @session.find(:css, 'div.center')
455
458
  end
456
459
 
457
460
  it 'should find an element above another element' do
458
- expect(@session.find(:css, 'div:not(.corner)', above: @center).text).to eq('2')
461
+ expect(@session.find(:css, 'div:not(.corner)', above: center).text).to eq('2')
459
462
  end
460
463
 
461
464
  it 'should find an element below another element' do
462
- expect(@session.find(:css, 'div:not(.corner):not(.footer)', below: @center).text).to eq('8')
465
+ expect(@session.find(:css, 'div:not(.corner):not(.footer)', below: center).text).to eq('8')
463
466
  end
464
467
 
465
468
  it 'should find an element left of another element' do
466
- expect(@session.find(:css, 'div:not(.corner)', left_of: @center).text).to eq('4')
469
+ expect(@session.find(:css, 'div:not(.corner)', left_of: center).text).to eq('4')
467
470
  end
468
471
 
469
472
  it 'should find an element right of another element' do
470
- expect(@session.find(:css, 'div:not(.corner)', right_of: @center).text).to eq('6')
473
+ expect(@session.find(:css, 'div:not(.corner)', right_of: center).text).to eq('6')
471
474
  end
472
475
 
473
476
  it 'should combine spatial filters' do
474
- expect(@session.find(:css, 'div', left_of: @center, above: @center).text).to eq('1')
475
- expect(@session.find(:css, 'div', right_of: @center, below: @center).text).to eq('9')
477
+ expect(@session.find(:css, 'div', left_of: center, above: center).text).to eq('1')
478
+ expect(@session.find(:css, 'div', right_of: center, below: center).text).to eq('9')
476
479
  end
477
480
 
478
481
  it 'should find an element "near" another element' do
479
- expect(@session.find(:css, 'div.distance', near: @center).text).to eq('2')
482
+ expect(@session.find(:css, 'div.distance', near: center).text).to eq('2')
480
483
  end
481
484
  end
482
485
 
@@ -234,18 +234,21 @@ Capybara::SpecHelper.spec '#has_css?' do
234
234
  context 'with spatial requirements', requires: [:spatial] do
235
235
  before do
236
236
  @session.visit('/spatial')
237
- @center = @session.find(:css, '.center')
237
+ end
238
+
239
+ let :center do
240
+ @session.find(:css, '.center')
238
241
  end
239
242
 
240
243
  it 'accepts spatial options' do
241
- expect(@session).to have_css('div', above: @center).thrice
242
- expect(@session).to have_css('div', above: @center, right_of: @center).once
244
+ expect(@session).to have_css('div', above: center).thrice
245
+ expect(@session).to have_css('div', above: center, right_of: center).once
243
246
  end
244
247
 
245
248
  it 'supports spatial sugar' do
246
- expect(@session).to have_css('div').left_of(@center).thrice
247
- expect(@session).to have_css('div').below(@center).right_of(@center).once
248
- expect(@session).to have_css('div').near(@center).exactly(8).times
249
+ expect(@session).to have_css('div').left_of(center).thrice
250
+ expect(@session).to have_css('div').below(center).right_of(center).once
251
+ expect(@session).to have_css('div').near(center).exactly(8).times
249
252
  end
250
253
  end
251
254
 
@@ -808,7 +808,10 @@ Capybara::SpecHelper.spec 'node' do
808
808
  context 'offset', requires: [:js] do
809
809
  before do
810
810
  @session.visit('/offset')
811
- @clicker = @session.find(:id, 'clicker')
811
+ end
812
+
813
+ let :clicker do
814
+ @session.find(:id, 'clicker')
812
815
  end
813
816
 
814
817
  context 'when w3c_click_offset is false' do
@@ -817,17 +820,17 @@ Capybara::SpecHelper.spec 'node' do
817
820
  end
818
821
 
819
822
  it 'should offset from top left of element' do
820
- @clicker.click(x: 10, y: 5)
823
+ clicker.click(x: 10, y: 5)
821
824
  expect(@session).to have_text(/clicked at 110,105/)
822
825
  end
823
826
 
824
827
  it 'should offset outside the element' do
825
- @clicker.click(x: -15, y: -10)
828
+ clicker.click(x: -15, y: -10)
826
829
  expect(@session).to have_text(/clicked at 85,90/)
827
830
  end
828
831
 
829
832
  it 'should default to click the middle' do
830
- @clicker.click
833
+ clicker.click
831
834
  expect(@session).to have_text(/clicked at 150,150/)
832
835
  end
833
836
  end
@@ -838,21 +841,30 @@ Capybara::SpecHelper.spec 'node' do
838
841
  end
839
842
 
840
843
  it 'should offset from center of element' do
841
- @clicker.click(x: 10, y: 5)
844
+ clicker.click(x: 10, y: 5)
842
845
  expect(@session).to have_text(/clicked at 160,155/)
843
846
  end
844
847
 
845
848
  it 'should offset outside from center of element' do
846
- @clicker.click(x: -65, y: -60)
849
+ clicker.click(x: -65, y: -60)
847
850
  expect(@session).to have_text(/clicked at 85,90/)
848
851
  end
849
852
 
850
853
  it 'should default to click the middle' do
851
- @clicker.click
854
+ clicker.click
852
855
  expect(@session).to have_text(/clicked at 150,150/)
853
856
  end
854
857
  end
855
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
856
868
  end
857
869
 
858
870
  describe '#double_click', requires: [:js] do
@@ -891,7 +903,10 @@ Capybara::SpecHelper.spec 'node' do
891
903
  context 'offset', requires: [:js] do
892
904
  before do
893
905
  @session.visit('/offset')
894
- @clicker = @session.find(:id, 'clicker')
906
+ end
907
+
908
+ let :clicker do
909
+ @session.find(:id, 'clicker')
895
910
  end
896
911
 
897
912
  context 'when w3c_click_offset is false' do
@@ -900,17 +915,17 @@ Capybara::SpecHelper.spec 'node' do
900
915
  end
901
916
 
902
917
  it 'should offset from top left of element' do
903
- @clicker.double_click(x: 10, y: 5)
918
+ clicker.double_click(x: 10, y: 5)
904
919
  expect(@session).to have_text(/clicked at 110,105/)
905
920
  end
906
921
 
907
922
  it 'should offset outside the element' do
908
- @clicker.double_click(x: -15, y: -10)
923
+ clicker.double_click(x: -15, y: -10)
909
924
  expect(@session).to have_text(/clicked at 85,90/)
910
925
  end
911
926
 
912
927
  it 'should default to click the middle' do
913
- @clicker.double_click
928
+ clicker.double_click
914
929
  expect(@session).to have_text(/clicked at 150,150/)
915
930
  end
916
931
  end
@@ -921,17 +936,17 @@ Capybara::SpecHelper.spec 'node' do
921
936
  end
922
937
 
923
938
  it 'should offset from center of element' do
924
- @clicker.double_click(x: 10, y: 5)
939
+ clicker.double_click(x: 10, y: 5)
925
940
  expect(@session).to have_text(/clicked at 160,155/)
926
941
  end
927
942
 
928
943
  it 'should offset outside from center of element' do
929
- @clicker.double_click(x: -65, y: -60)
944
+ clicker.double_click(x: -65, y: -60)
930
945
  expect(@session).to have_text(/clicked at 85,90/)
931
946
  end
932
947
 
933
948
  it 'should default to click the middle' do
934
- @clicker.double_click
949
+ clicker.double_click
935
950
  expect(@session).to have_text(/clicked at 150,150/)
936
951
  end
937
952
  end
@@ -974,7 +989,10 @@ Capybara::SpecHelper.spec 'node' do
974
989
  context 'offset', requires: [:js] do
975
990
  before do
976
991
  @session.visit('/offset')
977
- @clicker = @session.find(:id, 'clicker')
992
+ end
993
+
994
+ let :clicker do
995
+ @session.find(:id, 'clicker')
978
996
  end
979
997
 
980
998
  context 'when w3c_click_offset is false' do
@@ -983,17 +1001,17 @@ Capybara::SpecHelper.spec 'node' do
983
1001
  end
984
1002
 
985
1003
  it 'should offset from top left of element' do
986
- @clicker.right_click(x: 10, y: 5)
1004
+ clicker.right_click(x: 10, y: 5)
987
1005
  expect(@session).to have_text(/clicked at 110,105/)
988
1006
  end
989
1007
 
990
1008
  it 'should offset outside the element' do
991
- @clicker.right_click(x: -15, y: -10)
1009
+ clicker.right_click(x: -15, y: -10)
992
1010
  expect(@session).to have_text(/clicked at 85,90/)
993
1011
  end
994
1012
 
995
1013
  it 'should default to click the middle' do
996
- @clicker.right_click
1014
+ clicker.right_click
997
1015
  expect(@session).to have_text(/clicked at 150,150/)
998
1016
  end
999
1017
  end
@@ -1004,21 +1022,30 @@ Capybara::SpecHelper.spec 'node' do
1004
1022
  end
1005
1023
 
1006
1024
  it 'should offset from center of element' do
1007
- @clicker.right_click(x: 10, y: 5)
1025
+ clicker.right_click(x: 10, y: 5)
1008
1026
  expect(@session).to have_text(/clicked at 160,155/)
1009
1027
  end
1010
1028
 
1011
1029
  it 'should offset outside from center of element' do
1012
- @clicker.right_click(x: -65, y: -60)
1030
+ clicker.right_click(x: -65, y: -60)
1013
1031
  expect(@session).to have_text(/clicked at 85,90/)
1014
1032
  end
1015
1033
 
1016
1034
  it 'should default to click the middle' do
1017
- @clicker.right_click
1035
+ clicker.right_click
1018
1036
  expect(@session).to have_text(/clicked at 150,150/)
1019
1037
  end
1020
1038
  end
1021
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
1022
1049
  end
1023
1050
 
1024
1051
  describe '#send_keys', requires: [:send_keys] do