Use find hints to optimize Selenium style filter

This commit is contained in:
Thomas Walpole 2019-01-13 15:28:47 -08:00
parent c5f518880c
commit 9ce269c3ba
4 changed files with 59 additions and 24 deletions

View File

@ -43,7 +43,6 @@ module Capybara
def scroll_to(*args)
find(:xpath, '//body').scroll_to(*args)
end
end
end
end

View File

@ -405,7 +405,7 @@ module Capybara
when Symbol
synchronize { base.scroll_to(nil, pos_or_el_or_x) } unless pos_or_el_or_x == :current
when Capybara::Node::Element
synchronize { base.scroll_to(pos_or_el_or_x.base, align)}
synchronize { base.scroll_to(pos_or_el_or_x.base, align) }
else
synchronize { base.scroll_to(nil, nil, [pos_or_el_or_x, y]) }
end

View File

@ -180,19 +180,20 @@ module Capybara
end
def find_nodes_by_selector_format(node, exact)
options = {}
options[:uses_visibility] = true unless visible == :all
options[:texts] = text_fragments unless selector.format == :xpath
hints = {}
hints[:uses_visibility] = true unless visible == :all
hints[:texts] = text_fragments unless selector.format == :xpath
hints[:styles] = options[:style] if use_default_style_filter?
if selector.format == :css
if node.method(:find_css).arity != 1
node.find_css(css, **options)
node.find_css(css, **hints)
else
node.find_css(css)
end
elsif selector.format == :xpath
if node.method(:find_xpath).arity != 1
node.find_xpath(xpath(exact), **options)
node.find_xpath(xpath(exact), **hints)
else
node.find_xpath(xpath(exact))
end
@ -388,7 +389,7 @@ module Capybara
end
def matches_style?(node, styles)
@actual_styles = node.style(*styles.keys)
@actual_styles = node.initial_cache[:style] || node.style(*styles.keys)
styles.all? do |style, value|
if value.is_a? Regexp
@actual_styles[style.to_s] =~ value

View File

@ -3,32 +3,63 @@
module Capybara
module Selenium
module Find
def find_xpath(selector, uses_visibility: false, **_options)
find_by(:xpath, selector, uses_visibility: uses_visibility, texts: [])
def find_xpath(selector, uses_visibility: false, styles: nil, **_options)
find_by(:xpath, selector, uses_visibility: uses_visibility, texts: [], styles: styles)
end
def find_css(selector, uses_visibility: false, texts: [], **_options)
find_by(:css, selector, uses_visibility: uses_visibility, texts: texts)
def find_css(selector, uses_visibility: false, texts: [], styles: nil, **_options)
find_by(:css, selector, uses_visibility: uses_visibility, texts: texts, styles: styles)
end
private
def find_by(format, selector, uses_visibility:, texts:)
def find_by(format, selector, uses_visibility:, texts:, styles:)
els = find_context.find_elements(format, selector)
els = filter_by_text(els, texts) if (els.size > 1) && !texts.empty?
visibilities = uses_visibility && els.size > 1 ? element_visibilities(els) : []
els.map.with_index { |el, idx| build_node(el, visible: visibilities[idx]) }
end
hints = []
if (els.size > 2) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
els = filter_by_text(els, texts) unless texts.empty?
def element_visibilities(elements)
es_context = respond_to?(:execute_script) ? self : driver
es_context.execute_script <<~JS, elements
return arguments[0].map(#{is_displayed_atom})
JS
hints_js = +''
functions = []
if uses_visibility
hints_js << <<~VISIBILITY_JS
var vis_func = #{is_displayed_atom};
VISIBILITY_JS
functions << 'vis_func'
end
if styles.is_a? Hash
hints_js << <<~STYLE_JS
var style_func = function(el){
var el_styles = window.getComputedStyle(el);
return #{styles.keys.map(&:to_s)}.reduce(function(res, style){
res[style] = el_styles[style];
return res;
}, {});
};
STYLE_JS
functions << 'style_func'
end
unless functions.empty?
hints_js << <<~EACH_JS
return arguments[0].map(function(el){
return [#{functions.join(',')}].map(function(fn){ return fn.call(null, el) }); });
EACH_JS
hints = es_context.execute_script hints_js, els
hints.map! do |results|
result = {}
result[:style] = results.pop if styles.is_a? Hash
result[:visible] = results.pop if uses_visibility
result
end
end
end
els.map.with_index { |el, idx| build_node(el, hints[idx] || {}) }
end
def filter_by_text(elements, texts)
es_context = respond_to?(:execute_script) ? self : driver
es_context.execute_script <<~JS, elements, texts
var texts = arguments[1]
return arguments[0].filter(function(el){
@ -38,8 +69,12 @@ module Capybara
JS
end
def es_context
respond_to?(:execute_script) ? self : driver
end
def is_displayed_atom # rubocop:disable Naming/PredicateName
@is_displayed_atom ||= browser.send(:bridge).send(:read_atom, 'isDisplayed')
@@is_displayed_atom ||= browser.send(:bridge).send(:read_atom, 'isDisplayed')
end
end
end