capybara 3.29.0 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +91 -1
  3. data/README.md +10 -3
  4. data/lib/capybara.rb +17 -7
  5. data/lib/capybara/config.rb +7 -3
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/dsl.rb +10 -2
  8. data/lib/capybara/helpers.rb +3 -1
  9. data/lib/capybara/minitest.rb +232 -144
  10. data/lib/capybara/minitest/spec.rb +153 -97
  11. data/lib/capybara/node/actions.rb +35 -35
  12. data/lib/capybara/node/document.rb +2 -2
  13. data/lib/capybara/node/document_matchers.rb +3 -3
  14. data/lib/capybara/node/element.rb +23 -16
  15. data/lib/capybara/node/finders.rb +17 -11
  16. data/lib/capybara/node/matchers.rb +64 -51
  17. data/lib/capybara/node/simple.rb +4 -2
  18. data/lib/capybara/queries/ancestor_query.rb +1 -1
  19. data/lib/capybara/queries/base_query.rb +2 -1
  20. data/lib/capybara/queries/selector_query.rb +25 -5
  21. data/lib/capybara/queries/sibling_query.rb +1 -1
  22. data/lib/capybara/queries/style_query.rb +1 -1
  23. data/lib/capybara/queries/text_query.rb +6 -0
  24. data/lib/capybara/rack_test/browser.rb +7 -2
  25. data/lib/capybara/rack_test/driver.rb +1 -1
  26. data/lib/capybara/rack_test/form.rb +1 -1
  27. data/lib/capybara/rack_test/node.rb +34 -9
  28. data/lib/capybara/registration_container.rb +44 -0
  29. data/lib/capybara/registrations/servers.rb +1 -1
  30. data/lib/capybara/result.rb +29 -5
  31. data/lib/capybara/rspec/matcher_proxies.rb +4 -4
  32. data/lib/capybara/rspec/matchers.rb +27 -27
  33. data/lib/capybara/rspec/matchers/base.rb +12 -6
  34. data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
  35. data/lib/capybara/rspec/matchers/have_ancestor.rb +4 -3
  36. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  37. data/lib/capybara/rspec/matchers/have_selector.rb +15 -7
  38. data/lib/capybara/rspec/matchers/have_sibling.rb +3 -3
  39. data/lib/capybara/rspec/matchers/have_text.rb +3 -3
  40. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  41. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  42. data/lib/capybara/rspec/matchers/match_style.rb +2 -2
  43. data/lib/capybara/rspec/matchers/spatial_sugar.rb +2 -1
  44. data/lib/capybara/selector.rb +34 -17
  45. data/lib/capybara/selector/css.rb +1 -1
  46. data/lib/capybara/selector/definition.rb +7 -6
  47. data/lib/capybara/selector/definition/button.rb +8 -2
  48. data/lib/capybara/selector/definition/checkbox.rb +2 -2
  49. data/lib/capybara/selector/definition/css.rb +3 -1
  50. data/lib/capybara/selector/definition/datalist_input.rb +1 -1
  51. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  52. data/lib/capybara/selector/definition/element.rb +1 -1
  53. data/lib/capybara/selector/definition/field.rb +1 -1
  54. data/lib/capybara/selector/definition/file_field.rb +1 -1
  55. data/lib/capybara/selector/definition/fillable_field.rb +2 -2
  56. data/lib/capybara/selector/definition/label.rb +4 -2
  57. data/lib/capybara/selector/definition/link.rb +8 -0
  58. data/lib/capybara/selector/definition/radio_button.rb +2 -2
  59. data/lib/capybara/selector/definition/select.rb +32 -13
  60. data/lib/capybara/selector/definition/table.rb +6 -3
  61. data/lib/capybara/selector/filter_set.rb +11 -9
  62. data/lib/capybara/selector/filters/base.rb +6 -1
  63. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  64. data/lib/capybara/selector/selector.rb +8 -2
  65. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  66. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  67. data/lib/capybara/selenium/driver.rb +22 -11
  68. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +8 -10
  69. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +7 -9
  70. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +2 -2
  71. data/lib/capybara/selenium/extensions/html5_drag.rb +30 -13
  72. data/lib/capybara/selenium/node.rb +97 -18
  73. data/lib/capybara/selenium/nodes/chrome_node.rb +11 -14
  74. data/lib/capybara/selenium/nodes/edge_node.rb +4 -2
  75. data/lib/capybara/selenium/nodes/firefox_node.rb +4 -4
  76. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  77. data/lib/capybara/selenium/patches/logs.rb +3 -5
  78. data/lib/capybara/server.rb +15 -3
  79. data/lib/capybara/server/checker.rb +1 -1
  80. data/lib/capybara/server/middleware.rb +20 -10
  81. data/lib/capybara/session.rb +43 -26
  82. data/lib/capybara/session/config.rb +9 -3
  83. data/lib/capybara/session/matchers.rb +6 -6
  84. data/lib/capybara/spec/public/test.js +69 -6
  85. data/lib/capybara/spec/session/all_spec.rb +60 -5
  86. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  87. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  88. data/lib/capybara/spec/session/click_button_spec.rb +16 -0
  89. data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
  90. data/lib/capybara/spec/session/find_spec.rb +31 -8
  91. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  92. data/lib/capybara/spec/session/has_css_spec.rb +12 -9
  93. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  94. data/lib/capybara/spec/session/has_field_spec.rb +16 -0
  95. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  96. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  97. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  98. data/lib/capybara/spec/session/has_text_spec.rb +35 -0
  99. data/lib/capybara/spec/session/node_spec.rb +160 -29
  100. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  101. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  102. data/lib/capybara/spec/session/selectors_spec.rb +15 -2
  103. data/lib/capybara/spec/session/window/window_spec.rb +7 -7
  104. data/lib/capybara/spec/spec_helper.rb +2 -2
  105. data/lib/capybara/spec/test_app.rb +14 -18
  106. data/lib/capybara/spec/views/form.erb +18 -2
  107. data/lib/capybara/spec/views/with_dragula.erb +3 -1
  108. data/lib/capybara/spec/views/with_html.erb +2 -2
  109. data/lib/capybara/spec/views/with_js.erb +1 -0
  110. data/lib/capybara/version.rb +1 -1
  111. data/spec/capybara_spec.rb +1 -1
  112. data/spec/dsl_spec.rb +16 -3
  113. data/spec/minitest_spec.rb +1 -1
  114. data/spec/minitest_spec_spec.rb +46 -46
  115. data/spec/rack_test_spec.rb +13 -1
  116. data/spec/regexp_dissassembler_spec.rb +40 -36
  117. data/spec/result_spec.rb +43 -32
  118. data/spec/rspec/features_spec.rb +1 -0
  119. data/spec/rspec/shared_spec_matchers.rb +68 -56
  120. data/spec/rspec_spec.rb +4 -4
  121. data/spec/selector_spec.rb +1 -1
  122. data/spec/selenium_spec_chrome.rb +9 -6
  123. data/spec/selenium_spec_chrome_remote.rb +2 -0
  124. data/spec/selenium_spec_firefox.rb +7 -2
  125. data/spec/server_spec.rb +65 -31
  126. data/spec/session_spec.rb +1 -1
  127. data/spec/shared_selenium_node.rb +21 -3
  128. data/spec/shared_selenium_session.rb +33 -14
  129. data/spec/spec_helper.rb +1 -1
  130. metadata +6 -4
@@ -108,7 +108,9 @@ module Capybara
108
108
  !find_xpath(VISIBILITY_XPATH)
109
109
  else
110
110
  # No need for an xpath if only checking the current element
111
- !(native.key?('hidden') || (/display:\s?none/.match? native[:style]) || %w[script head].include?(tag_name))
111
+ !(native.key?('hidden') ||
112
+ /display:\s?none/.match?(native[:style] || '') ||
113
+ %w[script head].include?(tag_name))
112
114
  end
113
115
  end
114
116
 
@@ -150,7 +152,7 @@ module Capybara
150
152
  yield # simple nodes don't need to wait
151
153
  end
152
154
 
153
- def allow_reload!
155
+ def allow_reload!(*)
154
156
  # no op
155
157
  end
156
158
 
@@ -12,7 +12,7 @@ module Capybara
12
12
  ancestors = node.find_xpath(XPath.ancestor.to_s)
13
13
  .map(&method(:to_element))
14
14
  .select { |el| match_results.include?(el) }
15
- Capybara::Result.new(ancestors, self)
15
+ Capybara::Result.new(ordered_results(ancestors), self)
16
16
  end
17
17
  end
18
18
 
@@ -79,7 +79,8 @@ module Capybara
79
79
  if count
80
80
  message << " #{occurrences count}"
81
81
  elsif between
82
- message << " between #{between.first} and #{between.end ? between.last : 'infinite'} times"
82
+ message << " between #{between.begin ? between.first : 1} and" \
83
+ " #{between.end ? between.last : 'infinite'} times"
83
84
  elsif maximum
84
85
  message << " at most #{occurrences maximum}"
85
86
  elsif minimum
@@ -6,6 +6,7 @@ module Capybara
6
6
  module Queries
7
7
  class SelectorQuery < Queries::BaseQuery
8
8
  attr_reader :expression, :selector, :locator, :options
9
+
9
10
  SPATIAL_KEYS = %i[above below left_of right_of near].freeze
10
11
  VALID_KEYS = SPATIAL_KEYS + COUNT_KEYS +
11
12
  %i[text id class style visible obscured exact exact_text normalize_ws match wait filter_set]
@@ -14,13 +15,16 @@ module Capybara
14
15
  def initialize(*args,
15
16
  session_options:,
16
17
  enable_aria_label: session_options.enable_aria_label,
18
+ enable_aria_role: session_options.enable_aria_role,
17
19
  test_id: session_options.test_id,
18
20
  selector_format: nil,
21
+ order: nil,
19
22
  **options,
20
23
  &filter_block)
21
24
  @resolved_node = nil
22
25
  @resolved_count = 0
23
26
  @options = options.dup
27
+ @order = order
24
28
  @filter_cache = Hash.new { |hsh, key| hsh[key] = {} }
25
29
 
26
30
  super(@options)
@@ -28,7 +32,11 @@ module Capybara
28
32
 
29
33
  @selector = Selector.new(
30
34
  find_selector(args[0].is_a?(Symbol) ? args.shift : args[0]),
31
- config: { enable_aria_label: enable_aria_label, test_id: test_id },
35
+ config: {
36
+ enable_aria_label: enable_aria_label,
37
+ enable_aria_role: enable_aria_role,
38
+ test_id: test_id
39
+ },
32
40
  format: selector_format
33
41
  )
34
42
 
@@ -37,7 +45,7 @@ module Capybara
37
45
 
38
46
  raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
39
47
 
40
- @expression = selector.call(@locator, @options)
48
+ @expression = selector.call(@locator, **@options)
41
49
 
42
50
  warn_exact_usage
43
51
 
@@ -77,7 +85,9 @@ module Capybara
77
85
  end
78
86
 
79
87
  %i[above below left_of right_of near].each do |spatial_filter|
80
- desc << " #{spatial_filter} #{options[spatial_filter] rescue '<ERROR>'}" if options[spatial_filter] && show_for[:spatial] # rubocop:disable Style/RescueModifier
88
+ if options[spatial_filter] && show_for[:spatial]
89
+ desc << " #{spatial_filter} #{options[spatial_filter] rescue '<ERROR>'}" # rubocop:disable Style/RescueModifier
90
+ end
81
91
  end
82
92
 
83
93
  desc << selector.description(node_filters: show_for[:node], **options)
@@ -148,7 +158,7 @@ module Capybara
148
158
 
149
159
  node.synchronize do
150
160
  children = find_nodes_by_selector_format(node, exact).map(&method(:to_element))
151
- Capybara::Result.new(children, self)
161
+ Capybara::Result.new(ordered_results(children), self)
152
162
  end
153
163
  end
154
164
 
@@ -311,6 +321,15 @@ module Capybara
311
321
  filters
312
322
  end
313
323
 
324
+ def ordered_results(results)
325
+ case @order
326
+ when :reverse
327
+ results.reverse
328
+ else
329
+ results
330
+ end
331
+ end
332
+
314
333
  def custom_keys
315
334
  @custom_keys ||= node_filters.keys + expression_filters.keys
316
335
  end
@@ -338,7 +357,7 @@ module Capybara
338
357
  conditions[:id] = options[:id] if use_default_id_filter?
339
358
  conditions[:class] = options[:class] if use_default_class_filter?
340
359
  conditions[:style] = options[:style] if use_default_style_filter? && !options[:style].is_a?(Hash)
341
- builder(expr).add_attribute_conditions(conditions)
360
+ builder(expr).add_attribute_conditions(**conditions)
342
361
  end
343
362
 
344
363
  def use_default_id_filter?
@@ -562,6 +581,7 @@ module Capybara
562
581
 
563
582
  class Rectangle
564
583
  attr_reader :top, :bottom, :left, :right
584
+
565
585
  def initialize(position)
566
586
  # rubocop:disable Style/RescueModifier
567
587
  @top = position['top'] rescue position['y']
@@ -11,7 +11,7 @@ module Capybara
11
11
  siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
12
12
  .map(&method(:to_element))
13
13
  .select { |el| match_results.include?(el) }
14
- Capybara::Result.new(siblings, self)
14
+ Capybara::Result.new(ordered_results(siblings), self)
15
15
  end
16
16
  end
17
17
 
@@ -34,7 +34,7 @@ module Capybara
34
34
  private
35
35
 
36
36
  def stringify_keys(hsh)
37
- hsh.each_with_object({}) { |(k, v), str_keys| str_keys[k.to_s] = v }
37
+ hsh.transform_keys(&:to_s)
38
38
  end
39
39
 
40
40
  def valid_keys
@@ -6,6 +6,8 @@ module Capybara
6
6
  class TextQuery < BaseQuery
7
7
  def initialize(type = nil, expected_text, session_options:, **options) # rubocop:disable Style/OptionalArguments
8
8
  @type = type.nil? ? default_type : type
9
+ raise ArgumentError, '${@type} is not a valid type for a text query' unless valid_types.include?(@type)
10
+
9
11
  @options = options
10
12
  super(@options)
11
13
  self.session_options = session_options
@@ -89,6 +91,10 @@ module Capybara
89
91
  COUNT_KEYS + %i[wait exact normalize_ws]
90
92
  end
91
93
 
94
+ def valid_types
95
+ %i[all visible]
96
+ end
97
+
92
98
  def check_visible_text?
93
99
  @type == :visible
94
100
  end
@@ -30,7 +30,9 @@ class Capybara::RackTest::Browser
30
30
 
31
31
  def submit(method, path, attributes)
32
32
  path = request_path if path.nil? || path.empty?
33
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
33
+ uri = build_uri(path)
34
+ uri.query = '' if method&.to_s&.downcase == 'get'
35
+ process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
34
36
  end
35
37
 
36
38
  def follow(method, path, **attributes)
@@ -53,7 +55,10 @@ class Capybara::RackTest::Browser
53
55
  end
54
56
  end
55
57
  end
56
- raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
58
+
59
+ if last_response.redirect? # rubocop:disable Style/GuardClause
60
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
61
+ end
57
62
  end
58
63
 
59
64
  def process(method, path, attributes = {}, env = {})
@@ -42,7 +42,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
42
42
  end
43
43
 
44
44
  def visit(path, **attributes)
45
- browser.visit(path, attributes)
45
+ browser.visit(path, **attributes)
46
46
  end
47
47
 
48
48
  def refresh
@@ -56,7 +56,7 @@ private
56
56
  end
57
57
 
58
58
  def request_method
59
- /post/i.match?(self[:method]) ? :post : :get
59
+ /post/i.match?(self[:method] || '') ? :post : :get
60
60
  end
61
61
 
62
62
  def merge_param!(params, key, value)
@@ -45,6 +45,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
45
45
 
46
46
  if radio? then set_radio(value)
47
47
  elsif checkbox? then set_checkbox(value)
48
+ elsif range? then set_range(value)
48
49
  elsif input_field? then set_input(value)
49
50
  elsif textarea? then native['_capybara_raw_value'] = value.to_s
50
51
  end
@@ -76,8 +77,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
76
77
  set(!checked?)
77
78
  elsif tag_name == 'label'
78
79
  click_label
79
- elsif tag_name == 'details'
80
- toggle_details
80
+ elsif (details = native.xpath('.//ancestor-or-self::details').last)
81
+ toggle_details(details)
81
82
  end
82
83
  end
83
84
 
@@ -123,9 +124,18 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
123
124
  alias_method "unchecked_#{meth_name}", meth_name
124
125
  private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
125
126
 
126
- define_method meth_name do |*args|
127
- stale_check
128
- send("unchecked_#{meth_name}", *args)
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|
136
+ stale_check
137
+ send("unchecked_#{meth_name}", *args)
138
+ end
129
139
  end
130
140
  end
131
141
 
@@ -199,6 +209,14 @@ private
199
209
  end
200
210
  end
201
211
 
212
+ def set_range(value) # rubocop:disable Naming/AccessorMethodName
213
+ min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
214
+ value = value.to_f
215
+ value = value.clamp(min, max)
216
+ value = ((value - min) / step).round * step + min
217
+ native['value'] = value.clamp(min, max)
218
+ end
219
+
202
220
  def set_input(value) # rubocop:disable Naming/AccessorMethodName
203
221
  if text_or_password? && attribute_is_not_blank?(:maxlength)
204
222
  # Browser behavior for maxlength="0" is inconsistent, so we stick with
@@ -238,11 +256,14 @@ private
238
256
  labelled_control.set(!labelled_control.checked?) if checkbox_or_radio?(labelled_control)
239
257
  end
240
258
 
241
- def toggle_details
242
- if native.has_attribute?('open')
243
- native.remove_attribute('open')
259
+ def toggle_details(details = nil)
260
+ details ||= native.xpath('.//ancestor-or-self::details').last
261
+ return unless details
262
+
263
+ if details.has_attribute?('open')
264
+ details.remove_attribute('open')
244
265
  else
245
- native.set_attribute('open', 'open')
266
+ details.set_attribute('open', 'open')
246
267
  end
247
268
  end
248
269
 
@@ -284,6 +305,10 @@ protected
284
305
  tag_name == 'textarea'
285
306
  end
286
307
 
308
+ def range?
309
+ input_field? && type == 'range'
310
+ end
311
+
287
312
  OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze
288
313
  DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
289
314
  x.parent(:fieldset)[
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ # @api private
5
+ class RegistrationContainer
6
+ def names
7
+ @registered.keys
8
+ end
9
+
10
+ def [](name)
11
+ @registered[name]
12
+ end
13
+
14
+ def []=(name, value)
15
+ warn 'DEPRECATED: Directly setting drivers/servers is deprecated, please use Capybara.register_driver/register_server instead'
16
+ @registered[name] = value
17
+ end
18
+
19
+ def method_missing(method_name, *args, **options, &block)
20
+ if @registered.respond_to?(method_name)
21
+ 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)
26
+ end
27
+ super
28
+ end
29
+
30
+ def respond_to_missing?(method_name, include_private = false)
31
+ @registered.respond_to?(method_name) || super
32
+ end
33
+
34
+ private
35
+
36
+ def initialize
37
+ @registered = {}
38
+ end
39
+
40
+ def register(name, block)
41
+ @registered[name] = block
42
+ end
43
+ end
44
+ end
@@ -7,7 +7,7 @@ end
7
7
  Capybara.register_server :webrick do |app, port, host, **options|
8
8
  require 'rack/handler/webrick'
9
9
  options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
10
- Rack::Handler::WEBrick.run(app, options)
10
+ Rack::Handler::WEBrick.run(app, **options)
11
11
  end
12
12
 
13
13
  Capybara.register_server :puma do |app, port, host, **options|
@@ -31,6 +31,7 @@ module Capybara
31
31
  @filter_errors = []
32
32
  @results_enum = lazy_select_elements { |node| query.matches_filters?(node, @filter_errors) }
33
33
  @query = query
34
+ @allow_reload = false
34
35
  end
35
36
 
36
37
  def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
@@ -43,7 +44,7 @@ module Capybara
43
44
  @result_cache.each(&block)
44
45
  loop do
45
46
  next_result = @results_enum.next
46
- @result_cache << next_result
47
+ add_to_cache(next_result)
47
48
  yield next_result
48
49
  end
49
50
  self
@@ -59,7 +60,12 @@ module Capybara
59
60
  nil
60
61
  end
61
62
  when Range
62
- idx.end && idx.max # endless range will have end == nil
63
+ # idx.max is broken with beginless ranges
64
+ # idx.end && idx.max # endless range will have end == nil
65
+ max = idx.end
66
+ max = nil if max&.negative?
67
+ max -= 1 if max && idx.exclude_end?
68
+ max
63
69
  end
64
70
 
65
71
  if max_idx.nil?
@@ -94,7 +100,9 @@ module Capybara
94
100
  end
95
101
 
96
102
  if between
97
- min, max = between.min, (between.end && between.max)
103
+ min, max = (between.begin && between.min) || 1, between.end
104
+ max -= 1 if max && between.exclude_end?
105
+
98
106
  size = load_up_to(max ? max + 1 : min)
99
107
  return size <=> min unless between.include?(size)
100
108
  end
@@ -130,13 +138,26 @@ module Capybara
130
138
  @elements.length
131
139
  end
132
140
 
141
+ ##
142
+ # @api private
143
+ #
144
+ def allow_reload!
145
+ @allow_reload = true
146
+ self
147
+ end
148
+
133
149
  private
134
150
 
151
+ def add_to_cache(elem)
152
+ elem.allow_reload!(@result_cache.size) if @allow_reload
153
+ @result_cache << elem
154
+ end
155
+
135
156
  def load_up_to(num)
136
157
  loop do
137
158
  break if @result_cache.size >= num
138
159
 
139
- @result_cache << @results_enum.next
160
+ add_to_cache(@results_enum.next)
140
161
  end
141
162
  @result_cache.size
142
163
  end
@@ -150,10 +171,13 @@ module Capybara
150
171
  @rest ||= @elements - full_results
151
172
  end
152
173
 
153
- if (RUBY_PLATFORM == 'java') && (Gem::Version.new(JRUBY_VERSION) < Gem::Version.new('9.2.8.0'))
174
+ if RUBY_PLATFORM == 'java'
154
175
  # JRuby < 9.2.8.0 has an issue with lazy enumerators which
155
176
  # causes a concurrency issue with network requests here
156
177
  # https://github.com/jruby/jruby/issues/4212
178
+ # while JRuby >= 9.2.8.0 leaks threads when using lazy enumerators
179
+ # https://github.com/teamcapybara/capybara/issues/2349
180
+ # so disable the use and JRuby users will need to pay a performance penalty
157
181
  def lazy_select_elements(&block)
158
182
  @elements.select(&block).to_enum # non-lazy evaluation
159
183
  end
@@ -2,17 +2,17 @@
2
2
 
3
3
  module Capybara
4
4
  module RSpecMatcherProxies
5
- def all(*args, &block)
5
+ def all(*args, **kwargs, &block)
6
6
  if defined?(::RSpec::Matchers::BuiltIn::All) && args.first.respond_to?(:matches?)
7
7
  ::RSpec::Matchers::BuiltIn::All.new(*args)
8
8
  else
9
- find_all(*args, &block)
9
+ find_all(*args, **kwargs, &block)
10
10
  end
11
11
  end
12
12
 
13
- def within(*args, &block)
13
+ def within(*args, **kwargs, &block)
14
14
  if block_given?
15
- within_element(*args, &block)
15
+ within_element(*args, **kwargs, &block)
16
16
  else
17
17
  be_within(*args)
18
18
  end