capybara 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +22 -0
  3. data/README.md +27 -3
  4. data/lib/capybara.rb +19 -4
  5. data/lib/capybara/driver/base.rb +6 -2
  6. data/lib/capybara/driver/node.rb +13 -5
  7. data/lib/capybara/helpers.rb +2 -2
  8. data/lib/capybara/node/actions.rb +116 -17
  9. data/lib/capybara/node/base.rb +7 -1
  10. data/lib/capybara/node/element.rb +23 -3
  11. data/lib/capybara/node/finders.rb +45 -29
  12. data/lib/capybara/node/matchers.rb +13 -15
  13. data/lib/capybara/queries/selector_query.rb +22 -5
  14. data/lib/capybara/queries/text_query.rb +42 -6
  15. data/lib/capybara/rack_test/node.rb +13 -1
  16. data/lib/capybara/result.rb +80 -8
  17. data/lib/capybara/rspec/features.rb +13 -6
  18. data/lib/capybara/selector.rb +98 -71
  19. data/lib/capybara/selector/filter_set.rb +46 -0
  20. data/lib/capybara/selenium/driver.rb +22 -23
  21. data/lib/capybara/selenium/node.rb +14 -6
  22. data/lib/capybara/server.rb +20 -10
  23. data/lib/capybara/session.rb +44 -8
  24. data/lib/capybara/spec/session/all_spec.rb +4 -4
  25. data/lib/capybara/spec/session/assert_text.rb +23 -0
  26. data/lib/capybara/spec/session/check_spec.rb +66 -8
  27. data/lib/capybara/spec/session/choose_spec.rb +20 -0
  28. data/lib/capybara/spec/session/click_button_spec.rb +0 -3
  29. data/lib/capybara/spec/session/click_link_spec.rb +7 -0
  30. data/lib/capybara/spec/session/execute_script_spec.rb +6 -1
  31. data/lib/capybara/spec/session/find_button_spec.rb +19 -1
  32. data/lib/capybara/spec/session/find_field_spec.rb +21 -1
  33. data/lib/capybara/spec/session/find_link_spec.rb +19 -1
  34. data/lib/capybara/spec/session/find_spec.rb +32 -4
  35. data/lib/capybara/spec/session/first_spec.rb +4 -4
  36. data/lib/capybara/spec/session/has_field_spec.rb +4 -0
  37. data/lib/capybara/spec/session/has_text_spec.rb +2 -2
  38. data/lib/capybara/spec/session/node_spec.rb +24 -3
  39. data/lib/capybara/spec/session/reset_session_spec.rb +7 -0
  40. data/lib/capybara/spec/session/selectors_spec.rb +14 -0
  41. data/lib/capybara/spec/session/uncheck_spec.rb +39 -0
  42. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -2
  43. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  44. data/lib/capybara/spec/session/window/window_spec.rb +36 -22
  45. data/lib/capybara/spec/session/within_frame_spec.rb +19 -0
  46. data/lib/capybara/spec/spec_helper.rb +3 -0
  47. data/lib/capybara/spec/views/form.erb +34 -6
  48. data/lib/capybara/spec/views/with_html.erb +5 -1
  49. data/lib/capybara/spec/views/with_unload_alert.erb +3 -1
  50. data/lib/capybara/spec/views/with_windows.erb +2 -0
  51. data/lib/capybara/version.rb +1 -1
  52. data/spec/capybara_spec.rb +34 -0
  53. data/spec/rack_test_spec.rb +24 -0
  54. data/spec/result_spec.rb +25 -0
  55. data/spec/rspec/features_spec.rb +3 -3
  56. data/spec/selenium_spec.rb +6 -3
  57. data/spec/server_spec.rb +2 -2
  58. metadata +18 -4
@@ -23,7 +23,7 @@ module Capybara
23
23
  # session.has_css?('#foobar') # from Capybara::Node::Matchers
24
24
  #
25
25
  class Base
26
- attr_reader :session, :base, :parent
26
+ attr_reader :session, :base, :query_scope
27
27
 
28
28
  include Capybara::Node::Finders
29
29
  include Capybara::Node::Actions
@@ -108,6 +108,12 @@ module Capybara
108
108
  base.find_xpath(xpath)
109
109
  end
110
110
 
111
+ # @deprecated Use query_scope instead
112
+ def parent
113
+ warn "DEPRECATED: #parent is deprecated in favor of #query_scope - Note: #parent was not the elements parent in the document so it's most likely not what you wanted anyway"
114
+ query_scope
115
+ end
116
+
111
117
  protected
112
118
 
113
119
  def catch_error?(error, errors = nil)
@@ -23,9 +23,9 @@ module Capybara
23
23
  #
24
24
  class Element < Base
25
25
 
26
- def initialize(session, base, parent, query)
26
+ def initialize(session, base, query_scope, query)
27
27
  super(session, base)
28
- @parent = parent
28
+ @query_scope = query_scope
29
29
  @query = query
30
30
  end
31
31
 
@@ -282,6 +282,26 @@ module Capybara
282
282
  synchronize { base.disabled? }
283
283
  end
284
284
 
285
+ ##
286
+ #
287
+ # Whether or not the element is readonly.
288
+ #
289
+ # @return [Boolean] Whether the element is readonly
290
+ #
291
+ def readonly?
292
+ synchronize { base.readonly? }
293
+ end
294
+
295
+ ##
296
+ #
297
+ # Whether or not the element supports multiple results.
298
+ #
299
+ # @return [Boolean] Whether the element supports multiple results.
300
+ #
301
+ def multiple?
302
+ synchronize { base.multiple? }
303
+ end
304
+
285
305
  ##
286
306
  #
287
307
  # An XPath expression describing where on the page the element can be found
@@ -320,7 +340,7 @@ module Capybara
320
340
  def reload
321
341
  if @allow_reload
322
342
  begin
323
- reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
343
+ reloaded = query_scope.reload.first(@query.name, @query.locator, @query.options)
324
344
  @base = reloaded.base if reloaded
325
345
  rescue => e
326
346
  raise e unless catch_error?(e)
@@ -33,14 +33,14 @@ module Capybara
33
33
  synchronize(query.wait) do
34
34
  if query.match == :smart or query.match == :prefer_exact
35
35
  result = query.resolve_for(self, true)
36
- result = query.resolve_for(self, false) if result.size == 0 && !query.exact?
36
+ result = query.resolve_for(self, false) if result.empty? && !query.exact?
37
37
  else
38
38
  result = query.resolve_for(self)
39
39
  end
40
40
  if query.match == :one or query.match == :smart and result.size > 1
41
41
  raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
42
42
  end
43
- if result.size == 0
43
+ if result.empty?
44
44
  raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
45
45
  end
46
46
  result.first
@@ -51,22 +51,30 @@ module Capybara
51
51
  #
52
52
  # Find a form field on the page. The field can be found by its name, id or label text.
53
53
  #
54
- # @macro waiting_behavior
55
- #
56
- # @param [String] locator Which field to find
57
- #
58
- # @option options [Boolean] checked Match checked field?
59
- # @option options [Boolean] unchecked Match unchecked field?
60
- # @option options [Boolean, Symbol] disabled (false) Match disabled field?
61
- # * true - only finds a disabled field
62
- # * false - only finds an enabled field
63
- # * :all - finds either an enabled or disabled field
64
- # @option options [Boolean] readonly Match readonly field?
65
- # @option options [String] with Value of field to match on
66
- # @option options [String] type Type of field to match on
54
+ # @overload find_field([locator], options={})
55
+ # @param [String] locator name, id, placeholder or text of associated label element
56
+ #
57
+ # @macro waiting_behavior
58
+ #
59
+ #
60
+ # @option options [Boolean] checked Match checked field?
61
+ # @option options [Boolean] unchecked Match unchecked field?
62
+ # @option options [Boolean, Symbol] disabled (false) Match disabled field?
63
+ # * true - only finds a disabled field
64
+ # * false - only finds an enabled field
65
+ # * :all - finds either an enabled or disabled field
66
+ # @option options [Boolean] readonly Match readonly field?
67
+ # @option options [String, Regexp] with Value of field to match on
68
+ # @option options [String] type Type of field to match on
69
+ # @option options [Boolean] multiple Match fields that can have multiple values?
70
+ # @option options [String] id Match fields that match the id attribute
71
+ # @option options [String] name Match fields that match the name attribute
72
+ # @option options [String] placeholder Match fields that match the placeholder attribute
67
73
  # @return [Capybara::Node::Element] The found element
68
74
  #
69
- def find_field(locator, options={})
75
+
76
+ def find_field(locator=nil, options={})
77
+ locator, options = nil, locator if locator.is_a? Hash
70
78
  find(:field, locator, options)
71
79
  end
72
80
  alias_method :field_labeled, :find_field
@@ -75,13 +83,16 @@ module Capybara
75
83
  #
76
84
  # Find a link on the page. The link can be found by its id or text.
77
85
  #
78
- # @macro waiting_behavior
86
+ # @overload find_link([locator], options={})
87
+ # @param [String] locator id, title, text, or alt of enclosed img element
88
+ #
89
+ # @macro waiting_behavior
79
90
  #
80
- # @param [String] locator Which link to find
81
- # @option options [String,Regexp] href Value to match against the links href
91
+ # @option options [String,Regexp] href Value to match against the links href
82
92
  # @return [Capybara::Node::Element] The found element
83
93
  #
84
- def find_link(locator, options={})
94
+ def find_link(locator=nil, options={})
95
+ locator, options = nil, locator if locator.is_a? Hash
85
96
  find(:link, locator, options)
86
97
  end
87
98
 
@@ -91,17 +102,22 @@ module Capybara
91
102
  # This can be any \<input> element of type submit, reset, image, button or it can be a
92
103
  # \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
93
104
  # by their text content, and image \<input> elements by their alt attribute
94
-
95
- # @macro waiting_behavior
96
105
  #
97
- # @param [String] locator Which button to find
98
- # @option options [Boolean, Symbol] disabled (false) Match disabled button?
99
- # * true - only finds a disabled button
100
- # * false - only finds an enabled button
101
- # * :all - finds either an enabled or disabled button
106
+ # @overload find_button([locator], options={})
107
+ # @param [String] locator id, value, title, text content, alt of image
108
+ #
109
+ # @overload find_button(options={})
110
+ #
111
+ # @macro waiting_behavior
112
+ #
113
+ # @option options [Boolean, Symbol] disabled (false) Match disabled button?
114
+ # * true - only finds a disabled button
115
+ # * false - only finds an enabled button
116
+ # * :all - finds either an enabled or disabled button
102
117
  # @return [Capybara::Node::Element] The found element
103
118
  #
104
- def find_button(locator, options={})
119
+ def find_button(locator=nil, options={})
120
+ locator, options = nil, locator if locator.is_a? Hash
105
121
  find(:button, locator, options)
106
122
  end
107
123
 
@@ -111,7 +127,7 @@ module Capybara
111
127
  #
112
128
  # @macro waiting_behavior
113
129
  #
114
- # @param [String] id Which element to find
130
+ # @param [String] id id of element
115
131
  #
116
132
  # @return [Capybara::Node::Element] The found element
117
133
  #
@@ -123,8 +123,7 @@ module Capybara
123
123
  query = Capybara::Queries::SelectorQuery.new(*args)
124
124
  synchronize(query.wait) do
125
125
  result = query.resolve_for(self)
126
- matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
127
- unless matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
126
+ unless result.matches_count? && ((!result.empty?) || Capybara::Helpers.expects_none?(query.options))
128
127
  raise Capybara::ExpectationNotMet, result.failure_message
129
128
  end
130
129
  end
@@ -151,8 +150,7 @@ module Capybara
151
150
  query = Capybara::Queries::SelectorQuery.new(*args)
152
151
  synchronize(query.wait) do
153
152
  result = query.resolve_for(self)
154
- matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
155
- if matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
153
+ if result.matches_count? && ((!result.empty?) || Capybara::Helpers.expects_none?(query.options))
156
154
  raise Capybara::ExpectationNotMet, result.negative_failure_message
157
155
  end
158
156
  end
@@ -179,7 +177,7 @@ module Capybara
179
177
  def assert_matches_selector(*args)
180
178
  query = Capybara::Queries::MatchQuery.new(*args)
181
179
  synchronize(query.wait) do
182
- result = query.resolve_for(self.parent)
180
+ result = query.resolve_for(self.query_scope)
183
181
  unless result.include? self
184
182
  raise Capybara::ExpectationNotMet, "Item does not match the provided selector"
185
183
  end
@@ -190,7 +188,7 @@ module Capybara
190
188
  def assert_not_matches_selector(*args)
191
189
  query = Capybara::Queries::MatchQuery.new(*args)
192
190
  synchronize(query.wait) do
193
- result = query.resolve_for(self.parent)
191
+ result = query.resolve_for(self.query_scope)
194
192
  if result.include? self
195
193
  raise Capybara::ExpectationNotMet, 'Item matched the provided selector'
196
194
  end
@@ -350,10 +348,10 @@ module Capybara
350
348
  #
351
349
  # Note: 'textarea' and 'select' are valid type values, matching the associated tag names.
352
350
  #
353
- # @param [String] locator The label, name or id of a field to check for
354
- # @option options [String] :with The text content of the field
355
- # @option options [String] :type The type attribute of the field
356
- # @return [Boolean] Whether it exists
351
+ # @param [String] locator The label, name or id of a field to check for
352
+ # @option options [String, Regexp] :with The text content of the field or a Regexp to match
353
+ # @option options [String] :type The type attribute of the field
354
+ # @return [Boolean] Whether it exists
357
355
  #
358
356
  def has_field?(locator, options={})
359
357
  has_selector?(:field, locator, options)
@@ -364,10 +362,10 @@ module Capybara
364
362
  # Checks if the page or current node has no form field with the given
365
363
  # label, name or id. See {Capybara::Node::Matchers#has_field?}.
366
364
  #
367
- # @param [String] locator The label, name or id of a field to check for
368
- # @option options [String] :with The text content of the field
369
- # @option options [String] :type The type attribute of the field
370
- # @return [Boolean] Whether it doesn't exist
365
+ # @param [String] locator The label, name or id of a field to check for
366
+ # @option options [String, Regexp] :with The text content of the field or a Regexp to match
367
+ # @option options [String] :type The type attribute of the field
368
+ # @return [Boolean] Whether it doesn't exist
371
369
  #
372
370
  def has_no_field?(locator, options={})
373
371
  has_no_selector?(:field, locator, options)
@@ -501,7 +499,7 @@ module Capybara
501
499
  #
502
500
  # @!macro text_query_params
503
501
  # @overload $0(type, text, options = {})
504
- # @param [:all, :visible] type Whether to check for only visible or all text
502
+ # @param [:all, :visible] type Whether to check for only visible or all text. If this parameter is missing or nil then we use the value of `Capybara.ignore_hidden_elements`, which defaults to `true`, corresponding to `:visible`.
505
503
  # @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
506
504
  # @option options [Integer] :count (nil) Number of times the text is expected to occur
507
505
  # @option options [Integer] :minimum (nil) Minimum number of times the text is expected to occur
@@ -4,7 +4,7 @@ module Capybara
4
4
  class SelectorQuery < Queries::BaseQuery
5
5
  attr_accessor :selector, :locator, :options, :expression, :find, :negative
6
6
 
7
- VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum, :exact, :match, :wait]
7
+ VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum, :exact, :match, :wait, :filter_set]
8
8
  VALID_MATCH = [:first, :smart, :prefer_exact, :one]
9
9
 
10
10
  def initialize(*args)
@@ -45,15 +45,19 @@ module Capybara
45
45
  regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
46
46
  return false if not node.text(visible).match(regexp)
47
47
  end
48
+
48
49
  case visible
49
50
  when :visible then return false unless node.visible?
50
51
  when :hidden then return false if node.visible?
51
52
  end
52
- selector.custom_filters.each do |name, filter|
53
+
54
+ query_filters.all? do |name, filter|
53
55
  if options.has_key?(name)
54
- return false unless filter.matches?(node, options[name])
56
+ filter.matches?(node, options[name])
55
57
  elsif filter.default?
56
- return false unless filter.matches?(node, filter.default)
58
+ filter.matches?(node, filter.default)
59
+ else
60
+ true
57
61
  end
58
62
  end
59
63
  end
@@ -124,7 +128,20 @@ module Capybara
124
128
  private
125
129
 
126
130
  def valid_keys
127
- COUNT_KEYS + [:text, :visible, :exact, :match, :wait] + @selector.custom_filters.keys
131
+ vk = COUNT_KEYS + [:text, :visible, :exact, :match, :wait, :filter_set]
132
+ vk + custom_keys
133
+ end
134
+
135
+ def query_filters
136
+ if options.has_key?(:filter_set)
137
+ Capybara::Selector::FilterSet.all[options[:filter_set]].filters
138
+ else
139
+ @selector.custom_filters
140
+ end
141
+ end
142
+
143
+ def custom_keys
144
+ query_filters.keys
128
145
  end
129
146
 
130
147
  def assert_valid_keys
@@ -5,6 +5,7 @@ module Capybara
5
5
  class TextQuery < BaseQuery
6
6
  def initialize(*args)
7
7
  @type = (args.first.is_a?(Symbol) || args.first.nil?) ? args.shift : nil
8
+ @type = (Capybara.ignore_hidden_elements or Capybara.visible_text_only) ? :visible : :all if @type.nil?
8
9
  @expected_text, @options = args
9
10
  unless @expected_text.is_a?(Regexp)
10
11
  @expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
@@ -15,11 +16,22 @@ module Capybara
15
16
  end
16
17
 
17
18
  def resolve_for(node)
18
- @actual_text = Capybara::Helpers.normalize_whitespace(node.text(@type))
19
+ @node = node
20
+ @actual_text = text(node, @type)
19
21
  @count = @actual_text.scan(@search_regexp).size
20
22
  end
21
23
 
22
24
  def failure_message
25
+ build_message(true)
26
+ end
27
+
28
+ def negative_failure_message
29
+ build_message(false).sub(/(to find)/, 'not \1')
30
+ end
31
+
32
+ private
33
+
34
+ def build_message(report_on_invisible)
23
35
  description =
24
36
  if @expected_text.is_a?(Regexp)
25
37
  "text matching #{@expected_text.inspect}"
@@ -32,17 +44,41 @@ module Capybara
32
44
  message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
33
45
  end
34
46
  message << " in #{@actual_text.inspect}"
35
- end
36
47
 
37
- def negative_failure_message
38
- failure_message.sub(/(to find)/, 'not \1')
39
- end
48
+ details_message = []
40
49
 
41
- private
50
+ if @node and !@expected_text.is_a? Regexp
51
+ insensitive_regexp = Regexp.new(@expected_text, Regexp::IGNORECASE)
52
+ insensitive_count = @actual_text.scan(insensitive_regexp).size
53
+ if insensitive_count != @count
54
+ details_message << "it was found #{insensitive_count} #{Capybara::Helpers.declension("time", "times", insensitive_count)} using a case insensitive search"
55
+ end
56
+ end
57
+
58
+ if @node and check_visible_text? and report_on_invisible
59
+ invisible_text = text(@node, :all)
60
+ invisible_count = invisible_text.scan(@search_regexp).size
61
+ if invisible_count != @count
62
+ details_message << ". it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
63
+ end
64
+ end
65
+
66
+ message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
67
+
68
+ message
69
+ end
42
70
 
43
71
  def valid_keys
44
72
  COUNT_KEYS + [:wait]
45
73
  end
74
+
75
+ def check_visible_text?
76
+ @type == :visible
77
+ end
78
+
79
+ def text(node, query_type)
80
+ Capybara::Helpers.normalize_whitespace(node.text(query_type))
81
+ end
46
82
  end
47
83
  end
48
84
  end
@@ -17,7 +17,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
17
17
  end
18
18
 
19
19
  def set(value)
20
- if (Array === value) && !self[:multiple]
20
+ if (Array === value) && !multiple?
21
21
  raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
22
22
  end
23
23
 
@@ -60,6 +60,16 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
60
60
  ((tag_name == 'button') and type.nil? or type == "submit")
61
61
  associated_form = form
62
62
  Capybara::RackTest::Form.new(driver, associated_form).submit(self) if associated_form
63
+ elsif (tag_name == 'label')
64
+ labelled_control = if native[:for]
65
+ find_xpath("//input[@id='#{native[:for]}']").first
66
+ else
67
+ find_xpath(".//input").first
68
+ end
69
+
70
+ if labelled_control && (labelled_control.checkbox? || labelled_control.radio?)
71
+ labelled_control.set(!labelled_control.checked?)
72
+ end
63
73
  end
64
74
  end
65
75
 
@@ -183,6 +193,8 @@ private
183
193
  self[attribute] && !self[attribute].empty?
184
194
  end
185
195
 
196
+ protected
197
+
186
198
  def checkbox?
187
199
  input_field? && type == 'checkbox'
188
200
  end
@@ -25,27 +25,74 @@ module Capybara
25
25
 
26
26
  def initialize(elements, query)
27
27
  @elements = elements
28
- @result = elements.select { |node| query.matches_filters?(node) }
29
- @rest = @elements - @result
28
+ @result_cache = []
29
+ @results_enum = lazy_select_elements { |node| query.matches_filters?(node) }
30
30
  @query = query
31
31
  end
32
32
 
33
- def_delegators :@result, :each, :[], :at, :size, :count, :length,
34
- :first, :last, :values_at, :empty?, :inspect, :sample, :index
33
+ def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
34
+
35
+ alias :index :find_index
36
+
37
+ def each(&block)
38
+ @result_cache.each(&block)
39
+ loop do
40
+ next_result = @results_enum.next
41
+ @result_cache << next_result
42
+ block.call(next_result)
43
+ end
44
+ self
45
+ end
46
+
47
+ def [](*args)
48
+ if (args.size == 1) && ((idx = args[0]).is_a? Integer) && (idx > 0)
49
+ @result_cache << @results_enum.next while @result_cache.size <= idx
50
+ @result_cache[idx]
51
+ else
52
+ full_results[*args]
53
+ end
54
+ rescue StopIteration
55
+ return nil
56
+ end
57
+ alias :at :[]
58
+
59
+ def empty?
60
+ !any?
61
+ end
35
62
 
36
63
  def matches_count?
37
- Capybara::Helpers.matches_count?(@result.size, @query.options)
64
+ return Integer(@query.options[:count]) == count if @query.options[:count]
65
+
66
+ return false if @query.options[:between] && !(@query.options[:between] === count)
67
+
68
+ if @query.options[:minimum]
69
+ begin
70
+ @result_cache << @results_enum.next while @result_cache.size < Integer(@query.options[:minimum])
71
+ rescue StopIteration
72
+ return false
73
+ end
74
+ end
75
+
76
+ if @query.options[:maximum]
77
+ begin
78
+ @result_cache << @results_enum.next while @result_cache.size <= Integer(@query.options[:maximum])
79
+ return false
80
+ rescue StopIteration
81
+ end
82
+ end
83
+
84
+ return true
38
85
  end
39
86
 
40
87
  def failure_message
41
88
  message = Capybara::Helpers.failure_message(@query.description, @query.options)
42
89
  if count > 0
43
- message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << @result.map(&:text).map(&:inspect).join(", ")
90
+ message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << full_results.map(&:text).map(&:inspect).join(", ")
44
91
  else
45
92
  message << " but there were no matches"
46
93
  end
47
- unless @rest.empty?
48
- elements = @rest.map(&:text).map(&:inspect).join(", ")
94
+ unless rest.empty?
95
+ elements = rest.map(&:text).map(&:inspect).join(", ")
49
96
  message << ". Also found " << elements << ", which matched the selector but not all filters."
50
97
  end
51
98
  message
@@ -54,5 +101,30 @@ module Capybara
54
101
  def negative_failure_message
55
102
  failure_message.sub(/(to find)/, 'not \1')
56
103
  end
104
+
105
+ private
106
+
107
+ def full_results
108
+ loop do
109
+ @result_cache << @results_enum.next
110
+ end
111
+ @result_cache
112
+ end
113
+
114
+ def rest
115
+ @rest ||= @elements - full_results
116
+ end
117
+
118
+ def lazy_select_elements(&block)
119
+ if @elements.respond_to? :lazy #Ruby 2.0+
120
+ @elements.lazy.select &block
121
+ else
122
+ Enumerator.new do |yielder|
123
+ @elements.each do |val|
124
+ yielder.yield(val) if block.call(val)
125
+ end
126
+ end
127
+ end
128
+ end
57
129
  end
58
130
  end