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 :asset_root, :app_host, :run_server, :default_host
attr_accessor :server_port, :server_boot_timeout attr_accessor :server_port, :server_boot_timeout
attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements, :prefer_visible_elements 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.ignore_hidden_elements = false
config.prefer_visible_elements = true config.prefer_visible_elements = true
config.default_host = "http://www.example.com" config.default_host = "http://www.example.com"
config.automatic_reload = true
end end
Capybara.register_driver :rack_test do |app| Capybara.register_driver :rack_test do |app|

View File

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

View File

@ -22,7 +22,7 @@ module Capybara
# session.has_css?('#foobar') # from Capybara::Node::Matchers # session.has_css?('#foobar') # from Capybara::Node::Matchers
# #
class Base class Base
attr_reader :session, :base attr_reader :session, :base, :parent
include Capybara::Node::Finders include Capybara::Node::Finders
include Capybara::Node::Actions include Capybara::Node::Actions
@ -33,6 +33,10 @@ module Capybara
@base = base @base = base
end end
def reload
self
end
protected protected
def wait? def wait?

View File

@ -22,12 +22,18 @@ module Capybara
# #
class Element < Base 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 # @return [Object] The native element from the driver, this allows access to driver specific methods
# #
def native def native
base.native carefully { base.native }
end end
## ##
@ -35,7 +41,7 @@ module Capybara
# @return [String] The text of the element # @return [String] The text of the element
# #
def text def text
base.text carefully { base.text }
end end
## ##
@ -48,7 +54,7 @@ module Capybara
# @return [String] The value of the attribute # @return [String] The value of the attribute
# #
def [](attribute) def [](attribute)
base[attribute] carefully { base[attribute] }
end end
## ##
@ -56,7 +62,7 @@ module Capybara
# @return [String] The value of the form element # @return [String] The value of the form element
# #
def value def value
base.value carefully { base.value }
end end
## ##
@ -66,7 +72,7 @@ module Capybara
# @param [String] value The new value # @param [String] value The new value
# #
def set(value) def set(value)
base.set(value) carefully { base.set(value) }
end end
## ##
@ -74,7 +80,7 @@ module Capybara
# Select this node if is an option element inside a select tag # Select this node if is an option element inside a select tag
# #
def select_option def select_option
base.select_option carefully { base.select_option }
end end
## ##
@ -82,7 +88,7 @@ module Capybara
# Unselect this node if is an option element inside a multiple select tag # Unselect this node if is an option element inside a multiple select tag
# #
def unselect_option def unselect_option
base.unselect_option carefully { base.unselect_option }
end end
## ##
@ -90,7 +96,7 @@ module Capybara
# Click the Element # Click the Element
# #
def click def click
base.click carefully { base.click }
end end
## ##
@ -98,7 +104,7 @@ module Capybara
# @return [String] The tag name of the element # @return [String] The tag name of the element
# #
def tag_name def tag_name
base.tag_name carefully { base.tag_name }
end end
## ##
@ -109,7 +115,7 @@ module Capybara
# @return [Boolean] Whether the element is visible # @return [Boolean] Whether the element is visible
# #
def visible? def visible?
base.visible? carefully { base.visible? }
end end
## ##
@ -119,7 +125,7 @@ module Capybara
# @return [Boolean] Whether the element is checked # @return [Boolean] Whether the element is checked
# #
def checked? def checked?
base.checked? carefully { base.checked? }
end end
## ##
@ -129,7 +135,7 @@ module Capybara
# @return [Boolean] Whether the element is selected # @return [Boolean] Whether the element is selected
# #
def selected? def selected?
base.selected? carefully { base.selected? }
end end
## ##
@ -139,7 +145,7 @@ module Capybara
# @return [String] An XPath expression # @return [String] An XPath expression
# #
def path def path
base.path carefully { base.path }
end end
## ##
@ -150,7 +156,7 @@ module Capybara
# @param [String] event The name of the event to trigger # @param [String] event The name of the event to trigger
# #
def trigger(event) def trigger(event)
base.trigger(event) carefully { base.trigger(event) }
end end
## ##
@ -164,7 +170,13 @@ module Capybara
# @param [Capybara::Element] node The element to drag to # @param [Capybara::Element] node The element to drag to
# #
def drag_to(node) 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 end
def inspect def inspect
@ -173,6 +185,19 @@ module Capybara
%(#<Capybara::Element tag="#{tag_name}">) %(#<Capybara::Element tag="#{tag_name}">)
end 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 end
end end

View File

@ -120,10 +120,10 @@ module Capybara
def all(*args) def all(*args)
options = extract_normalized_options(args) options = extract_normalized_options(args)
Capybara::Selector.normalize(*args).xpaths. selector = Capybara::Selector.normalize(*args)
map { |path| find_in_base(path) }.flatten. selector.xpaths.
select { |node| matches_options(node, options) }. map { |path| find_in_base(selector, path) }.flatten.
map { |node| convert_element(node) } select { |node| matches_options(node, options) }
end end
## ##
@ -143,10 +143,11 @@ module Capybara
options = extract_normalized_options(args) options = extract_normalized_options(args)
found_elements = [] found_elements = []
Capybara::Selector.normalize(*args).xpaths.each do |path| selector = Capybara::Selector.normalize(*args)
find_in_base(path).each do |node| selector.xpaths.each do |path|
find_in_base(selector, path).each do |node|
if matches_options(node, options) 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? return found_elements.last if not Capybara.prefer_visible_elements or node.visible?
end end
end end
@ -156,12 +157,10 @@ module Capybara
protected protected
def find_in_base(xpath) def find_in_base(selector, xpath)
base.find(xpath) base.find(xpath).map do |node|
end Capybara::Node::Element.new(session, node, self, selector)
end
def convert_element(element)
Capybara::Node::Element.new(session, element)
end end
def wait_conditionally_until def wait_conditionally_until
@ -197,7 +196,7 @@ module Capybara
end end
def has_selected_options?(node, expected) 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? (expected - actual).empty?
end end
end end

View File

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

View File

@ -2,7 +2,7 @@ require 'selenium-webdriver'
class Capybara::Selenium::Driver < Capybara::Driver::Base class Capybara::Selenium::Driver < Capybara::Driver::Base
DEFAULT_OPTIONS = { DEFAULT_OPTIONS = {
:resynchronize => false, :resynchronize => true,
:resynchronization_timeout => 10, :resynchronization_timeout => 10,
:browser => :firefox :browser => :firefox
} }
@ -118,6 +118,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
# Browser must have already gone # Browser must have already gone
end end
def invalid_element_errors
[Selenium::WebDriver::Error::ObsoleteElementError]
end
private private
def load_wait_for_ajax_support def load_wait_for_ajax_support

View File

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

View File

@ -35,4 +35,9 @@ $(function() {
$('body').append('<p id="ajax_request_done">Ajax request done</p>'); $('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 it "should scope CSS selectors" do
@session.find(:css, '#second').should have_no_css('h1') @session.find(:css, '#second').should have_no_css('h1')
end 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 end
context "with css selectors" do context "with css selectors" do

View File

@ -18,6 +18,48 @@ shared_examples_for "session with javascript support" do
end end
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 describe '#find' do
it "should allow triggering of custom JS events" do it "should allow triggering of custom JS events" do
pending "cannot figure out how to do this with selenium" if @session.mode == :selenium pending "cannot figure out how to do this with selenium" if @session.mode == :selenium

View File

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