# frozen_string_literal: true module Watir module Locators class Element class Matcher include Exception include JSSnippets attr_reader :query_scope, :selector def initialize(query_scope, selector = {}) @query_scope = query_scope @selector = selector end def match(elements, values_to_match, filter) elements = matching_labels(elements, values_to_match) matching_elements(elements, values_to_match, filter: filter) end private def matching_labels(elements, values_to_match) %i[label_element visible_label_element].each do |key| label_value = values_to_match.delete(key) next if label_value.nil? locator_key = key.to_s.gsub('label', 'text').gsub('_element', '').to_sym return label_collection(elements, locator_key => label_value).compact end elements end def label_collection(elements, locator) @query_scope.labels.map do |label| next unless elements_match?(label.wd, locator) input = label.for.empty? ? label.input : Watir::Input.new(@query_scope, id: label.for) input.wd if elements.include?(input.wd) end end def matching_elements(elements, values_to_match, filter: :first) if filter == :first idx = element_index(elements, values_to_match) # Lazy evaluation to avoid fetching values for elements that will be discarded matches = elements.lazy.select do |el| elements_match?(el, values_to_match) end Watir.logger.debug "Iterating through #{elements.size} elements to locate #{@selector.inspect}" matches.take(idx + 1).to_a[idx] else Watir.logger.debug "Iterating through #{elements.size} elements to locate all #{@selector.inspect}" elements.select { |el| elements_match?(el, values_to_match) } end end def elements_match?(element, values_to_match) values_to_match.all? do |how, expected| if how == :tag_name validate_tag(element, expected) # TODO: Can this be class_name here or does that get converted? elsif %i[class class_name].include?(how) value = fetch_value(element, how) [expected].flatten.all? do |match| value.split.any? do |class_value| matches_values?(class_value, match) end end else matches_values?(fetch_value(element, how), expected) end end end def matches_values?(found, expected) expected.is_a?(Regexp) ? found =~ expected : found == expected end def fetch_value(element, how) case how when :tag_name element.tag_name.downcase when :text execute_js(:getTextContent, element).gsub(/\s+/, ' ').strip when :visible element.displayed? when :visible_text element.text when :href element.attribute('href')&.strip when :class, :class_name element.attribute('class') else how = how.to_s.tr('_', '-') if how.is_a?(::Symbol) element.attribute(how) end end def element_index(elements, values_to_match) idx = values_to_match.delete(:index) || 0 return idx unless idx.negative? elements.reverse! idx.abs - 1 end def validate_tag(element, expected) tag_name = fetch_value(element, :tag_name) matches_values?(tag_name, expected) end end end end end