Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/common/bidi/browser.py
4113 views
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
import os
18
from typing import Any
19
20
from selenium.webdriver.common.bidi.common import command_builder
21
from selenium.webdriver.common.bidi.session import UserPromptHandler
22
from selenium.webdriver.common.proxy import Proxy
23
24
25
class ClientWindowState:
26
"""Represents a window state."""
27
28
FULLSCREEN = "fullscreen"
29
MAXIMIZED = "maximized"
30
MINIMIZED = "minimized"
31
NORMAL = "normal"
32
33
VALID_STATES = {FULLSCREEN, MAXIMIZED, MINIMIZED, NORMAL}
34
35
36
class ClientWindowInfo:
37
"""Represents a client window information."""
38
39
def __init__(
40
self,
41
client_window: str,
42
state: str,
43
width: int,
44
height: int,
45
x: int,
46
y: int,
47
active: bool,
48
):
49
self.client_window = client_window
50
self.state = state
51
self.width = width
52
self.height = height
53
self.x = x
54
self.y = y
55
self.active = active
56
57
def get_state(self) -> str:
58
"""Gets the state of the client window.
59
60
Returns:
61
str: The state of the client window (one of the ClientWindowState constants).
62
"""
63
return self.state
64
65
def get_client_window(self) -> str:
66
"""Gets the client window identifier.
67
68
Returns:
69
str: The client window identifier.
70
"""
71
return self.client_window
72
73
def get_width(self) -> int:
74
"""Gets the width of the client window.
75
76
Returns:
77
int: The width of the client window.
78
"""
79
return self.width
80
81
def get_height(self) -> int:
82
"""Gets the height of the client window.
83
84
Returns:
85
int: The height of the client window.
86
"""
87
return self.height
88
89
def get_x(self) -> int:
90
"""Gets the x coordinate of the client window.
91
92
Returns:
93
int: The x coordinate of the client window.
94
"""
95
return self.x
96
97
def get_y(self) -> int:
98
"""Gets the y coordinate of the client window.
99
100
Returns:
101
int: The y coordinate of the client window.
102
"""
103
return self.y
104
105
def is_active(self) -> bool:
106
"""Checks if the client window is active.
107
108
Returns:
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
Args:
118
data: A dictionary containing the client window information.
119
120
Returns:
121
ClientWindowInfo: A new instance of ClientWindowInfo.
122
123
Raises:
124
ValueError: If required fields are missing or have invalid types.
125
"""
126
try:
127
client_window = data["clientWindow"]
128
if not isinstance(client_window, str):
129
raise ValueError("clientWindow must be a string")
130
131
state = data["state"]
132
if not isinstance(state, str):
133
raise ValueError("state must be a string")
134
if state not in ClientWindowState.VALID_STATES:
135
raise ValueError(f"Invalid state: {state}. Must be one of {ClientWindowState.VALID_STATES}")
136
137
width = data["width"]
138
if not isinstance(width, int) or width < 0:
139
raise ValueError(f"width must be a non-negative integer, got {width}")
140
141
height = data["height"]
142
if not isinstance(height, int) or height < 0:
143
raise ValueError(f"height must be a non-negative integer, got {height}")
144
145
x = data["x"]
146
if not isinstance(x, int):
147
raise ValueError(f"x must be an integer, got {type(x).__name__}")
148
149
y = data["y"]
150
if not isinstance(y, int):
151
raise ValueError(f"y must be an integer, got {type(y).__name__}")
152
153
active = data["active"]
154
if not isinstance(active, bool):
155
raise ValueError("active must be a boolean")
156
157
return cls(
158
client_window=client_window,
159
state=state,
160
width=width,
161
height=height,
162
x=x,
163
y=y,
164
active=active,
165
)
166
except (KeyError, TypeError) as e:
167
raise ValueError(f"Invalid data format for ClientWindowInfo: {e}") from e
168
169
170
class Browser:
171
"""BiDi implementation of the browser module."""
172
173
def __init__(self, conn):
174
self.conn = conn
175
176
def create_user_context(
177
self,
178
accept_insecure_certs: bool | None = None,
179
proxy: Proxy | None = None,
180
unhandled_prompt_behavior: UserPromptHandler | None = None,
181
) -> str:
182
"""Creates a new user context.
183
184
Args:
185
accept_insecure_certs: Optional flag to accept insecure TLS certificates.
186
proxy: Optional proxy configuration for the user context.
187
unhandled_prompt_behavior: Optional configuration for handling user prompts.
188
189
Returns:
190
str: The ID of the created user context.
191
"""
192
params: dict[str, Any] = {}
193
194
if accept_insecure_certs is not None:
195
params["acceptInsecureCerts"] = accept_insecure_certs
196
197
if proxy is not None:
198
params["proxy"] = proxy.to_bidi_dict()
199
200
if unhandled_prompt_behavior is not None:
201
params["unhandledPromptBehavior"] = unhandled_prompt_behavior.to_dict()
202
203
result = self.conn.execute(command_builder("browser.createUserContext", params))
204
return result["userContext"]
205
206
def get_user_contexts(self) -> list[str]:
207
"""Gets all user contexts.
208
209
Returns:
210
List[str]: A list of user context IDs.
211
"""
212
result = self.conn.execute(command_builder("browser.getUserContexts", {}))
213
return [context_info["userContext"] for context_info in result["userContexts"]]
214
215
def remove_user_context(self, user_context_id: str) -> None:
216
"""Removes a user context.
217
218
Args:
219
user_context_id: The ID of the user context to remove.
220
221
Raises:
222
ValueError: If the user context ID is "default" or does not exist.
223
"""
224
if user_context_id == "default":
225
raise ValueError("Cannot remove the default user context")
226
227
params = {"userContext": user_context_id}
228
self.conn.execute(command_builder("browser.removeUserContext", params))
229
230
def get_client_windows(self) -> list[ClientWindowInfo]:
231
"""Gets all client windows.
232
233
Returns:
234
List[ClientWindowInfo]: A list of client window information.
235
"""
236
result = self.conn.execute(command_builder("browser.getClientWindows", {}))
237
return [ClientWindowInfo.from_dict(window) for window in result["clientWindows"]]
238
239
def set_download_behavior(
240
self,
241
*,
242
allowed: bool | None = None,
243
destination_folder: str | os.PathLike | None = None,
244
user_contexts: list[str] | None = None,
245
) -> None:
246
"""Set the download behavior for the browser or specific user contexts.
247
248
Args:
249
allowed: True to allow downloads, False to deny downloads, or None to
250
clear download behavior (revert to default).
251
destination_folder: Required when allowed is True. Specifies the folder
252
to store downloads in.
253
user_contexts: Optional list of user context IDs to apply this
254
behavior to. If omitted, updates the default behavior.
255
256
Raises:
257
ValueError: If allowed=True and destination_folder is missing, or if
258
allowed=False and destination_folder is provided.
259
"""
260
params: dict[str, Any] = {}
261
262
if allowed is None:
263
params["downloadBehavior"] = None
264
else:
265
if allowed:
266
if not destination_folder:
267
raise ValueError("destination_folder is required when allowed=True.")
268
params["downloadBehavior"] = {
269
"type": "allowed",
270
"destinationFolder": os.fspath(destination_folder),
271
}
272
else:
273
if destination_folder:
274
raise ValueError("destination_folder should not be provided when allowed=False.")
275
params["downloadBehavior"] = {"type": "denied"}
276
277
if user_contexts is not None:
278
params["userContexts"] = user_contexts
279
280
self.conn.execute(command_builder("browser.setDownloadBehavior", params))
281
282