Repository: robotframework/SeleniumLibrary (python)

Issue:
Allow Open Browser keyword to pass capabilities also for local browsers
Sometimes it could be useful to allow capabilities be passed also to local browsers. The current implementation of the `Open Browser` only passes the capabilities  to the Selenium side only when `remote_url` argument is used. 

Pull request description:
Webdriver factory 2
Fixes #550 

Patch:
diff --git a/src/SeleniumLibrary/keywords/__init__.py b/src/SeleniumLibrary/keywords/__init__.py
index a3236bcf..cbaa29fc 100644
--- a/src/SeleniumLibrary/keywords/__init__.py
+++ b/src/SeleniumLibrary/keywords/__init__.py
@@ -26,4 +26,5 @@ from .screenshot import ScreenshotKeywords
 from .selectelement import SelectElementKeywords
 from .tableelement import TableElementKeywords
 from .waiting import WaitingKeywords
+from .webdrivercreator import WebDriverCreator
 from .window import WindowKeywords
diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py
index 3227c9cd..cbd1121a 100644
--- a/src/SeleniumLibrary/keywords/browsermanagement.py
+++ b/src/SeleniumLibrary/keywords/browsermanagement.py
@@ -14,40 +14,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import os.path
 import time
 import types
 
-from robot.utils import NormalizedDict
 from selenium import webdriver
 
 from SeleniumLibrary.base import keyword, LibraryComponent
 from SeleniumLibrary.locators import WindowManager
-from SeleniumLibrary.utils import (is_falsy, is_truthy, is_noney, secs_to_timestr,
-                                   timestr_to_secs, SELENIUM_VERSION)
-
-
-ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
-FIREFOX_PROFILE_DIR = os.path.join(ROOT_DIR, 'resources', 'firefoxprofile')
-BROWSER_NAMES = NormalizedDict({
-    'ff': "_make_ff",
-    'firefox': "_make_ff",
-    'ie': "_make_ie",
-    'internetexplorer': "_make_ie",
-    'googlechrome': "_make_chrome",
-    'gc': "_make_chrome",
-    'chrome': "_make_chrome",
-    'opera': "_make_opera",
-    'phantomjs': "_make_phantomjs",
-    'htmlunit': "_make_htmlunit",
-    'htmlunitwithjs': "_make_htmlunitwithjs",
-    'android': "_make_android",
-    'iphone': "_make_iphone",
-    'safari': "_make_safari",
-    'edge': "_make_edge",
-    'headlessfirefox': '_make_headless_ff',
-    'headlesschrome': '_make_headless_chrome'
-})
+from SeleniumLibrary.utils import (is_truthy, is_noney, secs_to_timestr,
+                                   timestr_to_secs)
+
+from .webdrivercreator import WebDriverCreator
 
 
 class BrowserManagementKeywords(LibraryComponent):
@@ -146,8 +123,7 @@ class BrowserManagementKeywords(LibraryComponent):
                       "remote server at '%s'." % (browser, url, remote_url))
         else:
             self.info("Opening browser '%s' to base url '%s'." % (browser, url))
-        browser_name = browser
-        driver = self._make_driver(browser_name, desired_capabilities,
+        driver = self._make_driver(browser, desired_capabilities,
                                    ff_profile_dir, remote_url)
         try:
             driver.get(url)
@@ -307,8 +283,8 @@ class BrowserManagementKeywords(LibraryComponent):
         """Verifies that current page title equals ``title``.
 
         The ``message`` argument can be used to override the default error
-        message. 
-        
+        message.
+
         ``message`` argument is new in SeleniumLibrary 3.1.
         """
         actual = self.get_title()
@@ -441,145 +417,17 @@ class BrowserManagementKeywords(LibraryComponent):
         """
         self.driver.implicitly_wait(timestr_to_secs(value))
 
-    def _get_driver_creation_function(self, browser_name):
-        try:
-            func_name = BROWSER_NAMES[browser_name]
-        except KeyError:
-            raise ValueError(browser_name + " is not a supported browser.")
-        return getattr(self, func_name)
-
-    def _make_driver(self, browser_name, desired_capabilities=None,
+    def _make_driver(self, browser, desired_capabilities=None,
                      profile_dir=None, remote=None):
-        creation_func = self._get_driver_creation_function(browser_name)
-        driver = creation_func(remote, desired_capabilities, profile_dir)
+        driver = WebDriverCreator(self.log_dir).create_driver(
+            browser=browser, desired_capabilities=desired_capabilities,
+            remote_url=remote, profile_dir=profile_dir)
         driver.set_script_timeout(self.ctx.timeout)
         driver.implicitly_wait(self.ctx.implicit_wait)
         if self.ctx.speed:
             self._monkey_patch_speed(driver)
         return driver
 
-    def _make_ff(self, remote, desired_capabilities, profile_dir, options=None):
-        if is_falsy(profile_dir):
-            profile = webdriver.FirefoxProfile()
-        else:
-            profile = webdriver.FirefoxProfile(profile_dir)
-        if is_truthy(remote):
-            driver = self._create_remote_web_driver(
-                webdriver.DesiredCapabilities.FIREFOX, remote,
-                desired_capabilities, profile, options=options)
-        else:
-            driver = webdriver.Firefox(firefox_profile=profile,
-                                       options=options,
-                                       **self._geckodriver_log_config)
-        return driver
-
-    def _make_headless_ff(self, remote, desired_capabilities, profile_dir):
-        options = webdriver.FirefoxOptions()
-        options.set_headless()
-        return self._make_ff(remote, desired_capabilities, profile_dir, options=options)
-
-    def _make_ie(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.Ie, webdriver.DesiredCapabilities.INTERNETEXPLORER,
-            remote, desired_capabilities)
-
-    def _make_chrome(self, remote, desired_capabilities, profile_dir, options=None):
-        return self._generic_make_driver(
-            webdriver.Chrome, webdriver.DesiredCapabilities.CHROME, remote,
-            desired_capabilities, options=options)
-
-    def _make_headless_chrome(self, remote, desired_capabilities, profile_dir):
-        options = webdriver.ChromeOptions()
-        options.set_headless()
-        return self._make_chrome(remote, desired_capabilities, profile_dir, options)
-
-    def _make_opera(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.Opera, webdriver.DesiredCapabilities.OPERA, remote,
-            desired_capabilities)
-
-    def _make_phantomjs(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.PhantomJS, webdriver.DesiredCapabilities.PHANTOMJS,
-            remote, desired_capabilities)
-
-    def _make_htmlunit(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.Remote, webdriver.DesiredCapabilities.HTMLUNIT, remote,
-            desired_capabilities)
-
-    def _make_htmlunitwithjs(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.Remote, webdriver.DesiredCapabilities.HTMLUNITWITHJS,
-            remote, desired_capabilities)
-
-    def _make_android(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.Remote, webdriver.DesiredCapabilities.ANDROID, remote,
-            desired_capabilities)
-
-    def _make_iphone(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.Remote, webdriver.DesiredCapabilities.IPHONE, remote,
-            desired_capabilities)
-
-    def _make_safari(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.Safari, webdriver.DesiredCapabilities.SAFARI, remote,
-            desired_capabilities)
-
-    def _make_edge(self, remote, desired_capabilities, profile_dir):
-        return self._generic_make_driver(
-            webdriver.Edge, webdriver.DesiredCapabilities.EDGE, remote,
-            desired_capabilities)
-
-    def _generic_make_driver(self, webdriver_type, desired_cap_type,
-                             remote_url, desired_caps, options=None):
-        """Generic driver creation
-
-        Most of the make driver functions just call this function which
-        creates the appropriate driver
-        """
-        if is_falsy(remote_url):
-            if options is None:
-                driver = webdriver_type()
-            else:
-                driver = webdriver_type(options=options)
-        else:
-            driver = self._create_remote_web_driver(desired_cap_type,
-                                                    remote_url, desired_caps,
-                                                    options=options)
-        return driver
-
-    def _create_remote_web_driver(self, capabilities_type, remote_url,
-                                  desired_capabilities=None, profile=None,
-                                  options=None):
-        '''parses the string based desired_capabilities if neccessary and
-        creates the associated remote web driver'''
-
-        desired_capabilities_object = capabilities_type.copy()
-        if not isinstance(desired_capabilities, dict):
-            desired_capabilities = self._parse_capabilities_string(desired_capabilities)
-        desired_capabilities_object.update(desired_capabilities or {})
-        return webdriver.Remote(desired_capabilities=desired_capabilities_object,
-                command_executor=str(remote_url), browser_profile=profile,
-                options=options)
-
-    def _parse_capabilities_string(self, capabilities_string):
-        '''parses the string based desired_capabilities which should be in the form
-        key1:val1,key2:val2
-        '''
-        desired_capabilities = {}
-
-        if is_falsy(capabilities_string):
-            return desired_capabilities
-
-        for cap in capabilities_string.split(","):
-            (key, value) = cap.split(":", 1)
-            desired_capabilities[key.strip()] = value.strip()
-
-        return desired_capabilities
-
     def _monkey_patch_speed(self, driver):
         def execute(self, driver_command, params=None):
             result = self._base_execute(driver_command, params)
@@ -591,9 +439,3 @@ class BrowserManagementKeywords(LibraryComponent):
             driver._base_execute = driver.execute
             driver.execute = types.MethodType(execute, driver)
         driver._speed = self.ctx.speed
-
-    @property
-    def _geckodriver_log_config(self):
-        if SELENIUM_VERSION.major == '3':
-            return {'log_path': os.path.join(self.log_dir, 'geckodriver.log')}
-        return {}
diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py
index 539667fe..d5b6dde4 100644
--- a/src/SeleniumLibrary/keywords/waiting.py
+++ b/src/SeleniumLibrary/keywords/waiting.py
@@ -18,7 +18,7 @@ import time
 
 from SeleniumLibrary.base import LibraryComponent, keyword
 from SeleniumLibrary.errors import ElementNotFound
-from SeleniumLibrary.utils import is_noney, is_truthy, secs_to_timestr
+from SeleniumLibrary.utils import is_noney, secs_to_timestr
 
 
 class WaitingKeywords(LibraryComponent):
diff --git a/src/SeleniumLibrary/keywords/webdrivercreator.py b/src/SeleniumLibrary/keywords/webdrivercreator.py
new file mode 100644
index 00000000..70c48bf3
--- /dev/null
+++ b/src/SeleniumLibrary/keywords/webdrivercreator.py
@@ -0,0 +1,193 @@
+# Copyright 2008-2011 Nokia Networks
+# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors
+# Copyright 2016-     Robot Framework Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import copy
+import os
+
+from selenium import webdriver
+
+from SeleniumLibrary.utils import is_falsy, is_truthy, SELENIUM_VERSION
+
+
+class WebDriverCreator(object):
+
+    browser_names = {
+        'googlechrome': "chrome",
+        'gc': "chrome",
+        'chrome': "chrome",
+        'headlesschrome': 'headless_chrome',
+        'ff': 'firefox',
+        'firefox': 'firefox',
+        'headlessfirefox': 'headless_firefox',
+        'ie': 'ie',
+        'internetexplorer': 'ie',
+        'edge': 'edge',
+        'opera': 'opera',
+        'safari': 'safari',
+        'phantomjs': 'phantomjs',
+        'htmlunit': 'htmlunit',
+        'htmlunitwithjs': 'htmlunit_with_js',
+        'android': 'android',
+        'iphone': 'iphone'
+    }
+
+    def __init__(self, log_dir):
+        self.log_dir = log_dir
+
+    def create_driver(self, browser, desired_capabilities, remote_url,
+                      profile_dir=None):
+        creation_method = self._get_creator_method(browser)
+        desired_capabilities = self._parse_capabilities(desired_capabilities)
+        if (creation_method == self.create_firefox
+                or creation_method == self.create_headless_firefox):
+            return creation_method(desired_capabilities, remote_url,
+                                   profile_dir)
+        return creation_method(desired_capabilities, remote_url)
+
+    def _get_creator_method(self, browser):
+        browser = browser.lower().replace(' ', '')
+        if browser in self.browser_names:
+            return getattr(self, 'create_{}'.format(self.browser_names[browser]))
+        raise ValueError('{} is not a supported browser.'.format(browser))
+
+    def _parse_capabilities(self, capabilities):
+        if isinstance(capabilities, dict):
+            return capabilities
+        desired_capabilities = {}
+        if is_falsy(capabilities):
+            return desired_capabilities
+        for part in capabilities.split(','):
+            key, value = part.split(':')
+            desired_capabilities[key.strip()] = value.strip()
+        return desired_capabilities
+
+    def create_chrome(self, desired_capabilities, remote_url, options=None):
+        default = webdriver.DesiredCapabilities.CHROME
+        if is_truthy(remote_url):
+            return self._remote(default, desired_capabilities, remote_url)
+        capabilities = self._combine_capabilites(default, desired_capabilities)
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            return webdriver.Chrome(desired_capabilities=capabilities, options=options)
+        return webdriver.Chrome(desired_capabilities=capabilities)
+
+    def create_headless_chrome(self, desired_capabilities, remote_url):
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            options = webdriver.ChromeOptions()
+            options.set_headless()
+        else:
+            options=None
+        return self.create_chrome(desired_capabilities, remote_url, options)
+
+    def create_firefox(self, desired_capabilities, remote_url, ff_profile_dir,
+                       options=None):
+        default = webdriver.DesiredCapabilities.FIREFOX
+        profile = self._get_ff_profile(ff_profile_dir)
+        if is_truthy(remote_url):
+            return self._remote(default, desired_capabilities, remote_url, profile)
+        capabilities = self._combine_capabilites(default, desired_capabilities)
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            return webdriver.Firefox(capabilities=capabilities, options=options,
+                                     firefox_profile=profile,
+                                     **self._geckodriver_log)
+        return webdriver.Firefox(capabilities=capabilities,
+                                 firefox_profile=profile,
+                                 **self._geckodriver_log)
+
+    def _get_ff_profile(self, ff_profile_dir):
+        if is_falsy(ff_profile_dir):
+            return webdriver.FirefoxProfile()
+        return webdriver.FirefoxProfile(ff_profile_dir)
+
+    @property
+    def _geckodriver_log(self):
+        if SELENIUM_VERSION.major >= '3':
+            return {'log_path': os.path.join(self.log_dir, 'geckodriver.log')}
+        return {}
+
+    def create_headless_firefox(self, desired_capabilities, remote_url,
+                                ff_profile_dir):
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            options = webdriver.FirefoxOptions()
+            options.set_headless()
+        else:
+            options = None
+        return self.create_firefox(desired_capabilities, remote_url,
+                                   ff_profile_dir, options)
+
+    def create_ie(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.INTERNETEXPLORER
+        if is_truthy(remote_url):
+            return self._remote(default, desired_capabilities, remote_url)
+        capabilities = self._combine_capabilites(default, desired_capabilities)
+        return webdriver.Ie(capabilities=capabilities)
+
+    def create_edge(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.EDGE
+        if is_truthy(remote_url):
+            return self._remote(default, desired_capabilities, remote_url)
+        capabilities = self._combine_capabilites(default, desired_capabilities)
+        return webdriver.Edge(capabilities=capabilities)
+
+    def create_opera(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.OPERA
+        if is_truthy(remote_url):
+            return self._remote(default, desired_capabilities, remote_url)
+        capabilities = self._combine_capabilites(default, desired_capabilities)
+        return webdriver.Opera(desired_capabilities=capabilities)
+
+    def create_safari(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.SAFARI
+        if is_truthy(remote_url):
+            return self._remote(default, desired_capabilities, remote_url)
+        capabilities = self._combine_capabilites(default, desired_capabilities)
+        return webdriver.Safari(desired_capabilities=capabilities)
+
+    def create_phantomjs(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.PHANTOMJS
+        if is_truthy(remote_url):
+            return self._remote(default, desired_capabilities, remote_url)
+        capabilities = self._combine_capabilites(default, desired_capabilities)
+        return webdriver.PhantomJS(desired_capabilities=capabilities)
+
+    def create_htmlunit(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.HTMLUNIT
+        return self._remote(default, desired_capabilities, remote_url)
+
+    def create_htmlunit_with_js(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.HTMLUNITWITHJS
+        return self._remote(default, desired_capabilities, remote_url)
+
+    def create_android(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.ANDROID
+        return self._remote(default, desired_capabilities, remote_url)
+
+    def create_iphone(self, desired_capabilities, remote_url):
+        default = webdriver.DesiredCapabilities.IPHONE
+        return self._remote(default, desired_capabilities, remote_url)
+
+    def _remote(self, default_capabilities, user_capabilities, remote_url,
+                profile_dir=None):
+        remote_url = str(remote_url)
+        capabilities = self._combine_capabilites(default_capabilities,
+                                                 user_capabilities)
+        return webdriver.Remote(command_executor=remote_url,
+                                desired_capabilities=capabilities,
+                                browser_profile=profile_dir)
+
+    def _combine_capabilites(self, default, user):
+        default = default.copy()
+        default.update(user)
+        return default


Test patch:
diff --git a/test/unit/keywords/test_browsermanagement.py b/test/unit/keywords/test_browsermanagement.py
index 6ad0d665..d2fb471f 100644
--- a/test/unit/keywords/test_browsermanagement.py
+++ b/test/unit/keywords/test_browsermanagement.py
@@ -4,96 +4,11 @@ from mockito import when, mock, verify, verifyNoMoreInteractions, unstub
 from selenium import webdriver
 
 from SeleniumLibrary.keywords import BrowserManagementKeywords
+from SeleniumLibrary.utils import SELENIUM_VERSION
 
 
 class BrowserManagementTests(unittest.TestCase):
 
-    def test_create_firefox_browser(self):
-        test_browsers = (
-            (webdriver.Firefox, "ff"),
-            (webdriver.Firefox, "firEfOx")
-        )
-
-        for test_browser in test_browsers:
-            self.verify_browser(*test_browser)
-
-    def test_create_ie_browser(self):
-        test_browsers = (
-            (webdriver.Ie, "ie"),
-            (webdriver.Ie, "Internet Explorer")
-        )
-
-        for test_browser in test_browsers:
-            self.verify_browser(*test_browser)
-
-    def test_create_chrome_browser(self):
-        test_browsers = (
-            (webdriver.Chrome, "gOOglEchrOmE"),
-            (webdriver.Chrome, "gc"),
-            (webdriver.Chrome, "chrome")
-        )
-
-        for test_browser in test_browsers:
-            self.verify_browser(*test_browser)
-
-    def test_create_opera_browser(self):
-        self.verify_browser(webdriver.Opera, "OPERA")
-
-    def test_create_phantomjs_browser(self):
-        self.verify_browser(webdriver.PhantomJS, "PHANTOMJS")
-
-    def test_create_remote_browser(self):
-        self.verify_browser(
-            webdriver.Remote, "chrome", remote="http://127.0.0.1/wd/hub"
-        )
-
-    def test_create_htmlunit_browser(self):
-        self.verify_browser(webdriver.Remote, "htmlunit")
-
-    def test_create_htmlunitwihtjs_browser(self):
-        self.verify_browser(webdriver.Remote, "htmlunitwithjs")
-
-    def test_parse_capabilities_string(self):
-        ctx = mock()
-        bm = BrowserManagementKeywords(ctx)
-        expected_caps = "key1:val1,key2:val2"
-        capabilities = bm._parse_capabilities_string(expected_caps)
-        self.assertTrue("val1", capabilities["key1"])
-        self.assertTrue("val2", capabilities["key2"])
-        self.assertTrue(2, len(capabilities))
-        unstub()
-
-    def test_parse_complex_capabilities_string(self):
-        ctx = mock()
-        bm = BrowserManagementKeywords(ctx)
-        expected_caps = "proxyType:manual,httpProxy:IP:port"
-        capabilities = bm._parse_capabilities_string(expected_caps)
-        self.assertTrue("manual", capabilities["proxyType"])
-        self.assertTrue("IP:port", capabilities["httpProxy"])
-        self.assertTrue(2, len(capabilities))
-        unstub()
-
-    def test_create_remote_browser_with_desired_prefs(self):
-        expected_caps = {"key1": "val1", "key2": "val2"}
-        self.verify_browser(
-            webdriver.Remote, "chrome", remote="http://127.0.0.1/wd/hub",
-            desired_capabilities=expected_caps
-        )
-
-    def test_create_remote_browser_with_string_desired_prefs(self):
-        expected_caps = "key1:val1,key2:val2"
-        self.verify_browser(
-            webdriver.Remote, "chrome", remote="http://127.0.0.1/wd/hub",
-            desired_capabilities=expected_caps)
-
-    def test_capabilities_attribute_not_modified(self):
-        expected_caps = {"some_cap": "42"}
-        self.verify_browser(
-            webdriver.Remote, "chrome", remote="http://127.0.0.1/wd/hub",
-            desired_capabilities=expected_caps
-        )
-        self.assertFalse("some_cap" in webdriver.DesiredCapabilities.CHROME)
-
     def test_set_selenium_timeout_only_affects_open_browsers(self):
         ctx = mock()
         ctx.timeout = 5.0
@@ -144,7 +59,12 @@ class BrowserManagementTests(unittest.TestCase):
         ctx = mock()
         ctx.speed = 5.0
         browser = mock()
-        when(webdriver).Chrome().thenReturn(browser)
+        caps = webdriver.DesiredCapabilities.CHROME
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            when(webdriver).Chrome(desired_capabilities=caps,
+                                   options=None).thenReturn(browser)
+        else:
+            when(webdriver).Chrome(desired_capabilities=caps).thenReturn(browser)
         bm = BrowserManagementKeywords(ctx)
         bm.open_browser('http://robotframework.org/', 'chrome')
         self.assertEqual(browser._speed, 5.0)
@@ -154,28 +74,13 @@ class BrowserManagementTests(unittest.TestCase):
         ctx = mock()
         ctx.speed = 0.0
         browser = mock()
-        when(webdriver).Chrome().thenReturn(browser)
+        caps = webdriver.DesiredCapabilities.CHROME
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            when(webdriver).Chrome(desired_capabilities=caps,
+                                   options=None).thenReturn(browser)
+        else:
+            when(webdriver).Chrome(desired_capabilities=caps).thenReturn(browser)
         bm = BrowserManagementKeywords(ctx)
         bm.open_browser('http://robotframework.org/', 'chrome')
         verify(browser, times=0).__call__('_speed')
         unstub()
-
-    def verify_browser(self, webdriver_type, browser_name, **kw):
-        # todo try lambda *x: was_called = true
-        ctx = mock()
-        bm = BrowserManagementKeywords(ctx)
-        old_init = webdriver_type.__init__
-        webdriver_type.__init__ = self.mock_init
-
-        try:
-            self.was_called = False
-            bm._make_driver(browser_name, **kw)
-        except AttributeError:
-            pass  #kinda dangerous but I'm too lazy to mock out all the set_timeout calls
-        finally:
-            webdriver_type.__init__ = old_init
-            self.assertTrue(self.was_called)
-        unstub()
-
-    def mock_init(self, *args, **kw):
-        self.was_called = True
diff --git a/test/unit/utils/test_webdrivercreator.py b/test/unit/utils/test_webdrivercreator.py
new file mode 100644
index 00000000..8d5d7831
--- /dev/null
+++ b/test/unit/utils/test_webdrivercreator.py
@@ -0,0 +1,330 @@
+import unittest
+
+from mockito import mock, verify, when, unstub
+from selenium import webdriver
+
+from SeleniumLibrary.utils import SELENIUM_VERSION
+from SeleniumLibrary.keywords import WebDriverCreator
+
+
+class WebDriverCreatorTests(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.creator = WebDriverCreator('/log/dir')
+
+    def tearDown(self):
+        unstub()
+
+    def test_get_creator_method(self):
+        method = self.creator._get_creator_method('chrome')
+        self.assertTrue(method)
+
+        method = self.creator._get_creator_method('Chrome')
+        self.assertTrue(method)
+
+        method = self.creator._get_creator_method('Fire Fox')
+        self.assertTrue(method)
+
+        with self.assertRaisesRegexp(ValueError, 'foobar is not a supported browser.'):
+            self.creator._get_creator_method('foobar')
+
+    def test_parse_capabilities(self):
+        caps = self.creator._parse_capabilities('key1:value1,key2:value2')
+        expected = {'key1': 'value1', 'key2': 'value2'}
+        self.assertDictEqual(caps, expected)
+
+        caps = self.creator._parse_capabilities(expected)
+        self.assertDictEqual(caps, expected)
+
+        caps = self.creator._parse_capabilities('key1 : value1 , key2: value2')
+        self.assertDictEqual(caps, expected)
+
+        caps = self.creator._parse_capabilities(' key 1 : value 1 , key2:value2')
+        expected = {'key 1': 'value 1', 'key2': 'value2'}
+        self.assertDictEqual(caps, expected)
+
+        caps = self.creator._parse_capabilities('')
+        self.assertDictEqual(caps, {})
+
+        caps = self.creator._parse_capabilities({})
+        self.assertDictEqual(caps, {})
+
+    def test_chrome(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.CHROME
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            when(webdriver).Chrome(desired_capabilities=caps,
+                                   options=None).thenReturn(expected_webdriver)
+        else:
+            when(webdriver).Chrome(desired_capabilities=caps).thenReturn(expected_webdriver)
+        driver = self.creator.create_chrome({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_chrome_remote(self):
+        url = 'http://localhost:4444/wd/hub'
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.CHROME
+        when(webdriver).Remote(command_executor=url,
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_chrome({}, url)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_chrome_healdless(self):
+        caps = webdriver.DesiredCapabilities.CHROME
+        expected_webdriver = mock()
+        options = mock()
+        when(webdriver).ChromeOptions().thenReturn(options)
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            when(webdriver).Chrome(desired_capabilities=caps,
+                                   options=options).thenReturn(expected_webdriver)
+        else:
+            when(webdriver).Chrome(desired_capabilities=caps).thenReturn(expected_webdriver)
+        driver = self.creator.create_headless_chrome({}, None)
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            verify(options).set_headless()
+        else:
+            verify(options, times=0).set_headless()
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_firefox(self):
+        expected_webdriver = mock()
+        profile = mock()
+        when(webdriver).FirefoxProfile().thenReturn(profile)
+        caps = webdriver.DesiredCapabilities.FIREFOX
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            when(webdriver).Firefox(capabilities=caps,
+                                    options=None,
+                                    firefox_profile=profile,
+                                    log_path='/log/dir/geckodriver.log').thenReturn(expected_webdriver)
+        else:
+            when(webdriver).Firefox(capabilities=caps,
+                                    firefox_profile=profile).thenReturn(expected_webdriver)
+        driver = self.creator.create_firefox({}, None, None)
+        self.assertEqual(driver, expected_webdriver)
+        verify(webdriver).FirefoxProfile()
+
+    def test_firefox_remote(self):
+        url = 'http://localhost:4444/wd/hub'
+        profile = mock()
+        when(webdriver).FirefoxProfile().thenReturn(profile)
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.FIREFOX
+        when(webdriver).Remote(command_executor=url,
+                               desired_capabilities=caps,
+                               browser_profile=profile).thenReturn(expected_webdriver)
+        driver = self.creator.create_firefox({}, url, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_firefox_profile(self):
+        expected_webdriver = mock()
+        profile = mock()
+        when(webdriver).FirefoxProfile('/profile/dir').thenReturn(profile)
+        caps = webdriver.DesiredCapabilities.FIREFOX
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            when(webdriver).Firefox(capabilities=caps,
+                                    options=None,
+                                    log_path='/log/dir/geckodriver.log',
+                                    firefox_profile=profile).thenReturn(expected_webdriver)
+        else:
+            when(webdriver).Firefox(capabilities=caps,
+                                    firefox_profile=profile).thenReturn(expected_webdriver)
+        driver = self.creator.create_firefox({}, None, '/profile/dir')
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_firefox_headless(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.FIREFOX
+        profile = mock()
+        when(webdriver).FirefoxProfile().thenReturn(profile)
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            options = mock()
+            when(webdriver).FirefoxOptions().thenReturn(options)
+            when(webdriver).Firefox(capabilities=caps,
+                                    options=options,
+                                    log_path='/log/dir/geckodriver.log',
+                                    firefox_profile=profile).thenReturn(expected_webdriver)
+        else:
+            when(webdriver).Firefox(capabilities=caps,
+                                    firefox_profile=profile).thenReturn(expected_webdriver)
+        driver = self.creator.create_headless_firefox({}, None, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_ie(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.INTERNETEXPLORER
+        when(webdriver).Ie(capabilities=caps).thenReturn(expected_webdriver)
+        driver = self.creator.create_ie({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_ie_remote(self):
+        url = 'http://localhost:4444/wd/hub'
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.INTERNETEXPLORER
+        when(webdriver).Remote(command_executor=url,
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_ie({}, url)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_edge(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.EDGE
+        when(webdriver).Edge(capabilities=caps).thenReturn(expected_webdriver)
+        driver = self.creator.create_edge({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_edge_remote(self):
+        url = 'http://localhost:4444/wd/hub'
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.EDGE
+        when(webdriver).Remote(command_executor=url,
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_edge({}, url)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_opera(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.OPERA
+        when(webdriver).Opera(desired_capabilities=caps).thenReturn(expected_webdriver)
+        driver = self.creator.create_opera({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_opera_remote(self):
+        url = 'http://localhost:4444/wd/hub'
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.OPERA
+        when(webdriver).Remote(command_executor=url,
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_opera({}, url)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_safari(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.SAFARI
+        when(webdriver).Safari(desired_capabilities=caps).thenReturn(expected_webdriver)
+        driver = self.creator.create_safari({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_safari_remote(self):
+        url = 'http://localhost:4444/wd/hub'
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.SAFARI
+        when(webdriver).Remote(command_executor=url,
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_safari({}, url)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_phantomjs(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.PHANTOMJS
+        when(webdriver).PhantomJS(desired_capabilities=caps).thenReturn(expected_webdriver)
+        driver = self.creator.create_phantomjs({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_phantomjs_remote(self):
+        url = 'http://localhost:4444/wd/hub'
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.PHANTOMJS
+        when(webdriver).Remote(command_executor=url,
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_phantomjs({}, url)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_htmlunit(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.HTMLUNIT
+        when(webdriver).Remote(command_executor='None',
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_htmlunit({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_htmlunit_with_js(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.HTMLUNITWITHJS
+        when(webdriver).Remote(command_executor='None',
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_htmlunit_with_js({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_android(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.ANDROID
+        when(webdriver).Remote(command_executor='None',
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_android({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_iphone(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.IPHONE
+        when(webdriver).Remote(command_executor='None',
+                               desired_capabilities=caps,
+                               browser_profile=None).thenReturn(expected_webdriver)
+        driver = self.creator.create_iphone({}, None)
+        self.assertEqual(driver, expected_webdriver)
+
+    def test_create_driver_chrome(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.CHROME
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            when(webdriver).Chrome(desired_capabilities=caps,
+                                   options=None).thenReturn(expected_webdriver)
+        else:
+            when(webdriver).Chrome(desired_capabilities=caps).thenReturn(expected_webdriver)
+        for browser in ['chrome', 'googlechrome', 'gc']:
+            driver = self.creator.create_driver(browser, None, None)
+            self.assertEqual(driver, expected_webdriver)
+
+    def test_create_driver_firefox(self):
+        expected_webdriver = mock()
+        profile = mock()
+        when(webdriver).FirefoxProfile().thenReturn(profile)
+        caps = webdriver.DesiredCapabilities.FIREFOX
+        if SELENIUM_VERSION.major >= '3' and SELENIUM_VERSION.minor >= '8':
+            when(webdriver).Firefox(capabilities=caps,
+                                    options=None,
+                                    log_path='/log/dir/geckodriver.log',
+                                    firefox_profile=profile).thenReturn(expected_webdriver)
+        else:
+            when(webdriver).Firefox(capabilities=caps,
+                                    firefox_profile=profile).thenReturn(expected_webdriver)
+        for browser in ['ff', 'firefox']:
+            driver = self.creator.create_driver(browser, None, None, None)
+            self.assertEqual(driver, expected_webdriver)
+
+    def test_create_driver_ie(self):
+        expected_webdriver = mock()
+        caps = webdriver.DesiredCapabilities.INTERNETEXPLORER
+        when(webdriver).Ie(capabilities=caps).thenReturn(expected_webdriver)
+        for browser in ['ie', 'Internet Explorer']:
+            driver = self.creator.create_driver(browser, None, None)
+            self.assertEqual(driver, expected_webdriver)
+
+    def test_combine_capabilites(self):
+        default = webdriver.DesiredCapabilities.CHROME.copy()
+        user = {'platform': 'Linux'}
+        combined = self.creator._combine_capabilites(default, user)
+        expected = default.copy()
+        expected['platform'] = 'Linux'
+        self.assertDictEqual(combined, expected)
+
+        combined = self.creator._combine_capabilites(default, {})
+        self.assertDictEqual(combined, default)
+
+        combined = self.creator._combine_capabilites({}, {})
+        self.assertDictEqual(combined, {})
+
+        with self.assertRaises(AttributeError):
+            self.creator._combine_capabilites('foo', 'bar')
+
+        with self.assertRaises(ValueError):
+            self.creator._combine_capabilites({}, 'bar')