Merge branch 'async_is_my_bitch'
This commit is contained in:
commit
011bf108f7
|
@ -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|
|
||||
|
|
|
@ -43,6 +43,10 @@ class Capybara::Driver::Base
|
|||
raise Capybara::NotSupportedByDriverError
|
||||
end
|
||||
|
||||
def invalid_element_errors
|
||||
[]
|
||||
end
|
||||
|
||||
def wait?
|
||||
false
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue