Skip to content

[py] do not use global var for devtools, allows multiple devtools to run #15881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions py/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,19 @@ def clean_driver(request):
except (AttributeError, TypeError):
raise Exception("This test requires a --driver to be specified.")
driver_reference = getattr(webdriver, driver_class)

# 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)

yield driver_reference
if request.node.get_closest_marker("no_driver_after_test"):
driver_reference = None
Expand Down
57 changes: 26 additions & 31 deletions py/selenium/webdriver/remote/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

"""The WebDriver implementation."""

import base64
Expand Down Expand Up @@ -75,7 +74,6 @@
from .websocket_connection import WebSocketConnection

cdp = None
devtools = None


def import_cdp():
Expand Down Expand Up @@ -267,6 +265,7 @@ def __init__(
self._storage = None
self._webextension = None
self._permissions = None
self._devtools = None

def __repr__(self):
return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>'
Expand Down Expand Up @@ -1182,32 +1181,28 @@ def orientation(self, value) -> None:
raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'")

def start_devtools(self):
global devtools
if self._websocket_connection:
return devtools, self._websocket_connection
global cdp
import_cdp()
if self.caps.get("se:cdp"):
ws_url = self.caps.get("se:cdp")
version = self.caps.get("se:cdpVersion").split(".")[0]
else:
global cdp
import_cdp()

if not devtools:
if self.caps.get("se:cdp"):
ws_url = self.caps.get("se:cdp")
version = self.caps.get("se:cdpVersion").split(".")[0]
else:
version, ws_url = self._get_cdp_details()

if not ws_url:
raise WebDriverException("Unable to find url to connect to from capabilities")

devtools = cdp.import_devtools(version)
if self.caps["browserName"].lower() == "firefox":
raise RuntimeError("CDP support for Firefox has been removed. Please switch to WebDriver BiDi.")
self._websocket_connection = WebSocketConnection(ws_url)
targets = self._websocket_connection.execute(devtools.target.get_targets())
target_id = targets[0].target_id
session = self._websocket_connection.execute(devtools.target.attach_to_target(target_id, True))
self._websocket_connection.session_id = session
return devtools, self._websocket_connection
version, ws_url = self._get_cdp_details()

if not ws_url:
raise WebDriverException("Unable to find url to connect to from capabilities")

self._devtools = cdp.import_devtools(version)
if self._websocket_connection:
return self._devtools, self._websocket_connection
if self.caps["browserName"].lower() == "firefox":
raise RuntimeError("CDP support for Firefox has been removed. Please switch to WebDriver BiDi.")
self._websocket_connection = WebSocketConnection(ws_url)
targets = self._websocket_connection.execute(self._devtools.target.get_targets())
target_id = targets[0].target_id
session = self._websocket_connection.execute(self._devtools.target.attach_to_target(target_id, True))
self._websocket_connection.session_id = session
return self._devtools, self._websocket_connection

@asynccontextmanager
async def bidi_connection(self):
Expand Down Expand Up @@ -1282,9 +1277,8 @@ def browser(self):

@property
def _session(self):
"""
Returns the BiDi session object for the current WebDriver session.
"""
"""Returns the BiDi session object for the current WebDriver
session."""
if not self._websocket_connection:
self._start_bidi()

Expand All @@ -1295,7 +1289,8 @@ def _session(self):

@property
def browsing_context(self):
"""Returns a browsing context module object for BiDi browsing context commands.
"""Returns a browsing context module object for BiDi browsing context
commands.

Returns:
--------
Expand Down
13 changes: 13 additions & 0 deletions py/test/selenium/webdriver/common/devtools_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,16 @@ def test_check_console_messages(driver, pages, recwarn):
assert console_api_calls[0].args[0].value == "I love cheese"
assert console_api_calls[1].type_ == "error"
assert console_api_calls[1].args[0].value == "I love bread"


@pytest.mark.xfail_safari
@pytest.mark.xfail_firefox
@pytest.mark.xfail_remote
def test_check_start_twice(clean_driver, clean_options):
driver1 = clean_driver(options=clean_options)
devtools1, connection1 = driver1.start_devtools()
driver1.quit()

driver2 = clean_driver(options=clean_options)
devtools2, connection2 = driver2.start_devtools()
driver2.quit()