capybara 1.1.4 → 2.0.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 (111) hide show
  1. data/{History.txt → History.md} +138 -0
  2. data/License.txt +22 -0
  3. data/README.md +850 -0
  4. data/lib/capybara/cucumber.rb +2 -5
  5. data/lib/capybara/driver/base.rb +6 -6
  6. data/lib/capybara/driver/node.rb +3 -2
  7. data/lib/capybara/dsl.rb +13 -124
  8. data/lib/capybara/helpers.rb +33 -0
  9. data/lib/capybara/node/actions.rb +16 -30
  10. data/lib/capybara/node/base.rb +56 -13
  11. data/lib/capybara/node/element.rb +18 -30
  12. data/lib/capybara/node/finders.rb +28 -90
  13. data/lib/capybara/node/matchers.rb +121 -73
  14. data/lib/capybara/node/simple.rb +13 -11
  15. data/lib/capybara/query.rb +78 -0
  16. data/lib/capybara/rack_test/browser.rb +27 -39
  17. data/lib/capybara/rack_test/driver.rb +13 -3
  18. data/lib/capybara/rack_test/node.rb +31 -2
  19. data/lib/capybara/result.rb +72 -0
  20. data/lib/capybara/rspec/features.rb +4 -1
  21. data/lib/capybara/rspec/matchers.rb +33 -63
  22. data/lib/capybara/rspec.rb +7 -4
  23. data/lib/capybara/selector.rb +97 -34
  24. data/lib/capybara/selenium/driver.rb +15 -62
  25. data/lib/capybara/selenium/node.rb +14 -21
  26. data/lib/capybara/server.rb +32 -27
  27. data/lib/capybara/session.rb +90 -50
  28. data/lib/capybara/spec/fixtures/another_test_file.txt +1 -0
  29. data/lib/capybara/spec/public/jquery-ui.js +791 -0
  30. data/lib/capybara/spec/public/jquery.js +9046 -0
  31. data/lib/capybara/spec/public/test.js +3 -0
  32. data/lib/capybara/spec/session/all_spec.rb +61 -59
  33. data/lib/capybara/spec/session/assert_selector.rb +123 -0
  34. data/lib/capybara/spec/session/attach_file_spec.rb +72 -55
  35. data/lib/capybara/spec/session/body_spec.rb +21 -0
  36. data/lib/capybara/spec/session/check_spec.rb +68 -48
  37. data/lib/capybara/spec/session/choose_spec.rb +32 -18
  38. data/lib/capybara/spec/session/click_button_spec.rb +263 -232
  39. data/lib/capybara/spec/session/click_link_or_button_spec.rb +40 -29
  40. data/lib/capybara/spec/session/click_link_spec.rb +96 -96
  41. data/lib/capybara/spec/session/current_url_spec.rb +88 -10
  42. data/lib/capybara/spec/session/evaluate_script_spec.rb +6 -0
  43. data/lib/capybara/spec/session/execute_script_spec.rb +7 -0
  44. data/lib/capybara/spec/session/fill_in_spec.rb +119 -103
  45. data/lib/capybara/spec/session/find_button_spec.rb +16 -14
  46. data/lib/capybara/spec/session/find_by_id_spec.rb +16 -14
  47. data/lib/capybara/spec/session/find_field_spec.rb +23 -21
  48. data/lib/capybara/spec/session/find_link_spec.rb +15 -14
  49. data/lib/capybara/spec/session/find_spec.rb +93 -115
  50. data/lib/capybara/spec/session/first_spec.rb +51 -85
  51. data/lib/capybara/spec/session/has_button_spec.rb +22 -24
  52. data/lib/capybara/spec/session/has_css_spec.rb +190 -205
  53. data/lib/capybara/spec/session/has_field_spec.rb +170 -144
  54. data/lib/capybara/spec/session/has_link_spec.rb +26 -29
  55. data/lib/capybara/spec/session/has_select_spec.rb +161 -109
  56. data/lib/capybara/spec/session/has_selector_spec.rb +94 -100
  57. data/lib/capybara/spec/session/has_table_spec.rb +22 -88
  58. data/lib/capybara/spec/session/has_text_spec.rb +195 -0
  59. data/lib/capybara/spec/session/has_xpath_spec.rb +100 -96
  60. data/lib/capybara/spec/session/headers.rb +4 -17
  61. data/lib/capybara/spec/session/html_spec.rb +15 -0
  62. data/lib/capybara/spec/session/node_spec.rb +205 -0
  63. data/lib/capybara/spec/session/reset_session_spec.rb +42 -0
  64. data/lib/capybara/spec/session/response_code.rb +4 -17
  65. data/lib/capybara/spec/session/save_page_spec.rb +46 -0
  66. data/lib/capybara/spec/session/screenshot.rb +13 -0
  67. data/lib/capybara/spec/session/select_spec.rb +99 -88
  68. data/lib/capybara/spec/session/source_spec.rb +12 -0
  69. data/lib/capybara/spec/session/text_spec.rb +15 -12
  70. data/lib/capybara/spec/session/uncheck_spec.rb +22 -17
  71. data/lib/capybara/spec/session/unselect_spec.rb +69 -58
  72. data/lib/capybara/spec/session/visit_spec.rb +74 -0
  73. data/lib/capybara/spec/session/within_frame_spec.rb +31 -0
  74. data/lib/capybara/spec/session/within_spec.rb +118 -131
  75. data/lib/capybara/spec/session/within_window_spec.rb +38 -0
  76. data/lib/capybara/spec/spec_helper.rb +84 -0
  77. data/lib/capybara/spec/test_app.rb +32 -6
  78. data/lib/capybara/spec/views/form.erb +12 -10
  79. data/lib/capybara/spec/views/host_links.erb +2 -2
  80. data/lib/capybara/spec/views/tables.erb +6 -66
  81. data/lib/capybara/spec/views/with_html.erb +9 -4
  82. data/lib/capybara/spec/views/with_js.erb +11 -7
  83. data/lib/capybara/version.rb +1 -1
  84. data/lib/capybara.rb +125 -6
  85. data/spec/basic_node_spec.rb +17 -5
  86. data/spec/capybara_spec.rb +9 -0
  87. data/spec/dsl_spec.rb +31 -17
  88. data/spec/rack_test_spec.rb +157 -0
  89. data/spec/result_spec.rb +51 -0
  90. data/spec/rspec/features_spec.rb +19 -2
  91. data/spec/rspec/matchers_spec.rb +170 -89
  92. data/spec/rspec_spec.rb +1 -3
  93. data/spec/selenium_spec.rb +53 -0
  94. data/spec/server_spec.rb +37 -25
  95. data/spec/spec_helper.rb +1 -30
  96. metadata +39 -31
  97. data/README.rdoc +0 -722
  98. data/lib/capybara/spec/driver.rb +0 -301
  99. data/lib/capybara/spec/session/current_host_spec.rb +0 -68
  100. data/lib/capybara/spec/session/has_content_spec.rb +0 -106
  101. data/lib/capybara/spec/session/javascript.rb +0 -306
  102. data/lib/capybara/spec/session.rb +0 -154
  103. data/lib/capybara/util/save_and_open_page.rb +0 -44
  104. data/lib/capybara/util/timeout.rb +0 -27
  105. data/spec/driver/rack_test_driver_spec.rb +0 -89
  106. data/spec/driver/selenium_driver_spec.rb +0 -37
  107. data/spec/save_and_open_page_spec.rb +0 -155
  108. data/spec/session/rack_test_session_spec.rb +0 -55
  109. data/spec/session/selenium_session_spec.rb +0 -26
  110. data/spec/string_spec.rb +0 -77
  111. data/spec/timeout_spec.rb +0 -28
@@ -24,7 +24,7 @@ module Capybara
24
24
  # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
25
25
  #
26
26
  def find(*args)
27
- wait_until { first(*args) or raise_find_error(*args) }.tap(&:allow_reload!)
27
+ synchronize { all(*args).find! }.tap(&:allow_reload!)
28
28
  end
29
29
 
30
30
  ##
@@ -35,7 +35,7 @@ module Capybara
35
35
  # @return [Capybara::Element] The found element
36
36
  #
37
37
  def find_field(locator)
38
- find(:xpath, XPath::HTML.field(locator))
38
+ find(:field, locator)
39
39
  end
40
40
  alias_method :field_labeled, :find_field
41
41
 
@@ -47,29 +47,29 @@ module Capybara
47
47
  # @return [Capybara::Element] The found element
48
48
  #
49
49
  def find_link(locator)
50
- find(:xpath, XPath::HTML.link(locator))
50
+ find(:link, locator)
51
51
  end
52
52
 
53
53
  ##
54
54
  #
55
- # Find a button on the page. The link can be found by its id, name or value.
55
+ # Find a button on the page. The button can be found by its id, name or value.
56
56
  #
57
57
  # @param [String] locator Which button to find
58
58
  # @return [Capybara::Element] The found element
59
59
  #
60
60
  def find_button(locator)
61
- find(:xpath, XPath::HTML.button(locator))
61
+ find(:button, locator)
62
62
  end
63
63
 
64
64
  ##
65
65
  #
66
66
  # Find a element on the page, given its id.
67
67
  #
68
- # @param [String] locator Which element to find
68
+ # @param [String] id Which element to find
69
69
  # @return [Capybara::Element] The found element
70
70
  #
71
71
  def find_by_id(id)
72
- find(:css, "##{id}")
72
+ find(:id, id)
73
73
  end
74
74
 
75
75
  ##
@@ -99,20 +99,23 @@ module Capybara
99
99
  # page.all('a', :text => 'Home')
100
100
  # page.all('#menu li', :visible => true)
101
101
  #
102
- # @param [:css, :xpath, String] kind_or_locator Either the kind of selector or the selector itself
103
- # @param [String] locator The selector
104
- # @param [Hash{Symbol => Object}] options Additional options
105
- # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
106
- # @option options [Boolean] visible Only find elements that are visible on the page
107
- # @return [Capybara::Element] The found elements
102
+ # @overload all([kind], locator, options)
103
+ # @param [:css, :xpath] kind The type of selector
104
+ # @param [String] locator The selector
105
+ # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
106
+ # @option options [Boolean] visible Only find elements that are visible on the page. Setting this to false
107
+ # (the default, unless Capybara.ignore_hidden_elements = true), finds
108
+ # invisible _and_ visible elements.
109
+ # @return [Array[Capybara::Element]] The found elements
108
110
  #
109
111
  def all(*args)
110
- selector = Capybara::Selector.normalize(*args)
111
- options = extract_normalized_options(args)
112
-
113
- selector.xpaths.
114
- map { |path| find_in_base(selector, path) }.flatten.
115
- select { |node| node.without_wait { matches_options(node, options) } }
112
+ query = Capybara::Query.new(*args)
113
+ elements = synchronize do
114
+ base.find(query.xpath).map do |node|
115
+ Capybara::Node::Element.new(session, node, self, query)
116
+ end
117
+ end
118
+ Capybara::Result.new(elements, query)
116
119
  end
117
120
 
118
121
  ##
@@ -120,79 +123,14 @@ module Capybara
120
123
  # Find the first element on the page matching the given selector
121
124
  # and options, or nil if no element matches.
122
125
  #
123
- # When only the first matching element is needed, this method can
124
- # be faster than all(*args).first.
125
- #
126
- # @param [:css, :xpath, String] kind_or_locator Either the kind of selector or the selector itself
127
- # @param [String] locator The selector
128
- # @param [Hash{Symbol => Object}] options Additional options; see {all}
129
- # @return Capybara::Element The found element
126
+ # @overload first([kind], locator, options)
127
+ # @param [:css, :xpath] kind The type of selector
128
+ # @param [String] locator The selector
129
+ # @param [Hash] options Additional options; see {all}
130
+ # @return [Capybara::Element] The found element or nil
130
131
  #
131
132
  def first(*args)
132
- selector = Capybara::Selector.normalize(*args)
133
- options = extract_normalized_options(args)
134
- found_elements = []
135
-
136
- selector.xpaths.each do |path|
137
- find_in_base(selector, path).each do |node|
138
- node.without_wait do
139
- if matches_options(node, options)
140
- found_elements << node
141
- return found_elements.last if not Capybara.prefer_visible_elements or node.visible?
142
- end
143
- end
144
- end
145
- end
146
- found_elements.first
147
- end
148
-
149
- protected
150
-
151
- def raise_find_error(*args)
152
- options = extract_normalized_options(args)
153
- normalized = Capybara::Selector.normalize(*args)
154
- message = options[:message] || "Unable to find #{normalized.name} #{normalized.locator.inspect}"
155
- message = normalized.failure_message.call(self, normalized) if normalized.failure_message
156
- raise Capybara::ElementNotFound, message
157
- end
158
-
159
- def find_in_base(selector, xpath)
160
- base.find(xpath).map do |node|
161
- Capybara::Node::Element.new(session, node, self, selector)
162
- end
163
- end
164
-
165
- def extract_normalized_options(args)
166
- options = if args.last.is_a?(Hash) then args.pop.dup else {} end
167
-
168
- if text = options[:text]
169
- options[:text] = Regexp.escape(text) unless text.kind_of?(Regexp)
170
- end
171
-
172
- if !options.has_key?(:visible)
173
- options[:visible] = Capybara.ignore_hidden_elements
174
- end
175
-
176
- if selected = options[:selected]
177
- options[:selected] = [selected].flatten
178
- end
179
-
180
- options
181
- end
182
-
183
- def matches_options(node, options)
184
- return false if options[:text] and not node.text.match(options[:text])
185
- return false if options[:visible] and not node.visible?
186
- return false if options[:with] and not node.value == options[:with]
187
- return false if options[:checked] and not node.checked?
188
- return false if options[:unchecked] and node.checked?
189
- return false if options[:selected] and not has_selected_options?(node, options[:selected])
190
- true
191
- end
192
-
193
- def has_selected_options?(node, expected)
194
- actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
195
- (expected - actual).empty?
133
+ all(*args).first
196
134
  end
197
135
  end
198
136
  end
@@ -25,33 +25,14 @@ module Capybara
25
25
  # has_selector? can also accept XPath expressions generated by the
26
26
  # XPath gem:
27
27
  #
28
- # xpath = XPath.generate { |x| x.descendant(:p) }
29
- # page.has_selector?(:xpath, xpath)
28
+ # page.has_selector?(:xpath, XPath.descendant(:p))
30
29
  #
31
30
  # @param (see Capybara::Node::Finders#all)
32
31
  # @option options [Integer] :count (nil) Number of times the expression should occur
33
32
  # @return [Boolean] If the expression exists
34
33
  #
35
34
  def has_selector?(*args)
36
- options = if args.last.is_a?(Hash) then args.last else {} end
37
- wait_until do
38
- results = all(*args)
39
-
40
- case
41
- when results.empty?
42
- false
43
- when options[:between]
44
- options[:between] === results.size
45
- when options[:count]
46
- options[:count].to_i == results.size
47
- when options[:maximum]
48
- options[:maximum].to_i >= results.size
49
- when options[:minimum]
50
- options[:minimum].to_i <= results.size
51
- else
52
- results.size > 0
53
- end or raise ExpectationNotMet
54
- end
35
+ assert_selector(*args)
55
36
  rescue Capybara::ExpectationNotMet
56
37
  return false
57
38
  end
@@ -65,29 +46,64 @@ module Capybara
65
46
  # @return [Boolean]
66
47
  #
67
48
  def has_no_selector?(*args)
68
- options = if args.last.is_a?(Hash) then args.last else {} end
69
- wait_until do
70
- results = all(*args)
71
-
72
- case
73
- when results.empty?
74
- true
75
- when options[:between]
76
- not(options[:between] === results.size)
77
- when options[:count]
78
- not(options[:count].to_i == results.size)
79
- when options[:maximum]
80
- not(options[:maximum].to_i >= results.size)
81
- when options[:minimum]
82
- not(options[:minimum].to_i <= results.size)
83
- else
84
- results.empty?
85
- end or raise ExpectationNotMet
86
- end
49
+ assert_no_selector(*args)
87
50
  rescue Capybara::ExpectationNotMet
88
51
  return false
89
52
  end
90
53
 
54
+ ##
55
+ #
56
+ # Asserts that a given selector is on the page or current node.
57
+ #
58
+ # page.assert_selector('p#foo')
59
+ # page.assert_selector(:xpath, './/p[@id="foo"]')
60
+ # page.assert_selector(:foo)
61
+ #
62
+ # By default it will check if the expression occurs at least once,
63
+ # but a different number can be specified.
64
+ #
65
+ # page.assert_selector('p#foo', :count => 4)
66
+ #
67
+ # This will check if the expression occurs exactly 4 times.
68
+ #
69
+ # It also accepts all options that {Capybara::Node::Finders#all} accepts,
70
+ # such as :text and :visible.
71
+ #
72
+ # page.assert_selector('li', :text => 'Horse', :visible => true)
73
+ #
74
+ # {assert_selector} can also accept XPath expressions generated by the
75
+ # XPath gem:
76
+ #
77
+ # page.assert_selector(:xpath, XPath.descendant(:p))
78
+ #
79
+ # @param (see Capybara::Node::Finders#all)
80
+ # @option options [Integer] :count (nil) Number of times the expression should occur
81
+ # @raise [Capybara::ExpectationNotMet] If the selector does not exist
82
+ #
83
+ def assert_selector(*args)
84
+ synchronize do
85
+ result = all(*args)
86
+ result.matches_count? or raise Capybara::ExpectationNotMet, result.failure_message
87
+ end
88
+ return true
89
+ end
90
+
91
+ ##
92
+ #
93
+ # Asserts that a given selector is not on the page or current node.
94
+ # Usage is identical to Capybara::Node::Matchers#assert_selector
95
+ #
96
+ # @param (see Capybara::Node::Finders#assert_selector)
97
+ # @raise [Capybara::ExpectationNotMet] If the selector exists
98
+ #
99
+ def assert_no_selector(*args)
100
+ synchronize do
101
+ result = all(*args)
102
+ result.matches_count? and raise Capybara::ExpectationNotMet, result.negative_failure_message
103
+ end
104
+ return true
105
+ end
106
+
91
107
  ##
92
108
  #
93
109
  # Checks if a given XPath expression is on the page or current node.
@@ -157,7 +173,7 @@ module Capybara
157
173
  # @return [Boolean] If the selector exists
158
174
  #
159
175
  def has_css?(path, options={})
160
- has_xpath?(XPath.css(path), options)
176
+ has_selector?(:css, path, options)
161
177
  end
162
178
 
163
179
  ##
@@ -169,7 +185,7 @@ module Capybara
169
185
  # @return [Boolean]
170
186
  #
171
187
  def has_no_css?(path, options={})
172
- has_no_xpath?(XPath.css(path), options)
188
+ has_no_selector?(:css, path, options)
173
189
  end
174
190
 
175
191
  ##
@@ -177,24 +193,46 @@ module Capybara
177
193
  # Checks if the page or current node has the given text content,
178
194
  # ignoring any HTML tags and normalizing whitespace.
179
195
  #
196
+ # This only matches displayable text and specifically excludes text
197
+ # contained within non-display nodes such as script or head tags.
198
+ #
180
199
  # @param [String] content The text to check for
181
200
  # @return [Boolean] Whether it exists
182
201
  #
183
- def has_content?(content)
184
- has_xpath?(XPath::HTML.content(content))
202
+ def has_text?(content)
203
+ synchronize do
204
+ unless Capybara::Helpers.normalize_whitespace(text).match(Capybara::Helpers.to_regexp(content))
205
+ raise ExpectationNotMet
206
+ end
207
+ end
208
+ return true
209
+ rescue Capybara::ExpectationNotMet
210
+ return false
185
211
  end
212
+ alias_method :has_content?, :has_text?
186
213
 
187
214
  ##
188
215
  #
189
216
  # Checks if the page or current node does not have the given text
190
217
  # content, ignoring any HTML tags and normalizing whitespace.
191
218
  #
219
+ # This only matches displayable text and specifically excludes text
220
+ # contained within non-display nodes such as script or head tags.
221
+ #
192
222
  # @param [String] content The text to check for
193
- # @return [Boolean] Whether it exists
223
+ # @return [Boolean] Whether it doesn't exist
194
224
  #
195
- def has_no_content?(content)
196
- has_no_xpath?(XPath::HTML.content(content))
225
+ def has_no_text?(content)
226
+ synchronize do
227
+ if Capybara::Helpers.normalize_whitespace(text).match(Capybara::Helpers.to_regexp(content))
228
+ raise ExpectationNotMet
229
+ end
230
+ end
231
+ return true
232
+ rescue Capybara::ExpectationNotMet
233
+ return false
197
234
  end
235
+ alias_method :has_no_content?, :has_no_text?
198
236
 
199
237
  ##
200
238
  #
@@ -207,7 +245,7 @@ module Capybara
207
245
  # @return [Boolean] Whether it exists
208
246
  #
209
247
  def has_link?(locator, options={})
210
- has_xpath?(XPath::HTML.link(locator, options))
248
+ has_selector?(:link, locator, options)
211
249
  end
212
250
 
213
251
  ##
@@ -219,7 +257,7 @@ module Capybara
219
257
  # @return [Boolean] Whether it doesn't exist
220
258
  #
221
259
  def has_no_link?(locator, options={})
222
- has_no_xpath?(XPath::HTML.link(locator, options))
260
+ has_no_selector?(:link, locator, options)
223
261
  end
224
262
 
225
263
  ##
@@ -231,7 +269,7 @@ module Capybara
231
269
  # @return [Boolean] Whether it exists
232
270
  #
233
271
  def has_button?(locator)
234
- has_xpath?(XPath::HTML.button(locator))
272
+ has_selector?(:button, locator)
235
273
  end
236
274
 
237
275
  ##
@@ -243,7 +281,7 @@ module Capybara
243
281
  # @return [Boolean] Whether it doesn't exist
244
282
  #
245
283
  def has_no_button?(locator)
246
- has_no_xpath?(XPath::HTML.button(locator))
284
+ has_no_selector?(:button, locator)
247
285
  end
248
286
 
249
287
  ##
@@ -257,13 +295,17 @@ module Capybara
257
295
  #
258
296
  # page.has_field?('Name', :with => 'Jonas')
259
297
  #
298
+ # It is also possible to filter by the field type attribute:
299
+ #
300
+ # page.has_field?('Email', :type => 'email')
301
+ #
260
302
  # @param [String] locator The label, name or id of a field to check for
261
303
  # @option options [String] :with The text content of the field
304
+ # @option options [String] :type The type attribute of the field
262
305
  # @return [Boolean] Whether it exists
263
306
  #
264
307
  def has_field?(locator, options={})
265
- options, with = split_options(options, :with)
266
- has_xpath?(XPath::HTML.field(locator, options), with)
308
+ has_selector?(:field, locator, options)
267
309
  end
268
310
 
269
311
  ##
@@ -273,11 +315,11 @@ module Capybara
273
315
  #
274
316
  # @param [String] locator The label, name or id of a field to check for
275
317
  # @option options [String] :with The text content of the field
318
+ # @option options [String] :type The type attribute of the field
276
319
  # @return [Boolean] Whether it doesn't exist
277
320
  #
278
321
  def has_no_field?(locator, options={})
279
- options, with = split_options(options, :with)
280
- has_no_xpath?(XPath::HTML.field(locator, options), with)
322
+ has_no_selector?(:field, locator, options)
281
323
  end
282
324
 
283
325
  ##
@@ -290,7 +332,7 @@ module Capybara
290
332
  # @return [Boolean] Whether it exists
291
333
  #
292
334
  def has_checked_field?(locator)
293
- has_xpath?(XPath::HTML.field(locator), :checked => true)
335
+ has_selector?(:field, locator, :checked => true)
294
336
  end
295
337
 
296
338
  ##
@@ -303,7 +345,7 @@ module Capybara
303
345
  # @return [Boolean] Whether it doesn't exists
304
346
  #
305
347
  def has_no_checked_field?(locator)
306
- has_no_xpath?(XPath::HTML.field(locator), :checked => true)
348
+ has_no_selector?(:field, locator, :checked => true)
307
349
  end
308
350
 
309
351
  ##
@@ -316,7 +358,7 @@ module Capybara
316
358
  # @return [Boolean] Whether it exists
317
359
  #
318
360
  def has_unchecked_field?(locator)
319
- has_xpath?(XPath::HTML.field(locator), :unchecked => true)
361
+ has_selector?(:field, locator, :unchecked => true)
320
362
  end
321
363
 
322
364
  ##
@@ -329,7 +371,7 @@ module Capybara
329
371
  # @return [Boolean] Whether it doesn't exists
330
372
  #
331
373
  def has_no_unchecked_field?(locator)
332
- has_no_xpath?(XPath::HTML.field(locator), :unchecked => true)
374
+ has_no_selector?(:field, locator, :unchecked => true)
333
375
  end
334
376
 
335
377
  ##
@@ -345,19 +387,23 @@ module Capybara
345
387
  #
346
388
  # page.has_select?('Language', :selected => ['English', 'German'])
347
389
  #
348
- # It's also possible to check if a given set of options exists for
390
+ # It's also possible to check if the exact set of options exists for
349
391
  # this select box:
350
392
  #
351
- # page.has_select?('Language', :options => ['English', 'German'])
393
+ # page.has_select?('Language', :options => ['English', 'German', 'Spanish'])
394
+ #
395
+ # You can also check for a partial set of options:
396
+ #
397
+ # page.has_select?('Language', :with_options => ['English', 'German'])
352
398
  #
353
399
  # @param [String] locator The label, name or id of a select box
354
400
  # @option options [Array] :options Options which should be contained in this select box
401
+ # @option options [Array] :with_options Partial set of options which should be contained in this select box
355
402
  # @option options [String, Array] :selected Options which should be selected
356
403
  # @return [Boolean] Whether it exists
357
404
  #
358
405
  def has_select?(locator, options={})
359
- options, selected = split_options(options, :selected)
360
- has_xpath?(XPath::HTML.select(locator, options), selected)
406
+ has_selector?(:select, locator, options)
361
407
  end
362
408
 
363
409
  ##
@@ -369,8 +415,7 @@ module Capybara
369
415
  # @return [Boolean] Whether it doesn't exist
370
416
  #
371
417
  def has_no_select?(locator, options={})
372
- options, selected = split_options(options, :selected)
373
- has_no_xpath?(XPath::HTML.select(locator, options), selected)
418
+ has_no_selector?(:select, locator, options)
374
419
  end
375
420
 
376
421
  ##
@@ -387,11 +432,10 @@ module Capybara
387
432
  # and the text needs to match exactly.
388
433
  #
389
434
  # @param [String] locator The id or caption of a table
390
- # @option options [Array[Array[String]]] :rows A set of rows the table should contain
391
435
  # @return [Boolean] Whether it exist
392
436
  #
393
437
  def has_table?(locator, options={})
394
- has_xpath?(XPath::HTML.table(locator, options))
438
+ has_selector?(:table, locator, options)
395
439
  end
396
440
 
397
441
  ##
@@ -403,15 +447,19 @@ module Capybara
403
447
  # @return [Boolean] Whether it doesn't exist
404
448
  #
405
449
  def has_no_table?(locator, options={})
406
- has_no_xpath?(XPath::HTML.table(locator, options))
450
+ has_no_selector?(:table, locator, options)
407
451
  end
408
452
 
409
- protected
410
-
411
- def split_options(options, key)
412
- options = options.dup
413
- [options, if options.has_key?(key) then {key => options.delete(key)} else {} end]
453
+ def ==(other)
454
+ if other.respond_to?(:native)
455
+ self.eql?(other) or native == other.native
456
+ else
457
+ self.eql?(other)
458
+ end
414
459
  end
460
+
461
+ private
462
+
415
463
  end
416
464
  end
417
465
  end
@@ -95,7 +95,7 @@ module Capybara
95
95
  # @return [Boolean] Whether the element is visible
96
96
  #
97
97
  def visible?
98
- native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none')]").size == 0
98
+ native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or name()='script' or name()='head']").size == 0
99
99
  end
100
100
 
101
101
  ##
@@ -118,22 +118,24 @@ module Capybara
118
118
  native[:selected]
119
119
  end
120
120
 
121
- def allow_reload!
122
- # no op
121
+ def synchronize
122
+ yield # simple nodes don't need to wait
123
123
  end
124
124
 
125
- def without_wait
126
- yield
125
+ def allow_reload!
126
+ # no op
127
127
  end
128
128
 
129
- protected
130
-
131
- def find_in_base(selector, xpath)
132
- native.xpath(xpath).map { |node| self.class.new(node) }
129
+ def unsynchronized
130
+ yield # simple nodes don't need to wait
133
131
  end
134
132
 
135
- def wait_until
136
- yield # simple nodes don't need to wait
133
+ def all(*args)
134
+ query = Capybara::Query.new(*args)
135
+ elements = native.xpath(query.xpath).map do |node|
136
+ self.class.new(node)
137
+ end
138
+ Capybara::Result.new(elements, query)
137
139
  end
138
140
  end
139
141
  end
@@ -0,0 +1,78 @@
1
+ module Capybara
2
+ class Query
3
+ attr_accessor :selector, :locator, :options, :xpath, :find, :negative
4
+
5
+ VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum]
6
+
7
+ def initialize(*args)
8
+ @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
9
+
10
+ unless options.has_key?(:visible)
11
+ @options[:visible] = Capybara.ignore_hidden_elements
12
+ end
13
+
14
+ if args[0].is_a?(Symbol)
15
+ @selector = Selector.all[args[0]]
16
+ @locator = args[1]
17
+ else
18
+ @selector = Selector.all.values.find { |s| s.match?(args[0]) }
19
+ @locator = args[0]
20
+ end
21
+ @selector ||= Selector.all[Capybara.default_selector]
22
+
23
+ @xpath = @selector.call(@locator).to_s
24
+
25
+ assert_valid_keys!
26
+ end
27
+
28
+ def name; selector.name; end
29
+ def label; selector.label or selector.name; end
30
+
31
+ def description
32
+ @description = "#{label} #{locator.inspect}"
33
+ @description << " with text #{options[:text].inspect}" if options[:text]
34
+ @description
35
+ end
36
+
37
+ def matches_filters?(node)
38
+ node.unsynchronized do
39
+ if options[:text]
40
+ regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text])
41
+ return false if not node.text.match(regexp)
42
+ end
43
+ return false if options[:visible] and not node.visible?
44
+ selector.custom_filters.each do |name, block|
45
+ return false if options.has_key?(name) and not block.call(node, options[name])
46
+ end
47
+ true
48
+ end
49
+ end
50
+
51
+ def matches_count?(count)
52
+ case
53
+ when options[:between]
54
+ options[:between] === count
55
+ when options[:count]
56
+ options[:count].to_i == count
57
+ when options[:maximum]
58
+ options[:maximum].to_i >= count
59
+ when options[:minimum]
60
+ options[:minimum].to_i <= count
61
+ else
62
+ count > 0
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def assert_valid_keys!
69
+ valid_keys = VALID_KEYS + @selector.custom_filters.keys
70
+ invalid_keys = @options.keys - valid_keys
71
+ unless invalid_keys.empty?
72
+ invalid_names = invalid_keys.map(&:inspect).join(", ")
73
+ valid_names = valid_keys.map(&:inspect).join(", ")
74
+ raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
75
+ end
76
+ end
77
+ end
78
+ end