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
@@ -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,29 @@ 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
+ env = { 'HTTP_REFERER' => referer_url }
39
+ env['CONTENT_TYPE'] = content_type if content_type
40
+ process_and_follow_redirects(
41
+ method,
42
+ uri.to_s,
43
+ attributes,
44
+ env
45
+ )
37
46
  end
38
47
 
39
48
  def follow(method, path, **attributes)
40
49
  return if fragment_or_script?(path)
41
50
 
42
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
51
+ process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
43
52
  end
44
53
 
45
54
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
46
55
  @current_fragment = build_uri(path).fragment
47
56
  process(method, path, attributes, env)
48
-
49
57
  return unless driver.follow_redirects?
50
58
 
51
59
  driver.redirect_limit.times do
@@ -69,18 +77,23 @@ class Capybara::RackTest::Browser
69
77
  @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
70
78
  @current_fragment = new_uri.fragment || @current_fragment
71
79
  reset_cache!
80
+ @new_visit_request = false
72
81
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
73
82
  end
74
83
 
75
84
  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?('/')
85
+ uri = URI.parse(path)
86
+ base_uri = base_relative_uri_for(uri)
80
87
 
88
+ uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')
89
+
90
+ if base_uri.absolute?
91
+ base_uri.merge(uri)
92
+ else
81
93
  uri.scheme ||= @current_scheme
82
94
  uri.host ||= @current_host
83
95
  uri.port ||= @current_port unless uri.default_port == @current_port
96
+ uri
84
97
  end
85
98
  end
86
99
 
@@ -123,8 +136,39 @@ class Capybara::RackTest::Browser
123
136
  dom.title
124
137
  end
125
138
 
139
+ def last_request
140
+ raise Rack::Test::Error if @new_visit_request
141
+
142
+ super
143
+ end
144
+
145
+ def last_response
146
+ raise Rack::Test::Error if @new_visit_request
147
+
148
+ super
149
+ end
150
+
126
151
  protected
127
152
 
153
+ def base_href
154
+ find(:css, 'head > base').first&.[](:href).to_s
155
+ end
156
+
157
+ def base_relative_uri_for(uri)
158
+ base_uri = URI.parse(base_href)
159
+ current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
160
+ c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
161
+ c.path = '/' if c.path.empty?
162
+ end
163
+
164
+ if [current_uri, base_uri].any?(&:absolute?)
165
+ current_uri.merge(base_uri)
166
+ else
167
+ base_uri.path = current_uri.path if base_uri.path.empty?
168
+ base_uri
169
+ end
170
+ end
171
+
128
172
  def build_rack_mock_session
129
173
  reset_host! unless current_host
130
174
  Rack::MockSession.new(app, current_host)
@@ -136,9 +180,21 @@ protected
136
180
  '/'
137
181
  end
138
182
 
183
+ def safe_last_request
184
+ last_request
185
+ rescue Rack::Test::Error
186
+ nil
187
+ end
188
+
139
189
  private
140
190
 
141
191
  def fragment_or_script?(path)
142
192
  path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
143
193
  end
194
+
195
+ def referer_url
196
+ build_uri(last_request.url).to_s
197
+ rescue Rack::Test::Error
198
+ ''
199
+ end
144
200
  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)
@@ -1,25 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'capybara/rack_test/errors'
4
+ require 'capybara/node/whitespace_normalizer'
4
5
 
5
6
  class Capybara::RackTest::Node < Capybara::Driver::Node
7
+ include Capybara::Node::WhitespaceNormalizer
8
+
6
9
  BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
7
10
 
8
11
  def all_text
9
- native.text
10
- .gsub(/[\u200b\u200e\u200f]/, '')
11
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
12
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
13
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
14
- .tr("\u00a0", ' ')
12
+ normalize_spacing(native.text)
15
13
  end
16
14
 
17
15
  def visible_text
18
- displayed_text.squeeze(' ')
19
- .gsub(/[\ \n]*\n[\ \n]*/, "\n")
20
- .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
- .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
22
- .tr("\u00a0", ' ')
16
+ normalize_visible_spacing(displayed_text)
23
17
  end
24
18
 
25
19
  def [](name)
@@ -108,6 +102,13 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
108
102
  end
109
103
  end
110
104
 
105
+ def readonly?
106
+ # readonly attribute not valid on these input types
107
+ return false if input_field? && %w[hidden range color checkbox radio file submit image reset button].include?(type)
108
+
109
+ super
110
+ end
111
+
111
112
  def path
112
113
  native.path
113
114
  end
@@ -124,23 +125,12 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
124
125
  alias_method "unchecked_#{meth_name}", meth_name
125
126
  private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
126
127
 
127
- if RUBY_VERSION >= '2.7'
128
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
129
- def #{meth_name}(...)
130
- stale_check
131
- method(:"unchecked_#{meth_name}").call(...)
132
- end
133
- METHOD
134
- else
135
- define_method meth_name do |*args|
128
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
129
+ def #{meth_name}(...)
136
130
  stale_check
137
- send("unchecked_#{meth_name}", *args)
131
+ method(:"unchecked_#{meth_name}").call(...)
138
132
  end
139
- end
140
- end
141
-
142
- def ==(other)
143
- native == other.native
133
+ METHOD
144
134
  end
145
135
 
146
136
  protected
@@ -150,16 +140,18 @@ protected
150
140
  if !string_node.visible?(check_ancestor)
151
141
  ''
152
142
  elsif native.text?
153
- native.text
154
- .gsub(/[\u200b\u200e\u200f]/, '')
155
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
143
+ native
144
+ .text
145
+ .delete(REMOVED_CHARACTERS)
146
+ .tr(SQUEEZED_SPACES, ' ')
147
+ .squeeze(' ')
156
148
  elsif native.element?
157
149
  text = native.children.map do |child|
158
150
  Capybara::RackTest::Node.new(driver, child).displayed_text(check_ancestor: false)
159
151
  end.join || ''
160
152
  text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
161
153
  text
162
- else
154
+ else # rubocop:disable Lint/DuplicateBranch
163
155
  ''
164
156
  end
165
157
  end
@@ -213,7 +205,7 @@ private
213
205
  min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
214
206
  value = value.to_f
215
207
  value = value.clamp(min, max)
216
- value = ((value - min) / step).round * step + min
208
+ value = (((value - min) / step).round * step) + min
217
209
  native['value'] = value.clamp(min, max)
218
210
  end
219
211
 
@@ -232,7 +224,14 @@ private
232
224
  end
233
225
  native.remove
234
226
  else
235
- native['value'] = value.to_s
227
+ value.to_s.tap do |set_value|
228
+ if set_value.end_with?("\n") && form&.css('input, textarea')&.count == 1
229
+ native['value'] = set_value.to_s.chop
230
+ Capybara::RackTest::Form.new(driver, form).submit(self)
231
+ else
232
+ native['value'] = set_value
233
+ end
234
+ end
236
235
  end
237
236
  end
238
237
 
@@ -241,7 +240,7 @@ private
241
240
  end
242
241
 
243
242
  def follow_link
244
- method = self['data-method'] if driver.options[:respect_data_method]
243
+ method = self['data-method'] || self['data-turbo-method'] if driver.options[:respect_data_method]
245
244
  method ||= :get
246
245
  driver.follow(method, self[:href].to_s)
247
246
  end
@@ -16,13 +16,10 @@ module Capybara
16
16
  @registered[name] = value
17
17
  end
18
18
 
19
- def method_missing(method_name, *args, **options, &block)
19
+ def method_missing(method_name, ...)
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
- return @registered.public_send(method_name, *args, **options, &block)
22
+ return @registered.public_send(method_name, ...)
26
23
  end
27
24
  super
28
25
  end
@@ -11,32 +11,32 @@ end
11
11
  Capybara.register_driver :selenium_headless do |app|
12
12
  version = Capybara::Selenium::Driver.load_selenium
13
13
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
14
- browser_options = ::Selenium::WebDriver::Firefox::Options.new.tap do |opts|
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|
21
21
  version = Capybara::Selenium::Driver.load_selenium
22
22
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
23
- browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
23
+ browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
24
24
  # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
25
25
  opts.add_argument('--disable-site-isolation-trials')
26
26
  end
27
27
 
28
- Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, 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|
32
32
  version = Capybara::Selenium::Driver.load_selenium
33
33
  options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
34
- browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
35
- opts.add_argument('--headless')
34
+ browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
35
+ opts.add_argument('--headless=new')
36
36
  opts.add_argument('--disable-gpu') if Gem.win_platform?
37
37
  # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
38
38
  opts.add_argument('--disable-site-isolation-trials')
39
39
  end
40
40
 
41
- Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
41
+ Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
42
42
  end
@@ -5,41 +5,62 @@ Capybara.register_server :default do |app, port, _host|
5
5
  end
6
6
 
7
7
  Capybara.register_server :webrick do |app, port, host, **options|
8
- require 'rack/handler/webrick'
8
+ base_class = begin
9
+ require 'rack/handler/webrick'
10
+ Rack
11
+ rescue LoadError
12
+ # Rack 3 separated out the webrick handle - no way test currently in Capybaras automated
13
+ # tests due to Sinatra not yet supporting Rack 3 - experimental
14
+ require 'rackup/handler/webrick'
15
+ Rackup
16
+ end
9
17
  options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
10
- Rack::Handler::WEBrick.run(app, **options)
18
+ base_class::Handler::WEBrick.run(app, **options)
11
19
  end
12
20
 
13
- Capybara.register_server :puma do |app, port, host, **options|
21
+ Capybara.register_server :puma do |app, port, host, **options| # rubocop:disable Metrics/BlockLength
22
+ begin
23
+ require 'rackup'
24
+ rescue LoadError # rubocop:disable Lint/SuppressedException
25
+ end
14
26
  begin
15
27
  require 'rack/handler/puma'
16
28
  rescue LoadError
17
29
  raise LoadError, 'Capybara is unable to load `puma` for its server, please add `puma` to your project or specify a different server via something like `Capybara.server = :webrick`.'
18
- else
19
- unless Rack::Handler::Puma.respond_to?(:config)
20
- raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
21
- end
30
+ end
31
+ puma_rack_handler = defined?(Rackup::Handler::Puma) ? Rackup::Handler::Puma : Rack::Handler::Puma
32
+
33
+ unless puma_rack_handler.respond_to?(:config)
34
+ raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
22
35
  end
23
36
 
24
37
  # If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
25
38
  # Therefore construct and run the Server instance ourselves.
26
- # Rack::Handler::Puma.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
39
+ # puma_rack_handler.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
27
40
  default_options = { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }
28
41
  options = default_options.merge(options)
29
42
 
30
- conf = Rack::Handler::Puma.config(app, options)
43
+ conf = puma_rack_handler.config(app, options)
31
44
  conf.clamp
32
- events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
33
45
 
34
46
  puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
35
47
  require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)
36
48
 
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]}"
49
+ logger = (defined?(Puma::LogWriter) ? Puma::LogWriter : Puma::Events).then do |cls|
50
+ conf.options[:Silent] ? cls.strings : cls.stdio
51
+ end
52
+ conf.options[:log_writer] = logger
53
+
54
+ logger.log 'Capybara starting Puma...'
55
+ logger.log "* Version #{Puma::Const::PUMA_VERSION}, codename: #{Puma::Const::CODE_NAME}"
56
+ logger.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
40
57
 
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]
58
+ Puma::Server.new(
59
+ conf.app,
60
+ defined?(Puma::LogWriter) ? nil : logger,
61
+ conf.options
62
+ ).tap do |s|
63
+ s.binder.parse conf.options[:binds], (s.log_writer rescue s.events) # rubocop:disable Style/RescueModifier
64
+ s.min_threads, s.max_threads = conf.options[:min_threads], conf.options[:max_threads] if s.respond_to? :min_threads=
44
65
  end.run.join
45
66
  end
@@ -34,7 +34,7 @@ module Capybara
34
34
  @allow_reload = false
35
35
  end
36
36
 
37
- def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
37
+ def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample, :to_ary
38
38
 
39
39
  alias index find_index
40
40
 
@@ -116,7 +116,7 @@ module Capybara
116
116
  message << ' but there were no matches'
117
117
  else
118
118
  message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " \
119
- << full_results.map(&:text).map(&:inspect).join(', ')
119
+ << full_results.map { |r| r.text.inspect }.join(', ')
120
120
  end
121
121
  unless rest.empty?
122
122
  elements = rest.map { |el| el.text rescue '<<ERROR>>' }.map(&:inspect).join(', ') # rubocop:disable Style/RescueModifier
@@ -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)
@@ -36,7 +36,7 @@ if RUBY_ENGINE == 'jruby'
36
36
  end
37
37
  end
38
38
 
39
- if defined?(::RSpec::Matchers)
39
+ if defined?(RSpec::Matchers)
40
40
  module ::RSpec::Matchers
41
41
  def self.included(base)
42
42
  base.send(:include, ::Capybara::RSpecMatcherProxies) if base.include?(::Capybara::DSL)
@@ -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,13 +70,13 @@ 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
77
77
  end
78
78
 
79
- Capybara::DSL.prepend ::Capybara::DSLRSpecProxyInstaller
79
+ Capybara::DSL.prepend Capybara::DSLRSpecProxyInstaller
80
80
 
81
- ::RSpec::Matchers.prepend ::Capybara::RSpecMatcherProxyInstaller if defined?(::RSpec::Matchers)
81
+ RSpec::Matchers.prepend Capybara::RSpecMatcherProxyInstaller if defined?(RSpec::Matchers)
82
82
  end
@@ -47,14 +47,16 @@ module Capybara
47
47
  end
48
48
 
49
49
  class WrappedElementMatcher < Base
50
- def matches?(actual)
50
+ def matches?(actual, &filter_block)
51
+ @filter_block ||= filter_block
51
52
  element_matches?(wrap(actual))
52
53
  rescue Capybara::ExpectationNotMet => e
53
54
  @failure_message = e.message
54
55
  false
55
56
  end
56
57
 
57
- def does_not_match?(actual)
58
+ def does_not_match?(actual, &filter_block)
59
+ @filter_block ||= filter_block
58
60
  element_does_not_match?(wrap(actual))
59
61
  rescue Capybara::ExpectationNotMet => e
60
62
  @failure_message_when_negated = e.message
@@ -86,12 +88,12 @@ module Capybara
86
88
  @matcher = matcher
87
89
  end
88
90
 
89
- def matches?(actual)
90
- @matcher.does_not_match?(actual)
91
+ def matches?(actual, &filter_block)
92
+ @matcher.does_not_match?(actual, &filter_block)
91
93
  end
92
94
 
93
- def does_not_match?(actual)
94
- @matcher.matches?(actual)
95
+ def does_not_match?(actual, &filter_block)
96
+ @matcher.matches?(actual, &filter_block)
95
97
  end
96
98
 
97
99
  def description
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if defined?(::RSpec::Expectations::Version)
3
+ if defined?(RSpec::Expectations::Version)
4
4
  module Capybara
5
5
  module RSpecMatchers
6
6
  module Matchers
@@ -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)
@@ -22,9 +22,7 @@ module Capybara
22
22
  el.assert_no_selector(*@args, **session_query_options, &@filter_block)
23
23
  end
24
24
 
25
- def description
26
- "have #{query.description}"
27
- end
25
+ def description = "have #{query.description}"
28
26
 
29
27
  def query
30
28
  @query ||= Capybara::Queries::SelectorQuery.new(*session_query_args, **session_query_options, &@filter_block)
@@ -40,9 +38,7 @@ module Capybara
40
38
  raise ArgumentError, 'The have_all_selectors matcher does not support use with not_to/should_not'
41
39
  end
42
40
 
43
- def description
44
- 'have all selectors'
45
- end
41
+ def description = 'have all selectors'
46
42
  end
47
43
 
48
44
  class HaveNoSelectors < WrappedElementMatcher
@@ -54,9 +50,7 @@ module Capybara
54
50
  raise ArgumentError, 'The have_none_of_selectors matcher does not support use with not_to/should_not'
55
51
  end
56
52
 
57
- def description
58
- 'have no selectors'
59
- end
53
+ def description = 'have no selectors'
60
54
  end
61
55
 
62
56
  class HaveAnySelectors < WrappedElementMatcher
@@ -64,13 +58,11 @@ module Capybara
64
58
  el.assert_any_of_selectors(*@args, **session_query_options, &@filter_block)
65
59
  end
66
60
 
67
- def does_not_match?(_actual)
61
+ def does_not_match?(el)
68
62
  el.assert_none_of_selectors(*@args, **session_query_options, &@filter_block)
69
63
  end
70
64
 
71
- def description
72
- 'have any selectors'
73
- end
65
+ def description = 'have any selectors'
74
66
  end
75
67
  end
76
68
  end