capybara 3.31.0 → 3.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +93 -13
  3. data/README.md +9 -4
  4. data/lib/capybara.rb +18 -8
  5. data/lib/capybara/config.rb +4 -6
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +4 -0
  8. data/lib/capybara/helpers.rb +25 -1
  9. data/lib/capybara/minitest.rb +215 -141
  10. data/lib/capybara/minitest/spec.rb +156 -97
  11. data/lib/capybara/node/actions.rb +16 -21
  12. data/lib/capybara/node/element.rb +3 -5
  13. data/lib/capybara/node/finders.rb +7 -6
  14. data/lib/capybara/node/matchers.rb +11 -11
  15. data/lib/capybara/node/simple.rb +5 -1
  16. data/lib/capybara/queries/ancestor_query.rb +1 -1
  17. data/lib/capybara/queries/current_path_query.rb +14 -4
  18. data/lib/capybara/queries/selector_query.rb +16 -10
  19. data/lib/capybara/queries/sibling_query.rb +1 -1
  20. data/lib/capybara/queries/style_query.rb +1 -1
  21. data/lib/capybara/queries/text_query.rb +7 -1
  22. data/lib/capybara/rack_test/browser.rb +9 -3
  23. data/lib/capybara/rack_test/driver.rb +1 -0
  24. data/lib/capybara/rack_test/form.rb +1 -1
  25. data/lib/capybara/rack_test/node.rb +1 -1
  26. data/lib/capybara/registration_container.rb +44 -0
  27. data/lib/capybara/registrations/patches/puma_ssl.rb +3 -1
  28. data/lib/capybara/registrations/servers.rb +2 -1
  29. data/lib/capybara/result.rb +8 -8
  30. data/lib/capybara/rspec.rb +2 -0
  31. data/lib/capybara/rspec/matcher_proxies.rb +5 -5
  32. data/lib/capybara/rspec/matchers.rb +7 -6
  33. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  34. data/lib/capybara/rspec/matchers/have_text.rb +1 -1
  35. data/lib/capybara/rspec/matchers/match_style.rb +5 -0
  36. data/lib/capybara/selector.rb +10 -1
  37. data/lib/capybara/selector/definition.rb +11 -9
  38. data/lib/capybara/selector/definition/button.rb +8 -5
  39. data/lib/capybara/selector/definition/css.rb +1 -1
  40. data/lib/capybara/selector/definition/datalist_input.rb +1 -1
  41. data/lib/capybara/selector/definition/element.rb +2 -1
  42. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  43. data/lib/capybara/selector/definition/label.rb +1 -1
  44. data/lib/capybara/selector/definition/link.rb +8 -0
  45. data/lib/capybara/selector/definition/select.rb +1 -1
  46. data/lib/capybara/selector/definition/table.rb +1 -1
  47. data/lib/capybara/selector/definition/table_row.rb +1 -1
  48. data/lib/capybara/selector/filter_set.rb +2 -2
  49. data/lib/capybara/selector/selector.rb +9 -1
  50. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  51. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  52. data/lib/capybara/selenium/driver.rb +35 -6
  53. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +9 -11
  54. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -11
  55. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +3 -3
  56. data/lib/capybara/selenium/extensions/find.rb +3 -3
  57. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  58. data/lib/capybara/selenium/node.rb +75 -12
  59. data/lib/capybara/selenium/nodes/chrome_node.rb +20 -11
  60. data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
  61. data/lib/capybara/selenium/nodes/safari_node.rb +1 -1
  62. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  63. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  64. data/lib/capybara/selenium/patches/logs.rb +7 -9
  65. data/lib/capybara/server/animation_disabler.rb +3 -2
  66. data/lib/capybara/server/middleware.rb +4 -2
  67. data/lib/capybara/session.rb +23 -14
  68. data/lib/capybara/session/config.rb +3 -1
  69. data/lib/capybara/session/matchers.rb +11 -11
  70. data/lib/capybara/spec/public/test.js +24 -1
  71. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  72. data/lib/capybara/spec/session/check_spec.rb +6 -0
  73. data/lib/capybara/spec/session/click_button_spec.rb +11 -0
  74. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  75. data/lib/capybara/spec/session/fill_in_spec.rb +9 -0
  76. data/lib/capybara/spec/session/find_spec.rb +11 -8
  77. data/lib/capybara/spec/session/has_button_spec.rb +18 -0
  78. data/lib/capybara/spec/session/has_css_spec.rb +11 -7
  79. data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
  80. data/lib/capybara/spec/session/has_field_spec.rb +16 -0
  81. data/lib/capybara/spec/session/has_select_spec.rb +4 -4
  82. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  83. data/lib/capybara/spec/session/has_text_spec.rb +0 -11
  84. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  85. data/lib/capybara/spec/session/node_spec.rb +76 -29
  86. data/lib/capybara/spec/session/refresh_spec.rb +1 -0
  87. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  88. data/lib/capybara/spec/session/window/window_spec.rb +7 -7
  89. data/lib/capybara/spec/spec_helper.rb +13 -14
  90. data/lib/capybara/spec/test_app.rb +22 -21
  91. data/lib/capybara/spec/views/form.erb +25 -1
  92. data/lib/capybara/spec/views/with_animation.erb +8 -0
  93. data/lib/capybara/spec/views/with_dragula.erb +3 -1
  94. data/lib/capybara/spec/views/with_html.erb +2 -2
  95. data/lib/capybara/spec/views/with_js.erb +2 -0
  96. data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
  97. data/lib/capybara/version.rb +1 -1
  98. data/lib/capybara/window.rb +3 -7
  99. data/spec/basic_node_spec.rb +9 -8
  100. data/spec/capybara_spec.rb +1 -1
  101. data/spec/dsl_spec.rb +14 -1
  102. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  103. data/spec/minitest_spec.rb +3 -2
  104. data/spec/rack_test_spec.rb +28 -5
  105. data/spec/regexp_dissassembler_spec.rb +0 -4
  106. data/spec/result_spec.rb +38 -31
  107. data/spec/rspec/features_spec.rb +3 -1
  108. data/spec/rspec/scenarios_spec.rb +4 -0
  109. data/spec/rspec/shared_spec_matchers.rb +63 -51
  110. data/spec/rspec_spec.rb +4 -0
  111. data/spec/selector_spec.rb +2 -1
  112. data/spec/selenium_spec_chrome.rb +4 -2
  113. data/spec/selenium_spec_chrome_remote.rb +2 -0
  114. data/spec/server_spec.rb +56 -49
  115. data/spec/shared_selenium_node.rb +18 -0
  116. data/spec/shared_selenium_session.rb +84 -7
  117. data/spec/spec_helper.rb +1 -1
  118. metadata +25 -24
  119. data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -7,27 +7,25 @@ module Capybara::Selenium::Driver::ChromeDriver
7
7
  def self.extended(base)
8
8
  bridge = base.send(:bridge)
9
9
  bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:log)
10
- bridge.extend Capybara::Selenium::IsDisplayed unless bridge.commands(:is_element_displayed)
10
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
11
11
  base.options[:native_displayed] = false if base.options[:native_displayed].nil?
12
12
  end
13
13
 
14
14
  def fullscreen_window(handle)
15
15
  within_given_window(handle) do
16
- begin
17
- super
18
- rescue NoMethodError => e
19
- raise unless e.message.match?(/full_screen_window/)
20
-
21
- result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
22
- result['value']
23
- end
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.include?('full_screen_window')
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
24
22
  end
25
23
  end
26
24
 
27
25
  def resize_window_to(handle, width, height)
28
26
  super
29
27
  rescue Selenium::WebDriver::Error::UnknownError => e
30
- raise unless e.message.match?(/failed to change window state/)
28
+ raise unless e.message.include?('failed to change window state')
31
29
 
32
30
  # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
33
31
  # and raises unnecessary error. Wait a bit and try again.
@@ -65,7 +63,7 @@ private
65
63
  end
66
64
 
67
65
  def clear_all_storage?
68
- storage_clears.none? { |s| s == false }
66
+ storage_clears.none? false
69
67
  end
70
68
 
71
69
  def uniform_storage_clear?
@@ -5,7 +5,7 @@ require 'capybara/selenium/nodes/edge_node'
5
5
  module Capybara::Selenium::Driver::EdgeDriver
6
6
  def self.extended(base)
7
7
  bridge = base.send(:bridge)
8
- bridge.extend Capybara::Selenium::IsDisplayed unless bridge.commands(:is_element_displayed)
8
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
9
9
  base.options[:native_displayed] = false if base.options[:native_displayed].nil?
10
10
  end
11
11
 
@@ -13,21 +13,19 @@ module Capybara::Selenium::Driver::EdgeDriver
13
13
  return super if edgedriver_version < 75
14
14
 
15
15
  within_given_window(handle) do
16
- begin
17
- super
18
- rescue NoMethodError => e
19
- raise unless e.message.match?(/full_screen_window/)
20
-
21
- result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
22
- result['value']
23
- end
16
+ super
17
+ rescue NoMethodError => e
18
+ raise unless e.message.include?('full_screen_window')
19
+
20
+ result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
21
+ result['value']
24
22
  end
25
23
  end
26
24
 
27
25
  def resize_window_to(handle, width, height)
28
26
  super
29
27
  rescue Selenium::WebDriver::Error::UnknownError => e
30
- raise unless e.message.match?(/failed to change window state/)
28
+ raise unless e.message.include?('failed to change window state')
31
29
 
32
30
  # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
33
31
  # and raises unnecessary error. Wait a bit and try again.
@@ -74,7 +72,7 @@ private
74
72
  end
75
73
 
76
74
  def clear_all_storage?
77
- storage_clears.none? { |s| s == false }
75
+ storage_clears.none? false
78
76
  end
79
77
 
80
78
  def uniform_storage_clear?
@@ -6,7 +6,7 @@ module Capybara::Selenium::Driver::FirefoxDriver
6
6
  def self.extended(driver)
7
7
  driver.extend Capybara::Selenium::Driver::W3CFirefoxDriver if w3c?(driver)
8
8
  bridge = driver.send(:bridge)
9
- bridge.extend Capybara::Selenium::IsDisplayed unless bridge.commands(:is_element_displayed)
9
+ bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
10
10
  end
11
11
 
12
12
  def self.w3c?(driver)
@@ -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
 
@@ -100,9 +100,9 @@ module Capybara
100
100
  def is_displayed_atom # rubocop:disable Naming/PredicateName
101
101
  @@is_displayed_atom ||= begin # rubocop:disable Style/ClassVars
102
102
  browser.send(:bridge).send(:read_atom, 'isDisplayed')
103
- rescue StandardError
104
- # If the atom doesn't exist or other error
105
- ''
103
+ rescue StandardError
104
+ # If the atom doesn't exist or other error
105
+ ''
106
106
  end
107
107
  end
108
108
  end
@@ -45,20 +45,18 @@ module Capybara
45
45
  JS
46
46
  end
47
47
 
48
+ SCROLL_POSITIONS = {
49
+ top: '0',
50
+ bottom: 'arguments[0].scrollHeight',
51
+ center: '(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
52
+ }.freeze
53
+
48
54
  def scroll_to_location(location)
49
- scroll_y = case location
50
- when :top
51
- '0'
52
- when :bottom
53
- 'arguments[0].scrollHeight'
54
- when :center
55
- '(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
56
- end
57
55
  driver.execute_script <<~JS, self
58
56
  if (arguments[0].scrollTo){
59
- arguments[0].scrollTo(0, #{scroll_y});
57
+ arguments[0].scrollTo(0, #{SCROLL_POSITIONS[location]});
60
58
  } else {
61
- arguments[0].scrollTop = #{scroll_y};
59
+ arguments[0].scrollTop = #{SCROLL_POSITIONS[location]};
62
60
  }
63
61
  JS
64
62
  end
@@ -104,10 +104,23 @@ 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
- e.message.match?(/Other element would receive the click/)
123
+ e.message.include?('Other element would receive the click')
111
124
  scroll_to_center
112
125
  end
113
126
 
@@ -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_APPEND_TEXT, self, value[4...-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
@@ -418,8 +464,8 @@ private
418
464
  end
419
465
  end
420
466
 
421
- def each_key(keys)
422
- normalize_keys(keys).each { |key| yield(key) }
467
+ def each_key(keys, &block)
468
+ normalize_keys(keys).each(&block)
423
469
  end
424
470
 
425
471
  def find_context
@@ -447,6 +493,9 @@ private
447
493
  var xpath = '';
448
494
  var pos, tempitem2;
449
495
 
496
+ if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) {
497
+ return "(: Shadow DOM element - no XPath :)";
498
+ };
450
499
  while(el !== xml.documentElement) {
451
500
  pos = 0;
452
501
  tempitem2 = el;
@@ -488,6 +537,16 @@ private
488
537
  })(arguments[0], arguments[1], arguments[2])
489
538
  JS
490
539
 
540
+ RAPID_APPEND_TEXT = <<~'JS'
541
+ (function(el, value) {
542
+ value = el.value + value;
543
+ if (el.maxLength && el.maxLength != -1){
544
+ value = value.slice(0, el.maxLength);
545
+ }
546
+ el.value = value;
547
+ })(arguments[0], arguments[1])
548
+ JS
549
+
491
550
  # SettableValue encapsulates time/date field formatting
492
551
  class SettableValue
493
552
  attr_reader :value
@@ -544,7 +603,11 @@ private
544
603
  end
545
604
 
546
605
  def empty?
547
- keys.empty? && !coords?
606
+ keys.empty? && !coords? && delay.zero?
607
+ end
608
+
609
+ def delay
610
+ options[:delay] || 0
548
611
  end
549
612
  end
550
613
  private_constant :ClickOptions
@@ -42,7 +42,7 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
42
42
  raise
43
43
  rescue ::Selenium::WebDriver::Error::WebDriverError => e
44
44
  # chromedriver 74 (at least on mac) raises the wrong error for this
45
- if e.message.match?(/element click intercepted/)
45
+ if e.message.include?('element click intercepted')
46
46
  raise ::Selenium::WebDriver::Error::ElementClickInterceptedError, e.message
47
47
  end
48
48
 
@@ -73,6 +73,24 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
73
73
  end
74
74
  end
75
75
 
76
+ def send_keys(*args)
77
+ args.chunk { |inp| inp.is_a?(String) && inp.match?(/\p{Emoji Presentation}/) }
78
+ .each do |contains_emoji, inputs|
79
+ if contains_emoji
80
+ inputs.join.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
81
+ .each do |emoji, clusters|
82
+ if emoji
83
+ driver.send(:execute_cdp, 'Input.insertText', text: clusters.join)
84
+ else
85
+ super(clusters.join)
86
+ end
87
+ end
88
+ else
89
+ super(*inputs)
90
+ end
91
+ end
92
+ end
93
+
76
94
  private
77
95
 
78
96
  def perform_legacy_drag(element, drop_modifiers)
@@ -92,12 +110,7 @@ private
92
110
  end
93
111
  end
94
112
 
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
- def browser_version(to_float = true)
113
+ def browser_version(to_float: true)
101
114
  caps = capabilities
102
115
  ver = (caps[:browser_version] || caps[:version])
103
116
  ver = ver.to_f if to_float
@@ -116,10 +129,6 @@ private
116
129
  capabilities['chrome']['chromedriverVersion'].split(' ')[0]
117
130
  end
118
131
 
119
- def capabilities
120
- driver.browser.capabilities
121
- end
122
-
123
132
  def native_displayed?
124
133
  (driver.options[:native_displayed] != false) &&
125
134
  (w3c? && chromedriver_supports_displayed_endpoint?) &&
@@ -42,7 +42,7 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
42
42
 
43
43
  def send_keys(*args)
44
44
  # https://github.com/mozilla/geckodriver/issues/846
45
- return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none? { |arg| arg.is_a? Array }
45
+ return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?(Array)
46
46
 
47
47
  native.click
48
48
  _send_keys(args).perform
@@ -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?
@@ -43,7 +43,7 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
43
43
  return '' unless visible?
44
44
 
45
45
  vis_text = driver.execute_script('return arguments[0].innerText', self)
46
- vis_text.gsub(/\ +/, ' ')
46
+ vis_text.squeeze(' ')
47
47
  .gsub(/[\ \n]*\n[\ \n]*/, "\n")
48
48
  .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
49
49
  .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
@@ -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
@@ -6,10 +6,10 @@ private
6
6
  def read_atom(function)
7
7
  @atoms ||= Hash.new do |hash, key|
8
8
  hash[key] = begin
9
- File.read(File.expand_path("../../atoms/#{key}.min.js", __FILE__))
10
- rescue Errno::ENOENT
11
- super
12
- end
9
+ File.read(File.expand_path("../../atoms/#{key}.min.js", __FILE__))
10
+ rescue Errno::ENOENT
11
+ super
12
+ end
13
13
  end
14
14
  @atoms[function]
15
15
  end