capybara 2.7.1 → 2.8.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 (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