2016-03-07 19:52:19 -05:00
|
|
|
# frozen_string_literal: true
|
2018-01-08 15:23:54 -05:00
|
|
|
|
2010-11-21 08:37:36 -05:00
|
|
|
module Capybara
|
|
|
|
module Node
|
|
|
|
##
|
|
|
|
#
|
|
|
|
# A {Capybara::Node::Base} represents either an element on a page through the subclass
|
|
|
|
# {Capybara::Node::Element} or a document through {Capybara::Node::Document}.
|
|
|
|
#
|
|
|
|
# Both types of Node share the same methods, used for interacting with the
|
|
|
|
# elements on the page. These methods are divided into three categories,
|
|
|
|
# finders, actions and matchers. These are found in the modules
|
|
|
|
# {Capybara::Node::Finders}, {Capybara::Node::Actions} and {Capybara::Node::Matchers}
|
|
|
|
# respectively.
|
|
|
|
#
|
|
|
|
# A {Capybara::Session} exposes all methods from {Capybara::Node::Document} directly:
|
|
|
|
#
|
|
|
|
# session = Capybara::Session.new(:rack_test, my_app)
|
|
|
|
# session.visit('/')
|
2016-10-04 14:10:29 -04:00
|
|
|
# session.fill_in('Foo', with: 'Bar') # from Capybara::Node::Actions
|
2010-11-21 08:37:36 -05:00
|
|
|
# bar = session.find('#bar') # from Capybara::Node::Finders
|
2016-10-04 14:10:29 -04:00
|
|
|
# bar.select('Baz', from: 'Quox') # from Capybara::Node::Actions
|
2010-11-21 08:37:36 -05:00
|
|
|
# session.has_css?('#foobar') # from Capybara::Node::Matchers
|
|
|
|
#
|
|
|
|
class Base
|
2016-07-18 17:16:22 -04:00
|
|
|
attr_reader :session, :base, :query_scope
|
2010-11-21 08:37:36 -05:00
|
|
|
|
|
|
|
include Capybara::Node::Finders
|
|
|
|
include Capybara::Node::Actions
|
|
|
|
include Capybara::Node::Matchers
|
|
|
|
|
|
|
|
def initialize(session, base)
|
|
|
|
@session = session
|
|
|
|
@base = base
|
|
|
|
end
|
|
|
|
|
2012-02-01 08:16:17 -05:00
|
|
|
# overridden in subclasses, e.g. Capybara::Node::Element
|
2011-07-13 09:39:17 -04:00
|
|
|
def reload
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2012-07-13 14:19:23 -04:00
|
|
|
##
|
|
|
|
#
|
2014-03-19 19:28:26 -04:00
|
|
|
# This method is Capybara's primary defence against asynchronicity
|
2012-07-13 14:19:23 -04:00
|
|
|
# problems. It works by attempting to run a given block of code until it
|
|
|
|
# succeeds. The exact behaviour of this method depends on a number of
|
2012-09-06 04:47:11 -04:00
|
|
|
# factors. Basically there are certain exceptions which, when raised
|
2012-07-13 14:19:23 -04:00
|
|
|
# from the block, instead of bubbling up, are caught, and the block is
|
|
|
|
# re-run.
|
|
|
|
#
|
2014-03-19 19:28:26 -04:00
|
|
|
# Certain drivers, such as RackTest, have no support for asynchronous
|
2012-07-13 14:19:23 -04:00
|
|
|
# processes, these drivers run the block, and any error raised bubbles up
|
|
|
|
# immediately. This allows faster turn around in the case where an
|
|
|
|
# expectation fails.
|
|
|
|
#
|
|
|
|
# Only exceptions that are {Capybara::ElementNotFound} or any subclass
|
|
|
|
# thereof cause the block to be rerun. Drivers may specify additional
|
|
|
|
# exceptions which also cause reruns. This usually occurs when a node is
|
|
|
|
# manipulated which no longer exists on the page. For example, the
|
|
|
|
# Selenium driver specifies
|
|
|
|
# `Selenium::WebDriver::Error::ObsoleteElementError`.
|
|
|
|
#
|
|
|
|
# As long as any of these exceptions are thrown, the block is re-run,
|
|
|
|
# until a certain amount of time passes. The amount of time defaults to
|
2015-04-14 16:56:30 -04:00
|
|
|
# {Capybara.default_max_wait_time} and can be overridden through the `seconds`
|
2012-09-06 04:47:11 -04:00
|
|
|
# argument. This time is compared with the system time to see how much
|
2015-04-14 17:41:01 -04:00
|
|
|
# time has passed. On rubies/platforms which don't support access to a monotonic process clock
|
|
|
|
# if the return value of `Time.now` is stubbed out, Capybara will raise `Capybara::FrozenInTime`.
|
2012-07-13 14:19:23 -04:00
|
|
|
#
|
2018-02-27 18:51:05 -05:00
|
|
|
# @param [Integer] seconds (current sessions default_max_wait_time) Maximum number of seconds to retry this block
|
|
|
|
# @param [Array<Exception>] errors (driver.invalid_element_errors +
|
2014-04-08 17:28:16 -04:00
|
|
|
# [Capybara::ElementNotFound]) exception types that cause the block to be rerun
|
2012-07-13 14:33:14 -04:00
|
|
|
# @return [Object] The result of the given block
|
2013-03-04 16:35:11 -05:00
|
|
|
# @raise [Capybara::FrozenInTime] If the return value of `Time.now` appears stuck
|
2012-07-13 14:19:23 -04:00
|
|
|
#
|
2018-10-13 17:46:58 -04:00
|
|
|
def synchronize(seconds = nil, errors: nil)
|
2018-08-22 20:15:16 -04:00
|
|
|
return yield if session.synchronized
|
|
|
|
|
2019-03-26 20:21:41 -04:00
|
|
|
seconds = session_options.default_max_wait_time if [nil, true].include? seconds
|
2018-08-22 20:15:16 -04:00
|
|
|
session.synchronized = true
|
|
|
|
timer = Capybara::Helpers.timer(expire_in: seconds)
|
|
|
|
begin
|
2011-08-12 07:52:12 -04:00
|
|
|
yield
|
2019-04-04 15:03:01 -04:00
|
|
|
rescue StandardError => e
|
2018-08-22 20:15:16 -04:00
|
|
|
session.raise_server_error!
|
2019-04-04 15:03:01 -04:00
|
|
|
raise e unless catch_error?(e, errors)
|
2018-09-24 12:43:46 -04:00
|
|
|
|
2019-02-15 15:58:45 -05:00
|
|
|
if driver.wait?
|
2019-04-04 15:03:01 -04:00
|
|
|
raise e if timer.expired?
|
2019-02-15 15:58:45 -05:00
|
|
|
|
|
|
|
sleep(0.01)
|
|
|
|
reload if session_options.automatic_reload
|
|
|
|
else
|
|
|
|
old_base = @base
|
|
|
|
reload if session_options.automatic_reload
|
2019-04-04 15:03:01 -04:00
|
|
|
raise e if old_base == @base
|
2019-02-15 15:58:45 -05:00
|
|
|
end
|
2018-08-22 20:15:16 -04:00
|
|
|
retry
|
|
|
|
ensure
|
|
|
|
session.synchronized = false
|
2011-08-12 07:52:12 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-02-16 12:13:58 -05:00
|
|
|
# @api private
|
2019-01-03 16:00:38 -05:00
|
|
|
def find_css(css, **options)
|
|
|
|
if base.method(:find_css).arity != 1
|
|
|
|
base.find_css(css, **options)
|
|
|
|
else
|
|
|
|
base.find_css(css)
|
|
|
|
end
|
2014-02-16 12:13:58 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# @api private
|
2019-01-03 16:00:38 -05:00
|
|
|
def find_xpath(xpath, **options)
|
2019-01-08 18:51:05 -05:00
|
|
|
if base.method(:find_xpath).arity != 1
|
2019-01-03 16:00:38 -05:00
|
|
|
base.find_xpath(xpath, **options)
|
|
|
|
else
|
|
|
|
base.find_xpath(xpath)
|
|
|
|
end
|
2014-02-16 12:13:58 -05:00
|
|
|
end
|
|
|
|
|
2016-12-15 12:04:01 -05:00
|
|
|
# @api private
|
|
|
|
def session_options
|
|
|
|
session.config
|
|
|
|
end
|
|
|
|
|
2018-02-13 14:14:59 -05:00
|
|
|
def to_capybara_node
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2012-02-01 08:16:17 -05:00
|
|
|
protected
|
|
|
|
|
2014-04-08 17:28:16 -04:00
|
|
|
def catch_error?(error, errors = nil)
|
|
|
|
errors ||= (driver.invalid_element_errors + [Capybara::ElementNotFound])
|
2018-01-12 19:57:41 -05:00
|
|
|
errors.any? { |type| error.is_a?(type) }
|
2013-03-14 05:03:49 -04:00
|
|
|
end
|
|
|
|
|
2010-11-21 08:37:36 -05:00
|
|
|
def driver
|
|
|
|
session.driver
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|