Repository: SeleniumHQ/selenium
Pull request #15657: [py] Re-enable remote tests in bazel and fix broken tests
Author: login: cgoldberg
Labels: name: C-py
description: Python Bindings
name: B-build
description: Includes scripting, bazel and CI integrations
name: Review effort 3/5
Created: 2025-04-22T20:32:33Z | Merged: 2025-04-24T22:06:24Z

### **User description**
### 🔗 Related Issues

### 💥 What does this PR do?

This PR fixes several issue with Python Remote WebDriver tests:
- Fixes bazel `//py:test-remote` target so it picks up tests in `py/test/selenium/webdriver/remote/`. Previously, it was skipping them.
- Moved `pytest_generate_tests` to main `conftest.py`, allowing all tests to access `--driver` flag while removing duplicate `conftest.py` files
- Update the `driver` fixture so it skips Remote WebDriver tests when called with a local driver
- Added new `firefox_options` fixture that returns an instance of `FirefoxOptions` with headless enabled/disabled depending on whether `--headless` arg was given. This is useful in tests that don't use the main `driver` fixture to launch the browser
- Some cleanup in the main `conftest.py`

### 🔄 Types of changes
- Cleanup (formatting, renaming)
- Bug fix (backwards compatible)


___

### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Centralize and fix driver parametrization for all tests
  - Move `pytest_generate_tests` to main `conftest.py`
  - Remove duplicate `conftest.py` files in subdirectories

- Fix and re-enable remote WebDriver tests in Bazel
  - Update Bazel config to include remote test directory
  - Adjust test skipping logic for remote/local driver distinction

- Add new `headless` fixture for consistent headless option access

- Refactor and clean up remote and Firefox test implementations


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>4 files</summary><table>
<tr>
  <td><strong>conftest.py</strong><dd><code>Centralize driver parametrization, add headless fixture, improve </code><br><code>skipping logic</code></dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-98e9e290ede5d0c7ac9e7df5b49edf63f5df5fcc946b7736e4a6139e4cda26e3">+22/-4</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>firefox_sizing_tests.py</strong><dd><code>Use new headless fixture and context manager for driver</code>&nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-d7416c3031b4161bc84a2aacc443a205a9f9baec189c92fe1376c2d5a683cafd">+8/-15</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>custom_element_tests.py</strong><dd><code>Use fixture for custom element driver, cleanup test logic</code></dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-77a6cf2e863316b3f01fe6c7a26b561e7d04e75b22acf85b868d5b082a2f20ed">+14/-6</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>remote_firefox_profile_tests.py</strong><dd><code>Refactor to use headless fixture and context manager, remove custom </code><br><code>fixtures</code></dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-1939f53bc8bdebf02dbefc8c76c4ad18fb92f8e6d41720058151e9bd3757bde9">+13/-22</a>&nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Cleanup</strong></td><td><details><summary>4 files</summary><table>
<tr>
  <td><strong>conftest.py</strong><dd><code>Remove duplicate driver parametrization logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-4894ee5e83893b4b9e1f2c8014386be4ff9449d33dccf74a236c446df3a09a49">+0/-21</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>conftest.py</strong><dd><code>Remove duplicate driver parametrization logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-da4af917403b65e60b6bbb34c145ace1960b6ecd6fbb949fe9b0f68e1aee70dc">+0/-21</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>conftest.py</strong><dd><code>Remove duplicate driver parametrization logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-8dbcc2f287cbf9d209fae76269f39037d22ab61e2d1e68bd2c447c5d8d41035c">+0/-21</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td><strong>conftest.py</strong><dd><code>Remove duplicate driver parametrization logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-e77a168b349808082230e721835a828b7134c0646cdace84e15927b29f4be2d8">+0/-21</a>&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1 files</summary><table>
<tr>
  <td><strong>remote_connection_tests.py</strong><dd><code>Add docstring, clarify test intent for browser-specific method</code></dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-10c82760157398bc5a3ab5ba815d2eb37ed7763ce4999b93379552545d70131d">+2/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table>
<tr>
  <td><strong>remote_custom_locator_tests.py</strong><dd><code>Mark tests as skipped, add pytest import</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-bd136d071186f266ea0c86c8916238483adac322586a350745e7690f2365fe81">+5/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Formatting</strong></td><td><details><summary>1 files</summary><table>
<tr>
  <td><strong>remote_downloads_tests.py</strong><dd><code>Minor import reordering</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-076c0af1d631fae433f5422d16b9eccbcac9db99a387833e2662d9bbed250b3a">+1/-0</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Bug fix</strong></td><td><details><summary>1 files</summary><table>
<tr>
  <td><strong>remote_hub_connection.py</strong><dd><code>Add options to Remote, improve assertion for SSL error</code>&nbsp; &nbsp; &nbsp; </dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-f0e9d09f74160652aa9aa1086766ce6e300bd1d435a4c242239e9f01f015ae4c">+6/-2</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>1 files</summary><table>
<tr>
  <td><strong>BUILD.bazel</strong><dd><code>Update Bazel config to include remote tests, remove obsolete </code><br><code>conftest.py references</code></dd></td>
  <td><a href="https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-3e8ff3a52e8e6c0b195ea328e17c50ba6d90abca1ce1624110607da20691d7a9">+1/-2</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></details></td></tr></tr></tbody></table>

___

> <details> <summary>  Need help?</summary><li>Type <code>/help how to ...</code> in the comments thread for any questions about Qodo Merge usage.</li><li>Check out the <a href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a> for more information.</li></details>


Diff:
diff --git a/py/BUILD.bazel b/py/BUILD.bazel
index 7cddd0d8515b8..530fd4d8d19f7 100644
--- a/py/BUILD.bazel
+++ b/py/BUILD.bazel
@@ -321,7 +321,6 @@ py_library(
         "test/selenium/webdriver/__init__.py",
         "test/selenium/webdriver/chrome/__init__.py",
         "test/selenium/webdriver/common/__init__.py",
-        "test/selenium/webdriver/common/conftest.py",
         "test/selenium/webdriver/common/network.py",
         "test/selenium/webdriver/common/webserver.py",
         "test/selenium/webdriver/firefox/__init__.py",
@@ -330,7 +329,6 @@ py_library(
         "test/selenium/webdriver/marionette/conftest.py",
         "test/selenium/webdriver/safari/conftest.py",
         "test/selenium/webdriver/support/__init__.py",
-        "test/selenium/webdriver/support/conftest.py",
     ],
     data = [
         "pyproject.toml",
@@ -547,6 +545,7 @@ py_test_suite(
     srcs = glob(
         [
             "test/selenium/webdriver/common/**/*.py",
+            "test/selenium/webdriver/remote/**/*.py",
             "test/selenium/webdriver/support/**/*.py",
         ],
         exclude = BIDI_TESTS,
diff --git a/py/conftest.py b/py/conftest.py
index 71d7103bbb045..b076e18313e71 100644
--- a/py/conftest.py
+++ b/py/conftest.py
@@ -96,6 +96,11 @@ def pytest_ignore_collect(path, config):
     return len([d for d in _drivers if d.lower() in parts]) > 0
 
 
+def pytest_generate_tests(metafunc):
+    if "driver" in metafunc.fixturenames and metafunc.config.option.drivers:
+        metafunc.parametrize("driver", metafunc.config.option.drivers, indirect=True)
+
+
 def get_driver_class(driver_option):
     """Generate the driver class name from the lowercase driver option."""
     if driver_option == "webkitgtk":
@@ -113,9 +118,16 @@ def get_driver_class(driver_option):
 @pytest.fixture(scope="function")
 def driver(request):
     kwargs = {}
+    driver_option = getattr(request, "param", "Chrome")
+
     # browser can be changed with `--driver=firefox` as an argument or to addopts in pytest.ini
-    driver_class = get_driver_class(getattr(request, "param", "Chrome"))
-    # skip tests if not available on the platform
+    driver_class = get_driver_class(driver_option)
+
+    # skip tests in the 'remote' directory if run with a local driver
+    if request.node.path.parts[-2] == "remote" and driver_class != "Remote":
+        pytest.skip(f"Remote tests can't be run with driver '{driver_option}'")
+
+    # skip tests that can't run on certain platforms
     _platform = platform.system()
     if driver_class == "Safari" and _platform != "Darwin":
         pytest.skip("Safari tests can only run on an Apple OS")
@@ -123,10 +135,12 @@ def driver(request):
         pytest.skip("IE and EdgeHTML Tests can only run on Windows")
     if "WebKit" in driver_class and _platform == "Windows":
         pytest.skip("WebKit tests cannot be run on Windows")
+
     # skip tests for drivers that don't support BiDi when --bidi is enabled
     if request.config.option.bidi:
         if driver_class in ("Ie", "Safari", "WebKitGTK", "WPEWebKit"):
             pytest.skip(f"{driver_class} does not support BiDi")
+
     # conditionally mark tests as expected to fail based on driver
     marker = request.node.get_closest_marker(f"xfail_{driver_class.lower()}")
 
@@ -177,6 +191,7 @@ def fin():
             kwargs["options"] = options
 
         driver_instance = getattr(webdriver, driver_class)(**kwargs)
+
     yield driver_instance
     # Close the browser after BiDi tests. Those make event subscriptions
     # and doesn't seems to be stable enough, causing the flakiness of the
@@ -217,7 +232,6 @@ def get_options(driver_class, config):
     if headless:
         if not options:
             options = getattr(webdriver, f"{driver_class}Options")()
-
         if driver_class == "Chrome" or driver_class == "Edge":
             options.add_argument("--headless=new")
         if driver_class == "Firefox":
@@ -226,7 +240,6 @@ def get_options(driver_class, config):
     if bidi:
         if not options:
             options = getattr(webdriver, f"{driver_class}Options")()
-
         options.web_socket_url = True
         options.unhandled_prompt_behavior = "ignore"
 
@@ -382,3 +395,11 @@ def clean_driver(request):
 
     if request.node.get_closest_marker("no_driver_after_test"):
         driver_reference = None
+
+
+@pytest.fixture
+def firefox_options(request):
+    options = webdriver.FirefoxOptions()
+    if request.config.option.headless:
+        options.add_argument("-headless")
+    return options
diff --git a/py/test/selenium/webdriver/chrome/conftest.py b/py/test/selenium/webdriver/chrome/conftest.py
deleted file mode 100644
index 043b7df7e146f..0000000000000
--- a/py/test/selenium/webdriver/chrome/conftest.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# 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.
-
-
-def pytest_generate_tests(metafunc):
-    if "driver" in metafunc.fixturenames and metafunc.config.option.drivers:
-        metafunc.parametrize("driver", metafunc.config.option.drivers, indirect=True)
diff --git a/py/test/selenium/webdriver/common/conftest.py b/py/test/selenium/webdriver/common/conftest.py
deleted file mode 100644
index 043b7df7e146f..0000000000000
--- a/py/test/selenium/webdriver/common/conftest.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# 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.
-
-
-def pytest_generate_tests(metafunc):
-    if "driver" in metafunc.fixturenames and metafunc.config.option.drivers:
-        metafunc.parametrize("driver", metafunc.config.option.drivers, indirect=True)
diff --git a/py/test/selenium/webdriver/edge/conftest.py b/py/test/selenium/webdriver/edge/conftest.py
deleted file mode 100644
index 043b7df7e146f..0000000000000
--- a/py/test/selenium/webdriver/edge/conftest.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# 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.
-
-
-def pytest_generate_tests(metafunc):
-    if "driver" in metafunc.fixturenames and metafunc.config.option.drivers:
-        metafunc.parametrize("driver", metafunc.config.option.drivers, indirect=True)
diff --git a/py/test/selenium/webdriver/firefox/firefox_sizing_tests.py b/py/test/selenium/webdriver/firefox/firefox_sizing_tests.py
index fa417a3d782d7..ce157bd7edfb2 100644
--- a/py/test/selenium/webdriver/firefox/firefox_sizing_tests.py
+++ b/py/test/selenium/webdriver/firefox/firefox_sizing_tests.py
@@ -22,7 +22,6 @@
 import pytest
 
 from selenium import webdriver
-from selenium.webdriver.firefox.options import Options
 
 
 def is_running_wayland():
@@ -30,33 +29,20 @@ def is_running_wayland():
 
 
 @pytest.mark.skipif(not is_running_wayland(), reason="This test only runs on Linux under Wayland")
-def test_firefox_opens_large_when_running_xwayland(request):  # noqa: F821
-    options = Options()
-    if request.config.getoption("--headless"):
-        options.add_argument("-headless")
+def test_firefox_opens_large_when_running_xwayland(firefox_options):
     # setting environment variable `MOZ_ENABLE_WAYLAND=0` forces Firefox
     # to run under XWayland on Wayland based systems
     with patch.dict("os.environ", {"MOZ_ENABLE_WAYLAND": "0"}):
-        try:
-            driver = webdriver.Firefox(options=options)
+        with webdriver.Firefox(options=firefox_options) as driver:
             size = driver.get_window_size()
             assert size["height"] > 500
             assert size["width"] > 500
-        finally:
-            driver.quit()
 
 
 @pytest.mark.skipif(not is_running_wayland(), reason="This test only runs on Linux under Wayland")
 @pytest.mark.xfail(reason="https://bugzilla.mozilla.org/show_bug.cgi?id=1959040")
-# Firefox opens in a small window when running on Linux/Wayland
-def test_firefox_opens_large_when_running_wayland(request):  # noqa: F821
-    options = Options()
-    if request.config.getoption("--headless"):
-        options.add_argument("-headless")
-    try:
-        driver = webdriver.Firefox(options=options)
+def test_firefox_opens_large_when_running_wayland(firefox_options):
+    with webdriver.Firefox(options=firefox_options) as driver:
         size = driver.get_window_size()
         assert size["height"] > 500
         assert size["width"] > 500
-    finally:
-        driver.quit()
diff --git a/py/test/selenium/webdriver/remote/remote_connection_tests.py b/py/test/selenium/webdriver/remote/remote_connection_tests.py
index e4046aff719a4..438bc26cb0c42 100644
--- a/py/test/selenium/webdriver/remote/remote_connection_tests.py
+++ b/py/test/selenium/webdriver/remote/remote_connection_tests.py
@@ -14,12 +14,14 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+
 import base64
 
 import filetype
 
 
 def test_browser_specific_method(driver, pages):
+    """This only works on Firefox"""
     pages.load("simpleTest.html")
     screenshot = driver.execute("FULL_PAGE_SCREENSHOT")["value"]
     result = base64.b64decode(screenshot)
diff --git a/py/test/selenium/webdriver/remote/custom_element_tests.py b/py/test/selenium/webdriver/remote/remote_custom_element_tests.py
similarity index 78%
rename from py/test/selenium/webdriver/remote/custom_element_tests.py
rename to py/test/selenium/webdriver/remote/remote_custom_element_tests.py
index 3fccb52ad3119..12a82e16fd79c 100644
--- a/py/test/selenium/webdriver/remote/custom_element_tests.py
+++ b/py/test/selenium/webdriver/remote/remote_custom_element_tests.py
@@ -14,6 +14,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+
+import pytest
+
 from selenium.webdriver.common.by import By
 from selenium.webdriver.remote.webelement import WebElement
 
@@ -24,20 +27,27 @@ def custom_method(self):
         return "Custom element method"
 
 
-def test_find_element_with_custom_class(driver, pages):
+@pytest.fixture()
+def custom_element_driver(driver):
+    try:
+        driver._web_element_cls = MyCustomElement
+        yield driver
+    finally:
+        driver._web_element_cls = WebElement
+
+
+def test_find_element_with_custom_class(custom_element_driver, pages):
     """Test to ensure custom element class is used for a single element."""
-    driver._web_element_cls = MyCustomElement
     pages.load("simpleTest.html")
-    element = driver.find_element(By.TAG_NAME, "body")
+    element = custom_element_driver.find_element(By.TAG_NAME, "body")
     assert isinstance(element, MyCustomElement)
     assert element.custom_method() == "Custom element method"
 
 
-def test_find_elements_with_custom_class(driver, pages):
+def test_find_elements_with_custom_class(custom_element_driver, pages):
     """Test to ensure custom element class is used for multiple elements."""
-    driver._web_element_cls = MyCustomElement
     pages.load("simpleTest.html")
-    elements = driver.find_elements(By.TAG_NAME, "div")
+    elements = custom_element_driver.find_elements(By.TAG_NAME, "div")
     assert all(isinstance(el, MyCustomElement) for el in elements)
     assert all(el.custom_method() == "Custom element method" for el in elements)
 
diff --git a/py/test/selenium/webdriver/remote/remote_custom_locator_tests.py b/py/test/selenium/webdriver/remote/remote_custom_locator_tests.py
index e235f2ee2e999..efa0463148856 100644
--- a/py/test/selenium/webdriver/remote/remote_custom_locator_tests.py
+++ b/py/test/selenium/webdriver/remote/remote_custom_locator_tests.py
@@ -14,6 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+
+import pytest
+
+from selenium import webdriver
 from selenium.webdriver.remote.locator_converter import LocatorConverter
 
 
@@ -25,16 +29,25 @@ def convert(self, by, value):
         return super().convert(by, value)
 
 
-def test_find_element_with_custom_locator(driver):
-    driver.get("data:text/html,<div custom-attr='example'>Test</div>")
-    element = driver.find_element("custom", "example")
+@pytest.fixture()
+def custom_locator_driver(firefox_options):
+    driver = webdriver.Remote(options=firefox_options, locator_converter=CustomLocatorConverter())
+    yield driver
+    driver.quit()
+
+
+def test_find_element_with_custom_locator(custom_locator_driver):
+    custom_locator_driver.get("data:text/html,<div custom-attr='example'>Test</div>")
+    element = custom_locator_driver.find_element("custom", "example")
     assert element is not None
     assert element.text == "Test"
 
 
-def test_find_elements_with_custom_locator(driver):
-    driver.get("data:text/html,<div custom-attr='example'>Test1</div><div custom-attr='example'>Test2</div>")
-    elements = driver.find_elements("custom", "example")
+def test_find_elements_with_custom_locator(custom_locator_driver):
+    custom_locator_driver.get(
+        "data:text/html,<div custom-attr='example'>Test1</div><div custom-attr='example'>Test2</div>"
+    )
+    elements = custom_locator_driver.find_elements("custom", "example")
     assert len(elements) == 2
     assert elements[0].text == "Test1"
     assert elements[1].text == "Test2"
diff --git a/py/test/selenium/webdriver/remote/remote_downloads_tests.py b/py/test/selenium/webdriver/remote/remote_downloads_tests.py
index faa7fd996ecd8..5a8ed7f1fcbd4 100644
--- a/py/test/selenium/webdriver/remote/remote_downloads_tests.py
+++ b/py/test/selenium/webdriver/remote/remote_downloads_tests.py
@@ -14,6 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+
 import os
 import tempfile
 
diff --git a/py/test/selenium/webdriver/remote/remote_firefox_profile_tests.py b/py/test/selenium/webdriver/remote/remote_firefox_profile_tests.py
index 6d5c1d0cb54df..70b1d49b59be9 100644
--- a/py/test/selenium/webdriver/remote/remote_firefox_profile_tests.py
+++ b/py/test/selenium/webdriver/remote/remote_firefox_profile_tests.py
@@ -15,26 +15,13 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import pytest
-
 from selenium import webdriver
+from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
 
 
-@pytest.fixture
-def driver(options):
-    with pytest.warns(None) as record:
-        driver = webdriver.Remote(options=options)
-    assert len(record) == 0
-    yield driver
-    driver.quit()
-
-
-@pytest.fixture
-def options():
-    options = webdriver.FirefoxOptions()
-    options.set_preference("browser.startup.homepage", "about:")
-    return options
-
-
-def test_profile_is_used(driver):
-    assert "about:blank" == driver.current_url or "about:" == driver.current_url
+def test_profile_is_used(firefox_options):
+    ff_profile = FirefoxProfile()
+    ff_profile.set_preference("browser.startup.page", "1")
+    firefox_options.profile = ff_profile
+    with webdriver.Remote(options=firefox_options) as driver:
+        assert "browser/content/blanktab.html" in driver.current_url
diff --git a/py/test/selenium/webdriver/remote/remote_hub_connection.py b/py/test/selenium/webdriver/remote/remote_hub_connection.py
index 2a70fe0bad826..5540444b941fa 100644
--- a/py/test/selenium/webdriver/remote/remote_hub_connection.py
+++ b/py/test/selenium/webdriver/remote/remote_hub_connection.py
@@ -19,10 +19,14 @@
 import urllib3
 
 from selenium import webdriver
+from selenium.webdriver.firefox.options import Options
 
 
 def test_command_executor_ssl_certificate_is_verified():
+    options = Options()
+    site = "wrong.host.badssl.com"
     with pytest.raises(urllib3.exceptions.MaxRetryError) as excinfo:
-        webdriver.Remote(command_executor="https://wrong.host.badssl.com/")
+        webdriver.Remote(command_executor="https://wrong.host.badssl.com/", options=options)
     assert isinstance(excinfo.value.reason, urllib3.exceptions.SSLError)
-    assert "doesn't match" in str(excinfo.value)
+    assert site in str(excinfo.value)
+    assert "certificate is not valid" in str(excinfo.value).lower()
diff --git a/py/test/selenium/webdriver/support/conftest.py b/py/test/selenium/webdriver/support/conftest.py
deleted file mode 100644
index 043b7df7e146f..0000000000000
--- a/py/test/selenium/webdriver/support/conftest.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# 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.
-
-
-def pytest_generate_tests(metafunc):
-    if "driver" in metafunc.fixturenames and metafunc.config.option.drivers:
-        metafunc.parametrize("driver", metafunc.config.option.drivers, indirect=True)


Reviews:
user: login: titusfortner
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T18:47:56Z
user: login: cgoldberg
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T19:31:55Z

Review comments:
diff_hunk: @@ -30,33 +30,26 @@ def is_running_wayland():
 
 
 @pytest.mark.skipif(not is_running_wayland(), reason="This test only runs on Linux under Wayland")
-def test_firefox_opens_large_when_running_xwayland(request):  # noqa: F821
-    options = Options()
-    if request.config.getoption("--headless"):
-        options.add_argument("-headless")
+def test_firefox_opens_large_when_running_xwayland(headless):
path: py/test/selenium/webdriver/firefox/firefox_sizing_tests.py
user: login: titusfortner
body: having a conditional in these tests isn't a good practice. Can we put the conditional in the fixture (maybe call it firefox_options) which adds headless arg if it applies?
created_at: 2025-04-23T18:47:56Z
author_association: MEMBER
diff_hunk: @@ -30,33 +30,26 @@ def is_running_wayland():
 
 
 @pytest.mark.skipif(not is_running_wayland(), reason="This test only runs on Linux under Wayland")
-def test_firefox_opens_large_when_running_xwayland(request):  # noqa: F821
-    options = Options()
-    if request.config.getoption("--headless"):
-        options.add_argument("-headless")
+def test_firefox_opens_large_when_running_xwayland(headless):
path: py/test/selenium/webdriver/firefox/firefox_sizing_tests.py
user: login: cgoldberg
body: I did that in a few tests.. I have no idea why :) I'll move it to a fixture.
created_at: 2025-04-23T19:31:55Z
author_association: MEMBER

PR comments:
user: login: qodo-code-review[bot]
created_at: 2025-04-22T20:33:00Z
body: ## PR Reviewer Guide 🔍

#### (Review updated until commit https://github.com/SeleniumHQ/selenium/commit/5d6d8c9864fb51e087b02612245fe29e549b1368)


Here are some key observations to aid the review process:

<table>
<tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr>
<tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr>
<tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr>
<tr><td>⚡&nbsp;<strong>Recommended focus areas for review</strong><br><br>

<details><summary><a href='https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-98e9e290ede5d0c7ac9e7df5b49edf63f5df5fcc946b7736e4a6139e4cda26e3R118-R198'><strong>Resource Cleanup</strong></a>

The custom_element_driver fixture in remote_custom_element_tests.py properly resets the web_element_cls, but the driver fixture doesn't reset it after tests. This could cause unexpected behavior if tests run in a specific order.
</summary>

```python
@pytest.fixture(scope="function")
def driver(request):
    kwargs = {}
    driver_option = getattr(request, "param", "Chrome")

    # browser can be changed with `--driver=firefox` as an argument or to addopts in pytest.ini
    driver_class = get_driver_class(driver_option)

    # skip tests in the 'remote' directory if run with a local driver
    if request.node.path.parts[-2] == "remote" and driver_class != "Remote":
        pytest.skip(f"Remote tests can't be run with driver '{driver_option}'")

    # skip tests that can't run on certain platforms
    _platform = platform.system()
    if driver_class == "Safari" and _platform != "Darwin":
        pytest.skip("Safari tests can only run on an Apple OS")
    if (driver_class == "Ie") and _platform != "Windows":
        pytest.skip("IE and EdgeHTML Tests can only run on Windows")
    if "WebKit" in driver_class and _platform == "Windows":
        pytest.skip("WebKit tests cannot be run on Windows")

    # skip tests for drivers that don't support BiDi when --bidi is enabled
    if request.config.option.bidi:
        if driver_class in ("Ie", "Safari", "WebKitGTK", "WPEWebKit"):
            pytest.skip(f"{driver_class} does not support BiDi")

    # conditionally mark tests as expected to fail based on driver
    marker = request.node.get_closest_marker(f"xfail_{driver_class.lower()}")

    if marker is not None:
        if "run" in marker.kwargs:
            if marker.kwargs["run"] is False:
                pytest.skip()
                yield
                return
        if "raises" in marker.kwargs:
            marker.kwargs.pop("raises")
        pytest.xfail(**marker.kwargs)

        def fin():
            global driver_instance
            if driver_instance is not None:
                driver_instance.quit()
            driver_instance = None

        request.addfinalizer(fin)

    driver_path = request.config.option.executable
    options = None

    global driver_instance
    if driver_instance is None:
        if driver_class == "Firefox":
            options = get_options(driver_class, request.config)
            if platform.system() == "Linux":
                # There are issues with window size/position when running Firefox
                # under Wayland, so we use XWayland instead.
                os.environ["MOZ_ENABLE_WAYLAND"] = "0"
        if driver_class == "Chrome":
            options = get_options(driver_class, request.config)
        if driver_class == "Edge":
            options = get_options(driver_class, request.config)
        if driver_class == "WebKitGTK":
            options = get_options(driver_class, request.config)
        if driver_class == "WPEWebKit":
            options = get_options(driver_class, request.config)
        if driver_class == "Remote":
            options = get_options("Firefox", request.config) or webdriver.FirefoxOptions()
            options.set_capability("moz:firefoxOptions", {})
            options.enable_downloads = True
        if driver_path is not None:
            kwargs["service"] = get_service(driver_class, driver_path)
        if options is not None:
            kwargs["options"] = options

        driver_instance = getattr(webdriver, driver_class)(**kwargs)

    yield driver_instance
    # Close the browser after BiDi tests. Those make event subscriptions
    # and doesn't seems to be stable enough, causing the flakiness of the
    # subsequent tests.
```

</details>

<details><summary><a href='https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-bd136d071186f266ea0c86c8916238483adac322586a350745e7690f2365fe81R31-R43'><strong>Incomplete Implementation</strong></a>

The tests are marked as skipped with "Needs to be updated" but there's no implementation of setting the custom locator converter on the driver. The tests will fail if unskipped without proper implementation.
</summary>

```python
@pytest.mark.skip(reason="Needs to be updated")
def test_find_element_with_custom_locator(driver):
    driver.get("data:text/html,<div custom-attr='example'>Test</div>")
    element = driver.find_element("custom", "example")
    assert element is not None
    assert element.text == "Test"


@pytest.mark.skip(reason="Needs to be updated")
def test_find_elements_with_custom_locator(driver):
    driver.get("data:text/html,<div custom-attr='example'>Test1</div><div custom-attr='example'>Test2</div>")
    elements = driver.find_elements("custom", "example")
    assert len(elements) == 2
```

</details>

</td></tr>
</table>

author_association: CONTRIBUTOR
user: login: titusfortner
created_at: 2025-04-22T23:48:05Z
body: Does it need a separate conftest? It'll be easy to overlook it if there are multiple. 
author_association: MEMBER
user: login: cgoldberg
created_at: 2025-04-23T00:55:55Z
body: > Does it need a separate conftest?

Good call... it doesn't. I just moved the function it contained into the main conftest, which allowed me to removed it, along with 3 other duplicate conftests.

I just updated the PR description too. This ended up larger than I was expecting, but it cleans up a lot of junk.

I haven't tested it all yet, so we'll see if CI passes :)
author_association: MEMBER
user: login: cgoldberg
created_at: 2025-04-23T01:04:11Z
body: @navin772 

Can you take a look at `py/test/selenium/webdriver/remote/remote_custom_locator_tests.py`? (either in this PR or in trunk once it gets merged).

There are 2 tests that you added in #14587. They fail for me locally, so I have them marked to skip (they weren't getting run in CI before this PR). I don't really understand what they do and don't know if they are failing because of a real issue or they are just broken tests.
author_association: MEMBER
user: login: qodo-code-review[bot]
created_at: 2025-04-23T04:09:34Z
body: ## PR Code Suggestions ✨

<!-- 5d6d8c9 -->

Explore these optional code suggestions:

<table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=2>Possible issue</td>
<td>



<details><summary>Fix path handling compatibility</summary>

___


**The code is using <code>request.node.path.parts</code> which assumes <code>path</code> is a <code>pathlib.Path</code> <br>object, but in some pytest versions it might be a string. Use <br><code>pathlib.Path(request.node.path).parts</code> to ensure compatibility.**

[py/conftest.py [126-128]](https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-98e9e290ede5d0c7ac9e7df5b49edf63f5df5fcc946b7736e4a6139e4cda26e3R126-R128)

```diff
 # skip tests in the 'remote' directory if run with a local driver
-if request.node.path.parts[-2] == "remote" and driver_class != "Remote":
+from pathlib import Path
+if Path(request.node.path).parts[-2] == "remote" and driver_class != "Remote":
     pytest.skip(f"Remote tests can't be run with driver '{driver_option}'")
```

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=0 -->

<details><summary>Suggestion importance[1-10]: 8</summary>

__

Why: This suggestion addresses a potential compatibility issue with pytest path handling. Using Path(request.node.path).parts ensures the code works correctly across different pytest versions where path might be a string instead of a Path object, preventing potential runtime errors.


</details></details></td><td align=center>Medium

</td></tr><tr><td>



<details><summary>Make SSL error check robust</summary>

___


**The assertion is too specific about the error message. SSL error messages can <br>vary between platforms and urllib3 versions. Use a more general assertion that <br>checks for SSL verification failure.**

[py/test/selenium/webdriver/remote/remote_hub_connection.py [31-32]](https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-f0e9d09f74160652aa9aa1086766ce6e300bd1d435a4c242239e9f01f015ae4cR31-R32)

```diff
 assert site in str(excinfo.value)
-assert "certificate is not valid" in str(excinfo.value).lower()
+# Check for any SSL verification failure message
+assert any(msg in str(excinfo.value).lower() for msg in ["ssl", "certificate", "verify"])
```

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=1 -->

<details><summary>Suggestion importance[1-10]: 6</summary>

__

Why: The suggestion improves test reliability by making the SSL error assertion more flexible. Since SSL error messages can vary between platforms and library versions, using a more general check prevents test failures due to minor message variations.


</details></details></td><td align=center>Low

</td></tr><tr><td rowspan=1>General</td>
<td>



<details><summary>Ensure proper fixture cleanup<!-- not_implemented --></summary>

___


**The fixture doesn't properly restore the original web element class if an <br>exception occurs during the test. Use try/finally to ensure proper cleanup <br>regardless of test outcome.**

[py/test/selenium/webdriver/remote/custom_element_tests.py [30-34]](https://github.com/SeleniumHQ/selenium/pull/15657/files#diff-77a6cf2e863316b3f01fe6c7a26b561e7d04e75b22acf85b868d5b082a2f20edR30-R34)

```diff
 @pytest.fixture()
 def custom_element_driver(driver):
-    driver._web_element_cls = MyCustomElement
-    yield driver
-    driver._web_element_cls = WebElement
+    original_cls = driver._web_element_cls
+    try:
+        driver._web_element_cls = MyCustomElement
+        yield driver
+    finally:
+        driver._web_element_cls = original_cls
```

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=2 -->

<details><summary>Suggestion importance[1-10]: 7</summary>

__

Why: This suggestion improves test robustness by properly saving and restoring the original web element class in a try/finally block, ensuring cleanup happens even if an exception occurs during test execution, preventing test state leakage.


</details></details></td><td align=center>Medium

</td></tr>
<tr><td align="center" colspan="2">

- [ ] Update <!-- /improve --more_suggestions=true -->

</td><td></td></tr></tbody></table>
author_association: CONTRIBUTOR
user: login: navin772
created_at: 2025-04-23T08:32:02Z
body: Hi @cgoldberg, the tests are failing because of a misconfiguration, the remote webdriver expects the `CustomLocatorConverter()` class to be passed as a param for the tests to pass.
Something like this:

```python
driver = webdriver.Remote(command_executor="http://localhost:4444", options=options, locator_converter=CustomLocatorConverter())
```

Do you  know how can I add the above only for my test file?
author_association: MEMBER
user: login: cgoldberg
created_at: 2025-04-23T12:26:36Z
body: @navin772 I'll fix it in this branch. Thanks!
author_association: MEMBER
user: login: cgoldberg
created_at: 2025-04-23T15:04:39Z
body: @navin772 thanks.. that worked.

> Do you know how can I add the above only for my test file?

I added a small fixture to the test file so it doesn't use the main `driver` fixture. It's not great, but it solves this problem.
author_association: MEMBER
user: login: navin772
created_at: 2025-04-23T15:28:28Z
body: @cgoldberg thanks for the changes, this PR looks good, just run the formatter once.

author_association: MEMBER
user: login: titusfortner
created_at: 2025-04-23T18:36:44Z
body: >  which allowed me to removed it, along with 3 other duplicate conftests

Right, I had no idea there were multiple of these, so thanks!
author_association: MEMBER
user: login: cgoldberg
created_at: 2025-04-24T22:02:55Z
body: The only failure in CI is not related to this PR.
author_association: MEMBER