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:
parent
6f5c1c330e
commit
021b87f6ba
9 changed files with 142 additions and 15 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
native.clear
|
||||
native.send_keys(value.to_s)
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>');
|
||||
}});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue