mirror of
https://github.com/teamcapybara/capybara.git
synced 2022-11-09 12:08:07 -05:00
Optimize Capybara::Result by evaluating filters lazily when possible
This commit is contained in:
parent
22dfca46b8
commit
676fba48e2
7 changed files with 118 additions and 17 deletions
|
@ -18,6 +18,7 @@ Release date: Unreleased
|
||||||
* Improved error messages for have_text matcher [Alex Chaffee, Thomas Walpole]
|
* Improved error messages for have_text matcher [Alex Chaffee, Thomas Walpole]
|
||||||
* The `:with` option for the field selector now accepts a regular expression for matching the field value [Uwe Kubosch]
|
* The `:with` option for the field selector now accepts a regular expression for matching the field value [Uwe Kubosch]
|
||||||
* Support matching on aria-label attribute when finding fields/links/buttons - Issue #1528 [Thomas Walpole]
|
* Support matching on aria-label attribute when finding fields/links/buttons - Issue #1528 [Thomas Walpole]
|
||||||
|
* Optimize Capybara::Result to only apply fields as necessary in common use-case of `.all[idx]` [Thomas Walpole]
|
||||||
|
|
||||||
#Version 2.7.1
|
#Version 2.7.1
|
||||||
Release date: 2016-05-01
|
Release date: 2016-05-01
|
||||||
|
|
|
@ -33,14 +33,14 @@ module Capybara
|
||||||
synchronize(query.wait) do
|
synchronize(query.wait) do
|
||||||
if query.match == :smart or query.match == :prefer_exact
|
if query.match == :smart or query.match == :prefer_exact
|
||||||
result = query.resolve_for(self, true)
|
result = query.resolve_for(self, true)
|
||||||
result = query.resolve_for(self, false) if result.size == 0 && !query.exact?
|
result = query.resolve_for(self, false) if result.empty? && !query.exact?
|
||||||
else
|
else
|
||||||
result = query.resolve_for(self)
|
result = query.resolve_for(self)
|
||||||
end
|
end
|
||||||
if query.match == :one or query.match == :smart and result.size > 1
|
if query.match == :one or query.match == :smart and result.size > 1
|
||||||
raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
|
raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
|
||||||
end
|
end
|
||||||
if result.size == 0
|
if result.empty?
|
||||||
raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
|
raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
|
||||||
end
|
end
|
||||||
result.first
|
result.first
|
||||||
|
|
|
@ -123,8 +123,7 @@ module Capybara
|
||||||
query = Capybara::Queries::SelectorQuery.new(*args)
|
query = Capybara::Queries::SelectorQuery.new(*args)
|
||||||
synchronize(query.wait) do
|
synchronize(query.wait) do
|
||||||
result = query.resolve_for(self)
|
result = query.resolve_for(self)
|
||||||
matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
|
unless result.matches_count? && ((!result.empty?) || Capybara::Helpers.expects_none?(query.options))
|
||||||
unless matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
|
|
||||||
raise Capybara::ExpectationNotMet, result.failure_message
|
raise Capybara::ExpectationNotMet, result.failure_message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -151,8 +150,7 @@ module Capybara
|
||||||
query = Capybara::Queries::SelectorQuery.new(*args)
|
query = Capybara::Queries::SelectorQuery.new(*args)
|
||||||
synchronize(query.wait) do
|
synchronize(query.wait) do
|
||||||
result = query.resolve_for(self)
|
result = query.resolve_for(self)
|
||||||
matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
|
if result.matches_count? && ((!result.empty?) || Capybara::Helpers.expects_none?(query.options))
|
||||||
if matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
|
|
||||||
raise Capybara::ExpectationNotMet, result.negative_failure_message
|
raise Capybara::ExpectationNotMet, result.negative_failure_message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,15 +45,19 @@ module Capybara
|
||||||
regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
|
regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
|
||||||
return false if not node.text(visible).match(regexp)
|
return false if not node.text(visible).match(regexp)
|
||||||
end
|
end
|
||||||
|
|
||||||
case visible
|
case visible
|
||||||
when :visible then return false unless node.visible?
|
when :visible then return false unless node.visible?
|
||||||
when :hidden then return false if node.visible?
|
when :hidden then return false if node.visible?
|
||||||
end
|
end
|
||||||
query_filters.each do |name, filter|
|
|
||||||
|
query_filters.all? do |name, filter|
|
||||||
if options.has_key?(name)
|
if options.has_key?(name)
|
||||||
return false unless filter.matches?(node, options[name])
|
filter.matches?(node, options[name])
|
||||||
elsif filter.default?
|
elsif filter.default?
|
||||||
return false unless filter.matches?(node, filter.default)
|
filter.matches?(node, filter.default)
|
||||||
|
else
|
||||||
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,27 +25,74 @@ module Capybara
|
||||||
|
|
||||||
def initialize(elements, query)
|
def initialize(elements, query)
|
||||||
@elements = elements
|
@elements = elements
|
||||||
@result = elements.select { |node| query.matches_filters?(node) }
|
@result_cache = []
|
||||||
@rest = @elements - @result
|
@results_enum = lazy_select_elements { |node| query.matches_filters?(node) }
|
||||||
@query = query
|
@query = query
|
||||||
end
|
end
|
||||||
|
|
||||||
def_delegators :@result, :each, :[], :at, :size, :count, :length,
|
def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
|
||||||
:first, :last, :values_at, :empty?, :inspect, :sample, :index
|
|
||||||
|
alias :index :find_index
|
||||||
|
|
||||||
|
def each(&block)
|
||||||
|
@result_cache.each(&block)
|
||||||
|
loop do
|
||||||
|
next_result = @results_enum.next
|
||||||
|
@result_cache << next_result
|
||||||
|
block.call(next_result)
|
||||||
|
end
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](*args)
|
||||||
|
if (args.size == 1) && ((idx = args[0]).is_a? Integer) && (idx > 0)
|
||||||
|
@result_cache << @results_enum.next while @result_cache.size <= idx
|
||||||
|
@result_cache[idx]
|
||||||
|
else
|
||||||
|
full_results[*args]
|
||||||
|
end
|
||||||
|
rescue StopIteration
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
alias :at :[]
|
||||||
|
|
||||||
|
def empty?
|
||||||
|
!any?
|
||||||
|
end
|
||||||
|
|
||||||
def matches_count?
|
def matches_count?
|
||||||
Capybara::Helpers.matches_count?(@result.size, @query.options)
|
return Integer(@query.options[:count]) == count if @query.options[:count]
|
||||||
|
|
||||||
|
return false if @query.options[:between] && !(@query.options[:between] === count)
|
||||||
|
|
||||||
|
if @query.options[:minimum]
|
||||||
|
begin
|
||||||
|
@result_cache << @results_enum.next while @result_cache.size < Integer(@query.options[:minimum])
|
||||||
|
rescue StopIteration
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if @query.options[:maximum]
|
||||||
|
begin
|
||||||
|
@result_cache << @results_enum.next while @result_cache.size <= Integer(@query.options[:maximum])
|
||||||
|
return false
|
||||||
|
rescue StopIteration
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
def failure_message
|
def failure_message
|
||||||
message = Capybara::Helpers.failure_message(@query.description, @query.options)
|
message = Capybara::Helpers.failure_message(@query.description, @query.options)
|
||||||
if count > 0
|
if count > 0
|
||||||
message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << @result.map(&:text).map(&:inspect).join(", ")
|
message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << full_results.map(&:text).map(&:inspect).join(", ")
|
||||||
else
|
else
|
||||||
message << " but there were no matches"
|
message << " but there were no matches"
|
||||||
end
|
end
|
||||||
unless @rest.empty?
|
unless rest.empty?
|
||||||
elements = @rest.map(&:text).map(&:inspect).join(", ")
|
elements = rest.map(&:text).map(&:inspect).join(", ")
|
||||||
message << ". Also found " << elements << ", which matched the selector but not all filters."
|
message << ". Also found " << elements << ", which matched the selector but not all filters."
|
||||||
end
|
end
|
||||||
message
|
message
|
||||||
|
@ -54,5 +101,30 @@ module Capybara
|
||||||
def negative_failure_message
|
def negative_failure_message
|
||||||
failure_message.sub(/(to find)/, 'not \1')
|
failure_message.sub(/(to find)/, 'not \1')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def full_results
|
||||||
|
loop do
|
||||||
|
@result_cache << @results_enum.next
|
||||||
|
end
|
||||||
|
@result_cache
|
||||||
|
end
|
||||||
|
|
||||||
|
def rest
|
||||||
|
@rest ||= @elements - full_results
|
||||||
|
end
|
||||||
|
|
||||||
|
def lazy_select_elements(&block)
|
||||||
|
if @elements.respond_to? :lazy #Ruby 2.0+
|
||||||
|
@elements.lazy.select &block
|
||||||
|
else
|
||||||
|
Enumerator.new do |yielder|
|
||||||
|
@elements.each do |val|
|
||||||
|
yielder.yield(val) if block.call(val)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -158,6 +158,7 @@ Capybara.add_selector(:field) do
|
||||||
with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
|
with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
|
||||||
end
|
end
|
||||||
filter(:type) do |node, type|
|
filter(:type) do |node, type|
|
||||||
|
type = type.to_s
|
||||||
if ['textarea', 'select'].include?(type)
|
if ['textarea', 'select'].include?(type)
|
||||||
node.tag_name == type
|
node.tag_name == type
|
||||||
else
|
else
|
||||||
|
|
|
@ -63,4 +63,29 @@ RSpec.describe Capybara::Result do
|
||||||
el.text == 'Gamma'
|
el.text == 'Gamma'
|
||||||
end).to eq(2)
|
end).to eq(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'supports all modes of []' do
|
||||||
|
expect(result[1].text).to eq 'Beta'
|
||||||
|
expect(result[0,2].map &:text).to eq ['Alpha', 'Beta']
|
||||||
|
expect(result[1..3].map &:text).to eq ['Beta', 'Gamma', 'Delta']
|
||||||
|
expect(result[-1].text).to eq 'Delta'
|
||||||
|
end
|
||||||
|
|
||||||
|
#Not a great test but it indirectly tests what is needed
|
||||||
|
it "should evaluate filters lazily" do
|
||||||
|
#Not processed until accessed
|
||||||
|
expect(result.instance_variable_get('@result_cache').size).to be 0
|
||||||
|
|
||||||
|
#Only one retrieved when needed
|
||||||
|
result.first
|
||||||
|
expect(result.instance_variable_get('@result_cache').size).to be 1
|
||||||
|
|
||||||
|
#works for indexed access
|
||||||
|
result[2]
|
||||||
|
expect(result.instance_variable_get('@result_cache').size).to be 3
|
||||||
|
|
||||||
|
#All cached when converted to array
|
||||||
|
result.to_a
|
||||||
|
expect(result.instance_variable_get('@result_cache').size).to eq 4
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue