111 lines
3.7 KiB
Ruby
111 lines
3.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Capybara
|
|
module Selenium
|
|
module Find
|
|
def find_xpath(selector, uses_visibility: false, styles: nil, position: false, **_options)
|
|
find_by(:xpath, selector, uses_visibility: uses_visibility, texts: [], styles: styles, position: position)
|
|
end
|
|
|
|
def find_css(selector, uses_visibility: false, texts: [], styles: nil, position: false, **_options)
|
|
find_by(:css, selector, uses_visibility: uses_visibility, texts: texts, styles: styles, position: position)
|
|
end
|
|
|
|
private
|
|
|
|
def find_by(format, selector, uses_visibility:, texts:, styles:, position:)
|
|
els = find_context.find_elements(format, selector)
|
|
hints = []
|
|
|
|
if (els.size > 2) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
|
|
els = filter_by_text(els, texts) unless texts.empty?
|
|
hints = gather_hints(els, uses_visibility: uses_visibility, styles: styles, position: position)
|
|
end
|
|
els.map.with_index { |el, idx| build_node(el, hints[idx] || {}) }
|
|
end
|
|
|
|
def gather_hints(elements, uses_visibility:, styles:, position:)
|
|
hints_js, functions = build_hints_js(uses_visibility, styles, position)
|
|
return [] unless functions.any?
|
|
|
|
(es_context.execute_script(hints_js, elements) || []).map! do |results|
|
|
hint = {}
|
|
hint[:style] = results.pop if functions.include?(:style_func)
|
|
hint[:position] = results.pop if functions.include?(:position_func)
|
|
hint[:visible] = results.pop if functions.include?(:vis_func)
|
|
hint
|
|
end
|
|
rescue ::Selenium::WebDriver::Error::StaleElementReferenceError,
|
|
::Capybara::NotSupportedByDriverError
|
|
# warn 'Unexpected Stale Element Error - skipping optimization'
|
|
[]
|
|
end
|
|
|
|
def filter_by_text(elements, texts)
|
|
es_context.execute_script <<~JS, elements, texts
|
|
var texts = arguments[1];
|
|
return arguments[0].filter(function(el){
|
|
var content = el.textContent.toLowerCase();
|
|
return texts.every(function(txt){ return content.indexOf(txt.toLowerCase()) != -1 });
|
|
})
|
|
JS
|
|
end
|
|
|
|
def build_hints_js(uses_visibility, styles, position)
|
|
functions = []
|
|
hints_js = +''
|
|
|
|
if uses_visibility && !is_displayed_atom.empty?
|
|
hints_js << <<~VISIBILITY_JS
|
|
var vis_func = #{is_displayed_atom};
|
|
VISIBILITY_JS
|
|
functions << :vis_func
|
|
end
|
|
|
|
if position
|
|
hints_js << <<~POSITION_JS
|
|
var position_func = function(el){
|
|
return el.getBoundingClientRect();
|
|
};
|
|
POSITION_JS
|
|
functions << :position_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
|
|
|
|
hints_js << <<~EACH_JS
|
|
return arguments[0].map(function(el){
|
|
return [#{functions.join(',')}].map(function(fn){ return fn.call(null, el) });
|
|
});
|
|
EACH_JS
|
|
|
|
[hints_js, functions]
|
|
end
|
|
|
|
def es_context
|
|
respond_to?(:execute_script) ? self : driver
|
|
end
|
|
|
|
def is_displayed_atom # rubocop:disable Naming/PredicateName
|
|
@@is_displayed_atom ||= begin # rubocop:disable Style/ClassVars
|
|
browser.send(:bridge).send(:read_atom, 'isDisplayed')
|
|
rescue StandardError
|
|
# If the atom doesn't exist or other error
|
|
''
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|