bbc-capybara 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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