capybara 2.7.1 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +22 -0
- data/README.md +27 -3
- data/lib/capybara.rb +19 -4
- data/lib/capybara/driver/base.rb +6 -2
- data/lib/capybara/driver/node.rb +13 -5
- data/lib/capybara/helpers.rb +2 -2
- data/lib/capybara/node/actions.rb +116 -17
- data/lib/capybara/node/base.rb +7 -1
- data/lib/capybara/node/element.rb +23 -3
- data/lib/capybara/node/finders.rb +45 -29
- data/lib/capybara/node/matchers.rb +13 -15
- data/lib/capybara/queries/selector_query.rb +22 -5
- data/lib/capybara/queries/text_query.rb +42 -6
- data/lib/capybara/rack_test/node.rb +13 -1
- data/lib/capybara/result.rb +80 -8
- data/lib/capybara/rspec/features.rb +13 -6
- data/lib/capybara/selector.rb +98 -71
- data/lib/capybara/selector/filter_set.rb +46 -0
- data/lib/capybara/selenium/driver.rb +22 -23
- data/lib/capybara/selenium/node.rb +14 -6
- data/lib/capybara/server.rb +20 -10
- data/lib/capybara/session.rb +44 -8
- data/lib/capybara/spec/session/all_spec.rb +4 -4
- data/lib/capybara/spec/session/assert_text.rb +23 -0
- data/lib/capybara/spec/session/check_spec.rb +66 -8
- data/lib/capybara/spec/session/choose_spec.rb +20 -0
- data/lib/capybara/spec/session/click_button_spec.rb +0 -3
- data/lib/capybara/spec/session/click_link_spec.rb +7 -0
- data/lib/capybara/spec/session/execute_script_spec.rb +6 -1
- data/lib/capybara/spec/session/find_button_spec.rb +19 -1
- data/lib/capybara/spec/session/find_field_spec.rb +21 -1
- data/lib/capybara/spec/session/find_link_spec.rb +19 -1
- data/lib/capybara/spec/session/find_spec.rb +32 -4
- data/lib/capybara/spec/session/first_spec.rb +4 -4
- data/lib/capybara/spec/session/has_field_spec.rb +4 -0
- data/lib/capybara/spec/session/has_text_spec.rb +2 -2
- data/lib/capybara/spec/session/node_spec.rb +24 -3
- data/lib/capybara/spec/session/reset_session_spec.rb +7 -0
- data/lib/capybara/spec/session/selectors_spec.rb +14 -0
- data/lib/capybara/spec/session/uncheck_spec.rb +39 -0
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -2
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
- data/lib/capybara/spec/session/window/window_spec.rb +36 -22
- data/lib/capybara/spec/session/within_frame_spec.rb +19 -0
- data/lib/capybara/spec/spec_helper.rb +3 -0
- data/lib/capybara/spec/views/form.erb +34 -6
- data/lib/capybara/spec/views/with_html.erb +5 -1
- data/lib/capybara/spec/views/with_unload_alert.erb +3 -1
- data/lib/capybara/spec/views/with_windows.erb +2 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/capybara_spec.rb +34 -0
- data/spec/rack_test_spec.rb +24 -0
- data/spec/result_spec.rb +25 -0
- data/spec/rspec/features_spec.rb +3 -3
- data/spec/selenium_spec.rb +6 -3
- data/spec/server_spec.rb +2 -2
- metadata +18 -4
data/lib/capybara/node/base.rb
CHANGED
@@ -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, :
|
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,
|
26
|
+
def initialize(session, base, query_scope, query)
|
27
27
|
super(session, base)
|
28
|
-
@
|
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 =
|
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.
|
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.
|
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
|
-
# @
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
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
|
-
|
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
|
-
# @
|
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
|
-
#
|
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
|
-
# @
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
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
|
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
|
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
|
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.
|
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.
|
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
|
354
|
-
# @option options [String] :with
|
355
|
-
# @option options [String] :type
|
356
|
-
# @return [Boolean]
|
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
|
368
|
-
# @option options [String] :with
|
369
|
-
# @option options [String] :type
|
370
|
-
# @return [Boolean]
|
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
|
-
|
53
|
+
|
54
|
+
query_filters.all? do |name, filter|
|
53
55
|
if options.has_key?(name)
|
54
|
-
|
56
|
+
filter.matches?(node, options[name])
|
55
57
|
elsif filter.default?
|
56
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
38
|
-
failure_message.sub(/(to find)/, 'not \1')
|
39
|
-
end
|
48
|
+
details_message = []
|
40
49
|
|
41
|
-
|
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) && !
|
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
|
data/lib/capybara/result.rb
CHANGED
@@ -25,27 +25,74 @@ module Capybara
|
|
25
25
|
|
26
26
|
def initialize(elements, query)
|
27
27
|
@elements = elements
|
28
|
-
@
|
29
|
-
@
|
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
|
34
|
-
|
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
|
-
|
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)}: " <<
|
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
|
48
|
-
elements =
|
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
|