Repository: pytest-dev/pytest-selenium (language: python)

Issue:
desired capabilities causes error
              Hello, since yesterday (release 4.10.0 of Selenium, https://github.com/SeleniumHQ/selenium/releases/tag/selenium-4.10.0), this warning is no more a warning but is causing the code to fail:

```
Traceback (most recent call last):
  File "/opt/venv/lib/python3.9/site-packages/pytest_selenium/pytest_selenium.py", line 200, in driver
    for retry in Retrying(
  File "/opt/venv/lib/python3.9/site-packages/tenacity/__init__.py", line 347, in __iter__
    do = self.iter(retry_state=retry_state)
  File "/opt/venv/lib/python3.9/site-packages/tenacity/__init__.py", line 325, in iter
    raise retry_exc.reraise()
  File "/opt/venv/lib/python3.9/site-packages/tenacity/__init__.py", line 158, in reraise
    raise self.last_attempt.result()
  File "/usr/lib/python3.9/concurrent/futures/_base.py", line 433, in result
    return self.__get_result()
  File "/usr/lib/python3.9/concurrent/futures/_base.py", line 389, in __get_result
    raise self._exception
  File "/opt/venv/lib/python3.9/site-packages/pytest_selenium/pytest_selenium.py", line 207, in driver
    driver = driver_class(**driver_kwargs)
TypeError: __init__() got an unexpected keyword argument 'desired_capabilities'
```

Versions:
OS: Debian 11
Python: 3.9.2
pytest: 7.3.1
pytest-selenium: 4.0.1

Regards

_Originally posted by @sdiemer in https://github.com/pytest-dev/pytest-selenium/issues/283#issuecomment-1582137811_
            


Discussion / hints:
Just chiming in, I stumbled upon the same problem.

OS: Ubuntu 22.04 LTS
Python: 3.10.6
pytest: 7.0.1
pytest-selenium: 4.0.1
Hello , we are facing the same issue. 

Is there any update / fix for this?
Currently the only workaround is pinning selenium to a version where it works. 
Downgrading to selenium==4.9.0 worked for me. 
CVE `PYSEC-2023-101` now requires `selenium>=4.10` but `pytest-selenium` has breaking errors with it. :-(
> CVE `PYSEC-2023-101` now requires `selenium>=4.10` but `pytest-selenium` has breaking errors with it. :-(

Thanks, I'll try to prioritise this. Not sure exactly how much work is involved at this time tho.
Isn't this a duplicate of #283?
Any update on this ? 
> Any update on this ?

Yeah, as soon as I'm done releasing v4 of pytest-html, this is my next thing.

Fix patch:
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 146d794..c51a6e1 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -67,7 +67,7 @@ services:
   webserver:
     container_name: webserver
     build:
-      context: docker
+      context: .
     environment:
       - PYTHONDONTWRITEBYTECODE=1
     networks:
diff --git a/docs/news.rst b/docs/news.rst
index 16627c0..063c160 100644
--- a/docs/news.rst
+++ b/docs/news.rst
@@ -1,6 +1,19 @@
 Release Notes
 =============
 
+4.0.2 (2023-09-13)
+------------------
+
+* Support selenium 4.10-4.15
+
+  * Thanks to `@noamkush <https://github.com/noamkush>`_ for the PR.
+
+* Remove py dependency
+
+  * Thanks to `@treiher <https://github.com/treiher>`_ for reporting the issue.
+
+  * Thanks to `@sandre35 <https://github.com/sandre35>`_ for the PR.
+
 4.0.1 (2023-05-28)
 ------------------
 
diff --git a/pyproject.toml b/pyproject.toml
index 731bee0..99a87e2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,10 +46,10 @@ classifiers = [
 dependencies = [
   "pytest>=6.0.0",
   "pytest-base-url>=2.0.0",
-  "pytest-html>=2.0.0",
+  "pytest-html>=4.0.0",
   "pytest-variables>=2.0.0",
   "requests>=2.26.0",
-  "selenium>=4.0.0",
+  "selenium>=4.10.0",
   "tenacity>=6.0.0",
 ]
 dynamic = [


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 491d95b..ef4cc7b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -40,14 +40,14 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
         include:
           - os: ubuntu-latest
             python-version: pypy3.9
             tox-env: py3.9
 
           - os: ubuntu-latest
-            python-version: 3.11
+            python-version: 3.12
             tox-env: devel
 
     steps:
diff --git a/src/pytest_selenium/drivers/browserstack.py b/src/pytest_selenium/drivers/browserstack.py
index df03d0a..71f1a49 100644
--- a/src/pytest_selenium/drivers/browserstack.py
+++ b/src/pytest_selenium/drivers/browserstack.py
@@ -3,6 +3,7 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import pytest
+from selenium.webdriver.common.options import ArgOptions
 
 from pytest_selenium.drivers.cloud import Provider
 from pytest_selenium.exceptions import MissingCloudSettingError
@@ -93,23 +94,22 @@ def pytest_selenium_runtest_makereport(item, report, summary, extra):
         summary.append("WARNING: Failed to update job status: {0}".format(e))
 
 
-def driver_kwargs(request, test, capabilities, **kwargs):
+def driver_kwargs(test, capabilities, **kwargs):
     provider = BrowserStack()
     assert provider.job_access
-    if (
-        "bstack:options" in capabilities
-        and type(capabilities["bstack:options"]) is dict
-    ):
-        capabilities["bstack:options"].setdefault("sessionName", test)
-        capabilities["bstack:options"].setdefault("userName", provider.username)
-        capabilities["bstack:options"].setdefault("accessKey", provider.key)
+    options = ArgOptions()
+    bstack_options = capabilities.pop("bstack:options", None)
+    if isinstance(bstack_options, dict):
+        bstack_options.setdefault("sessionName", test)
+        bstack_options.setdefault("userName", provider.username)
+        bstack_options.setdefault("accessKey", provider.key)
+        options.set_capability("bstack:options", bstack_options)
     else:
-        capabilities.setdefault("name", test)
-        capabilities.setdefault("browserstack.user", provider.username)
-        capabilities.setdefault("browserstack.key", provider.key)
-    kwargs = {
+        options.set_capability("name", test)
+        options.set_capability("browserstack.user", provider.username)
+        options.set_capability("browserstack.key", provider.key)
+    return {
         "command_executor": provider.executor,
-        "desired_capabilities": capabilities,
         "keep_alive": True,
+        "options": options,
     }
-    return kwargs
diff --git a/src/pytest_selenium/drivers/chrome.py b/src/pytest_selenium/drivers/chrome.py
index e198ab5..32531fd 100644
--- a/src/pytest_selenium/drivers/chrome.py
+++ b/src/pytest_selenium/drivers/chrome.py
@@ -3,24 +3,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 import pytest
 from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.chrome.service import Service
 
 
-def driver_kwargs(
-    capabilities, driver_args, driver_log, driver_path, chrome_options, **kwargs
-):
-    kwargs = {
-        "desired_capabilities": capabilities,
-        "service_log_path": driver_log,
-        "options": chrome_options,
-    }
-
-    if driver_args is not None:
-        kwargs["service_args"] = driver_args
-    if driver_path is not None:
-        kwargs["executable_path"] = driver_path
-    return kwargs
+def driver_kwargs(chrome_options, chrome_service, **kwargs):
+    return {"options": chrome_options, "service": chrome_service}
 
 
 @pytest.fixture
 def chrome_options():
     return Options()
+
+
+@pytest.fixture
+def chrome_service(driver_path, driver_args, driver_log):
+    return Service(
+        executable_path=driver_path, service_args=driver_args, log_output=driver_log
+    )
diff --git a/src/pytest_selenium/drivers/crossbrowsertesting.py b/src/pytest_selenium/drivers/crossbrowsertesting.py
index 9c9683c..7cbffc5 100644
--- a/src/pytest_selenium/drivers/crossbrowsertesting.py
+++ b/src/pytest_selenium/drivers/crossbrowsertesting.py
@@ -2,7 +2,7 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-from py.xml import html
+import html
 import pytest
 
 from pytest_selenium.drivers.cloud import Provider
@@ -113,13 +113,9 @@ def driver_kwargs(request, test, capabilities, **kwargs):
 
 
 def _video_html(video):
-    html.__tagspec__.update(dict([(x, 1) for x in ("video", "source")]))
-    video_attrs = {
-        "controls": "",
-        "poster": video.get("image"),
-        "play-pause-on-click": "",
-        "style": "border:1px solid #e6e6e6; float:right; height:240px; "
-        "margin-left:5px; overflow:hidden; width:320px",
-    }
-    source_attrs = {"src": video.get("video"), "type": "video/mp4"}
-    return str(html.video(html.source(**source_attrs), **video_attrs))
+    return (
+        f'<video controls="" play-pause-on-click="" poster="{html.escape(video.get("image"))}"'
+        'style="border:1px solid #e6e6e6; float:right; height:240px; margin-left:5px; overflow:hidden; width:320px">'
+        f'<source src="{html.escape(video.get("video"))}" type="video/mp4"></source>'
+        "</video>"
+    )
diff --git a/src/pytest_selenium/drivers/edge.py b/src/pytest_selenium/drivers/edge.py
index 78a2b9f..53f3227 100644
--- a/src/pytest_selenium/drivers/edge.py
+++ b/src/pytest_selenium/drivers/edge.py
@@ -3,23 +3,20 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 import pytest
 from selenium.webdriver.edge.options import Options
+from selenium.webdriver.edge.service import Service
 
 
-def driver_kwargs(capabilities, driver_log, driver_path, edge_options, **kwargs):
-
-    kwargs = {
-        "service_log_path": driver_log,
-        "options": edge_options,
-    }
-
-    if capabilities:
-        kwargs["capabilities"] = capabilities
-    if driver_path is not None:
-        kwargs["executable_path"] = driver_path
-
-    return kwargs
+def driver_kwargs(edge_options, edge_service, **kwargs):
+    return {"options": edge_options, "service": edge_service}
 
 
 @pytest.fixture
 def edge_options():
     return Options()
+
+
+@pytest.fixture
+def edge_service(driver_path, driver_args, driver_log):
+    return Service(
+        executable_path=driver_path, service_args=driver_args, log_output=driver_log
+    )
diff --git a/src/pytest_selenium/drivers/firefox.py b/src/pytest_selenium/drivers/firefox.py
index 4a9a2c1..d47f699 100644
--- a/src/pytest_selenium/drivers/firefox.py
+++ b/src/pytest_selenium/drivers/firefox.py
@@ -6,6 +6,7 @@
 import pytest
 
 from selenium.webdriver.firefox.options import Options
+from selenium.webdriver.firefox.service import Service
 
 LOGGER = logging.getLogger(__name__)
 
@@ -26,17 +27,8 @@ def pytest_configure(config):
     )
 
 
-def driver_kwargs(capabilities, driver_log, driver_path, firefox_options, **kwargs):
-    kwargs = {"service_log_path": driver_log}
-
-    if capabilities:
-        kwargs["capabilities"] = capabilities
-    if driver_path is not None:
-        kwargs["executable_path"] = driver_path
-
-    kwargs["options"] = firefox_options
-
-    return kwargs
+def driver_kwargs(firefox_options, firefox_service, **kwargs):
+    return {"options": firefox_options, "service": firefox_service}
 
 
 @pytest.fixture
@@ -52,6 +44,13 @@ def firefox_options(request):
     return options
 
 
+@pytest.fixture
+def firefox_service(driver_path, driver_args, driver_log):
+    return Service(
+        executable_path=driver_path, service_args=driver_args, log_output=driver_log
+    )
+
+
 def get_arguments_from_markers(node):
     arguments = []
     for m in node.iter_markers("firefox_arguments"):
diff --git a/src/pytest_selenium/drivers/internet_explorer.py b/src/pytest_selenium/drivers/internet_explorer.py
index a3da787..7490c26 100644
--- a/src/pytest_selenium/drivers/internet_explorer.py
+++ b/src/pytest_selenium/drivers/internet_explorer.py
@@ -1,14 +1,22 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import pytest
+from selenium.webdriver.ie.options import Options
+from selenium.webdriver.ie.service import Service
 
 
-def driver_kwargs(capabilities, driver_log, driver_path, **kwargs):
+def driver_kwargs(ie_options, ie_service, **kwargs):
+    return {"options": ie_options, "service": ie_service}
 
-    kwargs = {"service_log_path": driver_log}
 
-    if capabilities:
-        kwargs["capabilities"] = capabilities
-    if driver_path is not None:
-        kwargs["executable_path"] = driver_path
-    return kwargs
+@pytest.fixture
+def ie_options():
+    return Options()
+
+
+@pytest.fixture
+def ie_service(driver_path, driver_args, driver_log):
+    return Service(
+        executable_path=driver_path, service_args=driver_args, log_output=driver_log
+    )
diff --git a/src/pytest_selenium/drivers/remote.py b/src/pytest_selenium/drivers/remote.py
index c597ded..99cdaf9 100644
--- a/src/pytest_selenium/drivers/remote.py
+++ b/src/pytest_selenium/drivers/remote.py
@@ -4,16 +4,28 @@
 
 import os
 
+import pytest
+
 HOST = os.environ.get("SELENIUM_HOST", "localhost")
 PORT = os.environ.get("SELENIUM_PORT", 4444)
 
 
-def driver_kwargs(capabilities, host, port, **kwargs):
+def driver_kwargs(remote_options, host, port, **kwargs):
     host = host if host.startswith("http") else f"http://{host}"
     executor = f"{host}:{port}/wd/hub"
 
-    kwargs = {
+    return {
         "command_executor": executor,
-        "desired_capabilities": capabilities,
+        "options": remote_options,
     }
-    return kwargs
+
+
+@pytest.fixture
+def remote_options(chrome_options, firefox_options, edge_options, capabilities):
+    browser = capabilities.get("browserName", "").upper()
+    if browser == "CHROME":
+        return chrome_options
+    elif browser == "FIREFOX":
+        return firefox_options
+    elif browser == "EDGE":
+        return edge_options
diff --git a/src/pytest_selenium/drivers/safari.py b/src/pytest_selenium/drivers/safari.py
index 2b96653..3f6e1e6 100644
--- a/src/pytest_selenium/drivers/safari.py
+++ b/src/pytest_selenium/drivers/safari.py
@@ -1,10 +1,25 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import pytest
+from selenium.webdriver.safari.options import Options
+from selenium.webdriver.safari.service import Service
 
 
-def driver_kwargs(capabilities, driver_path, **kwargs):
-    kwargs = {"desired_capabilities": capabilities}
-    if driver_path is not None:
-        kwargs["executable_path"] = driver_path
-    return kwargs
+def driver_kwargs(safari_options, safari_service, **kwargs):
+    return {
+        "options": safari_options,
+        "service": safari_service,
+    }
+
+
+@pytest.fixture
+def safari_options():
+    return Options()
+
+
+@pytest.fixture
+def safari_service(driver_path, driver_args, driver_log):
+    return Service(
+        executable_path=driver_path, service_args=driver_args, log_output=driver_log
+    )
diff --git a/src/pytest_selenium/drivers/saucelabs.py b/src/pytest_selenium/drivers/saucelabs.py
index dff8abe..a8f375d 100644
--- a/src/pytest_selenium/drivers/saucelabs.py
+++ b/src/pytest_selenium/drivers/saucelabs.py
@@ -5,8 +5,8 @@
 import os
 import json
 
-from py.xml import html
 import pytest
+from selenium.webdriver.common.options import ArgOptions
 
 from pytest_selenium.drivers.cloud import Provider
 from pytest_selenium.exceptions import MissingCloudSettingError
@@ -115,11 +115,10 @@ def driver_kwargs(request, test, capabilities, **kwargs):
     if tags:
         _capabilities["tags"] = tags
 
-    kwargs = {
+    return {
         "command_executor": provider.executor,
-        "desired_capabilities": capabilities,
+        "options": ArgOptions(),
     }
-    return kwargs
 
 
 def _video_html(session):
@@ -151,26 +150,18 @@ def _video_html(session):
         session=session
     )
 
-    return str(
-        html.div(
-            html.object(
-                html.param(value="true", name="allowfullscreen"),
-                html.param(value="always", name="allowscriptaccess"),
-                html.param(value="high", name="quality"),
-                html.param(value="#000000", name="bgcolor"),
-                html.param(value=flash_vars.replace(" ", ""), name="flashvars"),
-                width="100%",
-                height="100%",
-                type="application/x-shockwave-flash",
-                data="https://cdn1.saucelabs.com/sauce_skin_deprecated/lib/"
-                "flowplayer/flowplayer-3.2.17.swf",
-                name="player_api",
-                id="player_api",
-            ),
-            id="player{session}".format(session=session),
-            style="border:1px solid #e6e6e6; float:right; height:240px;"
-            "margin-left:5px; overflow:hidden; width:320px",
-        )
+    return (
+        f'<div id="player{session}" style="border:1px solid #e6e6e6; float:right; height:240px; margin-left:5px;'
+        'overflow:hidden; width:320px">'
+        '<object data="https://cdn1.saucelabs.com/sauce_skin_deprecated/lib/flowplayer/flowplayer-3.2.17.swf"'
+        'height="100%" id="player_api" name="player_api" type="application/x-shockwave-flash" width="100%">'
+        '<param name="allowfullscreen" value="true"/>'
+        '<param name="allowscriptaccess" value="always"/>'
+        '<param name="quality" value="high"/>'
+        '<param name="bgcolor" value="#000000"/>'
+        f'<param name="flashvars" value="{flash_vars.replace(" ", "")}"/>'
+        "</object>"
+        "</div>"
     )
 
 
diff --git a/src/pytest_selenium/drivers/testingbot.py b/src/pytest_selenium/drivers/testingbot.py
index b9fa89f..832506a 100644
--- a/src/pytest_selenium/drivers/testingbot.py
+++ b/src/pytest_selenium/drivers/testingbot.py
@@ -5,7 +5,7 @@
 from hashlib import md5
 
 import pytest
-from py.xml import html
+from selenium.webdriver.common.options import ArgOptions
 
 from pytest_selenium.drivers.cloud import Provider
 
@@ -92,33 +92,28 @@ def pytest_selenium_runtest_makereport(item, report, summary, extra):
 def driver_kwargs(request, test, capabilities, host, port, **kwargs):
     provider = TestingBot(host, port)
 
-    capabilities.setdefault("name", test)
-    capabilities.setdefault("client_key", provider.key)
-    capabilities.setdefault("client_secret", provider.secret)
+    options = ArgOptions()
+    options.set_capability("name", test)
+    options.set_capability("client_key", provider.key)
+    options.set_capability("client_secret", provider.secret)
     markers = [x.name for x in request.node.iter_markers()]
-    groups = capabilities.get("groups", []) + markers
+    groups = capabilities.pop("groups", []) + markers
     if groups:
-        capabilities["groups"] = groups
-    kwargs = {
+        options.set_capability("groups", groups)
+    return {
         "command_executor": provider.executor,
-        "desired_capabilities": capabilities,
+        "options": options,
     }
-    return kwargs
 
 
 def _video_html(video_url, session):
-    return str(
-        html.div(
-            html.video(
-                html.source(src=video_url, type="video/mp4"),
-                width="100%",
-                height="100%",
-                controls="controls",
-            ),
-            id="mediaplayer{session}".format(session=session),
-            style="border:1px solid #e6e6e6; float:right; height:240px;"
-            "margin-left:5px; overflow:hidden; width:320px",
-        )
+    return (
+        f'<div id="mediaplayer{session}" style="border:1px solid #e6e6e6; float:right;'
+        'height:240px; margin-left:5px; overflow:hidden; width:320px">'
+        '<video controls="controls" height="100%" width="100%">'
+        f'<source src="{video_url}" type="video/mp4"></source>'
+        "</video>"
+        "</div>"
     )
 
 
diff --git a/src/pytest_selenium/pytest_selenium.py b/src/pytest_selenium/pytest_selenium.py
index b013dc5..e83e5c1 100644
--- a/src/pytest_selenium/pytest_selenium.py
+++ b/src/pytest_selenium/pytest_selenium.py
@@ -18,7 +18,6 @@
 from .utils import CaseInsensitiveDict
 from . import drivers
 
-
 LOGGER = logging.getLogger(__name__)
 
 SUPPORTED_DRIVERS = CaseInsensitiveDict(
@@ -44,29 +43,6 @@
     pass  # Appium is optional.
 
 
-def _merge(a, b):
-    """merges b and a configurations.
-    Based on http://bit.ly/2uFUHgb
-    """
-    for key in b:
-        if key in a:
-            if isinstance(a[key], dict) and isinstance(b[key], dict):
-                _merge(a[key], b[key], [] + [str(key)])
-            elif a[key] == b[key]:
-                pass  # same leaf value
-            elif isinstance(a[key], list):
-                if isinstance(b[key], list):
-                    a[key].extend(b[key])
-                else:
-                    a[key].append(b[key])
-            else:
-                # b wins
-                a[key] = b[key]
-        else:
-            a[key] = b[key]
-    return a
-
-
 def pytest_addhooks(pluginmanager):
     from . import hooks
 
@@ -92,29 +68,10 @@ def session_capabilities(pytestconfig):
 def capabilities(
     request,
     driver_class,
-    chrome_options,
-    firefox_options,
-    edge_options,
     session_capabilities,
 ):
     """Returns combined capabilities"""
     capabilities = copy.deepcopy(session_capabilities)  # make a copy
-    if driver_class == webdriver.Remote:
-        browser = capabilities.get("browserName", "").upper()
-        key, options = (None, None)
-        if browser == "CHROME":
-            key = getattr(chrome_options, "KEY", "goog:chromeOptions")
-            options = chrome_options.to_capabilities()
-            if key not in options:
-                key = "chromeOptions"
-        elif browser == "FIREFOX":
-            key = firefox_options.KEY
-            options = firefox_options.to_capabilities()
-        elif browser == "EDGE":
-            key = getattr(edge_options, "KEY", None)
-            options = edge_options.to_capabilities()
-        if all([key, options]):
-            capabilities[key] = _merge(capabilities.get(key, {}), options.get(key, {}))
     capabilities.update(get_capabilities_from_markers(request.node))
     return capabilities
 
@@ -142,12 +99,20 @@ def driver_kwargs(
     request,
     capabilities,
     chrome_options,
+    chrome_service,
     driver_args,
     driver_class,
     driver_log,
     driver_path,
     firefox_options,
+    firefox_service,
+    ie_options,
+    ie_service,
     edge_options,
+    edge_service,
+    safari_options,
+    safari_service,
+    remote_options,
     pytestconfig,
 ):
     kwargs = {}
@@ -156,11 +121,19 @@ def driver_kwargs(
         driver.driver_kwargs(
             capabilities=capabilities,
             chrome_options=chrome_options,
+            chrome_service=chrome_service,
             driver_args=driver_args,
             driver_log=driver_log,
             driver_path=driver_path,
             firefox_options=firefox_options,
+            firefox_service=firefox_service,
+            ie_options=ie_options,
+            ie_service=ie_service,
             edge_options=edge_options,
+            edge_service=edge_service,
+            safari_options=safari_options,
+            safari_service=safari_service,
+            remote_options=remote_options,
             host=pytestconfig.getoption("selenium_host"),
             port=pytestconfig.getoption("selenium_port"),
             service_log_path=None,
@@ -169,6 +142,9 @@ def driver_kwargs(
         )
     )
 
+    for name, value in capabilities.items():
+        kwargs["options"].set_capability(name, value)
+
     pytestconfig._driver_log = driver_log
     return kwargs
 
@@ -302,7 +278,7 @@ def pytest_runtest_makereport(item, call):
         )
     if summary:
         report.sections.append(("pytest-selenium", "\n".join(summary)))
-    report.extra = extra
+    report.extras = extra
 
 
 def _gather_url(item, report, driver, summary, extra):
diff --git a/testing/test_browserstack.py b/testing/test_browserstack.py
index 68b26a7..eac7cfc 100644
--- a/testing/test_browserstack.py
+++ b/testing/test_browserstack.py
@@ -112,9 +112,9 @@ def test_default_caps_in_jsonwp(monkeypatch, testdir):
         import pytest
         @pytest.mark.nondestructive
         def test_bstack_capabilities(driver_kwargs):
-            assert driver_kwargs['desired_capabilities']['browserstack.user'] == 'foo'
-            assert driver_kwargs['desired_capabilities']['browserstack.key'] == 'bar'
-            assert driver_kwargs['desired_capabilities']['name'] == '{0}'
+            assert driver_kwargs['options'].capabilities['browserstack.user'] == 'foo'
+            assert driver_kwargs['options'].capabilities['browserstack.key'] == 'bar'
+            assert driver_kwargs['options'].capabilities['name'] == '{0}'
     """.format(
             test_name
         )
@@ -136,9 +136,9 @@ def test_default_caps_in_jsonwp_with_conflict(monkeypatch, testdir):
         import pytest
         @pytest.mark.nondestructive
         def test_bstack_capabilities(driver_kwargs):
-            assert driver_kwargs['desired_capabilities']['browserstack.user'] == 'foo'
-            assert driver_kwargs['desired_capabilities']['browserstack.key'] == 'bar'
-            assert driver_kwargs['desired_capabilities']['name'] == 'conflicting_name'
+            assert driver_kwargs['options'].capabilities['browserstack.user'] == 'foo'
+            assert driver_kwargs['options'].capabilities['browserstack.key'] == 'bar'
+            assert driver_kwargs['options'].capabilities['name'] == 'conflicting_name'
     """
     )
     testdir.quick_qa(
@@ -158,7 +158,7 @@ def test_default_caps_in_W3C(monkeypatch, testdir):
         import pytest
         @pytest.mark.nondestructive
         def test_bstack_capabilities(driver_kwargs):
-            assert driver_kwargs['desired_capabilities']['bstack:options'] == {
+            assert driver_kwargs['options'].capabilities['bstack:options'] == {
                 'userName': 'foo',
                 'accessKey': 'bar',
                 'sessionName': 'test_default_caps_in_W3C.test_bstack_capabilities'
@@ -185,7 +185,7 @@ def test_default_caps_in_W3C_with_conflict(monkeypatch, testdir):
         import pytest
         @pytest.mark.nondestructive
         def test_bstack_capabilities(driver_kwargs):
-            assert driver_kwargs['desired_capabilities']['bstack:options'] == {
+            assert driver_kwargs['options'].capabilities['bstack:options'] == {
                 'userName': 'foo',
                 'accessKey': 'bar',
                 'sessionName': 'conflicting_name'
diff --git a/testing/test_driver_log.py b/testing/test_driver_log.py
index 883a2c5..0daff82 100644
--- a/testing/test_driver_log.py
+++ b/testing/test_driver_log.py
@@ -9,7 +9,7 @@
 
 pytestmark = pytest.mark.nondestructive
 
-LOG_REGEX = '<a class="text" href=".*" target="_blank">Driver Log</a>'
+LOG_REGEX = re.compile('<a class="text" href=".*" target="_blank">Driver Log</a>')
 
 
 @pytest.mark.xfail(reason="Remote driver currently doesn't support logs")
@@ -27,7 +27,7 @@ def test_driver_log(webtext):
     with open(str(path)) as f:
         html = f.read()
 
-    assert re.search(LOG_REGEX, html) is not None
+    assert LOG_REGEX.search(html) is not None
     log_path = testdir.tmpdir.dirpath("basetemp", "test_driver_log0", "driver.log")
     assert os.path.exists(str(log_path))
 
@@ -67,6 +67,6 @@ def test_no_driver_log(webtext):
     testdir.runpytestqa("--html", path)
     with open(str(path)) as f:
         html = f.read()
-    assert re.search(LOG_REGEX, html) is None
+    assert LOG_REGEX.search(html) is None
     log_path = testdir.tmpdir.dirpath("basetemp", "test_no_driver_log0", "driver.log")
     assert not os.path.exists(str(log_path))
diff --git a/testing/test_firefox.py b/testing/test_firefox.py
index d11b681..0dd2d49 100644
--- a/testing/test_firefox.py
+++ b/testing/test_firefox.py
@@ -78,7 +78,16 @@ def test_extension(testdir):
         import pytest
         from selenium.webdriver.common.by import By
         from selenium.common.exceptions import StaleElementReferenceException
+        from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
         from selenium.webdriver.support.ui import WebDriverWait
+
+        @pytest.fixture
+        def firefox_options(firefox_options):
+            profile = FirefoxProfile()
+            profile.add_extension('{0}')
+            firefox_options.profile = profile
+            return firefox_options
+
         @pytest.mark.nondestructive
         def test_extension(selenium):
             selenium.get('about:support')
@@ -88,9 +97,11 @@ def test_extension(selenium):
                     lambda s: s.find_element(By.ID,
                         'extensions-tbody').text)
             assert 'Test Extension (empty)' in extensions
-    """
+    """.format(
+            extension
+        )
     )
-    testdir.quick_qa("--firefox-extension", extension, file_test, passed=1)
+    testdir.quick_qa(file_test, passed=1)
 
 
 def test_preferences_marker(testdir):
diff --git a/testing/test_report.py b/testing/test_report.py
index 593b970..e5f19ef 100644
--- a/testing/test_report.py
+++ b/testing/test_report.py
@@ -9,7 +9,12 @@
 import pytest
 
 
-pytestmark = pytest.mark.nondestructive
+pytestmark = [
+    pytest.mark.nondestructive,
+    pytest.mark.skip(
+        reason="Grid doesn't support logging, see https://github.com/SeleniumHQ/selenium/issues/10949"
+    ),
+]
 
 URL_LINK = '<a class="url" href="{0}/" target="_blank">URL</a>'
 
diff --git a/testing/test_saucelabs.py b/testing/test_saucelabs.py
index d5163a9..b0cee0a 100644
--- a/testing/test_saucelabs.py
+++ b/testing/test_saucelabs.py
@@ -92,8 +92,8 @@ def test_credentials_in_capabilities(monkeypatch, testdir):
         import pytest
         @pytest.mark.nondestructive
         def test_sauce_capabilities(driver_kwargs):
-            assert driver_kwargs['desired_capabilities']['username'] == 'foo'
-            assert driver_kwargs['desired_capabilities']['accessKey'] == 'bar'
+            assert driver_kwargs['options'].capabilities['username'] == 'foo'
+            assert driver_kwargs['options'].capabilities['accessKey'] == 'bar'
     """
     )
 
@@ -107,7 +107,7 @@ def test_no_sauce_options(monkeypatch, testdir):
         @pytest.mark.nondestructive
         def test_sauce_capabilities(driver_kwargs):
             try:
-                driver_kwargs['desired_capabilities']['sauce:options']
+                driver_kwargs['options'].capabilities['sauce:options']
                 raise AssertionError('<sauce:options> should not be present!')
             except KeyError:
                 pass
@@ -173,7 +173,7 @@ def run_w3c_sauce_test(capabilities, expected_result, monkeypatch, testdir):
         import pytest
         @pytest.mark.nondestructive
         def test_sauce_capabilities(driver_kwargs):
-            actual = driver_kwargs['desired_capabilities']['sauce:options']
+            actual = driver_kwargs['options'].capabilities['sauce:options']
             assert actual == {}
     """.format(
             expected_result