capybara 3.36.0 → 3.39.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +94 -4
  3. data/README.md +24 -12
  4. data/lib/capybara/driver/node.rb +4 -0
  5. data/lib/capybara/dsl.rb +4 -10
  6. data/lib/capybara/helpers.rb +6 -2
  7. data/lib/capybara/minitest/spec.rb +2 -2
  8. data/lib/capybara/node/actions.rb +4 -4
  9. data/lib/capybara/node/base.rb +2 -1
  10. data/lib/capybara/node/element.rb +12 -1
  11. data/lib/capybara/node/finders.rb +10 -1
  12. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  13. data/lib/capybara/queries/base_query.rb +2 -2
  14. data/lib/capybara/queries/selector_query.rb +24 -9
  15. data/lib/capybara/queries/text_query.rb +1 -1
  16. data/lib/capybara/rack_test/browser.rb +63 -8
  17. data/lib/capybara/rack_test/driver.rb +4 -4
  18. data/lib/capybara/rack_test/form.rb +29 -7
  19. data/lib/capybara/rack_test/node.rb +19 -16
  20. data/lib/capybara/registration_container.rb +0 -3
  21. data/lib/capybara/registrations/drivers.rb +3 -3
  22. data/lib/capybara/registrations/servers.rb +30 -10
  23. data/lib/capybara/rspec/matcher_proxies.rb +3 -3
  24. data/lib/capybara/rspec/matchers/base.rb +8 -6
  25. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  26. data/lib/capybara/rspec/matchers/have_selector.rb +4 -4
  27. data/lib/capybara/rspec/matchers.rb +14 -14
  28. data/lib/capybara/selector/definition/link.rb +2 -1
  29. data/lib/capybara/selector/definition.rb +3 -2
  30. data/lib/capybara/selector/filter_set.rb +4 -5
  31. data/lib/capybara/selector/regexp_disassembler.rb +2 -5
  32. data/lib/capybara/selector/selector.rb +5 -1
  33. data/lib/capybara/selenium/driver.rb +11 -4
  34. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +8 -4
  35. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  36. data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
  37. data/lib/capybara/selenium/logger_suppressor.rb +6 -2
  38. data/lib/capybara/selenium/node.rb +72 -25
  39. data/lib/capybara/selenium/nodes/chrome_node.rb +5 -1
  40. data/lib/capybara/selenium/nodes/edge_node.rb +24 -2
  41. data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
  42. data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
  43. data/lib/capybara/selenium/patches/action_pauser.rb +3 -3
  44. data/lib/capybara/selenium/patches/atoms.rb +1 -1
  45. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  46. data/lib/capybara/server/animation_disabler.rb +36 -25
  47. data/lib/capybara/server/middleware.rb +1 -1
  48. data/lib/capybara/session/config.rb +4 -2
  49. data/lib/capybara/session.rb +19 -30
  50. data/lib/capybara/spec/public/test.js +4 -0
  51. data/lib/capybara/spec/session/all_spec.rb +6 -8
  52. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  53. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  54. data/lib/capybara/spec/session/check_spec.rb +1 -0
  55. data/lib/capybara/spec/session/click_link_spec.rb +11 -0
  56. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  57. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  58. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  59. data/lib/capybara/spec/session/find_spec.rb +7 -1
  60. data/lib/capybara/spec/session/first_spec.rb +1 -1
  61. data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
  62. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  63. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
  64. data/lib/capybara/spec/session/has_any_selectors_spec.rb +2 -2
  65. data/lib/capybara/spec/session/has_button_spec.rb +6 -0
  66. data/lib/capybara/spec/session/has_current_path_spec.rb +3 -3
  67. data/lib/capybara/spec/session/has_field_spec.rb +1 -1
  68. data/lib/capybara/spec/session/has_link_spec.rb +16 -0
  69. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  70. data/lib/capybara/spec/session/has_select_spec.rb +10 -4
  71. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  72. data/lib/capybara/spec/session/has_text_spec.rb +5 -13
  73. data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
  74. data/lib/capybara/spec/session/node_spec.rb +81 -0
  75. data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
  76. data/lib/capybara/spec/session/scroll_spec.rb +3 -1
  77. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  78. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  79. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  80. data/lib/capybara/spec/session/within_spec.rb +13 -0
  81. data/lib/capybara/spec/spec_helper.rb +8 -2
  82. data/lib/capybara/spec/test_app.rb +74 -6
  83. data/lib/capybara/spec/views/form.erb +17 -0
  84. data/lib/capybara/spec/views/with_html.erb +3 -3
  85. data/lib/capybara/spec/views/with_scope.erb +2 -2
  86. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  87. data/lib/capybara/version.rb +1 -1
  88. data/lib/capybara/window.rb +1 -1
  89. data/lib/capybara.rb +5 -2
  90. data/spec/capybara_spec.rb +12 -0
  91. data/spec/counter_spec.rb +35 -0
  92. data/spec/css_builder_spec.rb +1 -1
  93. data/spec/css_splitter_spec.rb +1 -1
  94. data/spec/dsl_spec.rb +4 -2
  95. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  96. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  97. data/spec/minitest_spec.rb +4 -0
  98. data/spec/minitest_spec_spec.rb +4 -0
  99. data/spec/per_session_config_spec.rb +1 -1
  100. data/spec/rack_test_spec.rb +16 -2
  101. data/spec/result_spec.rb +27 -29
  102. data/spec/rspec/features_spec.rb +2 -2
  103. data/spec/rspec/scenarios_spec.rb +2 -2
  104. data/spec/rspec/shared_spec_matchers.rb +1 -1
  105. data/spec/rspec_matchers_spec.rb +25 -0
  106. data/spec/rspec_spec.rb +2 -2
  107. data/spec/sauce_spec_chrome.rb +4 -4
  108. data/spec/selector_spec.rb +4 -4
  109. data/spec/selenium_spec_chrome.rb +8 -7
  110. data/spec/selenium_spec_chrome_remote.rb +9 -9
  111. data/spec/selenium_spec_edge.rb +12 -6
  112. data/spec/selenium_spec_firefox.rb +21 -5
  113. data/spec/selenium_spec_firefox_remote.rb +19 -4
  114. data/spec/selenium_spec_ie.rb +4 -2
  115. data/spec/selenium_spec_safari.rb +8 -2
  116. data/spec/server_spec.rb +7 -7
  117. data/spec/shared_selenium_session.rb +13 -6
  118. data/spec/spec_helper.rb +35 -2
  119. data/spec/whitespace_normalizer_spec.rb +54 -0
  120. data/spec/xpath_builder_spec.rb +1 -1
  121. metadata +9 -4
@@ -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)
@@ -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)
@@ -153,9 +147,11 @@ protected
153
147
  if !string_node.visible?(check_ancestor)
154
148
  ''
155
149
  elsif native.text?
156
- native.text
157
- .gsub(/[\u200b\u200e\u200f]/, '')
158
- .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
150
+ native
151
+ .text
152
+ .delete(REMOVED_CHARACTERS)
153
+ .tr(SQUEEZED_SPACES, ' ')
154
+ .squeeze(' ')
159
155
  elsif native.element?
160
156
  text = native.children.map do |child|
161
157
  Capybara::RackTest::Node.new(driver, child).displayed_text(check_ancestor: false)
@@ -235,7 +231,14 @@ private
235
231
  end
236
232
  native.remove
237
233
  else
238
- native['value'] = value.to_s
234
+ value.to_s.tap do |set_value|
235
+ if set_value.end_with?("\n") && form&.css('input, textarea')&.count
236
+ native['value'] = set_value.to_s.chop
237
+ Capybara::RackTest::Form.new(driver, form).submit(self)
238
+ else
239
+ native['value'] = set_value
240
+ end
241
+ end
239
242
  end
240
243
  end
241
244
 
@@ -244,7 +247,7 @@ private
244
247
  end
245
248
 
246
249
  def follow_link
247
- method = self['data-method'] if driver.options[:respect_data_method]
250
+ method = self['data-method'] || self['data-turbo-method'] if driver.options[:respect_data_method]
248
251
  method ||= :get
249
252
  driver.follow(method, self[:href].to_s)
250
253
  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
@@ -11,7 +11,7 @@ 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
17
  Capybara::Selenium::Driver.new(app, **{ :browser => :firefox, options_key => browser_options })
@@ -20,7 +20,7 @@ end
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
@@ -31,7 +31,7 @@ end
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|
34
+ browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
35
35
  opts.add_argument('--headless')
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
@@ -5,12 +5,24 @@ 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
@@ -29,17 +41,25 @@ Capybara.register_server :puma do |app, port, host, **options|
29
41
 
30
42
  conf = Rack::Handler::Puma.config(app, options)
31
43
  conf.clamp
32
- events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
33
44
 
34
45
  puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
35
46
  require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)
36
47
 
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]}"
48
+ logger = (defined?(Puma::LogWriter) ? Puma::LogWriter : Puma::Events).then do |cls|
49
+ conf.options[:Silent] ? cls.strings : cls.stdio
50
+ end
51
+ conf.options[:log_writer] = logger
52
+
53
+ logger.log 'Capybara starting Puma...'
54
+ logger.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
55
+ logger.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
40
56
 
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]
57
+ Puma::Server.new(
58
+ conf.app,
59
+ defined?(Puma::LogWriter) ? nil : logger,
60
+ conf.options
61
+ ).tap do |s|
62
+ s.binder.parse conf.options[:binds], (s.log_writer rescue s.events) # rubocop:disable Style/RescueModifier
63
+ s.min_threads, s.max_threads = conf.options[:min_threads], conf.options[:max_threads] if s.respond_to? :min_threads=
44
64
  end.run.join
45
65
  end
@@ -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)
@@ -76,7 +76,7 @@ else
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)
@@ -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
  ##
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Capybara.add_selector(:link, locator_type: [String, Symbol]) do
4
- xpath do |locator, href: true, alt: nil, title: nil, **|
4
+ xpath do |locator, href: true, alt: nil, title: nil, target: nil, **|
5
5
  xpath = XPath.descendant(:a)
6
6
  xpath = builder(xpath).add_attribute_conditions(href: href) unless href == false
7
7
 
@@ -25,6 +25,7 @@ Capybara.add_selector(:link, locator_type: [String, Symbol]) do
25
25
 
26
26
  xpath = xpath[find_by_attr(:title, title)]
27
27
  xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt) == alt]] if alt
28
+ xpath = xpath[find_by_attr(:target, target)] if target
28
29
 
29
30
  xpath
30
31
  end
@@ -203,7 +203,7 @@ module Capybara
203
203
 
204
204
  ##
205
205
  #
206
- # Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
206
+ # Set the default visibility mode that should be used if no visible option is passed when using the selector.
207
207
  # If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
208
208
  #
209
209
  # @param [Symbol] default_visibility Only find elements with the specified visibility:
@@ -261,7 +261,8 @@ module Capybara
261
261
  def parameter_names(block)
262
262
  key_types = %i[key keyreq]
263
263
  # user filter_map when we drop dupport for 2.6
264
- block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_, name)| name }
264
+ # block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_, name)| name }
265
+ block.parameters.filter_map { |(type, name)| name if key_types.include? type }
265
266
  end
266
267
 
267
268
  def expression(type, allowed_filters, &block)
@@ -101,11 +101,10 @@ module Capybara
101
101
  private
102
102
 
103
103
  def options_with_defaults(options)
104
- expression_filters.chain(node_filters)
105
- .select { |_n, filter| filter.default? }
106
- .each_with_object(options.dup) do |(name, filter), opts|
107
- opts[name] = filter.default unless opts.key?(name)
108
- end
104
+ expression_filters
105
+ .chain(node_filters)
106
+ .filter_map { |name, filter| [name, filter.default] if filter.default? }
107
+ .to_h.merge!(options)
109
108
  end
110
109
 
111
110
  def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
@@ -69,11 +69,8 @@ module Capybara
69
69
  suffixes = [[]]
70
70
  strs.reverse_each do |str|
71
71
  if str.is_a? Set
72
- prefixes = str.each_with_object([]) { |s, memo| memo.concat combine(s) }
73
-
74
- result = []
75
- prefixes.product(suffixes) { |pair| result << pair.flatten(1) }
76
- suffixes = result
72
+ prefixes = str.flat_map { |s| combine(s) }
73
+ suffixes = prefixes.product(suffixes).map { |pair| pair.flatten(1) }
77
74
  else
78
75
  suffixes.each { |arr| arr.unshift str }
79
76
  end
@@ -66,7 +66,11 @@ module Capybara
66
66
  end
67
67
  ensure
68
68
  unless locator_valid?(locator)
69
- warn "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. This will raise an error in a future version of Capybara."
69
+ Capybara::Helpers.warn(
70
+ "Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. " \
71
+ 'This will raise an error in a future version of Capybara. ' \
72
+ "Called from: #{Capybara::Helpers.filter_backtrace(caller)}"
73
+ )
70
74
  end
71
75
  end
72
76