capybara 3.35.0 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +168 -5
  3. data/README.md +199 -39
  4. data/lib/capybara/config.rb +16 -4
  5. data/lib/capybara/driver/base.rb +4 -0
  6. data/lib/capybara/driver/node.rb +5 -1
  7. data/lib/capybara/dsl.rb +4 -10
  8. data/lib/capybara/helpers.rb +9 -14
  9. data/lib/capybara/minitest/spec.rb +18 -6
  10. data/lib/capybara/minitest.rb +14 -1
  11. data/lib/capybara/node/actions.rb +14 -9
  12. data/lib/capybara/node/base.rb +2 -1
  13. data/lib/capybara/node/document.rb +2 -2
  14. data/lib/capybara/node/element.rb +13 -2
  15. data/lib/capybara/node/finders.rb +11 -2
  16. data/lib/capybara/node/matchers.rb +25 -0
  17. data/lib/capybara/node/simple.rb +5 -1
  18. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  19. data/lib/capybara/queries/active_element_query.rb +18 -0
  20. data/lib/capybara/queries/ancestor_query.rb +2 -1
  21. data/lib/capybara/queries/base_query.rb +2 -2
  22. data/lib/capybara/queries/current_path_query.rb +1 -1
  23. data/lib/capybara/queries/selector_query.rb +40 -11
  24. data/lib/capybara/queries/sibling_query.rb +2 -1
  25. data/lib/capybara/queries/text_query.rb +1 -1
  26. data/lib/capybara/rack_test/browser.rb +64 -8
  27. data/lib/capybara/rack_test/driver.rb +4 -4
  28. data/lib/capybara/rack_test/form.rb +29 -7
  29. data/lib/capybara/rack_test/node.rb +32 -33
  30. data/lib/capybara/registration_container.rb +2 -5
  31. data/lib/capybara/registrations/drivers.rb +7 -7
  32. data/lib/capybara/registrations/servers.rb +37 -16
  33. data/lib/capybara/result.rb +2 -2
  34. data/lib/capybara/rspec/matcher_proxies.rb +6 -6
  35. data/lib/capybara/rspec/matchers/base.rb +8 -6
  36. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  37. data/lib/capybara/rspec/matchers/have_selector.rb +9 -17
  38. data/lib/capybara/rspec/matchers.rb +21 -16
  39. data/lib/capybara/selector/builders/css_builder.rb +1 -1
  40. data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
  41. data/lib/capybara/selector/css.rb +6 -6
  42. data/lib/capybara/selector/definition/button.rb +10 -5
  43. data/lib/capybara/selector/definition/checkbox.rb +1 -1
  44. data/lib/capybara/selector/definition/file_field.rb +1 -1
  45. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  46. data/lib/capybara/selector/definition/link.rb +2 -1
  47. data/lib/capybara/selector/definition/radio_button.rb +1 -1
  48. data/lib/capybara/selector/definition/table.rb +1 -1
  49. data/lib/capybara/selector/definition/table_row.rb +2 -2
  50. data/lib/capybara/selector/definition.rb +4 -2
  51. data/lib/capybara/selector/filter_set.rb +4 -7
  52. data/lib/capybara/selector/regexp_disassembler.rb +2 -5
  53. data/lib/capybara/selector/selector.rb +5 -1
  54. data/lib/capybara/selector.rb +252 -0
  55. data/lib/capybara/selenium/driver.rb +31 -54
  56. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
  57. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -5
  58. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -7
  59. data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
  60. data/lib/capybara/selenium/node.rb +60 -38
  61. data/lib/capybara/selenium/nodes/chrome_node.rb +4 -16
  62. data/lib/capybara/selenium/nodes/edge_node.rb +19 -13
  63. data/lib/capybara/selenium/nodes/firefox_node.rb +3 -3
  64. data/lib/capybara/selenium/nodes/safari_node.rb +4 -4
  65. data/lib/capybara/selenium/patches/atoms.rb +1 -1
  66. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  67. data/lib/capybara/server/animation_disabler.rb +40 -23
  68. data/lib/capybara/server/middleware.rb +1 -1
  69. data/lib/capybara/server.rb +1 -1
  70. data/lib/capybara/session/config.rb +4 -2
  71. data/lib/capybara/session.rb +34 -34
  72. data/lib/capybara/spec/public/test.js +4 -0
  73. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  74. data/lib/capybara/spec/session/all_spec.rb +11 -15
  75. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  76. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  77. data/lib/capybara/spec/session/check_spec.rb +10 -0
  78. data/lib/capybara/spec/session/choose_spec.rb +6 -0
  79. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  80. data/lib/capybara/spec/session/click_link_spec.rb +12 -1
  81. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  82. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  83. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  84. data/lib/capybara/spec/session/find_spec.rb +15 -1
  85. data/lib/capybara/spec/session/first_spec.rb +1 -1
  86. data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
  87. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  88. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
  89. data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
  90. data/lib/capybara/spec/session/has_button_spec.rb +30 -0
  91. data/lib/capybara/spec/session/has_current_path_spec.rb +3 -3
  92. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  93. data/lib/capybara/spec/session/has_field_spec.rb +25 -1
  94. data/lib/capybara/spec/session/has_link_spec.rb +40 -0
  95. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  96. data/lib/capybara/spec/session/has_select_spec.rb +10 -4
  97. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  98. data/lib/capybara/spec/session/has_table_spec.rb +13 -2
  99. data/lib/capybara/spec/session/has_text_spec.rb +6 -14
  100. data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
  101. data/lib/capybara/spec/session/node_spec.rb +88 -1
  102. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  103. data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
  104. data/lib/capybara/spec/session/scroll_spec.rb +7 -5
  105. data/lib/capybara/spec/session/uncheck_spec.rb +1 -1
  106. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  107. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  108. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  109. data/lib/capybara/spec/session/within_spec.rb +13 -0
  110. data/lib/capybara/spec/spec_helper.rb +12 -5
  111. data/lib/capybara/spec/test_app.rb +91 -14
  112. data/lib/capybara/spec/views/animated.erb +1 -1
  113. data/lib/capybara/spec/views/form.erb +34 -4
  114. data/lib/capybara/spec/views/frame_child.erb +1 -1
  115. data/lib/capybara/spec/views/frame_one.erb +1 -1
  116. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  117. data/lib/capybara/spec/views/frame_two.erb +1 -1
  118. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  119. data/lib/capybara/spec/views/layout.erb +10 -0
  120. data/lib/capybara/spec/views/obscured.erb +1 -1
  121. data/lib/capybara/spec/views/offset.erb +2 -1
  122. data/lib/capybara/spec/views/path.erb +2 -2
  123. data/lib/capybara/spec/views/popup_one.erb +1 -1
  124. data/lib/capybara/spec/views/popup_two.erb +1 -1
  125. data/lib/capybara/spec/views/react.erb +2 -2
  126. data/lib/capybara/spec/views/scroll.erb +2 -1
  127. data/lib/capybara/spec/views/spatial.erb +1 -1
  128. data/lib/capybara/spec/views/with_animation.erb +2 -3
  129. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  130. data/lib/capybara/spec/views/with_dragula.erb +2 -2
  131. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  132. data/lib/capybara/spec/views/with_hover.erb +2 -2
  133. data/lib/capybara/spec/views/with_html.erb +5 -3
  134. data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
  135. data/lib/capybara/spec/views/with_js.erb +2 -3
  136. data/lib/capybara/spec/views/with_jstree.erb +1 -1
  137. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  138. data/lib/capybara/spec/views/with_scope.erb +2 -2
  139. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  140. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  141. data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
  142. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  143. data/lib/capybara/spec/views/with_windows.erb +1 -1
  144. data/lib/capybara/spec/views/within_frames.erb +1 -1
  145. data/lib/capybara/version.rb +1 -1
  146. data/lib/capybara/window.rb +1 -1
  147. data/lib/capybara.rb +30 -30
  148. data/spec/basic_node_spec.rb +16 -3
  149. data/spec/capybara_spec.rb +12 -0
  150. data/spec/counter_spec.rb +35 -0
  151. data/spec/css_builder_spec.rb +1 -1
  152. data/spec/css_splitter_spec.rb +1 -1
  153. data/spec/dsl_spec.rb +5 -3
  154. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  155. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  156. data/spec/minitest_spec.rb +12 -1
  157. data/spec/minitest_spec_spec.rb +4 -0
  158. data/spec/per_session_config_spec.rb +1 -1
  159. data/spec/rack_test_spec.rb +30 -12
  160. data/spec/result_spec.rb +41 -35
  161. data/spec/rspec/features_spec.rb +3 -3
  162. data/spec/rspec/scenarios_spec.rb +2 -2
  163. data/spec/rspec/shared_spec_matchers.rb +27 -3
  164. data/spec/rspec_matchers_spec.rb +25 -0
  165. data/spec/rspec_spec.rb +3 -3
  166. data/spec/sauce_spec_chrome.rb +5 -5
  167. data/spec/selector_spec.rb +4 -4
  168. data/spec/selenium_spec_chrome.rb +20 -18
  169. data/spec/selenium_spec_chrome_remote.rb +15 -19
  170. data/spec/selenium_spec_edge.rb +19 -6
  171. data/spec/selenium_spec_firefox.rb +26 -8
  172. data/spec/selenium_spec_firefox_remote.rb +18 -4
  173. data/spec/selenium_spec_ie.rb +7 -8
  174. data/spec/selenium_spec_safari.rb +34 -20
  175. data/spec/server_spec.rb +19 -7
  176. data/spec/shared_selenium_node.rb +0 -4
  177. data/spec/shared_selenium_session.rb +22 -14
  178. data/spec/spec_helper.rb +36 -3
  179. data/spec/whitespace_normalizer_spec.rb +54 -0
  180. data/spec/xpath_builder_spec.rb +1 -1
  181. metadata +49 -30
  182. data/lib/capybara/selenium/logger_suppressor.rb +0 -34
  183. data/lib/capybara/selenium/patches/action_pauser.rb +0 -26
  184. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -4,22 +4,22 @@
4
4
 
5
5
  require 'capybara/selenium/extensions/find'
6
6
  require 'capybara/selenium/extensions/scroll'
7
+ require 'capybara/node/whitespace_normalizer'
7
8
 
8
9
  class Capybara::Selenium::Node < Capybara::Driver::Node
10
+ include Capybara::Node::WhitespaceNormalizer
9
11
  include Capybara::Selenium::Find
10
12
  include Capybara::Selenium::Scroll
11
13
 
12
14
  def visible_text
15
+ raise NotImplementedError, 'Getting visible text is not currently supported directly on shadow roots' if shadow_root?
16
+
13
17
  native.text
14
18
  end
15
19
 
16
20
  def all_text
17
- text = driver.evaluate_script('arguments[0].textContent', self)
18
- text.gsub(/[\u200b\u200e\u200f]/, '')
19
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
20
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
22
- .tr("\u00a0", ' ')
21
+ text = driver.evaluate_script('arguments[0].textContent', self) || ''
22
+ normalize_spacing(text)
23
23
  end
24
24
 
25
25
  def [](name)
@@ -37,9 +37,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
37
37
  end
38
38
 
39
39
  def style(styles)
40
- styles.each_with_object({}) do |style, result|
41
- result[style] = native.css_value(style)
42
- end
40
+ styles.to_h { |style| [style, native.css_value(style)] }
43
41
  end
44
42
 
45
43
  ##
@@ -53,6 +51,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
53
51
  # :none => append the new value to the existing value <br/>
54
52
  # :backspace => send backspace keystrokes to clear the field <br/>
55
53
  # Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
54
+ # @option options [Boolean] :rapid (nil) Whether setting text inputs should use a faster &quot;rapid&quot; mode<br/>
55
+ # nil => Text inputs with length greater than 30 characters will be set using a faster driver script mode<br/>
56
+ # true => Rapid mode will be used regardless of input length<br/>
57
+ # false => Sends keys via conventional mode. This may be required to avoid losing key-presses if you have certain
58
+ # Javascript interactions on form inputs<br/>
56
59
  def set(value, **options)
57
60
  if value.is_a?(Array) && !multiple?
58
61
  raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
@@ -110,11 +113,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
110
113
  action.click(target)
111
114
  else
112
115
  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
116
+ action_pause(action, click_options.delay)
118
117
  action.release
119
118
  end
120
119
  end
@@ -133,13 +132,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
133
132
  target = click_options.coords? ? nil : native
134
133
  if click_options.delay.zero?
135
134
  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
135
  else
142
- raise ArgumentError, 'Delay is not supported when right clicking with legacy (non-w3c) selenium driver'
136
+ action.move_to(target) if target
137
+ action.pointer_down(:right).then do |act|
138
+ action_pause(act, click_options.delay)
139
+ end.pointer_up(:right)
143
140
  end
144
141
  end
145
142
  end
@@ -179,7 +176,12 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
179
176
  end
180
177
 
181
178
  def tag_name
182
- @tag_name ||= native.tag_name.downcase
179
+ @tag_name ||=
180
+ if native.respond_to? :tag_name
181
+ native.tag_name.downcase
182
+ else
183
+ shadow_root? ? 'ShadowRoot' : 'Unknown'
184
+ end
183
185
  end
184
186
 
185
187
  def visible?; boolean_attr(native.displayed?); end
@@ -199,10 +201,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
199
201
  native.attribute('isContentEditable') == 'true'
200
202
  end
201
203
 
202
- def ==(other)
203
- native == other.native
204
- end
205
-
206
204
  def path
207
205
  driver.evaluate_script GET_XPATH_SCRIPT, self
208
206
  end
@@ -218,6 +216,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
218
216
  native.rect
219
217
  end
220
218
 
219
+ def shadow_root
220
+ root = native.shadow_root
221
+ root && build_node(native.shadow_root)
222
+ end
223
+
221
224
  protected
222
225
 
223
226
  def scroll_if_needed
@@ -228,7 +231,7 @@ protected
228
231
  end
229
232
 
230
233
  def scroll_to_center
231
- script = <<-'JS'
234
+ script = <<-JS
232
235
  try {
233
236
  arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
234
237
  } catch(e) {
@@ -274,7 +277,7 @@ private
274
277
  elsif clear == :backspace
275
278
  # Clear field by sending the correct number of backspace keys.
276
279
  backspaces = [:backspace] * self.value.to_s.length
277
- send_keys(*([:end] + backspaces + [value]))
280
+ send_keys(:end, *backspaces, value)
278
281
  elsif clear.is_a? Array
279
282
  send_keys(*clear, value)
280
283
  else
@@ -282,7 +285,7 @@ private
282
285
  if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
283
286
  send_keys(value[0..3])
284
287
  driver.execute_script RAPID_APPEND_TEXT, self, value[4...-3]
285
- send_keys(value[-3..-1])
288
+ send_keys(value[-3..])
286
289
  else
287
290
  send_keys(value)
288
291
  end
@@ -298,7 +301,7 @@ private
298
301
 
299
302
  scroll_if_needed do
300
303
  action_with_modifiers(click_options) do |action|
301
- if block_given?
304
+ if block
302
305
  yield action
303
306
  else
304
307
  click_options.coords? ? action.click : action.click(native)
@@ -407,10 +410,20 @@ private
407
410
 
408
411
  def action_with_modifiers(click_options)
409
412
  actions = browser_action.tap do |acts|
410
- if click_options.center_offset? && click_options.coords?
411
- acts.move_to(native).move_by(*click_options.coords)
413
+ if click_options.coords?
414
+ if click_options.center_offset?
415
+ acts.move_to(native, *click_options.coords)
416
+ else
417
+ right_by, down_by = *click_options.coords
418
+ size = native.size
419
+ left_offset = (size[:width] / 2).to_i
420
+ top_offset = (size[:height] / 2).to_i
421
+ left = -left_offset + right_by
422
+ top = -top_offset + down_by
423
+ acts.move_to(native, left, top)
424
+ end
412
425
  else
413
- acts.move_to(native, *click_options.coords)
426
+ acts.move_to(native)
414
427
  end
415
428
  end
416
429
  modifiers_down(actions, click_options.keys)
@@ -448,9 +461,8 @@ private
448
461
  browser.capabilities
449
462
  end
450
463
 
451
- def w3c?
452
- (defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) ||
453
- capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
464
+ def action_pause(action, duration)
465
+ action.pause(device: action.pointer_inputs.first, duration: duration)
454
466
  end
455
467
 
456
468
  def normalize_keys(keys)
@@ -479,7 +491,7 @@ private
479
491
  def attrs(*attr_names)
480
492
  return attr_names.map { |name| self[name.to_s] } if ENV['CAPYBARA_THOROUGH']
481
493
 
482
- driver.evaluate_script <<~'JS', self, attr_names.map(&:to_s)
494
+ driver.evaluate_script <<~JS, self, attr_names.map(&:to_s)
483
495
  (function(el, names){
484
496
  return names.map(function(name){
485
497
  return el[name]
@@ -488,6 +500,16 @@ private
488
500
  JS
489
501
  end
490
502
 
503
+ def native_id
504
+ # Selenium 3 -> 4 changed the return of ref
505
+ type_or_id, id = native.ref
506
+ id || type_or_id
507
+ end
508
+
509
+ def shadow_root?
510
+ defined?(::Selenium::WebDriver::ShadowRoot) && native.is_a?(::Selenium::WebDriver::ShadowRoot)
511
+ end
512
+
491
513
  GET_XPATH_SCRIPT = <<~'JS'
492
514
  (function(el, xml){
493
515
  var xpath = '';
@@ -520,7 +542,7 @@ private
520
542
  })(arguments[0], document)
521
543
  JS
522
544
 
523
- OBSCURED_OR_OFFSET_SCRIPT = <<~'JS'
545
+ OBSCURED_OR_OFFSET_SCRIPT = <<~JS
524
546
  (function(el, x, y) {
525
547
  var box = el.getBoundingClientRect();
526
548
  if (x == null) x = box.width/2;
@@ -537,7 +559,7 @@ private
537
559
  })(arguments[0], arguments[1], arguments[2])
538
560
  JS
539
561
 
540
- RAPID_APPEND_TEXT = <<~'JS'
562
+ RAPID_APPEND_TEXT = <<~JS
541
563
  (function(el, value) {
542
564
  value = el.value + value;
543
565
  if (el.maxLength && el.maxLength != -1){
@@ -24,12 +24,6 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
24
24
  JS
25
25
  end
26
26
  super
27
- rescue *file_errors => e
28
- if e.message.match?(/File not found : .+\n.+/m)
29
- raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload"
30
- end
31
-
32
- raise
33
27
  end
34
28
 
35
29
  def drop(*args)
@@ -65,7 +59,7 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
65
59
  return super unless native_displayed?
66
60
 
67
61
  begin
68
- bridge.send(:execute, :is_element_displayed, id: native.ref)
62
+ bridge.send(:execute, :is_element_displayed, id: native_id)
69
63
  rescue Selenium::WebDriver::Error::UnknownCommandError
70
64
  # If the is_element_displayed command is unknown, no point in trying again
71
65
  driver.options[:native_displayed] = false
@@ -94,7 +88,7 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
94
88
  private
95
89
 
96
90
  def perform_legacy_drag(element, drop_modifiers)
97
- return super if chromedriver_fixed_actions_key_state? || !w3c? || element.obscured?
91
+ return super if chromedriver_fixed_actions_key_state? || element.obscured?
98
92
 
99
93
  raise ArgumentError, 'Modifier keys are not supported while dragging in this version of Chrome.' unless drop_modifiers.empty?
100
94
 
@@ -104,15 +98,9 @@ private
104
98
  browser_action.click_and_hold(native).move_to(element.native).release.perform
105
99
  end
106
100
 
107
- def file_errors
108
- @file_errors = ::Selenium::WebDriver.logger.suppress_deprecations do
109
- [::Selenium::WebDriver::Error::ExpectedError]
110
- end
111
- end
112
-
113
101
  def browser_version(to_float: true)
114
102
  caps = capabilities
115
- ver = (caps[:browser_version] || caps[:version])
103
+ ver = caps[:browser_version] || caps[:version]
116
104
  ver = ver.to_f if to_float
117
105
  ver
118
106
  end
@@ -131,7 +119,7 @@ private
131
119
 
132
120
  def native_displayed?
133
121
  (driver.options[:native_displayed] != false) &&
134
- (w3c? && chromedriver_supports_displayed_endpoint?) &&
122
+ chromedriver_supports_displayed_endpoint? &&
135
123
  (!ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS'])
136
124
  end
137
125
  end
@@ -24,12 +24,6 @@ class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
24
24
  JS
25
25
  end
26
26
  super
27
- rescue *file_errors => e
28
- if e.message.match?(/File not found : .+\n.+/m)
29
- raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload"
30
- end
31
-
32
- raise
33
27
  end
34
28
 
35
29
  def drop(*args)
@@ -38,7 +32,7 @@ class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
38
32
  html5_drop(*args)
39
33
  end
40
34
 
41
- def click(*)
35
+ def click(*, **)
42
36
  super
43
37
  rescue Selenium::WebDriver::Error::InvalidArgumentError => e
44
38
  tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
@@ -69,7 +63,7 @@ class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
69
63
  return super unless chrome_edge? && native_displayed?
70
64
 
71
65
  begin
72
- bridge.send(:execute, :is_element_displayed, id: native.ref)
66
+ bridge.send(:execute, :is_element_displayed, id: native_id)
73
67
  rescue Selenium::WebDriver::Error::UnknownCommandError
74
68
  # If the is_element_displayed command is unknown, no point in trying again
75
69
  driver.options[:native_displayed] = false
@@ -77,14 +71,26 @@ class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
77
71
  end
78
72
  end
79
73
 
80
- private
81
-
82
- def file_errors
83
- @file_errors = ::Selenium::WebDriver.logger.suppress_deprecations do
84
- [::Selenium::WebDriver::Error::ExpectedError]
74
+ def send_keys(*args)
75
+ args.chunk { |inp| inp.is_a?(String) && inp.match?(/\p{Emoji Presentation}/) }
76
+ .each do |contains_emoji, inputs|
77
+ if contains_emoji
78
+ inputs.join.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
79
+ .each do |emoji, clusters|
80
+ if emoji
81
+ driver.send(:execute_cdp, 'Input.insertText', text: clusters.join)
82
+ else
83
+ super(clusters.join)
84
+ end
85
+ end
86
+ else
87
+ super(*inputs)
88
+ end
85
89
  end
86
90
  end
87
91
 
92
+ private
93
+
88
94
  def browser_version
89
95
  @browser_version ||= begin
90
96
  caps = driver.browser.capabilities
@@ -11,8 +11,8 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
11
11
  super
12
12
  rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
13
13
  if tag_name == 'tr'
14
- warn 'You are attempting to click a table row which has issues in geckodriver/marionette - '\
15
- 'see https://github.com/mozilla/geckodriver/issues/1228. Your test should probably be '\
14
+ warn 'You are attempting to click a table row which has issues in geckodriver/marionette - ' \
15
+ 'see https://github.com/mozilla/geckodriver/issues/1228 - Your test should probably be ' \
16
16
  'clicking on a table cell like a user would. Clicking the first cell in the row instead.'
17
17
  return find_css('th:first-child,td:first-child')[0].click(keys, **options)
18
18
  end
@@ -76,7 +76,7 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
76
76
  return super unless native_displayed?
77
77
 
78
78
  begin
79
- bridge.send(:execute, :is_element_displayed, id: native.ref)
79
+ bridge.send(:execute, :is_element_displayed, id: native_id)
80
80
  rescue Selenium::WebDriver::Error::UnknownCommandError
81
81
  # If the is_element_displayed command is unknown, no point in trying again
82
82
  driver.options[:native_displayed] = false
@@ -11,10 +11,10 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
11
11
  super
12
12
  rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
13
13
  if tag_name == 'tr'
14
- warn 'You are attempting to click a table row which has issues in safaridriver - '\
15
- 'Your test should probably be clicking on a table cell like a user would. '\
14
+ warn 'You are attempting to click a table row which has issues in safaridriver - ' \
15
+ 'Your test should probably be clicking on a table cell like a user would. ' \
16
16
  'Clicking the first cell in the row instead.'
17
- return find_css('th:first-child,td:first-child')[0].click(keys, options)
17
+ return find_css('th:first-child,td:first-child')[0].click(keys, **options)
18
18
  end
19
19
  raise
20
20
  rescue ::Selenium::WebDriver::Error::WebDriverError => e
@@ -74,7 +74,7 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
74
74
  if clear == :backspace
75
75
  # Clear field by sending the correct number of backspace keys.
76
76
  backspaces = [:backspace] * self.value.to_s.length
77
- send_keys(*([[:control, 'e']] + backspaces + [value]))
77
+ send_keys([:control, 'e'], *backspaces, value)
78
78
  else
79
79
  super.tap do
80
80
  # React doesn't see the safaridriver element clear
@@ -15,4 +15,4 @@ private
15
15
  end
16
16
  end
17
17
 
18
- ::Selenium::WebDriver::Remote::Bridge.prepend CapybaraAtoms unless ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
18
+ Selenium::WebDriver::Remote::Bridge.prepend CapybaraAtoms unless ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
@@ -6,4 +6,4 @@ module PauseDurationFix
6
6
  end
7
7
  end
8
8
 
9
- ::Selenium::WebDriver::Interactions::Pause.prepend PauseDurationFix
9
+ Selenium::WebDriver::Interactions::Pause.prepend PauseDurationFix
@@ -16,48 +16,65 @@ module Capybara
16
16
 
17
17
  def initialize(app)
18
18
  @app = app
19
- @disable_markup = format(DISABLE_MARKUP_TEMPLATE, selector: self.class.selector_for(Capybara.disable_animation))
19
+ @disable_css_markup = format(DISABLE_CSS_MARKUP_TEMPLATE,
20
+ selector: self.class.selector_for(Capybara.disable_animation))
21
+ @disable_js_markup = +DISABLE_JS_MARKUP_TEMPLATE
20
22
  end
21
23
 
22
24
  def call(env)
23
- @status, @headers, @body = @app.call(env)
24
- return [@status, @headers, @body] unless html_content?
25
+ status, headers, body = @app.call(env)
26
+ return [status, headers, body] unless html_content?(headers)
25
27
 
26
- response = Rack::Response.new([], @status, @headers)
28
+ nonces = directive_nonces(headers).transform_values { |nonce| "nonce=\"#{nonce}\"" if nonce && !nonce.empty? }
29
+ response = Rack::Response.new([], status, headers)
27
30
 
28
- @body.each { |html| response.write insert_disable(html) }
29
- @body.close if @body.respond_to?(:close)
31
+ body.each { |html| response.write insert_disable(html, nonces) }
32
+ body.close if body.respond_to?(:close)
30
33
 
31
34
  response.finish
32
35
  end
33
36
 
34
37
  private
35
38
 
36
- attr_reader :disable_markup
39
+ attr_reader :disable_css_markup, :disable_js_markup
37
40
 
38
- def html_content?
39
- /html/.match?(@headers['Content-Type'])
41
+ def html_content?(headers)
42
+ /html/.match?(headers['Content-Type']) # rubocop:todo Performance/StringInclude
40
43
  end
41
44
 
42
- def insert_disable(html)
43
- html.sub(%r{(</body>)}, "#{disable_markup}\\1")
45
+ def insert_disable(html, nonces)
46
+ html.sub(%r{(</head>)}, "<style #{nonces['style-src']}>#{disable_css_markup}</style>\\1")
47
+ .sub(%r{(</body>)}, "<script #{nonces['script-src']}>#{disable_js_markup}</script>\\1")
44
48
  end
45
49
 
46
- DISABLE_MARKUP_TEMPLATE = <<~HTML
47
- <script>
50
+ def directive_nonces(headers)
51
+ headers.fetch('Content-Security-Policy', '')
52
+ .split(';')
53
+ .map(&:split) # rubocop:disable Style/MapToHash
54
+ .to_h do |s|
55
+ [
56
+ s[0], s[1..].filter_map do |value|
57
+ /^'nonce-(?<nonce>.+)'/ =~ value
58
+ nonce
59
+ end[0]
60
+ ]
61
+ end
62
+ end
63
+
64
+ DISABLE_CSS_MARKUP_TEMPLATE = <<~CSS
65
+ %<selector>s, %<selector>s::before, %<selector>s::after {
66
+ transition: none !important;
67
+ animation-duration: 0s !important;
68
+ animation-delay: 0s !important;
69
+ scroll-behavior: auto !important;
70
+ }
71
+ CSS
72
+
73
+ DISABLE_JS_MARKUP_TEMPLATE = <<~SCRIPT
48
74
  //<![CDATA[
49
75
  (typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
50
76
  //]]>
51
- </script>
52
- <style>
53
- %<selector>s, %<selector>s::before, %<selector>s::after {
54
- transition: none !important;
55
- animation-duration: 0s !important;
56
- animation-delay: 0s !important;
57
- scroll-behavior: auto !important;
58
- }
59
- </style>
60
- HTML
77
+ SCRIPT
61
78
  end
62
79
  end
63
80
  end
@@ -14,7 +14,7 @@ module Capybara
14
14
  end
15
15
 
16
16
  def decrement(uri)
17
- @mutex.synchronize { @value.delete_at(@value.index(uri) || @value.length) }
17
+ @mutex.synchronize { @value.delete_at(@value.index(uri) || - 1) }
18
18
  end
19
19
 
20
20
  def positive?
@@ -55,7 +55,7 @@ module Capybara
55
55
 
56
56
  res = @checker.request { |http| http.get('/__identify__') }
57
57
 
58
- return res.body == app.object_id.to_s if res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPRedirection)
58
+ res.body == app.object_id.to_s if res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPRedirection)
59
59
  rescue SystemCallError, Net::ReadTimeout, OpenSSL::SSL::SSLError
60
60
  false
61
61
  end
@@ -8,7 +8,7 @@ module Capybara
8
8
  automatic_reload match exact exact_text raise_server_errors visible_text_only
9
9
  automatic_label_click enable_aria_label save_path asset_host default_host app_host
10
10
  server_host server_port server_errors default_set_options disable_animation test_id
11
- predicates_wait default_normalize_ws w3c_click_offset enable_aria_role].freeze
11
+ predicates_wait default_normalize_ws w3c_click_offset enable_aria_role default_retry_interval].freeze
12
12
 
13
13
  attr_accessor(*OPTIONS)
14
14
 
@@ -21,6 +21,8 @@ module Capybara
21
21
  # See {Capybara.configure}
22
22
  # @!method default_max_wait_time
23
23
  # See {Capybara.configure}
24
+ # @!method default_retry_interval
25
+ # See {Capybara.configure}
24
26
  # @!method ignore_hidden_elements
25
27
  # See {Capybara.configure}
26
28
  # @!method automatic_reload
@@ -100,7 +102,7 @@ module Capybara
100
102
  remove_method :test_id=
101
103
  ##
102
104
  #
103
- # Set an attribue to be optionally matched against the locator for builtin selector types.
105
+ # Set an attribute to be optionally matched against the locator for builtin selector types.
104
106
  # This attribute will be checked by builtin selector types whenever id would normally be checked.
105
107
  # If `nil` then it will be ignored.
106
108
  #