Class: MiniAutobot::Connector

Inherits:
Object
  • Object
show all
Defined in:
lib/mini_autobot/connector.rb

Overview

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`.

Defined Under Namespace

Classes: Config

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Connector

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Initialize a new connector with a set of configuration files.

See Also:



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/mini_autobot/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)

      MiniAutobot.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]
      MiniAutobot.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
    MiniAutobot.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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

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.

Parameters:

  • name (#to_sym)

    symbol representing the method call

  • args (*Object)

    arguments to be passed along



245
246
247
248
249
250
251
252
# File 'lib/mini_autobot/connector.rb', line 245

def method_missing(name, *args, &block)
  if @config.respond_to?(name)
    @config.send(name, *args, *block)
  else
    MiniAutobot.logger.debug("Connector(##{self.object_id})->#{name}(#{args.map { |a| a.inspect }.join(', ')})")
    @driver.send(name, *args, &block)
  end
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



164
165
166
# File 'lib/mini_autobot/connector.rb', line 164

def config
  @config
end

Class Method Details

.browser_nameObject

Equivalent to @driver.browser



121
122
123
# File 'lib/mini_autobot/connector.rb', line 121

def self.browser_name
  Thread.current[:active_connector].browser
end

.finalize!(force = false) ⇒ Object

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



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/mini_autobot/connector.rb', line 48

def self.finalize!(force = false)
  return if MiniAutobot.settings.reuse_driver? && !force

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

  return unless MiniAutobot.settings.auto_finalize?

  while self.finalization_queue.size > 0
    connector = self.finalization_queue.pop
    begin
      connector.finalize!
    rescue => e
      MiniAutobot.logger.error("Could not finalize Connector(##{connector.object_id}): #{e.message}")
    end
  end
end

.get(connector_id, env_id) ⇒ Connector

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

Parameters:

  • connector (#to_s)

    the name of the connector profile to use.

  • env (#to_s)

    the name of the environment profile to use.

Returns:

  • (Connector)

    an initialized connector object

Raises:

  • ArgumentError



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/mini_autobot/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(MiniAutobot.root.join('config/mini_autobot', 'connectors'), connector_id)
  env_cfg = self.load(MiniAutobot.root.join('config/mini_autobot', 'environments'), env_id)
  cfg = Config.new(connector_cfg, env_cfg)

  if Thread.current[:active_connector] && !MiniAutobot.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_defaultConnector

Retrieve the default connector for the current environment.

Returns:

  • (Connector)

    an initialized connector object

Raises:

  • ArgumentError



111
112
113
114
115
116
117
118
# File 'lib/mini_autobot/connector.rb', line 111

def self.get_default
  connector = MiniAutobot.settings.connector
  env = MiniAutobot.settings.env
  MiniAutobot.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) ⇒ Hash

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

Parameters:

  • path (#to_path, #to_s)

    the path in which to find the profile

  • selector (String)

    semicolon-delimited selector set

Returns:

  • (Hash)

    immutable configuration values

Raises:

  • ArgumentError



131
132
133
134
135
136
137
138
139
140
# File 'lib/mini_autobot/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

.resolve(cfg, overrides) ⇒ Hash

Resolve a set of profile overrides.

Parameters:

  • cfg (Hash)

    the configuration structure optionally containing a key of ‘:overrides`

  • overrides (Enumerable<String>)

Returns:

  • (Hash)

    the resolved configuration



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/mini_autobot/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

Instance Method Details

#finalize!Object

Perform cleanup on the connector and driver.



167
168
169
170
# File 'lib/mini_autobot/connector.rb', line 167

def finalize!
  @driver.quit
  true
end

#reset!Boolean

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.

Returns:

  • (Boolean)


260
261
262
263
264
265
# File 'lib/mini_autobot/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) ⇒ Boolean

Forward unhandled message checks to the configuration and driver.

Parameters:

  • name (#to_sym)

Returns:

  • (Boolean)


271
272
273
# File 'lib/mini_autobot/connector.rb', line 271

def respond_to?(name)
  super || @config.respond_to?(name) || @driver.respond_to?(name)
end

#url_for(path) ⇒ URI

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

Parameters:

  • path (#to_s)

    the path to append after the root URL.

Returns:

  • (URI)

    the composed URL.

Raises:

  • (ArgumentError)


280
281
282
283
284
# File 'lib/mini_autobot/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