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
* Added DSL for acceptance tests, inspired by Luismi Cavallé's Steak [Jonas Nicklas] * 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 ### 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 Selenium RC. Provided Firefox is installed, everything is set up for you, and
you should be able to start using Selenium right away. 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
Celerity only runs on JRuby, so you'll need to install the celerity gem under Celerity only runs on JRuby, so you'll need to install the celerity gem under

View file

@ -1,6 +1,13 @@
require 'selenium-webdriver' require 'selenium-webdriver'
class Capybara::Driver::Selenium < Capybara::Driver::Base 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 class Node < Capybara::Driver::Node
def text def text
native.text native.text
@ -26,32 +33,34 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
def set(value) def set(value)
if tag_name == 'input' and type == 'radio' if tag_name == 'input' and type == 'radio'
native.click click
elsif tag_name == 'input' and type == 'checkbox' 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' elsif tag_name == 'textarea' or tag_name == 'input'
resynchronize do
native.clear native.clear
native.send_keys(value.to_s) native.send_keys(value.to_s)
end end
end end
end
def select_option def select_option
native.select resynchronize { native.select }
end end
def unselect_option def unselect_option
if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true' if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true'
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
end end
native.toggle if selected? resynchronize { native.toggle } if selected?
end end
def click def click
native.click resynchronize { native.click }
end end
def drag_to(element) def drag_to(element)
native.drag_and_drop_on(element.native) resynchronize { native.drag_and_drop_on(element.native) }
end end
def tag_name def tag_name
@ -76,6 +85,10 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
private private
def resynchronize
driver.resynchronize { yield }
end
# a reference to the select node if this is an option node # a reference to the select node if this is an option node
def select_node def select_node
find('./ancestor::select').first find('./ancestor::select').first
@ -91,7 +104,7 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
def browser def browser
unless @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 at_exit do
@browser.quit @browser.quit
end end
@ -101,7 +114,7 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
def initialize(app, options={}) def initialize(app, options={})
@app = app @app = app
@options = options @options = DEFAULT_OPTIONS.merge(options)
@rack_server = Capybara::Server.new(@app) @rack_server = Capybara::Server.new(@app)
@rack_server.boot if Capybara.run_server @rack_server.boot if Capybara.run_server
end end
@ -128,6 +141,18 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
def wait?; true; end 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) def execute_script(script)
browser.execute_script script browser.execute_script script
end end
@ -148,12 +173,57 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
browser.switch_to.window old_window browser.switch_to.window old_window
end 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) browser.switch_to.window(handle, &blk)
end end
private 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) def url(path)
rack_server.url(path) rack_server.url(path)
end end

View file

@ -127,6 +127,42 @@ shared_examples_for "driver with javascript support" do
@driver.evaluate_script('1+1').should == 2 @driver.evaluate_script('1+1').should == 2
end end
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 end
shared_examples_for "driver with header support" do shared_examples_for "driver with header support" do

View file

@ -25,9 +25,14 @@ $(function() {
}, 500); }, 500);
}); });
$('#with_focus_event').focus(function() { $('#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() { $('#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 redirect back
end end
get '/slow_response' do
sleep 2
'Finally!'
end
get '/set_cookie' do get '/set_cookie' do
cookie_value = 'test_cookie' cookie_value = 'test_cookie'
response.set_cookie('capybara', cookie_value) response.set_cookie('capybara', cookie_value)

View file

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

View file

@ -4,7 +4,7 @@ module Capybara
## ##
# Provides timeout similar to standard library Timeout, but avoids threads # 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 start_time = Time.now
result = nil result = nil
@ -14,7 +14,7 @@ module Capybara
delay = seconds - (Time.now - start_time) delay = seconds - (Time.now - start_time)
if delay <= 0 if delay <= 0
raise TimeoutError raise TimeoutError, error_message || "timed out"
end end
driver && driver.wait_until(delay) 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"
it_should_behave_like "driver with javascript support" 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 frame support"
it_should_behave_like "driver with support for window switching" it_should_behave_like "driver with support for window switching"
it_should_behave_like "driver without status code support" it_should_behave_like "driver without status code support"