module MiniAutobot module Utils module Castable module ClassMethods # Attempts to create a new page object from a driver state. Use the # instance method for convenience. Raises `NameError` if the page could # not be found. # # @param driver [Selenium::WebDriver] The instance of the current # WebDriver. # @param name [#to_s] The name of the page object to instantiate. # @return [Base] A subclass of `Base` representing the page object. # @raise InvalidPageState if the page cannot be casted to # @raise NameError if the page object doesn't exist def cast(driver, name) # Transform the name string into a file path and then into a module name klass_name = "mini_autobot/page_objects/#{name}".camelize # Attempt to load the class klass = begin klass_name.constantize rescue => exc msg = "" msg << "Cannot find page object '#{name}', " msg << "because could not load class '#{klass_name}' " msg << "with underlying error:\n #{exc.class}: #{exc.message}\n" msg << exc.backtrace.map { |str| " #{str}" }.join("\n") raise NameError, msg end # Instantiate the class, passing the driver automatically, and # validates to ensure the driver is in the correct state instance = klass.new(driver) begin instance.validate! rescue Minitest::Assertion => exc raise MiniAutobot::PageObjects::InvalidePageState, "#{klass}: #{exc.message}" end instance end end # Extend the base class in which this module is included in order to # inject class methods. # # @param base [Class] # @return [void] def self.included(base) base.extend(ClassMethods) end # The preferred way to create a new page object from the current page's # driver state. Raises a NameError if the page could not be found. If # casting causes a StaleElementReferenceError, the method will retry up # to 2 more times. # # @param name [String] see {Base.cast} # @return [Base] The casted page object. # @raise InvalidPageState if the page cannot be casted to # @raise NameError if the page object doesn't exist def cast(name) tries ||= 3 self.class.cast(@driver, name).tap do |new_page| self.freeze MiniAutobot.logger.debug("Casting #{self.class}(##{self.object_id}) into #{new_page.class}(##{new_page.object_id})") end rescue Selenium::WebDriver::Error::StaleElementReferenceError => sere MiniAutobot.logger.debug("#{self.class}(##{@driver.object_id})->cast(#{name}) raised a potentially-swallowed StaleElementReferenceError") sleep 1 retry unless (tries -= 1).zero? end # Cast the page to any of the listed `names`, in order of specification. # Returns the first page that accepts the casting, or returns nil, rather # than raising InvalidPageState. # # @param names [Enumerable] see {Base.cast} # @return [Base, nil] the casted page object, if successful; nil otherwise. # @raise NameError if the page object doesn't exist def cast_any(*names) # Try one at a time, swallowing InvalidPageState exceptions names.each do |name| begin return self.cast(name) rescue InvalidPageState # noop end end # Return nil otherwise return nil end end end end