bbc-capybara 1.1.2

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 (107) hide show
  1. data/History.txt +325 -0
  2. data/License.txt +22 -0
  3. data/README.md +815 -0
  4. data/lib/capybara.rb +368 -0
  5. data/lib/capybara/cucumber.rb +25 -0
  6. data/lib/capybara/driver/base.rb +64 -0
  7. data/lib/capybara/driver/node.rb +74 -0
  8. data/lib/capybara/dsl.rb +50 -0
  9. data/lib/capybara/node/actions.rb +146 -0
  10. data/lib/capybara/node/base.rb +63 -0
  11. data/lib/capybara/node/document.rb +25 -0
  12. data/lib/capybara/node/element.rb +201 -0
  13. data/lib/capybara/node/finders.rb +154 -0
  14. data/lib/capybara/node/matchers.rb +442 -0
  15. data/lib/capybara/node/simple.rb +132 -0
  16. data/lib/capybara/query.rb +63 -0
  17. data/lib/capybara/rack_test/browser.rb +126 -0
  18. data/lib/capybara/rack_test/driver.rb +80 -0
  19. data/lib/capybara/rack_test/form.rb +80 -0
  20. data/lib/capybara/rack_test/node.rb +121 -0
  21. data/lib/capybara/rails.rb +17 -0
  22. data/lib/capybara/rspec.rb +26 -0
  23. data/lib/capybara/rspec/features.rb +22 -0
  24. data/lib/capybara/rspec/matchers.rb +152 -0
  25. data/lib/capybara/selector.rb +156 -0
  26. data/lib/capybara/selenium/driver.rb +169 -0
  27. data/lib/capybara/selenium/node.rb +91 -0
  28. data/lib/capybara/server.rb +87 -0
  29. data/lib/capybara/session.rb +322 -0
  30. data/lib/capybara/spec/driver.rb +329 -0
  31. data/lib/capybara/spec/fixtures/capybara.jpg +3 -0
  32. data/lib/capybara/spec/fixtures/test_file.txt +1 -0
  33. data/lib/capybara/spec/public/jquery-ui.js +791 -0
  34. data/lib/capybara/spec/public/jquery.js +9046 -0
  35. data/lib/capybara/spec/public/test.js +43 -0
  36. data/lib/capybara/spec/session.rb +148 -0
  37. data/lib/capybara/spec/session/all_spec.rb +78 -0
  38. data/lib/capybara/spec/session/attach_file_spec.rb +76 -0
  39. data/lib/capybara/spec/session/check_spec.rb +68 -0
  40. data/lib/capybara/spec/session/choose_spec.rb +29 -0
  41. data/lib/capybara/spec/session/click_button_spec.rb +305 -0
  42. data/lib/capybara/spec/session/click_link_or_button_spec.rb +37 -0
  43. data/lib/capybara/spec/session/click_link_spec.rb +120 -0
  44. data/lib/capybara/spec/session/current_url_spec.rb +83 -0
  45. data/lib/capybara/spec/session/fill_in_spec.rb +127 -0
  46. data/lib/capybara/spec/session/find_button_spec.rb +18 -0
  47. data/lib/capybara/spec/session/find_by_id_spec.rb +18 -0
  48. data/lib/capybara/spec/session/find_field_spec.rb +26 -0
  49. data/lib/capybara/spec/session/find_link_spec.rb +19 -0
  50. data/lib/capybara/spec/session/find_spec.rb +168 -0
  51. data/lib/capybara/spec/session/first_spec.rb +105 -0
  52. data/lib/capybara/spec/session/has_button_spec.rb +32 -0
  53. data/lib/capybara/spec/session/has_css_spec.rb +243 -0
  54. data/lib/capybara/spec/session/has_field_spec.rb +192 -0
  55. data/lib/capybara/spec/session/has_link_spec.rb +37 -0
  56. data/lib/capybara/spec/session/has_select_spec.rb +129 -0
  57. data/lib/capybara/spec/session/has_selector_spec.rb +129 -0
  58. data/lib/capybara/spec/session/has_table_spec.rb +34 -0
  59. data/lib/capybara/spec/session/has_text_spec.rb +138 -0
  60. data/lib/capybara/spec/session/has_xpath_spec.rb +123 -0
  61. data/lib/capybara/spec/session/headers.rb +19 -0
  62. data/lib/capybara/spec/session/javascript.rb +312 -0
  63. data/lib/capybara/spec/session/response_code.rb +19 -0
  64. data/lib/capybara/spec/session/select_spec.rb +119 -0
  65. data/lib/capybara/spec/session/text_spec.rb +24 -0
  66. data/lib/capybara/spec/session/uncheck_spec.rb +21 -0
  67. data/lib/capybara/spec/session/unselect_spec.rb +67 -0
  68. data/lib/capybara/spec/session/within_spec.rb +178 -0
  69. data/lib/capybara/spec/test_app.rb +156 -0
  70. data/lib/capybara/spec/views/buttons.erb +4 -0
  71. data/lib/capybara/spec/views/fieldsets.erb +29 -0
  72. data/lib/capybara/spec/views/form.erb +366 -0
  73. data/lib/capybara/spec/views/frame_one.erb +8 -0
  74. data/lib/capybara/spec/views/frame_two.erb +8 -0
  75. data/lib/capybara/spec/views/header_links.erb +7 -0
  76. data/lib/capybara/spec/views/host_links.erb +12 -0
  77. data/lib/capybara/spec/views/popup_one.erb +8 -0
  78. data/lib/capybara/spec/views/popup_two.erb +8 -0
  79. data/lib/capybara/spec/views/postback.erb +13 -0
  80. data/lib/capybara/spec/views/tables.erb +62 -0
  81. data/lib/capybara/spec/views/with_html.erb +75 -0
  82. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  83. data/lib/capybara/spec/views/with_js.erb +53 -0
  84. data/lib/capybara/spec/views/with_scope.erb +36 -0
  85. data/lib/capybara/spec/views/with_simple_html.erb +1 -0
  86. data/lib/capybara/spec/views/within_frames.erb +10 -0
  87. data/lib/capybara/spec/views/within_popups.erb +25 -0
  88. data/lib/capybara/util/save_and_open_page.rb +45 -0
  89. data/lib/capybara/util/timeout.rb +27 -0
  90. data/lib/capybara/version.rb +3 -0
  91. data/spec/basic_node_spec.rb +89 -0
  92. data/spec/capybara_spec.rb +46 -0
  93. data/spec/driver/rack_test_driver_spec.rb +90 -0
  94. data/spec/driver/selenium_driver_spec.rb +55 -0
  95. data/spec/dsl_spec.rb +255 -0
  96. data/spec/fixtures/selenium_driver_rspec_failure.rb +8 -0
  97. data/spec/fixtures/selenium_driver_rspec_success.rb +8 -0
  98. data/spec/rspec/features_spec.rb +43 -0
  99. data/spec/rspec/matchers_spec.rb +564 -0
  100. data/spec/rspec_spec.rb +51 -0
  101. data/spec/save_and_open_page_spec.rb +155 -0
  102. data/spec/server_spec.rb +74 -0
  103. data/spec/session/rack_test_session_spec.rb +61 -0
  104. data/spec/session/selenium_session_spec.rb +26 -0
  105. data/spec/spec_helper.rb +31 -0
  106. data/spec/timeout_spec.rb +28 -0
  107. metadata +297 -0
@@ -0,0 +1,169 @@
1
+ require 'selenium-webdriver'
2
+
3
+ class Capybara::Selenium::Driver < Capybara::Driver::Base
4
+ DEFAULT_OPTIONS = {
5
+ :resynchronize => false,
6
+ :resynchronization_timeout => 10,
7
+ :browser => :firefox
8
+ }
9
+ SPECIAL_OPTIONS = [:browser, :resynchronize, :resynchronization_timeout]
10
+
11
+ attr_reader :app, :rack_server, :options
12
+
13
+ def browser
14
+ unless @browser
15
+ @browser = Selenium::WebDriver.for(options[:browser], options.reject { |key,val| SPECIAL_OPTIONS.include?(key) })
16
+
17
+ main = Process.pid
18
+ at_exit do
19
+ # Store the exit status of the test run since it goes away after calling the at_exit proc...
20
+ @exit_status = $!.status if $!.is_a?(SystemExit)
21
+ quit if Process.pid == main
22
+ exit @exit_status if @exit_status # Force exit with stored status
23
+ end
24
+ end
25
+ @browser
26
+ end
27
+
28
+ def initialize(app, options={})
29
+ @app = app
30
+ @browser = nil
31
+ @exit_status = nil
32
+ @options = DEFAULT_OPTIONS.merge(options)
33
+ @rack_server = Capybara::Server.new(@app)
34
+ @rack_server.boot if Capybara.run_server
35
+ end
36
+
37
+ def visit(path)
38
+ browser.navigate.to(url(path))
39
+ end
40
+
41
+ def source
42
+ browser.page_source
43
+ end
44
+
45
+ def body
46
+ browser.page_source
47
+ end
48
+
49
+ def current_url
50
+ browser.current_url
51
+ end
52
+
53
+ def set_orientation(orientation)
54
+ browser.rotation(orientation)
55
+ end
56
+
57
+ def find(selector)
58
+ browser.find_elements(:xpath, selector).map { |node| Capybara::Selenium::Node.new(self, node) }
59
+ end
60
+
61
+ def wait?; true; end
62
+
63
+ def resynchronize
64
+ if options[:resynchronize]
65
+ load_wait_for_ajax_support
66
+ yield
67
+ Capybara.timeout(options[:resynchronization_timeout], self, "failed to resynchronize, ajax request timed out") do
68
+ evaluate_script("!window.capybaraRequestsOutstanding")
69
+ end
70
+ else
71
+ yield
72
+ end
73
+ end
74
+
75
+ def execute_script(script)
76
+ browser.execute_script script
77
+ end
78
+
79
+ def evaluate_script(script)
80
+ browser.execute_script "return #{script}"
81
+ end
82
+
83
+ def reset!
84
+ # Use instance variable directly so we avoid starting the browser just to reset the session
85
+ if @browser
86
+ begin
87
+ @browser.manage.delete_all_cookies
88
+ rescue Selenium::WebDriver::Error::UnhandledError
89
+ # delete_all_cookies fails when we've previously gone
90
+ # to about:blank, so we rescue this error and do nothing
91
+ # instead.
92
+ end
93
+ @browser.navigate.to('about:blank')
94
+ end
95
+ end
96
+
97
+ def within_frame(frame_id)
98
+ old_window = browser.window_handle
99
+ browser.switch_to.frame(frame_id)
100
+ yield
101
+ browser.switch_to.window old_window
102
+ end
103
+
104
+ def find_window( selector )
105
+ original_handle = browser.window_handle
106
+ browser.window_handles.each do |handle|
107
+ browser.switch_to.window handle
108
+ if( selector == browser.execute_script("return window.name") ||
109
+ browser.title.include?(selector) ||
110
+ browser.current_url.include?(selector) ||
111
+ (selector == handle) )
112
+ browser.switch_to.window original_handle
113
+ return handle
114
+ end
115
+ end
116
+ raise Capybara::ElementNotFound, "Could not find a window identified by #{selector}"
117
+ end
118
+
119
+ def within_window(selector, &blk)
120
+ handle = find_window( selector )
121
+ browser.switch_to.window(handle, &blk)
122
+ end
123
+
124
+ def quit
125
+ @browser.quit
126
+ rescue Errno::ECONNREFUSED
127
+ # Browser must have already gone
128
+ end
129
+
130
+ def invalid_element_errors
131
+ [Selenium::WebDriver::Error::ObsoleteElementError, Selenium::WebDriver::Error::UnhandledError]
132
+ end
133
+
134
+ private
135
+
136
+ def load_wait_for_ajax_support
137
+ browser.execute_script <<-JS
138
+ window.capybaraRequestsOutstanding = 0;
139
+ (function() { // Overriding XMLHttpRequest
140
+ var oldXHR = window.XMLHttpRequest;
141
+
142
+ function newXHR() {
143
+ var realXHR = new oldXHR();
144
+
145
+ window.capybaraRequestsOutstanding++;
146
+ realXHR.addEventListener("readystatechange", function() {
147
+ if( realXHR.readyState == 4 ) {
148
+ setTimeout( function() {
149
+ window.capybaraRequestsOutstanding--;
150
+ if(window.capybaraRequestsOutstanding < 0) {
151
+ window.capybaraRequestsOutstanding = 0;
152
+ }
153
+ }, 500 );
154
+ }
155
+ }, false);
156
+
157
+ return realXHR;
158
+ }
159
+
160
+ window.XMLHttpRequest = newXHR;
161
+ })();
162
+ JS
163
+ end
164
+
165
+ def url(path)
166
+ rack_server.url(path)
167
+ end
168
+
169
+ end
@@ -0,0 +1,91 @@
1
+ class Capybara::Selenium::Node < Capybara::Driver::Node
2
+ def text
3
+ native.text
4
+ end
5
+
6
+ def [](name)
7
+ native.attribute(name.to_s)
8
+ rescue Selenium::WebDriver::Error::WebDriverError
9
+ nil
10
+ end
11
+
12
+ def value
13
+ if tag_name == "select" and self[:multiple] and not self[:multiple] == "false"
14
+ native.find_elements(:xpath, ".//option").select { |n| n.selected? }.map { |n| n[:value] || n.text }
15
+ else
16
+ native[:value]
17
+ end
18
+ end
19
+
20
+ def set(value)
21
+ if tag_name == 'input' and type == 'radio'
22
+ click
23
+ elsif tag_name == 'input' and type == 'checkbox'
24
+ click if value ^ native.attribute('checked').to_s.eql?("true")
25
+ elsif tag_name == 'input' and type == 'file'
26
+ resynchronize do
27
+ native.send_keys(value.to_s)
28
+ end
29
+ elsif tag_name == 'textarea' or tag_name == 'input'
30
+ resynchronize do
31
+ native.clear
32
+ native.send_keys(value.to_s)
33
+ end
34
+ end
35
+ end
36
+
37
+ def select_option
38
+ resynchronize { native.click } unless selected?
39
+ end
40
+
41
+ def unselect_option
42
+ if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true'
43
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
44
+ end
45
+ resynchronize { native.click } if selected?
46
+ end
47
+
48
+ def click
49
+ resynchronize { native.click }
50
+ end
51
+
52
+ def drag_to(element)
53
+ resynchronize { driver.browser.action.drag_and_drop(native, element.native).perform }
54
+ end
55
+
56
+ def tag_name
57
+ native.tag_name.downcase
58
+ end
59
+
60
+ def visible?
61
+ displayed = native.displayed?
62
+ displayed and displayed != "false"
63
+ end
64
+
65
+ def selected?
66
+ selected = native.selected?
67
+ selected and selected != "false"
68
+ end
69
+
70
+ alias :checked? :selected?
71
+
72
+ def find(locator)
73
+ native.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
74
+ end
75
+
76
+ private
77
+
78
+ def resynchronize
79
+ driver.resynchronize { yield }
80
+ end
81
+
82
+ # a reference to the select node if this is an option node
83
+ def select_node
84
+ find('./ancestor::select').first
85
+ end
86
+
87
+ def type
88
+ self[:type]
89
+ end
90
+
91
+ end
@@ -0,0 +1,87 @@
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
+ Capybara.server_host || "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(60) { responsive? }
69
+ end
70
+ end
71
+ rescue TimeoutError
72
+ raise "Rack application timed out during boot"
73
+ else
74
+ self
75
+ end
76
+
77
+ private
78
+
79
+ def find_available_port
80
+ server = TCPServer.new('127.0.0.1', 0)
81
+ server.addr[1]
82
+ ensure
83
+ server.close if server
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,322 @@
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
+ NODE_METHODS = [
30
+ :all, :first, :attach_file, :text, :check, :choose,
31
+ :click_link_or_button, :click_button, :click_link, :field_labeled,
32
+ :fill_in, :find, :find_button, :find_by_id, :find_field, :find_link,
33
+ :has_content?, :has_text?, :has_css?, :has_no_content?, :has_no_text?,
34
+ :has_no_css?, :has_no_xpath?,
35
+ :has_xpath?, :select, :uncheck, :has_link?, :has_no_link?, :has_button?,
36
+ :has_no_button?, :has_field?, :has_no_field?, :has_checked_field?,
37
+ :has_unchecked_field?, :has_no_table?, :has_table?, :unselect,
38
+ :has_select?, :has_no_select?, :has_selector?, :has_no_selector?,
39
+ :click_on, :has_no_checked_field?, :has_no_unchecked_field?
40
+ ]
41
+ SESSION_METHODS = [
42
+ :body, :html, :current_url, :current_host, :evaluate_script, :source,
43
+ :visit, :wait_until, :within, :within_fieldset, :within_table,
44
+ :within_frame, :within_window, :current_path, :save_page,
45
+ :save_and_open_page, :reset_session!
46
+ ]
47
+ DSL_METHODS = NODE_METHODS + SESSION_METHODS
48
+
49
+ attr_reader :mode, :app
50
+
51
+ def initialize(mode, app=nil)
52
+ @mode = mode
53
+ @app = app
54
+ end
55
+
56
+ def driver
57
+ @driver ||= begin
58
+ unless Capybara.drivers.has_key?(mode)
59
+ other_drivers = Capybara.drivers.keys.map { |key| key.inspect }
60
+ raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
61
+ end
62
+ Capybara.drivers[mode].call(app)
63
+ end
64
+ end
65
+
66
+ ##
67
+ #
68
+ # Reset the session, removing all cookies.
69
+ #
70
+ def reset!
71
+ driver.reset!
72
+ end
73
+ alias_method :cleanup!, :reset!
74
+ alias_method :reset_session!, :reset!
75
+
76
+ ##
77
+ #
78
+ # Returns a hash of response headers. Not supported by all drivers (e.g. Selenium)
79
+ #
80
+ # @return [Hash{String => String}] A hash of response headers.
81
+ #
82
+ def response_headers
83
+ driver.response_headers
84
+ end
85
+
86
+ ##
87
+ #
88
+ # Returns the current HTTP status code as an Integer. Not supported by all drivers (e.g. Selenium)
89
+ #
90
+ # @return [Integer] Current HTTP status code
91
+ #
92
+ def status_code
93
+ driver.status_code
94
+ end
95
+
96
+ ##
97
+ #
98
+ # @return [String] A snapshot of the HTML of the current document, as it looks right now (potentially modified by JavaScript).
99
+ #
100
+ def html
101
+ driver.body
102
+ end
103
+
104
+ ##
105
+ #
106
+ # @return [String] HTML source of the document, before being modified by JavaScript.
107
+ #
108
+ def source
109
+ driver.source
110
+ end
111
+ alias_method :body, :source
112
+
113
+ ##
114
+ #
115
+ # @return [String] Path of the current page, without any domain information
116
+ #
117
+ def current_path
118
+ path = URI.parse(current_url).path
119
+ path if path and not path.empty?
120
+ end
121
+
122
+ ##
123
+ #
124
+ # @return [String] Host of the current page
125
+ #
126
+ def current_host
127
+ uri = URI.parse(current_url)
128
+ "#{uri.scheme}://#{uri.host}" if uri.host
129
+ end
130
+
131
+ ##
132
+ #
133
+ # @return [String] Fully qualified URL of the current page
134
+ #
135
+ def current_url
136
+ driver.current_url
137
+ end
138
+
139
+ ##
140
+ #
141
+ # Navigate to the given URL. The URL can either be a relative URL or an absolute URL
142
+ # The behaviour of either depends on the driver.
143
+ #
144
+ # session.visit('/foo')
145
+ # session.visit('http://google.com')
146
+ #
147
+ # For drivers which can run against an external application, such as culerity and selenium
148
+ # giving an absolute URL will navigate to that page. This allows testing applications
149
+ # running on remote servers. For these drivers, setting Capybara.app_host will make the
150
+ # remote server the default. For example:
151
+ #
152
+ # Capybara.app_host = 'http://google.com'
153
+ # session.visit('/') # visits the google homepage
154
+ #
155
+ # @param [String] url The URL to navigate to
156
+ #
157
+ def visit(url)
158
+ driver.visit(url)
159
+ end
160
+
161
+ ##
162
+ #
163
+ # Execute the given block for a particular scope on the page. Within will find the first
164
+ # element matching the given selector and execute the block scoped to that element:
165
+ #
166
+ # within(:xpath, '//div[@id="delivery-address"]') do
167
+ # fill_in('Street', :with => '12 Main Street')
168
+ # end
169
+ #
170
+ # It is possible to omit the first parameter, in that case, the selector is assumed to be
171
+ # of the type set in Capybara.default_selector.
172
+ #
173
+ # within('div#delivery-address') do
174
+ # fill_in('Street', :with => '12 Main Street')
175
+ # end
176
+ #
177
+ # @overload within(*find_args)
178
+ # @param (see Capybara::Node::Finders#all)
179
+ #
180
+ # @overload within(a_node)
181
+ # @param [Capybara::Node::Base] a_node The node in whose scope the block should be evaluated
182
+ #
183
+ # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
184
+ #
185
+ def within(*args)
186
+ new_scope = if args.size == 1 && Capybara::Node::Base === args.first
187
+ args.first
188
+ else
189
+ find(*args)
190
+ end
191
+ begin
192
+ scopes.push(new_scope)
193
+ yield
194
+ ensure
195
+ scopes.pop
196
+ end
197
+ end
198
+
199
+ ##
200
+ #
201
+ # Execute the given block within the a specific fieldset given the id or legend of that fieldset.
202
+ #
203
+ # @param [String] locator Id or legend of the fieldset
204
+ #
205
+ def within_fieldset(locator)
206
+ within :fieldset, locator do
207
+ yield
208
+ end
209
+ end
210
+
211
+ ##
212
+ #
213
+ # Execute the given block within the a specific table given the id or caption of that table.
214
+ #
215
+ # @param [String] locator Id or caption of the table
216
+ #
217
+ def within_table(locator)
218
+ within :table, locator do
219
+ yield
220
+ end
221
+ end
222
+
223
+ ##
224
+ #
225
+ # Execute the given block within the given iframe given the id of that iframe. Only works on
226
+ # some drivers (e.g. Selenium)
227
+ #
228
+ # @param [String] locator Id of the frame
229
+ #
230
+ def within_frame(frame_id)
231
+ driver.within_frame(frame_id) do
232
+ yield
233
+ end
234
+ end
235
+
236
+ ##
237
+ #
238
+ # Execute the given block within the given window. Only works on
239
+ # some drivers (e.g. Selenium)
240
+ #
241
+ # @param [String] locator of the window
242
+ #
243
+ def within_window(handle, &blk)
244
+ driver.within_window(handle, &blk)
245
+ end
246
+
247
+ ##
248
+ #
249
+ # Retry executing the block until a truthy result is returned or the timeout time is exceeded
250
+ #
251
+ # @param [Integer] timeout The amount of seconds to retry executing the given block
252
+ #
253
+ def wait_until(timeout = Capybara.default_wait_time)
254
+ Capybara.timeout(timeout,driver) { yield }
255
+ end
256
+
257
+ ##
258
+ #
259
+ # Execute the given script, not returning a result. This is useful for scripts that return
260
+ # complex objects, such as jQuery statements. +execute_script+ should be used over
261
+ # +evaluate_script+ whenever possible.
262
+ #
263
+ # @param [String] script A string of JavaScript to execute
264
+ #
265
+ def execute_script(script)
266
+ driver.execute_script(script)
267
+ end
268
+
269
+ ##
270
+ #
271
+ # Evaluate the given JavaScript and return the result. Be careful when using this with
272
+ # scripts that return complex objects, such as jQuery statements. +execute_script+ might
273
+ # be a better alternative.
274
+ #
275
+ # @param [String] script A string of JavaScript to evaluate
276
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
277
+ #
278
+ def evaluate_script(script)
279
+ driver.evaluate_script(script)
280
+ end
281
+
282
+ ##
283
+ #
284
+ # Save a snapshot of the page and open it in a browser for inspection
285
+ #
286
+ def save_page
287
+ require 'capybara/util/save_and_open_page'
288
+ Capybara.save_page(body)
289
+ end
290
+
291
+ def save_and_open_page
292
+ require 'capybara/util/save_and_open_page'
293
+ Capybara.save_and_open_page(body)
294
+ end
295
+
296
+ def document
297
+ @document ||= Capybara::Node::Document.new(self, driver)
298
+ end
299
+
300
+ NODE_METHODS.each do |method|
301
+ class_eval <<-RUBY
302
+ def #{method}(*args, &block)
303
+ current_node.send(:#{method}, *args, &block)
304
+ end
305
+ RUBY
306
+ end
307
+
308
+ def inspect
309
+ %(#<Capybara::Session>)
310
+ end
311
+
312
+ private
313
+
314
+ def current_node
315
+ scopes.last
316
+ end
317
+
318
+ def scopes
319
+ @scopes ||= [document]
320
+ end
321
+ end
322
+ end