Class: MediawikiSelenium::Environment

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/mediawiki_selenium/environment.rb

Overview

Provides an interface that unifies environmental configuration, page objects, and browser setup. Additionally, it provides a DSL for switching between user/wiki/browser contexts in ways that help to decouple test implementation from the target wikis.

Default configuration for various resources (wiki URLs, users, etc.) is typically loaded from an environments.yml YAML file in the current working directory. It should contain defaults for each environment in which the tests are expected to run, indexed by environment name.

beta:
  mediawiki_url: http://en.wikipedia.beta.wmflabs.org/wiki/
  mediawiki_user: Selenium_user
test2:
  mediawiki_url: http://test2.wikipedia.org/wiki/
  mediawiki_user: Selenium_user

Which default set to use is determined by the value of the MEDIAWIKI_ENVIRONMENT environment variable, or an entry called "default" if none is set. (See Environment.load and Environment.load_default.) The easiest way to designate such a default set is to use a YAML anchor like so.

beta: &default
  mediawiki_url: http://en.wikipedia.beta.wmflabs.org/wiki/
  mediawiki_user: Selenium_user
test2:
  mediawiki_url: http://test2.wikipedia.org/wiki/
  mediawiki_user: Selenium_user
default: *default

Any additional configuration specified via environment variables overrides what is specified in the YAML file. For example, the following would use the default configuration as specified under beta in the YAML file but define mediawiki_user as Other_user instead of Selenium_user.

export MEDIAWIKI_ENVIRONMENT=beta MEDIAWIKI_USER=Other_user
bundle exec cucumber ...

There are various methods that allow you to perform actions in the context of some alternative resource, for example as a different user using #as_user, or on different wiki using #on_wiki. Instead of referencing the exact user names or URLs for these resources, you reference them by an ID which corresponds to configuration made in environments.yml.

# environments.yml:
beta:
  # ...
  mediawiki_user_b: Selenium_user2

# step definition:
Given(/^user B has linked to a page I created$/) do
  as_user(:b) { api.create_page(...) }
end

This level of abstraction is intended to reduce coupling between tests and test environments, and should promote step definitions that are more readable and congruent with the natural-language steps they implement.

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*configs) ⇒ Environment

Returns a new instance of Environment.



123
124
125
126
127
128
129
# File 'lib/mediawiki_selenium/environment.rb', line 123

def initialize(*configs)
  @_config = configs.map { |config| normalize_config(config) }.reduce(:merge)
  @_factory_cache = {}
  @_current_alternatives = {}

  extend(HeadlessHelper) if headless?
end

Class Attribute Details

.default_configurationObject

Returns the value of attribute default_configuration.



66
67
68
# File 'lib/mediawiki_selenium/environment.rb', line 66

def default_configuration
  @default_configuration
end

.default_test_directoryObject

Returns the value of attribute default_test_directory.



66
67
68
# File 'lib/mediawiki_selenium/environment.rb', line 66

def default_test_directory
  @default_test_directory
end

Class Method Details

.load(name, extra = {}, test_dir = nil) ⇒ Object

Instantiates a new environment using the given set of default configuration from environments.yml in the current working directory, and the additional hash of environment variables.

environments.yml

Parameters:

  • name (String)

    Name of the environment.

  • extra (Hash) (defaults to: {})

    Additional configuration to use.

  • test_dir (String) (defaults to: nil)

    Path from which to search upward for



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/mediawiki_selenium/environment.rb', line 77

def load(name, extra = {}, test_dir = nil)
  name = name.to_s
  configs = []

  unless name.empty?
    envs = YAML.load_file(search_for_configuration(test_dir || default_test_directory))
    raise ConfigurationError, "unknown environment `#{name}`" unless envs.include?(name)
    configs << envs[name]
  end

  configs << extra

  env = new(*configs)
  env
end

.load_default(test_dir = nil) ⇒ Object

Instantiates a new environment from the values of ENV and the default configuration corresponding to ENV["MEDIAWIKI_ENVIRONMENT"], if one is defined.

See Also:



99
100
101
# File 'lib/mediawiki_selenium/environment.rb', line 99

def load_default(test_dir = nil)
  load(ENV['MEDIAWIKI_ENVIRONMENT'] || 'default', ENV, test_dir)
end

.search_for_configuration(path) ⇒ String

Searches for environments.yml in the given path. If it isn't found, the search continues upward in the directory hierarchy.

Parameters:

  • path (String)

    Path to search for configuration

Returns:

  • (String)

    Qualified path to the configuration file



110
111
112
113
114
115
116
117
# File 'lib/mediawiki_selenium/environment.rb', line 110

def search_for_configuration(path)
  return default_configuration if path.nil? || path.empty? || path == '.'

  file_path = File.join(path, default_configuration)
  return file_path if File.exist?(file_path)

  search_for_configuration(File.dirname(path))
end

Instance Method Details

#==(other) ⇒ Boolean

Whether the given environment is equal to this one. Two environments are considered equal if they have identical configuration.

Parameters:

Returns:

  • (Boolean)


138
139
140
# File 'lib/mediawiki_selenium/environment.rb', line 138

def ==(other)
  config == other.config
end

#[](key) ⇒ String

Returns the configured value for the given env variable name.

Parameters:

  • key (Symbol)

    Environment variable name.

Returns:

  • (String)

See Also:



150
151
152
# File 'lib/mediawiki_selenium/environment.rb', line 150

def [](key)
  lookup(key)
end

#as_user(id) {|user, password| ... } ⇒ Object

Executes the given block within the context of an environment that's using the given alternative user and its password.

Examples:

Given(/^user B has linked to a page I created$/) do
  as_user(:b) { api.create_page(...) }
end

Parameters:

  • id (Symbol)

    Alternative user ID.

Yields:

Yield Parameters:

  • user (String)

    Alternative MediaWiki user.

  • password (String)

    Alternative MediaWiki password.



168
169
170
171
172
# File 'lib/mediawiki_selenium/environment.rb', line 168

def as_user(id, &blk)
  record_alternatives([:mediawiki_user, :mediawiki_password], id) do
    with(mediawiki_user: user(id), mediawiki_password: password(id), &blk)
  end
end

#browserWatir::Browser

Browser with which to drive tests.

Returns:

  • (Watir::Browser)


178
179
180
# File 'lib/mediawiki_selenium/environment.rb', line 178

def browser
  browser_factory.browser_for(browser_config)
end

#browser_factory(browser = browser_name) ⇒ BrowserFactory::Base

Factory used to instantiate and open new browsers.

Parameters:

  • browser (Symbol) (defaults to: browser_name)

    Browser name.

Returns:



188
189
190
191
192
193
194
195
# File 'lib/mediawiki_selenium/environment.rb', line 188

def browser_factory(browser = browser_name)
  browser = browser.to_s.downcase.to_sym

  @_factory_cache[[remote?, browser]] ||= BrowserFactory.new(browser).tap do |factory|
    factory.configure(:_browser_session)
    factory.extend(RemoteBrowserFactory) if remote?
  end
end

#browser_nameSymbol

Name of the browser we're using. If the :browser configuration contains a version at the end, only the name is returned.

Examples:

env = Environment.new(browser: 'internet_explorer 8.0')
env.browser_name # => :internet_explorer

Returns:

  • (Symbol)


206
207
208
# File 'lib/mediawiki_selenium/environment.rb', line 206

def browser_name
  browser_spec[0].to_sym
end

#browser_tagsArray<String>

Tag names that can be used to filter test scenarios for a specific browser and/or version.

Returns:

  • (Array<String>)


215
216
217
218
219
220
221
222
# File 'lib/mediawiki_selenium/environment.rb', line 215

def browser_tags
  tags = [browser_name.to_s]

  version = browser_version
  tags << "#{browser_name}_#{version}" if version

  tags
end

#browser_versionString

Version of the browser we're using. If a :version configuration is provided, that value is returned. Otherwise a version is searched for in the :browser configuration.

Returns:

  • (String)


230
231
232
# File 'lib/mediawiki_selenium/environment.rb', line 230

def browser_version
  lookup(:version, default: browser_spec[1])
end

#current_alternative(key) ⇒ Object

Returns the current alternate ID for the given configuration key.

Parameters:

  • key (Symbol)

    Configuration key.



238
239
240
# File 'lib/mediawiki_selenium/environment.rb', line 238

def current_alternative(key)
  @_current_alternatives[key]
end

#envself

A reference to this environment. Can be used in conjunction with #[] for syntactic sugar in looking up environment configuration where self would otherwise seem ambiguous.

Examples:

Then(/^I see my username on the page$/) do
  expect(on(SomePage).html).to include(env[:mediawiki_user])
end

Returns:

  • (self)


253
254
255
# File 'lib/mediawiki_selenium/environment.rb', line 253

def env
  self
end

#headless?true, false

Whether this environment is configured to run in headless mode (using Xvfb via the headless gem).

Returns:

  • (true, false)


262
263
264
# File 'lib/mediawiki_selenium/environment.rb', line 262

def headless?
  lookup(:headless, default: 'false').to_s == 'true'
end

#in_browser(id, overrides = {}) {|*args| ... } ⇒ Object

Executes the given block within the context of an environment that uses a unique browser session and possibly different configuration. Note that any given configuration overrides are scoped with a :browser_ prefix.

Examples:

Implement a "logged out" step following some authenticated one

When(/^I do something while logged in$/) do
  in_browser(:a) do
    # perform action in logged in session
  end
end

When(/^I do something else after logging out$/) do
  in_browser(:b) do
    # perform action in logged out session without actually logging
    # out since that would affect all auth sessions for the user
  end
end

Perform a subsequent step requiring a different browser language

When(/^I visit the same page with my browser in Spanish$/) do |scenario, block|
  in_browser(:a, language: "es") do
    # test that it now serves up Spanish text
  end
end

Parameters:

  • id (Symbol)

    Browser session ID.

  • overrides (Hash) (defaults to: {})

    Browser configuration overrides.

Yields:

  • (*args)

    Overridden browser configuration.



296
297
298
299
300
301
302
# File 'lib/mediawiki_selenium/environment.rb', line 296

def in_browser(id, overrides = {}, &blk)
  overrides = overrides.each.with_object({}) do |(name, value), hash|
    hash["browser_#{name}".to_sym] = value
  end

  with(overrides.merge(_browser_session: id), &blk)
end

#keep_browser_open?Boolean

Whether browsers should be left open after each scenario completes.

Returns:

  • (Boolean)


306
307
308
# File 'lib/mediawiki_selenium/environment.rb', line 306

def keep_browser_open?
  lookup(:keep_browser_open, default: 'false') == 'true'
end

#lookup(key, options = {}) ⇒ String

Returns the configured value for the given env variable name.

Examples:

Value of :browser_language and fail if it wasn't provided

env.lookup(:browser_language)

Value of :browser_language alternative :b

env.lookup(:browser_language, id: :b)

Value of :browser_language or try :browser_lang

env.lookup(:browser_language, default: -> { env.lookup(:browser_lang) })

Parameters:

  • key (Symbol)

    Environment variable name.

  • options (Hash) (defaults to: {})

    Options.

Options Hash (options):

  • :id (Symbol)

    Alternative ID.

  • :default (Object, Proc)

    Default value or promise of a value.

Returns:

  • (String)


328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/mediawiki_selenium/environment.rb', line 328

def lookup(key, options = {})
  key = "#{key}_#{options[:id]}" if options.fetch(:id, nil)
  key = normalize_key(key)

  value = config[key]

  if value.nil? || value.to_s.empty?
    if options.include?(:default)
      options[:default].is_a?(Proc) ? options[:default].call : options[:default]
    else
      raise ConfigurationError, "missing configuration for `#{key}`"
    end
  else
    value
  end
end

#lookup_all(keys, options = {}) ⇒ Hash{Symbol => String}

Returns the configured values for the given env variable names.

Parameters:

  • keys (Array<Symbol>)

    Environment variable names.

  • options (Hash) (defaults to: {})

    Options.

Options Hash (options):

  • :id (Symbol)

    Alternative ID.

  • :default (Object)

    Default if no configuration is found.

Returns:

  • (Hash{Symbol => String})

See Also:



356
357
358
359
360
# File 'lib/mediawiki_selenium/environment.rb', line 356

def lookup_all(keys, options = {})
  keys.each.with_object({}) do |key, hash|
    hash[key] = lookup(key, options)
  end
end

#on_wiki(id) {|wiki_url| ... } ⇒ Object

Executes the given block within the context of an environment that's using the given alternative wiki URL and its corresponding API endpoint.

If no API URL is explicitly defined for the given alternative, one is constructed relative to the wiki URL.

Examples:

Visit a random page on wiki B

on_wiki(:b) { visit(RandomPage) }

Parameters:

  • id (Symbol)

    Alternative wiki ID.

Yields:

Yield Parameters:

  • wiki_url (String)

    Alternative wiki URL.



376
377
378
# File 'lib/mediawiki_selenium/environment.rb', line 376

def on_wiki(id, &blk)
  with_alternative(:mediawiki_url, id, &blk)
end

#password(id = nil) ⇒ String

Returns the current value for :mediawiki_password or the value for the given alternative.

Parameters:

  • id (Symbol) (defaults to: nil)

    Alternative user ID.

Returns:

  • (String)


387
388
389
# File 'lib/mediawiki_selenium/environment.rb', line 387

def password(id = nil)
  lookup(:mediawiki_password, id: id, default: -> { lookup(:mediawiki_password) })
end

#remote?Boolean

Whether this environment has been configured to use remote browser sessions.

Returns:

  • (Boolean)


396
397
398
# File 'lib/mediawiki_selenium/environment.rb', line 396

def remote?
  RemoteBrowserFactory::REQUIRED_CONFIG.all? { |name| lookup(name, default: false) }
end

#setup(info = {}) ⇒ Object

Executes setup tasks, annotating the Selenium session with any configured job_name and build_number.

Additional helpers may perform their own tasks by implementing this method.

Examples:

Setup the environment before each scenario starts

Before do |scenario|
  setup(name: scenario.name)
end

Parameters:

  • info (Hash) (defaults to: {})

    Hash of test case information.



413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/mediawiki_selenium/environment.rb', line 413

def setup(info = {})
  browser_factory.configure do |options|
    options[:desired_capabilities][:name] = info[:name] || 'scenario'
  end

  browser_factory.configure(:job_name) do |job, options|
    options[:desired_capabilities][:name] += " #{job}"
  end

  browser_factory.configure(:build_number) do |build, options|
    options[:desired_capabilities][:name] += "##{build}"
  end
end

#teardown(info = {}) {|browser| ... } ⇒ Hash{String => String}

Executes teardown tasks including instructing all browser factories to close any open browsers and perform their own teardown tasks.

Teardown tasks may produce artifacts, which will be returned in the form { path => mime_type }.

Examples:

Teardown environment resources after each scenario completes

After do |scenario|
  artifacts = teardown(name: scenario.name, status: scenario.status)
  artifacts.each { |path, mime_type| embed(path, mime_type) }
end

Parameters:

  • info (Hash) (defaults to: {})

    Hash of test case information.

Yields:

Yield Parameters:

  • browser (Watir::Browser)

    Browser object, before it's closed.

Returns:

  • (Hash{String => String})

    Hash of path/mime-type artifacts.



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/mediawiki_selenium/environment.rb', line 446

def teardown(info = {})
  artifacts = {}

  @_factory_cache.each do |(_, browser_name), factory|
    factory.each do |browser|
      yield browser if block_given?
      browser.close unless keep_browser_open? && browser_name != :phantomjs
    end

    factory_artifacts = factory.teardown(self, info[:status] || :passed)
    artifacts.merge!(factory_artifacts) if factory_artifacts.is_a?(Hash)
  end

  artifacts
end

#user(id = nil) ⇒ String

Returns the current value for :mediawiki_user or the value for the given alternative.

Parameters:

  • id (Symbol) (defaults to: nil)

    Alternative user ID.

Returns:

  • (String)


469
470
471
# File 'lib/mediawiki_selenium/environment.rb', line 469

def user(id = nil)
  lookup(:mediawiki_user, id: id)
end

#user_label(id = nil) ⇒ String

Returns the current user, or the one for the given alternative, with all "_" replaced with " ".

Parameters:

  • id (Symbol) (defaults to: nil)

    Alternative user ID.

Returns:

  • (String)


480
481
482
# File 'lib/mediawiki_selenium/environment.rb', line 480

def user_label(id = nil)
  user(id).gsub('_', ' ')
end

#visit_wiki(id = nil) {|url| ... } ⇒ Object

Navigates the current browser to the given wiki.

Parameters:

  • id (Symbol) (defaults to: nil)

    Alternative wiki ID.

Yields:

  • (url)

Yield Parameters:

  • url (String)

    Wiki URL.



491
492
493
494
495
496
# File 'lib/mediawiki_selenium/environment.rb', line 491

def visit_wiki(id = nil)
  on_wiki(id) do |url|
    browser.goto url
    yield url if block_given?
  end
end

#wiki_url(path = nil) ⇒ Object

Qualifies any given relative path using the configured :mediawiki_url. Absolute URLs are left untouched.

Examples:

env = Environment.new(mediawiki_url: "http://an.example/wiki/")

env.wiki_url # => "http://an.example/wiki/"
env.wiki_url("page") # => "http://an.example/wiki/page"
env.wiki_url("/page") # => "http://an.example/page"
env.wiki_url("http://other.example") # => "http://other.example"


509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/mediawiki_selenium/environment.rb', line 509

def wiki_url(path = nil)
  url = lookup(:mediawiki_url)

  if path
    # Prefixing relative paths with an explicit "./" guarantees proper
    # parsing of paths like "Special:Page" that would otherwise be
    # confused for URI schemes.
    if path.include?(':')
      path_uri = URI.parse(path)
      path = "./#{path}" if path_uri.class == URI::Generic && !path.start_with?('/')
    end

    url = URI.parse(url).merge(path).to_s
  end

  url
end

#with_alternative(names, id) {|*args| ... } ⇒ Object

Executes the given block within the context of a new environment configured using the alternative versions of the given options. The alternative configuration values are resolved using the given ID and passed to the block as arguments.

Examples:

Overwrite :foo with the :b alternative

# given an environment with config { foo: "x", foo_b: "y", ... }
with_alternative(:foo, :b) do |foo|
  self # => #<Environment @config = { foo: "y", ... }>
  foo # => "y"
end

Overwrite both :foo and :bar with the :b alternatives

# given an environment with config { foo: "x", foo_b: "y", bar: "w", bar_b: "z" }
with_alternative([:foo, :bar], :b) do |foo, bar|
  self # => #<Environment @config = { foo: "y", bar: "z", ... }>
  foo # => "y"
  bar # => "z"
end

Parameters:

  • names (Symbol|Array<Symbol>)

    Configuration option or options.

  • id (Symbol)

    Alternative user ID.

Yields:

  • (*args)

    Values of the overridden configuration.



552
553
554
555
# File 'lib/mediawiki_selenium/environment.rb', line 552

def with_alternative(names, id, &blk)
  names = Array(names)
  record_alternatives(names, id) { with(lookup_all(names, id: id), &blk) }
end