capybara 0.3.9 → 0.4.0.rc

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 (70) hide show
  1. data/History.txt +43 -1
  2. data/README.rdoc +168 -98
  3. data/lib/capybara.rb +77 -15
  4. data/lib/capybara/driver/base.rb +21 -16
  5. data/lib/capybara/driver/celerity_driver.rb +39 -41
  6. data/lib/capybara/driver/culerity_driver.rb +2 -1
  7. data/lib/capybara/driver/node.rb +66 -0
  8. data/lib/capybara/driver/rack_test_driver.rb +66 -67
  9. data/lib/capybara/driver/selenium_driver.rb +43 -47
  10. data/lib/capybara/dsl.rb +44 -6
  11. data/lib/capybara/node.rb +185 -24
  12. data/lib/capybara/node/actions.rb +170 -0
  13. data/lib/capybara/node/finders.rb +150 -0
  14. data/lib/capybara/node/matchers.rb +360 -0
  15. data/lib/capybara/rails.rb +1 -0
  16. data/lib/capybara/selector.rb +52 -0
  17. data/lib/capybara/server.rb +68 -87
  18. data/lib/capybara/session.rb +221 -207
  19. data/lib/capybara/spec/driver.rb +45 -35
  20. data/lib/capybara/spec/public/test.js +1 -1
  21. data/lib/capybara/spec/session.rb +28 -53
  22. data/lib/capybara/spec/session/all_spec.rb +7 -3
  23. data/lib/capybara/spec/session/check_spec.rb +50 -52
  24. data/lib/capybara/spec/session/click_button_spec.rb +9 -0
  25. data/lib/capybara/spec/session/click_link_or_button_spec.rb +37 -0
  26. data/lib/capybara/spec/session/current_url_spec.rb +7 -0
  27. data/lib/capybara/spec/session/find_button_spec.rb +4 -2
  28. data/lib/capybara/spec/session/find_by_id_spec.rb +4 -2
  29. data/lib/capybara/spec/session/find_field_spec.rb +7 -3
  30. data/lib/capybara/spec/session/find_link_spec.rb +5 -3
  31. data/lib/capybara/spec/session/find_spec.rb +71 -6
  32. data/lib/capybara/spec/session/has_field_spec.rb +1 -1
  33. data/lib/capybara/spec/session/has_selector_spec.rb +129 -0
  34. data/lib/capybara/spec/session/has_xpath_spec.rb +4 -4
  35. data/lib/capybara/spec/session/javascript.rb +25 -5
  36. data/lib/capybara/spec/session/select_spec.rb +16 -2
  37. data/lib/capybara/spec/session/unselect_spec.rb +8 -1
  38. data/lib/capybara/spec/session/within_spec.rb +5 -5
  39. data/lib/capybara/spec/views/form.erb +65 -1
  40. data/lib/capybara/spec/views/popup_one.erb +8 -0
  41. data/lib/capybara/spec/views/popup_two.erb +8 -0
  42. data/lib/capybara/spec/views/with_html.erb +5 -0
  43. data/lib/capybara/spec/views/within_popups.erb +25 -0
  44. data/lib/capybara/{save_and_open_page.rb → util/save_and_open_page.rb} +3 -3
  45. data/lib/capybara/util/timeout.rb +27 -0
  46. data/lib/capybara/version.rb +1 -1
  47. data/spec/capybara_spec.rb +18 -8
  48. data/spec/driver/celerity_driver_spec.rb +10 -14
  49. data/spec/driver/culerity_driver_spec.rb +4 -3
  50. data/spec/driver/rack_test_driver_spec.rb +39 -2
  51. data/spec/driver/remote_culerity_driver_spec.rb +5 -7
  52. data/spec/driver/remote_selenium_driver_spec.rb +7 -10
  53. data/spec/driver/selenium_driver_spec.rb +3 -2
  54. data/spec/dsl_spec.rb +5 -14
  55. data/spec/save_and_open_page_spec.rb +19 -19
  56. data/spec/server_spec.rb +22 -10
  57. data/spec/session/celerity_session_spec.rb +17 -21
  58. data/spec/session/culerity_session_spec.rb +3 -3
  59. data/spec/session/rack_test_session_spec.rb +2 -2
  60. data/spec/session/selenium_session_spec.rb +2 -2
  61. data/spec/spec_helper.rb +27 -6
  62. data/spec/{wait_until_spec.rb → timeout_spec.rb} +14 -14
  63. metadata +88 -46
  64. data/lib/capybara/searchable.rb +0 -54
  65. data/lib/capybara/spec/session/click_spec.rb +0 -24
  66. data/lib/capybara/spec/session/locate_spec.rb +0 -65
  67. data/lib/capybara/wait_until.rb +0 -28
  68. data/lib/capybara/xpath.rb +0 -179
  69. data/spec/searchable_spec.rb +0 -66
  70. data/spec/xpath_spec.rb +0 -180
@@ -13,4 +13,5 @@ Capybara.app = Rack::Builder.new do
13
13
  end.to_app
14
14
 
15
15
  Capybara.asset_root = Rails.root.join('public')
16
+ Capybara.save_and_open_page_path = Rails.root.join('tmp/capybara')
16
17
 
@@ -0,0 +1,52 @@
1
+ module Capybara
2
+ class Selector
3
+ attr_reader :name, :options, :block
4
+
5
+ class << self
6
+ def all
7
+ @selectors ||= {}
8
+ end
9
+
10
+ def add(name, options={}, &block)
11
+ all[name.to_sym] = Capybara::Selector.new(name.to_sym, options, &block)
12
+ end
13
+
14
+ def remove(name)
15
+ all.delete(name.to_sym)
16
+ end
17
+
18
+ def normalize(name_or_locator, locator=nil)
19
+ xpath = if locator
20
+ all[name_or_locator.to_sym].call(locator)
21
+ else
22
+ selector = all.values.find { |s| s.match?(name_or_locator) }
23
+ selector ||= all[Capybara.default_selector]
24
+ selector.call(name_or_locator)
25
+ end
26
+ if xpath.respond_to?(:to_xpaths)
27
+ xpath.to_xpaths
28
+ else
29
+ [xpath.to_s].flatten
30
+ end
31
+ end
32
+ end
33
+
34
+ def initialize(name, options={}, &block)
35
+ @name = name
36
+ @options = options
37
+ @block = block
38
+ end
39
+
40
+ def call(locator)
41
+ @block.call(locator)
42
+ end
43
+
44
+ def match?(locator)
45
+ @options[:for] and @options[:for] === locator
46
+ end
47
+ end
48
+ end
49
+
50
+ Capybara::Selector.add(:xpath) { |xpath| xpath }
51
+ Capybara::Selector.add(:css) { |css| XPath.css(css) }
52
+ Capybara::Selector.add(:id, :for => Symbol) { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
@@ -1,114 +1,95 @@
1
1
  require 'uri'
2
2
  require 'net/http'
3
3
  require 'rack'
4
+ require 'capybara/util/timeout'
4
5
 
5
- class Capybara::Server
6
- class Identify
7
- def initialize(app)
8
- @app = app
9
- end
6
+ module Capybara
7
+ class Server
8
+ class Identify
9
+ def initialize(app)
10
+ @app = app
11
+ end
10
12
 
11
- def call(env)
12
- if env["PATH_INFO"] == "/__identify__"
13
- [200, {}, @app.object_id.to_s]
14
- else
15
- @app.call(env)
13
+ def call(env)
14
+ if env["PATH_INFO"] == "/__identify__"
15
+ [200, {}, @app.object_id.to_s]
16
+ else
17
+ @app.call(env)
18
+ end
16
19
  end
17
20
  end
18
- end
19
21
 
20
- attr_reader :app, :port
21
-
22
- def initialize(app)
23
- @app = app
24
- end
22
+ class << self
23
+ def ports
24
+ @ports ||= {}
25
+ end
26
+ end
25
27
 
26
- def host
27
- "localhost"
28
- end
28
+ attr_reader :app, :port
29
29
 
30
- def url(path)
31
- if path =~ /^http/
32
- path
33
- else
34
- (Capybara.app_host || "http://#{host}:#{port}") + path.to_s
30
+ def initialize(app)
31
+ @app = app
35
32
  end
36
- end
37
33
 
38
- def responsive?
39
- is_running_on_port?(port)
40
- end
34
+ def host
35
+ "127.0.0.1"
36
+ end
41
37
 
42
- def handler
43
- begin
44
- require 'rack/handler/thin'
45
- Rack::Handler::Thin
46
- rescue LoadError
47
- begin
48
- require 'rack/handler/mongrel'
49
- Rack::Handler::Mongrel
50
- rescue LoadError
51
- require 'rack/handler/webrick'
52
- Rack::Handler::WEBrick
38
+ def url(path)
39
+ if path =~ /^http/
40
+ path
41
+ else
42
+ (Capybara.app_host || "http://#{host}:#{port}") + path.to_s
53
43
  end
54
44
  end
55
- end
56
45
 
57
- def boot
58
- return self unless @app
59
- find_available_port
60
- Capybara.log "application has already booted" and return self if responsive?
61
- Capybara.log "booting Rack applicartion on port #{port}"
46
+ def responsive?
47
+ res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
62
48
 
63
- Thread.new do
64
- handler.run(Identify.new(@app), :Port => port, :AccessLog => [])
49
+ if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
50
+ return res.body == @app.object_id.to_s
51
+ end
52
+ rescue Errno::ECONNREFUSED, Errno::EBADF
53
+ return false
65
54
  end
66
- Capybara.log "checking if application has booted"
67
55
 
68
- Capybara::WaitUntil.timeout(10) do
69
- if responsive?
70
- Capybara.log("application has booted")
71
- true
72
- else
73
- sleep 0.5
74
- false
56
+ def boot
57
+ if @app
58
+ @port = Capybara::Server.ports[@app.object_id]
59
+
60
+ if not @port or not responsive?
61
+ @port = Capybara.server_port || find_available_port
62
+ Capybara::Server.ports[@app.object_id] = @port
63
+
64
+ Thread.new do
65
+ begin
66
+ require 'rack/handler/thin'
67
+ Thin::Logging.silent = true
68
+ Rack::Handler::Thin.run(Identify.new(@app), :Port => @port)
69
+ rescue LoadError
70
+ require 'rack/handler/webrick'
71
+ Rack::Handler::WEBrick.run(Identify.new(@app), :Port => @port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0))
72
+ end
73
+ end
74
+
75
+ Capybara.timeout(10) { if responsive? then true else sleep(0.5) and false end }
76
+ end
75
77
  end
78
+ rescue Timeout::Error
79
+ puts "Rack application timed out during boot"
80
+ exit
81
+ else
82
+ self
76
83
  end
77
- self
78
- rescue Timeout::Error
79
- Capybara.log "Rack application timed out during boot"
80
- exit
81
- end
82
84
 
83
- private
85
+ private
84
86
 
85
- def find_available_port
86
- @port = 9887
87
- @port += 1 while is_port_open?(@port) and not is_running_on_port?(@port)
88
- end
89
-
90
- def is_running_on_port?(tested_port)
91
- res = Net::HTTP.start(host, tested_port) { |http| http.get('/__identify__') }
92
-
93
- if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
94
- return res.body == @app.object_id.to_s
87
+ def find_available_port
88
+ server = TCPServer.new('127.0.0.1', 0)
89
+ server.addr[1]
90
+ ensure
91
+ server.close if server
95
92
  end
96
- rescue Errno::ECONNREFUSED, Errno::EBADF
97
- return false
98
- end
99
93
 
100
- def is_port_open?(tested_port)
101
- Timeout::timeout(1) do
102
- begin
103
- s = TCPSocket.new(host, tested_port)
104
- s.close
105
- return true
106
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
107
- return false
108
- end
109
- end
110
- rescue Timeout::Error
111
- return false
112
94
  end
113
-
114
95
  end
@@ -1,18 +1,38 @@
1
- require 'forwardable'
2
- require 'capybara/wait_until'
1
+ require 'capybara/util/timeout'
3
2
 
4
3
  module Capybara
5
- class Session
6
- extend Forwardable
7
- include Searchable
8
4
 
5
+ ##
6
+ #
7
+ # The Session class represents a single user's interaction with the system. The Session can use
8
+ # any of the underlying drivers. A session can be initialized manually like this:
9
+ #
10
+ # session = Capybara::Session.new(:culerity, MyRackApp)
11
+ #
12
+ # The application given as the second argument is optional. When running Capybara against an external
13
+ # page, you might want to leave it out:
14
+ #
15
+ # session = Capybara::Session.new(:culerity)
16
+ # session.visit('http://www.google.com')
17
+ #
18
+ # Session provides a number of methods for controlling the navigation of the page, such as +visit+,
19
+ # +current_path, and so on. It also delegate a number of methods to a Capybara::Document, representing
20
+ # the current HTML document. This allows interaction:
21
+ #
22
+ # session.fill_in('q', :with => 'Capybara')
23
+ # session.click_button('Search')
24
+ # session.should have_content('Capybara')
25
+ #
26
+ # When using capybara/dsl, the Session is initialized automatically for you.
27
+ #
28
+ class Session
9
29
  DSL_METHODS = [
10
- :all, :attach_file, :body, :check, :choose, :click, :click_button, :click_link, :current_url, :drag, :evaluate_script,
30
+ :all, :attach_file, :body, :check, :choose, :click_link_or_button, :click_button, :click_link, :current_url, :drag, :evaluate_script,
11
31
  :field_labeled, :fill_in, :find, :find_button, :find_by_id, :find_field, :find_link, :has_content?, :has_css?,
12
32
  :has_no_content?, :has_no_css?, :has_no_xpath?, :has_xpath?, :locate, :save_and_open_page, :select, :source, :uncheck,
13
- :visit, :wait_until, :within, :within_fieldset, :within_table, :within_frame, :has_link?, :has_no_link?, :has_button?,
14
- :has_no_button?, :has_field?, :has_no_field?, :has_checked_field?, :has_unchecked_field?, :has_no_table?, :has_table?,
15
- :unselect, :has_select?, :has_no_select?, :current_path, :scope_to
33
+ :visit, :wait_until, :within, :within_fieldset, :within_table, :within_frame, :within_window, :has_link?, :has_no_link?, :has_button?,
34
+ :has_no_button?, :has_field?, :has_no_field?, :has_checked_field?, :has_unchecked_field?, :has_no_table?, :has_table?,
35
+ :unselect, :has_select?, :has_no_select?, :current_path, :scope_to, :click
16
36
  ]
17
37
 
18
38
  attr_reader :mode, :app
@@ -23,254 +43,248 @@ module Capybara
23
43
  end
24
44
 
25
45
  def driver
26
- @driver ||= begin
27
- string = mode.to_s
28
- string.gsub!(%r{(^.)|(_.)}) { |m| m[m.length-1,1].upcase }
29
- Capybara::Driver.const_get(string.to_sym).new(app)
30
- rescue NameError
31
- raise Capybara::DriverNotFoundError, "no driver called #{mode} was found"
46
+ @driver ||= begin
47
+ unless Capybara.drivers.has_key?(mode)
48
+ other_drivers = Capybara.drivers.keys.map { |key| key.inspect }
49
+ raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
50
+ end
51
+ Capybara.drivers[mode].call(app)
32
52
  end
33
53
  end
34
54
 
35
- def_delegator :driver, :cleanup!
36
- def_delegator :driver, :current_url
37
- def_delegator :driver, :current_path
38
- def_delegator :driver, :response_headers
39
- def_delegator :driver, :status_code
40
- def_delegator :driver, :visit
41
- def_delegator :driver, :body
42
- def_delegator :driver, :source
43
-
44
- def click(locator)
45
- msg = "no link or button '#{locator}' found"
46
- locate(:xpath, XPath.link(locator).button(locator), msg).click
47
- end
48
-
49
- def click_link(locator)
50
- msg = "no link with title, id or text '#{locator}' found"
51
- locate(:xpath, XPath.link(locator), msg).click
52
- end
53
-
54
- def click_button(locator)
55
- msg = "no button with value or id or text '#{locator}' found"
56
- locate(:xpath, XPath.button(locator), msg).click
57
- end
58
-
59
- def drag(source_locator, target_locator)
60
- source = locate(:xpath, source_locator, "drag source '#{source_locator}' not found on page")
61
- target = locate(:xpath, target_locator, "drag target '#{target_locator}' not found on page")
62
- source.drag_to(target)
63
- end
64
-
65
- def fill_in(locator, options={})
66
- msg = "cannot fill in, no text field, text area or password field with id, name, or label '#{locator}' found"
67
- raise "Must pass a hash containing 'with'" if not options.is_a?(Hash) or not options.has_key?(:with)
68
- locate(:xpath, XPath.fillable_field(locator), msg).set(options[:with])
69
- end
70
-
71
- def choose(locator)
72
- msg = "cannot choose field, no radio button with id, name, or label '#{locator}' found"
73
- locate(:xpath, XPath.radio_button(locator), msg).set(true)
74
- end
75
-
76
- def check(locator)
77
- msg = "cannot check field, no checkbox with id, name, or label '#{locator}' found"
78
- locate(:xpath, XPath.checkbox(locator), msg).set(true)
79
- end
80
-
81
- def uncheck(locator)
82
- msg = "cannot uncheck field, no checkbox with id, name, or label '#{locator}' found"
83
- locate(:xpath, XPath.checkbox(locator), msg).set(false)
84
- end
85
-
86
- def select(value, options={})
87
- msg = "cannot select option, no select box with id, name, or label '#{options[:from]}' found"
88
- locate(:xpath, XPath.select(options[:from]), msg).select(value)
89
- end
90
-
91
- def unselect(value, options={})
92
- msg = "cannot unselect option, no select box with id, name, or label '#{options[:from]}' found"
93
- locate(:xpath, XPath.select(options[:from]), msg).unselect(value)
94
- end
95
-
96
- def attach_file(locator, path)
97
- msg = "cannot attach file, no file field with id, name, or label '#{locator}' found"
98
- locate(:xpath, XPath.file_field(locator), msg).set(path)
99
- end
100
-
101
- def within(kind, scope=nil)
102
- kind, scope = Capybara.default_selector, kind unless scope
103
- scope = XPath.from_css(scope) if kind == :css
104
- locate(:xpath, scope, "scope '#{scope}' not found on page")
55
+ ##
56
+ #
57
+ # Reset the session, removing all cookies.
58
+ #
59
+ def reset!
60
+ driver.reset!
61
+ end
62
+ alias_method :cleanup!, :reset!
63
+
64
+ ##
65
+ #
66
+ # Returns a hash of response headers. Not supported by all drivers (e.g. Selenium)
67
+ #
68
+ # @return [Hash{String => String}] A hash of response headers.
69
+ #
70
+ def response_headers
71
+ driver.response_headers
72
+ end
73
+
74
+ ##
75
+ #
76
+ # Returns the current HTTP status code as an Integer. Not supported by all drivers (e.g. Selenium)
77
+ #
78
+ # @return [Integer] Current HTTP status code
79
+ #
80
+ def status_code
81
+ driver.status_code
82
+ end
83
+
84
+ ##
85
+ #
86
+ # @return [String] A snapshot of the HTML of the current document, as it looks right now
87
+ #
88
+ def body
89
+ driver.body
90
+ end
91
+
92
+ ##
93
+ #
94
+ # @return [String] HTML source of the document, before being modified by JavaScript.
95
+ #
96
+ def source
97
+ driver.source
98
+ end
99
+
100
+ ##
101
+ #
102
+ # @return [String] Path of the current page, without any domain information
103
+ #
104
+ def current_path
105
+ URI.parse(current_url).path
106
+ end
107
+
108
+ ##
109
+ #
110
+ # @return [String] Fully qualified URL of the current page
111
+ #
112
+ def current_url
113
+ driver.current_url
114
+ end
115
+
116
+ ##
117
+ #
118
+ # Navigate to the given URL. The URL can either be a relative URL or an absolute URL
119
+ # The behaviour of either depends on the driver.
120
+ #
121
+ # session.visit('/foo')
122
+ # session.visit('http://google.com')
123
+ #
124
+ # For drivers which can run against an external application, such as culerity and selenium
125
+ # giving an absolute URL will navigate to that page. This allows testing applications
126
+ # running on remote servers. For these drivers, setting Capybara.app_host will make the
127
+ # remote server the default. For example:
128
+ #
129
+ # Capybara.app_host = 'http://google.com'
130
+ # session.visit('/') # visits the google homepage
131
+ #
132
+ # @param [String] url The URL to navigate to
133
+ #
134
+ def visit(url)
135
+ driver.visit(url)
136
+ end
137
+
138
+ ##
139
+ #
140
+ # Execute the given block for a particular scope on the page. Within will find the first
141
+ # element matching the given selector and execute the block scoped to that element:
142
+ #
143
+ # within(:xpath, '//div[@id="delivery-address"]') do
144
+ # fill_in('Street', :with => '12 Main Street')
145
+ # end
146
+ #
147
+ # It is possible to omit the first parameter, in that case, the selector is assumed to be
148
+ # of the type set in Capybara.default_selector.
149
+ #
150
+ # within('div#delivery-address') do
151
+ # fill_in('Street', :with => '12 Main Street')
152
+ # end
153
+ #
154
+ # @param [:css, :xpath, String] kind The type of selector or the selector if the second argument is blank
155
+ # @param [String] selector The selector within which to execute the given block
156
+ #
157
+ def within(kind, selector=nil)
158
+ new_scope = find(kind, selector, :message => "scope '#{selector || kind}' not found on page")
105
159
  begin
106
- scopes.push(scope)
160
+ scopes.push(new_scope)
107
161
  yield
108
162
  ensure
109
163
  scopes.pop
110
164
  end
111
165
  end
112
166
 
167
+ ##
168
+ #
169
+ # Execute the given block within the a specific fieldset given the id or legend of that fieldset.
170
+ #
171
+ # @param [String] locator Id or legend of the fieldset
172
+ #
113
173
  def within_fieldset(locator)
114
- within :xpath, XPath.fieldset(locator) do
174
+ within :xpath, XPath::HTML.fieldset(locator) do
115
175
  yield
116
176
  end
117
177
  end
118
178
 
179
+ ##
180
+ #
181
+ # Execute the given block within the a specific table given the id or caption of that table.
182
+ #
183
+ # @param [String] locator Id or caption of the table
184
+ #
119
185
  def within_table(locator)
120
- within :xpath, XPath.table(locator) do
186
+ within :xpath, XPath::HTML.table(locator) do
121
187
  yield
122
188
  end
123
189
  end
124
190
 
191
+ ##
192
+ #
193
+ # Execute the given block within the given iframe given the id of that iframe. Only works on
194
+ # some drivers (e.g. Selenium)
195
+ #
196
+ # @param [String] locator Id of the frame
197
+ #
125
198
  def within_frame(frame_id)
126
199
  driver.within_frame(frame_id) do
127
200
  yield
128
201
  end
129
202
  end
130
203
 
131
- def scope_to(*locator)
132
- scoped_session = self.clone
133
- scoped_session.instance_eval do
134
- @scopes = scopes + locator
135
- end
136
- scoped_session
137
- end
138
-
139
- def has_xpath?(path, options={})
140
- wait_conditionally_until do
141
- results = all(:xpath, path, options)
142
-
143
- if options[:count]
144
- results.size == options[:count]
145
- else
146
- results.size > 0
147
- end
148
- end
149
- rescue Capybara::TimeoutError
150
- return false
151
- end
152
-
153
- def has_no_xpath?(path, options={})
154
- wait_conditionally_until do
155
- results = all(:xpath, path, options)
156
-
157
- if options[:count]
158
- results.size != options[:count]
159
- else
160
- results.empty?
161
- end
162
- end
163
- rescue Capybara::TimeoutError
164
- return false
165
- end
166
-
167
- def has_css?(path, options={})
168
- has_xpath?(XPath.from_css(path), options)
204
+ ##
205
+ #
206
+ # Execute the given block within the given window. Only works on
207
+ # some drivers (e.g. Selenium)
208
+ #
209
+ # @param [String] locator of the window
210
+ #
211
+ def within_window(handle, &blk)
212
+ driver.within_window(handle, &blk)
169
213
  end
170
214
 
171
- def has_no_css?(path, options={})
172
- has_no_xpath?(XPath.from_css(path), options)
173
- end
174
-
175
- def has_content?(content)
176
- has_xpath?(XPath.content(content))
177
- end
178
-
179
- def has_no_content?(content)
180
- has_no_xpath?(XPath.content(content))
181
- end
182
-
183
- def has_link?(locator)
184
- has_xpath?(XPath.link(locator))
185
- end
186
-
187
- def has_no_link?(locator)
188
- has_no_xpath?(XPath.link(locator))
189
- end
190
-
191
- def has_button?(locator)
192
- has_xpath?(XPath.button(locator))
193
- end
194
-
195
- def has_no_button?(locator)
196
- has_no_xpath?(XPath.button(locator))
197
- end
198
-
199
- def has_field?(locator, options={})
200
- has_xpath?(XPath.field(locator, options))
201
- end
202
-
203
- def has_no_field?(locator, options={})
204
- has_no_xpath?(XPath.field(locator, options))
205
- end
206
-
207
- def has_checked_field?(locator)
208
- has_xpath?(XPath.field(locator, :checked => true))
209
- end
210
-
211
- def has_unchecked_field?(locator)
212
- has_xpath?(XPath.field(locator, :unchecked => true))
213
- end
214
-
215
- def has_select?(locator, options={})
216
- has_xpath?(XPath.select(locator, options))
215
+ ##
216
+ #
217
+ # Retry executing the block until a truthy result is returned or the timeout time is exceeded
218
+ #
219
+ # @param [Integer] timeout The amount of seconds to retry executing the given block
220
+ #
221
+ def wait_until(timeout = Capybara.default_wait_time)
222
+ Capybara.timeout(timeout,driver) { yield }
217
223
  end
218
224
 
219
- def has_no_select?(locator, options={})
220
- has_no_xpath?(XPath.select(locator, options))
225
+ ##
226
+ #
227
+ # Execute the given script, not returning a result. This is useful for scripts that return
228
+ # complex objects, such as jQuery statements. +execute_script+ should always be used over
229
+ # +evaluate_script+ whenever possible.
230
+ #
231
+ # @param [String] script A string of JavaScript to execute
232
+ #
233
+ def execute_script(script)
234
+ driver.execute_script(script)
221
235
  end
222
236
 
223
- def has_table?(locator, options={})
224
- has_xpath?(XPath.table(locator, options))
237
+ ##
238
+ #
239
+ # Evaluate the given JavaScript and return the result. Be careful when using this with
240
+ # scripts that return complex objects, such as jQuery statements. +execute_script+ might
241
+ # be a better alternative.
242
+ #
243
+ # @param [String] script A string of JavaScript to evaluate
244
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
245
+ #
246
+ def evaluate_script(script)
247
+ driver.evaluate_script(script)
225
248
  end
226
249
 
227
- def has_no_table?(locator, options={})
228
- has_no_xpath?(XPath.table(locator, options))
250
+ ##
251
+ #
252
+ # @deprecated click is deprecated, please use {Capybara::Node::Actions#click_link_or_button} instead
253
+ #
254
+ def click(locator)
255
+ Capybara.deprecate("click", "click_link_or_button")
256
+ current_node.click_link_or_button(locator)
229
257
  end
230
258
 
259
+ ##
260
+ #
261
+ # Save a snapshot of the page and open it in a browser for inspection
262
+ #
231
263
  def save_and_open_page
232
- require 'capybara/save_and_open_page'
233
- Capybara::SaveAndOpenPage.save_and_open_page(body)
264
+ require 'capybara/util/save_and_open_page'
265
+ Capybara.save_and_open_page(body)
234
266
  end
235
267
 
236
- #return node identified by locator or raise ElementNotFound(using desc)
237
- def locate(kind_or_locator, locator=nil, fail_msg = nil)
238
- node = wait_conditionally_until { find(kind_or_locator, locator) }
239
- ensure
240
- raise Capybara::ElementNotFound, fail_msg || "Unable to locate '#{locator || kind_or_locator}'" unless node
241
- return node
268
+ def document
269
+ Capybara::Document.new(self, driver)
242
270
  end
243
271
 
244
- def wait_until(timeout = Capybara.default_wait_time)
245
- WaitUntil.timeout(timeout,driver) { yield }
272
+ def method_missing(*args)
273
+ current_node.send(*args)
246
274
  end
247
275
 
248
- def execute_script(script)
249
- driver.execute_script(script)
250
- end
251
-
252
- def evaluate_script(script)
253
- driver.evaluate_script(script)
276
+ def respond_to?(method)
277
+ super || current_node.respond_to?(method)
254
278
  end
255
279
 
256
280
  private
257
281
 
258
- def wait_conditionally_until
259
- if driver.wait? then wait_until { yield } else yield end
260
- end
261
-
262
- def all_unfiltered(locator)
263
- XPath.wrap(locator).scope(current_scope).paths.map do |path|
264
- driver.find(path)
265
- end.flatten
266
- end
267
-
268
- def current_scope
269
- scopes.join('')
282
+ def current_node
283
+ scopes.last
270
284
  end
271
285
 
272
286
  def scopes
273
- @scopes ||= []
287
+ @scopes ||= [document]
274
288
  end
275
289
  end
276
290
  end