rcarvalho-capybara 0.4.1.1

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 (100) hide show
  1. data/History.txt +202 -0
  2. data/README.rdoc +540 -0
  3. data/lib/capybara.rb +231 -0
  4. data/lib/capybara/cucumber.rb +32 -0
  5. data/lib/capybara/driver/base.rb +60 -0
  6. data/lib/capybara/driver/celerity_driver.rb +164 -0
  7. data/lib/capybara/driver/culerity_driver.rb +26 -0
  8. data/lib/capybara/driver/node.rb +78 -0
  9. data/lib/capybara/driver/rack_test_driver.rb +303 -0
  10. data/lib/capybara/driver/selenium_driver.rb +165 -0
  11. data/lib/capybara/dsl.rb +109 -0
  12. data/lib/capybara/node/actions.rb +160 -0
  13. data/lib/capybara/node/base.rb +47 -0
  14. data/lib/capybara/node/document.rb +17 -0
  15. data/lib/capybara/node/element.rb +182 -0
  16. data/lib/capybara/node/finders.rb +201 -0
  17. data/lib/capybara/node/matchers.rb +391 -0
  18. data/lib/capybara/node/simple.rb +116 -0
  19. data/lib/capybara/rails.rb +17 -0
  20. data/lib/capybara/rspec.rb +18 -0
  21. data/lib/capybara/selector.rb +70 -0
  22. data/lib/capybara/server.rb +90 -0
  23. data/lib/capybara/session.rb +281 -0
  24. data/lib/capybara/spec/driver.rb +243 -0
  25. data/lib/capybara/spec/fixtures/capybara.jpg +0 -0
  26. data/lib/capybara/spec/fixtures/test_file.txt +1 -0
  27. data/lib/capybara/spec/public/jquery-ui.js +35 -0
  28. data/lib/capybara/spec/public/jquery.js +19 -0
  29. data/lib/capybara/spec/public/test.js +33 -0
  30. data/lib/capybara/spec/session.rb +110 -0
  31. data/lib/capybara/spec/session/all_spec.rb +78 -0
  32. data/lib/capybara/spec/session/attach_file_spec.rb +70 -0
  33. data/lib/capybara/spec/session/check_spec.rb +65 -0
  34. data/lib/capybara/spec/session/choose_spec.rb +26 -0
  35. data/lib/capybara/spec/session/click_button_spec.rb +252 -0
  36. data/lib/capybara/spec/session/click_link_or_button_spec.rb +36 -0
  37. data/lib/capybara/spec/session/click_link_spec.rb +113 -0
  38. data/lib/capybara/spec/session/current_url_spec.rb +15 -0
  39. data/lib/capybara/spec/session/fill_in_spec.rb +119 -0
  40. data/lib/capybara/spec/session/find_button_spec.rb +18 -0
  41. data/lib/capybara/spec/session/find_by_id_spec.rb +18 -0
  42. data/lib/capybara/spec/session/find_field_spec.rb +26 -0
  43. data/lib/capybara/spec/session/find_link_spec.rb +19 -0
  44. data/lib/capybara/spec/session/find_spec.rb +121 -0
  45. data/lib/capybara/spec/session/first_spec.rb +72 -0
  46. data/lib/capybara/spec/session/has_button_spec.rb +32 -0
  47. data/lib/capybara/spec/session/has_content_spec.rb +106 -0
  48. data/lib/capybara/spec/session/has_css_spec.rb +213 -0
  49. data/lib/capybara/spec/session/has_field_spec.rb +156 -0
  50. data/lib/capybara/spec/session/has_link_spec.rb +37 -0
  51. data/lib/capybara/spec/session/has_select_spec.rb +129 -0
  52. data/lib/capybara/spec/session/has_selector_spec.rb +129 -0
  53. data/lib/capybara/spec/session/has_table_spec.rb +96 -0
  54. data/lib/capybara/spec/session/has_xpath_spec.rb +123 -0
  55. data/lib/capybara/spec/session/headers.rb +19 -0
  56. data/lib/capybara/spec/session/javascript.rb +223 -0
  57. data/lib/capybara/spec/session/response_code.rb +19 -0
  58. data/lib/capybara/spec/session/select_spec.rb +105 -0
  59. data/lib/capybara/spec/session/uncheck_spec.rb +21 -0
  60. data/lib/capybara/spec/session/unselect_spec.rb +61 -0
  61. data/lib/capybara/spec/session/within_spec.rb +160 -0
  62. data/lib/capybara/spec/test_app.rb +117 -0
  63. data/lib/capybara/spec/views/buttons.erb +4 -0
  64. data/lib/capybara/spec/views/fieldsets.erb +29 -0
  65. data/lib/capybara/spec/views/form.erb +348 -0
  66. data/lib/capybara/spec/views/frame_one.erb +8 -0
  67. data/lib/capybara/spec/views/frame_two.erb +8 -0
  68. data/lib/capybara/spec/views/popup_one.erb +8 -0
  69. data/lib/capybara/spec/views/popup_two.erb +8 -0
  70. data/lib/capybara/spec/views/postback.erb +13 -0
  71. data/lib/capybara/spec/views/tables.erb +122 -0
  72. data/lib/capybara/spec/views/with_html.erb +69 -0
  73. data/lib/capybara/spec/views/with_js.erb +39 -0
  74. data/lib/capybara/spec/views/with_scope.erb +36 -0
  75. data/lib/capybara/spec/views/with_simple_html.erb +1 -0
  76. data/lib/capybara/spec/views/within_frames.erb +10 -0
  77. data/lib/capybara/spec/views/within_popups.erb +25 -0
  78. data/lib/capybara/util/save_and_open_page.rb +40 -0
  79. data/lib/capybara/util/timeout.rb +27 -0
  80. data/lib/capybara/version.rb +3 -0
  81. data/spec/basic_node_spec.rb +77 -0
  82. data/spec/capybara_spec.rb +46 -0
  83. data/spec/driver/celerity_driver_spec.rb +13 -0
  84. data/spec/driver/culerity_driver_spec.rb +14 -0
  85. data/spec/driver/rack_test_driver_spec.rb +84 -0
  86. data/spec/driver/remote_culerity_driver_spec.rb +22 -0
  87. data/spec/driver/remote_selenium_driver_spec.rb +16 -0
  88. data/spec/driver/selenium_driver_spec.rb +14 -0
  89. data/spec/dsl_spec.rb +157 -0
  90. data/spec/rspec_spec.rb +47 -0
  91. data/spec/save_and_open_page_spec.rb +159 -0
  92. data/spec/server_spec.rb +85 -0
  93. data/spec/session/celerity_session_spec.rb +24 -0
  94. data/spec/session/culerity_session_spec.rb +26 -0
  95. data/spec/session/rack_test_session_spec.rb +44 -0
  96. data/spec/session/selenium_session_spec.rb +26 -0
  97. data/spec/spec_helper.rb +40 -0
  98. data/spec/string_spec.rb +77 -0
  99. data/spec/timeout_spec.rb +28 -0
  100. metadata +343 -0
@@ -0,0 +1,116 @@
1
+ module Capybara
2
+ module Node
3
+
4
+ ##
5
+ #
6
+ # A {Capybara::Node::Simple} is a simpler version of {Capybara::Node::Base} which
7
+ # includes only {Capybara::Node::Finders} and {Capybara::Node::Matchers} and does
8
+ # not include {Capybara::Node::Actions}. This type of node is returned when
9
+ # using {Capybara.string}.
10
+ #
11
+ # It is useful in that it does not require a session, an application or a driver,
12
+ # but can still use Capybara's finders and matchers on any string that contains HTML.
13
+ #
14
+ class Simple
15
+ include Capybara::Node::Finders
16
+ include Capybara::Node::Matchers
17
+
18
+ attr_reader :native
19
+
20
+ def initialize(native)
21
+ native = Nokogiri::HTML(native) if native.is_a?(String)
22
+ @native = native
23
+ end
24
+
25
+ ##
26
+ #
27
+ # @return [String] The text of the element
28
+ #
29
+ def text
30
+ native.text
31
+ end
32
+
33
+ ##
34
+ #
35
+ # Retrieve the given attribute
36
+ #
37
+ # element[:title] # => HTML title attribute
38
+ #
39
+ # @param [Symbol] attribute The attribute to retrieve
40
+ # @return [String] The value of the attribute
41
+ #
42
+ def [](name)
43
+ attr_name = name.to_s
44
+ if attr_name == 'value'
45
+ value
46
+ elsif 'input' == tag_name and 'checkbox' == native[:type] and 'checked' == attr_name
47
+ native['checked'] == 'checked'
48
+ else
49
+ native[attr_name]
50
+ end
51
+ end
52
+
53
+ ##
54
+ #
55
+ # @return [String] The tag name of the element
56
+ #
57
+ def tag_name
58
+ native.node_name
59
+ end
60
+
61
+ ##
62
+ #
63
+ # An XPath expression describing where on the page the element can be found
64
+ #
65
+ # @return [String] An XPath expression
66
+ #
67
+ def path
68
+ native.path
69
+ end
70
+
71
+ ##
72
+ #
73
+ # @return [String] The value of the form element
74
+ #
75
+ def value
76
+ if tag_name == 'textarea'
77
+ native.content
78
+ elsif tag_name == 'select'
79
+ if native['multiple'] == 'multiple'
80
+ native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
81
+ else
82
+ option = native.xpath(".//option[@selected='selected']").first || native.xpath(".//option").first
83
+ option[:value] || option.content if option
84
+ end
85
+ else
86
+ native[:value]
87
+ end
88
+ end
89
+
90
+ ##
91
+ #
92
+ # Whether or not the element is visible. Does not support CSS, so
93
+ # the result may be inaccurate.
94
+ #
95
+ # @return [Boolean] Whether the element is visible
96
+ #
97
+ def visible?
98
+ native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none')]").size == 0
99
+ end
100
+
101
+ protected
102
+
103
+ def find_in_base(xpath)
104
+ native.xpath(xpath).map { |node| self.class.new(node) }
105
+ end
106
+
107
+ def convert_element(element)
108
+ element
109
+ end
110
+
111
+ def wait?
112
+ false
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,17 @@
1
+ require 'capybara'
2
+ require 'capybara/dsl'
3
+
4
+ Capybara.app = Rack::Builder.new do
5
+ map "/" do
6
+ if Rails.version.to_f >= 3.0
7
+ run Rails.application
8
+ else # Rails 2
9
+ use Rails::Rack::Static
10
+ run ActionController::Dispatcher.new
11
+ end
12
+ end
13
+ end.to_app
14
+
15
+ Capybara.asset_root = Rails.root.join('public')
16
+ Capybara.save_and_open_page_path = Rails.root.join('tmp/capybara')
17
+
@@ -0,0 +1,18 @@
1
+ require 'capybara'
2
+ require 'capybara/dsl'
3
+
4
+ RSpec.configure do |config|
5
+ config.include Capybara, :type => :acceptance
6
+ config.after do
7
+ if example.metadata[:type] == :acceptance
8
+ Capybara.reset_sessions!
9
+ Capybara.use_default_driver
10
+ end
11
+ end
12
+ config.before do
13
+ if example.metadata[:type] == :acceptance
14
+ Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
15
+ Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,70 @@
1
+ module Capybara
2
+ class Selector
3
+ attr_reader :name
4
+
5
+ class << self
6
+ def all
7
+ @selectors ||= {}
8
+ end
9
+
10
+ def add(name, &block)
11
+ all[name.to_sym] = Capybara::Selector.new(name.to_sym, &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, &block)
35
+ @name = name
36
+ instance_eval(&block)
37
+ end
38
+
39
+ def xpath(&block)
40
+ @xpath = block if block
41
+ @xpath
42
+ end
43
+
44
+ def match(&block)
45
+ @match = block if block
46
+ @match
47
+ end
48
+
49
+ def call(locator)
50
+ @xpath.call(locator)
51
+ end
52
+
53
+ def match?(locator)
54
+ @match and @match.call(locator)
55
+ end
56
+ end
57
+ end
58
+
59
+ Capybara.add_selector(:xpath) do
60
+ xpath { |xpath| xpath }
61
+ end
62
+
63
+ Capybara.add_selector(:css) do
64
+ xpath { |css| XPath.css(css) }
65
+ end
66
+
67
+ Capybara.add_selector(:id) do
68
+ xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
69
+ match { |value| value.is_a?(Symbol) }
70
+ end
@@ -0,0 +1,90 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'rack'
4
+ require 'capybara/util/timeout'
5
+
6
+ module Capybara
7
+ class Server
8
+ class Identify
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
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
19
+ end
20
+ end
21
+
22
+ class << self
23
+ def ports
24
+ @ports ||= {}
25
+ end
26
+ end
27
+
28
+ attr_reader :app, :port
29
+
30
+ def initialize(app)
31
+ @app = app
32
+ end
33
+
34
+ def host
35
+ "127.0.0.1"
36
+ end
37
+
38
+ def url(path)
39
+ if path =~ /^http/
40
+ path
41
+ else
42
+ (Capybara.app_host || "http://#{host}:#{port}") + path.to_s
43
+ end
44
+ end
45
+
46
+ def responsive?
47
+ res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
48
+
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
54
+ end
55
+
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
+ Capybara.server.call(Identify.new(@app), @port)
66
+ end
67
+
68
+ Capybara.timeout(Capybara.server_boot_timeout) do
69
+ if responsive? then true else sleep(0.5) and false end
70
+ end
71
+ end
72
+ end
73
+ rescue TimeoutError
74
+ puts "Rack application timed out during boot"
75
+ exit
76
+ else
77
+ self
78
+ end
79
+
80
+ private
81
+
82
+ def find_available_port
83
+ server = TCPServer.new('127.0.0.1', 0)
84
+ server.addr[1]
85
+ ensure
86
+ server.close if server
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,281 @@
1
+ require 'capybara/util/timeout'
2
+
3
+ module Capybara
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
29
+ DSL_METHODS = [
30
+ :all, :first, :attach_file, :body, :check, :choose, :click_link_or_button, :click_button, :click_link, :current_url, :drag, :drag_to_location, :evaluate_script,
31
+ :field_labeled, :fill_in, :find, :find_button, :find_by_id, :find_field, :find_link, :has_content?, :has_css?,
32
+ :has_no_content?, :has_no_css?, :has_no_xpath?, :has_xpath?, :locate, :save_and_open_page, :select, :source, :uncheck,
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, :click, :has_selector?, :has_no_selector?, :click_on
36
+ ]
37
+
38
+ attr_reader :mode, :app
39
+
40
+ def initialize(mode, app=nil)
41
+ @mode = mode
42
+ @app = app
43
+ end
44
+
45
+ def driver
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)
52
+ end
53
+ end
54
+
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 (see Capybara::Node::Finders#all)
155
+ # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
156
+ #
157
+ def within(*args)
158
+ new_scope = find(*args)
159
+ begin
160
+ scopes.push(new_scope)
161
+ yield
162
+ ensure
163
+ scopes.pop
164
+ end
165
+ end
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
+ #
173
+ def within_fieldset(locator)
174
+ within :xpath, XPath::HTML.fieldset(locator) do
175
+ yield
176
+ end
177
+ end
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
+ #
185
+ def within_table(locator)
186
+ within :xpath, XPath::HTML.table(locator) do
187
+ yield
188
+ end
189
+ end
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
+ #
198
+ def within_frame(frame_id)
199
+ driver.within_frame(frame_id) do
200
+ yield
201
+ end
202
+ end
203
+
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)
213
+ end
214
+
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 }
223
+ end
224
+
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)
235
+ end
236
+
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)
248
+ end
249
+
250
+ ##
251
+ #
252
+ # Save a snapshot of the page and open it in a browser for inspection
253
+ #
254
+ def save_and_open_page
255
+ require 'capybara/util/save_and_open_page'
256
+ Capybara.save_and_open_page(body)
257
+ end
258
+
259
+ def document
260
+ Capybara::Node::Document.new(self, driver)
261
+ end
262
+
263
+ def method_missing(*args)
264
+ current_node.send(*args)
265
+ end
266
+
267
+ def respond_to?(method)
268
+ super || current_node.respond_to?(method)
269
+ end
270
+
271
+ private
272
+
273
+ def current_node
274
+ scopes.last
275
+ end
276
+
277
+ def scopes
278
+ @scopes ||= [document]
279
+ end
280
+ end
281
+ end