mirror of
https://github.com/teamcapybara/capybara.git
synced 2022-11-09 12:08:07 -05:00
Session#all should wait for elements
This commit is contained in:
parent
cb309a8bea
commit
9410b6c176
5 changed files with 56 additions and 26 deletions
|
@ -218,10 +218,11 @@ module Capybara
|
|||
# page.all('a', text: 'Home')
|
||||
# page.all('#menu li', visible: true)
|
||||
#
|
||||
# By default if no elements are found, an empty array is returned;
|
||||
# however, expectations can be set on the number of elements to be found which
|
||||
# will trigger Capybara's waiting behavior for the expectations to match.The
|
||||
# expectations can be set using
|
||||
# By default Capybara's waiting behavior will wait up to `Capybara.default_max_wait_time`
|
||||
# seconds for matching elements to be available and then return an empty result if none
|
||||
# are available. It is possible to set expectations on the number of results located and
|
||||
# Capybara will raise an exception if the number of elements located don't satisfy the
|
||||
# specified conditions. The expectations can be set using
|
||||
#
|
||||
# page.assert_selector('p#foo', count: 4)
|
||||
# page.assert_selector('p#foo', maximum: 10)
|
||||
|
@ -232,9 +233,9 @@ module Capybara
|
|||
# count matching.
|
||||
#
|
||||
# @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to Capybara.default_selector
|
||||
# @param [String] locator The selector
|
||||
# @param [String] locator The locator for the specified selector
|
||||
# @option options [String, Regexp] text Only find elements which contain this text or match this regexp
|
||||
# @option options [String, Boolean] exact_text (Capybara.exact_text) When String the string the elements contained text must match exactly, when Boolean controls whether the :text option must match exactly
|
||||
# @option options [String, Boolean] exact_text (Capybara.exact_text) When String the elements contained text must match exactly, when Boolean controls whether the :text option must match exactly
|
||||
# @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
|
||||
# * true - only finds visible elements.
|
||||
# * false - finds invisible _and_ visible elements.
|
||||
|
@ -246,21 +247,29 @@ module Capybara
|
|||
# @option options [Integer] minimum Minimum number of matches that are expected to be found
|
||||
# @option options [Range] between Number of matches found must be within the given range
|
||||
# @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially
|
||||
# @option options [Integer] wait (Capybara.default_max_wait_time) The time to wait for element count expectations to become true
|
||||
# @option options [Integer, false] wait (Capybara.default_max_wait_time) The time to wait for matching elements to become available
|
||||
# @overload all([kind = Capybara.default_selector], locator = nil, options = {})
|
||||
# @overload all([kind = Capybara.default_selector], locator = nil, options = {}, &filter_block)
|
||||
# @yieldparam element [Capybara::Node::Element] The element being considered for inclusion in the results
|
||||
# @yieldreturn [Boolean] Should the element be considered in the results?
|
||||
# @return [Capybara::Result] A collection of found elements
|
||||
#
|
||||
# @raise [Capybara::ExpectationNotMet] The number of elements found doesn't match the specified conditions
|
||||
def all(*args, **options, &optional_filter_block)
|
||||
minimum_specified = [:count, :minimum, :between].any? {|k| options.key?(k)}
|
||||
options = {minimum: 1}.merge(options) unless minimum_specified
|
||||
options[:session_options] = session_options
|
||||
args.push(options)
|
||||
query = Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
|
||||
synchronize(query.wait) do
|
||||
result = query.resolve_for(self)
|
||||
raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count?
|
||||
result
|
||||
begin
|
||||
result = nil
|
||||
synchronize(query.wait) do
|
||||
result = query.resolve_for(self)
|
||||
raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count?
|
||||
result
|
||||
end
|
||||
rescue Capybara::ExpectationNotMet
|
||||
raise if minimum_specified || (result.compare_count == 1)
|
||||
Result.new([], nil)
|
||||
end
|
||||
end
|
||||
alias_method :find_all, :all
|
||||
|
@ -269,12 +278,14 @@ module Capybara
|
|||
#
|
||||
# Find the first element on the page matching the given selector
|
||||
# and options. Will raise an error if no matching element is found
|
||||
# unless the `allow_nil` option is true.
|
||||
#
|
||||
# @overload first([kind], locator, options)
|
||||
# @param [:css, :xpath] kind The type of selector
|
||||
# @param [String] locator The selector
|
||||
# @param [Hash] options Additional options; see {#all}
|
||||
# @return [Capybara::Node::Element] The found element or nil
|
||||
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires and `allow_nil` is not true
|
||||
#
|
||||
def first(*args, allow_nil: false, **options, &optional_filter_block)
|
||||
options = {minimum: 1}.merge(options)
|
||||
|
|
|
@ -63,7 +63,7 @@ module Capybara
|
|||
end
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def matches_count?
|
||||
def compare_count
|
||||
# Only check filters for as many elements as necessary to determine result
|
||||
if @query.options[:count]
|
||||
count_opt = Integer(@query.options[:count])
|
||||
|
@ -71,7 +71,7 @@ module Capybara
|
|||
break if @result_cache.size > count_opt
|
||||
@result_cache << @results_enum.next
|
||||
end
|
||||
return @result_cache.size == count_opt
|
||||
return @result_cache.size <=> count_opt
|
||||
end
|
||||
|
||||
if @query.options[:minimum]
|
||||
|
@ -79,7 +79,7 @@ module Capybara
|
|||
begin
|
||||
@result_cache << @results_enum.next while @result_cache.size < min_opt
|
||||
rescue StopIteration
|
||||
return false
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -87,24 +87,31 @@ module Capybara
|
|||
max_opt = Integer(@query.options[:maximum])
|
||||
begin
|
||||
@result_cache << @results_enum.next while @result_cache.size <= max_opt
|
||||
return false
|
||||
return 1
|
||||
rescue StopIteration
|
||||
end
|
||||
end
|
||||
|
||||
if @query.options[:between]
|
||||
max = Integer(@query.options[:between].max)
|
||||
max = Integer(@query.options[:between].max)
|
||||
min = Integer(@query.options[:between].min)
|
||||
loop do
|
||||
break if @result_cache.size > max
|
||||
@result_cache << @results_enum.next
|
||||
end
|
||||
return false unless (@query.options[:between] === @result_cache.size)
|
||||
return 0 if (@query.options[:between] === @result_cache.size)
|
||||
return -1 if @result_cache.size < min
|
||||
return 1
|
||||
end
|
||||
|
||||
return true
|
||||
return 0
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def matches_count?
|
||||
compare_count == 0
|
||||
end
|
||||
|
||||
def failure_message
|
||||
message = @query.failure_message
|
||||
if count > 0
|
||||
|
@ -113,7 +120,7 @@ module Capybara
|
|||
message << " but there were no matches"
|
||||
end
|
||||
unless rest.empty?
|
||||
elements = rest.map(&:text).map(&:inspect).join(", ")
|
||||
elements = rest.map { |el| el.text rescue "<<ERROR>>" }.map(&:inspect).join(", ")
|
||||
message << ". Also found " << elements << ", which matched the selector but not all filters."
|
||||
end
|
||||
message
|
||||
|
|
|
@ -159,7 +159,7 @@ Capybara.add_selector(:link) do
|
|||
when Regexp
|
||||
node[:href].match href
|
||||
else
|
||||
node.first(:xpath, XPath.self[XPath.attr(:href).equals(href.to_s)], minimum: 0)
|
||||
node.first(:xpath, XPath.self[XPath.attr(:href).equals(href.to_s)], allow_nil: true, wait: false)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -371,9 +371,9 @@ Capybara.add_selector(:select) do
|
|||
|
||||
filter(:options) do |node, options|
|
||||
if node.visible?
|
||||
actual = node.all(:xpath, './/option').map { |option| option.text }
|
||||
actual = node.all(:xpath, './/option', wait: false).map { |option| option.text }
|
||||
else
|
||||
actual = node.all(:xpath, './/option', visible: false).map { |option| option.text(:all) }
|
||||
actual = node.all(:xpath, './/option', visible: false, wait: false).map { |option| option.text(:all) }
|
||||
end
|
||||
options.sort == actual.sort
|
||||
end
|
||||
|
@ -387,12 +387,12 @@ Capybara.add_selector(:select) do
|
|||
end
|
||||
|
||||
filter(:selected) do |node, selected|
|
||||
actual = node.all(:xpath, './/option', visible: false).select(&:selected?).map { |option| option.text(:all) }
|
||||
actual = node.all(:xpath, './/option', visible: false, wait: false).select(&:selected?).map { |option| option.text(:all) }
|
||||
Array(selected).sort == actual.sort
|
||||
end
|
||||
|
||||
filter(:with_selected) do |node, selected|
|
||||
actual = node.all(:xpath, './/option', visible: false).select(&:selected?).map { |option| option.text(:all) }
|
||||
actual = node.all(:xpath, './/option', visible: false, wait: false).select(&:selected?).map { |option| option.text(:all) }
|
||||
(Array(selected) - actual).empty?
|
||||
end
|
||||
|
||||
|
|
|
@ -14,6 +14,18 @@ Capybara::SpecHelper.spec "#all" do
|
|||
expect(@session.all('//div[@id="nosuchthing"]')).to be_empty
|
||||
end
|
||||
|
||||
it "should wait for matching elements to appear", requires: [:js] do
|
||||
@session.visit('/with_js')
|
||||
@session.click_link('Click me')
|
||||
expect(@session.all(:css, "a#has-been-clicked")).not_to be_empty
|
||||
end
|
||||
|
||||
it "should not wait if `minimum: 0` option is specified", requires: [:js] do
|
||||
@session.visit('/with_js')
|
||||
@session.click_link('Click me')
|
||||
expect(@session.all(:css, "a#has-been-clicked", minimum: 0)).to be_empty
|
||||
end
|
||||
|
||||
it "should accept an XPath instance" do
|
||||
@session.visit('/form')
|
||||
@xpath = Capybara::Selector.all[:fillable_field].call('Name')
|
||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe Capybara::Result do
|
|||
end
|
||||
|
||||
let :result do
|
||||
string.all '//li'
|
||||
string.all '//li', minimum: 0 # pass minimum: 0 so lazy evaluation doesn't get triggered yet
|
||||
end
|
||||
|
||||
it "has a length" do
|
||||
|
|
Loading…
Add table
Reference in a new issue