capybara 1.1.4 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/History.txt +100 -0
  2. data/License.txt +22 -0
  3. data/README.md +829 -0
  4. data/lib/capybara.rb +124 -6
  5. data/lib/capybara/cucumber.rb +2 -5
  6. data/lib/capybara/driver/base.rb +5 -5
  7. data/lib/capybara/driver/node.rb +2 -2
  8. data/lib/capybara/dsl.rb +3 -121
  9. data/lib/capybara/node/actions.rb +12 -28
  10. data/lib/capybara/node/base.rb +5 -13
  11. data/lib/capybara/node/element.rb +21 -21
  12. data/lib/capybara/node/finders.rb +27 -89
  13. data/lib/capybara/node/matchers.rb +107 -69
  14. data/lib/capybara/node/simple.rb +11 -13
  15. data/lib/capybara/query.rb +78 -0
  16. data/lib/capybara/rack_test/browser.rb +16 -27
  17. data/lib/capybara/rack_test/driver.rb +11 -1
  18. data/lib/capybara/rack_test/node.rb +17 -1
  19. data/lib/capybara/result.rb +84 -0
  20. data/lib/capybara/rspec/matchers.rb +28 -63
  21. data/lib/capybara/selector.rb +97 -33
  22. data/lib/capybara/selenium/driver.rb +14 -61
  23. data/lib/capybara/selenium/node.rb +6 -15
  24. data/lib/capybara/server.rb +32 -27
  25. data/lib/capybara/session.rb +54 -30
  26. data/lib/capybara/spec/public/jquery-ui.js +791 -0
  27. data/lib/capybara/spec/public/jquery.js +9046 -0
  28. data/lib/capybara/spec/public/test.js +4 -1
  29. data/lib/capybara/spec/session.rb +56 -27
  30. data/lib/capybara/spec/session/all_spec.rb +8 -4
  31. data/lib/capybara/spec/session/attach_file_spec.rb +12 -9
  32. data/lib/capybara/spec/session/check_spec.rb +6 -3
  33. data/lib/capybara/spec/session/choose_spec.rb +4 -1
  34. data/lib/capybara/spec/session/click_button_spec.rb +5 -14
  35. data/lib/capybara/spec/session/click_link_or_button_spec.rb +2 -1
  36. data/lib/capybara/spec/session/click_link_spec.rb +3 -17
  37. data/lib/capybara/spec/session/current_url_spec.rb +77 -9
  38. data/lib/capybara/spec/session/fill_in_spec.rb +8 -18
  39. data/lib/capybara/spec/session/find_spec.rb +19 -46
  40. data/lib/capybara/spec/session/first_spec.rb +2 -34
  41. data/lib/capybara/spec/session/has_css_spec.rb +1 -1
  42. data/lib/capybara/spec/session/has_field_spec.rb +28 -0
  43. data/lib/capybara/spec/session/has_select_spec.rb +84 -31
  44. data/lib/capybara/spec/session/has_table_spec.rb +7 -69
  45. data/lib/capybara/spec/session/has_text_spec.rb +168 -0
  46. data/lib/capybara/spec/session/javascript.rb +65 -81
  47. data/lib/capybara/spec/session/node_spec.rb +115 -0
  48. data/lib/capybara/spec/session/screenshot.rb +29 -0
  49. data/lib/capybara/spec/session/select_spec.rb +12 -12
  50. data/lib/capybara/spec/session/text_spec.rb +9 -4
  51. data/lib/capybara/spec/session/unselect_spec.rb +12 -6
  52. data/lib/capybara/spec/session/visit_spec.rb +76 -0
  53. data/lib/capybara/spec/session/within_frame_spec.rb +33 -0
  54. data/lib/capybara/spec/session/within_spec.rb +47 -58
  55. data/lib/capybara/spec/session/within_window_spec.rb +40 -0
  56. data/lib/capybara/spec/test_app.rb +27 -3
  57. data/lib/capybara/spec/views/form.erb +11 -10
  58. data/lib/capybara/spec/views/host_links.erb +2 -2
  59. data/lib/capybara/spec/views/tables.erb +6 -66
  60. data/lib/capybara/spec/views/with_html.erb +3 -3
  61. data/lib/capybara/spec/views/with_js.erb +11 -8
  62. data/lib/capybara/util/save_and_open_page.rb +4 -3
  63. data/lib/capybara/version.rb +1 -1
  64. data/spec/basic_node_spec.rb +15 -3
  65. data/spec/dsl_spec.rb +12 -10
  66. data/spec/rack_test_spec.rb +152 -0
  67. data/spec/rspec/features_spec.rb +0 -2
  68. data/spec/rspec/matchers_spec.rb +164 -89
  69. data/spec/rspec_spec.rb +0 -2
  70. data/spec/selenium_spec.rb +67 -0
  71. data/spec/server_spec.rb +35 -23
  72. data/spec/spec_helper.rb +18 -2
  73. metadata +30 -30
  74. data/README.rdoc +0 -722
  75. data/lib/capybara/spec/driver.rb +0 -301
  76. data/lib/capybara/spec/session/current_host_spec.rb +0 -68
  77. data/lib/capybara/spec/session/has_content_spec.rb +0 -106
  78. data/lib/capybara/util/timeout.rb +0 -27
  79. data/spec/driver/rack_test_driver_spec.rb +0 -89
  80. data/spec/driver/selenium_driver_spec.rb +0 -37
  81. data/spec/session/rack_test_session_spec.rb +0 -55
  82. data/spec/session/selenium_session_spec.rb +0 -26
  83. data/spec/string_spec.rb +0 -77
  84. data/spec/timeout_spec.rb +0 -28
@@ -33,29 +33,19 @@ module Capybara
33
33
  @base = base
34
34
  end
35
35
 
36
+ # overridden in subclasses, e.g. Capybara::Node::Element
36
37
  def reload
37
38
  self
38
39
  end
39
40
 
40
- def without_wait
41
- orig = @wait_disabled
42
- @wait_disabled = true
43
- yield
44
- ensure
45
- @wait_disabled = orig
46
- end
47
-
48
- protected
49
-
50
- def wait_until(seconds=Capybara.default_wait_time)
41
+ def synchronize(seconds=Capybara.default_wait_time)
51
42
  start_time = Time.now
52
43
 
53
44
  begin
54
45
  yield
55
46
  rescue => e
56
- raise e if @wait_disabled
57
47
  raise e unless driver.wait?
58
- raise e unless (driver.respond_to?(:invalid_element_errors) and driver.invalid_element_errors.include?(e.class)) or e.is_a?(Capybara::ElementNotFound)
48
+ raise e unless driver.invalid_element_errors.include?(e.class) or e.is_a?(Capybara::ElementNotFound)
59
49
  raise e if (Time.now - start_time) >= seconds
60
50
  sleep(0.05)
61
51
  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
@@ -64,6 +54,8 @@ module Capybara
64
54
  end
65
55
  end
66
56
 
57
+ protected
58
+
67
59
  def driver
68
60
  session.driver
69
61
  end
@@ -22,10 +22,10 @@ module Capybara
22
22
  #
23
23
  class Element < Base
24
24
 
25
- def initialize(session, base, parent, selector)
25
+ def initialize(session, base, parent, query)
26
26
  super(session, base)
27
27
  @parent = parent
28
- @selector = selector
28
+ @query = query
29
29
  end
30
30
 
31
31
  def allow_reload!
@@ -37,7 +37,7 @@ module Capybara
37
37
  # @return [Object] The native element from the driver, this allows access to driver specific methods
38
38
  #
39
39
  def native
40
- wait_until { base.native }
40
+ synchronize { base.native }
41
41
  end
42
42
 
43
43
  ##
@@ -45,7 +45,7 @@ module Capybara
45
45
  # @return [String] The text of the element
46
46
  #
47
47
  def text
48
- wait_until { base.text }
48
+ synchronize { base.text }
49
49
  end
50
50
 
51
51
  ##
@@ -58,7 +58,7 @@ module Capybara
58
58
  # @return [String] The value of the attribute
59
59
  #
60
60
  def [](attribute)
61
- wait_until { base[attribute] }
61
+ synchronize { base[attribute] }
62
62
  end
63
63
 
64
64
  ##
@@ -66,7 +66,7 @@ module Capybara
66
66
  # @return [String] The value of the form element
67
67
  #
68
68
  def value
69
- wait_until { base.value }
69
+ synchronize { base.value }
70
70
  end
71
71
 
72
72
  ##
@@ -76,7 +76,7 @@ module Capybara
76
76
  # @param [String] value The new value
77
77
  #
78
78
  def set(value)
79
- wait_until { base.set(value) }
79
+ synchronize { base.set(value) }
80
80
  end
81
81
 
82
82
  ##
@@ -84,7 +84,7 @@ module Capybara
84
84
  # Select this node if is an option element inside a select tag
85
85
  #
86
86
  def select_option
87
- wait_until { base.select_option }
87
+ synchronize { base.select_option }
88
88
  end
89
89
 
90
90
  ##
@@ -92,7 +92,7 @@ module Capybara
92
92
  # Unselect this node if is an option element inside a multiple select tag
93
93
  #
94
94
  def unselect_option
95
- wait_until { base.unselect_option }
95
+ synchronize { base.unselect_option }
96
96
  end
97
97
 
98
98
  ##
@@ -100,7 +100,7 @@ module Capybara
100
100
  # Click the Element
101
101
  #
102
102
  def click
103
- wait_until { base.click }
103
+ synchronize { base.click }
104
104
  end
105
105
 
106
106
  ##
@@ -108,7 +108,7 @@ module Capybara
108
108
  # @return [String] The tag name of the element
109
109
  #
110
110
  def tag_name
111
- wait_until { base.tag_name }
111
+ synchronize { base.tag_name }
112
112
  end
113
113
 
114
114
  ##
@@ -119,7 +119,7 @@ module Capybara
119
119
  # @return [Boolean] Whether the element is visible
120
120
  #
121
121
  def visible?
122
- wait_until { base.visible? }
122
+ synchronize { base.visible? }
123
123
  end
124
124
 
125
125
  ##
@@ -129,7 +129,7 @@ module Capybara
129
129
  # @return [Boolean] Whether the element is checked
130
130
  #
131
131
  def checked?
132
- wait_until { base.checked? }
132
+ synchronize { base.checked? }
133
133
  end
134
134
 
135
135
  ##
@@ -139,7 +139,7 @@ module Capybara
139
139
  # @return [Boolean] Whether the element is selected
140
140
  #
141
141
  def selected?
142
- wait_until { base.selected? }
142
+ synchronize { base.selected? }
143
143
  end
144
144
 
145
145
  ##
@@ -149,7 +149,7 @@ module Capybara
149
149
  # @return [String] An XPath expression
150
150
  #
151
151
  def path
152
- wait_until { base.path }
152
+ synchronize { base.path }
153
153
  end
154
154
 
155
155
  ##
@@ -160,7 +160,7 @@ module Capybara
160
160
  # @param [String] event The name of the event to trigger
161
161
  #
162
162
  def trigger(event)
163
- wait_until { base.trigger(event) }
163
+ synchronize { base.trigger(event) }
164
164
  end
165
165
 
166
166
  ##
@@ -174,24 +174,24 @@ module Capybara
174
174
  # @param [Capybara::Element] node The element to drag to
175
175
  #
176
176
  def drag_to(node)
177
- wait_until { base.drag_to(node.base) }
177
+ synchronize { base.drag_to(node.base) }
178
178
  end
179
179
 
180
180
  def find(*args)
181
- wait_until { super }
181
+ synchronize { super }
182
182
  end
183
183
 
184
184
  def first(*args)
185
- wait_until { super }
185
+ synchronize { super }
186
186
  end
187
187
 
188
188
  def all(*args)
189
- wait_until { super }
189
+ synchronize { super }
190
190
  end
191
191
 
192
192
  def reload
193
193
  if @allow_reload
194
- reloaded = parent.reload.first(@selector.name, @selector.locator, @selector.options)
194
+ reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
195
195
  @base = reloaded.base if reloaded
196
196
  end
197
197
  self
@@ -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,7 +47,7 @@ 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
  ##
@@ -58,18 +58,18 @@ module Capybara
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
@@ -33,29 +33,19 @@ module Capybara
33
33
  # @return [Boolean] If the expression exists
34
34
  #
35
35
  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
36
+ assert_selector(*args)
55
37
  rescue Capybara::ExpectationNotMet
56
38
  return false
57
39
  end
58
40
 
41
+ def assert_selector(*args)
42
+ synchronize do
43
+ result = all(*args)
44
+ result.matches_count? or raise Capybara::ExpectationNotMet, result.failure_message
45
+ end
46
+ return true
47
+ end
48
+
59
49
  ##
60
50
  #
61
51
  # Checks if a given selector is not on the page or current node.
@@ -65,29 +55,19 @@ module Capybara
65
55
  # @return [Boolean]
66
56
  #
67
57
  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
58
+ assert_no_selector(*args)
87
59
  rescue Capybara::ExpectationNotMet
88
60
  return false
89
61
  end
90
62
 
63
+ def assert_no_selector(*args)
64
+ synchronize do
65
+ result = all(*args)
66
+ result.matches_count? and raise Capybara::ExpectationNotMet, result.negative_failure_message
67
+ end
68
+ return true
69
+ end
70
+
91
71
  ##
92
72
  #
93
73
  # Checks if a given XPath expression is on the page or current node.
@@ -157,7 +137,7 @@ module Capybara
157
137
  # @return [Boolean] If the selector exists
158
138
  #
159
139
  def has_css?(path, options={})
160
- has_xpath?(XPath.css(path), options)
140
+ has_selector?(:css, path, options)
161
141
  end
162
142
 
163
143
  ##
@@ -169,7 +149,7 @@ module Capybara
169
149
  # @return [Boolean]
170
150
  #
171
151
  def has_no_css?(path, options={})
172
- has_no_xpath?(XPath.css(path), options)
152
+ has_no_selector?(:css, path, options)
173
153
  end
174
154
 
175
155
  ##
@@ -177,24 +157,48 @@ module Capybara
177
157
  # Checks if the page or current node has the given text content,
178
158
  # ignoring any HTML tags and normalizing whitespace.
179
159
  #
160
+ # Unlike has_content this only matches displayable text and specifically
161
+ # excludes text contained within non-display nodes such as script or head tags.
162
+ #
180
163
  # @param [String] content The text to check for
181
164
  # @return [Boolean] Whether it exists
182
165
  #
183
- def has_content?(content)
184
- has_xpath?(XPath::HTML.content(content))
166
+ def has_text?(content)
167
+ normalized_content = normalize_whitespace(content)
168
+
169
+ synchronize do
170
+ normalize_whitespace(text).match(escape_regexp(normalized_content)) or
171
+ raise ExpectationNotMet
172
+ end
173
+ return true
174
+ rescue Capybara::ExpectationNotMet
175
+ return false
185
176
  end
177
+ alias_method :has_content?, :has_text?
186
178
 
187
179
  ##
188
180
  #
189
181
  # Checks if the page or current node does not have the given text
190
182
  # content, ignoring any HTML tags and normalizing whitespace.
191
183
  #
184
+ # Unlike has_content this only matches displayable text and specifically
185
+ # excludes text contained within non-display nodes such as script or head tags.
186
+ #
192
187
  # @param [String] content The text to check for
193
188
  # @return [Boolean] Whether it exists
194
189
  #
195
- def has_no_content?(content)
196
- has_no_xpath?(XPath::HTML.content(content))
190
+ def has_no_text?(content)
191
+ normalized_content = normalize_whitespace(content)
192
+
193
+ synchronize do
194
+ !normalize_whitespace(text).match(escape_regexp(normalized_content)) or
195
+ raise ExpectationNotMet
196
+ end
197
+ return true
198
+ rescue Capybara::ExpectationNotMet
199
+ return false
197
200
  end
201
+ alias_method :has_no_content?, :has_no_text?
198
202
 
199
203
  ##
200
204
  #
@@ -207,7 +211,7 @@ module Capybara
207
211
  # @return [Boolean] Whether it exists
208
212
  #
209
213
  def has_link?(locator, options={})
210
- has_xpath?(XPath::HTML.link(locator, options))
214
+ has_selector?(:link, locator, options)
211
215
  end
212
216
 
213
217
  ##
@@ -219,7 +223,7 @@ module Capybara
219
223
  # @return [Boolean] Whether it doesn't exist
220
224
  #
221
225
  def has_no_link?(locator, options={})
222
- has_no_xpath?(XPath::HTML.link(locator, options))
226
+ has_no_selector?(:link, locator, options)
223
227
  end
224
228
 
225
229
  ##
@@ -231,7 +235,7 @@ module Capybara
231
235
  # @return [Boolean] Whether it exists
232
236
  #
233
237
  def has_button?(locator)
234
- has_xpath?(XPath::HTML.button(locator))
238
+ has_selector?(:button, locator)
235
239
  end
236
240
 
237
241
  ##
@@ -243,7 +247,7 @@ module Capybara
243
247
  # @return [Boolean] Whether it doesn't exist
244
248
  #
245
249
  def has_no_button?(locator)
246
- has_no_xpath?(XPath::HTML.button(locator))
250
+ has_no_selector?(:button, locator)
247
251
  end
248
252
 
249
253
  ##
@@ -257,13 +261,17 @@ module Capybara
257
261
  #
258
262
  # page.has_field?('Name', :with => 'Jonas')
259
263
  #
264
+ # It is also possible to filter by the field type attribute:
265
+ #
266
+ # page.has_field?('Email', :type => 'email')
267
+ #
260
268
  # @param [String] locator The label, name or id of a field to check for
261
269
  # @option options [String] :with The text content of the field
270
+ # @option options [String] :type The type attribute of the field
262
271
  # @return [Boolean] Whether it exists
263
272
  #
264
273
  def has_field?(locator, options={})
265
- options, with = split_options(options, :with)
266
- has_xpath?(XPath::HTML.field(locator, options), with)
274
+ has_selector?(:field, locator, options)
267
275
  end
268
276
 
269
277
  ##
@@ -273,11 +281,11 @@ module Capybara
273
281
  #
274
282
  # @param [String] locator The label, name or id of a field to check for
275
283
  # @option options [String] :with The text content of the field
284
+ # @option options [String] :type The type attribute of the field
276
285
  # @return [Boolean] Whether it doesn't exist
277
286
  #
278
287
  def has_no_field?(locator, options={})
279
- options, with = split_options(options, :with)
280
- has_no_xpath?(XPath::HTML.field(locator, options), with)
288
+ has_no_selector?(:field, locator, options)
281
289
  end
282
290
 
283
291
  ##
@@ -290,7 +298,7 @@ module Capybara
290
298
  # @return [Boolean] Whether it exists
291
299
  #
292
300
  def has_checked_field?(locator)
293
- has_xpath?(XPath::HTML.field(locator), :checked => true)
301
+ has_selector?(:field, locator, :checked => true)
294
302
  end
295
303
 
296
304
  ##
@@ -303,7 +311,7 @@ module Capybara
303
311
  # @return [Boolean] Whether it doesn't exists
304
312
  #
305
313
  def has_no_checked_field?(locator)
306
- has_no_xpath?(XPath::HTML.field(locator), :checked => true)
314
+ has_no_selector?(:field, locator, :checked => true)
307
315
  end
308
316
 
309
317
  ##
@@ -316,7 +324,7 @@ module Capybara
316
324
  # @return [Boolean] Whether it exists
317
325
  #
318
326
  def has_unchecked_field?(locator)
319
- has_xpath?(XPath::HTML.field(locator), :unchecked => true)
327
+ has_selector?(:field, locator, :unchecked => true)
320
328
  end
321
329
 
322
330
  ##
@@ -329,7 +337,7 @@ module Capybara
329
337
  # @return [Boolean] Whether it doesn't exists
330
338
  #
331
339
  def has_no_unchecked_field?(locator)
332
- has_no_xpath?(XPath::HTML.field(locator), :unchecked => true)
340
+ has_no_selector?(:field, locator, :unchecked => true)
333
341
  end
334
342
 
335
343
  ##
@@ -345,19 +353,23 @@ module Capybara
345
353
  #
346
354
  # page.has_select?('Language', :selected => ['English', 'German'])
347
355
  #
348
- # It's also possible to check if a given set of options exists for
356
+ # It's also possible to check if the exact set of options exists for
349
357
  # this select box:
350
358
  #
351
- # page.has_select?('Language', :options => ['English', 'German'])
359
+ # page.has_select?('Language', :options => ['English', 'German', 'Spanish'])
360
+ #
361
+ # You can also check for a partial set of options:
362
+ #
363
+ # page.has_select?('Language', :with_options => ['English', 'German'])
352
364
  #
353
365
  # @param [String] locator The label, name or id of a select box
354
366
  # @option options [Array] :options Options which should be contained in this select box
367
+ # @option options [Array] :with_options Partial set of options which should be contained in this select box
355
368
  # @option options [String, Array] :selected Options which should be selected
356
369
  # @return [Boolean] Whether it exists
357
370
  #
358
371
  def has_select?(locator, options={})
359
- options, selected = split_options(options, :selected)
360
- has_xpath?(XPath::HTML.select(locator, options), selected)
372
+ has_selector?(:select, locator, options)
361
373
  end
362
374
 
363
375
  ##
@@ -369,8 +381,7 @@ module Capybara
369
381
  # @return [Boolean] Whether it doesn't exist
370
382
  #
371
383
  def has_no_select?(locator, options={})
372
- options, selected = split_options(options, :selected)
373
- has_no_xpath?(XPath::HTML.select(locator, options), selected)
384
+ has_no_selector?(:select, locator, options)
374
385
  end
375
386
 
376
387
  ##
@@ -387,11 +398,10 @@ module Capybara
387
398
  # and the text needs to match exactly.
388
399
  #
389
400
  # @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
401
  # @return [Boolean] Whether it exist
392
402
  #
393
403
  def has_table?(locator, options={})
394
- has_xpath?(XPath::HTML.table(locator, options))
404
+ has_selector?(:table, locator, options)
395
405
  end
396
406
 
397
407
  ##
@@ -403,14 +413,42 @@ module Capybara
403
413
  # @return [Boolean] Whether it doesn't exist
404
414
  #
405
415
  def has_no_table?(locator, options={})
406
- has_no_xpath?(XPath::HTML.table(locator, options))
416
+ has_no_selector?(:table, locator, options)
407
417
  end
408
418
 
409
- protected
419
+ def ==(other)
420
+ if other.respond_to?(:native)
421
+ self.eql?(other) or native == other.native
422
+ else
423
+ self.eql?(other)
424
+ end
425
+ end
426
+
427
+ private
428
+
429
+ ##
430
+ #
431
+ # Normalizes whitespace space by stripping leading and trailing
432
+ # whitespace and replacing sequences of whitespace characters
433
+ # with a single space.
434
+ #
435
+ # @param [String] text Text to normalize
436
+ # @return [String] Normalized text
437
+ #
438
+ def normalize_whitespace(text)
439
+ text.is_a?(Regexp) ? text : text.gsub(/\s+/, ' ').strip
440
+ end
410
441
 
411
- def split_options(options, key)
412
- options = options.dup
413
- [options, if options.has_key?(key) then {key => options.delete(key)} else {} end]
442
+ ##
443
+ #
444
+ # Escapes any characters that would have special meaning in a regexp
445
+ # if text is not a regexp
446
+ #
447
+ # @param [String] text Text to escape
448
+ # @return [String] Escaped text
449
+ #
450
+ def escape_regexp(text)
451
+ text.is_a?(Regexp) ? text : Regexp.escape(text)
414
452
  end
415
453
  end
416
454
  end