capybara 3.35.3 → 3.38.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +90 -4
  3. data/README.md +28 -12
  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 +8 -13
  9. data/lib/capybara/minitest/spec.rb +2 -2
  10. data/lib/capybara/node/actions.rb +10 -5
  11. data/lib/capybara/node/base.rb +2 -1
  12. data/lib/capybara/node/document.rb +2 -2
  13. data/lib/capybara/node/element.rb +13 -2
  14. data/lib/capybara/node/finders.rb +9 -2
  15. data/lib/capybara/node/simple.rb +5 -1
  16. data/lib/capybara/queries/active_element_query.rb +18 -0
  17. data/lib/capybara/queries/ancestor_query.rb +2 -1
  18. data/lib/capybara/queries/base_query.rb +2 -2
  19. data/lib/capybara/queries/current_path_query.rb +1 -1
  20. data/lib/capybara/queries/selector_query.rb +38 -10
  21. data/lib/capybara/queries/sibling_query.rb +2 -1
  22. data/lib/capybara/queries/text_query.rb +1 -1
  23. data/lib/capybara/rack_test/browser.rb +63 -8
  24. data/lib/capybara/rack_test/driver.rb +4 -4
  25. data/lib/capybara/rack_test/form.rb +29 -7
  26. data/lib/capybara/rack_test/node.rb +10 -7
  27. data/lib/capybara/registration_container.rb +0 -3
  28. data/lib/capybara/registrations/drivers.rb +3 -3
  29. data/lib/capybara/registrations/servers.rb +17 -9
  30. data/lib/capybara/rspec/matcher_proxies.rb +3 -3
  31. data/lib/capybara/rspec/matchers/have_selector.rb +5 -5
  32. data/lib/capybara/rspec/matchers.rb +14 -14
  33. data/lib/capybara/selector/builders/css_builder.rb +1 -1
  34. data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
  35. data/lib/capybara/selector/css.rb +1 -1
  36. data/lib/capybara/selector/definition/button.rb +9 -4
  37. data/lib/capybara/selector/definition/checkbox.rb +1 -1
  38. data/lib/capybara/selector/definition/file_field.rb +1 -1
  39. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  40. data/lib/capybara/selector/definition/radio_button.rb +1 -1
  41. data/lib/capybara/selector/definition.rb +4 -2
  42. data/lib/capybara/selector/filter_set.rb +4 -7
  43. data/lib/capybara/selector/regexp_disassembler.rb +2 -5
  44. data/lib/capybara/selector/selector.rb +5 -1
  45. data/lib/capybara/selector.rb +1 -0
  46. data/lib/capybara/selenium/driver.rb +28 -11
  47. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
  48. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +1 -1
  49. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  50. data/lib/capybara/selenium/extensions/html5_drag.rb +2 -4
  51. data/lib/capybara/selenium/logger_suppressor.rb +4 -0
  52. data/lib/capybara/selenium/node.rb +74 -23
  53. data/lib/capybara/selenium/nodes/chrome_node.rb +1 -1
  54. data/lib/capybara/selenium/nodes/edge_node.rb +1 -1
  55. data/lib/capybara/selenium/nodes/firefox_node.rb +3 -3
  56. data/lib/capybara/selenium/nodes/safari_node.rb +4 -4
  57. data/lib/capybara/server/animation_disabler.rb +41 -23
  58. data/lib/capybara/server/middleware.rb +1 -1
  59. data/lib/capybara/session/config.rb +4 -2
  60. data/lib/capybara/session.rb +31 -32
  61. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  62. data/lib/capybara/spec/session/all_spec.rb +9 -13
  63. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  64. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  65. data/lib/capybara/spec/session/check_spec.rb +10 -0
  66. data/lib/capybara/spec/session/choose_spec.rb +6 -0
  67. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  68. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  69. data/lib/capybara/spec/session/find_spec.rb +6 -0
  70. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
  71. data/lib/capybara/spec/session/has_any_selectors_spec.rb +4 -0
  72. data/lib/capybara/spec/session/has_button_spec.rb +30 -0
  73. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  74. data/lib/capybara/spec/session/has_field_spec.rb +25 -1
  75. data/lib/capybara/spec/session/has_link_spec.rb +36 -0
  76. data/lib/capybara/spec/session/has_select_spec.rb +10 -4
  77. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  78. data/lib/capybara/spec/session/has_text_spec.rb +6 -14
  79. data/lib/capybara/spec/session/node_spec.rb +66 -1
  80. data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
  81. data/lib/capybara/spec/session/scroll_spec.rb +4 -4
  82. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  83. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  84. data/lib/capybara/spec/session/within_spec.rb +13 -0
  85. data/lib/capybara/spec/spec_helper.rb +12 -5
  86. data/lib/capybara/spec/test_app.rb +91 -14
  87. data/lib/capybara/spec/views/animated.erb +1 -1
  88. data/lib/capybara/spec/views/form.erb +24 -3
  89. data/lib/capybara/spec/views/frame_child.erb +1 -1
  90. data/lib/capybara/spec/views/frame_one.erb +1 -1
  91. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  92. data/lib/capybara/spec/views/frame_two.erb +1 -1
  93. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  94. data/lib/capybara/spec/views/layout.erb +10 -0
  95. data/lib/capybara/spec/views/obscured.erb +1 -1
  96. data/lib/capybara/spec/views/offset.erb +2 -1
  97. data/lib/capybara/spec/views/path.erb +2 -2
  98. data/lib/capybara/spec/views/popup_one.erb +1 -1
  99. data/lib/capybara/spec/views/popup_two.erb +1 -1
  100. data/lib/capybara/spec/views/react.erb +2 -2
  101. data/lib/capybara/spec/views/scroll.erb +2 -1
  102. data/lib/capybara/spec/views/spatial.erb +1 -1
  103. data/lib/capybara/spec/views/with_animation.erb +2 -3
  104. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  105. data/lib/capybara/spec/views/with_dragula.erb +2 -2
  106. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  107. data/lib/capybara/spec/views/with_hover.erb +2 -2
  108. data/lib/capybara/spec/views/with_html.erb +3 -3
  109. data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
  110. data/lib/capybara/spec/views/with_js.erb +2 -3
  111. data/lib/capybara/spec/views/with_jstree.erb +1 -1
  112. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  113. data/lib/capybara/spec/views/with_scope.erb +2 -2
  114. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  115. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  116. data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
  117. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  118. data/lib/capybara/spec/views/with_windows.erb +1 -1
  119. data/lib/capybara/spec/views/within_frames.erb +1 -1
  120. data/lib/capybara/version.rb +1 -1
  121. data/lib/capybara/window.rb +1 -1
  122. data/lib/capybara.rb +23 -24
  123. data/spec/basic_node_spec.rb +16 -3
  124. data/spec/capybara_spec.rb +12 -0
  125. data/spec/counter_spec.rb +35 -0
  126. data/spec/dsl_spec.rb +5 -3
  127. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  128. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  129. data/spec/minitest_spec.rb +4 -0
  130. data/spec/minitest_spec_spec.rb +4 -0
  131. data/spec/per_session_config_spec.rb +1 -1
  132. data/spec/rack_test_spec.rb +28 -10
  133. data/spec/result_spec.rb +32 -35
  134. data/spec/rspec/features_spec.rb +3 -3
  135. data/spec/rspec/scenarios_spec.rb +1 -1
  136. data/spec/rspec/shared_spec_matchers.rb +3 -3
  137. data/spec/rspec_spec.rb +2 -2
  138. data/spec/sauce_spec_chrome.rb +3 -3
  139. data/spec/selector_spec.rb +4 -4
  140. data/spec/selenium_spec_chrome.rb +11 -10
  141. data/spec/selenium_spec_chrome_remote.rb +11 -8
  142. data/spec/selenium_spec_edge.rb +2 -0
  143. data/spec/selenium_spec_firefox.rb +15 -4
  144. data/spec/selenium_spec_firefox_remote.rb +4 -2
  145. data/spec/selenium_spec_ie.rb +6 -7
  146. data/spec/selenium_spec_safari.rb +33 -19
  147. data/spec/server_spec.rb +5 -5
  148. data/spec/shared_selenium_node.rb +0 -4
  149. data/spec/shared_selenium_session.rb +23 -14
  150. data/spec/spec_helper.rb +1 -1
  151. metadata +38 -14
  152. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -9,7 +9,7 @@ module Capybara
9
9
 
10
10
  SPATIAL_KEYS = %i[above below left_of right_of near].freeze
11
11
  VALID_KEYS = SPATIAL_KEYS + COUNT_KEYS +
12
- %i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set]
12
+ %i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set focused]
13
13
  VALID_MATCH = %i[first smart prefer_exact one].freeze
14
14
 
15
15
  def initialize(*args,
@@ -27,6 +27,12 @@ module Capybara
27
27
  @order = order
28
28
  @filter_cache = Hash.new { |hsh, key| hsh[key] = {} }
29
29
 
30
+ if @options[:text].is_a?(Regexp) && [true, false].include?(@options[:exact_text])
31
+ Capybara::Helpers.warn(
32
+ "Boolean 'exact_text' option is not supported when 'text' option is a Regexp - ignoring"
33
+ )
34
+ end
35
+
30
36
  super(@options)
31
37
  self.session_options = session_options
32
38
 
@@ -73,6 +79,8 @@ module Capybara
73
79
 
74
80
  desc << " with id #{options[:id]}" if options[:id]
75
81
  desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
82
+ desc << ' that is focused' if options[:focused]
83
+ desc << ' that is not focused' if options[:focused] == false
76
84
 
77
85
  desc << case options[:style]
78
86
  when String
@@ -264,7 +272,7 @@ module Capybara
264
272
  end
265
273
 
266
274
  def valid_keys
267
- VALID_KEYS + custom_keys
275
+ (VALID_KEYS + custom_keys).uniq
268
276
  end
269
277
 
270
278
  def matches_node_filters?(node, errors)
@@ -371,6 +379,10 @@ module Capybara
371
379
  options.key?(:style) && !custom_keys.include?(:style)
372
380
  end
373
381
 
382
+ def use_default_focused_filter?
383
+ options.key?(:focused) && !custom_keys.include?(:focused)
384
+ end
385
+
374
386
  def use_spatial_filter?
375
387
  options.values_at(*SPATIAL_KEYS).compact.any?
376
388
  end
@@ -435,6 +447,7 @@ module Capybara
435
447
  matches_id_filter?(node) &&
436
448
  matches_class_filter?(node) &&
437
449
  matches_style_filter?(node) &&
450
+ matches_focused_filter?(node) &&
438
451
  matches_text_filter?(node) &&
439
452
  matches_exact_text_filter?(node)
440
453
  end
@@ -494,6 +507,12 @@ module Capybara
494
507
  end
495
508
  end
496
509
 
510
+ def matches_focused_filter?(node)
511
+ return true unless use_default_focused_filter?
512
+
513
+ (node == node.session.active_element) == options[:focused]
514
+ end
515
+
497
516
  def need_to_process_classes?
498
517
  case options[:class]
499
518
  when Regexp then true
@@ -528,16 +547,19 @@ module Capybara
528
547
  def matches_text_filter?(node)
529
548
  value = options[:text]
530
549
  return true unless value
531
- return matches_text_exactly?(node, value) if exact_text == true
550
+ return matches_text_exactly?(node, value) if exact_text == true && !value.is_a?(Regexp)
532
551
 
533
552
  regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
534
553
  matches_text_regexp?(node, regexp)
535
554
  end
536
555
 
537
556
  def matches_exact_text_filter?(node)
538
- return true unless exact_text.is_a?(String)
539
-
540
- matches_text_exactly?(node, exact_text)
557
+ case exact_text
558
+ when String, Regexp
559
+ matches_text_exactly?(node, exact_text)
560
+ else
561
+ true
562
+ end
541
563
  end
542
564
 
543
565
  def matches_visibility_filters?(node)
@@ -548,7 +570,9 @@ module Capybara
548
570
  when :visible
549
571
  node.initial_cache[:visible] || (node.initial_cache[:visible].nil? && node.visible?)
550
572
  when :hidden
551
- (node.initial_cache[:visible] == false) || (node.initial_cache[:visbile].nil? && !node.visible?)
573
+ # TODO: check why the 'visbile' cache spelling mistake wasn't caught in a test
574
+ # (node.initial_cache[:visible] == false) || (node.initial_cache[:visbile].nil? && !node.visible?)
575
+ (node.initial_cache[:visible] == false) || (node.initial_cache[:visible].nil? && !node.visible?)
552
576
  else
553
577
  true
554
578
  end
@@ -565,17 +589,21 @@ module Capybara
565
589
 
566
590
  def matches_text_exactly?(node, value)
567
591
  regexp = value.is_a?(Regexp) ? value : /\A#{Regexp.escape(value.to_s)}\z/
568
- matches_text_regexp?(node, regexp)
592
+ matches_text_regexp(node, regexp).then { |m| m&.pre_match == '' && m&.post_match == '' }
569
593
  end
570
594
 
571
595
  def normalize_ws
572
596
  options.fetch(:normalize_ws, session_options.default_normalize_ws)
573
597
  end
574
598
 
575
- def matches_text_regexp?(node, regexp)
599
+ def matches_text_regexp(node, regexp)
576
600
  text_visible = visible
577
601
  text_visible = :all if text_visible == :hidden
578
- node.text(text_visible, normalize_ws: normalize_ws).match?(regexp)
602
+ node.text(text_visible, normalize_ws: normalize_ws).match(regexp)
603
+ end
604
+
605
+ def matches_text_regexp?(node, regexp)
606
+ !matches_text_regexp(node, regexp).nil?
579
607
  end
580
608
 
581
609
  def default_visibility
@@ -7,7 +7,8 @@ module Capybara
7
7
  def resolve_for(node, exact = nil)
8
8
  @sibling_node = node
9
9
  node.synchronize do
10
- match_results = super(node.session.current_scope, exact)
10
+ scope = node.respond_to?(:session) ? node.session.current_scope : node.find(:xpath, '/*')
11
+ match_results = super(scope, exact)
11
12
  siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
12
13
  .map(&method(:to_element))
13
14
  .select { |el| match_results.include?(el) }
@@ -13,7 +13,7 @@ module Capybara
13
13
  self.session_options = session_options
14
14
 
15
15
  if expected_text.nil? && !exact?
16
- warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. '\
16
+ warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. ' \
17
17
  "Please specify a string or regexp instead. #{Capybara::Helpers.filter_backtrace(caller)}"
18
18
  end
19
19
 
@@ -20,6 +20,8 @@ class Capybara::RackTest::Browser
20
20
  end
21
21
 
22
22
  def visit(path, **attributes)
23
+ @new_visit_request = true
24
+ reset_cache!
23
25
  reset_host!
24
26
  process_and_follow_redirects(:get, path, attributes)
25
27
  end
@@ -29,23 +31,28 @@ class Capybara::RackTest::Browser
29
31
  request(last_request.fullpath, last_request.env)
30
32
  end
31
33
 
32
- def submit(method, path, attributes)
34
+ def submit(method, path, attributes, content_type: nil)
33
35
  path = request_path if path.nil? || path.empty?
34
36
  uri = build_uri(path)
35
37
  uri.query = '' if method.to_s.casecmp('get').zero?
36
- process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
38
+ process_and_follow_redirects(
39
+ method,
40
+ uri.to_s,
41
+ attributes,
42
+ 'HTTP_REFERER' => referer_url,
43
+ 'CONTENT_TYPE' => content_type
44
+ )
37
45
  end
38
46
 
39
47
  def follow(method, path, **attributes)
40
48
  return if fragment_or_script?(path)
41
49
 
42
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
50
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
43
51
  end
44
52
 
45
53
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
46
54
  @current_fragment = build_uri(path).fragment
47
55
  process(method, path, attributes, env)
48
-
49
56
  return unless driver.follow_redirects?
50
57
 
51
58
  driver.redirect_limit.times do
@@ -69,18 +76,23 @@ class Capybara::RackTest::Browser
69
76
  @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
70
77
  @current_fragment = new_uri.fragment || @current_fragment
71
78
  reset_cache!
79
+ @new_visit_request = false
72
80
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
73
81
  end
74
82
 
75
83
  def build_uri(path)
76
- URI.parse(path).tap do |uri|
77
- uri.path = request_path if path.empty? || path.start_with?('?')
78
- uri.path = '/' if uri.path.empty?
79
- uri.path = request_path.sub(%r{/[^/]*$}, '/') + uri.path unless uri.path.start_with?('/')
84
+ uri = URI.parse(path)
85
+ base_uri = base_relative_uri_for(uri)
80
86
 
87
+ uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')
88
+
89
+ if base_uri.absolute?
90
+ base_uri.merge(uri)
91
+ else
81
92
  uri.scheme ||= @current_scheme
82
93
  uri.host ||= @current_host
83
94
  uri.port ||= @current_port unless uri.default_port == @current_port
95
+ uri
84
96
  end
85
97
  end
86
98
 
@@ -123,8 +135,39 @@ class Capybara::RackTest::Browser
123
135
  dom.title
124
136
  end
125
137
 
138
+ def last_request
139
+ raise Rack::Test::Error if @new_visit_request
140
+
141
+ super
142
+ end
143
+
144
+ def last_response
145
+ raise Rack::Test::Error if @new_visit_request
146
+
147
+ super
148
+ end
149
+
126
150
  protected
127
151
 
152
+ def base_href
153
+ find(:css, 'head > base').first&.[](:href).to_s
154
+ end
155
+
156
+ def base_relative_uri_for(uri)
157
+ base_uri = URI.parse(base_href)
158
+ current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
159
+ c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
160
+ c.path = '/' if c.path.empty?
161
+ end
162
+
163
+ if [current_uri, base_uri].any?(&:absolute?)
164
+ current_uri.merge(base_uri)
165
+ else
166
+ base_uri.path = current_uri.path if base_uri.path.empty?
167
+ base_uri
168
+ end
169
+ end
170
+
128
171
  def build_rack_mock_session
129
172
  reset_host! unless current_host
130
173
  Rack::MockSession.new(app, current_host)
@@ -136,9 +179,21 @@ protected
136
179
  '/'
137
180
  end
138
181
 
182
+ def safe_last_request
183
+ last_request
184
+ rescue Rack::Test::Error
185
+ nil
186
+ end
187
+
139
188
  private
140
189
 
141
190
  def fragment_or_script?(path)
142
191
  path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
143
192
  end
193
+
194
+ def referer_url
195
+ build_uri(last_request.url).to_s
196
+ rescue Rack::Test::Error
197
+ ''
198
+ end
144
199
  end
@@ -98,10 +98,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
98
98
  @browser = nil
99
99
  end
100
100
 
101
- def get(*args, &block); browser.get(*args, &block); end
102
- def post(*args, &block); browser.post(*args, &block); end
103
- def put(*args, &block); browser.put(*args, &block); end
104
- def delete(*args, &block); browser.delete(*args, &block); end
101
+ def get(...); browser.get(...); end
102
+ def post(...); browser.post(...); end
103
+ def put(...); browser.put(...); end
104
+ def delete(...); browser.delete(...); end
105
105
  def header(key, value); browser.header(key, value); end
106
106
 
107
107
  def invalid_element_errors
@@ -16,6 +16,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
16
16
  def path; @empty_file.path; end
17
17
  def size; 0; end
18
18
  def read; ''; end
19
+ def append_to(_); end
20
+ def set_encoding(_); end # rubocop:disable Naming/AccessorMethodName
19
21
  end
20
22
 
21
23
  def params(button)
@@ -28,19 +30,31 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
28
30
 
29
31
  form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) }
30
32
 
31
- form_elements.each_with_object(make_params) do |field, params|
33
+ form_params = form_elements.each_with_object({}.compare_by_identity) do |field, params|
32
34
  case field.name
33
35
  when 'input', 'button' then add_input_param(field, params)
34
36
  when 'select' then add_select_param(field, params)
35
37
  when 'textarea' then add_textarea_param(field, params)
36
38
  end
39
+ end
40
+
41
+ form_params.each_with_object(make_params) do |(name, value), params|
42
+ merge_param!(params, name, value)
37
43
  end.to_params_hash
44
+
45
+ # form_elements.each_with_object(make_params) do |field, params|
46
+ # case field.name
47
+ # when 'input', 'button' then add_input_param(field, params)
48
+ # when 'select' then add_select_param(field, params)
49
+ # when 'textarea' then add_textarea_param(field, params)
50
+ # end
51
+ # end.to_params_hash
38
52
  end
39
53
 
40
54
  def submit(button)
41
55
  action = button&.[]('formaction') || native['action']
42
56
  method = button&.[]('formmethod') || request_method
43
- driver.submit(method, action.to_s, params(button))
57
+ driver.submit(method, action.to_s, params(button), content_type: native['enctype'])
44
58
  end
45
59
 
46
60
  def multipart?
@@ -86,6 +100,8 @@ private
86
100
 
87
101
  Capybara::RackTest::Node.new(driver, field).value.to_s
88
102
  when 'file'
103
+ return if value.empty? && params.keys.include?(name) && Rack::Test::VERSION.to_f >= 2.0 # rubocop:disable Performance/InefficientHashSearch
104
+
89
105
  if multipart?
90
106
  file_to_upload(value)
91
107
  else
@@ -94,7 +110,8 @@ private
94
110
  else
95
111
  value
96
112
  end
97
- merge_param!(params, name, value)
113
+ # merge_param!(params, name, value)
114
+ params[name] = value
98
115
  end
99
116
 
100
117
  def file_to_upload(filename)
@@ -107,18 +124,23 @@ private
107
124
  end
108
125
 
109
126
  def add_select_param(field, params)
127
+ name = field['name']
110
128
  if field.has_attribute?('multiple')
111
- field.xpath('.//option[@selected]').each do |option|
112
- merge_param!(params, field['name'], (option['value'] || option.text).to_s)
129
+ value = field.xpath('.//option[@selected]').map do |option|
130
+ # merge_param!(params, field['name'], (option['value'] || option.text).to_s)
131
+ (option['value'] || option.text).to_s
113
132
  end
133
+ params[name] = value unless value.empty?
114
134
  else
115
135
  option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
116
- merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
136
+ # merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
137
+ params[name] = (option['value'] || option.text).to_s if option
117
138
  end
118
139
  end
119
140
 
120
141
  def add_textarea_param(field, params)
121
- merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
142
+ # merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n"))
143
+ params[field['name']] = field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n")
122
144
  end
123
145
 
124
146
  def submitter?(el)
@@ -108,6 +108,13 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
108
108
  end
109
109
  end
110
110
 
111
+ def readonly?
112
+ # readonly attribute not valid on these input types
113
+ return false if input_field? && %w[hidden range color checkbox radio file submit image reset button].include?(type)
114
+
115
+ super
116
+ end
117
+
111
118
  def path
112
119
  native.path
113
120
  end
@@ -139,10 +146,6 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
139
146
  end
140
147
  end
141
148
 
142
- def ==(other)
143
- native == other.native
144
- end
145
-
146
149
  protected
147
150
 
148
151
  # @api private
@@ -159,7 +162,7 @@ protected
159
162
  end.join || ''
160
163
  text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
161
164
  text
162
- else
165
+ else # rubocop:disable Lint/DuplicateBranch
163
166
  ''
164
167
  end
165
168
  end
@@ -213,7 +216,7 @@ private
213
216
  min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
214
217
  value = value.to_f
215
218
  value = value.clamp(min, max)
216
- value = ((value - min) / step).round * step + min
219
+ value = (((value - min) / step).round * step) + min
217
220
  native['value'] = value.clamp(min, max)
218
221
  end
219
222
 
@@ -241,7 +244,7 @@ private
241
244
  end
242
245
 
243
246
  def follow_link
244
- method = self['data-method'] if driver.options[:respect_data_method]
247
+ method = self['data-method'] || self['data-turbo-method'] if driver.options[:respect_data_method]
245
248
  method ||= :get
246
249
  driver.follow(method, self[:href].to_s)
247
250
  end
@@ -19,9 +19,6 @@ module Capybara
19
19
  def method_missing(method_name, *args, **options, &block)
20
20
  if @registered.respond_to?(method_name)
21
21
  Capybara::Helpers.warn "DEPRECATED: Calling '#{method_name}' on the drivers/servers container is deprecated without replacement"
22
- # RUBY 2.6 will send an empty hash rather than nothing with **options so fix that
23
- return @registered.public_send(method_name, *args, &block) if options.empty?
24
-
25
22
  return @registered.public_send(method_name, *args, **options, &block)
26
23
  end
27
24
  super
@@ -14,7 +14,7 @@ Capybara.register_driver :selenium_headless do |app|
14
14
  browser_options = ::Selenium::WebDriver::Firefox::Options.new.tap do |opts|
15
15
  opts.add_argument '-headless'
16
16
  end
17
- Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
17
+ Capybara::Selenium::Driver.new(app, **{ :browser => :firefox, options_key => browser_options })
18
18
  end
19
19
 
20
20
  Capybara.register_driver :selenium_chrome do |app|
@@ -25,7 +25,7 @@ Capybara.register_driver :selenium_chrome do |app|
25
25
  opts.add_argument('--disable-site-isolation-trials')
26
26
  end
27
27
 
28
- Capybara::Selenium::Driver.new(app, **Hash[:browser => :chrome, options_key => browser_options])
28
+ Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
29
29
  end
30
30
 
31
31
  Capybara.register_driver :selenium_chrome_headless do |app|
@@ -38,5 +38,5 @@ Capybara.register_driver :selenium_chrome_headless do |app|
38
38
  opts.add_argument('--disable-site-isolation-trials')
39
39
  end
40
40
 
41
- Capybara::Selenium::Driver.new(app, **Hash[:browser => :chrome, options_key => browser_options])
41
+ Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
42
42
  end
@@ -10,7 +10,7 @@ Capybara.register_server :webrick do |app, port, host, **options|
10
10
  Rack::Handler::WEBrick.run(app, **options)
11
11
  end
12
12
 
13
- Capybara.register_server :puma do |app, port, host, **options|
13
+ Capybara.register_server :puma do |app, port, host, **options| # rubocop:disable Metrics/BlockLength
14
14
  begin
15
15
  require 'rack/handler/puma'
16
16
  rescue LoadError
@@ -29,17 +29,25 @@ Capybara.register_server :puma do |app, port, host, **options|
29
29
 
30
30
  conf = Rack::Handler::Puma.config(app, options)
31
31
  conf.clamp
32
- events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
33
32
 
34
33
  puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
35
34
  require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)
36
35
 
37
- events.log 'Capybara starting Puma...'
38
- events.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
39
- events.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
40
-
41
- Puma::Server.new(conf.app, events, conf.options).tap do |s|
42
- s.binder.parse conf.options[:binds], s.events
43
- s.min_threads, s.max_threads = conf.options[:min_threads], conf.options[:max_threads]
36
+ logger = (defined?(::Puma::LogWriter) ? ::Puma::LogWriter : ::Puma::Events).then do |cls|
37
+ conf.options[:Silent] ? cls.strings : cls.stdio
38
+ end
39
+ conf.options[:log_writer] = logger
40
+
41
+ logger.log 'Capybara starting Puma...'
42
+ logger.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
43
+ logger.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
44
+
45
+ Puma::Server.new(
46
+ conf.app,
47
+ defined?(::Puma::LogWriter) ? nil : logger,
48
+ conf.options
49
+ ).tap do |s|
50
+ s.binder.parse conf.options[:binds], (s.log_writer rescue s.events) # rubocop:disable Style/RescueModifier
51
+ s.min_threads, s.max_threads = conf.options[:min_threads], conf.options[:max_threads] if s.respond_to? :min_threads=
44
52
  end.run.join
45
53
  end
@@ -23,7 +23,7 @@ end
23
23
  if RUBY_ENGINE == 'jruby'
24
24
  # :nocov:
25
25
  module Capybara::DSL
26
- class <<self
26
+ class << self
27
27
  remove_method :included
28
28
 
29
29
  def included(base)
@@ -55,7 +55,7 @@ else
55
55
  end
56
56
 
57
57
  def self.prepended(base)
58
- class <<base
58
+ class << base
59
59
  prepend ClassMethods
60
60
  end
61
61
  end
@@ -70,7 +70,7 @@ else
70
70
  end
71
71
 
72
72
  def self.prepended(base)
73
- class <<base
73
+ class << base
74
74
  prepend ClassMethods
75
75
  end
76
76
  end
@@ -8,10 +8,10 @@ module Capybara
8
8
  class HaveSelector < CountableWrappedElementMatcher
9
9
  def initialize(*args, **kw_args, &filter_block)
10
10
  super
11
- if (RUBY_VERSION >= '2.7') && (@args.size < 2) && @kw_args.keys.any?(String) # rubocop:disable Style/GuardClause
12
- @args.push(@kw_args)
13
- @kw_args = {}
14
- end
11
+ return unless (@args.size < 2) && @kw_args.keys.any?(String)
12
+
13
+ @args.push(@kw_args)
14
+ @kw_args = {}
15
15
  end
16
16
 
17
17
  def element_matches?(el)
@@ -64,7 +64,7 @@ module Capybara
64
64
  el.assert_any_of_selectors(*@args, **session_query_options, &@filter_block)
65
65
  end
66
66
 
67
- def does_not_match?(_actual)
67
+ def does_not_match?(el)
68
68
  el.assert_none_of_selectors(*@args, **session_query_options, &@filter_block)
69
69
  end
70
70
 
@@ -15,36 +15,36 @@ module Capybara
15
15
  # RSpec matcher for whether the element(s) matching a given selector exist.
16
16
  #
17
17
  # @see Capybara::Node::Matchers#assert_selector
18
- def have_selector(*args, **kw_args, &optional_filter_block)
19
- Matchers::HaveSelector.new(*args, **kw_args, &optional_filter_block)
18
+ def have_selector(...)
19
+ Matchers::HaveSelector.new(...)
20
20
  end
21
21
 
22
22
  # RSpec matcher for whether the element(s) matching a group of selectors exist.
23
23
  #
24
24
  # @see Capybara::Node::Matchers#assert_all_of_selectors
25
- def have_all_of_selectors(*args, **kw_args, &optional_filter_block)
26
- Matchers::HaveAllSelectors.new(*args, **kw_args, &optional_filter_block)
25
+ def have_all_of_selectors(...)
26
+ Matchers::HaveAllSelectors.new(...)
27
27
  end
28
28
 
29
29
  # RSpec matcher for whether no element(s) matching a group of selectors exist.
30
30
  #
31
31
  # @see Capybara::Node::Matchers#assert_none_of_selectors
32
- def have_none_of_selectors(*args, **kw_args, &optional_filter_block)
33
- Matchers::HaveNoSelectors.new(*args, **kw_args, &optional_filter_block)
32
+ def have_none_of_selectors(...)
33
+ Matchers::HaveNoSelectors.new(...)
34
34
  end
35
35
 
36
36
  # RSpec matcher for whether the element(s) matching any of a group of selectors exist.
37
37
  #
38
38
  # @see Capybara::Node::Matchers#assert_any_of_selectors
39
- def have_any_of_selectors(*args, **kw_args, &optional_filter_block)
40
- Matchers::HaveAnySelectors.new(*args, **kw_args, &optional_filter_block)
39
+ def have_any_of_selectors(...)
40
+ Matchers::HaveAnySelectors.new(...)
41
41
  end
42
42
 
43
43
  # RSpec matcher for whether the current element matches a given selector.
44
44
  #
45
45
  # @see Capybara::Node::Matchers#assert_matches_selector
46
- def match_selector(*args, **kw_args, &optional_filter_block)
47
- Matchers::MatchSelector.new(*args, **kw_args, &optional_filter_block)
46
+ def match_selector(...)
47
+ Matchers::MatchSelector.new(...)
48
48
  end
49
49
 
50
50
  %i[css xpath].each do |selector|
@@ -177,15 +177,15 @@ module Capybara
177
177
  # RSpec matcher for whether sibling element(s) matching a given selector exist.
178
178
  #
179
179
  # @see Capybara::Node::Matchers#assert_sibling
180
- def have_sibling(*args, **kw_args, &optional_filter_block)
181
- Matchers::HaveSibling.new(*args, **kw_args, &optional_filter_block)
180
+ def have_sibling(...)
181
+ Matchers::HaveSibling.new(...)
182
182
  end
183
183
 
184
184
  # RSpec matcher for whether ancestor element(s) matching a given selector exist.
185
185
  #
186
186
  # @see Capybara::Node::Matchers#assert_ancestor
187
- def have_ancestor(*args, **kw_args, &optional_filter_block)
188
- Matchers::HaveAncestor.new(*args, **kw_args, &optional_filter_block)
187
+ def have_ancestor(...)
188
+ Matchers::HaveAncestor.new(...)
189
189
  end
190
190
 
191
191
  ##
@@ -76,7 +76,7 @@ module Capybara
76
76
  else
77
77
  cls = Array(classes).reject { |c| c.is_a? Regexp }.group_by { |cl| cl.match?(/^!(?!!!)/) }
78
78
  [(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
79
- cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
79
+ cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..))})" }).join]
80
80
  end
81
81
  end
82
82
  end
@@ -51,7 +51,7 @@ module Capybara
51
51
  else
52
52
  Array(classes).reject { |c| c.is_a? Regexp }.map do |klass|
53
53
  if klass.match?(/^!(?!!!)/)
54
- !XPath.attr(:class).contains_word(klass.slice(1..-1))
54
+ !XPath.attr(:class).contains_word(klass.slice(1..))
55
55
  else
56
56
  XPath.attr(:class).contains_word(klass.sub(/^!!/, ''))
57
57
  end
@@ -43,7 +43,7 @@ module Capybara
43
43
  when '"', "'"
44
44
  selector << parse_string(char, str)
45
45
  when '\\'
46
- selector << char + str.getc
46
+ selector << (char + str.getc)
47
47
  when ','
48
48
  selectors << selector.strip
49
49
  selector.clear