strongqa/howitzer

View on GitHub
lib/howitzer/web/page.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'singleton'
require 'rspec/expectations'
require 'addressable/template'
require 'howitzer/meta'
require 'howitzer/web/capybara_methods_proxy'
require 'howitzer/web/page_validator'
require 'howitzer/web/element_dsl'
require 'howitzer/web/iframe_dsl'
require 'howitzer/web/page_dsl'
require 'howitzer/web/section_dsl'
require 'howitzer/exceptions'

module Howitzer
  module Web
    # This class represents a single web page. This is a parent class for all web pages
    class Page
      UnknownPage = Class.new # :nodoc:
      include Singleton
      include CapybaraMethodsProxy
      include ElementDsl
      include IframeDsl
      include PageDsl
      include SectionDsl
      include PageValidator
      include ::RSpec::Matchers
      include RSpec::Wait

      # This Ruby callback makes all inherited classes as singleton classes.

      def self.inherited(subclass)
        super
        subclass.class_eval { include Singleton }
      end

      # Opens a web page in browser
      # @note It tries to open the page twice and then raises the error if a validation is failed
      # @param validate [Boolean] if fase will skip current page validation (is opened)
      # @param url_processor [Class] custom url processor. For details see 'addressable' gem
      # @param params [Array] placeholder names and their values
      # @return [Page]

      def self.open(validate: true, url_processor: nil, **params)
        url = expanded_url(params, url_processor)
        Howitzer::Log.info "Open #{name} page by '#{url}' url"
        retryable(tries: 2, logger: Howitzer::Log, trace: true, on: Exception) do |retries|
          Howitzer::Log.info 'Retry...' unless retries.zero?
          Capybara.current_session.visit(url)
        end
        given if validate
      end

      # Returns a singleton instance of the web page
      # @return [Page]

      def self.given
        displayed?
        instance
      end

      # Tries to identify current page name or raise the error if ambiguous page matching
      # @return [String] a page name
      # @raise [UnknownPage] when no any matched pages
      # @raise [AmbiguousPageMatchingError] when matched more than 1 page

      def self.current_page
        page_list = matched_pages
        return UnknownPage if page_list.count.zero?
        return page_list.first if page_list.count == 1

        raise Howitzer::AmbiguousPageMatchingError, ambiguous_page_msg(page_list)
      end

      # Waits until a web page is opened
      # @param timeout [Integer] time in seconds a required web page to be loaded
      # @return [Boolean]
      # @raise [IncorrectPageError] when timeout expired and the page is not displayed

      def self.displayed?(timeout = Howitzer.page_load_idle_timeout)
        end_time = ::Time.now + timeout
        until ::Time.now > end_time
          return true if opened?

          sleep(0.5)
        end
        raise Howitzer::IncorrectPageError, incorrect_page_msg
      end

      # @return [String] current page url from browser

      def self.current_url
        Capybara.current_session.current_url
      end

      # Returns an expanded page url for the page opening
      # @param params [Array] placeholders and their values
      # @param url_processor [Class] custom url processor. For details see Addressable gem
      # @return [String]
      # @raise [NoPathForPageError] if an url is not specified for the page

      def self.expanded_url(params = {}, url_processor = nil)
        if defined?(path_value)
          return "#{site_value}#{Addressable::Template.new(path_value).expand(params, url_processor)}"
        end

        raise Howitzer::NoPathForPageError, "Please specify path for '#{self}' page. Example: path '/home'"
      end

      # Provides access to meta information about entities on the page
      # @return [Meta::Entry]
      def meta
        @meta ||= Meta::Entry.new(self)
      end

      class << self
        protected

        # DSL to specify an relative path pattern for the page opening
        # @param value [String] a path pattern, for details please see Addressable gem
        # @see .site
        # @example
        #   class ArticlePage < Howitzer::Web::Page
        #     url '/articles/:id'
        #   end
        #   ArticlePage.open(id: 10)
        # @!visibility public

        def path(value)
          define_singleton_method(:path_value) { value.to_s }
          private_class_method :path_value
        end

        # DSL to specify a site for the page opening
        # @note By default it specifies Howitzer.app_uri.site as a site
        # @param value [String] a site as combination of protocol, host and port
        # @example
        #   class AuthPage < Howitzer::Web::Page
        #     site 'https:/example.com'
        #   end
        #
        #   class LoginPage < AuthPage
        #     path '/login'
        #   end
        # @!visibility public

        def site(value)
          define_singleton_method(:site_value) { value }
          private_class_method :site_value
        end

        private

        def incorrect_page_msg
          "Current page: #{current_page}, expected: #{self}.\n" \
            "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
        end

        def ambiguous_page_msg(page_list)
          "Current page matches more that one page class (#{page_list.join(', ')}).\n" \
            "\tCurrent url: #{current_url}\n\tCurrent title: #{instance.title}"
        end
      end

      site Howitzer.app_uri.site

      def initialize
        check_validations_are_defined!
        current_window.maximize if Howitzer.maximized_window &&
                                   !%w[chrome headless_chrome].include?(Capybara.current_driver)
      end

      # Reloads current page in a browser

      def reload
        Howitzer::Log.info "Reload '#{current_url}'"
        visit current_url
      end
    end
  end
end