strongqa/howitzer

View on GitHub
lib/howitzer/capybara_helpers.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'rest-client'
require 'howitzer/exceptions'

# There is an issue with supporting Ruby 3 by Selenium Webdriver 3.x version
# https://github.com/SeleniumHQ/selenium/issues/9001
# Migration to Selenium Webdriver 4 is planned when it will be released without alfa, beta stages.
# :nocov:
if Gem::Requirement.new('>=3').satisfied_by?(Gem::Version.new(RUBY_VERSION)) &&
   Gem::Requirement.new(['>=3', '<4']).satisfied_by?(Gem::Version.new(Selenium::WebDriver::VERSION))
  module Selenium
    module WebDriver
      module Remote
        class Bridge # rubocop:disable Style/Documentation
          class << self
            alias original_handshake handshake

            def handshake(opts = {})
              original_handshake(**opts)
            end
          end
        end
      end
    end
  end
end
# :nocov:

module Howitzer
  # This module holds capybara helpers methods
  module CapybaraHelpers
    CHECK_YOUR_SETTINGS_MSG = 'Please check your settings'.freeze # :nodoc:
    HOWITZER_KNOWN_BROWSERS = [ # :nodoc:
      CLOUD_BROWSERS = [
        SAUCE = :sauce,
        TESTINGBOT = :testingbot,
        BROWSERSTACK = :browserstack,
        CROSSBROWSERTESTING = :crossbrowsertesting
      ].freeze,
      LOCAL_BROWSERS = [
        HEADLESS_CHROME = :headless_chrome,
        HEADLESS_FIREFOX = :headless_firefox,
        SELENIUM = :selenium,
        SELENIUM_GRID = :selenium_grid
      ].freeze
    ].freeze

    # @return [Boolean] true if current driver related with SauceLab,
    #   Testingbot or Browserstack cloud service

    def cloud_driver?
      CLOUD_BROWSERS.include?(Howitzer.driver.to_sym)
    end

    # @return [Boolean] whether or not current browser is
    #   Internet Explorer.
    # @raise [CloudBrowserNotSpecifiedError] if cloud driver and missing browser name
    # @raise [SelBrowserNotSpecifiedError] if selenium driver and missing browser name

    def ie_browser?
      browser? :ie, :iexplore
    end

    # @return [Boolean] whether or not current browser is FireFox.
    # @raise [CloudBrowserNotSpecifiedError] if cloud driver and missing browser name
    # @raise [SelBrowserNotSpecifiedError] if selenium driver and missing browser name

    def ff_browser?
      browser? :ff, :firefox
    end

    # @return [Boolean] whether or not current browser is Google Chrome.
    # @raise [CloudBrowserNotSpecifiedError] if cloud driver and missing browser name
    # @raise [SelBrowserNotSpecifiedError] if selenium driver and missing browser name

    def chrome_browser?
      browser? :chrome
    end

    # @return [Boolean] whether or not current browser is Safari.
    # @raise [CloudBrowserNotSpecifiedError] if cloud driver and missing browser name
    # @raise [SelBrowserNotSpecifiedError] if selenium driver and missing browser name

    def safari_browser?
      browser? :safari
    end

    # @param time_in_numeric [Integer] number of seconds
    # @return [String] formatted duration time

    def duration(time_in_numeric)
      secs = time_in_numeric.to_i
      mins = secs / 60
      hours = mins / 60
      return "[#{hours}h #{mins % 60}m #{secs % 60}s]" if hours.positive?
      return "[#{mins}m #{secs % 60}s]" if mins.positive?
      return "[0m #{secs}s]" if secs >= 0
    end

    # Updates a job status on the job cloud
    # @note SauceLabs is currently supported only
    # @param json_data [Hash] for example, (passed: true)

    def update_cloud_job_status(json_data = {})
      case Howitzer.driver.to_sym
      when SAUCE then update_sauce_job_status(json_data)
      else
        '[NOT IMPLEMENTED]'
      end
    end

    # Tries to load appropriate driver gem
    # @param driver [String] a driver name
    # @param lib [String] what is required to load
    # @param gem [String] a gem name
    # @raise [LoadError] if the gem is missing in a bunder context

    def load_driver_gem!(driver, lib, gem)
      require lib
    rescue LoadError
      raise LoadError,
            "`:#{driver}` driver is unable to load `#{lib}`, please add `gem '#{gem}'` to your Gemfile."
    end

    # @return [Hash] selenium capabilities required for a cloud driver

    def required_cloud_caps
      {
        platform: Howitzer.cloud_platform,
        browserName: Howitzer.cloud_browser_name,
        version: Howitzer.cloud_browser_version,
        name: "#{prefix_name} #{Howitzer.cloud_browser_name}"
      }
    end

    # @return [Hash] selenium W3C capabilities required for a cloud driver

    def required_w3c_cloud_caps
      {
        browserName: Howitzer.cloud_browser_name,
        browserVersion: Howitzer.cloud_browser_version
      }
    end

    # @return [Boolean] whether or not Selenium is W3C compatible.

    def w3c_selenium?
      Gem::Requirement.new('>=4').satisfied_by?(Gem::Version.new(Selenium::WebDriver::VERSION))
    end

    # Buids selenium driver for a cloud service
    # @param app [<Rack>] a rack application that this server will contain
    # @param caps [Hash] remote capabilities
    # @param url [String] a remote hub url
    # @return [Capybara::Selenium::Driver]

    def cloud_driver(app, caps, url)
      http_client = ::Selenium::WebDriver::Remote::Http::Default.new
      http_client.read_timeout = Howitzer.cloud_http_idle_timeout
      http_client.open_timeout = Howitzer.cloud_http_idle_timeout
      options = {
        url: url,
        http_client: http_client,
        browser: :remote
      }
      options[w3c_selenium? ? :capabilities : :desired_capabilities] =
        ::Selenium::WebDriver::Remote::Capabilities.new(caps)
      driver = Capybara::Selenium::Driver.new(app, **options)
      driver.browser.file_detector = remote_file_detector
      driver
    end

    # @return [String] path to cloud resources (logs, videos, etc.)
    # @note Currently SauceLabs is supported only
    # @raise [ArgumentError] if unknown kind

    def cloud_resource_path(kind)
      case Howitzer.driver.to_sym
      when SAUCE then sauce_resource_path(kind)
      else
        '[NOT IMPLEMENTED]'
      end
    end

    private

    def browser?(*browser_aliases)
      return cloud_browser?(*browser_aliases) if cloud_driver?
      return selenium_browser?(*browser_aliases) if selenium_driver? || selenium_grid_driver?
    end

    def cloud_browser?(*browser_aliases)
      unless Howitzer.cloud_browser_name.nil?
        return browser_aliases.include?(Howitzer.cloud_browser_name.to_s.downcase.to_sym)
      end

      raise Howitzer::CloudBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG
    end

    def selenium_browser?(*browser_aliases)
      return browser_aliases.include?(Howitzer.selenium_browser.to_s.to_sym) unless Howitzer.selenium_browser.nil?

      raise Howitzer::SelBrowserNotSpecifiedError, CHECK_YOUR_SETTINGS_MSG
    end

    def selenium_driver?
      Howitzer.driver.to_sym == SELENIUM
    end

    def headless_chrome_driver?
      Howitzer.driver.to_sym == HEADLESS_CHROME
    end

    def headless_firefox_driver?
      Howitzer.driver.to_sym == HEADLESS_FIREFOX
    end

    def selenium_grid_driver?
      Howitzer.driver.to_sym == SELENIUM_GRID
    end

    def prefix_name
      (Howitzer.current_rake_task || 'ALL').upcase
    end

    def sauce_resource_path(kind)
      name =
        case kind
        when :video then 'video.flv'
        when :server_log then 'selenium-server.log'
        else
          raise ArgumentError, "Unknown '#{kind}' kind"
        end
      host = "https://#{Howitzer.cloud_auth_login}:#{Howitzer.cloud_auth_pass}@saucelabs.com"
      path = "/rest/#{Howitzer.cloud_auth_login}/jobs/#{session_id}/results/#{name}"
      "#{host}#{path}"
    end

    def update_sauce_job_status(json_data = {})
      host = "https://#{Howitzer.cloud_auth_login}:#{Howitzer.cloud_auth_pass}@saucelabs.com"
      path = "/rest/v1/#{Howitzer.cloud_auth_login}/jobs/#{session_id}"
      url = "#{host}#{path}"
      ::RestClient.put url, json_data.to_json, content_type: :json, accept: :json
    end

    def session_id
      Capybara.current_session.driver.browser.instance_variable_get(:@bridge).session_id
    end

    def remote_file_detector
      lambda do |args|
        str = args.first.to_s
        str if File.exist?(str)
      end
    end
  end
end