Merge branch 'async_is_my_bitch'

This commit is contained in:
Jonas Nicklas 2011-08-12 14:12:49 +02:00
commit 011bf108f7
13 changed files with 170 additions and 57 deletions

View File

@ -16,7 +16,7 @@ module Capybara
attr_accessor :asset_root, :app_host, :run_server, :default_host
attr_accessor :server_port, :server_boot_timeout
attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements, :prefer_visible_elements
attr_accessor :save_and_open_page_path
attr_accessor :save_and_open_page_path, :automatic_reload
##
#
@ -236,6 +236,7 @@ Capybara.configure do |config|
config.ignore_hidden_elements = false
config.prefer_visible_elements = true
config.default_host = "http://www.example.com"
config.automatic_reload = true
end
Capybara.register_driver :rack_test do |app|

View File

@ -43,6 +43,10 @@ class Capybara::Driver::Base
raise Capybara::NotSupportedByDriverError
end
def invalid_element_errors
[]
end
def wait?
false
end

View File

@ -22,7 +22,7 @@ module Capybara
# session.has_css?('#foobar') # from Capybara::Node::Matchers
#
class Base
attr_reader :session, :base
attr_reader :session, :base, :parent
include Capybara::Node::Finders
include Capybara::Node::Actions
@ -33,8 +33,27 @@ module Capybara
@base = base
end
def reload
self
end
protected
def wait_until(seconds=Capybara.default_wait_time)
start_time = Time.now
begin
yield
rescue => e
raise e unless wait?
raise e unless (driver.respond_to?(:invalid_element_errors) and driver.invalid_element_errors.include?(e.class)) or e.is_a?(Capybara::ElementNotFound)
raise e if (Time.now - start_time) >= seconds
sleep(0.05)
reload if Capybara.automatic_reload
retry
end
end
def wait?
driver.wait?
end

View File

@ -22,12 +22,18 @@ module Capybara
#
class Element < Base
def initialize(session, base, parent, selector)
super(session, base)
@parent = parent
@selector = selector
end
##
#
# @return [Object] The native element from the driver, this allows access to driver specific methods
#
def native
base.native
wait_until { base.native }
end
##
@ -35,7 +41,7 @@ module Capybara
# @return [String] The text of the element
#
def text
base.text
wait_until { base.text }
end
##
@ -48,7 +54,7 @@ module Capybara
# @return [String] The value of the attribute
#
def [](attribute)
base[attribute]
wait_until { base[attribute] }
end
##
@ -56,7 +62,7 @@ module Capybara
# @return [String] The value of the form element
#
def value
base.value
wait_until { base.value }
end
##
@ -66,7 +72,7 @@ module Capybara
# @param [String] value The new value
#
def set(value)
base.set(value)
wait_until { base.set(value) }
end
##
@ -74,7 +80,7 @@ module Capybara
# Select this node if is an option element inside a select tag
#
def select_option
base.select_option
wait_until { base.select_option }
end
##
@ -82,7 +88,7 @@ module Capybara
# Unselect this node if is an option element inside a multiple select tag
#
def unselect_option
base.unselect_option
wait_until { base.unselect_option }
end
##
@ -90,7 +96,7 @@ module Capybara
# Click the Element
#
def click
base.click
wait_until { base.click }
end
##
@ -98,7 +104,7 @@ module Capybara
# @return [String] The tag name of the element
#
def tag_name
base.tag_name
wait_until { base.tag_name }
end
##
@ -109,7 +115,7 @@ module Capybara
# @return [Boolean] Whether the element is visible
#
def visible?
base.visible?
wait_until { base.visible? }
end
##
@ -119,7 +125,7 @@ module Capybara
# @return [Boolean] Whether the element is checked
#
def checked?
base.checked?
wait_until { base.checked? }
end
##
@ -129,7 +135,7 @@ module Capybara
# @return [Boolean] Whether the element is selected
#
def selected?
base.selected?
wait_until { base.selected? }
end
##
@ -139,7 +145,7 @@ module Capybara
# @return [String] An XPath expression
#
def path
base.path
wait_until { base.path }
end
##
@ -150,7 +156,7 @@ module Capybara
# @param [String] event The name of the event to trigger
#
def trigger(event)
base.trigger(event)
wait_until { base.trigger(event) }
end
##
@ -164,7 +170,25 @@ module Capybara
# @param [Capybara::Element] node The element to drag to
#
def drag_to(node)
base.drag_to(node.base)
wait_until { base.drag_to(node.base) }
end
def find(*args)
wait_until { super }
end
def first(*args)
wait_until { super }
end
def all(*args)
wait_until { super }
end
def reload
reloaded = parent.reload.first(@selector.name, @selector.locator, @selector.options)
@base = reloaded.base if reloaded
self
end
def inspect
@ -172,7 +196,6 @@ module Capybara
rescue NotSupportedByDriverError
%(#<Capybara::Element tag="#{tag_name}">)
end
end
end
end

View File

@ -24,18 +24,7 @@ module Capybara
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires
#
def find(*args)
begin
node = wait_conditionally_until { first(*args) }
rescue TimeoutError
end
unless node
options = extract_normalized_options(args)
normalized = Capybara::Selector.normalize(*args)
message = options[:message] || "Unable to find #{normalized.name} #{normalized.locator.inspect}"
message = normalized.failure_message.call(self, normalized) if normalized.failure_message
raise Capybara::ElementNotFound, message
end
return node
wait_until { first(*args) or raise_find_error(*args) }
end
##
@ -120,10 +109,10 @@ module Capybara
def all(*args)
options = extract_normalized_options(args)
Capybara::Selector.normalize(*args).xpaths.
map { |path| find_in_base(path) }.flatten.
select { |node| matches_options(node, options) }.
map { |node| convert_element(node) }
selector = Capybara::Selector.normalize(*args)
selector.xpaths.
map { |path| find_in_base(selector, path) }.flatten.
select { |node| matches_options(node, options) }
end
##
@ -143,10 +132,11 @@ module Capybara
options = extract_normalized_options(args)
found_elements = []
Capybara::Selector.normalize(*args).xpaths.each do |path|
find_in_base(path).each do |node|
selector = Capybara::Selector.normalize(*args)
selector.xpaths.each do |path|
find_in_base(selector, path).each do |node|
if matches_options(node, options)
found_elements << convert_element(node)
found_elements << node
return found_elements.last if not Capybara.prefer_visible_elements or node.visible?
end
end
@ -156,16 +146,18 @@ module Capybara
protected
def find_in_base(xpath)
base.find(xpath)
def raise_find_error(*args)
options = extract_normalized_options(args)
normalized = Capybara::Selector.normalize(*args)
message = options[:message] || "Unable to find #{normalized.name} #{normalized.locator.inspect}"
message = normalized.failure_message.call(self, normalized) if normalized.failure_message
raise Capybara::ElementNotFound, message
end
def convert_element(element)
Capybara::Node::Element.new(session, element)
end
def wait_conditionally_until
if wait? then session.wait_until { yield } else yield end
def find_in_base(selector, xpath)
base.find(xpath).map do |node|
Capybara::Node::Element.new(session, node, self, selector)
end
end
def extract_normalized_options(args)
@ -197,7 +189,7 @@ module Capybara
end
def has_selected_options?(node, expected)
actual = node.find('.//option').select { |option| option.selected? }.map { |option| option.text }
actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
(expected - actual).empty?
end
end

View File

@ -120,14 +120,10 @@ module Capybara
protected
def find_in_base(xpath)
def find_in_base(selector, xpath)
native.xpath(xpath).map { |node| self.class.new(node) }
end
def convert_element(element)
element
end
def wait?
false
end

View File

@ -118,6 +118,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
# Browser must have already gone
end
def invalid_element_errors
[Selenium::WebDriver::Error::ObsoleteElementError]
end
private
def load_wait_for_ajax_support

View File

@ -284,7 +284,7 @@ module Capybara
end
def document
Capybara::Node::Document.new(self, driver)
@document ||= Capybara::Node::Document.new(self, driver)
end
NODE_METHODS.each do |method|

View File

@ -138,6 +138,7 @@ shared_examples_for "driver with resynchronization support" do
before { @driver.visit('/with_js') }
describe "#find" do
context "with synchronization turned on" do
before { @driver.options[:resynchronize] = true }
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
@ -146,16 +147,14 @@ shared_examples_for "driver with resynchronization support" do
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[:resynchronize] = true }
before { @driver.options[:resynchronization_timeout] = 0.1 }
it "should raise an error" do
@ -163,10 +162,11 @@ shared_examples_for "driver with resynchronization support" 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
after { @driver.options[:resynchronize] = false }
after { @driver.options[:resynchronization_timeout] = 10 }
end
shared_examples_for "driver with header support" do

View File

@ -35,4 +35,9 @@ $(function() {
$('body').append('<p id="ajax_request_done">Ajax request done</p>');
}});
});
$('#reload-link').click(function() {
setTimeout(function() {
$('#reload-me').replaceWith('<div id="reload-me"><em><a>RELOADED</a></em></div>');
}, 250)
});
});

View File

@ -32,6 +32,12 @@ shared_examples_for "find" do
it "should scope CSS selectors" do
@session.find(:css, '#second').should have_no_css('h1')
end
it "should have a reference to its parent if there is one" do
@node = @session.find(:css, '#first')
@node.parent.should == @node.session.document
@node.find('a').parent.should == @node
end
end
context "with css selectors" do

View File

@ -18,6 +18,64 @@ shared_examples_for "session with javascript support" do
end
end
describe 'Node#reload', :focus => true do
context "without automatic reload" do
before { Capybara.automatic_reload = false }
it "should reload the current context of the node" do
@session.visit('/with_js')
node = @session.find(:css, '#reload-me')
@session.click_link('Reload!')
sleep(0.3)
node.reload.text.should == 'RELOADED'
node.text.should == 'RELOADED'
end
it "should reload a parent node" do
@session.visit('/with_js')
node = @session.find(:css, '#reload-me').find(:css, 'em')
@session.click_link('Reload!')
sleep(0.3)
node.reload.text.should == 'RELOADED'
node.text.should == 'RELOADED'
end
it "should not automatically reload" do
@session.visit('/with_js')
node = @session.find(:css, '#reload-me')
@session.click_link('Reload!')
sleep(0.3)
running { node.text.should == 'RELOADED' }.should raise_error
end
after { Capybara.automatic_reload = true }
end
context "with automatic reload" do
it "should reload the current context of the node automatically" do
@session.visit('/with_js')
node = @session.find(:css, '#reload-me')
@session.click_link('Reload!')
sleep(0.3)
node.text.should == 'RELOADED'
end
it "should reload a parent node automatically" do
@session.visit('/with_js')
node = @session.find(:css, '#reload-me').find(:css, 'em')
@session.click_link('Reload!')
sleep(0.3)
node.text.should == 'RELOADED'
end
it "should reload a node automatically when using find" do
@session.visit('/with_js')
node = @session.find(:css, '#reload-me')
@session.click_link('Reload!')
sleep(0.3)
node.find(:css, 'a').text.should == 'RELOADED'
end
end
end
describe '#find' do
it "should allow triggering of custom JS events" do
pending "cannot figure out how to do this with selenium" if @session.mode == :selenium

View File

@ -38,6 +38,11 @@
<p>
<input type="submit" id="fire_ajax_request" value="Fire Ajax Request"/>
</p>
<p>
<a id="reload-link" href="#">Reload!</a>
<div id="reload-me"><em>waiting to be reloaded</em></div>
</p>
</body>
</html>