1
0
Fork 0
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:
Thomas Walpole 2017-11-14 12:14:24 -08:00
parent cb309a8bea
commit 9410b6c176
5 changed files with 56 additions and 26 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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')

View file

@ -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