Path: blob/trunk/py/selenium/webdriver/common/bidi/browser.py
4113 views
# Licensed to the Software Freedom Conservancy (SFC) under one1# or more contributor license agreements. See the NOTICE file2# distributed with this work for additional information3# regarding copyright ownership. The SFC licenses this file4# to you under the Apache License, Version 2.0 (the5# "License"); you may not use this file except in compliance6# with the License. You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing,11# software distributed under the License is distributed on an12# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13# KIND, either express or implied. See the License for the14# specific language governing permissions and limitations15# under the License.16import os17from typing import Any1819from selenium.webdriver.common.bidi.common import command_builder20from selenium.webdriver.common.bidi.session import UserPromptHandler21from selenium.webdriver.common.proxy import Proxy222324class ClientWindowState:25"""Represents a window state."""2627FULLSCREEN = "fullscreen"28MAXIMIZED = "maximized"29MINIMIZED = "minimized"30NORMAL = "normal"3132VALID_STATES = {FULLSCREEN, MAXIMIZED, MINIMIZED, NORMAL}333435class ClientWindowInfo:36"""Represents a client window information."""3738def __init__(39self,40client_window: str,41state: str,42width: int,43height: int,44x: int,45y: int,46active: bool,47):48self.client_window = client_window49self.state = state50self.width = width51self.height = height52self.x = x53self.y = y54self.active = active5556def get_state(self) -> str:57"""Gets the state of the client window.5859Returns:60str: The state of the client window (one of the ClientWindowState constants).61"""62return self.state6364def get_client_window(self) -> str:65"""Gets the client window identifier.6667Returns:68str: The client window identifier.69"""70return self.client_window7172def get_width(self) -> int:73"""Gets the width of the client window.7475Returns:76int: The width of the client window.77"""78return self.width7980def get_height(self) -> int:81"""Gets the height of the client window.8283Returns:84int: The height of the client window.85"""86return self.height8788def get_x(self) -> int:89"""Gets the x coordinate of the client window.9091Returns:92int: The x coordinate of the client window.93"""94return self.x9596def get_y(self) -> int:97"""Gets the y coordinate of the client window.9899Returns:100int: The y coordinate of the client window.101"""102return self.y103104def is_active(self) -> bool:105"""Checks if the client window is active.106107Returns:108bool: True if the client window is active, False otherwise.109"""110return self.active111112@classmethod113def from_dict(cls, data: dict) -> "ClientWindowInfo":114"""Creates a ClientWindowInfo instance from a dictionary.115116Args:117data: A dictionary containing the client window information.118119Returns:120ClientWindowInfo: A new instance of ClientWindowInfo.121122Raises:123ValueError: If required fields are missing or have invalid types.124"""125try:126client_window = data["clientWindow"]127if not isinstance(client_window, str):128raise ValueError("clientWindow must be a string")129130state = data["state"]131if not isinstance(state, str):132raise ValueError("state must be a string")133if state not in ClientWindowState.VALID_STATES:134raise ValueError(f"Invalid state: {state}. Must be one of {ClientWindowState.VALID_STATES}")135136width = data["width"]137if not isinstance(width, int) or width < 0:138raise ValueError(f"width must be a non-negative integer, got {width}")139140height = data["height"]141if not isinstance(height, int) or height < 0:142raise ValueError(f"height must be a non-negative integer, got {height}")143144x = data["x"]145if not isinstance(x, int):146raise ValueError(f"x must be an integer, got {type(x).__name__}")147148y = data["y"]149if not isinstance(y, int):150raise ValueError(f"y must be an integer, got {type(y).__name__}")151152active = data["active"]153if not isinstance(active, bool):154raise ValueError("active must be a boolean")155156return cls(157client_window=client_window,158state=state,159width=width,160height=height,161x=x,162y=y,163active=active,164)165except (KeyError, TypeError) as e:166raise ValueError(f"Invalid data format for ClientWindowInfo: {e}") from e167168169class Browser:170"""BiDi implementation of the browser module."""171172def __init__(self, conn):173self.conn = conn174175def create_user_context(176self,177accept_insecure_certs: bool | None = None,178proxy: Proxy | None = None,179unhandled_prompt_behavior: UserPromptHandler | None = None,180) -> str:181"""Creates a new user context.182183Args:184accept_insecure_certs: Optional flag to accept insecure TLS certificates.185proxy: Optional proxy configuration for the user context.186unhandled_prompt_behavior: Optional configuration for handling user prompts.187188Returns:189str: The ID of the created user context.190"""191params: dict[str, Any] = {}192193if accept_insecure_certs is not None:194params["acceptInsecureCerts"] = accept_insecure_certs195196if proxy is not None:197params["proxy"] = proxy.to_bidi_dict()198199if unhandled_prompt_behavior is not None:200params["unhandledPromptBehavior"] = unhandled_prompt_behavior.to_dict()201202result = self.conn.execute(command_builder("browser.createUserContext", params))203return result["userContext"]204205def get_user_contexts(self) -> list[str]:206"""Gets all user contexts.207208Returns:209List[str]: A list of user context IDs.210"""211result = self.conn.execute(command_builder("browser.getUserContexts", {}))212return [context_info["userContext"] for context_info in result["userContexts"]]213214def remove_user_context(self, user_context_id: str) -> None:215"""Removes a user context.216217Args:218user_context_id: The ID of the user context to remove.219220Raises:221ValueError: If the user context ID is "default" or does not exist.222"""223if user_context_id == "default":224raise ValueError("Cannot remove the default user context")225226params = {"userContext": user_context_id}227self.conn.execute(command_builder("browser.removeUserContext", params))228229def get_client_windows(self) -> list[ClientWindowInfo]:230"""Gets all client windows.231232Returns:233List[ClientWindowInfo]: A list of client window information.234"""235result = self.conn.execute(command_builder("browser.getClientWindows", {}))236return [ClientWindowInfo.from_dict(window) for window in result["clientWindows"]]237238def set_download_behavior(239self,240*,241allowed: bool | None = None,242destination_folder: str | os.PathLike | None = None,243user_contexts: list[str] | None = None,244) -> None:245"""Set the download behavior for the browser or specific user contexts.246247Args:248allowed: True to allow downloads, False to deny downloads, or None to249clear download behavior (revert to default).250destination_folder: Required when allowed is True. Specifies the folder251to store downloads in.252user_contexts: Optional list of user context IDs to apply this253behavior to. If omitted, updates the default behavior.254255Raises:256ValueError: If allowed=True and destination_folder is missing, or if257allowed=False and destination_folder is provided.258"""259params: dict[str, Any] = {}260261if allowed is None:262params["downloadBehavior"] = None263else:264if allowed:265if not destination_folder:266raise ValueError("destination_folder is required when allowed=True.")267params["downloadBehavior"] = {268"type": "allowed",269"destinationFolder": os.fspath(destination_folder),270}271else:272if destination_folder:273raise ValueError("destination_folder should not be provided when allowed=False.")274params["downloadBehavior"] = {"type": "denied"}275276if user_contexts is not None:277params["userContexts"] = user_contexts278279self.conn.execute(command_builder("browser.setDownloadBehavior", params))280281282