capybara 2.0.3 → 2.1.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 (82) hide show
  1. data/History.md +82 -0
  2. data/README.md +57 -52
  3. data/lib/capybara/cucumber.rb +4 -1
  4. data/lib/capybara/driver/base.rb +13 -9
  5. data/lib/capybara/driver/node.rb +18 -6
  6. data/lib/capybara/helpers.rb +111 -22
  7. data/lib/capybara/node/actions.rb +30 -25
  8. data/lib/capybara/node/base.rb +25 -32
  9. data/lib/capybara/node/document.rb +6 -2
  10. data/lib/capybara/node/element.rb +44 -7
  11. data/lib/capybara/node/finders.rb +49 -22
  12. data/lib/capybara/node/matchers.rb +38 -21
  13. data/lib/capybara/node/simple.rb +31 -9
  14. data/lib/capybara/query.rb +72 -26
  15. data/lib/capybara/rack_test/browser.rb +11 -3
  16. data/lib/capybara/rack_test/css_handlers.rb +8 -0
  17. data/lib/capybara/rack_test/driver.rb +11 -3
  18. data/lib/capybara/rack_test/form.rb +8 -1
  19. data/lib/capybara/rack_test/node.rb +91 -35
  20. data/lib/capybara/rails.rb +0 -1
  21. data/lib/capybara/result.rb +21 -37
  22. data/lib/capybara/rspec/matchers.rb +60 -31
  23. data/lib/capybara/selector.rb +46 -13
  24. data/lib/capybara/selenium/driver.rb +42 -7
  25. data/lib/capybara/selenium/node.rb +23 -5
  26. data/lib/capybara/server.rb +1 -1
  27. data/lib/capybara/session.rb +47 -12
  28. data/lib/capybara/spec/public/test.js +12 -0
  29. data/lib/capybara/spec/session/all_spec.rb +9 -8
  30. data/lib/capybara/spec/session/attach_file_spec.rb +14 -0
  31. data/lib/capybara/spec/session/check_spec.rb +14 -0
  32. data/lib/capybara/spec/session/choose_spec.rb +14 -0
  33. data/lib/capybara/spec/session/click_button_spec.rb +77 -1
  34. data/lib/capybara/spec/session/click_link_or_button_spec.rb +65 -0
  35. data/lib/capybara/spec/session/click_link_spec.rb +24 -0
  36. data/lib/capybara/spec/session/current_scope_spec.rb +29 -0
  37. data/lib/capybara/spec/session/fill_in_spec.rb +26 -0
  38. data/lib/capybara/spec/session/find_button_spec.rb +12 -0
  39. data/lib/capybara/spec/session/find_by_id_spec.rb +12 -1
  40. data/lib/capybara/spec/session/find_field_spec.rb +30 -0
  41. data/lib/capybara/spec/session/find_link_spec.rb +12 -0
  42. data/lib/capybara/spec/session/find_spec.rb +264 -16
  43. data/lib/capybara/spec/session/first_spec.rb +25 -8
  44. data/lib/capybara/spec/session/has_css_spec.rb +2 -2
  45. data/lib/capybara/spec/session/has_field_spec.rb +10 -2
  46. data/lib/capybara/spec/session/has_selector_spec.rb +9 -0
  47. data/lib/capybara/spec/session/has_text_spec.rb +96 -0
  48. data/lib/capybara/spec/session/has_title_spec.rb +47 -0
  49. data/lib/capybara/spec/session/node_spec.rb +48 -0
  50. data/lib/capybara/spec/session/reset_session_spec.rb +10 -2
  51. data/lib/capybara/spec/session/save_page_spec.rb +30 -0
  52. data/lib/capybara/spec/session/select_spec.rb +65 -0
  53. data/lib/capybara/spec/session/text_spec.rb +31 -0
  54. data/lib/capybara/spec/session/title_spec.rb +16 -0
  55. data/lib/capybara/spec/session/uncheck_spec.rb +14 -0
  56. data/lib/capybara/spec/session/unselect_spec.rb +42 -0
  57. data/lib/capybara/spec/session/visit_spec.rb +3 -3
  58. data/lib/capybara/spec/session/within_frame_spec.rb +14 -0
  59. data/lib/capybara/spec/session/within_spec.rb +3 -3
  60. data/lib/capybara/spec/spec_helper.rb +7 -2
  61. data/lib/capybara/spec/views/form.erb +39 -2
  62. data/lib/capybara/spec/views/frame_child.erb +9 -0
  63. data/lib/capybara/spec/views/frame_parent.erb +8 -0
  64. data/lib/capybara/spec/views/with_base_tag.erb +10 -0
  65. data/lib/capybara/spec/views/with_count.erb +7 -0
  66. data/lib/capybara/spec/views/with_hover.erb +17 -0
  67. data/lib/capybara/spec/views/with_html.erb +23 -2
  68. data/lib/capybara/spec/views/with_js.erb +5 -0
  69. data/lib/capybara/spec/views/with_scope.erb +6 -1
  70. data/lib/capybara/spec/views/with_title.erb +1 -0
  71. data/lib/capybara/spec/views/within_frames.erb +1 -0
  72. data/lib/capybara/version.rb +1 -1
  73. data/lib/capybara.rb +43 -62
  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 +15 -8
  79. data/spec/spec_helper.rb +2 -1
  80. data.tar.gz.sig +0 -0
  81. metadata +82 -22
  82. metadata.gz.sig +0 -0
@@ -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
  ##
@@ -42,12 +44,13 @@ module Capybara
42
44
  #
43
45
  # page.fill_in 'Name', :with => 'Bob'
44
46
  #
45
- # @param [String] locator Which field to fill in
46
- # @param [Hash{:with => String}] The value to fill in
47
+ # @param [String] locator Which field to fill in
48
+ # @param [Hash{:with => String}] options The value to fill in
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
  ##
@@ -97,14 +100,15 @@ module Capybara
97
100
  #
98
101
  # page.select 'March', :from => 'Month'
99
102
  #
100
- # @param [String] value Which option to select
101
- # @param [Hash{:from => String}] The id, name or label of the select box
103
+ # @param [String] value Which option to select
104
+ # @param [Hash{:from => String}] options The id, name or label of the select box
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
 
@@ -116,14 +120,15 @@ module Capybara
116
120
  #
117
121
  # page.unselect 'March', :from => 'Month'
118
122
  #
119
- # @param [String] value Which option to unselect
120
- # @param [Hash{:from => String}] The id, name or label of the select box
123
+ # @param [String] value Which option to unselect
124
+ # @param [Hash{:from => String}] options The id, name or label of the select box
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,25 @@ 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] type Whether to return only visible or all text
53
+ # @return [String] The text of the element
54
+ #
55
+ def text(type=nil)
56
+ type ||= :all unless Capybara.ignore_hidden_elements or Capybara.visible_text_only
57
+ synchronize do
58
+ if type == :all
59
+ base.all_text
60
+ else
61
+ base.visible_text
62
+ end
63
+ end
49
64
  end
50
65
 
51
66
  ##
@@ -103,6 +118,14 @@ module Capybara
103
118
  synchronize { base.click }
104
119
  end
105
120
 
121
+ ##
122
+ #
123
+ # Hover on the Element
124
+ #
125
+ def hover
126
+ synchronize { base.hover }
127
+ end
128
+
106
129
  ##
107
130
  #
108
131
  # @return [String] The tag name of the element
@@ -142,6 +165,16 @@ module Capybara
142
165
  synchronize { base.selected? }
143
166
  end
144
167
 
168
+ ##
169
+ #
170
+ # Whether or not the element is disabled.
171
+ #
172
+ # @return [Boolean] Whether the element is disabled
173
+ #
174
+ def disabled?
175
+ synchronize { base.disabled? }
176
+ end
177
+
145
178
  ##
146
179
  #
147
180
  # An XPath expression describing where on the page the element can be found
@@ -179,15 +212,19 @@ module Capybara
179
212
 
180
213
  def reload
181
214
  if @allow_reload
182
- reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
183
- @base = reloaded.base if reloaded
215
+ begin
216
+ reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
217
+ @base = reloaded.base if reloaded
218
+ rescue => e
219
+ raise e unless catch_error?(e)
220
+ end
184
221
  end
185
222
  self
186
223
  end
187
224
 
188
225
  def inspect
189
226
  %(#<Capybara::Element tag="#{tag_name}" path="#{path}">)
190
- rescue NotSupportedByDriverError
227
+ rescue NotSupportedByDriverError, 'Capybara::Node::Element#inspect'
191
228
  %(#<Capybara::Element tag="#{tag_name}">)
192
229
  end
193
230
  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
  ##
@@ -125,12 +137,27 @@ module Capybara
125
137
  # @overload first([kind], locator, options)
126
138
  # @param [:css, :xpath] kind The type of selector
127
139
  # @param [String] locator The selector
128
- # @param [Hash] options Additional options; see {all}
140
+ # @param [Hash] options Additional options; see {#all}
129
141
  # @return [Capybara::Element] The found element or nil
130
142
  #
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
@@ -13,7 +13,7 @@ module Capybara
13
13
  # By default it will check if the expression occurs at least once,
14
14
  # but a different number can be specified.
15
15
  #
16
- # page.has_selector?('p#foo', :count => 4)
16
+ # page.has_selector?('p.foo', :count => 4)
17
17
  #
18
18
  # This will check if the expression occurs exactly 4 times.
19
19
  #
@@ -28,8 +28,12 @@ module Capybara
28
28
  # page.has_selector?(:xpath, XPath.descendant(:p))
29
29
  #
30
30
  # @param (see Capybara::Node::Finders#all)
31
- # @option options [Integer] :count (nil) Number of times the expression should occur
32
- # @return [Boolean] If the expression exists
31
+ # @param args
32
+ # @option args [Integer] :count (nil) Number of times the text should occur
33
+ # @option args [Integer] :minimum (nil) Minimum number of times the text should occur
34
+ # @option args [Integer] :maximum (nil) Maximum number of times the text should occur
35
+ # @option args [Range] :between (nil) Range of times that should contain number of times text occurs
36
+ # @return [Boolean] If the expression exists
33
37
  #
34
38
  def has_selector?(*args)
35
39
  assert_selector(*args)
@@ -71,7 +75,7 @@ module Capybara
71
75
  #
72
76
  # page.assert_selector('li', :text => 'Horse', :visible => true)
73
77
  #
74
- # {assert_selector} can also accept XPath expressions generated by the
78
+ # `assert_selector` can also accept XPath expressions generated by the
75
79
  # XPath gem:
76
80
  #
77
81
  # page.assert_selector(:xpath, XPath.descendant(:p))
@@ -193,17 +197,26 @@ module Capybara
193
197
  # Checks if the page or current node has the given text content,
194
198
  # ignoring any HTML tags and normalizing whitespace.
195
199
  #
196
- # This only matches displayable text and specifically excludes text
197
- # contained within non-display nodes such as script or head tags.
200
+ # By default it will check if the text occurs at least once,
201
+ # but a different number can be specified.
202
+ #
203
+ # page.has_text?('lorem ipsum', between: 2..4)
204
+ #
205
+ # This will check if the text occurs from 2 to 4 times.
198
206
  #
199
- # @param [String] content The text to check for
200
- # @return [Boolean] Whether it exists
207
+ # @overload has_text?([type], text, [options])
208
+ # @param [:all, :visible] type Whether to check for only visible or all text
209
+ # @param [String, Regexp] text The text/regexp to check for
210
+ # @param [Hash] options additional options
211
+ # @option options [Integer] :count (nil) Number of times the text should occur
212
+ # @option options [Integer] :minimum (nil) Minimum number of times the text should occur
213
+ # @option options [Integer] :maximum (nil) Maximum number of times the text should occur
214
+ # @option options [Range] :between (nil) Range of times that should contain number of times text occurs
215
+ # @return [Boolean] Whether it exists
201
216
  #
202
- def has_text?(content)
217
+ def has_text?(*args)
203
218
  synchronize do
204
- unless Capybara::Helpers.normalize_whitespace(text).match(Capybara::Helpers.to_regexp(content))
205
- raise ExpectationNotMet
206
- end
219
+ raise ExpectationNotMet unless text_found?(*args)
207
220
  end
208
221
  return true
209
222
  rescue Capybara::ExpectationNotMet
@@ -216,17 +229,12 @@ module Capybara
216
229
  # Checks if the page or current node does not have the given text
217
230
  # content, ignoring any HTML tags and normalizing whitespace.
218
231
  #
219
- # This only matches displayable text and specifically excludes text
220
- # contained within non-display nodes such as script or head tags.
232
+ # @param (see #has_text?)
233
+ # @return [Boolean] Whether it doesn't exist
221
234
  #
222
- # @param [String] content The text to check for
223
- # @return [Boolean] Whether it doesn't exist
224
- #
225
- def has_no_text?(content)
235
+ def has_no_text?(*args)
226
236
  synchronize do
227
- if Capybara::Helpers.normalize_whitespace(text).match(Capybara::Helpers.to_regexp(content))
228
- raise ExpectationNotMet
229
- end
237
+ raise ExpectationNotMet if text_found?(*args)
230
238
  end
231
239
  return true
232
240
  rescue Capybara::ExpectationNotMet
@@ -299,6 +307,8 @@ module Capybara
299
307
  #
300
308
  # page.has_field?('Email', :type => 'email')
301
309
  #
310
+ # Note: 'textarea' and 'select' are valid type values, matching the associated tag names.
311
+ #
302
312
  # @param [String] locator The label, name or id of a field to check for
303
313
  # @option options [String] :with The text content of the field
304
314
  # @option options [String] :type The type attribute of the field
@@ -450,6 +460,13 @@ module Capybara
450
460
 
451
461
  private
452
462
 
463
+ def text_found?(*args)
464
+ type = args.shift if args.first.is_a?(Symbol) or args.first.nil?
465
+ content, options = args
466
+ count = Capybara::Helpers.normalize_whitespace(text(type)).scan(Capybara::Helpers.to_regexp(content)).count
467
+
468
+ Capybara::Helpers.matches_count?(count, options || {})
469
+ end
453
470
  end
454
471
  end
455
472
  end
@@ -26,7 +26,7 @@ module Capybara
26
26
  #
27
27
  # @return [String] The text of the element
28
28
  #
29
- def text
29
+ def text(type=nil)
30
30
  native.text
31
31
  end
32
32
 
@@ -36,8 +36,8 @@ module Capybara
36
36
  #
37
37
  # element[:title] # => HTML title attribute
38
38
  #
39
- # @param [Symbol] attribute The attribute to retrieve
40
- # @return [String] The value of the attribute
39
+ # @param [Symbol] name The attribute name to retrieve
40
+ # @return [String] The value of the attribute
41
41
  #
42
42
  def [](name)
43
43
  attr_name = name.to_s
@@ -108,6 +108,15 @@ module Capybara
108
108
  native[:checked]
109
109
  end
110
110
 
111
+ ##
112
+ #
113
+ # Whether or not the element is disabled.
114
+ #
115
+ # @return [Boolean] Whether the element is disabled
116
+ def disabled?
117
+ native[:disabled]
118
+ end
119
+
111
120
  ##
112
121
  #
113
122
  # Whether or not the element is selected.
@@ -118,7 +127,7 @@ module Capybara
118
127
  native[:selected]
119
128
  end
120
129
 
121
- def synchronize
130
+ def synchronize(seconds=nil)
122
131
  yield # simple nodes don't need to wait
123
132
  end
124
133
 
@@ -126,13 +135,26 @@ module Capybara
126
135
  # no op
127
136
  end
128
137
 
129
- def unsynchronized
130
- yield # simple nodes don't need to wait
138
+ def title
139
+ native.xpath("//title").first.text
131
140
  end
132
141
 
133
- def all(*args)
134
- query = Capybara::Query.new(*args)
135
- elements = native.xpath(query.xpath).map do |node|
142
+ def has_title?(content)
143
+ title.match(Capybara::Helpers.to_regexp(content))
144
+ end
145
+
146
+ def has_no_title?(content)
147
+ not has_title?(content)
148
+ end
149
+
150
+ private
151
+
152
+ def resolve_query(query, exact=nil)
153
+ elements = if query.selector.format == :css
154
+ native.css(query.css)
155
+ else
156
+ native.xpath(query.xpath(exact))
157
+ end.map do |node|
136
158
  self.class.new(node)
137
159
  end
138
160
  Capybara::Result.new(elements, query)