1
0
Fork 0
mirror of https://github.com/teamcapybara/capybara.git synced 2022-11-09 12:08:07 -05:00

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) def scroll_to(*args)
find(:xpath, '//body').scroll_to(*args) find(:xpath, '//body').scroll_to(*args)
end end
end end
end end
end end

View file

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

View file

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