From 1cb1bb6b846206c16909e25e0dc5759d75b9e7ea Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Fri, 11 Apr 2025 17:08:29 +0530 Subject: [PATCH 1/3] add bidi command session status --- py/selenium/webdriver/common/bidi/session.py | 18 +++++++ py/selenium/webdriver/remote/webdriver.py | 24 +++++++++ .../webdriver/common/bidi_session_tests.py | 51 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 py/test/selenium/webdriver/common/bidi_session_tests.py diff --git a/py/selenium/webdriver/common/bidi/session.py b/py/selenium/webdriver/common/bidi/session.py index dbe5d26644a87..77d645a1c7334 100644 --- a/py/selenium/webdriver/common/bidi/session.py +++ b/py/selenium/webdriver/common/bidi/session.py @@ -44,3 +44,21 @@ def session_unsubscribe(*events, browsing_contexts=None): cmd_dict["params"]["browsingContexts"] = browsing_contexts _ = yield cmd_dict return None + + +def session_status(): + """ + The session.status command returns information about the remote end's readiness + to create new sessions and may include implementation-specific metadata. + + Returns + ------- + dict + Dictionary containing the ready state (bool), message (str) and metadata + """ + cmd_dict = { + "method": "session.status", + "params": {}, + } + result = yield cmd_dict + return result diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index a97952d84383f..6bda9ee7a466a 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -1269,6 +1269,30 @@ def network(self): return self._network + def get_bidi_session_status(self): + """ + Get the session status using WebDriver BiDi. + Returns information about whether a remote end is in a state + in which it can create new sessions. + + Returns: + ------- + dict + Dictionary containing the ready state (bool), message (str) and metadata + + Example: + -------- + >>> status = driver.get_bidi_session_status() + >>> print(status["ready"]) + >>> print(status["message"]) + """ + if not self._websocket_connection: + self._start_bidi() + + from selenium.webdriver.common.bidi.session import session_status + + return self._websocket_connection.execute(session_status()) + def _get_cdp_details(self): import json diff --git a/py/test/selenium/webdriver/common/bidi_session_tests.py b/py/test/selenium/webdriver/common/bidi_session_tests.py new file mode 100644 index 0000000000000..df29127d69550 --- /dev/null +++ b/py/test/selenium/webdriver/common/bidi_session_tests.py @@ -0,0 +1,51 @@ +# 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 pytest + +from selenium.webdriver.common.window import WindowTypes + + +@pytest.mark.xfail_safari +def test_session_status(driver): + result = driver.get_bidi_session_status() + + assert result is not None + assert "ready" in result + assert "message" in result + assert isinstance(result["ready"], bool) + assert isinstance(result["message"], str) + + +@pytest.mark.xfail_safari +def test_session_status_not_closed_with_one_window(driver): + # initial session status + initial_status = driver.get_bidi_session_status() + assert initial_status is not None + + # Open new window and tab + driver.switch_to.new_window(WindowTypes.WINDOW) + driver.switch_to.new_window(WindowTypes.TAB) + + # Close one window + driver.close() + + # Session should still be active + status_after_closing = driver.get_bidi_session_status() + assert status_after_closing is not None + assert "ready" in status_after_closing + assert "message" in status_after_closing From 9d490568df18fad65dda24a8fd10f2ac2941da60 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Mon, 14 Apr 2025 12:23:42 +0530 Subject: [PATCH 2/3] use Session class for session module --- py/selenium/webdriver/common/bidi/script.py | 9 ++- py/selenium/webdriver/common/bidi/session.py | 73 +++++++++---------- py/selenium/webdriver/remote/webdriver.py | 26 +++---- .../webdriver/common/bidi_session_tests.py | 7 +- 4 files changed, 52 insertions(+), 63 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/script.py b/py/selenium/webdriver/common/bidi/script.py index 1dc8d101d670e..cfefee96fce75 100644 --- a/py/selenium/webdriver/common/bidi/script.py +++ b/py/selenium/webdriver/common/bidi/script.py @@ -18,8 +18,7 @@ from dataclasses import dataclass from typing import List -from .session import session_subscribe -from .session import session_unsubscribe +from .session import Session class Script: @@ -43,12 +42,14 @@ def remove_console_message_handler(self, id): def _subscribe_to_log_entries(self): if not self.log_entry_subscribed: - self.conn.execute(session_subscribe(LogEntryAdded.event_class)) + session = Session(self.conn) + self.conn.execute(session.subscribe(LogEntryAdded.event_class)) self.log_entry_subscribed = True def _unsubscribe_from_log_entries(self): if self.log_entry_subscribed and LogEntryAdded.event_class not in self.conn.callbacks: - self.conn.execute(session_unsubscribe(LogEntryAdded.event_class)) + session = Session(self.conn) + self.conn.execute(session.unsubscribe(LogEntryAdded.event_class)) self.log_entry_subscribed = False def _handle_log_entry(self, type, handler): diff --git a/py/selenium/webdriver/common/bidi/session.py b/py/selenium/webdriver/common/bidi/session.py index 77d645a1c7334..693f2b4931ba4 100644 --- a/py/selenium/webdriver/common/bidi/session.py +++ b/py/selenium/webdriver/common/bidi/session.py @@ -15,50 +15,43 @@ # specific language governing permissions and limitations # under the License. +from selenium.webdriver.common.bidi.common import command_builder -def session_subscribe(*events, browsing_contexts=None): - cmd_dict = { - "method": "session.subscribe", - "params": { - "events": events, - }, - } - if browsing_contexts is None: - browsing_contexts = [] - if browsing_contexts: - cmd_dict["params"]["browsingContexts"] = browsing_contexts - _ = yield cmd_dict - return None +class Session: + + def __init__(self, conn): + self.conn = conn -def session_unsubscribe(*events, browsing_contexts=None): - cmd_dict = { - "method": "session.unsubscribe", - "params": { + def subscribe(self, *events, browsing_contexts=None): + params = { "events": events, - }, - } - if browsing_contexts is None: - browsing_contexts = [] - if browsing_contexts: - cmd_dict["params"]["browsingContexts"] = browsing_contexts - _ = yield cmd_dict - return None + } + if browsing_contexts is None: + browsing_contexts = [] + if browsing_contexts: + params["browsingContexts"] = browsing_contexts + return command_builder("session.subscribe", params) + def unsubscribe(self, *events, browsing_contexts=None): + params = { + "events": events, + } + if browsing_contexts is None: + browsing_contexts = [] + if browsing_contexts: + params["browsingContexts"] = browsing_contexts + return command_builder("session.unsubscribe", params) -def session_status(): - """ - The session.status command returns information about the remote end's readiness - to create new sessions and may include implementation-specific metadata. + def status(self): + """ + The session.status command returns information about the remote end's readiness + to create new sessions and may include implementation-specific metadata. - Returns - ------- - dict - Dictionary containing the ready state (bool), message (str) and metadata - """ - cmd_dict = { - "method": "session.status", - "params": {}, - } - result = yield cmd_dict - return result + Returns + ------- + dict + Dictionary containing the ready state (bool), message (str) and metadata + """ + cmd = command_builder("session.status", {}) + return self.conn.execute(cmd) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index c0138e1326c6a..7c688f142b70f 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -44,6 +44,7 @@ from selenium.webdriver.common.bidi.browser import Browser from selenium.webdriver.common.bidi.network import Network from selenium.webdriver.common.bidi.script import Script +from selenium.webdriver.common.bidi.session import Session from selenium.webdriver.common.by import By from selenium.webdriver.common.options import ArgOptions from selenium.webdriver.common.options import BaseOptions @@ -256,6 +257,7 @@ def __init__( self._script = None self._network = None self._browser = None + self._session = None def __repr__(self): return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>' @@ -1294,29 +1296,23 @@ def browser(self): return self._browser - def get_bidi_session_status(self): - """ - Get the session status using WebDriver BiDi. - Returns information about whether a remote end is in a state - in which it can create new sessions. - - Returns: - ------- - dict - Dictionary containing the ready state (bool), message (str) and metadata + @property + def session(self): + """Returns the BiDi session object for the current WebDriver session. Example: -------- - >>> status = driver.get_bidi_session_status() - >>> print(status["ready"]) - >>> print(status["message"]) + >>> driver.session.subscribe() + >>> driver.session.unsubscribe() + >>> session = driver.session.status() """ if not self._websocket_connection: self._start_bidi() - from selenium.webdriver.common.bidi.session import session_status + if self._session is None: + self._session = Session(self._websocket_connection) - return self._websocket_connection.execute(session_status()) + return self._session def _get_cdp_details(self): import json diff --git a/py/test/selenium/webdriver/common/bidi_session_tests.py b/py/test/selenium/webdriver/common/bidi_session_tests.py index df29127d69550..0e18321e396ae 100644 --- a/py/test/selenium/webdriver/common/bidi_session_tests.py +++ b/py/test/selenium/webdriver/common/bidi_session_tests.py @@ -22,8 +22,7 @@ @pytest.mark.xfail_safari def test_session_status(driver): - result = driver.get_bidi_session_status() - + result = driver.session.status() assert result is not None assert "ready" in result assert "message" in result @@ -34,7 +33,7 @@ def test_session_status(driver): @pytest.mark.xfail_safari def test_session_status_not_closed_with_one_window(driver): # initial session status - initial_status = driver.get_bidi_session_status() + initial_status = driver.session.status() assert initial_status is not None # Open new window and tab @@ -45,7 +44,7 @@ def test_session_status_not_closed_with_one_window(driver): driver.close() # Session should still be active - status_after_closing = driver.get_bidi_session_status() + status_after_closing = driver.session.status() assert status_after_closing is not None assert "ready" in status_after_closing assert "message" in status_after_closing From eb47febcad92b4af7e8c82bd350fc9546a48ad54 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Wed, 23 Apr 2025 15:14:49 +0530 Subject: [PATCH 3/3] make webdriver bidi session private --- py/selenium/webdriver/remote/webdriver.py | 19 +++++++------------ .../webdriver/common/bidi_session_tests.py | 6 +++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 7c688f142b70f..0fe824c62f444 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -257,7 +257,7 @@ def __init__( self._script = None self._network = None self._browser = None - self._session = None + self._bidi_session = None def __repr__(self): return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>' @@ -1297,22 +1297,17 @@ def browser(self): return self._browser @property - def session(self): - """Returns the BiDi session object for the current WebDriver session. - - Example: - -------- - >>> driver.session.subscribe() - >>> driver.session.unsubscribe() - >>> session = driver.session.status() + def _session(self): + """ + Returns the BiDi session object for the current WebDriver session. """ if not self._websocket_connection: self._start_bidi() - if self._session is None: - self._session = Session(self._websocket_connection) + if self._bidi_session is None: + self._bidi_session = Session(self._websocket_connection) - return self._session + return self._bidi_session def _get_cdp_details(self): import json diff --git a/py/test/selenium/webdriver/common/bidi_session_tests.py b/py/test/selenium/webdriver/common/bidi_session_tests.py index 0e18321e396ae..8bb59a1751c30 100644 --- a/py/test/selenium/webdriver/common/bidi_session_tests.py +++ b/py/test/selenium/webdriver/common/bidi_session_tests.py @@ -22,7 +22,7 @@ @pytest.mark.xfail_safari def test_session_status(driver): - result = driver.session.status() + result = driver._session.status() assert result is not None assert "ready" in result assert "message" in result @@ -33,7 +33,7 @@ def test_session_status(driver): @pytest.mark.xfail_safari def test_session_status_not_closed_with_one_window(driver): # initial session status - initial_status = driver.session.status() + initial_status = driver._session.status() assert initial_status is not None # Open new window and tab @@ -44,7 +44,7 @@ def test_session_status_not_closed_with_one_window(driver): driver.close() # Session should still be active - status_after_closing = driver.session.status() + status_after_closing = driver._session.status() assert status_after_closing is not None assert "ready" in status_after_closing assert "message" in status_after_closing