capybara 2.0.3 → 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data.tar.gz.sig +0 -0
  2. data/History.md +73 -0
  3. data/README.md +52 -5
  4. data/lib/capybara.rb +44 -62
  5. data/lib/capybara/cucumber.rb +4 -1
  6. data/lib/capybara/driver/base.rb +13 -9
  7. data/lib/capybara/driver/node.rb +18 -6
  8. data/lib/capybara/helpers.rb +111 -22
  9. data/lib/capybara/node/actions.rb +24 -19
  10. data/lib/capybara/node/base.rb +25 -32
  11. data/lib/capybara/node/document.rb +6 -2
  12. data/lib/capybara/node/element.rb +45 -7
  13. data/lib/capybara/node/finders.rb +48 -21
  14. data/lib/capybara/node/matchers.rb +38 -21
  15. data/lib/capybara/node/simple.rb +29 -7
  16. data/lib/capybara/query.rb +72 -26
  17. data/lib/capybara/rack_test/browser.rb +11 -3
  18. data/lib/capybara/rack_test/css_handlers.rb +8 -0
  19. data/lib/capybara/rack_test/driver.rb +11 -3
  20. data/lib/capybara/rack_test/form.rb +8 -1
  21. data/lib/capybara/rack_test/node.rb +92 -36
  22. data/lib/capybara/rails.rb +1 -2
  23. data/lib/capybara/result.rb +21 -37
  24. data/lib/capybara/rspec/matchers.rb +60 -31
  25. data/lib/capybara/selector.rb +46 -13
  26. data/lib/capybara/selenium/driver.rb +42 -6
  27. data/lib/capybara/selenium/node.rb +23 -5
  28. data/lib/capybara/session.rb +46 -11
  29. data/lib/capybara/spec/public/test.js +12 -0
  30. data/lib/capybara/spec/session/all_spec.rb +9 -8
  31. data/lib/capybara/spec/session/attach_file_spec.rb +14 -0
  32. data/lib/capybara/spec/session/check_spec.rb +14 -0
  33. data/lib/capybara/spec/session/choose_spec.rb +14 -0
  34. data/lib/capybara/spec/session/click_button_spec.rb +77 -1
  35. data/lib/capybara/spec/session/click_link_or_button_spec.rb +65 -0
  36. data/lib/capybara/spec/session/click_link_spec.rb +24 -0
  37. data/lib/capybara/spec/session/current_scope_spec.rb +29 -0
  38. data/lib/capybara/spec/session/fill_in_spec.rb +14 -0
  39. data/lib/capybara/spec/session/find_button_spec.rb +12 -0
  40. data/lib/capybara/spec/session/find_by_id_spec.rb +12 -1
  41. data/lib/capybara/spec/session/find_field_spec.rb +30 -0
  42. data/lib/capybara/spec/session/find_link_spec.rb +12 -0
  43. data/lib/capybara/spec/session/find_spec.rb +258 -16
  44. data/lib/capybara/spec/session/first_spec.rb +25 -8
  45. data/lib/capybara/spec/session/has_css_spec.rb +2 -2
  46. data/lib/capybara/spec/session/has_field_spec.rb +10 -2
  47. data/lib/capybara/spec/session/has_selector_spec.rb +9 -0
  48. data/lib/capybara/spec/session/has_text_spec.rb +96 -0
  49. data/lib/capybara/spec/session/has_title_spec.rb +47 -0
  50. data/lib/capybara/spec/session/node_spec.rb +43 -0
  51. data/lib/capybara/spec/session/reset_session_spec.rb +10 -2
  52. data/lib/capybara/spec/session/save_page_spec.rb +30 -0
  53. data/lib/capybara/spec/session/select_spec.rb +65 -0
  54. data/lib/capybara/spec/session/text_spec.rb +31 -0
  55. data/lib/capybara/spec/session/title_spec.rb +16 -0
  56. data/lib/capybara/spec/session/uncheck_spec.rb +14 -0
  57. data/lib/capybara/spec/session/unselect_spec.rb +42 -0
  58. data/lib/capybara/spec/session/visit_spec.rb +3 -3
  59. data/lib/capybara/spec/session/within_frame_spec.rb +14 -0
  60. data/lib/capybara/spec/session/within_spec.rb +3 -3
  61. data/lib/capybara/spec/spec_helper.rb +6 -1
  62. data/lib/capybara/spec/views/form.erb +39 -2
  63. data/lib/capybara/spec/views/frame_child.erb +9 -0
  64. data/lib/capybara/spec/views/frame_parent.erb +8 -0
  65. data/lib/capybara/spec/views/with_base_tag.erb +10 -0
  66. data/lib/capybara/spec/views/with_count.erb +7 -0
  67. data/lib/capybara/spec/views/with_hover.erb +17 -0
  68. data/lib/capybara/spec/views/with_html.erb +21 -2
  69. data/lib/capybara/spec/views/with_js.erb +5 -0
  70. data/lib/capybara/spec/views/with_scope.erb +6 -1
  71. data/lib/capybara/spec/views/with_title.erb +1 -0
  72. data/lib/capybara/spec/views/within_frames.erb +1 -0
  73. data/lib/capybara/version.rb +1 -1
  74. data/spec/basic_node_spec.rb +75 -24
  75. data/spec/dsl_spec.rb +2 -1
  76. data/spec/rack_test_spec.rb +4 -3
  77. data/spec/rspec/matchers_spec.rb +105 -17
  78. data/spec/server_spec.rb +8 -8
  79. data/spec/spec_helper.rb +2 -1
  80. metadata +83 -23
  81. metadata.gz.sig +0 -0
@@ -1,31 +1,120 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Capybara
4
+
5
+ # @api private
4
6
  module Helpers
5
- class << self
6
- ##
7
- #
8
- # Normalizes whitespace space by stripping leading and trailing
9
- # whitespace and replacing sequences of whitespace characters
10
- # with a single space.
11
- #
12
- # @param [String] text Text to normalize
13
- # @return [String] Normalized text
14
- #
15
- def normalize_whitespace(text)
16
- text.to_s.gsub(/[[:space:]]+/, ' ').strip
7
+ extend self
8
+
9
+ ##
10
+ #
11
+ # Normalizes whitespace space by stripping leading and trailing
12
+ # whitespace and replacing sequences of whitespace characters
13
+ # with a single space.
14
+ #
15
+ # @param [String] text Text to normalize
16
+ # @return [String] Normalized text
17
+ #
18
+ def normalize_whitespace(text)
19
+ text.to_s.gsub(/[[:space:]]+/, ' ').strip
20
+ end
21
+
22
+ ##
23
+ #
24
+ # Escapes any characters that would have special meaning in a regexp
25
+ # if text is not a regexp
26
+ #
27
+ # @param [String] text Text to escape
28
+ # @return [String] Escaped text
29
+ #
30
+ def to_regexp(text)
31
+ text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(normalize_whitespace(text)))
32
+ end
33
+
34
+ ##
35
+ #
36
+ # Injects a `<base>` tag into the given HTML code, pointing to
37
+ # `Capybara.asset_host`.
38
+ #
39
+ # @param [String] html HTML code to inject into
40
+ # @param [String] The modified HTML code
41
+ #
42
+ def inject_asset_host(html)
43
+ if Capybara.asset_host
44
+ if Nokogiri::HTML(html).css("base").empty? and match = html.match(/<head[^<]*?>/)
45
+ html.clone.insert match.end(0), "<base href='#{Capybara.asset_host}' />"
46
+ end
47
+ else
48
+ html
17
49
  end
50
+ end
51
+
52
+ ##
53
+ #
54
+ # Checks if the given count matches the given count options. By default,
55
+ # when no options are given, count should be larger than zero.
56
+ #
57
+ # @param [Integer] count The actual number. Should be coercible via Integer()
58
+ # @option [Range] between Count must be within the given range
59
+ # @option [Integer] count Count must be exactly this
60
+ # @option [Integer] maximum Count must be smaller than or equal to this value
61
+ # @option [Integer] minimum Count must be larger than or equal to this value
62
+ #
63
+ def matches_count?(count, options={})
64
+ case
65
+ when options[:between]
66
+ options[:between] === count
67
+ when options[:count]
68
+ Integer(options[:count]) == count
69
+ when options[:maximum]
70
+ Integer(options[:maximum]) >= count
71
+ when options[:minimum]
72
+ Integer(options[:minimum]) <= count
73
+ else
74
+ count > 0
75
+ end
76
+ end
77
+
78
+ ##
79
+ #
80
+ # Generates a failure message given a description of the query and count
81
+ # options.
82
+ #
83
+ # @param [String] description Description of a query
84
+ # @option [Range] between Count should have been within the given range
85
+ # @option [Integer] count Count should have been exactly this
86
+ # @option [Integer] maximum Count should have been smaller than or equal to this value
87
+ # @option [Integer] minimum Count should have been larger than or equal to this value
88
+ #
89
+ def failure_message(description, options={})
90
+ message = "expected to find #{description}"
91
+ if options[:count]
92
+ message << " #{options[:count]} #{declension('time', 'times', options[:count])}"
93
+ elsif options[:between]
94
+ message << " between #{options[:between].first} and #{options[:between].last} times"
95
+ elsif options[:maximum]
96
+ message << " at most #{options[:maximum]} #{declension('time', 'times', options[:maximum])}"
97
+ elsif options[:minimum]
98
+ message << " at least #{options[:minimum]} #{declension('time', 'times', options[:minimum])}"
99
+ end
100
+ message
101
+ end
18
102
 
19
- ##
20
- #
21
- # Escapes any characters that would have special meaning in a regexp
22
- # if text is not a regexp
23
- #
24
- # @param [String] text Text to escape
25
- # @return [String] Escaped text
26
- #
27
- def to_regexp(text)
28
- text.is_a?(Regexp) ? text : Regexp.escape(normalize_whitespace(text))
103
+ ##
104
+ #
105
+ # A poor man's `pluralize`. Given two declensions, one singular and one
106
+ # plural, as well as a count, this will pick the correct declension. This
107
+ # way we can generate gramatically correct error message.
108
+ #
109
+ # @param [String] singular The singular form of the word
110
+ # @param [String] plural The plural form of the word
111
+ # @param [Integer] count The number of items
112
+ #
113
+ def declension(singular, plural, count)
114
+ if count == 1
115
+ singular
116
+ else
117
+ plural
29
118
  end
30
119
  end
31
120
  end
@@ -9,8 +9,8 @@ module Capybara
9
9
  #
10
10
  # @param [String] locator Text, id or value of link or button
11
11
  #
12
- def click_link_or_button(locator)
13
- find(:link_or_button, locator).click
12
+ def click_link_or_button(locator, options={})
13
+ find(:link_or_button, locator, options).click
14
14
  end
15
15
  alias_method :click_on, :click_link_or_button
16
16
 
@@ -20,9 +20,11 @@ module Capybara
20
20
  # alt text inside the link.
21
21
  #
22
22
  # @param [String] locator Text, id or text of link
23
+ # @param options
24
+ # @option options [String] :href The value the href attribute must be
23
25
  #
24
- def click_link(locator)
25
- find(:link, locator).click
26
+ def click_link(locator, options={})
27
+ find(:link, locator, options).click
26
28
  end
27
29
 
28
30
  ##
@@ -31,8 +33,8 @@ module Capybara
31
33
  #
32
34
  # @param [String] locator Text, id or value of button
33
35
  #
34
- def click_button(locator)
35
- find(:button, locator).click
36
+ def click_button(locator, options={})
37
+ find(:button, locator, options).click
36
38
  end
37
39
 
38
40
  ##
@@ -47,7 +49,8 @@ module Capybara
47
49
  #
48
50
  def fill_in(locator, options={})
49
51
  raise "Must pass a hash containing 'with'" if not options.is_a?(Hash) or not options.has_key?(:with)
50
- find(:fillable_field, locator).set(options[:with])
52
+ with = options.delete(:with)
53
+ find(:fillable_field, locator, options).set(with)
51
54
  end
52
55
 
53
56
  ##
@@ -59,8 +62,8 @@ module Capybara
59
62
  #
60
63
  # @param [String] locator Which radio button to choose
61
64
  #
62
- def choose(locator)
63
- find(:radio_button, locator).set(true)
65
+ def choose(locator, options={})
66
+ find(:radio_button, locator, options).set(true)
64
67
  end
65
68
 
66
69
  ##
@@ -72,8 +75,8 @@ module Capybara
72
75
  #
73
76
  # @param [String] locator Which check box to check
74
77
  #
75
- def check(locator)
76
- find(:checkbox, locator).set(true)
78
+ def check(locator, options={})
79
+ find(:checkbox, locator, options).set(true)
77
80
  end
78
81
 
79
82
  ##
@@ -85,8 +88,8 @@ module Capybara
85
88
  #
86
89
  # @param [String] locator Which check box to uncheck
87
90
  #
88
- def uncheck(locator)
89
- find(:checkbox, locator).set(false)
91
+ def uncheck(locator, options={})
92
+ find(:checkbox, locator, options).set(false)
90
93
  end
91
94
 
92
95
  ##
@@ -102,9 +105,10 @@ module Capybara
102
105
  #
103
106
  def select(value, options={})
104
107
  if options.has_key?(:from)
105
- find(:select, options[:from]).find(:option, value).select_option
108
+ from = options.delete(:from)
109
+ find(:select, from, options).find(:option, value, options).select_option
106
110
  else
107
- find(:option, value).select_option
111
+ find(:option, value, options).select_option
108
112
  end
109
113
  end
110
114
 
@@ -121,9 +125,10 @@ module Capybara
121
125
  #
122
126
  def unselect(value, options={})
123
127
  if options.has_key?(:from)
124
- find(:select, options[:from]).find(:option, value).unselect_option
128
+ from = options.delete(:from)
129
+ find(:select, from, options).find(:option, value, options).unselect_option
125
130
  else
126
- find(:option, value).unselect_option
131
+ find(:option, value, options).unselect_option
127
132
  end
128
133
  end
129
134
 
@@ -137,11 +142,11 @@ module Capybara
137
142
  # @param [String] locator Which field to attach the file to
138
143
  # @param [String] path The path of the file that will be attached, or an array of paths
139
144
  #
140
- def attach_file(locator, path)
145
+ def attach_file(locator, path, options={})
141
146
  Array(path).each do |p|
142
147
  raise Capybara::FileNotFound, "cannot attach file, #{p} does not exist" unless File.exist?(p.to_s)
143
148
  end
144
- find(:file_field, locator).set(path)
149
+ find(:file_field, locator, options).set(path)
145
150
  end
146
151
  end
147
152
  end
@@ -31,7 +31,6 @@ module Capybara
31
31
  def initialize(session, base)
32
32
  @session = session
33
33
  @base = base
34
- @unsynchronized = false
35
34
  end
36
35
 
37
36
  # overridden in subclasses, e.g. Capybara::Node::Element
@@ -64,50 +63,44 @@ module Capybara
64
63
  # until a certain amount of time passes. The amount of time defaults to
65
64
  # {Capybara.default_wait_time} and can be overriden through the `seconds`
66
65
  # argument. This time is compared with the system time to see how much
67
- # time has passed. If the return value of {Time.now} is stubbed out,
66
+ # time has passed. If the return value of `Time.now` is stubbed out,
68
67
  # Capybara will raise `Capybara::FrozenInTime`.
69
68
  #
70
- # @param [Integer] seconds Number of seconds to retry this block
69
+ # @param [Integer] seconds Number of seconds to retry this block
71
70
  # @return [Object] The result of the given block
72
- # @raise [Capybara::FrozenInTime] If the return value of {Time.now} appears stuck
71
+ # @raise [Capybara::FrozenInTime] If the return value of `Time.now` appears stuck
73
72
  #
74
73
  def synchronize(seconds=Capybara.default_wait_time)
75
74
  start_time = Time.now
76
75
 
77
- begin
76
+ if session.synchronized
78
77
  yield
79
- rescue => e
80
- raise e if @unsynchronized
81
- raise e unless driver.wait?
82
- raise e unless driver.invalid_element_errors.include?(e.class) || e.is_a?(Capybara::ElementNotFound)
83
- raise e if (Time.now - start_time) >= seconds
84
- sleep(0.05)
85
- raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if Time.now == start_time
86
- reload if Capybara.automatic_reload
87
- retry
78
+ else
79
+ session.synchronized = true
80
+ begin
81
+ yield
82
+ rescue => e
83
+ raise e unless driver.wait?
84
+ raise e unless catch_error?(e)
85
+ raise e if (Time.now - start_time) >= seconds
86
+ sleep(0.05)
87
+ raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if Time.now == start_time
88
+ reload if Capybara.automatic_reload
89
+ retry
90
+ ensure
91
+ session.synchronized = false
92
+ end
88
93
  end
89
94
  end
90
95
 
91
- ##
92
- #
93
- # Within the given block, prevent synchronize from having any effect.
94
- #
95
- # This is an internal method which should not be called unless you are
96
- # absolutely sure of what you're doing.
97
- #
98
- # @api private
99
- # @return [Object] The result of the given block
100
- #
101
- def unsynchronized
102
- orig = @unsynchronized
103
- @unsynchronized = true
104
- yield
105
- ensure
106
- @unsynchronized = orig
107
- end
108
-
109
96
  protected
110
97
 
98
+ def catch_error?(error)
99
+ (driver.invalid_element_errors + [Capybara::ElementNotFound]).any? do |type|
100
+ error.is_a?(type)
101
+ end
102
+ end
103
+
111
104
  def driver
112
105
  session.driver
113
106
  end
@@ -17,8 +17,12 @@ module Capybara
17
17
  #
18
18
  # @return [String] The text of the document
19
19
  #
20
- def text
21
- find(:xpath, '/html').text
20
+ def text(type=nil)
21
+ find(:xpath, '/html').text(type)
22
+ end
23
+
24
+ def title
25
+ session.driver.title
22
26
  end
23
27
  end
24
28
  end
@@ -42,10 +42,26 @@ module Capybara
42
42
 
43
43
  ##
44
44
  #
45
- # @return [String] The text of the element
46
- #
47
- def text
48
- synchronize { base.text }
45
+ # Retrieve the text of the element. If `Capybara.ignore_hidden_elements`
46
+ # is `true`, which it is by default, then this will return only text
47
+ # which is visible. The exact semantics of this may differ between
48
+ # drivers, but generally any text within elements with `display:none` is
49
+ # ignored. This behaviour can be overridden by passing `:all` to this
50
+ # method.
51
+ #
52
+ # @param [:all, :visible] Whether to return only visible or all text
53
+ #
54
+ # @return [String] The text of the element
55
+ #
56
+ def text(type=nil)
57
+ type ||= :all unless Capybara.ignore_hidden_elements or Capybara.visible_text_only
58
+ synchronize do
59
+ if type == :all
60
+ base.all_text
61
+ else
62
+ base.visible_text
63
+ end
64
+ end
49
65
  end
50
66
 
51
67
  ##
@@ -103,6 +119,14 @@ module Capybara
103
119
  synchronize { base.click }
104
120
  end
105
121
 
122
+ ##
123
+ #
124
+ # Hover on the Element
125
+ #
126
+ def hover
127
+ synchronize { base.hover }
128
+ end
129
+
106
130
  ##
107
131
  #
108
132
  # @return [String] The tag name of the element
@@ -142,6 +166,16 @@ module Capybara
142
166
  synchronize { base.selected? }
143
167
  end
144
168
 
169
+ ##
170
+ #
171
+ # Whether or not the element is disabled.
172
+ #
173
+ # @return [Boolean] Whether the element is disabled
174
+ #
175
+ def disabled?
176
+ synchronize { base.disabled? }
177
+ end
178
+
145
179
  ##
146
180
  #
147
181
  # An XPath expression describing where on the page the element can be found
@@ -179,15 +213,19 @@ module Capybara
179
213
 
180
214
  def reload
181
215
  if @allow_reload
182
- reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
183
- @base = reloaded.base if reloaded
216
+ begin
217
+ reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
218
+ @base = reloaded.base if reloaded
219
+ rescue => e
220
+ raise e unless catch_error?(e)
221
+ end
184
222
  end
185
223
  self
186
224
  end
187
225
 
188
226
  def inspect
189
227
  %(#<Capybara::Element tag="#{tag_name}" path="#{path}">)
190
- rescue NotSupportedByDriverError
228
+ rescue NotSupportedByDriverError, 'Capybara::Node::Element#inspect'
191
229
  %(#<Capybara::Element tag="#{tag_name}">)
192
230
  end
193
231
  end
@@ -19,11 +19,29 @@ module Capybara
19
19
  # page.find('li', :text => 'Quox').click_link('Delete')
20
20
  #
21
21
  # @param (see Capybara::Node::Finders#all)
22
- # @return [Capybara::Element] The found element
23
- # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
22
+ # @option options [Boolean] match The matching strategy to use.
23
+ # @option options [false, Numeric] wait How long to wait for the element to appear.
24
+ #
25
+ # @return [Capybara::Element] The found element
26
+ # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
24
27
  #
25
28
  def find(*args)
26
- synchronize { all(*args).find! }.tap(&:allow_reload!)
29
+ query = Capybara::Query.new(*args)
30
+ synchronize(query.wait) do
31
+ if query.match == :smart or query.match == :prefer_exact
32
+ result = resolve_query(query, true)
33
+ result = resolve_query(query, false) if result.size == 0 and not query.exact?
34
+ else
35
+ result = resolve_query(query)
36
+ end
37
+ if query.match == :one or query.match == :smart and result.size > 1
38
+ raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
39
+ end
40
+ if result.size == 0
41
+ raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
42
+ end
43
+ result.first
44
+ end.tap(&:allow_reload!)
27
45
  end
28
46
 
29
47
  ##
@@ -33,8 +51,8 @@ module Capybara
33
51
  # @param [String] locator Which field to find
34
52
  # @return [Capybara::Element] The found element
35
53
  #
36
- def find_field(locator)
37
- find(:field, locator)
54
+ def find_field(locator, options={})
55
+ find(:field, locator, options)
38
56
  end
39
57
  alias_method :field_labeled, :find_field
40
58
 
@@ -45,8 +63,8 @@ module Capybara
45
63
  # @param [String] locator Which link to find
46
64
  # @return [Capybara::Element] The found element
47
65
  #
48
- def find_link(locator)
49
- find(:link, locator)
66
+ def find_link(locator, options={})
67
+ find(:link, locator, options)
50
68
  end
51
69
 
52
70
  ##
@@ -56,8 +74,8 @@ module Capybara
56
74
  # @param [String] locator Which button to find
57
75
  # @return [Capybara::Element] The found element
58
76
  #
59
- def find_button(locator)
60
- find(:button, locator)
77
+ def find_button(locator, options={})
78
+ find(:button, locator, options)
61
79
  end
62
80
 
63
81
  ##
@@ -67,8 +85,8 @@ module Capybara
67
85
  # @param [String] id Which element to find
68
86
  # @return [Capybara::Element] The found element
69
87
  #
70
- def find_by_id(id)
71
- find(:id, id)
88
+ def find_by_id(id, options={})
89
+ find(:id, id, options)
72
90
  end
73
91
 
74
92
  ##
@@ -103,18 +121,12 @@ module Capybara
103
121
  # @param [String] locator The selector
104
122
  # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
105
123
  # @option options [Boolean] visible Only find elements that are visible on the page. Setting this to false
106
- # (the default, unless Capybara.ignore_hidden_elements = true), finds
107
- # invisible _and_ visible elements.
108
- # @return [Array[Capybara::Element]] The found elements
124
+ # finds invisible _and_ visible elements.
125
+ # @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially
126
+ # @return [Capybara::Result] A collection of found elements
109
127
  #
110
128
  def all(*args)
111
- query = Capybara::Query.new(*args)
112
- elements = synchronize do
113
- base.find(query.xpath).map do |node|
114
- Capybara::Node::Element.new(session, node, self, query)
115
- end
116
- end
117
- Capybara::Result.new(elements, query)
129
+ resolve_query(Capybara::Query.new(*args))
118
130
  end
119
131
 
120
132
  ##
@@ -131,6 +143,21 @@ module Capybara
131
143
  def first(*args)
132
144
  all(*args).first
133
145
  end
146
+
147
+ private
148
+
149
+ def resolve_query(query, exact=nil)
150
+ elements = synchronize do
151
+ if query.selector.format==:css
152
+ base.find_css(query.css)
153
+ else
154
+ base.find_xpath(query.xpath(exact))
155
+ end.map do |node|
156
+ Capybara::Node::Element.new(session, node, self, query)
157
+ end
158
+ end
159
+ Capybara::Result.new(elements, query)
160
+ end
134
161
  end
135
162
  end
136
163
  end