1
0
Fork 0
mirror of https://github.com/teamcapybara/capybara.git synced 2022-11-09 12:08:07 -05:00

Selenium driver automatically waits for AJAX requests to finish

This commit is contained in:
Jonas Nicklas and Nicklas Ramhöj 2011-02-22 15:53:14 +01:00 committed by Jonas Nicklas and Nicklas Ramhöj
parent 6f5c1c330e
commit 021b87f6ba
9 changed files with 142 additions and 15 deletions

View file

@ -5,6 +5,7 @@ Release date:
### Added
* Added DSL for acceptance tests, inspired by Luismi Cavallé's Steak [Jonas Nicklas]
* Selenium driver automatically waits for AJAX requests to finish [mgiambalvo, Nicklas Ramhöj and Jonas Nicklas]
### Changed

View file

@ -174,6 +174,11 @@ At the moment, Capybara supports Webdriver, also called Selenium 2.0, *not*
Selenium RC. Provided Firefox is installed, everything is set up for you, and
you should be able to start using Selenium right away.
By default Capybara tried to synchronize AJAX requests, so it will wait for
AJAX requests to finish after you've interacted with the page. You can switch
off this behaviour by setting the driver option <tt>:resynchronize</tt> to
<tt>false</tt>. See the section on configuring drivers.
== Celerity
Celerity only runs on JRuby, so you'll need to install the celerity gem under

View file

@ -1,6 +1,13 @@
require 'selenium-webdriver'
class Capybara::Driver::Selenium < Capybara::Driver::Base
DEFAULT_OPTIONS = {
:resynchronize => true,
:resynchronization_timeout => 10,
:browser => :firefox
}
SPECIAL_OPTIONS = [:browser, :resynchronize, :resynchronization_timeout]
class Node < Capybara::Driver::Node
def text
native.text
@ -26,32 +33,34 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
def set(value)
if tag_name == 'input' and type == 'radio'
native.click
click
elsif tag_name == 'input' and type == 'checkbox'
native.click if value ^ native.attribute('checked').to_s.eql?("true")
click if value ^ native.attribute('checked').to_s.eql?("true")
elsif tag_name == 'textarea' or tag_name == 'input'
resynchronize do
native.clear
native.send_keys(value.to_s)
end
end
end
def select_option
native.select
resynchronize { native.select }
end
def unselect_option
if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true'
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
end
native.toggle if selected?
resynchronize { native.toggle } if selected?
end
def click
native.click
resynchronize { native.click }
end
def drag_to(element)
native.drag_and_drop_on(element.native)
resynchronize { native.drag_and_drop_on(element.native) }
end
def tag_name
@ -76,6 +85,10 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
private
def resynchronize
driver.resynchronize { yield }
end
# a reference to the select node if this is an option node
def select_node
find('./ancestor::select').first
@ -91,7 +104,7 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
def browser
unless @browser
@browser = Selenium::WebDriver.for(options[:browser] || :firefox, options.reject{|key,val| key == :browser})
@browser = Selenium::WebDriver.for(options[:browser], options.reject { |key,val| SPECIAL_OPTIONS.include?(key) })
at_exit do
@browser.quit
end
@ -101,7 +114,7 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
def initialize(app, options={})
@app = app
@options = options
@options = DEFAULT_OPTIONS.merge(options)
@rack_server = Capybara::Server.new(@app)
@rack_server.boot if Capybara.run_server
end
@ -128,6 +141,18 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
def wait?; true; end
def resynchronize
if options[:resynchronize]
load_wait_for_ajax_support
yield
Capybara.timeout(options[:resynchronization_timeout], self, "failed to resynchronize, ajax request timed out") do
evaluate_script("!window.capybaraRequestsOutstanding")
end
else
yield
end
end
def execute_script(script)
browser.execute_script script
end
@ -148,12 +173,57 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
browser.switch_to.window old_window
end
def within_window(handle, &blk)
def find_window( selector )
original_handle = browser.window_handle
browser.window_handles.each do |handle|
browser.switch_to.window handle
if( selector == browser.execute_script("return window.name") ||
browser.title.include?(selector) ||
browser.current_url.include?(selector) ||
(selector == handle) )
browser.switch_to.window original_handle
return handle
end
end
raise Capybara::ElementNotFound, "Could not find a window identified by #{selector}"
end
def within_window(selector, &blk)
handle = find_window( selector )
browser.switch_to.window(handle, &blk)
end
private
def load_wait_for_ajax_support
browser.execute_script <<-JS
window.capybaraRequestsOutstanding = 0;
(function() { // Overriding XMLHttpRequest
var oldXHR = window.XMLHttpRequest;
function newXHR() {
var realXHR = new oldXHR();
window.capybaraRequestsOutstanding++;
realXHR.addEventListener("readystatechange", function() {
if( realXHR.readyState == 4 ) {
setTimeout( function() {
window.capybaraRequestsOutstanding--;
if(window.capybaraRequestsOutstanding < 0) {
window.capybaraRequestsOutstanding = 0;
}
}, 500 );
}
}, false);
return realXHR;
}
window.XMLHttpRequest = newXHR;
})();
JS
end
def url(path)
rack_server.url(path)
end

View file

@ -127,6 +127,42 @@ shared_examples_for "driver with javascript support" do
@driver.evaluate_script('1+1').should == 2
end
end
end
shared_examples_for "driver with resynchronization support" do
before { @driver.visit('/with_js') }
describe "#find" do
context "with synchronization turned on" do
it "should wait for all ajax requests to finish" do
@driver.find('//input[@id="fire_ajax_request"]').first.click
@driver.find('//p[@id="ajax_request_done"]').should_not be_empty
end
end
context "with resynchronization turned off" do
before { @driver.options[:resynchronize] = false }
it "should not wait for ajax requests to finish" do
@driver.find('//input[@id="fire_ajax_request"]').first.click
@driver.find('//p[@id="ajax_request_done"]').should be_empty
end
after { @driver.options[:resynchronize] = true }
end
context "with short synchronization timeout" do
before { @driver.options[:resynchronization_timeout] = 0.1 }
it "should raise an error" do
expect do
@driver.find('//input[@id="fire_ajax_request"]').first.click
end.to raise_error(Capybara::TimeoutError, "failed to resynchronize, ajax request timed out")
end
after { @driver.options[:resynchronization_timeout] = 10 }
end
end
end
shared_examples_for "driver with header support" do

View file

@ -25,9 +25,14 @@ $(function() {
}, 500);
});
$('#with_focus_event').focus(function() {
$('body').append('<p id="focus_event_triggered">Focus Event triggered</p>')
$('body').append('<p id="focus_event_triggered">Focus Event triggered</p>');
});
$('#checkbox_with_event').click(function() {
$('body').append('<p id="checkbox_event_triggered">Checkbox event triggered</p>')
$('body').append('<p id="checkbox_event_triggered">Checkbox event triggered</p>');
});
$('#fire_ajax_request').click(function() {
$.ajax({url: "/slow_response", context: document.body, success: function() {
$('body').append('<p id="ajax_request_done">Ajax request done</p>');
}});
});
});

View file

@ -59,6 +59,11 @@ class TestApp < Sinatra::Base
redirect back
end
get '/slow_response' do
sleep 2
'Finally!'
end
get '/set_cookie' do
cookie_value = 'test_cookie'
response.set_cookie('capybara', cookie_value)

View file

@ -34,6 +34,10 @@
<p>
<input type="checkbox" id="checkbox_with_event"/>
</p>
<p>
<input type="submit" id="fire_ajax_request" value="Fire Ajax Request"/>
</p>
</body>
</html>

View file

@ -4,7 +4,7 @@ module Capybara
##
# Provides timeout similar to standard library Timeout, but avoids threads
#
def timeout(seconds = 1, driver = nil, &block)
def timeout(seconds = 1, driver = nil, error_message = nil, &block)
start_time = Time.now
result = nil
@ -14,7 +14,7 @@ module Capybara
delay = seconds - (Time.now - start_time)
if delay <= 0
raise TimeoutError
raise TimeoutError, error_message || "timed out"
end
driver && driver.wait_until(delay)

View file

@ -7,6 +7,7 @@ describe Capybara::Driver::Selenium do
it_should_behave_like "driver"
it_should_behave_like "driver with javascript support"
it_should_behave_like "driver with resynchronization support"
it_should_behave_like "driver with frame support"
it_should_behave_like "driver with support for window switching"
it_should_behave_like "driver without status code support"