# frozen_string_literal: true module Watir # # Base class for element collections. # class ElementCollection include Enumerable include Exception include JSSnippets include Waitable include Locators::ClassHelpers def initialize(query_scope, selector) @query_scope = query_scope @selector = selector @to_a = nil build unless @selector.key?(:element) end # # Relocates elements then yields each element in resulting collection. # # @example # divs = browser.divs(class: 'kls') # divs.each do |div| # puts div.text # end # # @yieldparam [Watir::Element] element Iterate through the elements in this collection. # def each(&blk) reset! to_a.each(&blk) end alias length count alias size count alias empty? none? alias exist? any? alias exists? any? def build selector_builder.build(@selector.dup) end # # Get the element at the given index or range. # # Any call to an ElementCollection that includes an adjacent selector # can not be lazy loaded because it must store the correct type # # Ranges can not be lazy loaded # # @param [Integer, Range] value Index (0-based) or Range of desired element(s) # @return [Watir::Element, Watir::ElementCollection] Returns an instance of a Watir::Element subclass # def [](value) if value.is_a?(Range) to_a[value] elsif @selector.key? :adjacent to_a[value] || element_class.new(@query_scope, invalid_locator: true) elsif @to_a && @to_a[value] @to_a[value] else element_class.new(@query_scope, @selector.merge(index: value)) end end # # First element of the collection # # @return [Watir::Element] Returns an instance of a Watir::Element subclass # def first self[0] end # # Last element of the collection # # @return [Watir::Element] Returns an instance of a Watir::Element subclass # def last self[-1] end # # This collection as an Array. # # @return [Array] # def to_a hash = {} @to_a ||= elements_with_tags.map.with_index do |(el, tag_name), idx| selector = @selector.dup selector[:index] = idx unless idx.zero? element = element_class.new(@query_scope, selector) if [HTMLElement, Input].include? element.class construct_subtype(element, hash, tag_name).tap { |e| e.cache = el } else element.tap { |e| e.cache = el } end end end # # Locate all elements and return self. # # @return ElementCollection # def locate to_a self end # # Returns browser. # # @return [Watir::Browser] # def browser @query_scope.browser end # # Returns true if two element collections are equal. # # @example # browser.select_list(name: "new_user_languages").options == browser.select_list(id: "new_user_languages").options # #=> true # # @example # browser.select_list(name: "new_user_role").options == browser.select_list(id: "new_user_languages").options # #=> false # def ==(other) to_a == other.to_a end alias eql? == # # Removes cache of previously located elements in the collection. # # @example # options = browser.select_list(name: "new_user_languages").options # options.reset! # options[0] # #=> nil # def reset! @to_a = nil end private def elements ensure_context if selector_builder.built.key?(:scope) @query_scope.send(:element_call) { locate_all } else locate_all end end def elements_with_tags els = elements if @selector[:tag_name] els.map { |e| [e, @selector[:tag_name]] } else retries = 0 begin els.zip(execute_js(:getElementTags, els)) rescue Selenium::WebDriver::Error::StaleElementReferenceError retries += 1 sleep 0.5 retry unless retries > 2 raise LocatorException, "Unable to locate element collection from #{@selector} due to changing page" end end end def ensure_context if @query_scope.is_a?(Browser) || (!@query_scope.located? && @query_scope.is_a?(IFrame)) @query_scope.browser.locate elsif @query_scope.located? && @query_scope.stale? @query_scope.locate end @query_scope.switch_to! if @query_scope.is_a?(IFrame) end def locate_all locator.locate_all(selector_builder.built) end def element_class Watir.const_get(self.class.name.sub(/Collection$/, '')) end def construct_subtype(element, hash, tag_name) selector = element.selector hash[tag_name] ||= 0 hash[tag_name] += 1 selector[:index] = hash[tag_name] - 1 selector[:tag_name] = tag_name Watir.element_class_for(tag_name).new(@query_scope, selector) end end # ElementCollection end # Watir