Elements are automatically reloaded

This commit is contained in:
Jonas Nicklas 2011-07-13 15:39:17 +02:00
parent 91de98bb03
commit 941e50132a
12 changed files with 129 additions and 38 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,6 +33,10 @@ module Capybara
@base = base
end
def reload
self
end
protected
def wait?

View File

@ -22,12 +22,18 @@ module Capybara
#
class Element < Base
def initialize(session, base, parent=nil, 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
carefully { base.native }
end
##
@ -35,7 +41,7 @@ module Capybara
# @return [String] The text of the element
#
def text
base.text
carefully { base.text }
end
##
@ -48,7 +54,7 @@ module Capybara
# @return [String] The value of the attribute
#
def [](attribute)
base[attribute]
carefully { base[attribute] }
end
##
@ -56,7 +62,7 @@ module Capybara
# @return [String] The value of the form element
#
def value
base.value
carefully { base.value }
end
##
@ -66,7 +72,7 @@ module Capybara
# @param [String] value The new value
#
def set(value)
base.set(value)
carefully { 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
carefully { 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
carefully { base.unselect_option }
end
##
@ -90,7 +96,7 @@ module Capybara
# Click the Element
#
def click
base.click
carefully { base.click }
end
##
@ -98,7 +104,7 @@ module Capybara
# @return [String] The tag name of the element
#
def tag_name
base.tag_name
carefully { base.tag_name }
end
##
@ -109,7 +115,7 @@ module Capybara
# @return [Boolean] Whether the element is visible
#
def visible?
base.visible?
carefully { base.visible? }
end
##
@ -119,7 +125,7 @@ module Capybara
# @return [Boolean] Whether the element is checked
#
def checked?
base.checked?
carefully { base.checked? }
end
##
@ -129,7 +135,7 @@ module Capybara
# @return [Boolean] Whether the element is selected
#
def selected?
base.selected?
carefully { base.selected? }
end
##
@ -139,7 +145,7 @@ module Capybara
# @return [String] An XPath expression
#
def path
base.path
carefully { 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)
carefully { base.trigger(event) }
end
##
@ -164,7 +170,13 @@ module Capybara
# @param [Capybara::Element] node The element to drag to
#
def drag_to(node)
base.drag_to(node.base)
carefully { base.drag_to(node.base) }
end
def reload
reloaded = parent.reload.first(@selector.name, @selector.locator, @selector.options)
@base = reloaded.base if reloaded
self
end
def inspect
@ -173,6 +185,19 @@ module Capybara
%(#<Capybara::Element tag="#{tag_name}">)
end
def carefully(seconds=Capybara.default_wait_time)
start_time = Time.now
begin
yield
rescue => e
raise e unless driver.respond_to?(:invalid_element_errors) and driver.invalid_element_errors.include?(e.class)
raise e if (Time.now - start_time) >= seconds
sleep(0.05)
reload
retry
end
end
end
end
end

View File

@ -120,10 +120,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 +143,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,12 +157,10 @@ module Capybara
protected
def find_in_base(xpath)
base.find(xpath)
end
def convert_element(element)
Capybara::Node::Element.new(session, element)
def find_in_base(selector, xpath)
base.find(xpath).map do |node|
Capybara::Node::Element.new(session, node, self, selector)
end
end
def wait_conditionally_until
@ -197,7 +196,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

@ -2,7 +2,7 @@ require 'selenium-webdriver'
class Capybara::Selenium::Driver < Capybara::Driver::Base
DEFAULT_OPTIONS = {
:resynchronize => false,
:resynchronize => true,
:resynchronization_timeout => 10,
:browser => :firefox
}
@ -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

@ -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>RELOADED</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,48 @@ 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
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
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>