[py] Add support for the new MicrosoftEdge (#7459)

Extracting Chromium from Chrome module. Have both Chrome and Edge
inherited from Chromium module. Add is_legacy parameter to launch
the new MicrosoftEdge.

Cr-Mirrored-From: https://chromium.googlesource.com/external/github.com/SeleniumHQ/selenium
Cr-Mirrored-Commit: 139ca6cddf95feb4446931c75f2bb7ce9ff83496
diff --git a/MANIFEST.in b/MANIFEST.in
index a911439..04207c9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,6 +4,7 @@
 recursive-include selenium/webdriver/common/actions *.py
 recursive-include selenium/webdriver/common/html5 *.py
 recursive-include selenium/common *.py
+recursive-include selenium/webdriver/chromium *.py
 recursive-include selenium/webdriver/chrome *.py
 recursive-include selenium/webdriver/opera *.py
 recursive-include selenium/webdriver/phantomjs *.py
diff --git a/build.desc b/build.desc
index 78e8563..ee7c116 100644
--- a/build.desc
+++ b/build.desc
@@ -43,6 +43,11 @@
   browsers = [ "edge"])
 
 py_test(
+  name = "chromium_edge_test",
+  deps = [":test_edge"],
+  browsers = ["chromiumedge"])
+
+py_test(
   name = "remote_firefox_test",
   deps = [ ":test_remote_firefox" ],
   browsers = [ "remote_firefox" ])
diff --git a/conftest.py b/conftest.py
index 7ba634a..f1b90e7 100644
--- a/conftest.py
+++ b/conftest.py
@@ -44,6 +44,7 @@
     'Remote',
     'Safari',
     'WebKitGTK',
+    'ChromiumEdge',
 )
 
 
@@ -117,6 +118,9 @@
             options = get_options('Firefox', request.config)
         if driver_class == 'WebKitGTK':
             options = get_options(driver_class, request.config)
+        if driver_class == 'ChromiumEdge':
+            options = get_options('Edge', request.config)
+            kwargs.update({'is_legacy': False})
         if driver_path is not None:
             kwargs['executable_path'] = driver_path
         if options is not None:
@@ -131,6 +135,10 @@
     browser_path = config.option.binary
     browser_args = config.option.args
     options = None
+
+    if driver_class == 'Edge':
+        return getattr(webdriver, '{}Options'.format(driver_class))(False)
+
     if browser_path or browser_args:
         options = getattr(webdriver, '{}Options'.format(driver_class))()
         if driver_class == 'WebKitGTK':
diff --git a/selenium/webdriver/__init__.py b/selenium/webdriver/__init__.py
index b0f1791..77dca82 100644
--- a/selenium/webdriver/__init__.py
+++ b/selenium/webdriver/__init__.py
@@ -23,6 +23,8 @@
 from .ie.webdriver import WebDriver as Ie  # noqa
 from .ie.options import Options as IeOptions  # noqa
 from .edge.webdriver import WebDriver as Edge  # noqa
+from .edge.webdriver import WebDriver as ChromiumEdge  # noqa
+from .edge.options import Options as EdgeOptions #noqa
 from .opera.webdriver import WebDriver as Opera  # noqa
 from .safari.webdriver import WebDriver as Safari  # noqa
 from .blackberry.webdriver import WebDriver as BlackBerry  # noqa
diff --git a/selenium/webdriver/chrome/options.py b/selenium/webdriver/chrome/options.py
index c5ec418..5325aa3 100644
--- a/selenium/webdriver/chrome/options.py
+++ b/selenium/webdriver/chrome/options.py
@@ -19,159 +19,19 @@
 import os
 
 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
-from selenium.webdriver.common.options import ArgOptions
+from selenium.webdriver.chromium.options import ChromiumOptions
 
 
-class Options(ArgOptions):
+class Options(ChromiumOptions):
     KEY = "goog:chromeOptions"
 
-    def __init__(self):
-        super(Options, self).__init__()
-        self._binary_location = ''
-        self._extension_files = []
-        self._extensions = []
-        self._experimental_options = {}
-        self._debugger_address = None
-
-    @property
-    def binary_location(self):
-        """
-        :Returns: The location of the binary, otherwise an empty string
-        """
-        return self._binary_location
-
-    @binary_location.setter
-    def binary_location(self, value):
-        """
-        Allows you to set where the chromium binary lives
-
-        :Args:
-         - value: path to the Chromium binary
-        """
-        self._binary_location = value
-
-    @property
-    def debugger_address(self):
-        """
-        :Returns: The address of the remote devtools instance
-        """
-        return self._debugger_address
-
-    @debugger_address.setter
-    def debugger_address(self, value):
-        """
-        Allows you to set the address of the remote devtools instance
-        that the ChromeDriver instance will try to connect to during an
-        active wait.
-
-        :Args:
-         - value: address of remote devtools instance if any (hostname[:port])
-        """
-        self._debugger_address = value
-
-    @property
-    def extensions(self):
-        """
-        :Returns: A list of encoded extensions that will be loaded into chrome
-        """
-        encoded_extensions = []
-        for ext in self._extension_files:
-            file_ = open(ext, 'rb')
-            # Should not use base64.encodestring() which inserts newlines every
-            # 76 characters (per RFC 1521).  Chromedriver has to remove those
-            # unnecessary newlines before decoding, causing performance hit.
-            encoded_extensions.append(base64.b64encode(file_.read()).decode('UTF-8'))
-
-            file_.close()
-        return encoded_extensions + self._extensions
-
-    def add_extension(self, extension):
-        """
-        Adds the path to the extension to a list that will be used to extract it
-        to the ChromeDriver
-
-        :Args:
-         - extension: path to the \\*.crx file
-        """
-        if extension:
-            extension_to_add = os.path.abspath(os.path.expanduser(extension))
-            if os.path.exists(extension_to_add):
-                self._extension_files.append(extension_to_add)
-            else:
-                raise IOError("Path to the extension doesn't exist")
-        else:
-            raise ValueError("argument can not be null")
-
-    def add_encoded_extension(self, extension):
-        """
-        Adds Base64 encoded string with extension data to a list that will be used to extract it
-        to the ChromeDriver
-
-        :Args:
-         - extension: Base64 encoded string with extension data
-        """
-        if extension:
-            self._extensions.append(extension)
-        else:
-            raise ValueError("argument can not be null")
-
-    @property
-    def experimental_options(self):
-        """
-        :Returns: A dictionary of experimental options for chrome
-        """
-        return self._experimental_options
-
-    def add_experimental_option(self, name, value):
-        """
-        Adds an experimental option which is passed to chrome.
-
-        :Args:
-          name: The experimental option name.
-          value: The option value.
-        """
-        self._experimental_options[name] = value
-
-    @property
-    def headless(self):
-        """
-        :Returns: True if the headless argument is set, else False
-        """
-        return '--headless' in self._arguments
-
-    @headless.setter
-    def headless(self, value):
-        """
-        Sets the headless argument
-
-        :Args:
-          value: boolean value indicating to set the headless option
-        """
-        args = {'--headless'}
-        if value is True:
-            self._arguments.extend(args)
-        else:
-            self._arguments = list(set(self._arguments) - args)
-
-    def to_capabilities(self):
-        """
-        Creates a capabilities with all the options that have been set
-
-        :Returns: A dictionary with everything
-        """
-        caps = self._caps
-        chrome_options = self.experimental_options.copy()
-        chrome_options["extensions"] = self.extensions
-        if self.binary_location:
-            chrome_options["binary"] = self.binary_location
-        chrome_options["args"] = self.arguments
-        if self.debugger_address:
-            chrome_options["debuggerAddress"] = self.debugger_address
-
-        caps[self.KEY] = chrome_options
-
-        return caps
-
     @property
     def default_capabilities(self):
         return DesiredCapabilities.CHROME.copy()
+
+    def to_capabilities(self):
+        """
+        Creates a capabilities with all the options that have been set and
+        :Returns: A dictionary with everything
+        """
+        return super(Options, self).to_capabilities(self.KEY)
diff --git a/selenium/webdriver/chrome/service.py b/selenium/webdriver/chrome/service.py
index 5a67b2c..786c538 100644
--- a/selenium/webdriver/chrome/service.py
+++ b/selenium/webdriver/chrome/service.py
@@ -15,10 +15,10 @@
 # specific language governing permissions and limitations
 # under the License.
 
-from selenium.webdriver.common import service
+from selenium.webdriver.chromium import service
 
 
-class Service(service.Service):
+class Service(service.ChromiumService):
     """
     Object that manages the starting and stopping of the ChromeDriver
     """
@@ -27,19 +27,16 @@
                  log_path=None, env=None):
         """
         Creates a new instance of the Service
-
         :Args:
          - executable_path : Path to the ChromeDriver
          - port : Port the service is running on
          - service_args : List of args to pass to the chromedriver service
          - log_path : Path for the chromedriver service to log to"""
 
-        self.service_args = service_args or []
-        if log_path:
-            self.service_args.append('--log-path=%s' % log_path)
-
-        service.Service.__init__(self, executable_path, port=port, env=env,
-                                 start_error_message="Please see https://sites.google.com/a/chromium.org/chromedriver/home")
-
-    def command_line_args(self):
-        return ["--port=%d" % self.port] + self.service_args
+        super(Service, self).__init__(
+            executable_path,
+            port,
+            service_args,
+            log_path,
+            env,
+            "Please see https://sites.google.com/a/chromium.org/chromedriver/home")
diff --git a/selenium/webdriver/chrome/webdriver.py b/selenium/webdriver/chrome/webdriver.py
index 97d12d1..3b3617d 100644
--- a/selenium/webdriver/chrome/webdriver.py
+++ b/selenium/webdriver/chrome/webdriver.py
@@ -15,20 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 import warnings
-from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
-from .remote_connection import ChromeRemoteConnection
-from .service import Service
+from selenium.webdriver.chromium.webdriver import ChromiumDriver
 from .options import Options
+from .service import Service
 
 
 DEFAULT_PORT = 0
 DEFAULT_SERVICE_LOG_PATH = None
 
 
-class WebDriver(RemoteWebDriver):
+class WebDriver(ChromiumDriver):
     """
     Controls the ChromeDriver and allows you to drive the browser.
-
     You will need to download the ChromeDriver executable from
     http://chromedriver.storage.googleapis.com/index.html
     """
@@ -39,9 +37,7 @@
                  chrome_options=None, service=None, keep_alive=True):
         """
         Creates a new instance of the chrome driver.
-
         Starts the service and then creates new instance of chrome driver.
-
         :Args:
          - executable_path - Deprecated: path to the executable. If the default is used it assumes the executable is in the $PATH
          - port - Deprecated: port you would like the service to run, if left as 0, a free port will be found.
@@ -52,172 +48,16 @@
          - service_log_path - Deprecated: Where to log information from the driver.
          - keep_alive - Whether to configure ChromeRemoteConnection to use HTTP keep-alive.
         """
-        if executable_path != 'chromedriver':
-            warnings.warn('executable_path has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
-        if desired_capabilities is not None:
-            warnings.warn('desired_capabilities has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
-        if port != DEFAULT_PORT:
-            warnings.warn('port has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
-        self.port = port
-        if service_log_path != DEFAULT_SERVICE_LOG_PATH:
-            warnings.warn('service_log_path has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
-
         if chrome_options:
             warnings.warn('use options instead of chrome_options',
                           DeprecationWarning, stacklevel=2)
             options = chrome_options
 
-        if options is None:
-            # desired_capabilities stays as passed in
-            if desired_capabilities is None:
-                desired_capabilities = self.create_options().to_capabilities()
-        else:
-            if desired_capabilities is None:
-                desired_capabilities = options.to_capabilities()
-            else:
-                desired_capabilities.update(options.to_capabilities())
+        if service is None:
+            service = Service(executable_path, port, service_args, service_log_path)
 
-        if service:
-            self.service = service
-        else:
-            self.service = Service(
-                executable_path,
-                port=port,
-                service_args=service_args,
-                log_path=service_log_path)
-        self.service.start()
-
-        try:
-            RemoteWebDriver.__init__(
-                self,
-                command_executor=ChromeRemoteConnection(
-                    remote_server_addr=self.service.service_url,
-                    keep_alive=keep_alive),
-                desired_capabilities=desired_capabilities)
-        except Exception:
-            self.quit()
-            raise
-        self._is_remote = False
-
-    def launch_app(self, id):
-        """Launches Chrome app specified by id."""
-        return self.execute("launchApp", {'id': id})
-
-    def get_network_conditions(self):
-        """
-        Gets Chrome network emulation settings.
-
-        :Returns:
-            A dict. For example:
-
-            {'latency': 4, 'download_throughput': 2, 'upload_throughput': 2,
-            'offline': False}
-
-        """
-        return self.execute("getNetworkConditions")['value']
-
-    def set_network_conditions(self, **network_conditions):
-        """
-        Sets Chrome network emulation settings.
-
-        :Args:
-         - network_conditions: A dict with conditions specification.
-
-        :Usage:
-            ::
-
-                driver.set_network_conditions(
-                    offline=False,
-                    latency=5,  # additional latency (ms)
-                    download_throughput=500 * 1024,  # maximal throughput
-                    upload_throughput=500 * 1024)  # maximal throughput
-
-            Note: 'throughput' can be used to set both (for download and upload).
-        """
-        self.execute("setNetworkConditions", {
-            'network_conditions': network_conditions
-        })
-
-    def execute_cdp_cmd(self, cmd, cmd_args):
-        """
-        Execute Chrome Devtools Protocol command and get returned result
-
-        The command and command args should follow chrome devtools protocol domains/commands, refer to link
-        https://chromedevtools.github.io/devtools-protocol/
-
-        :Args:
-         - cmd: A str, command name
-         - cmd_args: A dict, command args. empty dict {} if there is no command args
-
-        :Usage:
-            ::
-
-                driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})
-
-        :Returns:
-            A dict, empty dict {} if there is no result to return.
-            For example to getResponseBody:
-
-            {'base64Encoded': False, 'body': 'response body string'}
-
-        """
-        return self.execute("executeCdpCommand", {'cmd': cmd, 'params': cmd_args})['value']
-
-    def get_sinks(self):
-        """
-        :Returns: A list of sinks avaliable for Cast.
-        """
-        return self.execute('getSinks')['value']
-
-    def get_issue_message(self):
-        """
-        :Returns: An error message when there is any issue in a Cast session.
-        """
-        return self.execute('getIssueMessage')['value']
-
-    def set_sink_to_use(self, sink_name):
-        """
-        Sets a specific sink, using its name, as a Cast session receiver target.
-
-        :Args:
-         - sink_name: Name of the sink to use as the target.
-        """
-        return self.execute('setSinkToUse', {'sinkName': sink_name})
-
-    def start_tab_mirroring(self, sink_name):
-        """
-        Starts a tab mirroring session on a specific receiver target.
-
-        :Args:
-         - sink_name: Name of the sink to use as the target.
-        """
-        return self.execute('startTabMirroring', {'sinkName': sink_name})
-
-    def stop_casting(self, sink_name):
-        """
-        Stops the existing Cast session on a specific receiver target.
-
-        :Args:
-         - sink_name: Name of the sink to stop the Cast session.
-        """
-        return self.execute('stopCasting', {'sinkName': sink_name})
-
-    def quit(self):
-        """
-        Closes the browser and shuts down the ChromeDriver executable
-        that is started when starting the ChromeDriver
-        """
-        try:
-            RemoteWebDriver.quit(self)
-        except Exception:
-            # We don't care about the message because something probably has gone wrong
-            pass
-        finally:
-            self.service.stop()
+        super(WebDriver, self).__init__(executable_path, port, options, service_args, desired_capabilities, service_log_path,
+            service, keep_alive)
 
     def create_options(self):
         return Options()
diff --git a/selenium/webdriver/chromium/__init__.py b/selenium/webdriver/chromium/__init__.py
new file mode 100644
index 0000000..3f22a88
--- /dev/null
+++ b/selenium/webdriver/chromium/__init__.py
@@ -0,0 +1,16 @@
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you 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.
\ No newline at end of file
diff --git a/selenium/webdriver/chromium/options.py b/selenium/webdriver/chromium/options.py
new file mode 100644
index 0000000..66fcfd3
--- /dev/null
+++ b/selenium/webdriver/chromium/options.py
@@ -0,0 +1,169 @@
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you 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 base64
+import os
+
+from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
+from selenium.webdriver.common.options import ArgOptions
+
+
+class ChromiumOptions(ArgOptions):
+
+    def __init__(self):
+        super(ChromiumOptions, self).__init__()
+        self._binary_location = ''
+        self._extension_files = []
+        self._extensions = []
+        self._experimental_options = {}
+        self._debugger_address = None
+
+    @property
+    def binary_location(self):
+        """
+        :Returns: The location of the binary, otherwise an empty string
+        """
+        return self._binary_location
+
+    @binary_location.setter
+    def binary_location(self, value):
+        """
+        Allows you to set where the chromium binary lives
+        :Args:
+         - value: path to the Chromium binary
+        """
+        self._binary_location = value
+
+    @property
+    def debugger_address(self):
+        """
+        :Returns: The address of the remote devtools instance
+        """
+        return self._debugger_address
+
+    @debugger_address.setter
+    def debugger_address(self, value):
+        """
+        Allows you to set the address of the remote devtools instance
+        that the ChromeDriver instance will try to connect to during an
+        active wait.
+        :Args:
+         - value: address of remote devtools instance if any (hostname[:port])
+        """
+        self._debugger_address = value
+
+    @property
+    def extensions(self):
+        """
+        :Returns: A list of encoded extensions that will be loaded
+        """
+        encoded_extensions = []
+        for ext in self._extension_files:
+            file_ = open(ext, 'rb')
+            # Should not use base64.encodestring() which inserts newlines every
+            # 76 characters (per RFC 1521).  Chromedriver has to remove those
+            # unnecessary newlines before decoding, causing performance hit.
+            encoded_extensions.append(base64.b64encode(file_.read()).decode('UTF-8'))
+
+            file_.close()
+        return encoded_extensions + self._extensions
+
+    def add_extension(self, extension):
+        """
+        Adds the path to the extension to a list that will be used to extract it
+        to the ChromeDriver
+        :Args:
+         - extension: path to the \\*.crx file
+        """
+        if extension:
+            extension_to_add = os.path.abspath(os.path.expanduser(extension))
+            if os.path.exists(extension_to_add):
+                self._extension_files.append(extension_to_add)
+            else:
+                raise IOError("Path to the extension doesn't exist")
+        else:
+            raise ValueError("argument can not be null")
+
+    def add_encoded_extension(self, extension):
+        """
+        Adds Base64 encoded string with extension data to a list that will be used to extract it
+        to the ChromeDriver
+        :Args:
+         - extension: Base64 encoded string with extension data
+        """
+        if extension:
+            self._extensions.append(extension)
+        else:
+            raise ValueError("argument can not be null")
+
+    @property
+    def experimental_options(self):
+        """
+        :Returns: A dictionary of experimental options for chromium
+        """
+        return self._experimental_options
+
+    def add_experimental_option(self, name, value):
+        """
+        Adds an experimental option which is passed to chromium.
+        :Args:
+          name: The experimental option name.
+          value: The option value.
+        """
+        self._experimental_options[name] = value
+
+    @property
+    def headless(self):
+        """
+        :Returns: True if the headless argument is set, else False
+        """
+        return '--headless' in self._arguments
+
+    @headless.setter
+    def headless(self, value):
+        """
+        Sets the headless argument
+        :Args:
+          value: boolean value indicating to set the headless option
+        """
+        args = {'--headless'}
+        if value is True:
+            self._arguments.extend(args)
+        else:
+            self._arguments = list(set(self._arguments) - args)
+
+    def to_capabilities(self, capabilityKey):
+        """
+        Creates a capabilities with all the options that have been set
+        :Returns: A dictionary with everything
+        """
+        caps = self._caps
+        chrome_options = self.experimental_options.copy()
+        chrome_options["extensions"] = self.extensions
+        if self.binary_location:
+            chrome_options["binary"] = self.binary_location
+        chrome_options["args"] = self.arguments
+        if self.debugger_address:
+            chrome_options["debuggerAddress"] = self.debugger_address
+
+        caps[capabilityKey] = chrome_options
+
+        return caps
+
+    @property
+    def default_capabilities(self):
+        pass
diff --git a/selenium/webdriver/chrome/remote_connection.py b/selenium/webdriver/chromium/remote_connection.py
similarity index 97%
rename from selenium/webdriver/chrome/remote_connection.py
rename to selenium/webdriver/chromium/remote_connection.py
index d8408a0..71770b0 100644
--- a/selenium/webdriver/chrome/remote_connection.py
+++ b/selenium/webdriver/chromium/remote_connection.py
@@ -18,7 +18,7 @@
 from selenium.webdriver.remote.remote_connection import RemoteConnection
 
 
-class ChromeRemoteConnection(RemoteConnection):
+class ChromiumRemoteConnection(RemoteConnection):
 
     def __init__(self, remote_server_addr, keep_alive=True):
         RemoteConnection.__init__(self, remote_server_addr, keep_alive)
diff --git a/selenium/webdriver/chromium/service.py b/selenium/webdriver/chromium/service.py
new file mode 100644
index 0000000..719dc13
--- /dev/null
+++ b/selenium/webdriver/chromium/service.py
@@ -0,0 +1,46 @@
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you 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.
+
+from selenium.webdriver.common import service
+
+
+class ChromiumService(service.Service):
+    """
+    Object that manages the starting and stopping the WebDriver instance of the ChromiumDriver
+    """
+
+    def __init__(self, executable_path, port=0, service_args=None,
+                 log_path=None, env=None, start_error_message=None):
+        """
+        Creates a new instance of the Service
+        :Args:
+         - executable_path : Path to the WebDriver executable
+         - port : Port the service is running on
+         - service_args : List of args to pass to the WebDriver service
+         - log_path : Path for the WebDriver service to log to"""
+
+        self.service_args = service_args or []
+        if log_path:
+            self.service_args.append('--log-path=%s' % log_path)
+
+        if start_error_message is None:
+            raise AttributeError("start_error_message should not be empty")
+
+        service.Service.__init__(self, executable_path, port=port, env=env, start_error_message=start_error_message)
+
+    def command_line_args(self):
+        return ["--port=%d" % self.port] + self.service_args
diff --git a/selenium/webdriver/chromium/webdriver.py b/selenium/webdriver/chromium/webdriver.py
new file mode 100644
index 0000000..434f457
--- /dev/null
+++ b/selenium/webdriver/chromium/webdriver.py
@@ -0,0 +1,192 @@
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you 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 warnings
+from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
+from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection
+from .service import ChromiumService
+from .options import ChromiumOptions
+
+
+DEFAULT_PORT = 0
+DEFAULT_SERVICE_LOG_PATH = None
+
+
+class ChromiumDriver(RemoteWebDriver):
+    """
+    Controls the WebDriver instance of ChromiumDriver and allows you to drive the browser.
+    """
+
+    def __init__(self, executable_path="chromedriver", port=DEFAULT_PORT,
+                 options=None, service_args=None,
+                 desired_capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH,
+                 service=None, keep_alive=True):
+        """
+        Creates a new WebDriver instance of the ChromiumDriver.
+        Starts the service and then creates new WebDriver instance of ChromiumDriver.
+        :Args:
+         - executable_path - Deprecated: path to the executable. If the default is used it assumes the executable is in the $PATH
+         - port - Deprecated: port you would like the service to run, if left as 0, a free port will be found.
+         - options - this takes an instance of ChromiumOptions
+         - service_args - Deprecated: List of args to pass to the driver service
+         - desired_capabilities - Deprecated: Dictionary object with non-browser specific
+           capabilities only, such as "proxy" or "loggingPref".
+         - service_log_path - Deprecated: Where to log information from the driver.
+         - keep_alive - Whether to configure ChromiumRemoteConnection to use HTTP keep-alive.
+        """
+        if executable_path != 'chromedriver':
+            warnings.warn('executable_path has been deprecated, please pass in a Service object',
+                          DeprecationWarning, stacklevel=2)
+        if desired_capabilities is not None:
+            warnings.warn('desired_capabilities has been deprecated, please pass in a Service object',
+                          DeprecationWarning, stacklevel=2)
+        if port != DEFAULT_PORT:
+            warnings.warn('port has been deprecated, please pass in a Service object',
+                          DeprecationWarning, stacklevel=2)
+        self.port = port
+        if service_log_path != DEFAULT_SERVICE_LOG_PATH:
+            warnings.warn('service_log_path has been deprecated, please pass in a Service object',
+                          DeprecationWarning, stacklevel=2)
+
+        if options is None:
+            # desired_capabilities stays as passed in
+            if desired_capabilities is None:
+                desired_capabilities = self.create_options().to_capabilities()
+        else:
+            if desired_capabilities is None:
+                desired_capabilities = options.to_capabilities()
+            else:
+                desired_capabilities.update(options.to_capabilities())
+
+        if service is None:
+            raise AttributeError('service cannot be None')
+
+        self.service = service
+        self.service.start()
+
+        try:
+            RemoteWebDriver.__init__(
+                self,
+                command_executor=ChromiumRemoteConnection(
+                    remote_server_addr=self.service.service_url,
+                    keep_alive=keep_alive),
+                desired_capabilities=desired_capabilities)
+        except Exception:
+            self.quit()
+            raise
+        self._is_remote = False
+
+    def launch_app(self, id):
+        """Launches Chromium app specified by id."""
+        return self.execute("launchApp", {'id': id})
+
+    def get_network_conditions(self):
+        """
+        Gets Chromium network emulation settings.
+        :Returns:
+            A dict. For example:
+            {'latency': 4, 'download_throughput': 2, 'upload_throughput': 2,
+            'offline': False}
+        """
+        return self.execute("getNetworkConditions")['value']
+
+    def set_network_conditions(self, **network_conditions):
+        """
+        Sets Chromium network emulation settings.
+        :Args:
+         - network_conditions: A dict with conditions specification.
+        :Usage:
+            ::
+                driver.set_network_conditions(
+                    offline=False,
+                    latency=5,  # additional latency (ms)
+                    download_throughput=500 * 1024,  # maximal throughput
+                    upload_throughput=500 * 1024)  # maximal throughput
+            Note: 'throughput' can be used to set both (for download and upload).
+        """
+        self.execute("setNetworkConditions", {
+            'network_conditions': network_conditions
+        })
+
+    def execute_cdp_cmd(self, cmd, cmd_args):
+        """
+        Execute Chrome Devtools Protocol command and get returned result
+        The command and command args should follow chrome devtools protocol domains/commands, refer to link
+        https://chromedevtools.github.io/devtools-protocol/
+        :Args:
+         - cmd: A str, command name
+         - cmd_args: A dict, command args. empty dict {} if there is no command args
+        :Usage:
+            ::
+                driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})
+        :Returns:
+            A dict, empty dict {} if there is no result to return.
+            For example to getResponseBody:
+            {'base64Encoded': False, 'body': 'response body string'}
+        """
+        return self.execute("executeCdpCommand", {'cmd': cmd, 'params': cmd_args})['value']
+
+    def get_sinks(self):
+        """
+        :Returns: A list of sinks avaliable for Cast.
+        """
+        return self.execute('getSinks')['value']
+
+    def get_issue_message(self):
+        """
+        :Returns: An error message when there is any issue in a Cast session.
+        """
+        return self.execute('getIssueMessage')['value']
+
+    def set_sink_to_use(self, sink_name):
+        """
+        Sets a specific sink, using its name, as a Cast session receiver target.
+        :Args:
+         - sink_name: Name of the sink to use as the target.
+        """
+        return self.execute('setSinkToUse', {'sinkName': sink_name})
+
+    def start_tab_mirroring(self, sink_name):
+        """
+        Starts a tab mirroring session on a specific receiver target.
+        :Args:
+         - sink_name: Name of the sink to use as the target.
+        """
+        return self.execute('startTabMirroring', {'sinkName': sink_name})
+
+    def stop_casting(self, sink_name):
+        """
+        Stops the existing Cast session on a specific receiver target.
+        :Args:
+         - sink_name: Name of the sink to stop the Cast session.
+        """
+        return self.execute('stopCasting', {'sinkName': sink_name})
+
+    def quit(self):
+        """
+        Closes the browser and shuts down the ChromiumDriver executable
+        that is started when starting the ChromiumDriver
+        """
+        try:
+            RemoteWebDriver.quit(self)
+        except Exception:
+            # We don't care about the message because something probably has gone wrong
+            pass
+        finally:
+            self.service.stop()
+
+    def create_options(self):
+        pass
diff --git a/selenium/webdriver/edge/options.py b/selenium/webdriver/edge/options.py
index 2fa5b03..93a215c 100644
--- a/selenium/webdriver/edge/options.py
+++ b/selenium/webdriver/edge/options.py
@@ -16,21 +16,31 @@
 # under the License.
 
 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
-from selenium.webdriver.common.options import BaseOptions
+from selenium.webdriver.chromium.options import ChromiumOptions
 
 
-class Options(BaseOptions):
+class Options(ChromiumOptions):
+    KEY = "goog:chromeOptions"
 
-    def __init__(self):
+    def __init__(self, is_legacy=True):
         super(Options, self).__init__()
-        self._page_load_strategy = "normal"
+        self._is_legacy = is_legacy
+
+        if is_legacy:
+            self._page_load_strategy = "normal"
 
     @property
     def page_load_strategy(self):
+        if not self._is_legacy:
+            raise AttributeError("Page Load Strategy only exists in Legacy Mode")
+
         return self._page_load_strategy
 
     @page_load_strategy.setter
     def page_load_strategy(self, value):
+        if not self._is_legacy:
+            raise AttributeError("Page Load Strategy only exists in Legacy Mode")
+
         if value not in ['normal', 'eager', 'none']:
             raise ValueError("Page Load Strategy should be 'normal', 'eager' or 'none'.")
         self._page_load_strategy = value
@@ -38,9 +48,11 @@
     def to_capabilities(self):
         """
         Creates a capabilities with all the options that have been set and
-
         :Returns: A dictionary with everything
         """
+        if not self._is_legacy:
+            return super(Options, self).to_capabilities(self.KEY)
+
         caps = self._caps
         caps['pageLoadStrategy'] = self._page_load_strategy
 
diff --git a/selenium/webdriver/edge/service.py b/selenium/webdriver/edge/service.py
index 4f2bf87..fbf2df9 100644
--- a/selenium/webdriver/edge/service.py
+++ b/selenium/webdriver/edge/service.py
@@ -15,43 +15,40 @@
 # specific language governing permissions and limitations
 # under the License.
 
-from selenium.webdriver.common import service
+from selenium.webdriver.chromium import service
 
 
-class Service(service.Service):
+class Service(service.ChromiumService):
 
-    def __init__(self, executable_path, port=0, verbose=False, log_path=None):
+    def __init__(self, executable_path, port=0, verbose=False, log_path=None, is_legacy=True, service_args=None, env=None):
         """
         Creates a new instance of the EdgeDriver service.
-
         EdgeDriver provides an interface for Microsoft WebDriver to use
         with Microsoft Edge.
-
-        :param executable_path: Path to the Microsoft WebDriver binary.
-        :param port: Run the remote service on a specified port.
-            Defaults to 0, which binds to a random open port of the
-            system's choosing.
-        :verbose: Whether to make the webdriver more verbose (passes the
-            --verbose option to the binary). Defaults to False.
-        :param log_path: Optional path for the webdriver binary to log to.
-            Defaults to None which disables logging.
-
+        :Args:
+         - executable_path : Path to the Microsoft WebDriver binary.
+         - port : Run the remote service on a specified port. Defaults to 0, which binds to a random open port
+           of the system's choosing.
+         - verbose : Whether to make the webdriver more verbose (passes the --verbose option to the binary).
+           Defaults to False. Should be only used for legacy mode.
+         - log_path : Optional path for the webdriver binary to log to. Defaults to None which disables logging.
+         - is_legacy : Whether to use MicrosoftWebDriver.exe (legacy) or MSEdgeDriver.exe (chromium-based). Defaults to True.
+         - service_args : List of args to pass to the WebDriver service.
         """
+        self.service_args = service_args or []
 
-        self.service_args = []
-        if verbose:
-            self.service_args.append("--verbose")
+        if is_legacy:
+            if verbose:
+                self.service_args.append("--verbose")
 
-        params = {
-            "executable": executable_path,
-            "port": port,
-            "start_error_message": "Please download from https://go.microsoft.com/fwlink/?LinkId=619687"
-        }
+            if log_path:
+                params["log_file"] = open(log_path, "a+")
 
-        if log_path:
-            params["log_file"] = open(log_path, "a+")
-
-        service.Service.__init__(self, **params)
-
-    def command_line_args(self):
-        return ["--port=%d" % self.port] + self.service_args
+        service.ChromiumService.__init__(
+            self,
+            executable_path,
+            port,
+            service_args,
+            log_path,
+            env,
+            "Please download from https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/")
diff --git a/selenium/webdriver/edge/webdriver.py b/selenium/webdriver/edge/webdriver.py
index d74241b..657f9ee 100644
--- a/selenium/webdriver/edge/webdriver.py
+++ b/selenium/webdriver/edge/webdriver.py
@@ -14,78 +14,49 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-import warnings
-
 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
 from selenium.webdriver.edge.service import Service
-from selenium.webdriver.remote.remote_connection import RemoteConnection
-from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
+from selenium.webdriver.chromium.webdriver import ChromiumDriver
+
 
 DEFAULT_PORT = 0
 DEFAULT_SERVICE_LOG_PATH = None
 
 
-class WebDriver(RemoteWebDriver):
+class WebDriver(ChromiumDriver):
 
     def __init__(self, executable_path='MicrosoftWebDriver.exe',
                  capabilities=None, port=DEFAULT_PORT, verbose=False,
                  service_log_path=None, log_path=DEFAULT_SERVICE_LOG_PATH,
-                 service=None, options=None, keep_alive=False):
+                 service=None, options=None, keep_alive=False, is_legacy=True,
+                 service_args=None):
         """
-        Creates a new instance of the chrome driver.
-
-        Starts the service and then creates new instance of chrome driver.
-
+        Creates a new instance of the edge driver.
+        Starts the service and then creates new instance of edge driver.
         :Args:
-         - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH
-         - capabilities - Dictionary object with non-browser specific
-           capabilities only, such as "proxy" or "loggingPref".
-         - port - port you would like the service to run, if left as 0, a free port will be found.
-         - verbose - whether to set verbose logging in the service
-         - service_log_path - Where to log information from the driver.
+         - executable_path - Deprecated: path to the executable. If the default is used it assumes the executable is in the $PATH
+         - capabilities - Dictionary object with non-browser specific capabilities only, such as "proxy" or "loggingPref".
+           Only available in Legacy mode
+         - port - Deprecated: port you would like the service to run, if left as 0, a free port will be found.
+         - verbose - whether to set verbose logging in the service. Only available in Legacy Mode
+         - service_log_path - Deprecated: Where to log information from the driver.
          - keep_alive - Whether to configure EdgeRemoteConnection to use HTTP keep-alive.
+         - service_args - Deprecated: List of args to pass to the driver service
+         - is_legacy: Whether to use MicrosoftWebDriver.exe (legacy) or MSEdgeDriver.exe (chromium-based). Defaults to True.
          """
-        if port != DEFAULT_PORT:
-            warnings.warn('port has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
-        self.port = port
+        if not is_legacy:
+            executable_path = "msedgedriver"
 
-        if service_log_path != DEFAULT_SERVICE_LOG_PATH:
-            warnings.warn('service_log_path has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
-        if capabilities is not None:
-            warnings.warn('capabilities has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
-        if service_log_path != DEFAULT_SERVICE_LOG_PATH:
-            warnings.warn('service_log_path has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
-        if verbose:
-            warnings.warn('verbose has been deprecated, please pass in a Service object',
-                          DeprecationWarning, stacklevel=2)
+        service = service or Service(executable_path,
+                                    port=port, verbose=verbose,
+                                    log_path=service_log_path, is_legacy=is_legacy)
 
-        if service:
-            self.service = service
-        else:
-            self.service = Service(executable_path, port=self.port, verbose=verbose,
-                                   log_path=service_log_path)
-        self.service.start()
-
-        if capabilities is None:
-            capabilities = DesiredCapabilities.EDGE
-
-        RemoteWebDriver.__init__(
-            self,
-            command_executor=RemoteConnection(self.service.service_url,
-                                              keep_alive=keep_alive),
-            desired_capabilities=capabilities)
-        self._is_remote = False
-
-    @property
-    def edge_service(self):
-        warnings.warn("'edge_service' has been deprecated, please use 'service'",
-                      DeprecationWarning, stacklevel=2)
-        return self.service
-
-    def quit(self):
-        RemoteWebDriver.quit(self)
-        self.service.stop()
+        super(WebDriver, self).__init__(
+            executable_path,
+            port,
+            options,
+            service_args,
+            DesiredCapabilities.EDGE,
+            service_log_path,
+            service,
+            keep_alive)
diff --git a/setup.py b/setup.py
index 12a3850..85ebdff 100755
--- a/setup.py
+++ b/setup.py
@@ -54,6 +54,7 @@
                  'selenium.common',
                  'selenium.webdriver',
                  'selenium.webdriver.android',
+                 'selenium.webdriver.chromium',
                  'selenium.webdriver.chrome',
                  'selenium.webdriver.common',
                  'selenium.webdriver.common.html5',
diff --git a/test/selenium/webdriver/common/alerts_tests.py b/test/selenium/webdriver/common/alerts_tests.py
index 7ed1a12..ca72ec7 100644
--- a/test/selenium/webdriver/common/alerts_tests.py
+++ b/test/selenium/webdriver/common/alerts_tests.py
@@ -142,6 +142,10 @@
     condition=sys.platform == 'darwin',
     reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=26',
     run=False)
[email protected]_chromiumedge(
+    condition=sys.platform == 'darwin',
+    reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=26',
+    run=False)
 def testAlertShouldNotAllowAdditionalCommandsIfDimissed(driver, pages):
     pages.load("alerts.html")
     driver.find_element(By.ID, "alert").click()
@@ -246,6 +250,7 @@
 
 @pytest.mark.xfail_firefox(reason='Non W3C conformant')
 @pytest.mark.xfail_chrome(reason='Non W3C conformant')
[email protected]_chromiumedge(reason='Non W3C conformant')
 def testShouldHandleAlertOnPageBeforeUnload(driver, pages):
     pages.load("pageWithOnBeforeUnloadMessage.html")
 
@@ -287,6 +292,8 @@
 
 @pytest.mark.xfail_chrome(
     reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=1537')
[email protected]_chromiumedge(
+    reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=1537')
 @pytest.mark.xfail_marionette(
     reason='https://bugzilla.mozilla.org/show_bug.cgi?id=1279211')
 @pytest.mark.xfail_remote(
diff --git a/test/selenium/webdriver/common/api_example_tests.py b/test/selenium/webdriver/common/api_example_tests.py
index 679c9be..7a45f4a 100644
--- a/test/selenium/webdriver/common/api_example_tests.py
+++ b/test/selenium/webdriver/common/api_example_tests.py
@@ -286,6 +286,7 @@
 
 
 @pytest.mark.xfail_chrome(raises=WebDriverException)
[email protected]_chromiumedge(raises=WebDriverException)
 @pytest.mark.xfail_marionette(raises=WebDriverException)
 def testGetLogTypes(driver, pages):
     pages.load("blank.html")
@@ -293,6 +294,7 @@
 
 
 @pytest.mark.xfail_chrome(raises=WebDriverException)
[email protected]_chromiumedge(raises=WebDriverException)
 @pytest.mark.xfail_marionette(raises=WebDriverException)
 def testGetLog(driver, pages):
     pages.load("blank.html")
diff --git a/test/selenium/webdriver/common/appcache_tests.py b/test/selenium/webdriver/common/appcache_tests.py
index 946a33b..33f147c 100644
--- a/test/selenium/webdriver/common/appcache_tests.py
+++ b/test/selenium/webdriver/common/appcache_tests.py
@@ -22,6 +22,7 @@
 
 
 @pytest.mark.xfail_chrome
[email protected]_chromiumedge
 @pytest.mark.xfail_marionette(raises=WebDriverException)
 @pytest.mark.xfail_remote
 def testWeCanGetTheStatusOfTheAppCache(driver, pages):
diff --git a/test/selenium/webdriver/common/click_scrolling_tests.py b/test/selenium/webdriver/common/click_scrolling_tests.py
index bc8adb8..f3744f0 100644
--- a/test/selenium/webdriver/common/click_scrolling_tests.py
+++ b/test/selenium/webdriver/common/click_scrolling_tests.py
@@ -87,6 +87,8 @@
 
 @pytest.mark.xfail_chrome(
     reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=1542')
[email protected]_chromiumedge(
+    reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=1542')
 @pytest.mark.xfail_marionette
 @pytest.mark.xfail_remote
 def testShouldNotScrollIfAlreadyScrolledAndElementIsInView(driver, pages):
diff --git a/test/selenium/webdriver/common/driver_element_finding_tests.py b/test/selenium/webdriver/common/driver_element_finding_tests.py
index a1402f9..7b02881 100755
--- a/test/selenium/webdriver/common/driver_element_finding_tests.py
+++ b/test/selenium/webdriver/common/driver_element_finding_tests.py
@@ -337,6 +337,7 @@
 
 
 @pytest.mark.xfail_chrome(raises=InvalidSelectorException)
[email protected]_chromiumedge(raises=InvalidSelectorException)
 @pytest.mark.xfail_firefox(raises=InvalidSelectorException)
 @pytest.mark.xfail_remote(raises=InvalidSelectorException)
 @pytest.mark.xfail_marionette(raises=WebDriverException)
diff --git a/test/selenium/webdriver/common/frame_switching_tests.py b/test/selenium/webdriver/common/frame_switching_tests.py
index 4c7c9a6..05e368f 100644
--- a/test/selenium/webdriver/common/frame_switching_tests.py
+++ b/test/selenium/webdriver/common/frame_switching_tests.py
@@ -367,6 +367,7 @@
 
 
 @pytest.mark.xfail_chrome(raises=NoSuchElementException)
[email protected]_chromiumedge(raises=NoSuchElementException)
 @pytest.mark.xfail_marionette(raises=WebDriverException,
                               reason='https://github.com/mozilla/geckodriver/issues/614')
 @pytest.mark.xfail_remote(raises=WebDriverException,
diff --git a/test/selenium/webdriver/common/page_load_timeout_tests.py b/test/selenium/webdriver/common/page_load_timeout_tests.py
index 19c83aa..b74d9fd 100644
--- a/test/selenium/webdriver/common/page_load_timeout_tests.py
+++ b/test/selenium/webdriver/common/page_load_timeout_tests.py
@@ -33,6 +33,7 @@
 
 
 @pytest.mark.xfail_chrome
[email protected]_chromiumedge
 def testClickShouldTimeout(driver, pages):
     pages.load("simpleTest.html")
     driver.set_page_load_timeout(0.01)
diff --git a/test/selenium/webdriver/common/page_loading_tests.py b/test/selenium/webdriver/common/page_loading_tests.py
index 2f19280..8cb321a 100644
--- a/test/selenium/webdriver/common/page_loading_tests.py
+++ b/test/selenium/webdriver/common/page_loading_tests.py
@@ -119,6 +119,7 @@
 @pytest.mark.xfail_marionette(run=False)
 @pytest.mark.xfail_remote(run=False)
 @pytest.mark.xfail_chrome(run=False)
[email protected]_chromiumedge(run=False)
 def testShouldNotHangifDocumentOpenCallIsNeverFollowedByDocumentCloseCall(driver, pages):
     pages.load("document_write_in_onload.html")
     driver.find_element(By.XPATH, "//body")
diff --git a/test/selenium/webdriver/common/position_and_size_tests.py b/test/selenium/webdriver/common/position_and_size_tests.py
index 0725241..2285880 100644
--- a/test/selenium/webdriver/common/position_and_size_tests.py
+++ b/test/selenium/webdriver/common/position_and_size_tests.py
@@ -57,6 +57,7 @@
 
 
 @pytest.mark.xfail_chrome
[email protected]_chromiumedge
 @pytest.mark.xfail_marionette
 @pytest.mark.xfail_remote
 def testShouldGetCoordinatesOfAnElementInAFrame(driver, pages):
@@ -68,6 +69,7 @@
 
 
 @pytest.mark.xfail_chrome
[email protected]_chromiumedge
 @pytest.mark.xfail_marionette
 @pytest.mark.xfail_remote
 def testShouldGetCoordinatesOfAnElementInANestedFrame(driver, pages):
diff --git a/test/selenium/webdriver/common/select_class_tests.py b/test/selenium/webdriver/common/select_class_tests.py
index 99fbca3..a38293b 100644
--- a/test/selenium/webdriver/common/select_class_tests.py
+++ b/test/selenium/webdriver/common/select_class_tests.py
@@ -43,6 +43,7 @@
 
 
 @pytest.mark.xfail_chrome
[email protected]_chromiumedge
 @pytest.mark.xfail_firefox
 @pytest.mark.xfail_remote
 @pytest.mark.xfail_marionette(reason='https://bugzilla.mozilla.org/show_bug.cgi?id=1429403')
@@ -69,6 +70,7 @@
 
 
 @pytest.mark.xfail_chrome
[email protected]_chromiumedge
 @pytest.mark.xfail_firefox
 @pytest.mark.xfail_remote
 @pytest.mark.xfail_marionette(reason='https://bugzilla.mozilla.org/show_bug.cgi?id=1429403')
@@ -97,6 +99,8 @@
 
 @pytest.mark.xfail_chrome(
     reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=822')
[email protected]_chromiumedge(
+    reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=822')
 def testSelectByVisibleTextShouldNormalizeSpaces(driver, pages):
     pages.load("formPage.html")
 
@@ -109,6 +113,7 @@
 
 
 @pytest.mark.xfail_chrome
[email protected]_chromiumedge
 @pytest.mark.xfail_firefox
 @pytest.mark.xfail_remote
 @pytest.mark.xfail_marionette(reason='https://bugzilla.mozilla.org/show_bug.cgi?id=1429403')
diff --git a/test/selenium/webdriver/common/window_switching_tests.py b/test/selenium/webdriver/common/window_switching_tests.py
index 8ce87f6..52eff32 100644
--- a/test/selenium/webdriver/common/window_switching_tests.py
+++ b/test/selenium/webdriver/common/window_switching_tests.py
@@ -193,6 +193,7 @@
 
 @pytest.mark.xfail_ie
 @pytest.mark.xfail_chrome
[email protected]_chromiumedge
 def testShouldBeAbleToCreateANewWindow(driver, pages):
     original_handle = driver.current_window_handle
 
diff --git a/test/selenium/webdriver/common/window_tests.py b/test/selenium/webdriver/common/window_tests.py
index 11eeb2a..65afeee 100644
--- a/test/selenium/webdriver/common/window_tests.py
+++ b/test/selenium/webdriver/common/window_tests.py
@@ -23,6 +23,7 @@
 
 @pytest.mark.xfail_ie
 @pytest.mark.xfail_chrome(reason="Fails on Travis")
[email protected]_chromiumedge(reason="Fails on Travis")
 @pytest.mark.xfail_marionette(reason="Fails on Travis")
 @pytest.mark.xfail_firefox(reason="Fails on Travis")
 @pytest.mark.xfail_remote(reason="Fails on Travis")
@@ -119,6 +120,8 @@
 
 @pytest.mark.xfail_chrome(raises=WebDriverException,
                           reason='Fullscreen command not implemented')
[email protected]_chromiumedge(raises=WebDriverException,
+                          reason='Fullscreen command not implemented')
 @pytest.mark.xfail_firefox(raises=WebDriverException,
                            reason='Fullscreen command not implemented')
 @pytest.mark.xfail_safari(raises=WebDriverException,
@@ -145,6 +148,8 @@
 
 @pytest.mark.xfail_chrome(raises=WebDriverException,
                           reason='Minimize command not implemented')
[email protected]_chromiumedge(raises=WebDriverException,
+                          reason='Minimize command not implemented')
 @pytest.mark.xfail_firefox(raises=WebDriverException,
                            reason='Minimize command not implemented')
 @pytest.mark.xfail_safari(raises=WebDriverException,
diff --git a/tox.ini b/tox.ini
index 3d9b4c4..afa5d9f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,12 +12,13 @@
   py{27,36}-marionette: py.test --driver=Marionette {posargs}
   py{27,36}-remote: py.test --driver=Remote {posargs}
   py{27,36}-safari: py.test --driver=Safari {posargs}
+  py{27,36}-chromiumedge: py.test --driver=ChromiumEdge {posargs}
 install_command = pip install -v --no-index --find-links=../third_party/py {opts} {packages}
 deps =
   pytest==3.0.3
   pytest-instafail==0.3.0
   pytest-mock==1.5.0
-  py{27,33,34,35,36}-{unit,chrome,firefox,marionette}: pytest-xdist==1.15
+  py{27,33,34,35,36}-{unit,chrome,firefox,marionette,chromiumedge}: pytest-xdist==1.15
   urllib3==1.23
 
 [testenv:docs]