Dduvall has submitted this change and it was merged.

Change subject: Environment abstraction layer and browser factories
......................................................................


Environment abstraction layer and browser factories

Implemented Environment to better encapsulate environment configuration
and allow for easy switching between different contexts (different
users, wikis, etc.) without relying on mutating the global state of ENV.
It also helps one to avoid coupling in step definitions by supplying
helpers such as `as_user(:b)` or `on_wiki(:b)` to replace references to
specific application state (e.g. 'Given I'm logged in as "Specific
User").

Implemented BrowserFactory classes to provide an environment "binding"
system by which one can customize the browser options before it's
instantiated/opened using settings passed in via environment variables.

Refactored loading of MediawikiSelenium classes and implementation to
use autoload. Avoid loading of cucumber-related definitions (steps, env,
hooks, etc.) so that the library can be loaded in other contexts (e.g.
for testing the framework itself).

Started writing specs.

Change-Id: I1a3aa64b8fdca73aff2778ec12143a8789e4843f
---
A .rspec
M lib/mediawiki_selenium.rb
A lib/mediawiki_selenium/browser_factory.rb
A lib/mediawiki_selenium/browser_factory/base.rb
A lib/mediawiki_selenium/browser_factory/chrome.rb
A lib/mediawiki_selenium/browser_factory/firefox.rb
A lib/mediawiki_selenium/browser_factory/phantomjs.rb
A lib/mediawiki_selenium/environment.rb
A lib/mediawiki_selenium/step_definitions.rb
A lib/mediawiki_selenium/support.rb
M lib/mediawiki_selenium/support/env.rb
A lib/mediawiki_selenium/support/pages.rb
M lib/mediawiki_selenium/support/pages/api_page.rb
M lib/mediawiki_selenium/support/pages/login_page.rb
M lib/mediawiki_selenium/support/pages/random_page.rb
M lib/mediawiki_selenium/support/pages/reset_preferences_page.rb
M mediawiki_selenium.gemspec
A spec/browser_factory/base_spec.rb
A spec/environment_spec.rb
A spec/spec_helper.rb
20 files changed, 717 insertions(+), 22 deletions(-)

Approvals:
  Dduvall: Looks good to me, approved



diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..4e1e0d2
--- /dev/null
+++ b/.rspec
@@ -0,0 +1 @@
+--color
diff --git a/lib/mediawiki_selenium.rb b/lib/mediawiki_selenium.rb
index 612105f..2b59a4a 100644
--- a/lib/mediawiki_selenium.rb
+++ b/lib/mediawiki_selenium.rb
@@ -9,22 +9,9 @@
 https://git.wikimedia.org/blob/mediawiki%2Fselenium/HEAD/CREDITS.
 =end
 
-require "mediawiki_selenium/version"
-
-require "mediawiki_selenium/support/env"
-require "mediawiki_selenium/support/hooks"
-require "mediawiki_selenium/support/sauce"
-
-require "mediawiki_selenium/step_definitions/login_steps"
-require "mediawiki_selenium/step_definitions/navigation_steps"
-require "mediawiki_selenium/step_definitions/preferences_steps"
-require "mediawiki_selenium/step_definitions/resource_loader_steps"
-require "mediawiki_selenium/step_definitions/upload_file_steps"
-
-require "mediawiki_selenium/support/modules/api_helper"
-require "mediawiki_selenium/support/modules/url_module"
-
-require "mediawiki_selenium/support/pages/api_page"
-require "mediawiki_selenium/support/pages/login_page"
-require "mediawiki_selenium/support/pages/random_page"
-require "mediawiki_selenium/support/pages/reset_preferences_page"
+module MediawikiSelenium
+  autoload :VERSION, "mediawiki_selenium/version"
+  autoload :ApiHelper, "mediawiki_selenium/support/modules/api_helper"
+  autoload :BrowserFactory, "mediawiki_selenium/browser_factory"
+  autoload :Environment, "mediawiki_selenium/environment"
+end
diff --git a/lib/mediawiki_selenium/browser_factory.rb 
b/lib/mediawiki_selenium/browser_factory.rb
new file mode 100644
index 0000000..10660a9
--- /dev/null
+++ b/lib/mediawiki_selenium/browser_factory.rb
@@ -0,0 +1,21 @@
+module MediawikiSelenium
+  # Browser factory.
+  #
+  module BrowserFactory
+    autoload :Base, "mediawiki_selenium/browser_factory/base"
+    autoload :Firefox, "mediawiki_selenium/browser_factory/firefox"
+    autoload :Chrome, "mediawiki_selenium/browser_factory/chrome"
+    autoload :Phantomjs, "mediawiki_selenium/browser_factory/phantomjs"
+
+    # Resolves a new factory for the given browser name.
+    #
+    # @param name [Symbol] Browser name.
+    #
+    # @return [BrowserFactory::Base]
+    #
+    def self.new(name)
+      factory_class = 
const_get(name.to_s.split("_").map(&:capitalize).join(""))
+      factory_class.new(name)
+    end
+  end
+end
diff --git a/lib/mediawiki_selenium/browser_factory/base.rb 
b/lib/mediawiki_selenium/browser_factory/base.rb
new file mode 100644
index 0000000..9ba0319
--- /dev/null
+++ b/lib/mediawiki_selenium/browser_factory/base.rb
@@ -0,0 +1,75 @@
+require "watir-webdriver"
+
+module MediawikiSelenium
+  module BrowserFactory
+    class Base
+      attr_reader :type
+
+      class << self
+        def bind(name, &blk)
+          raise ArgumentError, "no block given" unless block_given?
+
+          default_bindings[name] ||= []
+          default_bindings[name] << blk
+        end
+
+        def bindings
+          if superclass <= Base
+            default_bindings.merge(superclass.bindings) { |key, old, new| old 
+ new }
+          else
+            default_bindings
+          end
+        end
+
+        def default_bindings
+          @default_bindings ||= {}
+        end
+      end
+
+      bind(:browser_timeout) { |value, options| options[:http_client].timeout 
= value.to_i }
+
+      def initialize(type)
+        @type = type
+        @bindings = {}
+        @browser_cache = {}
+      end
+
+      def bind(name, &blk)
+        @bindings[name] ||= []
+        @bindings[name] << (blk || proc {})
+      end
+
+      def bindings
+        self.class.bindings.merge(@bindings) { |key, old, new| old + new }
+      end
+
+      def browser_for(env)
+        config = env.lookup_all(bindings.keys)
+        @browser_cache[config] ||= Watir::Browser.new(type, 
browser_options(config))
+      end
+
+      protected
+
+      def capabilities
+        Selenium::WebDriver::Remote::Capabilities.send(type)
+      end
+
+      def http_client
+        Selenium::WebDriver::Remote::Http::Default.new
+      end
+
+      private
+
+      def browser_options(config)
+        { http_client: http_client, desired_capabilities: capabilities }.tap 
do |watir_options|
+          bindings.each do |(name, bindings_for_option)|
+            bindings_for_option.each do |binding|
+              value = config[name]
+              binding.call(value, watir_options) unless value.nil? || 
value.to_s.empty?
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/mediawiki_selenium/browser_factory/chrome.rb 
b/lib/mediawiki_selenium/browser_factory/chrome.rb
new file mode 100644
index 0000000..b81429a
--- /dev/null
+++ b/lib/mediawiki_selenium/browser_factory/chrome.rb
@@ -0,0 +1,28 @@
+module MediawikiSelenium
+  module BrowserFactory
+    class Chrome < Base
+      def arguments
+        [].tap do |args|
+          bind(:browser_user_agent) do |user_agent|
+            args << "--user-agent=#{user_agent}"
+          end
+        end
+      end
+
+      def capabilities
+        super.merge(
+          "chrome.profile" => profile.as_json["zip"],
+          "chromeOptions" => { "args" => arguments },
+        )
+      end
+
+      def profile
+        Selenium::WebDriver::Chrome::Profile.new.tap do |profile|
+          bind(:browser_language) do |language|
+            profile["intl.accept_languages"] = language
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/mediawiki_selenium/browser_factory/firefox.rb 
b/lib/mediawiki_selenium/browser_factory/firefox.rb
new file mode 100644
index 0000000..56bc029
--- /dev/null
+++ b/lib/mediawiki_selenium/browser_factory/firefox.rb
@@ -0,0 +1,34 @@
+module MediawikiSelenium
+  module BrowserFactory
+    class Firefox < Base
+      bind(:browser_timeout) do |timeout, opts|
+        timeout = timeout.to_i
+        
opts[:desired_capabilities][:firefox_profile]["dom.max_script_run_time"] = 
timeout
+        
opts[:desired_capabilities][:firefox_profile]["dom.max_chrome_script_run_time"] 
= timeout
+      end
+
+      def capabilities
+        super.tap do |capabilities|
+          capabilities[:firefox_profile] = 
Selenium::WebDriver::Firefox::Profile.new
+        end
+      end
+
+      def profile
+        Selenium::WebDriver::Firefox::Profile.new.tap do |profile|
+          bind(:browser_timeout) do |timeout|
+            profile["dom.max_script_run_time"] = timeout
+            profile["dom.max_chrome_script_run_time"] = timeout
+          end
+
+          bind(:browser_language) do |language|
+            profile["intl.accept_languages"] = language
+          end
+
+          bind(:browser_user_agent) do |user_agent|
+            profile["general.useragent.override"] = user_agent
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/mediawiki_selenium/browser_factory/phantomjs.rb 
b/lib/mediawiki_selenium/browser_factory/phantomjs.rb
new file mode 100644
index 0000000..5a8309b
--- /dev/null
+++ b/lib/mediawiki_selenium/browser_factory/phantomjs.rb
@@ -0,0 +1,17 @@
+module MediawikiSelenium
+  module BrowserFactory
+    class Phantomjs < Base
+      def capabilities
+        super.tap do |capabilities|
+          bind(:browser_language) do |language|
+            capabilities["phantomjs.page.customHeaders.Accept-Language"] = 
language
+          end
+
+          bind(:browser_user_agent) do |user_agent|
+            capabilities["phantomjs.page.settings.userAgent"] = user_agent
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/mediawiki_selenium/environment.rb 
b/lib/mediawiki_selenium/environment.rb
new file mode 100644
index 0000000..58a5da6
--- /dev/null
+++ b/lib/mediawiki_selenium/environment.rb
@@ -0,0 +1,183 @@
+module MediawikiSelenium
+  # Provides an abstraction layer between the environmental configuration and
+  # step definitions.
+  #
+  class Environment
+    include Comparable
+
+    class ConfigurationError < StandardError
+      attr_reader :name
+
+      def initialize(name)
+        @name = name
+      end
+
+      def to_s
+        "missing configuration for #{name}"
+      end
+    end
+
+    CORE_BROWSER_OPTIONS = [
+      :mediawiki_url,
+      :mediawiki_user,
+    ]
+
+    REQUIRED_CONFIG = [
+      :browser,
+      :mediawiki_api_url,
+      :mediawiki_password,
+      :mediawiki_url,
+      :mediawiki_user,
+    ]
+
+    attr_reader :config
+
+    def initialize(config)
+      @config = normalize_config(config)
+      @factory_cache = {}
+    end
+
+    def initialize_clone(other)
+      @config = other.config.clone
+    end
+
+    # Two environments are considered equal if they have identical
+    # configuration.
+    #
+    def ==(other)
+      @config == other.config
+    end
+
+    # Executes the given block within the context of an environment that's
+    # using the given alternative user and its password.
+    #
+    # @param id [Symbol] Alternative user ID.
+    #
+    # @yield [env]
+    # @yieldparam env [Environment] Environment
+    #
+    # @return [Environment]
+    #
+    def as_user(id, &blk)
+      with_alternative([:mediawiki_user, :mediawiki_password], id, &blk)
+    end
+
+    # Binds new possible configuration for the given browser.
+    #
+    # @param browser_name [Symbol] Browser name.
+    # @param option_name [Symbol] Option name.
+    #
+    # @yield []
+    #
+    def bind(browser_name, option_name, &blk)
+      browser_factory(browser_name).bind(option_name, &blk)
+    end
+
+    # Browser with which to drive tests.
+    #
+    # @return [Watir::Browser]
+    #
+    def browser
+      browser_factory.browser_for(self)
+    end
+
+    def browser_factory(type = browser_name)
+      type = type.to_s.downcase.to_sym
+
+      @factory_cache[type] ||= BrowserFactory.new(type).tap do |factory|
+        CORE_BROWSER_OPTIONS.each { |name| factory.bind(name) }
+      end
+    end
+
+    # Name of the browser we're using.
+    #
+    # @return [Symbol]
+    #
+    def browser_name
+      lookup(:browser).downcase.to_sym
+    end
+
+    def lookup(key, id = nil)
+      key = "#{key}_#{id}" unless id.nil?
+      key = key.to_sym
+      value = @config[key]
+
+      if value.nil? || value.to_s.empty?
+        if id.nil?
+          if REQUIRED_CONFIG.include?(key)
+            raise ConfigurationError, key
+          else
+            nil
+          end
+        else
+          lookup(key)
+        end
+      else
+        value
+      end
+    end
+
+    def lookup_all(keys, id = nil)
+      keys.each.with_object({}) do |key, hash|
+        value = lookup(key, id)
+        hash[key] = value unless value.nil?
+      end
+    end
+
+    # Executes the given block within the context of an environment that's
+    # using the given alternative wiki URL and its corresponding API endpoint.
+    #
+    # @param id [Symbol] Alternative wiki ID.
+    #
+    # @yield [env]
+    # @yieldparam env [Environment] Environment
+    #
+    # @return [Environment]
+    #
+    def on_wiki(id, &blk)
+      with_alternative([:mediawiki_url, :mediawiki_api_url], id, &blk)
+    end
+
+    # Yields a new environment using the alternative versions of the given
+    # configuration options. The alternative values are resolved by looking up
+    # options that correspond to the given ones but have the given ID
+    # appended.
+    #
+    # @example Overwrite :option with the value from :option_b
+    #   # given an environment with { option: "x", option_b: "y", ... }
+    #   env.with_alternative(:option, :b) do |env|
+    #     env # => { option: "y", ... }
+    #   end
+    #
+    # @example Overwrite both :option and :other with :option_b and :other_b
+    #   # given { option: "x", option_b: "y", other: "w", other_b: "z" }
+    #   env.with_alternative([:option, :other], :b) do |env|
+    #     env # => { option: "y", other: "z", ... }
+    #   end
+    #
+    # @param names [Array|Symbol] Configuration option or options.
+    # @param id [Symbol] Alternative user ID.
+    #
+    # @yield [env]
+    # @yieldparam env [Environment] The modified environment.
+    #
+    # @return [Environment] The modified environment.
+    #
+    def with_alternative(names, id, &blk)
+      with(lookup_all(Array(names), id), &blk)
+    end
+
+    private
+
+    def normalize_config(hash)
+      hash.each.with_object({}) { |(k, v), acc| acc[k.to_s.downcase.to_sym] = 
v }
+    end
+
+    def with(overrides = {})
+      clone.tap do |env|
+        env.config.merge!(normalize_config(overrides))
+        yield env
+      end
+    end
+  end
+end
diff --git a/lib/mediawiki_selenium/step_definitions.rb 
b/lib/mediawiki_selenium/step_definitions.rb
new file mode 100644
index 0000000..b8608f6
--- /dev/null
+++ b/lib/mediawiki_selenium/step_definitions.rb
@@ -0,0 +1,5 @@
+require "mediawiki_selenium/step_definitions/login_steps"
+require "mediawiki_selenium/step_definitions/navigation_steps"
+require "mediawiki_selenium/step_definitions/preferences_steps"
+require "mediawiki_selenium/step_definitions/resource_loader_steps"
+require "mediawiki_selenium/step_definitions/upload_file_steps"
diff --git a/lib/mediawiki_selenium/support.rb 
b/lib/mediawiki_selenium/support.rb
new file mode 100644
index 0000000..f5a6cb6
--- /dev/null
+++ b/lib/mediawiki_selenium/support.rb
@@ -0,0 +1,3 @@
+require "mediawiki_selenium/support/env"
+require "mediawiki_selenium/support/hooks"
+require "mediawiki_selenium/support/sauce"
diff --git a/lib/mediawiki_selenium/support/env.rb 
b/lib/mediawiki_selenium/support/env.rb
index 35af866..140a908 100644
--- a/lib/mediawiki_selenium/support/env.rb
+++ b/lib/mediawiki_selenium/support/env.rb
@@ -11,7 +11,6 @@
 
 # before all
 require "bundler/setup"
-require "page-object"
 require "page-object/page_factory"
 require "rest_client"
 require "watir-webdriver"
@@ -25,6 +24,8 @@
 World(MediawikiSelenium::SauceHelper)
 World(MediawikiSelenium::StrictPending)
 
+World { MediawikiSelenium::Environment.new(ENV) }
+
 def browser(test_name, configuration = nil)
   if environment == :saucelabs
     sauce_browser(test_name, configuration)
diff --git a/lib/mediawiki_selenium/support/pages.rb 
b/lib/mediawiki_selenium/support/pages.rb
new file mode 100644
index 0000000..f316bcf
--- /dev/null
+++ b/lib/mediawiki_selenium/support/pages.rb
@@ -0,0 +1,4 @@
+require "mediawiki_selenium/support/pages/api_page"
+require "mediawiki_selenium/support/pages/login_page"
+require "mediawiki_selenium/support/pages/random_page"
+require "mediawiki_selenium/support/pages/reset_preferences_page"
diff --git a/lib/mediawiki_selenium/support/pages/api_page.rb 
b/lib/mediawiki_selenium/support/pages/api_page.rb
index ba2ef39..53da9ee 100644
--- a/lib/mediawiki_selenium/support/pages/api_page.rb
+++ b/lib/mediawiki_selenium/support/pages/api_page.rb
@@ -1,3 +1,4 @@
+require "page-object"
 require "mediawiki_api"
 
 class APIPage
diff --git a/lib/mediawiki_selenium/support/pages/login_page.rb 
b/lib/mediawiki_selenium/support/pages/login_page.rb
index 89857bf..44fbf78 100644
--- a/lib/mediawiki_selenium/support/pages/login_page.rb
+++ b/lib/mediawiki_selenium/support/pages/login_page.rb
@@ -8,6 +8,8 @@
 mediawiki_selenium top-level directory and at
 https://git.wikimedia.org/blob/mediawiki%2Fselenium/HEAD/CREDITS.
 =end
+require "page-object"
+require "mediawiki_selenium/support/modules/url_module"
 
 class LoginPage
   include PageObject
diff --git a/lib/mediawiki_selenium/support/pages/random_page.rb 
b/lib/mediawiki_selenium/support/pages/random_page.rb
index c181ea3..40e33cd 100644
--- a/lib/mediawiki_selenium/support/pages/random_page.rb
+++ b/lib/mediawiki_selenium/support/pages/random_page.rb
@@ -8,6 +8,8 @@
 mediawiki_selenium top-level directory and at
 https://git.wikimedia.org/blob/mediawiki%2Fselenium/HEAD/CREDITS.
 =end
+require "page-object"
+require "mediawiki_selenium/support/modules/url_module"
 
 class RandomPage
   include PageObject
diff --git a/lib/mediawiki_selenium/support/pages/reset_preferences_page.rb 
b/lib/mediawiki_selenium/support/pages/reset_preferences_page.rb
index 255c772..76f2281 100644
--- a/lib/mediawiki_selenium/support/pages/reset_preferences_page.rb
+++ b/lib/mediawiki_selenium/support/pages/reset_preferences_page.rb
@@ -8,6 +8,8 @@
 mediawiki_selenium top-level directory and at
 https://git.wikimedia.org/blob/mediawiki%2Fselenium/HEAD/CREDITS.
 =end
+require "page-object"
+require "mediawiki_selenium/support/modules/url_module"
 
 class ResetPreferencesPage
   include PageObject
diff --git a/mediawiki_selenium.gemspec b/mediawiki_selenium.gemspec
index 39c1c44..549af4c 100644
--- a/mediawiki_selenium.gemspec
+++ b/mediawiki_selenium.gemspec
@@ -26,7 +26,10 @@
   spec.add_runtime_dependency "rspec-expectations", "~> 2.14", ">= 2.14.4"
   spec.add_runtime_dependency "syntax", "~> 1.2", ">= 1.2.0"
 
-  spec.add_development_dependency "redcarpet"
+  spec.add_development_dependency "bundler", "~> 1.6", ">= 1.6.3"
+  spec.add_development_dependency "yard", "~> 0.8", ">= 0.8.7.4"
+  spec.add_development_dependency "redcarpet", "~> 3.2", ">= 3.2.0"
   spec.add_development_dependency "rubocop", "~> 0.26.1"
-  spec.add_development_dependency "yard"
+  spec.add_development_dependency "rspec-core", "~> 2.14", ">= 2.14.4"
+  spec.add_development_dependency "rspec-mocks", "~> 2.14", ">= 2.14.4"
 end
diff --git a/spec/browser_factory/base_spec.rb 
b/spec/browser_factory/base_spec.rb
new file mode 100644
index 0000000..e53cda5
--- /dev/null
+++ b/spec/browser_factory/base_spec.rb
@@ -0,0 +1,166 @@
+require "spec_helper"
+
+module MediawikiSelenium::BrowserFactory
+  describe Base do
+    let(:factory_class) { Class.new(Base) }
+    let(:factory) { factory_class.new(browser_type) }
+    let(:browser_type) { :lynx }
+
+    describe ".bind" do
+      subject { factory_class.bind(option_name, &block) }
+
+      let(:option_name) { :foo }
+      let(:block) { proc { } }
+
+      it "adds a new default binding for the given option" do
+        subject
+        expect(factory_class.default_bindings).to include(option_name)
+        expect(factory_class.default_bindings[option_name]).to include(block)
+      end
+
+      context "given no block" do
+        subject { factory_class.bind(option_name) }
+
+        it "raises an ArgumentError" do
+          expect { subject }.to raise_error(ArgumentError)
+        end
+      end
+    end
+
+    describe ".bindings" do
+      subject { factory_class.bindings }
+
+      before do
+        factory_class.bind(:foo) {}
+      end
+
+      it "includes the base class's default bindings" do
+        expect(subject).to include(Base.default_bindings)
+      end
+
+      it "includes its own bindings" do
+        expect(subject).to include(factory_class.default_bindings)
+      end
+    end
+
+    describe ".default_bindings" do
+      subject { factory_class.default_bindings }
+
+      it { is_expected.to be_a(Hash) }
+
+      context "before any bindings are defined" do
+        it { is_expected.to be_empty }
+      end
+
+      context "when bindings are defined" do
+        before do
+          factory_class.bind(:foo) {}
+          factory_class.bind(:bar) {}
+          factory_class.bind(:bar) {}
+        end
+
+        it "includes all defined bindings" do
+          expect(subject).to include(:foo, :bar)
+        end
+
+        it "includes all bindings for the same option name" do
+          expect(subject[:bar].length).to be(2)
+        end
+      end
+    end
+
+    describe "#bind" do
+      subject { factory.bind(option_name, &block) }
+      before { subject }
+
+      let(:option_name) { :foo }
+      let(:block) { proc { } }
+
+      it "adds a new binding for the given option" do
+        expect(factory.bindings).to include(option_name)
+        expect(factory.bindings[option_name]).to include(block)
+      end
+
+      context "given no block" do
+        subject { factory.bind(option_name) }
+
+        it "will default to an empty block" do
+          expect(factory.bindings[option_name]).not_to include(nil)
+        end
+      end
+    end
+
+    describe "#bindings" do
+      subject { factory.bindings }
+
+      before do
+        factory_class.bind(:foo) {}
+        factory.bind(:bar)
+      end
+
+      it "includes the class-level bindings" do
+        expect(subject).to include(factory_class.bindings)
+      end
+
+      it "includes its own bindings" do
+        expect(subject).to include(:bar)
+      end
+    end
+
+    describe "#browser_for" do
+      subject { factory.browser_for(env) }
+
+      let(:env) { MediawikiSelenium::Environment.new(foo: "x", bar: "y") }
+
+      let(:watir_browser) { double(Watir::Browser) }
+      let(:capabilities) { double(Selenium::WebDriver::Remote::Capabilities) }
+
+      before do
+        factory.bind(:foo)
+
+        expect(Selenium::WebDriver::Remote::Capabilities).to 
receive(browser_type).
+          at_least(:once).and_return(capabilities)
+      end
+
+      it "creates a new Watir::Browser" do
+        expect(Watir::Browser).to receive(:new).once.and_return(watir_browser)
+        expect(subject).to be(watir_browser)
+      end
+
+      context "called more than once" do
+        let(:env1) { env }
+
+        context "with differing env configuration" do
+          context "that is bound" do
+            let(:env2) { MediawikiSelenium::Environment.new(foo: "z") }
+
+            it "returns two distinct browsers" do
+              expect(Watir::Browser).to receive(:new).twice
+
+              factory.browser_for(env1)
+              factory.browser_for(env2)
+            end
+          end
+
+          context "that is not bound" do
+            let(:env2) { MediawikiSelenium::Environment.new(foo: "x", bar: 
"z") }
+
+            it "returns a cached browser" do
+              expect(Watir::Browser).to 
receive(:new).once.and_return(watir_browser)
+              expect(factory.browser_for(env1)).to 
be(factory.browser_for(env2))
+            end
+          end
+        end
+
+        context "with the same env configuration" do
+          let(:env2) { MediawikiSelenium::Environment.new(foo: "x") }
+
+          it "returns a cached browser" do
+            expect(Watir::Browser).to 
receive(:new).once.and_return(watir_browser)
+            expect(factory.browser_for(env1)).to be(factory.browser_for(env2))
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/environment_spec.rb b/spec/environment_spec.rb
new file mode 100644
index 0000000..bd9d341
--- /dev/null
+++ b/spec/environment_spec.rb
@@ -0,0 +1,156 @@
+require "spec_helper"
+
+module MediawikiSelenium
+  describe Environment do
+    subject { env }
+
+    let(:env) { Environment.new(config) }
+    let(:config) { minimum_config }
+
+    let(:minimum_config) do
+      {
+        browser: browser,
+        mediawiki_api_url: mediawiki_api_url,
+        mediawiki_url: mediawiki_url,
+        mediawiki_user: mediawiki_user,
+        mediawiki_password: mediawiki_password,
+      }
+    end
+
+    let(:browser) { "firefox" }
+    let(:mediawiki_api_url) { "http://an.example/wiki/api.php"; }
+    let(:mediawiki_url) { "http://an.example/wiki/"; }
+    let(:mediawiki_user) { "mw user" }
+    let(:mediawiki_password) { "mw password" }
+
+    describe "#==" do
+      subject { env == other }
+
+      context "given an environment with the same configuration" do
+        let(:other) { Environment.new(env.config) }
+
+        it "considers them equal" do
+          expect(subject).to be(true)
+        end
+      end
+
+      context "given an environment with different configuration" do
+        let(:other) { Environment.new(env.config.merge(some: "extra")) }
+
+        it "considers them not equal" do
+          expect(subject).to be(false)
+        end
+      end
+    end
+
+    describe "#as_user" do
+      let(:config) do
+        {
+          mediawiki_user: "user",
+          mediawiki_password: "pass",
+          mediawiki_user_b: "user b",
+          mediawiki_password_b: "pass b",
+        }
+      end
+
+      it "yields a new environment for the alternative user and its password" 
do
+        expected_env = Environment.new(config.merge(
+          mediawiki_user: "user b",
+          mediawiki_password: "pass b",
+        ))
+        expect { |block| env.as_user(:b, &block) }.to 
yield_with_args(expected_env)
+      end
+    end
+
+    describe "#browser_factory" do
+      subject { env.browser_factory }
+
+      it "is a factory for the configured browser" do
+        expect(subject).to be_a(BrowserFactory::Firefox)
+      end
+
+      it "binds the core browser options" do
+        expect(subject.bindings).to include(*Environment::CORE_BROWSER_OPTIONS)
+      end
+
+      context "given a type" do
+        subject { env.browser_factory(:chrome) }
+
+        it "is a factory for that type" do
+          expect(subject).to be_a(BrowserFactory::Chrome)
+        end
+      end
+    end
+
+    describe "#browser_name" do
+      subject { env.browser_name }
+
+      let(:browser) { "Firefox" }
+
+      it "is always a lowercase symbol" do
+        expect(subject).to be(:firefox)
+      end
+
+      context "missing browser configuration" do
+        let(:browser) { nil }
+
+        it "raises a ConfigurationError" do
+          expect { subject }.to raise_error(Environment::ConfigurationError)
+        end
+      end
+    end
+
+    describe "#on_wiki" do
+      let(:config) do
+        {
+          mediawiki_url: "http://an.example/wiki";,
+          mediawiki_url_b: "http://alt.example/wiki";,
+          mediawiki_api_url: "http://an.example/api";,
+          mediawiki_api_url_b: "http://alt.example/api";,
+        }
+      end
+
+      it "yields a new environment for the alternative wiki and API urls" do
+        expected_env = Environment.new(config.merge(
+          mediawiki_url: "http://alt.example/wiki";,
+          mediawiki_api_url: "http://alt.example/api";,
+        ))
+        expect { |block| env.on_wiki(:b, &block) }.to 
yield_with_args(expected_env)
+      end
+    end
+
+    describe "#with_alternative" do
+      let(:config) do
+        {
+          mediawiki_url: "http://an.example/wiki";,
+          mediawiki_url_b: "http://alt.example/wiki";,
+          mediawiki_api_url: "http://an.example/api";,
+          mediawiki_api_url_b: "http://alt.example/api";,
+        }
+      end
+
+      context "given one option name and an ID" do
+        let(:names) { :mediawiki_url }
+
+        it "yields an environment that substitutes it using the alternative" do
+          expected_env = Environment.new(config.merge(
+            mediawiki_url: "http://alt.example/wiki";,
+          ))
+          expect { |block| env.with_alternative(names, :b, &block) }.to 
yield_with_args(expected_env)
+        end
+      end
+
+      context "given multiple option names and an ID" do
+        let(:names) { [:mediawiki_url, :mediawiki_api_url] }
+
+        it "yields an environment that substitutes both using the 
alternatives" do
+          expected_env = Environment.new(config.merge(
+            mediawiki_url: "http://alt.example/wiki";,
+            mediawiki_api_url: "http://alt.example/api";,
+          ))
+          expect { |block| env.with_alternative(names, :b, &block) }.to 
yield_with_args(expected_env)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..f92911d
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,4 @@
+require "bundler/setup"
+require "mediawiki_selenium"
+
+Bundler.require(:development)

-- 
To view, visit https://gerrit.wikimedia.org/r/169850
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I1a3aa64b8fdca73aff2778ec12143a8789e4843f
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/selenium
Gerrit-Branch: env-abstraction-layer
Gerrit-Owner: Dduvall <[email protected]>
Gerrit-Reviewer: Dduvall <[email protected]>
Gerrit-Reviewer: Hashar <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to