Path: blob/trunk/py/selenium/webdriver/remote/webdriver.py
1864 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.16"""The WebDriver implementation."""1718import base6419import contextlib20import copy21import os22import pkgutil23import tempfile24import types25import warnings26import zipfile27from abc import ABCMeta28from base64 import b64decode, urlsafe_b64encode29from contextlib import asynccontextmanager, contextmanager30from importlib import import_module31from typing import Any, Optional, Union, cast3233from selenium.common.exceptions import (34InvalidArgumentException,35JavascriptException,36NoSuchCookieException,37NoSuchElementException,38WebDriverException,39)40from selenium.webdriver.common.bidi.browser import Browser41from selenium.webdriver.common.bidi.browsing_context import BrowsingContext42from selenium.webdriver.common.bidi.emulation import Emulation43from selenium.webdriver.common.bidi.input import Input44from selenium.webdriver.common.bidi.network import Network45from selenium.webdriver.common.bidi.permissions import Permissions46from selenium.webdriver.common.bidi.script import Script47from selenium.webdriver.common.bidi.session import Session48from selenium.webdriver.common.bidi.storage import Storage49from selenium.webdriver.common.bidi.webextension import WebExtension50from selenium.webdriver.common.by import By51from selenium.webdriver.common.fedcm.dialog import Dialog52from selenium.webdriver.common.options import ArgOptions, BaseOptions53from selenium.webdriver.common.print_page_options import PrintOptions54from selenium.webdriver.common.timeouts import Timeouts55from selenium.webdriver.common.virtual_authenticator import (56Credential,57VirtualAuthenticatorOptions,58required_virtual_authenticator,59)60from selenium.webdriver.support.relative_locator import RelativeBy6162from .bidi_connection import BidiConnection63from .client_config import ClientConfig64from .command import Command65from .errorhandler import ErrorHandler66from .fedcm import FedCM67from .file_detector import FileDetector, LocalFileDetector68from .locator_converter import LocatorConverter69from .mobile import Mobile70from .remote_connection import RemoteConnection71from .script_key import ScriptKey72from .shadowroot import ShadowRoot73from .switch_to import SwitchTo74from .webelement import WebElement75from .websocket_connection import WebSocketConnection7677cdp = None787980def import_cdp():81global cdp82if not cdp:83cdp = import_module("selenium.webdriver.common.bidi.cdp")848586def _create_caps(caps):87"""Makes a W3C alwaysMatch capabilities object.8889Filters out capability names that are not in the W3C spec. Spec-compliant90drivers will reject requests containing unknown capability names.9192Moves the Firefox profile, if present, from the old location to the new Firefox93options object.9495Parameters:96-----------97caps : dict98- A dictionary of capabilities requested by the caller.99"""100caps = copy.deepcopy(caps)101always_match = {}102for k, v in caps.items():103always_match[k] = v104return {"capabilities": {"firstMatch": [{}], "alwaysMatch": always_match}}105106107def get_remote_connection(108capabilities: dict,109command_executor: Union[str, RemoteConnection],110keep_alive: bool,111ignore_local_proxy: bool,112client_config: Optional[ClientConfig] = None,113) -> RemoteConnection:114if isinstance(command_executor, str):115client_config = client_config or ClientConfig(remote_server_addr=command_executor)116client_config.remote_server_addr = command_executor117command_executor = RemoteConnection(client_config=client_config)118from selenium.webdriver.chrome.remote_connection import ChromeRemoteConnection119from selenium.webdriver.edge.remote_connection import EdgeRemoteConnection120from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection121from selenium.webdriver.safari.remote_connection import SafariRemoteConnection122123candidates = [ChromeRemoteConnection, EdgeRemoteConnection, SafariRemoteConnection, FirefoxRemoteConnection]124handler = next((c for c in candidates if c.browser_name == capabilities.get("browserName")), RemoteConnection)125126if hasattr(command_executor, "client_config") and command_executor.client_config:127remote_server_addr = command_executor.client_config.remote_server_addr128else:129remote_server_addr = command_executor130131return handler(132remote_server_addr=remote_server_addr,133keep_alive=keep_alive,134ignore_proxy=ignore_local_proxy,135client_config=client_config,136)137138139def create_matches(options: list[BaseOptions]) -> dict:140capabilities: dict[str, Any] = {"capabilities": {}}141opts = []142for opt in options:143opts.append(opt.to_capabilities())144opts_size = len(opts)145samesies = {}146147# Can not use bitwise operations on the dicts or lists due to148# https://bugs.python.org/issue38210149for i in range(opts_size):150min_index = i151if i + 1 < opts_size:152first_keys = opts[min_index].keys()153154for kys in first_keys:155if kys in opts[i + 1].keys():156if opts[min_index][kys] == opts[i + 1][kys]:157samesies.update({kys: opts[min_index][kys]})158159always = {}160for k, v in samesies.items():161always[k] = v162163for opt_dict in opts:164for k in always:165del opt_dict[k]166167capabilities["capabilities"]["alwaysMatch"] = always168capabilities["capabilities"]["firstMatch"] = opts169170return capabilities171172173class BaseWebDriver(metaclass=ABCMeta):174"""Abstract Base Class for all Webdriver subtypes.175176ABC's allow custom implementations of Webdriver to be registered so177that isinstance type checks will succeed.178"""179180181class WebDriver(BaseWebDriver):182"""Controls a browser by sending commands to a remote server. This server183is expected to be running the WebDriver wire protocol as defined at184https://www.selenium.dev/documentation/legacy/json_wire_protocol/.185186Attributes:187-----------188session_id - String ID of the browser session started and controlled by this WebDriver.189capabilities - Dictionary of effective capabilities of this browser session as returned190by the remote server. See https://www.selenium.dev/documentation/legacy/desired_capabilities/191command_executor : str or remote_connection.RemoteConnection object used to execute commands.192error_handler - errorhandler.ErrorHandler object used to handle errors.193"""194195_web_element_cls = WebElement196_shadowroot_cls = ShadowRoot197198def __init__(199self,200command_executor: Union[str, RemoteConnection] = "http://127.0.0.1:4444",201keep_alive: bool = True,202file_detector: Optional[FileDetector] = None,203options: Optional[Union[BaseOptions, list[BaseOptions]]] = None,204locator_converter: Optional[LocatorConverter] = None,205web_element_cls: Optional[type[WebElement]] = None,206client_config: Optional[ClientConfig] = None,207) -> None:208"""Create a new driver that will issue commands using the wire209protocol.210211Parameters:212-----------213command_executor : str or remote_connection.RemoteConnection214- Either a string representing the URL of the remote server or a custom215remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.216keep_alive : bool (Deprecated)217- Whether to configure remote_connection.RemoteConnection to use HTTP keep-alive. Defaults to True.218file_detector : object or None219- Pass a custom file detector object during instantiation. If None, the default220LocalFileDetector() will be used.221options : options.Options222- Instance of a driver options.Options class.223locator_converter : object or None224- Custom locator converter to use. Defaults to None.225web_element_cls : class226- Custom class to use for web elements. Defaults to WebElement.227client_config : object or None228- Custom client configuration to use. Defaults to None.229"""230231if options is None:232raise TypeError(233"missing 1 required keyword-only argument: 'options' (instance of driver `options.Options` class)"234)235elif isinstance(options, list):236capabilities = create_matches(options)237_ignore_local_proxy = False238else:239capabilities = options.to_capabilities()240_ignore_local_proxy = options._ignore_local_proxy241self.command_executor = command_executor242if isinstance(self.command_executor, (str, bytes)):243self.command_executor = get_remote_connection(244capabilities,245command_executor=command_executor,246keep_alive=keep_alive,247ignore_local_proxy=_ignore_local_proxy,248client_config=client_config,249)250self._is_remote = True251self.session_id: Optional[str] = None252self.caps: dict[str, Any] = {}253self.pinned_scripts: dict[str, Any] = {}254self.error_handler = ErrorHandler()255self._switch_to = SwitchTo(self)256self._mobile = Mobile(self)257self.file_detector = file_detector or LocalFileDetector()258self.locator_converter = locator_converter or LocatorConverter()259self._web_element_cls = web_element_cls or self._web_element_cls260self._authenticator_id = None261self.start_client()262self.start_session(capabilities)263self._fedcm = FedCM(self)264265self._websocket_connection = None266self._script = None267self._network = None268self._browser = None269self._bidi_session = None270self._browsing_context = None271self._storage = None272self._webextension = None273self._permissions = None274self._emulation = None275self._input = None276self._devtools = None277278def __repr__(self):279return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>'280281def __enter__(self):282return self283284def __exit__(285self,286exc_type: Optional[type[BaseException]],287exc: Optional[BaseException],288traceback: Optional[types.TracebackType],289):290self.quit()291292@contextmanager293def file_detector_context(self, file_detector_class, *args, **kwargs):294"""Overrides the current file detector (if necessary) in limited295context. Ensures the original file detector is set afterwards.296297Parameters:298-----------299file_detector_class : object300- Class of the desired file detector. If the class is different301from the current file_detector, then the class is instantiated with args and kwargs302and used as a file detector during the duration of the context manager.303args : tuple304- Optional arguments that get passed to the file detector class during instantiation.305kwargs : dict306- Keyword arguments, passed the same way as args.307308Example:309--------310>>> with webdriver.file_detector_context(UselessFileDetector):311>>> someinput.send_keys('/etc/hosts')312"""313last_detector = None314if not isinstance(self.file_detector, file_detector_class):315last_detector = self.file_detector316self.file_detector = file_detector_class(*args, **kwargs)317try:318yield319finally:320if last_detector:321self.file_detector = last_detector322323@property324def mobile(self) -> Mobile:325return self._mobile326327@property328def name(self) -> str:329"""Returns the name of the underlying browser for this instance.330331Example:332--------333>>> name = driver.name334"""335if "browserName" in self.caps:336return self.caps["browserName"]337raise KeyError("browserName not specified in session capabilities")338339def start_client(self):340"""Called before starting a new session.341342This method may be overridden to define custom startup behavior.343"""344pass345346def stop_client(self):347"""Called after executing a quit command.348349This method may be overridden to define custom shutdown350behavior.351"""352pass353354def start_session(self, capabilities: dict) -> None:355"""Creates a new session with the desired capabilities.356357Parameters:358-----------359capabilities : dict360- A capabilities dict to start the session with.361"""362363caps = _create_caps(capabilities)364try:365response = self.execute(Command.NEW_SESSION, caps)["value"]366self.session_id = response.get("sessionId")367self.caps = response.get("capabilities")368except Exception:369if hasattr(self, "service") and self.service is not None:370self.service.stop()371raise372373def _wrap_value(self, value):374if isinstance(value, dict):375converted = {}376for key, val in value.items():377converted[key] = self._wrap_value(val)378return converted379if isinstance(value, self._web_element_cls):380return {"element-6066-11e4-a52e-4f735466cecf": value.id}381if isinstance(value, self._shadowroot_cls):382return {"shadow-6066-11e4-a52e-4f735466cecf": value.id}383if isinstance(value, list):384return list(self._wrap_value(item) for item in value)385return value386387def create_web_element(self, element_id: str) -> WebElement:388"""Creates a web element with the specified `element_id`."""389return self._web_element_cls(self, element_id)390391def _unwrap_value(self, value):392if isinstance(value, dict):393if "element-6066-11e4-a52e-4f735466cecf" in value:394return self.create_web_element(value["element-6066-11e4-a52e-4f735466cecf"])395if "shadow-6066-11e4-a52e-4f735466cecf" in value:396return self._shadowroot_cls(self, value["shadow-6066-11e4-a52e-4f735466cecf"])397for key, val in value.items():398value[key] = self._unwrap_value(val)399return value400if isinstance(value, list):401return list(self._unwrap_value(item) for item in value)402return value403404def execute_cdp_cmd(self, cmd: str, cmd_args: dict):405"""Execute Chrome Devtools Protocol command and get returned result The406command and command args should follow chrome devtools protocol407domains/commands, refer to link408https://chromedevtools.github.io/devtools-protocol/409410Parameters:411-----------412cmd : str,413- Command name414415cmd_args : dict416- Command args417- Empty dict {} if there is no command args418419Returns:420--------421A dict, empty dict {} if there is no result to return.422- To getResponseBody: {'base64Encoded': False, 'body': 'response body string'}423424Example:425--------426>>> driver.execute_cdp_cmd("Network.getResponseBody", {"requestId": requestId})427428"""429return self.execute("executeCdpCommand", {"cmd": cmd, "params": cmd_args})["value"]430431def execute(self, driver_command: str, params: Optional[dict[str, Any]] = None) -> dict[str, Any]:432"""Sends a command to be executed by a command.CommandExecutor.433434Parameters:435-----------436driver_command : str437- The name of the command to execute as a string.438439params : dict440- A dictionary of named Parameters to send with the command.441442Returns:443--------444dict - The command's JSON response loaded into a dictionary object.445"""446params = self._wrap_value(params)447448if self.session_id:449if not params:450params = {"sessionId": self.session_id}451elif "sessionId" not in params:452params["sessionId"] = self.session_id453454response = cast(RemoteConnection, self.command_executor).execute(driver_command, params)455456if response:457self.error_handler.check_response(response)458response["value"] = self._unwrap_value(response.get("value", None))459return response460# If the server doesn't send a response, assume the command was461# a success462return {"success": 0, "value": None, "sessionId": self.session_id}463464def get(self, url: str) -> None:465"""Navigate the browser to the specified URL in the current window or466tab.467468The method does not return until the page is fully loaded (i.e. the469onload event has fired).470471Parameters:472-----------473url : str474- The URL to be opened by the browser.475- Must include the protocol (e.g., http://, https://).476477Example:478--------479>>> driver = webdriver.Chrome()480>>> driver.get("https://example.com")481"""482self.execute(Command.GET, {"url": url})483484@property485def title(self) -> str:486"""Returns the title of the current page.487488Example:489--------490>>> element = driver.find_element(By.ID, "foo")491>>> print(element.title())492"""493return self.execute(Command.GET_TITLE).get("value", "")494495def pin_script(self, script: str, script_key=None) -> ScriptKey:496"""Store common javascript scripts to be executed later by a unique497hashable ID.498499Example:500--------501>>> script = "return document.getElementById('foo').value"502"""503script_key_instance = ScriptKey(script_key)504self.pinned_scripts[script_key_instance.id] = script505return script_key_instance506507def unpin(self, script_key: ScriptKey) -> None:508"""Remove a pinned script from storage.509510Example:511--------512>>> driver.unpin(script_key)513"""514try:515self.pinned_scripts.pop(script_key.id)516except KeyError:517raise KeyError(f"No script with key: {script_key} existed in {self.pinned_scripts}") from None518519def get_pinned_scripts(self) -> list[str]:520"""Return a list of all pinned scripts.521522Example:523--------524>>> pinned_scripts = driver.get_pinned_scripts()525"""526return list(self.pinned_scripts)527528def execute_script(self, script: str, *args):529"""Synchronously Executes JavaScript in the current window/frame.530531Parameters:532-----------533script : str534- The javascript to execute.535536*args : tuple537- Any applicable arguments for your JavaScript.538539Example:540--------541>>> input_id = "username"542>>> input_value = "test_user"543>>> driver.execute_script("document.getElementById(arguments[0]).value = arguments[1];", input_id, input_value)544"""545if isinstance(script, ScriptKey):546try:547script = self.pinned_scripts[script.id]548except KeyError:549raise JavascriptException("Pinned script could not be found")550551converted_args = list(args)552command = Command.W3C_EXECUTE_SCRIPT553554return self.execute(command, {"script": script, "args": converted_args})["value"]555556def execute_async_script(self, script: str, *args):557"""Asynchronously Executes JavaScript in the current window/frame.558559Parameters:560-----------561script : str562- The javascript to execute.563564*args : tuple565- Any applicable arguments for your JavaScript.566567Example:568--------569>>> script = "var callback = arguments[arguments.length - 1]; "570... "window.setTimeout(function(){ callback('timeout') }, 3000);"571>>> driver.execute_async_script(script)572"""573converted_args = list(args)574command = Command.W3C_EXECUTE_SCRIPT_ASYNC575576return self.execute(command, {"script": script, "args": converted_args})["value"]577578@property579def current_url(self) -> str:580"""Gets the URL of the current page.581582Example:583--------584>>> print(driver.current_url)585"""586return self.execute(Command.GET_CURRENT_URL)["value"]587588@property589def page_source(self) -> str:590"""Gets the source of the current page.591592Example:593--------594>>> print(driver.page_source)595"""596return self.execute(Command.GET_PAGE_SOURCE)["value"]597598def close(self) -> None:599"""Closes the current window.600601Example:602--------603>>> driver.close()604"""605self.execute(Command.CLOSE)606607def quit(self) -> None:608"""Quits the driver and closes every associated window.609610Example:611--------612>>> driver.quit()613"""614try:615self.execute(Command.QUIT)616finally:617self.stop_client()618executor = cast(RemoteConnection, self.command_executor)619executor.close()620621@property622def current_window_handle(self) -> str:623"""Returns the handle of the current window.624625Example:626--------627>>> print(driver.current_window_handle)628"""629return self.execute(Command.W3C_GET_CURRENT_WINDOW_HANDLE)["value"]630631@property632def window_handles(self) -> list[str]:633"""Returns the handles of all windows within the current session.634635Example:636--------637>>> print(driver.window_handles)638"""639return self.execute(Command.W3C_GET_WINDOW_HANDLES)["value"]640641def maximize_window(self) -> None:642"""Maximizes the current window that webdriver is using.643644Example:645--------646>>> driver.maximize_window()647"""648command = Command.W3C_MAXIMIZE_WINDOW649self.execute(command, None)650651def fullscreen_window(self) -> None:652"""Invokes the window manager-specific 'full screen' operation.653654Example:655--------656>>> driver.fullscreen_window()657"""658self.execute(Command.FULLSCREEN_WINDOW)659660def minimize_window(self) -> None:661"""Invokes the window manager-specific 'minimize' operation."""662self.execute(Command.MINIMIZE_WINDOW)663664def print_page(self, print_options: Optional[PrintOptions] = None) -> str:665"""Takes PDF of the current page.666667The driver makes a best effort to return a PDF based on the668provided Parameters.669670Example:671--------672>>> driver.print_page()673"""674options: Union[dict[str, Any], Any] = {}675if print_options:676options = print_options.to_dict()677678return self.execute(Command.PRINT_PAGE, options)["value"]679680@property681def switch_to(self) -> SwitchTo:682"""Return an object containing all options to switch focus into.683684Returns:685--------686SwitchTo: an object containing all options to switch focus into.687688Examples:689--------690>>> element = driver.switch_to.active_element691>>> alert = driver.switch_to.alert692>>> driver.switch_to.default_content()693>>> driver.switch_to.frame("frame_name")694>>> driver.switch_to.frame(1)695>>> driver.switch_to.frame(driver.find_elements(By.TAG_NAME, "iframe")[0])696>>> driver.switch_to.parent_frame()697>>> driver.switch_to.window("main")698"""699return self._switch_to700701# Navigation702def back(self) -> None:703"""Goes one step backward in the browser history.704705Example:706--------707>>> driver.back()708"""709self.execute(Command.GO_BACK)710711def forward(self) -> None:712"""Goes one step forward in the browser history.713714Example:715--------716>>> driver.forward()717"""718self.execute(Command.GO_FORWARD)719720def refresh(self) -> None:721"""Refreshes the current page.722723Example:724--------725>>> driver.refresh()726"""727self.execute(Command.REFRESH)728729# Options730def get_cookies(self) -> list[dict]:731"""Returns a set of dictionaries, corresponding to cookies visible in732the current session.733734Returns:735--------736cookies:List[dict] : A list of dictionaries, corresponding to cookies visible in the current737738Example:739--------740>>> cookies = driver.get_cookies()741"""742return self.execute(Command.GET_ALL_COOKIES)["value"]743744def get_cookie(self, name) -> Optional[dict]:745"""Get a single cookie by name. Raises ValueError if the name is empty746or whitespace. Returns the cookie if found, None if not.747748Example:749--------750>>> cookie = driver.get_cookie("my_cookie")751"""752if not name or name.isspace():753raise ValueError("Cookie name cannot be empty")754755with contextlib.suppress(NoSuchCookieException):756return self.execute(Command.GET_COOKIE, {"name": name})["value"]757758return None759760def delete_cookie(self, name) -> None:761"""Deletes a single cookie with the given name. Raises ValueError if762the name is empty or whitespace.763764Example:765--------766>>> driver.delete_cookie("my_cookie")767"""768769# firefox deletes all cookies when "" is passed as name770if not name or name.isspace():771raise ValueError("Cookie name cannot be empty")772773self.execute(Command.DELETE_COOKIE, {"name": name})774775def delete_all_cookies(self) -> None:776"""Delete all cookies in the scope of the session.777778Example:779--------780>>> driver.delete_all_cookies()781"""782self.execute(Command.DELETE_ALL_COOKIES)783784def add_cookie(self, cookie_dict) -> None:785"""Adds a cookie to your current session.786787Parameters:788-----------789cookie_dict : dict790- A dictionary object, with required keys - "name" and "value";791- Optional keys - "path", "domain", "secure", "httpOnly", "expiry", "sameSite"792793Examples:794--------795>>> driver.add_cookie({"name": "foo", "value": "bar"})796>>> driver.add_cookie({"name": "foo", "value": "bar", "path": "/"})797>>> driver.add_cookie({"name": "foo", "value": "bar", "path": "/", "secure": True})798>>> driver.add_cookie({"name": "foo", "value": "bar", "sameSite": "Strict"})799"""800if "sameSite" in cookie_dict:801assert cookie_dict["sameSite"] in ["Strict", "Lax", "None"]802self.execute(Command.ADD_COOKIE, {"cookie": cookie_dict})803else:804self.execute(Command.ADD_COOKIE, {"cookie": cookie_dict})805806# Timeouts807def implicitly_wait(self, time_to_wait: float) -> None:808"""Sets a sticky timeout to implicitly wait for an element to be found,809or a command to complete. This method only needs to be called one time810per session. To set the timeout for calls to execute_async_script, see811set_script_timeout.812813Parameters:814-----------815time_to_wait : float816- Amount of time to wait (in seconds)817818Example:819--------820>>> driver.implicitly_wait(30)821"""822self.execute(Command.SET_TIMEOUTS, {"implicit": int(float(time_to_wait) * 1000)})823824def set_script_timeout(self, time_to_wait: float) -> None:825"""Set the amount of time that the script should wait during an826execute_async_script call before throwing an error.827828Parameters:829-----------830time_to_wait : float831- The amount of time to wait (in seconds)832833Example:834--------835>>> driver.set_script_timeout(30)836"""837self.execute(Command.SET_TIMEOUTS, {"script": int(float(time_to_wait) * 1000)})838839def set_page_load_timeout(self, time_to_wait: float) -> None:840"""Set the amount of time to wait for a page load to complete before841throwing an error.842843Parameters:844-----------845time_to_wait : float846- The amount of time to wait (in seconds)847848Example:849--------850>>> driver.set_page_load_timeout(30)851"""852try:853self.execute(Command.SET_TIMEOUTS, {"pageLoad": int(float(time_to_wait) * 1000)})854except WebDriverException:855self.execute(Command.SET_TIMEOUTS, {"ms": float(time_to_wait) * 1000, "type": "page load"})856857@property858def timeouts(self) -> Timeouts:859"""Get all the timeouts that have been set on the current session.860861Returns:862--------863Timeouts: A named tuple with the following fields:864- implicit_wait: The time to wait for elements to be found.865- page_load: The time to wait for a page to load.866- script: The time to wait for scripts to execute.867868Example:869--------870>>> driver.timeouts871"""872timeouts = self.execute(Command.GET_TIMEOUTS)["value"]873timeouts["implicit_wait"] = timeouts.pop("implicit") / 1000874timeouts["page_load"] = timeouts.pop("pageLoad") / 1000875timeouts["script"] = timeouts.pop("script") / 1000876return Timeouts(**timeouts)877878@timeouts.setter879def timeouts(self, timeouts) -> None:880"""Set all timeouts for the session. This will override any previously881set timeouts.882883Example:884--------885>>> my_timeouts = Timeouts()886>>> my_timeouts.implicit_wait = 10887>>> driver.timeouts = my_timeouts888"""889_ = self.execute(Command.SET_TIMEOUTS, timeouts._to_json())["value"]890891def find_element(self, by=By.ID, value: Optional[str] = None) -> WebElement:892"""Find an element given a By strategy and locator.893894Parameters:895-----------896by : selenium.webdriver.common.by.By897The locating strategy to use. Default is `By.ID`. Supported values include:898- By.ID: Locate by element ID.899- By.NAME: Locate by the `name` attribute.900- By.XPATH: Locate by an XPath expression.901- By.CSS_SELECTOR: Locate by a CSS selector.902- By.CLASS_NAME: Locate by the `class` attribute.903- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").904- By.LINK_TEXT: Locate a link element by its exact text.905- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.906- RelativeBy: Locate elements relative to a specified root element.907908Example:909--------910element = driver.find_element(By.ID, 'foo')911912Returns:913-------914WebElement915The first matching `WebElement` found on the page.916"""917by, value = self.locator_converter.convert(by, value)918919if isinstance(by, RelativeBy):920elements = self.find_elements(by=by, value=value)921if not elements:922raise NoSuchElementException(f"Cannot locate relative element with: {by.root}")923return elements[0]924925return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"]926927def find_elements(self, by=By.ID, value: Optional[str] = None) -> list[WebElement]:928"""Find elements given a By strategy and locator.929930Parameters:931-----------932by : selenium.webdriver.common.by.By933The locating strategy to use. Default is `By.ID`. Supported values include:934- By.ID: Locate by element ID.935- By.NAME: Locate by the `name` attribute.936- By.XPATH: Locate by an XPath expression.937- By.CSS_SELECTOR: Locate by a CSS selector.938- By.CLASS_NAME: Locate by the `class` attribute.939- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").940- By.LINK_TEXT: Locate a link element by its exact text.941- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.942- RelativeBy: Locate elements relative to a specified root element.943944Example:945--------946element = driver.find_elements(By.ID, 'foo')947948Returns:949-------950List[WebElement]951list of `WebElements` matching locator strategy found on the page.952"""953by, value = self.locator_converter.convert(by, value)954955if isinstance(by, RelativeBy):956_pkg = ".".join(__name__.split(".")[:-1])957raw_data = pkgutil.get_data(_pkg, "findElements.js")958if raw_data is None:959raise FileNotFoundError(f"Could not find findElements.js in package {_pkg}")960raw_function = raw_data.decode("utf8")961find_element_js = f"/* findElements */return ({raw_function}).apply(null, arguments);"962return self.execute_script(find_element_js, by.to_dict())963964# Return empty list if driver returns null965# See https://github.com/SeleniumHQ/selenium/issues/4555966return self.execute(Command.FIND_ELEMENTS, {"using": by, "value": value})["value"] or []967968@property969def capabilities(self) -> dict:970"""Returns the drivers current capabilities being used.971972Example:973--------974>>> print(driver.capabilities)975"""976return self.caps977978def get_screenshot_as_file(self, filename) -> bool:979"""Saves a screenshot of the current window to a PNG image file.980Returns False if there is any IOError, else returns True. Use full981paths in your filename.982983Parameters:984-----------985filename : str986- The full path you wish to save your screenshot to. This987- should end with a `.png` extension.988989Example:990--------991>>> driver.get_screenshot_as_file("/Screenshots/foo.png")992"""993if not str(filename).lower().endswith(".png"):994warnings.warn(995"name used for saved screenshot does not match file type. It should end with a `.png` extension",996UserWarning,997stacklevel=2,998)999png = self.get_screenshot_as_png()1000try:1001with open(filename, "wb") as f:1002f.write(png)1003except OSError:1004return False1005finally:1006del png1007return True10081009def save_screenshot(self, filename) -> bool:1010"""Saves a screenshot of the current window to a PNG image file.1011Returns False if there is any IOError, else returns True. Use full1012paths in your filename.10131014Parameters:1015-----------1016filename : str1017- The full path you wish to save your screenshot to. This1018- should end with a `.png` extension.10191020Example:1021--------1022>>> driver.save_screenshot("/Screenshots/foo.png")1023"""1024return self.get_screenshot_as_file(filename)10251026def get_screenshot_as_png(self) -> bytes:1027"""Gets the screenshot of the current window as a binary data.10281029Example:1030--------1031>>> driver.get_screenshot_as_png()1032"""1033return b64decode(self.get_screenshot_as_base64().encode("ascii"))10341035def get_screenshot_as_base64(self) -> str:1036"""Gets the screenshot of the current window as a base64 encoded string1037which is useful in embedded images in HTML.10381039Example:1040--------1041>>> driver.get_screenshot_as_base64()1042"""1043return self.execute(Command.SCREENSHOT)["value"]10441045def set_window_size(self, width, height, windowHandle: str = "current") -> None:1046"""Sets the width and height of the current window. (window.resizeTo)10471048Parameters:1049-----------1050width : int1051- the width in pixels to set the window to10521053height : int1054- the height in pixels to set the window to10551056Example:1057--------1058>>> driver.set_window_size(800, 600)1059"""1060self._check_if_window_handle_is_current(windowHandle)1061self.set_window_rect(width=int(width), height=int(height))10621063def get_window_size(self, windowHandle: str = "current") -> dict:1064"""Gets the width and height of the current window.10651066Example:1067--------1068>>> driver.get_window_size()1069"""10701071self._check_if_window_handle_is_current(windowHandle)1072size = self.get_window_rect()10731074if size.get("value", None):1075size = size["value"]10761077return {k: size[k] for k in ("width", "height")}10781079def set_window_position(self, x: float, y: float, windowHandle: str = "current") -> dict:1080"""Sets the x,y position of the current window. (window.moveTo)10811082Parameters:1083---------1084x : float1085- The x-coordinate in pixels to set the window position10861087y : float1088- The y-coordinate in pixels to set the window position10891090Example:1091--------1092>>> driver.set_window_position(0, 0)1093"""1094self._check_if_window_handle_is_current(windowHandle)1095return self.set_window_rect(x=int(x), y=int(y))10961097def get_window_position(self, windowHandle="current") -> dict:1098"""Gets the x,y position of the current window.10991100Example:1101--------1102>>> driver.get_window_position()1103"""11041105self._check_if_window_handle_is_current(windowHandle)1106position = self.get_window_rect()11071108return {k: position[k] for k in ("x", "y")}11091110def _check_if_window_handle_is_current(self, windowHandle: str) -> None:1111"""Warns if the window handle is not equal to `current`."""1112if windowHandle != "current":1113warnings.warn("Only 'current' window is supported for W3C compatible browsers.", stacklevel=2)11141115def get_window_rect(self) -> dict:1116"""Gets the x, y coordinates of the window as well as height and width1117of the current window.11181119Example:1120--------1121>>> driver.get_window_rect()1122"""1123return self.execute(Command.GET_WINDOW_RECT)["value"]11241125def set_window_rect(self, x=None, y=None, width=None, height=None) -> dict:1126"""Sets the x, y coordinates of the window as well as height and width1127of the current window. This method is only supported for W3C compatible1128browsers; other browsers should use `set_window_position` and1129`set_window_size`.11301131Example:1132--------1133>>> driver.set_window_rect(x=10, y=10)1134>>> driver.set_window_rect(width=100, height=200)1135>>> driver.set_window_rect(x=10, y=10, width=100, height=200)1136"""11371138if (x is None and y is None) and (not height and not width):1139raise InvalidArgumentException("x and y or height and width need values")11401141return self.execute(Command.SET_WINDOW_RECT, {"x": x, "y": y, "width": width, "height": height})["value"]11421143@property1144def file_detector(self) -> FileDetector:1145return self._file_detector11461147@file_detector.setter1148def file_detector(self, detector) -> None:1149"""Set the file detector to be used when sending keyboard input. By1150default, this is set to a file detector that does nothing.11511152- see FileDetector1153- see LocalFileDetector1154- see UselessFileDetector11551156Parameters:1157-----------1158detector : Any1159- The detector to use. Must not be None.1160"""1161if not detector:1162raise WebDriverException("You may not set a file detector that is null")1163if not isinstance(detector, FileDetector):1164raise WebDriverException("Detector has to be instance of FileDetector")1165self._file_detector = detector11661167@property1168def orientation(self):1169"""Gets the current orientation of the device.11701171Example:1172--------1173>>> orientation = driver.orientation1174"""1175return self.execute(Command.GET_SCREEN_ORIENTATION)["value"]11761177@orientation.setter1178def orientation(self, value) -> None:1179"""Sets the current orientation of the device.11801181Parameters:1182-----------1183value : str1184- orientation to set it to.11851186Example:1187--------1188>>> driver.orientation = "landscape"1189"""1190allowed_values = ["LANDSCAPE", "PORTRAIT"]1191if value.upper() in allowed_values:1192self.execute(Command.SET_SCREEN_ORIENTATION, {"orientation": value})1193else:1194raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'")11951196def start_devtools(self):1197global cdp1198import_cdp()1199if self.caps.get("se:cdp"):1200ws_url = self.caps.get("se:cdp")1201version = self.caps.get("se:cdpVersion").split(".")[0]1202else:1203version, ws_url = self._get_cdp_details()12041205if not ws_url:1206raise WebDriverException("Unable to find url to connect to from capabilities")12071208self._devtools = cdp.import_devtools(version)1209if self._websocket_connection:1210return self._devtools, self._websocket_connection1211if self.caps["browserName"].lower() == "firefox":1212raise RuntimeError("CDP support for Firefox has been removed. Please switch to WebDriver BiDi.")1213self._websocket_connection = WebSocketConnection(ws_url)1214targets = self._websocket_connection.execute(self._devtools.target.get_targets())1215for target in targets:1216if target.target_id == self.current_window_handle:1217target_id = target.target_id1218break1219session = self._websocket_connection.execute(self._devtools.target.attach_to_target(target_id, True))1220self._websocket_connection.session_id = session1221return self._devtools, self._websocket_connection12221223@asynccontextmanager1224async def bidi_connection(self):1225global cdp1226import_cdp()1227if self.caps.get("se:cdp"):1228ws_url = self.caps.get("se:cdp")1229version = self.caps.get("se:cdpVersion").split(".")[0]1230else:1231version, ws_url = self._get_cdp_details()12321233if not ws_url:1234raise WebDriverException("Unable to find url to connect to from capabilities")12351236devtools = cdp.import_devtools(version)1237async with cdp.open_cdp(ws_url) as conn:1238targets = await conn.execute(devtools.target.get_targets())1239for target in targets:1240if target.target_id == self.current_window_handle:1241target_id = target.target_id1242break1243async with conn.open_session(target_id) as session:1244yield BidiConnection(session, cdp, devtools)12451246@property1247def script(self):1248if not self._websocket_connection:1249self._start_bidi()12501251if not self._script:1252self._script = Script(self._websocket_connection, self)12531254return self._script12551256def _start_bidi(self):1257if self.caps.get("webSocketUrl"):1258ws_url = self.caps.get("webSocketUrl")1259else:1260raise WebDriverException("Unable to find url to connect to from capabilities")12611262self._websocket_connection = WebSocketConnection(ws_url)12631264@property1265def network(self):1266if not self._websocket_connection:1267self._start_bidi()12681269if not hasattr(self, "_network") or self._network is None:1270self._network = Network(self._websocket_connection)12711272return self._network12731274@property1275def browser(self):1276"""Returns a browser module object for BiDi browser commands.12771278Returns:1279--------1280Browser: an object containing access to BiDi browser commands.12811282Examples: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"""1289if not self._websocket_connection:1290self._start_bidi()12911292if self._browser is None:1293self._browser = Browser(self._websocket_connection)12941295return self._browser12961297@property1298def _session(self):1299"""Returns the BiDi session object for the current WebDriver1300session."""1301if not self._websocket_connection:1302self._start_bidi()13031304if self._bidi_session is None:1305self._bidi_session = Session(self._websocket_connection)13061307return self._bidi_session13081309@property1310def browsing_context(self):1311"""Returns a browsing context module object for BiDi browsing context1312commands.13131314Returns:1315--------1316BrowsingContext: an object containing access to BiDi browsing context commands.13171318Examples:1319---------1320>>> context_id = driver.browsing_context.create(type="tab")1321>>> driver.browsing_context.navigate(context=context_id, url="https://www.selenium.dev")1322>>> driver.browsing_context.capture_screenshot(context=context_id)1323>>> driver.browsing_context.close(context_id)1324"""1325if not self._websocket_connection:1326self._start_bidi()13271328if self._browsing_context is None:1329self._browsing_context = BrowsingContext(self._websocket_connection)13301331return self._browsing_context13321333@property1334def storage(self):1335"""Returns a storage module object for BiDi storage commands.13361337Returns:1338--------1339Storage: an object containing access to BiDi storage commands.13401341Examples:1342---------1343>>> cookie_filter = CookieFilter(name="example")1344>>> result = driver.storage.get_cookies(filter=cookie_filter)1345>>> driver.storage.set_cookie(cookie=PartialCookie(1346"name", BytesValue(BytesValue.TYPE_STRING, "value"), "domain")1347)1348>>> driver.storage.delete_cookies(filter=CookieFilter(name="example"))1349"""1350if not self._websocket_connection:1351self._start_bidi()13521353if self._storage is None:1354self._storage = Storage(self._websocket_connection)13551356return self._storage13571358@property1359def permissions(self):1360"""Returns a permissions module object for BiDi permissions commands.13611362Returns:1363--------1364Permissions: an object containing access to BiDi permissions commands.13651366Examples:1367---------1368>>> from selenium.webdriver.common.bidi.permissions import PermissionDescriptor, PermissionState1369>>> descriptor = PermissionDescriptor("geolocation")1370>>> driver.permissions.set_permission(descriptor, PermissionState.GRANTED, "https://example.com")1371"""1372if not self._websocket_connection:1373self._start_bidi()13741375if self._permissions is None:1376self._permissions = Permissions(self._websocket_connection)13771378return self._permissions13791380@property1381def webextension(self):1382"""Returns a webextension module object for BiDi webextension commands.13831384Returns:1385--------1386WebExtension: an object containing access to BiDi webextension commands.13871388Examples:1389---------1390>>> extension_path = "/path/to/extension"1391>>> extension_result = driver.webextension.install(path=extension_path)1392>>> driver.webextension.uninstall(extension_result)1393"""1394if not self._websocket_connection:1395self._start_bidi()13961397if self._webextension is None:1398self._webextension = WebExtension(self._websocket_connection)13991400return self._webextension14011402@property1403def emulation(self):1404"""Returns an emulation module object for BiDi emulation commands.14051406Returns:1407--------1408Emulation: an object containing access to BiDi emulation commands.14091410Examples:1411---------1412>>> from selenium.webdriver.common.bidi.emulation import GeolocationCoordinates1413>>> coordinates = GeolocationCoordinates(37.7749, -122.4194)1414>>> driver.emulation.set_geolocation_override(coordinates=coordinates, contexts=[context_id])1415"""1416if not self._websocket_connection:1417self._start_bidi()14181419if self._emulation is None:1420self._emulation = Emulation(self._websocket_connection)14211422return self._emulation14231424@property1425def input(self):1426"""Returns an input module object for BiDi input commands.14271428Returns:1429--------1430Input: an object containing access to BiDi input commands.14311432Examples:1433---------1434>>> from selenium.webdriver.common.bidi.input import KeySourceActions, KeyDownAction, KeyUpAction1435>>> key_actions = KeySourceActions(id="keyboard", actions=[KeyDownAction(value="a"), KeyUpAction(value="a")])1436>>> driver.input.perform_actions(driver.current_window_handle, [key_actions])1437>>> driver.input.release_actions(driver.current_window_handle)1438"""1439if not self._websocket_connection:1440self._start_bidi()14411442if self._input is None:1443self._input = Input(self._websocket_connection)14441445return self._input14461447def _get_cdp_details(self):1448import json14491450import urllib314511452http = urllib3.PoolManager()1453try:1454if self.caps.get("browserName") == "chrome":1455debugger_address = self.caps.get("goog:chromeOptions").get("debuggerAddress")1456elif self.caps.get("browserName") in ("MicrosoftEdge", "webview2"):1457debugger_address = self.caps.get("ms:edgeOptions").get("debuggerAddress")1458except AttributeError:1459raise WebDriverException("Can't get debugger address.")14601461res = http.request("GET", f"http://{debugger_address}/json/version")1462data = json.loads(res.data)14631464browser_version = data.get("Browser")1465websocket_url = data.get("webSocketDebuggerUrl")14661467import re14681469version = re.search(r".*/(\d+)\.", browser_version).group(1)14701471return version, websocket_url14721473# Virtual Authenticator Methods1474def add_virtual_authenticator(self, options: VirtualAuthenticatorOptions) -> None:1475"""Adds a virtual authenticator with the given options.14761477Example:1478--------1479>>> from selenium.webdriver.common.virtual_authenticator import VirtualAuthenticatorOptions1480>>> options = VirtualAuthenticatorOptions(protocol="u2f", transport="usb", device_id="myDevice123")1481>>> driver.add_virtual_authenticator(options)1482"""1483self._authenticator_id = self.execute(Command.ADD_VIRTUAL_AUTHENTICATOR, options.to_dict())["value"]14841485@property1486def virtual_authenticator_id(self) -> Optional[str]:1487"""Returns the id of the virtual authenticator.14881489Example:1490--------1491>>> print(driver.virtual_authenticator_id)1492"""1493return self._authenticator_id14941495@required_virtual_authenticator1496def remove_virtual_authenticator(self) -> None:1497"""Removes a previously added virtual authenticator.14981499The authenticator is no longer valid after removal, so no1500methods may be called.15011502Example:1503--------1504>>> driver.remove_virtual_authenticator()1505"""1506self.execute(Command.REMOVE_VIRTUAL_AUTHENTICATOR, {"authenticatorId": self._authenticator_id})1507self._authenticator_id = None15081509@required_virtual_authenticator1510def add_credential(self, credential: Credential) -> None:1511"""Injects a credential into the authenticator.15121513Example:1514--------1515>>> from selenium.webdriver.common.credential import Credential1516>>> credential = Credential(id="[email protected]", password="aPassword")1517>>> driver.add_credential(credential)1518"""1519self.execute(Command.ADD_CREDENTIAL, {**credential.to_dict(), "authenticatorId": self._authenticator_id})15201521@required_virtual_authenticator1522def get_credentials(self) -> list[Credential]:1523"""Returns the list of credentials owned by the authenticator.15241525Example:1526--------1527>>> credentials = driver.get_credentials()1528"""1529credential_data = self.execute(Command.GET_CREDENTIALS, {"authenticatorId": self._authenticator_id})1530return [Credential.from_dict(credential) for credential in credential_data["value"]]15311532@required_virtual_authenticator1533def remove_credential(self, credential_id: Union[str, bytearray]) -> None:1534"""Removes a credential from the authenticator.15351536Example:1537--------1538>>> credential_id = "[email protected]"1539>>> driver.remove_credential(credential_id)1540"""1541# Check if the credential is bytearray converted to b64 string1542if isinstance(credential_id, bytearray):1543credential_id = urlsafe_b64encode(credential_id).decode()15441545self.execute(1546Command.REMOVE_CREDENTIAL, {"credentialId": credential_id, "authenticatorId": self._authenticator_id}1547)15481549@required_virtual_authenticator1550def remove_all_credentials(self) -> None:1551"""Removes all credentials from the authenticator.15521553Example:1554--------1555>>> driver.remove_all_credentials()1556"""1557self.execute(Command.REMOVE_ALL_CREDENTIALS, {"authenticatorId": self._authenticator_id})15581559@required_virtual_authenticator1560def set_user_verified(self, verified: bool) -> None:1561"""Sets whether the authenticator will simulate success or fail on user1562verification.15631564Parameters:1565-----------1566verified: True if the authenticator will pass user verification, False otherwise.15671568Example:1569--------1570>>> driver.set_user_verified(True)1571"""1572self.execute(Command.SET_USER_VERIFIED, {"authenticatorId": self._authenticator_id, "isUserVerified": verified})15731574def get_downloadable_files(self) -> list:1575"""Retrieves the downloadable files as a list of file names.15761577Example:1578--------1579>>> files = driver.get_downloadable_files()1580"""1581if "se:downloadsEnabled" not in self.capabilities:1582raise WebDriverException("You must enable downloads in order to work with downloadable files.")15831584return self.execute(Command.GET_DOWNLOADABLE_FILES)["value"]["names"]15851586def download_file(self, file_name: str, target_directory: str) -> None:1587"""Downloads a file with the specified file name to the target1588directory.15891590Parameters:1591-----------1592file_name : str1593- The name of the file to download.15941595target_directory : str1596- The path to the directory to save the downloaded file.15971598Example:1599--------1600>>> driver.download_file("example.zip", "/path/to/directory")1601"""1602if "se:downloadsEnabled" not in self.capabilities:1603raise WebDriverException("You must enable downloads in order to work with downloadable files.")16041605if not os.path.exists(target_directory):1606os.makedirs(target_directory)16071608contents = self.execute(Command.DOWNLOAD_FILE, {"name": file_name})["value"]["contents"]16091610with tempfile.TemporaryDirectory() as tmp_dir:1611zip_file = os.path.join(tmp_dir, file_name + ".zip")1612with open(zip_file, "wb") as file:1613file.write(base64.b64decode(contents))16141615with zipfile.ZipFile(zip_file, "r") as zip_ref:1616zip_ref.extractall(target_directory)16171618def delete_downloadable_files(self) -> None:1619"""Deletes all downloadable files.16201621Example:1622--------1623>>> driver.delete_downloadable_files()1624"""1625if "se:downloadsEnabled" not in self.capabilities:1626raise WebDriverException("You must enable downloads in order to work with downloadable files.")16271628self.execute(Command.DELETE_DOWNLOADABLE_FILES)16291630@property1631def fedcm(self) -> FedCM:1632"""Returns the Federated Credential Management (FedCM) dialog object1633for interaction.16341635Returns:1636-------1637FedCM: an object providing access to all Federated Credential Management (FedCM) dialog commands.16381639Examples:1640--------1641>>> title = driver.fedcm.title1642>>> subtitle = driver.fedcm.subtitle1643>>> dialog_type = driver.fedcm.dialog_type1644>>> accounts = driver.fedcm.account_list1645>>> driver.fedcm.select_account(0)1646>>> driver.fedcm.accept()1647>>> driver.fedcm.dismiss()1648>>> driver.fedcm.enable_delay()1649>>> driver.fedcm.disable_delay()1650>>> driver.fedcm.reset_cooldown()1651"""1652return self._fedcm16531654@property1655def supports_fedcm(self) -> bool:1656"""Returns whether the browser supports FedCM capabilities.16571658Example:1659--------1660>>> print(driver.supports_fedcm)1661"""1662return self.capabilities.get(ArgOptions.FEDCM_CAPABILITY, False)16631664def _require_fedcm_support(self):1665"""Raises an exception if FedCM is not supported."""1666if not self.supports_fedcm:1667raise WebDriverException(1668"This browser does not support Federated Credential Management. "1669"Please ensure you're using a supported browser."1670)16711672@property1673def dialog(self):1674"""Returns the FedCM dialog object for interaction.16751676Example:1677--------1678>>> dialog = driver.dialog1679"""1680self._require_fedcm_support()1681return Dialog(self)16821683def fedcm_dialog(self, timeout=5, poll_frequency=0.5, ignored_exceptions=None):1684"""Waits for and returns the FedCM dialog.16851686Parameters:1687-----------1688timeout : int1689- How long to wait for the dialog16901691poll_frequency : floatHow1692- Frequently to poll16931694ignored_exceptions : Any1695- Exceptions to ignore while waiting16961697Returns:1698-------1699The FedCM dialog object if found17001701Raises:1702-------1703TimeoutException if dialog doesn't appear1704WebDriverException if FedCM not supported1705"""1706from selenium.common.exceptions import NoAlertPresentException1707from selenium.webdriver.support.wait import WebDriverWait17081709self._require_fedcm_support()17101711if ignored_exceptions is None:1712ignored_exceptions = (NoAlertPresentException,)17131714def _check_fedcm():1715try:1716dialog = Dialog(self)1717return dialog if dialog.type else None1718except NoAlertPresentException:1719return None17201721wait = WebDriverWait(self, timeout, poll_frequency=poll_frequency, ignored_exceptions=ignored_exceptions)1722return wait.until(lambda _: _check_fedcm())172317241725