lib/calabash-cucumber/wait_helpers.rb in calabash-cucumber-0.10.0.pre1 vs lib/calabash-cucumber/wait_helpers.rb in calabash-cucumber-0.10.0.pre2
- old
+ new
@@ -3,32 +3,85 @@
require 'fileutils'
require 'calabash-cucumber/utils/logging'
module Calabash
module Cucumber
+
+ # A collection of methods that help you wait for things.
module WaitHelpers
include Calabash::Cucumber::Logging
include Calabash::Cucumber::Core
include Calabash::Cucumber::TestsHelpers
+ # @!visibility private
CLIENT_TIMEOUT_ADDITION = 5
+ # `WaitError` is the error type raised
+ # when a timeout occurs during a wait.
+ # To handle a timeout without causing test failure use
+ # @example
+ # begin
+ # ...
+ # rescue Calabash::Cucumber::WaitHelpers::WaitError => e
+ # ...
+ # end
+ #
class WaitError < RuntimeError
end
- CALABASH_CONDITIONS = {:none_animating => "NONE_ANIMATING",
- :no_network_indicator => "NO_NETWORK_INDICATOR"}
+ # Currently two conditions that can be
+ # waited for using `wait_for_condition`: `:none_animating` no UIKit object is animating
+ # and `:no_network_indicator` status bar network indicator not showing.
+ CALABASH_CONDITIONS = {:none_animating => 'NONE_ANIMATING',
+ :no_network_indicator => 'NO_NETWORK_INDICATOR'}
- # 'post_timeout' is the time to wait after a wait function returns true
+ # The default options used in the "wait*" methods
DEFAULT_OPTS = {
- :timeout => 30,
- :retry_frequency => 0.3,
- :post_timeout => 0,
- :timeout_message => 'Timed out waiting...',
- :screenshot_on_error => true
+ # default upper limit on how long to wait
+ :timeout => 30,
+ # default polling frequency for waiting
+ :retry_frequency => 0.3,
+ # default extra wait after the condition becomes true
+ :post_timeout => 0,
+ # default message if timeout occurs
+ :timeout_message => 'Timed out waiting...',
+ # Calabash will generate a screenshot by default if waiting times out
+ :screenshot_on_error => true
}.freeze
+ # Waits for a condition to be true. The condition is specified by a given block that is called repeatedly.
+ # If the block returns a 'trueish' value the condition is considered true and
+ # `wait_for` immediately returns.
+ # There is a `:timeout` option that specifies a maximum number of seconds to wait.
+ # If the given block doesn't return a 'trueish' value before the `:timeout` seconds has elapsed,
+ # the waiting fails and raises a {Calabash::Cucumber::WaitHelpers::WaitError} error.
+ #
+ # The `options` hash
+ # controls the details of waiting (see `options_or_timeout` below).
+ # {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} specifies the default waiting options.
+ #
+ # `wait_for` is a low-level building-block for waiting and often there are higher-level
+ # waiting methods what use `wait_for` in their implementation (e.g. `wait_for_element_exists`).
+ # @see #wait_for_element_exists
+ #
+ # @example Waiting for an element (see also `wait_for_element_exists`)
+ # wait_for(timeout: 60,
+ # timeout_message: "Could not find 'Sign in' button") do
+ # element_exists("button marked:'Sign in'")
+ # end
+ # @param [Hash] options_or_timeout options for controlling the details of the wait.
+ # Note for backwards compatibility with old Calabash versions can also be a number which is
+ # then interpreted as a timeout.
+ # @option options_or_timeout [Numeric] :timeout (30) upper limit on how long to wait (in seconds)
+ # @option options_or_timeout [Numeric] :retry_frequency (0.3) how often to poll (i.e., call the given block)
+ # @option options_or_timeout [Numeric] :post_timeout (0) if positive, an extra wait is made after the condition
+ # is satisfied
+ # @option options_or_timeout [String] :timeout_message the error message to use if condition is not satisfied
+ # in time
+ # @option options_or_timeout [Boolean] :screenshot_on_error generate a screenshot on error
+ # @return [nil] when the condition is satisfied
+ # @raise [Calabash::Cucumber::WaitHelpers::WaitError] when the timeout is exceeded
def wait_for(options_or_timeout=DEFAULT_OPTS, &block)
#note Hash is preferred, number acceptable for backwards compat
default_timeout = 30
timeout = options_or_timeout || default_timeout
post_timeout=0
@@ -52,33 +105,49 @@
end
sleep(post_timeout) if post_timeout > 0
rescue WaitError => e
msg = timeout_message || e
if screenshot_on_error
- sleep(retry_frequency)
- return screenshot_and_retry(msg, &block)
+ sleep(retry_frequency)
+ return screenshot_and_retry(msg, &block)
else
- raise wait_error(msg)
- end
+ raise wait_error(msg)
+ end
rescue Exception => e
handle_error_with_options(e, nil, screenshot_on_error)
end
end
- def screenshot_and_retry(msg, &block)
- path = screenshot
- res = yield
- # Validate after taking screenshot
- if res
- FileUtils.rm_f(path)
- return res
- else
- embed(path, 'image/png', msg)
- raise wait_error(msg)
- end
- end
-
+ # Repeatedly runs an action (for side-effects) until a condition is satisfied.
+ # Similar to `wait_for` but specifies both a condition to wait for and an action to repeatedly perform
+ # to make the condition true (e.g. scrolling). The return value of the action is ignored.
+ #
+ # The block represents the action and options :until or :until_exists specify the condition to wait for.
+ # Same options as `wait_for` can be provided.
+ #
+ # @see #wait_for
+ #
+ # @example Scrolling until we find an element
+ # wait_poll(timeout: 10,
+ # timeout_message: 'Unable to find "Example"',
+ # until_exists: "* marked:'Example'") do
+ # scroll("tableView", :down)
+ # end
+ #
+ # @example Win the battle
+ # wait_poll(timeout: 60,
+ # timeout_message: 'Defeat!',
+ # until: lambda { enemy_defeated? }) do
+ # launch_the_missiles!
+ # end
+ # @param [Hash] opts options for controlling the details of the wait in addition to the options specified below,
+ # all options in {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} also apply and can be overridden.
+ # @option opts [Proc] :until if specified this lambda/Proc becomes the condition to wait for.
+ # @option opts [String] :until_exists if specified, a calabash query to wait for. Exactly one of `:until` and
+ # `:until_exists` must be specified
+ # @return [nil] when the condition is satisfied
+ # @raise [Calabash::Cucumber::WaitHelpers::WaitError] when the timeout is exceeded
def wait_poll(opts, &block)
test = opts[:until]
if test.nil?
cond = opts[:until_exists]
raise 'Must provide :until or :until_exists' unless cond
@@ -92,31 +161,87 @@
false
end
end
end
- #options for wait_for apply
+ # Waits for a Calabash query to return a non-empty result (typically a UI element to be visible).
+ # Uses `wait_for`.
+ # @see #wait_for
+ # @see Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS
+ #
+ # @example Waiting for an element to be visible
+ # wait_for_element_exists("button marked:'foo'", timeout: 60)
+ # @param [String] element_query a Calabash query to wait for (i.e. `element_exists(element_query)`)
+ # @param [Hash] options options for controlling the details of the wait.
+ # The same options as {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} apply.
+ # @return [nil] when the condition is satisfied
+ # @raise [Calabash::Cucumber::WaitHelpers::WaitError] when the timeout is exceeded
+ def wait_for_element_exists(element_query, options={})
+ options[:timeout_message] = options[:timeout_message] || "Timeout waiting for element: #{element_query}"
+ wait_for(options) { element_exists(element_query) }
+ end
+
+ # Waits for one or more Calabash queries to all return non-empty results (typically a UI elements to be visible).
+ # Uses `wait_for`.
+ # @see #wait_for
+ # @see #wait_for_element_exists
+ # @see Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS
+ #
+ # @param [Array<String>] elements_arr an Array of Calabash queries to wait for (i.e. `element_exists(element_query)`)
+ # @param [Hash] options options for controlling the details of the wait.
+ # The same options as {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} apply.
+ # @return [nil] when the condition is satisfied
+ # @raise [Calabash::Cucumber::WaitHelpers::WaitError] when the timeout is exceeded
def wait_for_elements_exist(elements_arr, options={})
if elements_arr.is_a?(String)
elements_arr = [elements_arr]
end
- options[:timeout_message] = options[:timeout_message] || "Timeout waiting for elements: #{elements_arr.join(",")}"
+ options[:timeout_message] = options[:timeout_message] || "Timeout waiting for elements: #{elements_arr.join(',')}"
wait_for(options) do
elements_arr.all? { |q| element_exists(q) }
end
end
- #options for wait_for apply
+
+ # Waits for a Calabash query to return an empty result (typically a UI element to disappear).
+ # Uses `wait_for`.
+ # @see #wait_for
+ # @see #wait_for_element_exists
+ # @see Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS
+ #
+ # @param [String] element_query a Calabash query to be empty (i.e. `element_does_not_exist(element_query)`)
+ # @param [Hash] options options for controlling the details of the wait.
+ # The same options as {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} apply.
+ # @return [nil] when the condition is satisfied
+ # @raise [Calabash::Cucumber::WaitHelpers::WaitError] when the timeout is exceeded
+ def wait_for_element_does_not_exists(element_query, options={})
+ options[:timeout_message] = options[:timeout_message] || "Timeout waiting for element to not exist: #{element_query}"
+ wait_for(options) { element_does_not_exist(element_query) }
+ end
+
+ # Waits for one or more Calabash queries to all return empty results (typically a UI elements to disappear).
+ # Uses `wait_for`.
+ # @see #wait_for
+ # @see #wait_for_element_exists
+ # @see #wait_for_element_does_not_exists
+ # @see Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS
+ #
+ # @param [Array<String>] elements_arr an Array of Calabash queries to be empty (i.e. `element_does_not_exist(element_query)`)
+ # @param [Hash] options options for controlling the details of the wait.
+ # The same options as {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} apply.
+ # @return [nil] when the condition is satisfied
+ # @raise [Calabash::Cucumber::WaitHelpers::WaitError] when the timeout is exceeded
def wait_for_elements_do_not_exist(elements_arr, options={})
if elements_arr.is_a?(String)
elements_arr = [elements_arr]
end
options[:timeout_message] = options[:timeout_message] || "Timeout waiting for no elements matching: #{elements_arr.join(",")}"
wait_for(options) do
elements_arr.none? { |q| element_exists(q) }
end
end
+ # @!visibility private
def wait_for_condition(options = {})
timeout = options[:timeout]
unless timeout && timeout > 0
timeout = 30
end
@@ -140,17 +265,17 @@
screenshot_on_error = options[:screenshot_on_error]
end
begin
Timeout::timeout(timeout+CLIENT_TIMEOUT_ADDITION, WaitError) do
- res = http({:method => :post, :path => 'condition'},
- options)
- res = JSON.parse(res)
- unless res['outcome'] == 'SUCCESS'
- raise WaitError.new(res['reason'])
- end
- sleep(options[:post_timeout]) if options[:post_timeout] > 0
+ res = http({:method => :post, :path => 'condition'},
+ options)
+ res = JSON.parse(res)
+ unless res['outcome'] == 'SUCCESS'
+ raise WaitError.new(res['reason'])
+ end
+ sleep(options[:post_timeout]) if options[:post_timeout] > 0
end
rescue WaitError => e
msg = timeout_message || e
if screenshot_on_error
sleep(retry_frequency)
@@ -166,95 +291,150 @@
rescue Exception => e
handle_error_with_options(e,nil, screenshot_on_error)
end
end
+ # Waits for all elements to stop animating (EXPERIMENTAL).
+ # @param [Hash] options options for controlling the details of the wait.
+ # @option options [Numeric] :timeout (30) maximum time to wait
+ # @return [nil] when the condition is satisfied
+ # @raise [Calabash::Cucumber::WaitHelpers::WaitError] when the timeout is exceeded
def wait_for_none_animating(options = {})
options[:condition] = CALABASH_CONDITIONS[:none_animating]
wait_for_condition(options)
end
+ # Waits for the status-bar network indicator to stop animating (network activity done).
+ # @param [Hash] options options for controlling the details of the wait.
+ # @option options [Numeric] :timeout (30) maximum time to wait
+ # @return [nil] when the condition is satisfied
+ # @raise [Calabash::Cucumber::WaitHelpers::WaitError] when the timeout is exceeded
def wait_for_no_network_indicator(options = {})
options[:condition] = CALABASH_CONDITIONS[:no_network_indicator]
wait_for_condition(options)
end
- #may be called with a string (query) or an array of strings
+ # Combines waiting for elements and waiting for animations.
+ # @param [Array] done_queries Calabash queries to wait for (one or more).
+ # @param [Hash] check_options ({}) options used for `wait_for_elements_exists(done_queries, check_options)`
+ # @param [Hash] animation_options ({}) options used for `wait_for_none_animating(animation_options)`
def wait_for_transition(done_queries, check_options={},animation_options={})
done_queries = [*done_queries]
wait_for_elements_exist(done_queries,check_options)
wait_for_none_animating(animation_options)
end
+ # Combines touching an element and `wait_for_transition`
+ # @see #wait_for_transition
+ # @param [String] touch_q the Calabash query to touch
+ # @param [Array] done_queries passed to `wait_for_transition`
+ # @param [Hash] check_options ({}) passed to `wait_for_transition`
+ # @param [Hash] animation_options ({}) passed to `wait_for_transition`
def touch_transition(touch_q, done_queries,check_options={},animation_options={})
touch(touch_q)
wait_for_transition(done_queries,check_options,animation_options)
end
- def handle_error_with_options(ex, timeout_message, screenshot_on_error)
- msg = (timeout_message || ex)
- if ex
- msg = "#{msg} (#{ex.class})"
- end
- if screenshot_on_error
- screenshot_and_raise msg
- else
- raise msg
- end
- end
-
- def wait_error(msg)
- (msg.is_a?(String) ? WaitError.new(msg) : msg)
- end
-
# Performs a lambda action until the element (a query string) appears.
- # The default action is to do nothing.
+ # The default action is to do nothing. Similar to `wait_poll`.
#
- # Raises an error if no uiquery is specified. Same options as wait_for
- # which are timeout, retry frequency, post_timeout, timeout_message, and
- # screenshot on error.
+ # Raises an error if no uiquery is specified.
#
- # Example usage:
- # until_element_exists("Button", :action => lambda { swipe("up") })
+ # @see #wait_poll
+ #
+ # @example
+ # until_element_exists("button", :action => lambda { swipe("up") })
+ # @param [String] uiquery the Calabash query to wait for
+ # @param [Hash] opts options for controlling the details of the wait.
+ # The same options as {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} apply.
def until_element_exists(uiquery, opts = {})
- extra_opts = { :until_exists => uiquery, :action => lambda { ; } }
+ extra_opts = { :until_exists => uiquery, :action => lambda {} }
opts = DEFAULT_OPTS.merge(extra_opts).merge(opts)
- wait_poll(opts) do
+ wait_poll(opts) do
opts[:action].call
end
end
# Performs a lambda action until the element (a query string) disappears.
- # The default action is to do nothing.
+ # The default action is to do nothing.
#
- # Raises an error if no uiquery is specified. Same options as wait_for
- # which are timeout, retry frequency, post_timeout, timeout_message, and
- # screenshot on error.
+ # Raises an error if no uiquery is specified.
#
- # Example usage:
- # until_element_does_not_exist("Button", :action => lambda { swipe("up") })
+ # @example
+ # until_element_does_not_exist("button", :action => lambda { swipe("up") })
+ # @see #wait_poll
+ # @param [String] uiquery the Calabash query to wait for disappearing.
+ # @param [Hash] opts options for controlling the details of the wait.
+ # The same options as {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} apply.
def until_element_does_not_exist(uiquery, opts = {})
- condition = lambda { element_exists(uiquery) ? false : true }
- extra_opts = { :until => condition, :action => lambda { ; } }
+ condition = lambda {element_does_not_exist(uiquery)}
+ extra_opts = { :until => condition, :action => lambda {} }
opts = DEFAULT_OPTS.merge(extra_opts).merge(opts)
- wait_poll(opts) do
+ wait_poll(opts) do
opts[:action].call
end
end
# Performs a lambda action once the element exists.
# The default behavior is to touch the specified element.
#
- # Raises an error if no uiquery is specified. Same options as wait_for
- # which are timeout, retry frequency, post_timeout, timeout_message, and
- # screenshot on error.
+ # Raises an error if no uiquery is specified.
#
- # Example usage: when_element_exists("Button", :timeout => 10)
+ # @example
+ # when_element_exists("button", :timeout => 10)
+ # @see #wait_for
+ # @param [String] uiquery the Calabash query to wait for.
+ # @param [Hash] opts options for controlling the details of the wait.
+ # The same options as {Calabash::Cucumber::WaitHelpers::DEFAULT_OPTS} apply.
def when_element_exists(uiquery, opts = {})
- action = { :action => lambda { touch uiquery } }
- opts = DEFAULT_OPTS.merge(action).merge(opts)
- wait_for_elements_exist([uiquery], opts)
- opts[:action].call
+ action = opts[:action] || lambda { touch(uiquery) }
+ wait_for_element_exists(uiquery, opts)
+ action.call
+ end
+
+ # @!visibility private
+ def screenshot_and_retry(msg, &block)
+ path = screenshot
+ res = yield
+ # Validate after taking screenshot
+ if res
+ FileUtils.rm_f(path)
+ return res
+ else
+ embed(path, 'image/png', msg)
+ raise wait_error(msg)
+ end
+ end
+
+ # @!visibility private
+ # raises an error by raising a exception and conditionally takes a
+ # screenshot based on the value of +screenshot_on_error+.
+ # @param [Exception,nil] ex an exception to raise
+ # @param [String,nil] timeout_message the message of the raise
+ # @param [Boolean] screenshot_on_error if true takes a screenshot before
+ # raising an error
+ # @return [nil]
+ # @raise RuntimeError based on +ex+ and +timeout_message+
+ def handle_error_with_options(ex, timeout_message, screenshot_on_error)
+ msg = (timeout_message || ex)
+ if ex
+ msg = "#{msg} (#{ex.class})"
+ end
+ if screenshot_on_error
+ screenshot_and_raise msg
+ else
+ raise msg
+ end
+ end
+
+ # @private
+ # if +msg+ is a String, a new WaitError is returned. Otherwise +msg+
+ # itself is returned.
+ # @param [String,Object] msg a message to raise
+ # @return [WaitError] if +msg+ is a String, returns a new WaitError
+ # @return [Object] if +msg+ is anything else, returns +msg+
+ def wait_error(msg)
+ (msg.is_a?(String) ? WaitError.new(msg) : msg)
end
end
end
end