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
|
||||||
|
|
||||||
* 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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>');
|
||||||
|
}});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue