Skip to content

Commit 14a154c

Browse files
committed
implement BiDi browser module
1 parent 78ffa20 commit 14a154c

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from typing import Dict, List
19+
20+
21+
class ClientWindowState:
22+
"""Represents a window state."""
23+
FULLSCREEN = "fullscreen"
24+
MAXIMIZED = "maximized"
25+
MINIMIZED = "minimized"
26+
NORMAL = "normal"
27+
28+
29+
class ClientWindowInfo:
30+
"""Represents a client window information."""
31+
32+
def __init__(
33+
self,
34+
client_window: str,
35+
state: str,
36+
width: int,
37+
height: int,
38+
x: int,
39+
y: int,
40+
active: bool,
41+
):
42+
self.client_window = client_window
43+
self.state = state
44+
self.width = width
45+
self.height = height
46+
self.x = x
47+
self.y = y
48+
self.active = active
49+
50+
def get_state(self) -> str:
51+
"""Gets the state of the client window.
52+
53+
Returns:
54+
-------
55+
str: The state of the client window (one of the ClientWindowState constants).
56+
"""
57+
return self.state
58+
59+
def get_client_window(self) -> str:
60+
"""Gets the client window identifier.
61+
62+
Returns:
63+
-------
64+
str: The client window identifier.
65+
"""
66+
return self.client_window
67+
68+
def get_width(self) -> int:
69+
"""Gets the width of the client window.
70+
71+
Returns:
72+
-------
73+
int: The width of the client window.
74+
"""
75+
return self.width
76+
77+
def get_height(self) -> int:
78+
"""Gets the height of the client window.
79+
80+
Returns:
81+
-------
82+
int: The height of the client window.
83+
"""
84+
return self.height
85+
86+
def get_x(self) -> int:
87+
"""Gets the x coordinate of the client window.
88+
89+
Returns:
90+
-------
91+
int: The x coordinate of the client window.
92+
"""
93+
return self.x
94+
95+
def get_y(self) -> int:
96+
"""Gets the y coordinate of the client window.
97+
98+
Returns:
99+
-------
100+
int: The y coordinate of the client window.
101+
"""
102+
return self.y
103+
104+
def is_active(self) -> bool:
105+
"""Checks if the client window is active.
106+
107+
Returns:
108+
-------
109+
bool: True if the client window is active, False otherwise.
110+
"""
111+
return self.active
112+
113+
@classmethod
114+
def from_dict(cls, data: Dict) -> "ClientWindowInfo":
115+
"""Creates a ClientWindowInfo instance from a dictionary.
116+
117+
Parameters:
118+
-----------
119+
data: A dictionary containing the client window information.
120+
121+
Returns:
122+
-------
123+
ClientWindowInfo: A new instance of ClientWindowInfo.
124+
"""
125+
return cls(
126+
client_window=data.get("clientWindow"),
127+
state=data.get("state"),
128+
width=data.get("width"),
129+
height=data.get("height"),
130+
x=data.get("x"),
131+
y=data.get("y"),
132+
active=data.get("active"),
133+
)
134+
135+
136+
class Browser:
137+
"""
138+
BiDi implementation of the browser module.
139+
"""
140+
141+
def __init__(self, conn):
142+
self.conn = conn
143+
144+
def command_builder(self, method: str, params: Dict = None) -> Dict:
145+
"""Build a command iterator to send to the browser.
146+
147+
Parameters:
148+
-----------
149+
method: The method to execute.
150+
params: The parameters to pass to the method. Default is None.
151+
"""
152+
if params is None:
153+
params = {}
154+
155+
command = {"method": method, "params": params}
156+
cmd = yield command
157+
return cmd
158+
159+
def create_user_context(self) -> str:
160+
"""Creates a new user context.
161+
162+
Returns:
163+
-------
164+
str: The ID of the created user context.
165+
"""
166+
result = self.conn.execute(self.command_builder("browser.createUserContext", {}))
167+
return result["userContext"]
168+
169+
def get_user_contexts(self) -> List[str]:
170+
"""Gets all user contexts.
171+
172+
Returns:
173+
-------
174+
List[str]: A list of user context IDs.
175+
"""
176+
result = self.conn.execute(self.command_builder("browser.getUserContexts", {}))
177+
return [context_info["userContext"] for context_info in result["userContexts"]]
178+
179+
def remove_user_context(self, user_context_id: str) -> None:
180+
"""Removes a user context.
181+
182+
Parameters:
183+
-----------
184+
user_context_id: The ID of the user context to remove.
185+
186+
Raises:
187+
------
188+
Exception: If the user context ID is "default" or does not exist.
189+
"""
190+
params = {"userContext": user_context_id}
191+
self.conn.execute(self.command_builder("browser.removeUserContext", params))
192+
193+
def get_client_windows(self) -> List[ClientWindowInfo]:
194+
"""Gets all client windows.
195+
196+
Returns:
197+
-------
198+
List[ClientWindowInfo]: A list of client window information.
199+
"""
200+
result = self.conn.execute(self.command_builder("browser.getClientWindows", {}))
201+
return [ClientWindowInfo.from_dict(window) for window in result["clientWindows"]]

py/selenium/webdriver/remote/webdriver.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from selenium.common.exceptions import NoSuchCookieException
4242
from selenium.common.exceptions import NoSuchElementException
4343
from selenium.common.exceptions import WebDriverException
44+
from selenium.webdriver.common.bidi.browser import Browser
4445
from selenium.webdriver.common.bidi.network import Network
4546
from selenium.webdriver.common.bidi.script import Script
4647
from selenium.webdriver.common.by import By
@@ -254,6 +255,7 @@ def __init__(
254255
self._websocket_connection = None
255256
self._script = None
256257
self._network = None
258+
self._browser = None
257259

258260
def __repr__(self):
259261
return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>'
@@ -1269,6 +1271,29 @@ def network(self):
12691271

12701272
return self._network
12711273

1274+
@property
1275+
def browser(self):
1276+
"""Returns a browser module object for BiDi browser commands.
1277+
1278+
Returns:
1279+
--------
1280+
Browser: an object containing access to BiDi browser commands.
1281+
1282+
Examples:
1283+
---------
1284+
>>> user_context = driver.browser.create_user_context()
1285+
>>> user_contexts = driver.browser.get_user_contexts()
1286+
>>> client_windows = driver.browser.get_client_windows()
1287+
>>> driver.browser.remove_user_context(user_context)
1288+
"""
1289+
if not self._websocket_connection:
1290+
self._start_bidi()
1291+
1292+
if self._browser is None:
1293+
self._browser = Browser(self._websocket_connection)
1294+
1295+
return self._browser
1296+
12721297
def _get_cdp_details(self):
12731298
import json
12741299

0 commit comments

Comments
 (0)