class Browsery::Connector

A connector provides a thin layer that combines configuration files and access to the WebDriver. It's a thin layer in that, other than initialize, it is a drop-in replacement for WebDriver calls.

For example, if you usually access a method as `@driver.find_element`, you can still access them as the same method under `@connector.find_element`.

Attributes

finalization_queue[RW]
config[R]

Public Class Methods

browser_name() click to toggle source

Equivalent to @driver.browser

# File lib/browsery/connector.rb, line 121
def self.browser_name
  Thread.current[:active_connector].browser
end
finalize!(force = false) click to toggle source

Finalize connectors in the pool that are no longer used, and then clear the pool if it should be empty.

# File lib/browsery/connector.rb, line 48
def self.finalize!(force = false)
  return if Browsery.settings.reuse_driver? && !force

  if Thread.current[:active_connector]
    self.finalization_queue << Thread.current[:active_connector]
    Thread.current[:active_connector] = nil
  end

  return unless Browsery.settings.auto_finalize?

  while self.finalization_queue.size > 0
    connector = self.finalization_queue.pop
    begin
      connector.finalize!
    rescue => e
      Browsery.logger.error("Could not finalize Connector(##{connector.object_id}): #{e.message}")
    end
  end
end
get(connector_id, env_id) click to toggle source

Given a connector profile and an environment profile, this method will instantiate a connector object with the correct WebDriver instance and settings.

@raise ArgumentError @param connector [#to_s] the name of the connector profile to use. @param env [#to_s] the name of the environment profile to use. @return [Connector] an initialized connector object

# File lib/browsery/connector.rb, line 76
def self.get(connector_id, env_id)
  # Ensure arguments are at least provided
  raise ArgumentError, "A connector must be provided" if connector_id.blank?
  raise ArgumentError, "An environment must be provided" if env_id.blank?

  # Find the connector and environment profiles
  connector_cfg = self.load(Browsery.root.join('config/browsery', 'connectors'), connector_id)
  env_cfg = self.load(Browsery.root.join('config/browsery', 'environments'), env_id)
  cfg = Config.new(connector_cfg, env_cfg)

  if Thread.current[:active_connector] && !Browsery.settings.reuse_driver?
    self.finalization_queue << Thread.current[:active_connector]
    Thread.current[:active_connector] = nil
  end

  # If the current thread already has an active connector, and the connector
  # is of the same type requested, reuse it after calling `reset!`
  active_connector = Thread.current[:active_connector]
  if active_connector.present?
    if active_connector.config == cfg
      active_connector.reset!
    else
      self.finalization_queue << active_connector
      active_connector = nil
    end
  end

  # Reuse or instantiate
  Thread.current[:active_connector] = active_connector || Connector.new(cfg)
end
get_default() click to toggle source

Retrieve the default connector for the current environment.

@raise ArgumentError @return [Connector] an initialized connector object

# File lib/browsery/connector.rb, line 111
def self.get_default
  connector = Browsery.settings.connector
  env = Browsery.settings.env
  Browsery.logger.debug("Retrieving connector with settings (#{connector}, #{env})")

  # Get a connector instance and use it in the new page object
  self.get(connector, env)
end
load(path, selector) click to toggle source

Load profile from a specific path using the selector(s) specified.

@raise ArgumentError @param path [#to_path, to_s] the path in which to find the profile @param selector [String] semicolon-delimited selector set @return [Hash] immutable configuration values

# File lib/browsery/connector.rb, line 131
def self.load(path, selector)
  overrides = selector.to_s.split(/:/)
  name      = overrides.shift
  filepath  = path.join("#{name}.yml")
  raise ArgumentError, "Cannot load profile #{name.inspect} because #{filepath.inspect} does not exist" unless filepath.exist?

  cfg = YAML.load(ERB.new(File.read(filepath)).result)
  cfg = self.resolve(cfg, overrides)
  cfg.freeze
end
new(config) click to toggle source

Initialize a new connector with a set of configuration files.

@see Connector.get @api private

# File lib/browsery/connector.rb, line 176
def initialize(config)
  @config = config

  # Load and configure the WebDriver, if necessary
  if concon = config.connector
    driver_config = { }
    driver = concon[:driver]
    raise ArgumentError, "Connector driver must not be empty" if driver.nil?

    # Handle hub-related options, like hub URLs (for remote execution)
    if hub = concon[:hub]
      builder = URI.parse(hub[:url])
      builder.user     = hub[:user] if hub.has_key?(:user)
      builder.password = hub[:pass] if hub.has_key?(:pass)

      Browsery.logger.debug("Connector(##{self.object_id}): targeting remote #{builder.to_s}")
      driver_config[:url] = builder.to_s
    end

    # Handle driver-related timeouts
    if timeouts = concon[:timeouts]
      client = Selenium::WebDriver::Remote::Http::Default.new
      client.timeout = timeouts[:driver]
      driver_config[:http_client] = client
    end

    # Handle archetypal capability lists
    if archetype = concon[:archetype]
      Browsery.logger.debug("Connector(##{self.object_id}): using #{archetype.inspect} as capabilities archetype")
      caps = Selenium::WebDriver::Remote::Capabilities.send(archetype)
      if caps_set = concon[:capabilities]
        caps.merge!(caps_set)
      end
      driver_config[:desired_capabilities] = caps
    end

    # Load Firefox profile if specified - applicable only when using the firefoxdriver
    if profile = concon[:profile]
      driver_config[:profile] = profile
    end

    # Initialize the driver and declare explicit browser timeouts
    Browsery.logger.debug("Connector(##{self.object_id}): using WebDriver(#{driver.inspect}, #{driver_config.inspect})")
    @driver = Selenium::WebDriver.for(driver.to_sym, driver_config)

    # Resize browser window for local browser with 'resolution'
    if concon[:resolution]
      width = concon[:resolution].split(/x/)[0].to_i
      height = concon[:resolution].split(/x/)[1].to_i
      @driver.manage.window.resize_to(width, height)
    end

    # setTimeout is undefined for safari driver so skip these steps for it
    unless @driver.browser == :safari
      if timeouts = concon[:timeouts]
        @driver.manage.timeouts.implicit_wait  = timeouts[:implicit_wait]  if timeouts[:implicit_wait]
        @driver.manage.timeouts.page_load      = timeouts[:page_load]      if timeouts[:page_load]
        @driver.manage.timeouts.script_timeout = timeouts[:script_timeout] if timeouts[:script_timeout]
      end
    end
  end
end
resolve(cfg, overrides) click to toggle source

Resolve a set of profile overrides.

@param cfg [Hash] the configuration structure optionally containing a

key of `:overrides`

@param overrides [Enumerable<String>] @return [Hash] the resolved configuration

# File lib/browsery/connector.rb, line 148
def self.resolve(cfg, overrides)
  cfg = cfg.dup.with_indifferent_access

  if options = cfg.delete(:overrides)
    # Evaluate each override in turn, allowing each override to--well,
    # override--anything coming before it
    overrides.each do |override|
      if tree = options[override]
        cfg.deep_merge!(tree)
      end
    end
  end

  cfg
end

Public Instance Methods

finalize!() click to toggle source

Perform cleanup on the connector and driver.

# File lib/browsery/connector.rb, line 167
def finalize!
  @driver.quit
  true
end
method_missing(name, *args, &block) click to toggle source

Forward any other method call to the configuration container; if that fails, forward it to the WebDriver. The WebDriver will take care of any method resolution errors.

@param name [#to_sym] symbol representing the method call @param args [*Object] arguments to be passed along

# File lib/browsery/connector.rb, line 245
def method_missing(name, *args, &block)
  if @config.respond_to?(name)
    @config.send(name, *args, *block)
  else
    Browsery.logger.debug("Connector(##{self.object_id})->#{name}(#{args.map { |a| a.inspect }.join(', ')})")
    @driver.send(name, *args, &block)
  end
end
reset!() click to toggle source

Resets the current session by deleting all cookies and clearing all local and session storage. Local and session storage are only cleared if the underlying driver supports it, and even then, only if the storage supports atomic clearing.

@return [Boolean]

# File lib/browsery/connector.rb, line 260
def reset!
  @driver.manage.delete_all_cookies
  @driver.try(:local_storage).try(:clear)
  @driver.try(:session_storage).try(:clear)
  true
end
respond_to?(name) click to toggle source

Forward unhandled message checks to the configuration and driver.

@param name [#to_sym] @return [Boolean]

Calls superclass method
# File lib/browsery/connector.rb, line 271
def respond_to?(name)
  super || @config.respond_to?(name) || @driver.respond_to?(name)
end
url_for(path) click to toggle source

Compose a URL from the provided path and the environment profile. The latter contains things like the hostname, port, SSL settings.

@param path [#to_s] the path to append after the root URL. @return [URI] the composed URL.

# File lib/browsery/connector.rb, line 280
def url_for(path)
  root = @config.env[:root]
  raise ArgumentError, "The 'root' attribute is missing from the environment profile" unless root
  URI.join(root, path)
end