initial commit

This commit is contained in:
2026-06-25 21:29:21 +00:00
commit 0d0a7456de
2738 changed files with 542622 additions and 0 deletions
@@ -0,0 +1,16 @@
# 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.
@@ -0,0 +1,23 @@
# 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.
class BidiConnection:
def __init__(self, session, cdp, devtools_import) -> None:
self.session = session
self.cdp = cdp
self.devtools = devtools_import
@@ -0,0 +1,156 @@
# 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 base64
import os
import socket
from enum import Enum
from urllib import parse
import certifi
from selenium.webdriver.common.proxy import Proxy, ProxyType
class AuthType(Enum):
BASIC = "Basic"
BEARER = "Bearer"
X_API_KEY = "X-API-Key"
class _ClientConfigDescriptor:
def __init__(self, name):
self.name = name
def __get__(self, obj, cls):
return obj.__dict__[self.name]
def __set__(self, obj, value) -> None:
obj.__dict__[self.name] = value
class ClientConfig:
remote_server_addr = _ClientConfigDescriptor("_remote_server_addr")
"""Gets and Sets Remote Server."""
keep_alive = _ClientConfigDescriptor("_keep_alive")
"""Gets and Sets Keep Alive value."""
proxy = _ClientConfigDescriptor("_proxy")
"""Gets and Sets the proxy used for communicating with the driver/server."""
ignore_certificates = _ClientConfigDescriptor("_ignore_certificates")
"""Gets and Sets the ignore certificate check value."""
init_args_for_pool_manager = _ClientConfigDescriptor("_init_args_for_pool_manager")
"""Gets and Sets the ignore certificate check."""
timeout = _ClientConfigDescriptor("_timeout")
"""Gets and Sets the timeout (in seconds) used for communicating with the driver/server."""
ca_certs = _ClientConfigDescriptor("_ca_certs")
"""Gets and Sets the path to bundle of CA certificates."""
username = _ClientConfigDescriptor("_username")
"""Gets and Sets the username used for basic authentication to the remote."""
password = _ClientConfigDescriptor("_password")
"""Gets and Sets the password used for basic authentication to the remote."""
auth_type = _ClientConfigDescriptor("_auth_type")
"""Gets and Sets the type of authentication to the remote server."""
token = _ClientConfigDescriptor("_token")
"""Gets and Sets the token used for authentication to the remote server."""
user_agent = _ClientConfigDescriptor("_user_agent")
"""Gets and Sets user agent to be added to the request headers."""
extra_headers = _ClientConfigDescriptor("_extra_headers")
"""Gets and Sets extra headers to be added to the request."""
websocket_timeout = _ClientConfigDescriptor("_websocket_timeout")
"""Gets and Sets the WebSocket response wait timeout (in seconds) used for communicating with the browser."""
websocket_interval = _ClientConfigDescriptor("_websocket_interval")
"""Gets and Sets the WebSocket response wait interval (in seconds) used for communicating with the browser."""
def __init__(
self,
remote_server_addr: str,
keep_alive: bool | None = True,
proxy: Proxy | None = Proxy(raw={"proxyType": ProxyType.SYSTEM}),
ignore_certificates: bool | None = False,
init_args_for_pool_manager: dict | None = None,
timeout: int | None = None,
ca_certs: str | None = None,
username: str | None = None,
password: str | None = None,
auth_type: AuthType | None = AuthType.BASIC,
token: str | None = None,
user_agent: str | None = None,
extra_headers: dict | None = None,
websocket_timeout: float | None = 30.0,
websocket_interval: float | None = 0.1,
) -> None:
self.remote_server_addr = remote_server_addr
self.keep_alive = keep_alive
self.proxy = proxy
self.ignore_certificates = ignore_certificates
self.init_args_for_pool_manager = init_args_for_pool_manager or {}
self.timeout = socket.getdefaulttimeout() if timeout is None else timeout
self.username = username
self.password = password
self.auth_type = auth_type
self.token = token
self.user_agent = user_agent
self.extra_headers = extra_headers
self.websocket_timeout = websocket_timeout
self.websocket_interval = websocket_interval
self.ca_certs = (
(os.getenv("REQUESTS_CA_BUNDLE") if "REQUESTS_CA_BUNDLE" in os.environ else certifi.where())
if ca_certs is None
else ca_certs
)
def reset_timeout(self) -> None:
"""Resets the timeout to the default value of socket."""
self._timeout = socket.getdefaulttimeout()
def get_proxy_url(self) -> str | None:
"""Returns the proxy URL to use for the connection."""
proxy_type = self.proxy.proxy_type
remote_add = parse.urlparse(self.remote_server_addr)
if proxy_type is ProxyType.DIRECT:
return None
if proxy_type is ProxyType.SYSTEM:
_no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY"))
if _no_proxy:
for entry in map(str.strip, _no_proxy.split(",")):
if entry == "*":
return None
n_url = parse.urlparse(entry)
if n_url.netloc and remote_add.netloc == n_url.netloc:
return None
if n_url.path in remote_add.netloc:
return None
return os.environ.get(
"https_proxy" if self.remote_server_addr.startswith("https://") else "http_proxy",
os.environ.get("HTTPS_PROXY" if self.remote_server_addr.startswith("https://") else "HTTP_PROXY"),
)
if proxy_type is ProxyType.MANUAL:
return self.proxy.sslProxy if self.remote_server_addr.startswith("https://") else self.proxy.http_proxy
return None
def get_auth_header(self) -> dict | None:
"""Returns the authorization to add to the request headers."""
if self.auth_type is AuthType.BASIC and self.username and self.password:
credentials = f"{self.username}:{self.password}"
encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
return {"Authorization": f"{AuthType.BASIC.value} {encoded_credentials}"}
if self.auth_type is AuthType.BEARER and self.token:
return {"Authorization": f"{AuthType.BEARER.value} {self.token}"}
if self.auth_type is AuthType.X_API_KEY and self.token:
return {f"{AuthType.X_API_KEY.value}": f"{self.token}"}
return None
@@ -0,0 +1,137 @@
# 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.
class Command:
"""Defines constants for the standard WebDriver commands.
While these constants have no meaning in and of themselves, they are
used to marshal commands through a service that implements WebDriver's
remote wire protocol:
https://w3c.github.io/webdriver/
"""
NEW_SESSION: str = "newSession"
DELETE_SESSION: str = "deleteSession"
NEW_WINDOW: str = "newWindow"
CLOSE: str = "close"
QUIT: str = "quit"
GET: str = "get"
GO_BACK: str = "goBack"
GO_FORWARD: str = "goForward"
REFRESH: str = "refresh"
ADD_COOKIE: str = "addCookie"
GET_COOKIE: str = "getCookie"
GET_ALL_COOKIES: str = "getCookies"
DELETE_COOKIE: str = "deleteCookie"
DELETE_ALL_COOKIES: str = "deleteAllCookies"
FIND_ELEMENT: str = "findElement"
FIND_ELEMENTS: str = "findElements"
FIND_CHILD_ELEMENT: str = "findChildElement"
FIND_CHILD_ELEMENTS: str = "findChildElements"
CLEAR_ELEMENT: str = "clearElement"
CLICK_ELEMENT: str = "clickElement"
SEND_KEYS_TO_ELEMENT: str = "sendKeysToElement"
W3C_GET_CURRENT_WINDOW_HANDLE: str = "w3cGetCurrentWindowHandle"
W3C_GET_WINDOW_HANDLES: str = "w3cGetWindowHandles"
SET_WINDOW_RECT: str = "setWindowRect"
GET_WINDOW_RECT: str = "getWindowRect"
SWITCH_TO_WINDOW: str = "switchToWindow"
SWITCH_TO_FRAME: str = "switchToFrame"
SWITCH_TO_PARENT_FRAME: str = "switchToParentFrame"
W3C_GET_ACTIVE_ELEMENT: str = "w3cGetActiveElement"
GET_CURRENT_URL: str = "getCurrentUrl"
GET_PAGE_SOURCE: str = "getPageSource"
GET_TITLE: str = "getTitle"
W3C_EXECUTE_SCRIPT: str = "w3cExecuteScript"
W3C_EXECUTE_SCRIPT_ASYNC: str = "w3cExecuteScriptAsync"
GET_ELEMENT_TEXT: str = "getElementText"
GET_ELEMENT_TAG_NAME: str = "getElementTagName"
IS_ELEMENT_SELECTED: str = "isElementSelected"
IS_ELEMENT_ENABLED: str = "isElementEnabled"
GET_ELEMENT_RECT: str = "getElementRect"
GET_ELEMENT_ATTRIBUTE: str = "getElementAttribute"
GET_ELEMENT_PROPERTY: str = "getElementProperty"
GET_ELEMENT_VALUE_OF_CSS_PROPERTY: str = "getElementValueOfCssProperty"
GET_ELEMENT_ARIA_ROLE: str = "getElementAriaRole"
GET_ELEMENT_ARIA_LABEL: str = "getElementAriaLabel"
SCREENSHOT: str = "screenshot"
ELEMENT_SCREENSHOT: str = "elementScreenshot"
EXECUTE_ASYNC_SCRIPT: str = "executeAsyncScript"
SET_TIMEOUTS: str = "setTimeouts"
GET_TIMEOUTS: str = "getTimeouts"
W3C_MAXIMIZE_WINDOW: str = "w3cMaximizeWindow"
GET_LOG: str = "getLog"
GET_AVAILABLE_LOG_TYPES: str = "getAvailableLogTypes"
FULLSCREEN_WINDOW: str = "fullscreenWindow"
MINIMIZE_WINDOW: str = "minimizeWindow"
PRINT_PAGE: str = "printPage"
# Alerts
W3C_DISMISS_ALERT: str = "w3cDismissAlert"
W3C_ACCEPT_ALERT: str = "w3cAcceptAlert"
W3C_SET_ALERT_VALUE: str = "w3cSetAlertValue"
W3C_GET_ALERT_TEXT: str = "w3cGetAlertText"
# Advanced user interactions
W3C_ACTIONS: str = "actions"
W3C_CLEAR_ACTIONS: str = "clearActionState"
# Screen Orientation
SET_SCREEN_ORIENTATION: str = "setScreenOrientation"
GET_SCREEN_ORIENTATION: str = "getScreenOrientation"
# Mobile
GET_NETWORK_CONNECTION: str = "getNetworkConnection"
SET_NETWORK_CONNECTION: str = "setNetworkConnection"
CURRENT_CONTEXT_HANDLE: str = "getCurrentContextHandle"
CONTEXT_HANDLES: str = "getContextHandles"
SWITCH_TO_CONTEXT: str = "switchToContext"
# Web Components
GET_SHADOW_ROOT: str = "getShadowRoot"
FIND_ELEMENT_FROM_SHADOW_ROOT: str = "findElementFromShadowRoot"
FIND_ELEMENTS_FROM_SHADOW_ROOT: str = "findElementsFromShadowRoot"
# Virtual Authenticator
ADD_VIRTUAL_AUTHENTICATOR: str = "addVirtualAuthenticator"
REMOVE_VIRTUAL_AUTHENTICATOR: str = "removeVirtualAuthenticator"
ADD_CREDENTIAL: str = "addCredential"
GET_CREDENTIALS: str = "getCredentials"
REMOVE_CREDENTIAL: str = "removeCredential"
REMOVE_ALL_CREDENTIALS: str = "removeAllCredentials"
SET_USER_VERIFIED: str = "setUserVerified"
# Remote File Management
UPLOAD_FILE: str = "uploadFile"
GET_DOWNLOADABLE_FILES: str = "getDownloadableFiles"
DOWNLOAD_FILE: str = "downloadFile"
DELETE_DOWNLOADABLE_FILES: str = "deleteDownloadableFiles"
# Remote Session Events
FIRE_SESSION_EVENT: str = "fireSessionEvent"
# Federated Credential Management (FedCM)
GET_FEDCM_TITLE: str = "getFedcmTitle"
GET_FEDCM_DIALOG_TYPE: str = "getFedcmDialogType"
GET_FEDCM_ACCOUNT_LIST: str = "getFedcmAccountList"
SELECT_FEDCM_ACCOUNT: str = "selectFedcmAccount"
CLICK_FEDCM_DIALOG_BUTTON: str = "clickFedcmDialogButton"
CANCEL_FEDCM_DIALOG: str = "cancelFedcmDialog"
SET_FEDCM_DELAY: str = "setFedcmDelay"
RESET_FEDCM_COOLDOWN: str = "resetFedcmCooldown"
@@ -0,0 +1,232 @@
# 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 json
from typing import Any
from selenium.common.exceptions import (
DetachedShadowRootException,
ElementClickInterceptedException,
ElementNotInteractableException,
ElementNotSelectableException,
ElementNotVisibleException,
ImeActivationFailedException,
ImeNotAvailableException,
InsecureCertificateException,
InvalidArgumentException,
InvalidCookieDomainException,
InvalidCoordinatesException,
InvalidElementStateException,
InvalidSelectorException,
InvalidSessionIdException,
JavascriptException,
MoveTargetOutOfBoundsException,
NoAlertPresentException,
NoSuchCookieException,
NoSuchElementException,
NoSuchFrameException,
NoSuchShadowRootException,
NoSuchWindowException,
ScreenshotException,
SessionNotCreatedException,
StaleElementReferenceException,
TimeoutException,
UnableToSetCookieException,
UnexpectedAlertPresentException,
UnknownMethodException,
WebDriverException,
)
class ExceptionMapping:
"""Maps each errorcode in ErrorCode object to corresponding exception.
Please refer to https://www.w3.org/TR/webdriver2/#errors for w3c specification.
"""
NO_SUCH_ELEMENT = NoSuchElementException
NO_SUCH_FRAME = NoSuchFrameException
NO_SUCH_SHADOW_ROOT = NoSuchShadowRootException
STALE_ELEMENT_REFERENCE = StaleElementReferenceException
ELEMENT_NOT_VISIBLE = ElementNotVisibleException
INVALID_ELEMENT_STATE = InvalidElementStateException
UNKNOWN_ERROR = WebDriverException
ELEMENT_IS_NOT_SELECTABLE = ElementNotSelectableException
JAVASCRIPT_ERROR = JavascriptException
TIMEOUT = TimeoutException
NO_SUCH_WINDOW = NoSuchWindowException
INVALID_COOKIE_DOMAIN = InvalidCookieDomainException
UNABLE_TO_SET_COOKIE = UnableToSetCookieException
UNEXPECTED_ALERT_OPEN = UnexpectedAlertPresentException
NO_ALERT_OPEN = NoAlertPresentException
SCRIPT_TIMEOUT = TimeoutException
IME_NOT_AVAILABLE = ImeNotAvailableException
IME_ENGINE_ACTIVATION_FAILED = ImeActivationFailedException
INVALID_SELECTOR = InvalidSelectorException
SESSION_NOT_CREATED = SessionNotCreatedException
MOVE_TARGET_OUT_OF_BOUNDS = MoveTargetOutOfBoundsException
INVALID_XPATH_SELECTOR = InvalidSelectorException
INVALID_XPATH_SELECTOR_RETURN_TYPER = InvalidSelectorException
ELEMENT_NOT_INTERACTABLE = ElementNotInteractableException
INSECURE_CERTIFICATE = InsecureCertificateException
INVALID_ARGUMENT = InvalidArgumentException
INVALID_COORDINATES = InvalidCoordinatesException
INVALID_SESSION_ID = InvalidSessionIdException
NO_SUCH_COOKIE = NoSuchCookieException
UNABLE_TO_CAPTURE_SCREEN = ScreenshotException
ELEMENT_CLICK_INTERCEPTED = ElementClickInterceptedException
UNKNOWN_METHOD = UnknownMethodException
DETACHED_SHADOW_ROOT = DetachedShadowRootException
class ErrorCode:
"""Error codes defined in the WebDriver wire protocol."""
# Keep in sync with org.openqa.selenium.remote.ErrorCodes and errorcodes.h
SUCCESS = 0
NO_SUCH_ELEMENT = [7, "no such element"]
NO_SUCH_FRAME = [8, "no such frame"]
NO_SUCH_SHADOW_ROOT = ["no such shadow root"]
UNKNOWN_COMMAND = [9, "unknown command"]
STALE_ELEMENT_REFERENCE = [10, "stale element reference"]
ELEMENT_NOT_VISIBLE = [11, "element not visible"]
INVALID_ELEMENT_STATE = [12, "invalid element state"]
UNKNOWN_ERROR = [13, "unknown error"]
ELEMENT_IS_NOT_SELECTABLE = [15, "element not selectable"]
JAVASCRIPT_ERROR = [17, "javascript error"]
XPATH_LOOKUP_ERROR = [19, "invalid selector"]
TIMEOUT = [21, "timeout"]
NO_SUCH_WINDOW = [23, "no such window"]
INVALID_COOKIE_DOMAIN = [24, "invalid cookie domain"]
UNABLE_TO_SET_COOKIE = [25, "unable to set cookie"]
UNEXPECTED_ALERT_OPEN = [26, "unexpected alert open"]
NO_ALERT_OPEN = [27, "no such alert"]
SCRIPT_TIMEOUT = [28, "script timeout"]
INVALID_ELEMENT_COORDINATES = [29, "invalid element coordinates"]
IME_NOT_AVAILABLE = [30, "ime not available"]
IME_ENGINE_ACTIVATION_FAILED = [31, "ime engine activation failed"]
INVALID_SELECTOR = [32, "invalid selector"]
SESSION_NOT_CREATED = [33, "session not created"]
MOVE_TARGET_OUT_OF_BOUNDS = [34, "move target out of bounds"]
INVALID_XPATH_SELECTOR = [51, "invalid selector"]
INVALID_XPATH_SELECTOR_RETURN_TYPER = [52, "invalid selector"]
ELEMENT_NOT_INTERACTABLE = [60, "element not interactable"]
INSECURE_CERTIFICATE = ["insecure certificate"]
INVALID_ARGUMENT = [61, "invalid argument"]
INVALID_COORDINATES = ["invalid coordinates"]
INVALID_SESSION_ID = ["invalid session id"]
NO_SUCH_COOKIE = [62, "no such cookie"]
UNABLE_TO_CAPTURE_SCREEN = [63, "unable to capture screen"]
ELEMENT_CLICK_INTERCEPTED = [64, "element click intercepted"]
UNKNOWN_METHOD = ["unknown method exception"]
DETACHED_SHADOW_ROOT = [65, "detached shadow root"]
METHOD_NOT_ALLOWED = [405, "unsupported operation"]
class ErrorHandler:
"""Handles errors returned by the WebDriver server."""
def check_response(self, response: dict[str, Any]) -> None:
"""Check that a JSON response from the WebDriver does not have an error.
Args:
response: The JSON response from the WebDriver server as a dictionary
object.
Raises:
WebDriverException: If the response contains an error message.
"""
status = response.get("status", None)
if not status or status == ErrorCode.SUCCESS:
return
value = None
message = response.get("message", "")
screen: str = response.get("screen", "")
stacktrace = None
if isinstance(status, int):
value_json = response.get("value", None)
if value_json and isinstance(value_json, str):
try:
value = json.loads(value_json)
if isinstance(value, dict):
if len(value) == 1:
value = value["value"]
status = value.get("error", None)
if not status:
status = value.get("status", ErrorCode.UNKNOWN_ERROR)
message = value.get("value") or value.get("message")
if not isinstance(message, str):
value = message
message = message.get("message") if isinstance(message, dict) else None
else:
message = value.get("message", None)
except ValueError:
pass
exception_class: type[WebDriverException]
e = ErrorCode()
error_codes = [item for item in dir(e) if not item.startswith("__")]
for error_code in error_codes:
error_info = getattr(ErrorCode, error_code)
if isinstance(error_info, list) and status in error_info:
exception_class = getattr(ExceptionMapping, error_code, WebDriverException)
break
else:
exception_class = WebDriverException
if not value:
value = response["value"]
if isinstance(value, str):
raise exception_class(value)
if message == "" and "message" in value:
message = value["message"]
screen = None # type: ignore[assignment]
if "screen" in value:
screen = value["screen"]
stacktrace = None
st_value = value.get("stackTrace") or value.get("stacktrace")
if st_value:
if isinstance(st_value, str):
stacktrace = st_value.split("\n")
else:
stacktrace = []
try:
for frame in st_value:
line = frame.get("lineNumber", "")
file = frame.get("fileName", "<anonymous>")
if line:
file = f"{file}:{line}"
meth = frame.get("methodName", "<anonymous>")
if "className" in frame:
meth = f"{frame['className']}.{meth}"
msg = " at %s (%s)"
msg = msg % (meth, file)
stacktrace.append(msg)
except TypeError:
pass
if exception_class == UnexpectedAlertPresentException:
alert_text = None
if "data" in value:
alert_text = value["data"].get("text")
elif "alert" in value:
alert_text = value["alert"].get("text")
raise exception_class(message, screen, stacktrace, alert_text)
raise exception_class(message, screen, stacktrace)
@@ -0,0 +1,68 @@
# 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.
from selenium.webdriver.remote.command import Command
class FedCM:
def __init__(self, driver) -> None:
self._driver = driver
@property
def title(self) -> str:
"""Gets the title of the dialog."""
return self._driver.execute(Command.GET_FEDCM_TITLE)["value"].get("title")
@property
def subtitle(self) -> str | None:
"""Gets the subtitle of the dialog."""
return self._driver.execute(Command.GET_FEDCM_TITLE)["value"].get("subtitle")
@property
def dialog_type(self) -> str:
"""Gets the type of the dialog currently being shown."""
return self._driver.execute(Command.GET_FEDCM_DIALOG_TYPE).get("value")
@property
def account_list(self) -> list[dict]:
"""Gets the list of accounts shown in the dialog."""
return self._driver.execute(Command.GET_FEDCM_ACCOUNT_LIST).get("value")
def select_account(self, index: int) -> None:
"""Selects an account from the dialog by index."""
self._driver.execute(Command.SELECT_FEDCM_ACCOUNT, {"accountIndex": index})
def accept(self) -> None:
"""Clicks the continue button in the dialog."""
self._driver.execute(Command.CLICK_FEDCM_DIALOG_BUTTON, {"dialogButton": "ConfirmIdpLoginContinue"})
def dismiss(self) -> None:
"""Cancels/dismisses the FedCM dialog."""
self._driver.execute(Command.CANCEL_FEDCM_DIALOG)
def enable_delay(self) -> None:
"""Re-enables the promise rejection delay for FedCM."""
self._driver.execute(Command.SET_FEDCM_DELAY, {"enabled": True})
def disable_delay(self) -> None:
"""Disables the promise rejection delay for FedCM."""
self._driver.execute(Command.SET_FEDCM_DELAY, {"enabled": False})
def reset_cooldown(self) -> None:
"""Resets the FedCM dialog cooldown, allowing immediate retriggers."""
self._driver.execute(Command.RESET_FEDCM_COOLDOWN)
@@ -0,0 +1,49 @@
# 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.
from abc import ABCMeta, abstractmethod
from contextlib import suppress
from pathlib import Path
from selenium.webdriver.common.utils import keys_to_typing
class FileDetector(metaclass=ABCMeta):
"""Identify whether a sequence of characters represents a file path."""
@abstractmethod
def is_local_file(self, *keys: str | int | float) -> str | None:
raise NotImplementedError
class UselessFileDetector(FileDetector):
"""A file detector that never finds anything."""
def is_local_file(self, *keys: str | int | float) -> str | None:
return None
class LocalFileDetector(FileDetector):
"""Detects files on the local disk."""
def is_local_file(self, *keys: str | int | float) -> str | None:
file_path = "".join(keys_to_typing(keys))
with suppress(OSError):
if Path(file_path).is_file():
return file_path
return None
@@ -0,0 +1,68 @@
function(){return (function(){/*
Copyright The Closure Library Authors.
Copyright The Closure Compiler Authors.
SPDX-License-Identifier: Apache-2.0
*/
var aa=aa||{},h=this||self;try{var ba=window}catch(a){ba=h};/*
Copyright The Closure Library Authors.
SPDX-License-Identifier: Apache-2.0
*/
function k(a){var b=typeof a;return b=="object"&&a!=null||b=="function"}
function ca(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(c=="[object Window]")return"object";if(c=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(c=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
else if(b=="function"&&typeof a.call=="undefined")return"object";return b};function n(a,b){this.code=a;this.g=q[a]||r;this.message=b||"";a=this.g.replace(/((?:^|\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(b<0||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}(function(){function a(){}a.prototype=Error.prototype;n.prototype=new a;n.prototype.constructor=n})();var r="unknown error",q={15:"element not selectable",11:"element not visible"};q[31]=r;q[30]=r;q[24]="invalid cookie domain";
q[29]="invalid element coordinates";q[12]="invalid element state";q[32]="invalid selector";q[51]="invalid selector";q[52]="invalid selector";q[17]="javascript error";q[405]="unsupported operation";q[34]="move target out of bounds";q[27]="no such alert";q[7]="no such element";q[8]="no such frame";q[23]="no such window";q[28]="script timeout";q[33]="session not created";q[10]="stale element reference";q[21]="timeout";q[25]="unable to set cookie";q[26]="unexpected alert open";q[13]=r;q[9]="unknown command";var da;var ea=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(typeof a==="string")return typeof b!=="string"||b.length!=1?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},t=Array.prototype.forEach?function(a,b,c){Array.prototype.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=typeof a==="string"?a.split(""):a,f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)},fa=Array.prototype.filter?function(a,b){return Array.prototype.filter.call(a,
b,void 0)}:function(a,b){for(var c=a.length,d=[],e=0,f=typeof a==="string"?a.split(""):a,g=0;g<c;g++)if(g in f){var m=f[g];b.call(void 0,m,g,a)&&(d[e++]=m)}return d},ha=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=Array(c),e=typeof a==="string"?a.split(""):a,f=0;f<c;f++)f in e&&(d[f]=b.call(void 0,e[f],f,a));return d},ia=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,
d=typeof a==="string"?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1},ja=Array.prototype.every?function(a,b,c){return Array.prototype.every.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=typeof a==="string"?a.split(""):a,f=0;f<d;f++)if(f in e&&!b.call(c,e[f],f,a))return!1;return!0};
function ka(a,b){a:{for(var c=a.length,d=typeof a==="string"?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break a}b=-1}return b<0?null:typeof a==="string"?a.charAt(b):a[b]}function la(a,b){a.sort(b||ma)}function ma(a,b){return a>b?1:a<b?-1:0};function na(a){var b=a.length-1;return b>=0&&a.indexOf(" ",b)==b}var y=aa.S&&String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\s\xa0]*([\s\S]*?)[\s\xa0]*$/.exec(a)[1]};
function oa(a,b){var c=0;a=y(String(a)).split(".");b=y(String(b)).split(".");for(var d=Math.max(a.length,b.length),e=0;c==0&&e<d;e++){var f=a[e]||"",g=b[e]||"";do{f=/(\d*)(\D*)(.*)/.exec(f)||["","","",""];g=/(\d*)(\D*)(.*)/.exec(g)||["","","",""];if(f[0].length==0&&g[0].length==0)break;c=pa(f[1].length==0?0:parseInt(f[1],10),g[1].length==0?0:parseInt(g[1],10))||pa(f[2].length==0,g[2].length==0)||pa(f[2],g[2]);f=f[3];g=g[3]}while(c==0)}return c}function pa(a,b){return a<b?-1:a>b?1:0};function z(){var a=h.navigator;return a&&(a=a.userAgent)?a:""}function A(a){return z().indexOf(a)!=-1};function qa(){return A("Firefox")||A("FxiOS")}function ra(){return(A("Chrome")||A("CriOS"))&&!A("Edge")||A("Silk")};function C(){return A("iPhone")&&!A("iPod")&&!A("iPad")};function sa(a,b){var c=ta;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var ua=A("Opera"),D=A("Trident")||A("MSIE"),va=A("Edge"),wa=A("Gecko")&&!(z().toLowerCase().indexOf("webkit")!=-1&&!A("Edge"))&&!(A("Trident")||A("MSIE"))&&!A("Edge"),ya=z().toLowerCase().indexOf("webkit")!=-1&&!A("Edge");function za(){var a=h.document;return a?a.documentMode:void 0}var E;
a:{var Aa="",Ba=function(){var a=z();if(wa)return/rv:([^\);]+)(\)|;)/.exec(a);if(va)return/Edge\/([\d\.]+)/.exec(a);if(D)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(ya)return/WebKit\/(\S+)/.exec(a);if(ua)return/(?:Version)[ \/]?(\S+)/.exec(a)}();Ba&&(Aa=Ba?Ba[1]:"");if(D){var Ca=za();if(Ca!=null&&Ca>parseFloat(Aa)){E=String(Ca);break a}}E=Aa}var ta={};function Da(a){return sa(a,function(){return oa(E,a)>=0})}var Ea;if(h.document&&D){var Fa=za();Ea=Fa?Fa:parseInt(E,10)||void 0}else Ea=void 0;
var F=Ea;function G(a,b){this.x=a!==void 0?a:0;this.y=b!==void 0?b:0}G.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};G.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};G.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function H(a,b){this.width=a;this.height=b}H.prototype.aspectRatio=function(){return this.width/this.height};H.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};H.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};H.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Ga(a){return String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()})};function I(a){return a?new Ha(K(a)):da||(da=new Ha)}function Ia(a,b){return typeof b==="string"?a.getElementById(b):b}function Ja(a){for(;a&&a.nodeType!=1;)a=a.previousSibling;return a}function Ka(a,b){if(!a||!b)return!1;if(a.contains&&b.nodeType==1)return a==b||a.contains(b);if(typeof a.compareDocumentPosition!="undefined")return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}function K(a){return a.nodeType==9?a:a.ownerDocument||a.document}
function La(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function Ha(a){this.g=a||h.document||document}
function L(a,b,c,d){a=a.g;d=d||a;var e=b&&b!="*"?String(b).toUpperCase():"";if(d.querySelectorAll&&d.querySelector&&(e||c))c=d.querySelectorAll(e+(c?"."+c:""));else if(c&&d.getElementsByClassName)if(b=d.getElementsByClassName(c),e){d={};for(var f=a=0,g;g=b[f];f++)e==g.nodeName&&(d[a++]=g);d.length=a;c=d}else c=b;else if(b=d.getElementsByTagName(e||"*"),c){d={};for(f=a=0;g=b[f];f++){e=g.className;var m;if(m=typeof e.split=="function")m=ea(e.split(/\s+/),c)>=0;m&&(d[a++]=g)}d.length=a;c=d}else c=b;
return c};var Ma={o:function(a){return!(!a.querySelectorAll||!a.querySelector)},j:function(a,b){if(!a)throw new n(32,"No class name specified");a=y(a);if(a.indexOf(" ")!==-1)throw new n(32,"Compound class names not permitted");if(Ma.o(b))try{return b.querySelector("."+a.replace(/\./g,"\\."))||null}catch(c){throw new n(32,"An invalid or illegal class name was specified");}a=L(I(b),"*",a,b);return a.length?a[0]:null},h:function(a,b){if(!a)throw new n(32,"No class name specified");a=y(a);if(a.indexOf(" ")!==-1)throw new n(32,
"Compound class names not permitted");if(Ma.o(b))try{return b.querySelectorAll("."+a.replace(/\./g,"\\."))}catch(c){throw new n(32,"An invalid or illegal class name was specified");}return L(I(b),"*",a,b)}};var Na=qa(),Qa=C()||A("iPod"),Ra=A("iPad"),Sa=A("Android")&&!(ra()||qa()||A("Opera")||A("Silk")),Ta=ra(),Ua=A("Safari")&&!(ra()||A("Coast")||A("Opera")||A("Edge")||A("Edg/")||A("OPR")||qa()||A("Silk")||A("Android"))&&!(C()||A("iPad")||A("iPod"));function M(a){return(a=a.exec(z()))?a[1]:""}(function(){if(Na)return M(/Firefox\/([0-9.]+)/);if(D||va||ua)return E;if(Ta){if(C()||A("iPad")||A("iPod")||A("Macintosh")){var a=M(/CriOS\/([0-9.]+)/);if(a)return a}return M(/Chrome\/([0-9.]+)/)}if(Ua&&!(C()||A("iPad")||A("iPod")))return M(/Version\/([0-9.]+)/);if(Qa||Ra){if(a=/Version\/(\S+).*Mobile\/(\S+)/.exec(z()))return a[1]+"."+a[2]}else if(Sa)return(a=M(/Android\s+([0-9.]+)/))?a:M(/Version\/([0-9.]+)/);return""})();var Va=D&&!(Number(F)>=8),Wa=D&&!(Number(F)>=9);var N={j:function(a,b){if(typeof b.querySelector!=="function"&&D&&(D?oa(F,8)>=0:Da(8))&&!k(b.querySelector))throw Error("CSS selection is not supported");if(!a)throw new n(32,"No selector specified");a=y(a);try{var c=b.querySelector(a)}catch(d){throw new n(32,"An invalid or illegal selector was specified");}return c&&c.nodeType==1?c:null},h:function(a,b){if(typeof b.querySelectorAll!=="function"&&D&&(D?oa(F,8)>=0:Da(8))&&!k(b.querySelector))throw Error("CSS selection is not supported");if(!a)throw new n(32,
"No selector specified");a=y(a);try{return b.querySelectorAll(a)}catch(c){throw new n(32,"An invalid or illegal selector was specified");}}};var Xa={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",
darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",
ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",
lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var Ya="backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor".split(" "),Za=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,$a=/^#(?:[0-9a-f]{3}){1,2}$/i,ab=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i,bb=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function O(a,b){b=b.toLowerCase();return b=="style"?cb(a.style.cssText):Va&&b=="value"&&P(a,"INPUT")?a.value:Wa&&a[b]===!0?String(a.getAttribute(b)):(a=a.getAttributeNode(b))&&a.specified?a.value:null}var db=RegExp("[;]+(?=(?:(?:[^\"]*\"){2})*[^\"]*$)(?=(?:(?:[^']*'){2})*[^']*$)(?=(?:[^()]*\\([^()]*\\))*[^()]*$)");
function cb(a){var b=[];t(a.split(db),function(c){var d=c.indexOf(":");d>0&&(c=[c.slice(0,d),c.slice(d+1)],c.length==2&&b.push(c[0].toLowerCase(),":",c[1],";"))});b=b.join("");return b=b.charAt(b.length-1)==";"?b:b+";"}function P(a,b){b&&typeof b!=="string"&&(b=b.toString());return a instanceof HTMLFormElement?!!a&&a.nodeType==1&&(!b||"FORM"==b):!!a&&a.nodeType==1&&(!b||a.tagName.toUpperCase()==b)};function eb(a,b,c,d){this.top=a;this.g=b;this.l=c;this.left=d}eb.prototype.ceil=function(){this.top=Math.ceil(this.top);this.g=Math.ceil(this.g);this.l=Math.ceil(this.l);this.left=Math.ceil(this.left);return this};eb.prototype.floor=function(){this.top=Math.floor(this.top);this.g=Math.floor(this.g);this.l=Math.floor(this.l);this.left=Math.floor(this.left);return this};
eb.prototype.round=function(){this.top=Math.round(this.top);this.g=Math.round(this.g);this.l=Math.round(this.l);this.left=Math.round(this.left);return this};function Q(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}Q.prototype.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};Q.prototype.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
Q.prototype.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var fb=typeof ShadowRoot==="function";function gb(a){for(a=a.parentNode;a&&a.nodeType!=1&&a.nodeType!=9&&a.nodeType!=11;)a=a.parentNode;return P(a)?a:null}
function R(a,b){b=Ga(b);if(b=="float"||b=="cssFloat"||b=="styleFloat")b=Wa?"styleFloat":"cssFloat";a:{var c=b;var d=K(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||"";break a}c=""}a=c||hb(a,b);if(a===null)a=null;else if(ea(Ya,b)>=0){b:{var e=a.match(ab);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),b>=0&&b<=255&&c>=0&&c<=255&&d>=0&&d<=255&&e>=0&&e<=1)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(bb))if(b=
Number(d[1]),c=Number(d[2]),d=Number(d[3]),b>=0&&b<=255&&c>=0&&c<=255&&d>=0&&d<=255){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=Xa[b.toLowerCase()];if(!c&&(c=b.charAt(0)=="#"?b:"#"+b,c.length==4&&(c=c.replace(Za,"#$1$1$2$2$3$3")),!$a.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?"rgba("+b.join(", ")+")":a}return a}
function hb(a,b){var c=a.currentStyle||a.style,d=c[b];d===void 0&&typeof c.getPropertyValue==="function"&&(d=c.getPropertyValue(b));return d!="inherit"?d!==void 0?d:null:(a=gb(a))?hb(a,b):null}
function ib(a,b,c){function d(g){var m=S(g);if(m.height>0&&m.width>0)return!0;if(P(g,"PATH")&&(m.height>0||m.width>0))return g=R(g,"stroke-width"),!!g&&parseInt(g,10)>0;m=R(g,"visibility");return m!="collapse"&&m!="hidden"&&c(g)?R(g,"overflow")!="hidden"&&ia(g.childNodes,function(x){return x.nodeType==3?(x=x.nodeValue,/^[\s]*$/.test(x)&&/[\n\r\t]/.test(x)?!1:!0):P(x)&&d(x)}):!1}function e(g){return jb(g)==T&&ja(g.childNodes,function(m){return!P(m)||e(m)||!d(m)})}if(!P(a))throw Error("Argument to isShown must be of type Element");
if(P(a,"BODY"))return!0;if(P(a,"OPTION")||P(a,"OPTGROUP"))return a=La(a,function(g){return P(g,"SELECT")}),!!a&&ib(a,!0,c);var f=kb(a);if(f)return!!f.image&&f.rect.width>0&&f.rect.height>0&&ib(f.image,b,c);if(P(a,"INPUT")&&a.type.toLowerCase()=="hidden"||P(a,"NOSCRIPT"))return!1;f=R(a,"visibility");return f!="collapse"&&f!="hidden"&&c(a)&&(b||lb(a)!=0)&&d(a)?!e(a):!1}
function mb(a){function b(c){if(P(c)&&(R(c,"display")=="none"||R(c,"content-visibility")=="hidden"))return!1;var d;if((d=c.parentNode)&&d.shadowRoot&&c.assignedSlot!==void 0)d=c.assignedSlot?c.assignedSlot.parentNode:null;else if(c.getDestinationInsertionPoints){var e=c.getDestinationInsertionPoints();e.length>0&&(d=e[e.length-1])}if(fb&&d instanceof ShadowRoot){if(d.host.shadowRoot&&d.host.shadowRoot!==d)return!1;d=d.host}return!d||d.nodeType!=9&&d.nodeType!=11?d&&P(d,"DETAILS")&&!d.open&&!P(c,"SUMMARY")?
!1:!!d&&b(d):!0}return ib(a,!1,b)}var T="hidden";
function jb(a){function b(l){function p(xa){if(xa==g)return!0;var Oa=R(xa,"display");return Oa.lastIndexOf("inline",0)==0||Oa=="contents"||Pa=="absolute"&&R(xa,"position")=="static"?!1:!0}var Pa=R(l,"position");if(Pa=="fixed")return v=!0,l==g?null:g;for(l=gb(l);l&&!p(l);)l=gb(l);return l}function c(l){var p=l;if(x=="visible")if(l==g&&m)p=m;else if(l==m)return{x:"visible",y:"visible"};p={x:R(p,"overflow-x"),y:R(p,"overflow-y")};l==g&&(p.x=p.x=="visible"?"auto":p.x,p.y=p.y=="visible"?"auto":p.y);return p}
function d(l){if(l==g){var p=(new Ha(f)).g;l=p.scrollingElement?p.scrollingElement:ya||p.compatMode!="CSS1Compat"?p.body||p.documentElement:p.documentElement;p=p.parentWindow||p.defaultView;l=D&&p.pageYOffset!=l.scrollTop?new G(l.scrollLeft,l.scrollTop):new G(p.pageXOffset||l.scrollLeft,p.pageYOffset||l.scrollTop)}else l=new G(l.scrollLeft,l.scrollTop);return l}var e=nb(a),f=K(a),g=f.documentElement,m=f.body,x=R(g,"overflow"),v;for(a=b(a);a;a=b(a)){var u=c(a);if(u.x!="visible"||u.y!="visible"){var w=
S(a);if(w.width==0||w.height==0)return T;var B=e.g<w.left,J=e.l<w.top;if(B&&u.x=="hidden"||J&&u.y=="hidden")return T;if(B&&u.x!="visible"||J&&u.y!="visible"){B=d(a);J=e.l<w.top-B.y;if(e.g<w.left-B.x&&u.x!="visible"||J&&u.x!="visible")return T;e=jb(a);return e==T?T:"scroll"}B=e.left>=w.left+w.width;w=e.top>=w.top+w.height;if(B&&u.x=="hidden"||w&&u.y=="hidden")return T;if(B&&u.x!="visible"||w&&u.y!="visible"){if(v&&(u=d(a),e.left>=g.scrollWidth-u.x||e.g>=g.scrollHeight-u.y))return T;e=jb(a);return e==
T?T:"scroll"}}}return"none"}
function S(a){var b=kb(a);if(b)return b.rect;if(P(a,"HTML"))return a=K(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a=a.compatMode=="CSS1Compat"?a.documentElement:a.body,a=new H(a.clientWidth,a.clientHeight),new Q(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new Q(0,0,0,0)}b=new Q(c.left,c.top,c.right-c.left,c.bottom-c.top);D&&a.ownerDocument.body&&(a=K(a),b.left-=a.documentElement.clientLeft+a.body.clientLeft,b.top-=a.documentElement.clientTop+a.body.clientTop);
return b}function kb(a){var b=P(a,"MAP");if(!b&&!P(a,"AREA"))return null;var c=b?a:P(a.parentNode,"MAP")?a.parentNode:null,d=null,e=null;c&&c.name&&(d=N.j('*[usemap="#'+c.name+'"]',K(c)))&&(e=S(d),b||a.shape.toLowerCase()=="default"||(a=ob(a),b=Math.min(Math.max(a.left,0),e.width),c=Math.min(Math.max(a.top,0),e.height),e=new Q(b+e.left,c+e.top,Math.min(a.width,e.width-b),Math.min(a.height,e.height-c))));return{image:d,rect:e||new Q(0,0,0,0)}}
function ob(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if(b=="rect"&&a.length==4){b=a[0];var c=a[1];return new Q(b,c,a[2]-b,a[3]-c)}if(b=="circle"&&a.length==3)return b=a[2],new Q(a[0]-b,a[1]-b,2*b,2*b);if(b=="poly"&&a.length>2){b=a[0];c=a[1];for(var d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new Q(b,c,d-b,e-c)}return new Q(0,0,0,0)}function nb(a){a=S(a);return new eb(a.top,a.left+a.width,a.top+a.height,a.left)}
function pb(a){return a.replace(/^[^\S\xa0]+|[^\S\xa0]+$/g,"")}function qb(a){var b=[];fb?rb(a,b):sb(a,b);a=ha(b,pb);return pb(a.join("\n")).replace(/\xa0/g," ")}
function tb(a,b,c){if(P(a,"BR"))b.push("");else{var d=P(a,"TD"),e=R(a,"display"),f=!d&&!(ea(ub,e)>=0),g=a.previousElementSibling!==void 0?a.previousElementSibling:Ja(a.previousSibling);g=g?R(g,"display"):"";var m=R(a,"float")||R(a,"cssFloat")||R(a,"styleFloat");!f||g=="run-in"&&m=="none"||/^[\s\xa0]*$/.test(b[b.length-1]||"")||b.push("");var x=mb(a),v=null,u=null;x&&(v=R(a,"white-space"),u=R(a,"text-transform"));t(a.childNodes,function(w){c(w,b,x,v,u)});a=b[b.length-1]||"";!d&&e!="table-cell"||!a||
na(a)||(b[b.length-1]+=" ");f&&e!="run-in"&&!/^[\s\xa0]*$/.test(a)&&b.push("")}}function sb(a,b){tb(a,b,function(c,d,e,f,g){c.nodeType==3&&e?vb(c,d,f,g):P(c)&&sb(c,d)})}var ub="inline inline-block inline-table none table-cell table-column table-column-group".split(" ");
function vb(a,b,c,d){a=a.nodeValue.replace(/[\u200b\u200e\u200f]/g,"");a=a.replace(/(\r\n|\r|\n)/g,"\n");if(c=="normal"||c=="nowrap")a=a.replace(/\n/g," ");a=c=="pre"||c=="pre-wrap"?a.replace(/[ \f\t\v\u2028\u2029]/g,"\u00a0"):a.replace(/[ \f\t\v\u2028\u2029]+/g," ");d=="capitalize"?(c=/(^|[^'_0-9A-Za-z\u00C0-\u02AF\u1E00-\u1EFF\u24B6-\u24E9\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF])([A-Za-z\u00C0-\u02AF\u1E00-\u1EFF\u24B6-\u24E9])/g,a=a.replace(c,function(){return arguments[1]+arguments[2].toUpperCase()}),
c=/(^|[^'_0-9A-Za-z\u00C0-\u02AF\u1E00-\u1EFF\u24B6-\u24E9])([_*])([A-Za-z\u00C0-\u02AF\u1E00-\u1EFF\u24D0-\u24E9])/g,a=a.replace(c,function(){return arguments[1]+arguments[2]+arguments[3].toUpperCase()})):d=="uppercase"?a=a.toUpperCase():d=="lowercase"&&(a=a.toLowerCase());c=b.pop()||"";na(c)&&a.lastIndexOf(" ",0)==0&&(a=a.substr(1));b.push(c+a)}
function lb(a){if(Wa){if(R(a,"position")=="relative")return 1;a=R(a,"filter");return(a=a.match(/^alpha\(opacity=(\d*)\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\(Opacity=(\d*)\)/))?Number(a[1])/100:1}return wb(a)}function wb(a){var b=1,c=R(a,"opacity");c&&(b=Number(c));(a=gb(a))&&(b*=wb(a));return b}
function xb(a,b,c,d,e){if(a.nodeType==3&&c)vb(a,b,d,e);else if(P(a))if(P(a,"CONTENT")||P(a,"SLOT")){for(var f=a;f.parentNode;)f=f.parentNode;f instanceof ShadowRoot?(f=P(a,"CONTENT")?a.getDistributedNodes():a.assignedNodes(),t(f.length>0?f:a.childNodes,function(g){xb(g,b,c,d,e)})):rb(a,b)}else if(P(a,"SHADOW")){for(f=a;f.parentNode;)f=f.parentNode;if(f instanceof ShadowRoot&&(a=f))for(a=a.olderShadowRoot;a;)t(a.childNodes,function(g){xb(g,b,c,d,e)}),a=a.olderShadowRoot}else rb(a,b)}
function rb(a,b){if(a.shadowRoot){var c=R(a,"white-space"),d=R(a,"text-transform");t(a.shadowRoot.childNodes,function(e){xb(e,b,!0,c,d)})}tb(a,b,function(e,f,g,m,x){var v=null;e.nodeType==1?v=e:e.nodeType==3&&(v=e);v!=null&&(v.assignedSlot!=null||v.getDestinationInsertionPoints&&v.getDestinationInsertionPoints().length>0)||xb(e,f,g,m,x)})};var yb={o:function(a,b){return!(!a.querySelectorAll||!a.querySelector)&&!/^\d.*/.test(b)},j:function(a,b){var c=I(b),d=Ia(c.g,a);return d?O(d,"id")==a&&b!=d&&Ka(b,d)?d:ka(L(c,"*"),function(e){return O(e,"id")==a&&b!=e&&Ka(b,e)}):null},h:function(a,b){if(!a)return[];if(yb.o(b,a))try{return b.querySelectorAll("#"+yb.H(a))}catch(c){return[]}b=L(I(b),"*",null,b);return fa(b,function(c){return O(c,"id")==a})},H:function(a){return a.replace(/([\s'"\\#.:;,!?+<>=~*^$|%&@`{}\-\/\[\]\(\)])/g,"\\$1")}};var U={},zb={};U.B=function(a,b,c){try{var d=N.h("a",b)}catch(e){d=L(I(b),"A",null,b)}return ka(d,function(e){e=qb(e);e=e.replace(/^[\s]+|[\s]+$/g,"");return c&&e.indexOf(a)!=-1||e==a})};U.A=function(a,b,c){try{var d=N.h("a",b)}catch(e){d=L(I(b),"A",null,b)}return fa(d,function(e){e=qb(e);e=e.replace(/^[\s]+|[\s]+$/g,"");return c&&e.indexOf(a)!=-1||e==a})};U.j=function(a,b){return U.B(a,b,!1)};U.h=function(a,b){return U.A(a,b,!1)};zb.j=function(a,b){return U.B(a,b,!0)};
zb.h=function(a,b){return U.A(a,b,!0)};var V={m:function(a,b){return function(c){var d=V.i(a);d=S(d);c=S(c);return b.call(null,d,c)}},F:function(a){return V.m(a,function(b,c){return c.top+c.height<=b.top})},G:function(a){return V.m(a,function(b,c){return c.top>=b.top+b.height})},J:function(a){return V.m(a,function(b,c){return c.left+c.width<=b.left})},L:function(a){return V.m(a,function(b,c){return c.left>=b.left+b.width})},N:function(a){return V.m(a,function(b,c){return c.left<b.left+b.width&&c.left+c.width>b.left&&c.top+c.height<=b.top})},
O:function(a){return V.m(a,function(b,c){return c.left<b.left+b.width&&c.left+c.width>b.left&&c.top>=b.top+b.height})},P:function(a){return V.m(a,function(b,c){return c.top<b.top+b.height&&c.top+c.height>b.top&&c.left+c.width<=b.left})},R:function(a){return V.m(a,function(b,c){return c.top<b.top+b.height&&c.top+c.height>b.top&&c.left>=b.left+b.width})},K:function(a,b){var c;b?c=b:typeof a.distance==="number"&&(c=a.distance);c||(c=50);return function(d){var e=V.i(a);if(e===d)return!1;e=S(e);d=S(d);
e=new Q(e.left-c,e.top-c,e.width+c*2,e.height+c*2);return e.left<=d.left+d.width&&d.left<=e.left+e.width&&e.top<=d.top+d.height&&d.top<=e.top+e.height}},i:function(a){if(k(a)&&a.nodeType==1)return a;if(typeof a==="function")return V.i(a.call(null));if(k(a)){var b;a:{if(b=Ab(a)){var c=Bb[b];if(c&&typeof c.j==="function"){b=c.j(a[b],ba.document);break a}}throw new n(61,"Unsupported locator strategy: "+b);}if(!b)throw new n(7,"No element has been found by "+JSON.stringify(a));return b}throw new n(61,
"Selector is of wrong type: "+JSON.stringify(a));}};V.D={above:V.F,below:V.G,left:V.J,near:V.K,right:V.L,straightAbove:V.N,straightBelow:V.O,straightLeft:V.P,straightRight:V.R};V.C={above:V.i,below:V.i,left:V.i,near:V.i,right:V.i,straightAbove:V.i,straightBelow:V.i,straightLeft:V.i,straightRight:V.i};
V.I=function(a,b){var c=[];t(a,function(e){e&&ja(b,function(f){var g=f.kind,m=V.D[g];if(!m)throw new n(61,"Cannot find filter suitable for "+g);return m.apply(null,f.args)(e)},null)&&c.push(e)},null);a=b[b.length-1];var d=V.C[a?a.kind:"unknown"];return d?(a=d.apply(null,a.args))?V.M(a,c):c:c};
V.M=function(a,b){function c(f){f=S(f);return Math.sqrt(Math.pow(d-(f.left+Math.max(1,f.width)/2),2)+Math.pow(e-(f.top+Math.max(1,f.height)/2),2))}a=S(a);var d=a.left+Math.max(1,a.width)/2,e=a.top+Math.max(1,a.height)/2;la(b,function(f,g){return c(f)-c(g)});return b};V.j=function(a,b){a=V.h(a,b);return a.length==0?null:a[0]};
V.h=function(a,b){if(!a.hasOwnProperty("root")||!a.hasOwnProperty("filters"))throw new n(61,"Locator not suitable for relative locators: "+JSON.stringify(a));var c=a.filters,d=ca(c);if(d!="array"&&(d!="object"||typeof c.length!="number"))throw new n(61,"Targets should be an array: "+JSON.stringify(a));var e;P(a.root)?e=[a.root]:e=Cb(a.root,b);return e.length==0?[]:V.I(e,a.filters)};var Db={j:function(a,b){if(a==="")throw new n(32,'Unable to locate an element with the tagName ""');return b.getElementsByTagName(a)[0]||null},h:function(a,b){if(a==="")throw new n(32,'Unable to locate an element with the tagName ""');return b.getElementsByTagName(a)}};var W={};W.s=function(){var a={T:"http://www.w3.org/2000/svg"};return function(b){return a[b]||null}}();
W.v=function(a,b,c){var d=K(a);if(!d.documentElement)return null;try{var e=d.createNSResolver?d.createNSResolver(d.documentElement):W.s;if(D&&!Da(7))return d.evaluate.call(d,b,a,e,c,null);if(!D||Number(F)>=9){for(var f={},g=d.getElementsByTagName("*"),m=0;m<g.length;++m){var x=g[m],v=x.namespaceURI;if(v&&!f[v]){var u=x.lookupPrefix(v);if(!u){var w=v.match(".*/(\\w+)/?$");u=w?w[1]:"xhtml"}f[v]=u}}var B={},J;for(J in f)B[f[J]]=J;e=function(l){return B[l]||null}}try{return d.evaluate(b,a,e,c,null)}catch(l){if(l.name===
"TypeError")return e=d.createNSResolver?d.createNSResolver(d.documentElement):W.s,d.evaluate(b,a,e,c,null);throw l;}}catch(l){if(!wa||l.name!="NS_ERROR_ILLEGAL_VALUE")throw new n(32,"Unable to locate an element with the xpath expression "+b+" because of the following error:\n"+l);}};W.u=function(a,b){if(!a||a.nodeType!=1)throw new n(32,'The result of the xpath expression "'+b+'" is: '+a+". It should be an element.");};
W.j=function(a,b){var c=function(){var d=W.v(b,a,9);return d?d.singleNodeValue||null:b.selectSingleNode?(d=K(b),d.setProperty&&d.setProperty("SelectionLanguage","XPath"),b.selectSingleNode(a)):null}();c!==null&&W.u(c,a);return c};
W.h=function(a,b){var c=function(){var d=W.v(b,a,7);if(d){for(var e=d.snapshotLength,f=[],g=0;g<e;++g)f.push(d.snapshotItem(g));return f}return b.selectNodes?(d=K(b),d.setProperty&&d.setProperty("SelectionLanguage","XPath"),b.selectNodes(a)):[]}();t(c,function(d){W.u(d,a)});return c};var Bb={className:Ma,"class name":Ma,css:N,"css selector":N,relative:V,id:yb,linkText:U,"link text":U,name:{j:function(a,b){b=L(I(b),"*",null,b);return ka(b,function(c){return O(c,"name")==a})},h:function(a,b){b=L(I(b),"*",null,b);return fa(b,function(c){return O(c,"name")==a})}},partialLinkText:zb,"partial link text":zb,tagName:Db,"tag name":Db,xpath:W};function Ab(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null}
function Cb(a,b){var c=Ab(a);if(c){var d=Bb[c];if(d&&typeof d.h==="function")return d.h(a[c],b||ba.document)}throw new n(61,"Unsupported locator strategy: "+c);};var Eb=Cb,X=["se_exportedFunctionSymbol"],Y=h;X[0]in Y||typeof Y.execScript=="undefined"||Y.execScript("var "+X[0]);for(var Z;X.length&&(Z=X.shift());)X.length||Eb===void 0?Y[Z]&&Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=Eb;; return this.se_exportedFunctionSymbol.apply(null,arguments);}).apply(window, arguments);}
@@ -0,0 +1,17 @@
function(){return (function(){/*
Copyright The Closure Library Authors.
Copyright The Closure Compiler Authors.
SPDX-License-Identifier: Apache-2.0
*/
var d=this||self;/*
Copyright The Closure Library Authors.
SPDX-License-Identifier: Apache-2.0
*/
var f=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(typeof a==="string")return typeof b!=="string"||b.length!=1?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},h=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,e=typeof a==="string"?a.split(""):a,g=0;g<c;g++)g in e&&b.call(void 0,e[g],g,a)};function k(a,b){this.code=a;this.g=l[a]||m;this.message=b||"";a=this.g.replace(/((?:^|\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(b<0||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}(function(){function a(){}a.prototype=Error.prototype;k.prototype=new a;k.prototype.constructor=k})();var m="unknown error",l={15:"element not selectable",11:"element not visible"};l[31]=m;l[30]=m;l[24]="invalid cookie domain";
l[29]="invalid element coordinates";l[12]="invalid element state";l[32]="invalid selector";l[51]="invalid selector";l[52]="invalid selector";l[17]="javascript error";l[405]="unsupported operation";l[34]="move target out of bounds";l[27]="no such alert";l[7]="no such element";l[8]="no such frame";l[23]="no such window";l[28]="script timeout";l[33]="session not created";l[10]="stale element reference";l[21]="timeout";l[25]="unable to set cookie";l[26]="unexpected alert open";l[13]=m;l[9]="unknown command";function n(){var a=d.navigator;return a&&(a=a.userAgent)?a:""}function p(a){return n().indexOf(a)!=-1};function q(){return p("Firefox")||p("FxiOS")}function r(){return(p("Chrome")||p("CriOS"))&&!p("Edge")||p("Silk")};function t(){return p("iPhone")&&!p("iPod")&&!p("iPad")};var u=p("Opera"),v=p("Trident")||p("MSIE"),w=p("Edge"),x=p("Gecko")&&!(n().toLowerCase().indexOf("webkit")!=-1&&!p("Edge"))&&!(p("Trident")||p("MSIE"))&&!p("Edge"),y=n().toLowerCase().indexOf("webkit")!=-1&&!p("Edge");function A(){var a=d.document;return a?a.documentMode:void 0}var B;
a:{var C="",D=function(){var a=n();if(x)return/rv:([^\);]+)(\)|;)/.exec(a);if(w)return/Edge\/([\d\.]+)/.exec(a);if(v)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(y)return/WebKit\/(\S+)/.exec(a);if(u)return/(?:Version)[ \/]?(\S+)/.exec(a)}();D&&(C=D?D[1]:"");if(v){var E=A();if(E!=null&&E>parseFloat(C)){B=String(E);break a}}B=C}var F;if(d.document&&v){var G=A();F=G?G:parseInt(B,10)||void 0}else F=void 0;var H=F;var I=q(),J=t()||p("iPod"),K=p("iPad"),L=p("Android")&&!(r()||q()||p("Opera")||p("Silk")),M=r(),N=p("Safari")&&!(r()||p("Coast")||p("Opera")||p("Edge")||p("Edg/")||p("OPR")||q()||p("Silk")||p("Android"))&&!(t()||p("iPad")||p("iPod"));function O(a){return(a=a.exec(n()))?a[1]:""}(function(){if(I)return O(/Firefox\/([0-9.]+)/);if(v||w||u)return B;if(M){if(t()||p("iPad")||p("iPod")||p("Macintosh")){var a=O(/CriOS\/([0-9.]+)/);if(a)return a}return O(/Chrome\/([0-9.]+)/)}if(N&&!(t()||p("iPad")||p("iPod")))return O(/Version\/([0-9.]+)/);if(J||K){if(a=/Version\/(\S+).*Mobile\/(\S+)/.exec(n()))return a[1]+"."+a[2]}else if(L)return(a=O(/Android\s+([0-9.]+)/))?a:O(/Version\/([0-9.]+)/);return""})();var P=v&&!(Number(H)>=8),aa=v&&!(Number(H)>=9);var ba={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},Q={IMG:" ",BR:"\n"};function R(a,b,c){if(!(a.nodeName in ba))if(a.nodeType==3)c?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in Q)b.push(Q[a.nodeName]);else for(a=a.firstChild;a;)R(a,b,c),a=a.nextSibling};function S(a,b){b=b.toLowerCase();return b=="style"?ca(a.style.cssText):P&&b=="value"&&T(a,"INPUT")?a.value:aa&&a[b]===!0?String(a.getAttribute(b)):(a=a.getAttributeNode(b))&&a.specified?a.value:null}var da=RegExp("[;]+(?=(?:(?:[^\"]*\"){2})*[^\"]*$)(?=(?:(?:[^']*'){2})*[^']*$)(?=(?:[^()]*\\([^()]*\\))*[^()]*$)");
function ca(a){var b=[];h(a.split(da),function(c){var e=c.indexOf(":");e>0&&(c=[c.slice(0,e),c.slice(e+1)],c.length==2&&b.push(c[0].toLowerCase(),":",c[1],";"))});b=b.join("");return b=b.charAt(b.length-1)==";"?b:b+";"}function U(a,b){P&&b=="value"&&T(a,"OPTION")&&S(a,"value")===null?(b=[],R(a,b,!1),a=b.join("")):a=a[b];return a}
function T(a,b){b&&typeof b!=="string"&&(b=b.toString());return a instanceof HTMLFormElement?!!a&&a.nodeType==1&&(!b||"FORM"==b):!!a&&a.nodeType==1&&(!b||a.tagName.toUpperCase()==b)}function V(a){return T(a,"OPTION")?!0:T(a,"INPUT")?(a=a.type.toLowerCase(),a=="checkbox"||a=="radio"):!1};var ea={"class":"className",readonly:"readOnly"},fa="allowfullscreen allowpaymentrequest allowusermedia async autofocus autoplay checked compact complete controls declare default defaultchecked defaultselected defer disabled ended formnovalidate hidden indeterminate iscontenteditable ismap itemscope loop multiple muted nohref nomodule noresize noshade novalidate nowrap open paused playsinline pubdate readonly required reversed scoped seamless seeking selected truespeed typemustmatch willvalidate".split(" ");function W(a,b){var c=null,e=b.toLowerCase();if("style"==e)return(c=a.style)&&typeof c!=="string"&&(c=c.cssText),c;if(("selected"==e||"checked"==e)&&V(a)){if(!V(a))throw new k(15,"Element is not selectable");b="selected";c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";return U(a,b)?"true":null}var g=T(a,"A");if(T(a,"IMG")&&e=="src"||g&&e=="href")return(c=S(a,e))&&(c=U(a,e)),c;if("spellcheck"==e){c=S(a,e);if(c!==null){if(c.toLowerCase()=="false")return"false";if(c.toLowerCase()==
"true")return"true"}return U(a,e)+""}g=ea[b]||b;if(f(fa,e)>=0)return(c=S(a,b)!==null||U(a,g))?"true":null;try{var z=U(a,g)}catch(ha){}(e=z==null)||(e=typeof z,e=e=="object"&&z!=null||e=="function");e?c=S(a,b):c=z;return c!=null?c.toString():null}var X=["se_exportedFunctionSymbol"],Y=d;X[0]in Y||typeof Y.execScript=="undefined"||Y.execScript("var "+X[0]);for(var Z;X.length&&(Z=X.shift());)X.length||W===void 0?Y[Z]&&Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=W;; return this.se_exportedFunctionSymbol.apply(null,arguments);}).apply(window, arguments);}
@@ -0,0 +1,39 @@
function(){return (function(){/*
Copyright The Closure Library Authors.
Copyright The Closure Compiler Authors.
SPDX-License-Identifier: Apache-2.0
*/
var aa=aa||{},g=this||self;/*
Copyright The Closure Library Authors.
SPDX-License-Identifier: Apache-2.0
*/
var ca=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(typeof a==="string")return typeof b!=="string"||b.length!=1?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},da=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,e=typeof a==="string"?a.split(""):a,d=0;d<c;d++)if(d in e&&b.call(void 0,e[d],d,a))return!0;return!1},ea=Array.prototype.every?function(a,
b){return Array.prototype.every.call(a,b,void 0)}:function(a,b){for(var c=a.length,e=typeof a==="string"?a.split(""):a,d=0;d<c;d++)if(d in e&&!b.call(void 0,e[d],d,a))return!1;return!0};var fa={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",
darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",
ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",
lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var ha="backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor".split(" "),ia=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,ja=/^#(?:[0-9a-f]{3}){1,2}$/i,ka=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i,la=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function m(a,b){this.code=a;this.g=p[a]||q;this.message=b||"";a=this.g.replace(/((?:^|\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(b<0||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}(function(){function a(){}a.prototype=Error.prototype;m.prototype=new a;m.prototype.constructor=m})();var q="unknown error",p={15:"element not selectable",11:"element not visible"};p[31]=q;p[30]=q;p[24]="invalid cookie domain";
p[29]="invalid element coordinates";p[12]="invalid element state";p[32]="invalid selector";p[51]="invalid selector";p[52]="invalid selector";p[17]="javascript error";p[405]="unsupported operation";p[34]="move target out of bounds";p[27]="no such alert";p[7]="no such element";p[8]="no such frame";p[23]="no such window";p[28]="script timeout";p[33]="session not created";p[10]="stale element reference";p[21]="timeout";p[25]="unable to set cookie";p[26]="unexpected alert open";p[13]=q;p[9]="unknown command";var u=aa.i&&String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\s\xa0]*([\s\S]*?)[\s\xa0]*$/.exec(a)[1]};
function ma(a){var b=0;a=u(String(a)).split(".");for(var c=u("8").split("."),e=Math.max(a.length,c.length),d=0;b==0&&d<e;d++){var f=a[d]||"",h=c[d]||"";do{f=/(\d*)(\D*)(.*)/.exec(f)||["","","",""];h=/(\d*)(\D*)(.*)/.exec(h)||["","","",""];if(f[0].length==0&&h[0].length==0)break;b=w(f[1].length==0?0:parseInt(f[1],10),h[1].length==0?0:parseInt(h[1],10))||w(f[2].length==0,h[2].length==0)||w(f[2],h[2]);f=f[3];h=h[3]}while(b==0)}return b}function w(a,b){return a<b?-1:a>b?1:0};function x(){var a=g.navigator;return a&&(a=a.userAgent)?a:""}function y(a){return x().indexOf(a)!=-1};function A(){return y("Firefox")||y("FxiOS")}function B(){return(y("Chrome")||y("CriOS"))&&!y("Edge")||y("Silk")};function na(a){return String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()})};function C(){return y("iPhone")&&!y("iPod")&&!y("iPad")};function ra(a){var b=sa;return Object.prototype.hasOwnProperty.call(b,8)?b[8]:b[8]=a(8)};var ta=y("Opera"),D=y("Trident")||y("MSIE"),ua=y("Edge"),va=y("Gecko")&&!(x().toLowerCase().indexOf("webkit")!=-1&&!y("Edge"))&&!(y("Trident")||y("MSIE"))&&!y("Edge"),wa=x().toLowerCase().indexOf("webkit")!=-1&&!y("Edge");function xa(){var a=g.document;return a?a.documentMode:void 0}var E;
a:{var F="",G=function(){var a=x();if(va)return/rv:([^\);]+)(\)|;)/.exec(a);if(ua)return/Edge\/([\d\.]+)/.exec(a);if(D)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(wa)return/WebKit\/(\S+)/.exec(a);if(ta)return/(?:Version)[ \/]?(\S+)/.exec(a)}();G&&(F=G?G[1]:"");if(D){var H=xa();if(H!=null&&H>parseFloat(F)){E=String(H);break a}}E=F}var sa={};function ya(){return ra(function(){return ma(E)>=0})}var I;if(g.document&&D){var za=xa();I=za?za:parseInt(E,10)||void 0}else I=void 0;var Aa=I;var Ba=A(),Ca=C()||y("iPod"),Da=y("iPad"),Ea=y("Android")&&!(B()||A()||y("Opera")||y("Silk")),Fa=B(),Ga=y("Safari")&&!(B()||y("Coast")||y("Opera")||y("Edge")||y("Edg/")||y("OPR")||A()||y("Silk")||y("Android"))&&!(C()||y("iPad")||y("iPod"));function J(a){return(a=a.exec(x()))?a[1]:""}(function(){if(Ba)return J(/Firefox\/([0-9.]+)/);if(D||ua||ta)return E;if(Fa){if(C()||y("iPad")||y("iPod")||y("Macintosh")){var a=J(/CriOS\/([0-9.]+)/);if(a)return a}return J(/Chrome\/([0-9.]+)/)}if(Ga&&!(C()||y("iPad")||y("iPod")))return J(/Version\/([0-9.]+)/);if(Ca||Da){if(a=/Version\/(\S+).*Mobile\/(\S+)/.exec(x()))return a[1]+"."+a[2]}else if(Ea)return(a=J(/Android\s+([0-9.]+)/))?a:J(/Version\/([0-9.]+)/);return""})();var K;if(K=D)K=!(Number(Aa)>=9);var Ha=K;function L(a,b){this.x=a!==void 0?a:0;this.y=b!==void 0?b:0}L.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};L.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};L.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function M(a,b){this.width=a;this.height=b}M.prototype.aspectRatio=function(){return this.width/this.height};M.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};M.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};M.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function N(a){return a.nodeType==9?a:a.ownerDocument||a.document}function Ia(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function Ja(a){this.g=a||g.document||document};function O(a,b){b&&typeof b!=="string"&&(b=b.toString());return a instanceof HTMLFormElement?!!a&&a.nodeType==1&&(!b||"FORM"==b):!!a&&a.nodeType==1&&(!b||a.tagName.toUpperCase()==b)};function Q(a,b,c,e){this.top=a;this.g=b;this.h=c;this.left=e}Q.prototype.ceil=function(){this.top=Math.ceil(this.top);this.g=Math.ceil(this.g);this.h=Math.ceil(this.h);this.left=Math.ceil(this.left);return this};Q.prototype.floor=function(){this.top=Math.floor(this.top);this.g=Math.floor(this.g);this.h=Math.floor(this.h);this.left=Math.floor(this.left);return this};
Q.prototype.round=function(){this.top=Math.round(this.top);this.g=Math.round(this.g);this.h=Math.round(this.h);this.left=Math.round(this.left);return this};function R(a,b,c,e){this.left=a;this.top=b;this.width=c;this.height=e}R.prototype.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};R.prototype.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
R.prototype.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var Ka=typeof ShadowRoot==="function";function S(a){for(a=a.parentNode;a&&a.nodeType!=1&&a.nodeType!=9&&a.nodeType!=11;)a=a.parentNode;return O(a)?a:null}
function T(a,b){b=na(b);if(b=="float"||b=="cssFloat"||b=="styleFloat")b=Ha?"styleFloat":"cssFloat";a:{var c=b;var e=N(a);if(e.defaultView&&e.defaultView.getComputedStyle&&(e=e.defaultView.getComputedStyle(a,null))){c=e[c]||e.getPropertyValue(c)||"";break a}c=""}a=c||La(a,b);if(a===null)a=null;else if(ca(ha,b)>=0){b:{var d=a.match(ka);if(d&&(b=Number(d[1]),c=Number(d[2]),e=Number(d[3]),d=Number(d[4]),b>=0&&b<=255&&c>=0&&c<=255&&e>=0&&e<=255&&d>=0&&d<=1)){b=[b,c,e,d];break b}b=null}if(!b)b:{if(e=a.match(la))if(b=
Number(e[1]),c=Number(e[2]),e=Number(e[3]),b>=0&&b<=255&&c>=0&&c<=255&&e>=0&&e<=255){b=[b,c,e,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=fa[b.toLowerCase()];if(!c&&(c=b.charAt(0)=="#"?b:"#"+b,c.length==4&&(c=c.replace(ia,"#$1$1$2$2$3$3")),!ja.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?"rgba("+b.join(", ")+")":a}return a}
function La(a,b){var c=a.currentStyle||a.style,e=c[b];e===void 0&&typeof c.getPropertyValue==="function"&&(e=c.getPropertyValue(b));return e!="inherit"?e!==void 0?e:null:(a=S(a))?La(a,b):null}
function U(a,b,c){function e(h){var n=V(h);if(n.height>0&&n.width>0)return!0;if(O(h,"PATH")&&(n.height>0||n.width>0))return h=T(h,"stroke-width"),!!h&&parseInt(h,10)>0;n=T(h,"visibility");return n!="collapse"&&n!="hidden"&&c(h)?T(h,"overflow")!="hidden"&&da(h.childNodes,function(v){return v.nodeType==3?(v=v.nodeValue,/^[\s]*$/.test(v)&&/[\n\r\t]/.test(v)?!1:!0):O(v)&&e(v)}):!1}function d(h){return Ma(h)==W&&ea(h.childNodes,function(n){return!O(n)||d(n)||!e(n)})}if(!O(a))throw Error("Argument to isShown must be of type Element");
if(O(a,"BODY"))return!0;if(O(a,"OPTION")||O(a,"OPTGROUP"))return a=Ia(a,function(h){return O(h,"SELECT")}),!!a&&U(a,!0,c);var f=Na(a);if(f)return!!f.image&&f.rect.width>0&&f.rect.height>0&&U(f.image,b,c);if(O(a,"INPUT")&&a.type.toLowerCase()=="hidden"||O(a,"NOSCRIPT"))return!1;f=T(a,"visibility");return f!="collapse"&&f!="hidden"&&c(a)&&(b||Oa(a)!=0)&&e(a)?!d(a):!1}var W="hidden";
function Ma(a){function b(k){function l(ba){if(ba==h)return!0;var oa=T(ba,"display");return oa.lastIndexOf("inline",0)==0||oa=="contents"||pa=="absolute"&&T(ba,"position")=="static"?!1:!0}var pa=T(k,"position");if(pa=="fixed")return qa=!0,k==h?null:h;for(k=S(k);k&&!l(k);)k=S(k);return k}function c(k){var l=k;if(v=="visible")if(k==h&&n)l=n;else if(k==n)return{x:"visible",y:"visible"};l={x:T(l,"overflow-x"),y:T(l,"overflow-y")};k==h&&(l.x=l.x=="visible"?"auto":l.x,l.y=l.y=="visible"?"auto":l.y);return l}
function e(k){if(k==h){var l=(new Ja(f)).g;k=l.scrollingElement?l.scrollingElement:wa||l.compatMode!="CSS1Compat"?l.body||l.documentElement:l.documentElement;l=l.parentWindow||l.defaultView;k=D&&l.pageYOffset!=k.scrollTop?new L(k.scrollLeft,k.scrollTop):new L(l.pageXOffset||k.scrollLeft,l.pageYOffset||k.scrollTop)}else k=new L(k.scrollLeft,k.scrollTop);return k}var d=Pa(a),f=N(a),h=f.documentElement,n=f.body,v=T(h,"overflow"),qa;for(a=b(a);a;a=b(a)){var r=c(a);if(r.x!="visible"||r.y!="visible"){var t=
V(a);if(t.width==0||t.height==0)return W;var z=d.g<t.left,P=d.h<t.top;if(z&&r.x=="hidden"||P&&r.y=="hidden")return W;if(z&&r.x!="visible"||P&&r.y!="visible"){z=e(a);P=d.h<t.top-z.y;if(d.g<t.left-z.x&&r.x!="visible"||P&&r.x!="visible")return W;d=Ma(a);return d==W?W:"scroll"}z=d.left>=t.left+t.width;t=d.top>=t.top+t.height;if(z&&r.x=="hidden"||t&&r.y=="hidden")return W;if(z&&r.x!="visible"||t&&r.y!="visible"){if(qa&&(r=e(a),d.left>=h.scrollWidth-r.x||d.g>=h.scrollHeight-r.y))return W;d=Ma(a);return d==
W?W:"scroll"}}}return"none"}
function V(a){var b=Na(a);if(b)return b.rect;if(O(a,"HTML"))return a=N(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a=a.compatMode=="CSS1Compat"?a.documentElement:a.body,a=new M(a.clientWidth,a.clientHeight),new R(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(e){return new R(0,0,0,0)}b=new R(c.left,c.top,c.right-c.left,c.bottom-c.top);D&&a.ownerDocument.body&&(a=N(a),b.left-=a.documentElement.clientLeft+a.body.clientLeft,b.top-=a.documentElement.clientTop+a.body.clientTop);
return b}
function Na(a){var b=O(a,"MAP");if(!b&&!O(a,"AREA"))return null;var c=b?a:O(a.parentNode,"MAP")?a.parentNode:null,e=null,d=null;if(c&&c.name){e='*[usemap="#'+c.name+'"]';c=N(c);var f;if(f=typeof c.querySelector!=="function"&&D&&(D?ma(Aa)>=0:ya())){f=c.querySelector;var h=typeof f;f=!(h=="object"&&f!=null||h=="function")}if(f)throw Error("CSS selection is not supported");if(!e)throw new m(32,"No selector specified");e=u(e);try{var n=c.querySelector(e)}catch(v){throw new m(32,"An invalid or illegal selector was specified");}if(e=
n&&n.nodeType==1?n:null)d=V(e),b||a.shape.toLowerCase()=="default"||(a=Qa(a),b=Math.min(Math.max(a.left,0),d.width),n=Math.min(Math.max(a.top,0),d.height),d=new R(b+d.left,n+d.top,Math.min(a.width,d.width-b),Math.min(a.height,d.height-n)))}return{image:e,rect:d||new R(0,0,0,0)}}
function Qa(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if(b=="rect"&&a.length==4){b=a[0];var c=a[1];return new R(b,c,a[2]-b,a[3]-c)}if(b=="circle"&&a.length==3)return b=a[2],new R(a[0]-b,a[1]-b,2*b,2*b);if(b=="poly"&&a.length>2){b=a[0];c=a[1];for(var e=b,d=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),e=Math.max(e,a[f]),c=Math.min(c,a[f+1]),d=Math.max(d,a[f+1]);return new R(b,c,e-b,d-c)}return new R(0,0,0,0)}function Pa(a){a=V(a);return new Q(a.top,a.left+a.width,a.top+a.height,a.left)}
function Oa(a){if(Ha){if(T(a,"position")=="relative")return 1;a=T(a,"filter");return(a=a.match(/^alpha\(opacity=(\d*)\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\(Opacity=(\d*)\)/))?Number(a[1])/100:1}return Ra(a)}function Ra(a){var b=1,c=T(a,"opacity");c&&(b=Number(c));(a=S(a))&&(b*=Ra(a));return b};function Sa(a,b){function c(e){if(O(e)&&(T(e,"display")=="none"||T(e,"content-visibility")=="hidden"))return!1;var d;if((d=e.parentNode)&&d.shadowRoot&&e.assignedSlot!==void 0)d=e.assignedSlot?e.assignedSlot.parentNode:null;else if(e.getDestinationInsertionPoints){var f=e.getDestinationInsertionPoints();f.length>0&&(d=f[f.length-1])}if(Ka&&d instanceof ShadowRoot){if(d.host.shadowRoot&&d.host.shadowRoot!==d)return!1;d=d.host}return!d||d.nodeType!=9&&d.nodeType!=11?d&&O(d,"DETAILS")&&!d.open&&!O(e,
"SUMMARY")?!1:!!d&&c(d):!0}return U(a,!!b,c)}var X=["se_exportedFunctionSymbol"],Y=g;X[0]in Y||typeof Y.execScript=="undefined"||Y.execScript("var "+X[0]);for(var Z;X.length&&(Z=X.shift());)X.length||Sa===void 0?Y[Z]&&Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=Sa;; return this.se_exportedFunctionSymbol.apply(null,arguments);}).apply(window, arguments);}
@@ -0,0 +1,33 @@
# 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.
from selenium.common.exceptions import InvalidSelectorException
from selenium.webdriver.common.by import By
class LocatorConverter:
def convert(self, by, value):
# Default conversion logic
if by == By.ID:
return By.CSS_SELECTOR, f'[id="{value}"]'
elif by == By.CLASS_NAME:
if value and any(char.isspace() for char in value.strip()):
raise InvalidSelectorException("Compound class names are not allowed.")
return By.CSS_SELECTOR, f".{value}"
elif by == By.NAME:
return By.CSS_SELECTOR, f'[name="{value}"]'
return by, value
@@ -0,0 +1,81 @@
# 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.
from selenium.webdriver.remote.command import Command
class _ConnectionType:
def __init__(self, mask):
self.mask = mask
@property
def airplane_mode(self):
return self.mask % 2 == 1
@property
def wifi(self):
return (self.mask / 2) % 2 == 1
@property
def data(self):
return (self.mask / 4) > 0
class Mobile:
ConnectionType = _ConnectionType
ALL_NETWORK = ConnectionType(6)
WIFI_NETWORK = ConnectionType(2)
DATA_NETWORK = ConnectionType(4)
AIRPLANE_MODE = ConnectionType(1)
def __init__(self, driver):
import weakref
self._driver = weakref.proxy(driver)
@property
def network_connection(self):
return self.ConnectionType(self._driver.execute(Command.GET_NETWORK_CONNECTION)["value"])
def set_network_connection(self, network):
"""Set the network connection for the remote device.
Example of setting airplane mode::
driver.mobile.set_network_connection(driver.mobile.AIRPLANE_MODE)
"""
mode = network.mask if isinstance(network, self.ConnectionType) else network
return self.ConnectionType(
self._driver.execute(
Command.SET_NETWORK_CONNECTION, {"name": "network_connection", "parameters": {"type": mode}}
)["value"]
)
@property
def context(self):
"""Returns the current context (Native or WebView)."""
return self._driver.execute(Command.CURRENT_CONTEXT_HANDLE)
@context.setter
def context(self, new_context) -> None:
"""Sets the current context."""
self._driver.execute(Command.SWITCH_TO_CONTEXT, {"name": new_context})
@property
def contexts(self):
"""Returns a list of available contexts."""
return self._driver.execute(Command.CONTEXT_HANDLES)
@@ -0,0 +1,495 @@
# 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 logging
import string
import sys
import warnings
from base64 import b64encode
from urllib import parse
from urllib.parse import unquote, urlparse
import urllib3
from selenium import __version__
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.remote import utils
from selenium.webdriver.remote.client_config import ClientConfig
from selenium.webdriver.remote.command import Command
from selenium.webdriver.remote.errorhandler import ErrorCode
LOGGER = logging.getLogger(__name__)
remote_commands = {
Command.NEW_SESSION: ("POST", "/session"),
Command.QUIT: ("DELETE", "/session/$sessionId"),
Command.W3C_GET_CURRENT_WINDOW_HANDLE: ("GET", "/session/$sessionId/window"),
Command.W3C_GET_WINDOW_HANDLES: ("GET", "/session/$sessionId/window/handles"),
Command.GET: ("POST", "/session/$sessionId/url"),
Command.GO_FORWARD: ("POST", "/session/$sessionId/forward"),
Command.GO_BACK: ("POST", "/session/$sessionId/back"),
Command.REFRESH: ("POST", "/session/$sessionId/refresh"),
Command.W3C_EXECUTE_SCRIPT: ("POST", "/session/$sessionId/execute/sync"),
Command.W3C_EXECUTE_SCRIPT_ASYNC: ("POST", "/session/$sessionId/execute/async"),
Command.GET_CURRENT_URL: ("GET", "/session/$sessionId/url"),
Command.GET_TITLE: ("GET", "/session/$sessionId/title"),
Command.GET_PAGE_SOURCE: ("GET", "/session/$sessionId/source"),
Command.SCREENSHOT: ("GET", "/session/$sessionId/screenshot"),
Command.ELEMENT_SCREENSHOT: ("GET", "/session/$sessionId/element/$id/screenshot"),
Command.FIND_ELEMENT: ("POST", "/session/$sessionId/element"),
Command.FIND_ELEMENTS: ("POST", "/session/$sessionId/elements"),
Command.W3C_GET_ACTIVE_ELEMENT: ("GET", "/session/$sessionId/element/active"),
Command.FIND_CHILD_ELEMENT: ("POST", "/session/$sessionId/element/$id/element"),
Command.FIND_CHILD_ELEMENTS: ("POST", "/session/$sessionId/element/$id/elements"),
Command.CLICK_ELEMENT: ("POST", "/session/$sessionId/element/$id/click"),
Command.CLEAR_ELEMENT: ("POST", "/session/$sessionId/element/$id/clear"),
Command.GET_ELEMENT_TEXT: ("GET", "/session/$sessionId/element/$id/text"),
Command.SEND_KEYS_TO_ELEMENT: ("POST", "/session/$sessionId/element/$id/value"),
Command.GET_ELEMENT_TAG_NAME: ("GET", "/session/$sessionId/element/$id/name"),
Command.IS_ELEMENT_SELECTED: ("GET", "/session/$sessionId/element/$id/selected"),
Command.IS_ELEMENT_ENABLED: ("GET", "/session/$sessionId/element/$id/enabled"),
Command.GET_ELEMENT_RECT: ("GET", "/session/$sessionId/element/$id/rect"),
Command.GET_ELEMENT_ATTRIBUTE: ("GET", "/session/$sessionId/element/$id/attribute/$name"),
Command.GET_ELEMENT_PROPERTY: ("GET", "/session/$sessionId/element/$id/property/$name"),
Command.GET_ELEMENT_ARIA_ROLE: ("GET", "/session/$sessionId/element/$id/computedrole"),
Command.GET_ELEMENT_ARIA_LABEL: ("GET", "/session/$sessionId/element/$id/computedlabel"),
Command.GET_SHADOW_ROOT: ("GET", "/session/$sessionId/element/$id/shadow"),
Command.FIND_ELEMENT_FROM_SHADOW_ROOT: ("POST", "/session/$sessionId/shadow/$shadowId/element"),
Command.FIND_ELEMENTS_FROM_SHADOW_ROOT: ("POST", "/session/$sessionId/shadow/$shadowId/elements"),
Command.GET_ALL_COOKIES: ("GET", "/session/$sessionId/cookie"),
Command.ADD_COOKIE: ("POST", "/session/$sessionId/cookie"),
Command.GET_COOKIE: ("GET", "/session/$sessionId/cookie/$name"),
Command.DELETE_ALL_COOKIES: ("DELETE", "/session/$sessionId/cookie"),
Command.DELETE_COOKIE: ("DELETE", "/session/$sessionId/cookie/$name"),
Command.SWITCH_TO_FRAME: ("POST", "/session/$sessionId/frame"),
Command.SWITCH_TO_PARENT_FRAME: ("POST", "/session/$sessionId/frame/parent"),
Command.SWITCH_TO_WINDOW: ("POST", "/session/$sessionId/window"),
Command.NEW_WINDOW: ("POST", "/session/$sessionId/window/new"),
Command.CLOSE: ("DELETE", "/session/$sessionId/window"),
Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY: ("GET", "/session/$sessionId/element/$id/css/$propertyName"),
Command.EXECUTE_ASYNC_SCRIPT: ("POST", "/session/$sessionId/execute_async"),
Command.SET_TIMEOUTS: ("POST", "/session/$sessionId/timeouts"),
Command.GET_TIMEOUTS: ("GET", "/session/$sessionId/timeouts"),
Command.W3C_DISMISS_ALERT: ("POST", "/session/$sessionId/alert/dismiss"),
Command.W3C_ACCEPT_ALERT: ("POST", "/session/$sessionId/alert/accept"),
Command.W3C_SET_ALERT_VALUE: ("POST", "/session/$sessionId/alert/text"),
Command.W3C_GET_ALERT_TEXT: ("GET", "/session/$sessionId/alert/text"),
Command.W3C_ACTIONS: ("POST", "/session/$sessionId/actions"),
Command.W3C_CLEAR_ACTIONS: ("DELETE", "/session/$sessionId/actions"),
Command.SET_WINDOW_RECT: ("POST", "/session/$sessionId/window/rect"),
Command.GET_WINDOW_RECT: ("GET", "/session/$sessionId/window/rect"),
Command.W3C_MAXIMIZE_WINDOW: ("POST", "/session/$sessionId/window/maximize"),
Command.SET_SCREEN_ORIENTATION: ("POST", "/session/$sessionId/orientation"),
Command.GET_SCREEN_ORIENTATION: ("GET", "/session/$sessionId/orientation"),
Command.GET_NETWORK_CONNECTION: ("GET", "/session/$sessionId/network_connection"),
Command.SET_NETWORK_CONNECTION: ("POST", "/session/$sessionId/network_connection"),
Command.GET_LOG: ("POST", "/session/$sessionId/se/log"),
Command.GET_AVAILABLE_LOG_TYPES: ("GET", "/session/$sessionId/se/log/types"),
Command.CURRENT_CONTEXT_HANDLE: ("GET", "/session/$sessionId/context"),
Command.CONTEXT_HANDLES: ("GET", "/session/$sessionId/contexts"),
Command.SWITCH_TO_CONTEXT: ("POST", "/session/$sessionId/context"),
Command.FULLSCREEN_WINDOW: ("POST", "/session/$sessionId/window/fullscreen"),
Command.MINIMIZE_WINDOW: ("POST", "/session/$sessionId/window/minimize"),
Command.PRINT_PAGE: ("POST", "/session/$sessionId/print"),
Command.ADD_VIRTUAL_AUTHENTICATOR: ("POST", "/session/$sessionId/webauthn/authenticator"),
Command.REMOVE_VIRTUAL_AUTHENTICATOR: (
"DELETE",
"/session/$sessionId/webauthn/authenticator/$authenticatorId",
),
Command.ADD_CREDENTIAL: ("POST", "/session/$sessionId/webauthn/authenticator/$authenticatorId/credential"),
Command.GET_CREDENTIALS: ("GET", "/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials"),
Command.REMOVE_CREDENTIAL: (
"DELETE",
"/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials/$credentialId",
),
Command.REMOVE_ALL_CREDENTIALS: (
"DELETE",
"/session/$sessionId/webauthn/authenticator/$authenticatorId/credentials",
),
Command.SET_USER_VERIFIED: ("POST", "/session/$sessionId/webauthn/authenticator/$authenticatorId/uv"),
Command.UPLOAD_FILE: ("POST", "/session/$sessionId/se/file"),
Command.GET_DOWNLOADABLE_FILES: ("GET", "/session/$sessionId/se/files"),
Command.DOWNLOAD_FILE: ("POST", "/session/$sessionId/se/files"),
Command.DELETE_DOWNLOADABLE_FILES: ("DELETE", "/session/$sessionId/se/files"),
Command.FIRE_SESSION_EVENT: ("POST", "/session/$sessionId/se/event"),
# Federated Credential Management (FedCM)
Command.GET_FEDCM_TITLE: ("GET", "/session/$sessionId/fedcm/gettitle"),
Command.GET_FEDCM_DIALOG_TYPE: ("GET", "/session/$sessionId/fedcm/getdialogtype"),
Command.GET_FEDCM_ACCOUNT_LIST: ("GET", "/session/$sessionId/fedcm/accountlist"),
Command.CLICK_FEDCM_DIALOG_BUTTON: ("POST", "/session/$sessionId/fedcm/clickdialogbutton"),
Command.CANCEL_FEDCM_DIALOG: ("POST", "/session/$sessionId/fedcm/canceldialog"),
Command.SELECT_FEDCM_ACCOUNT: ("POST", "/session/$sessionId/fedcm/selectaccount"),
Command.SET_FEDCM_DELAY: ("POST", "/session/$sessionId/fedcm/setdelayenabled"),
Command.RESET_FEDCM_COOLDOWN: ("POST", "/session/$sessionId/fedcm/resetcooldown"),
}
class RemoteConnection:
"""A connection with the Remote WebDriver server.
Communicates with the server using the WebDriver wire protocol:
https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
"""
browser_name: str | None = None
# Keep backward compatibility for AppiumConnection - https://github.com/SeleniumHQ/selenium/issues/14694
import os
import socket
import certifi
_timeout = socket.getdefaulttimeout()
_ca_certs = os.getenv("REQUESTS_CA_BUNDLE") if "REQUESTS_CA_BUNDLE" in os.environ else certifi.where()
_client_config: ClientConfig
system = sys.platform
if system == "darwin":
system = "mac"
# Class variables for headers
extra_headers = None
user_agent = f"selenium/{__version__} (python {system})"
@property
def client_config(self):
return self._client_config
@classmethod
def get_timeout(cls):
"""Returns timeout value in seconds for all http requests made to the Remote Connection.
Returns:
Timeout value in seconds for all http requests made to the
Remote Connection
"""
warnings.warn(
"get_timeout() in RemoteConnection is deprecated, get timeout from client_config instead",
DeprecationWarning,
stacklevel=2,
)
return cls._client_config.timeout
@classmethod
def set_timeout(cls, timeout):
"""Override the default timeout.
Args:
timeout: timeout value for http requests in seconds
"""
warnings.warn(
"set_timeout() in RemoteConnection is deprecated, set timeout in client_config instead",
DeprecationWarning,
stacklevel=2,
)
cls._client_config.timeout = timeout
@classmethod
def reset_timeout(cls):
"""Reset the http request timeout to socket._GLOBAL_DEFAULT_TIMEOUT."""
warnings.warn(
"reset_timeout() in RemoteConnection is deprecated, use reset_timeout() in client_config instead",
DeprecationWarning,
stacklevel=2,
)
cls._client_config.reset_timeout()
@classmethod
def get_certificate_bundle_path(cls):
"""Returns paths of the .pem encoded certificate to verify connection to command executor.
Returns:
Paths of the .pem encoded certificate to verify connection to
command executor. Defaults to certifi.where() or
REQUESTS_CA_BUNDLE env variable if set.
"""
warnings.warn(
"get_certificate_bundle_path() in RemoteConnection is deprecated, get ca_certs from client_config instead",
DeprecationWarning,
stacklevel=2,
)
return cls._client_config.ca_certs
@classmethod
def set_certificate_bundle_path(cls, path):
"""Set the path to the certificate bundle for verifying command executor connection.
Can also be set to None to disable certificate validation.
Args:
path: path of a .pem encoded certificate chain.
"""
warnings.warn(
"set_certificate_bundle_path() in RemoteConnection is deprecated, set ca_certs in client_config instead",
DeprecationWarning,
stacklevel=2,
)
cls._client_config.ca_certs = path
@classmethod
def get_remote_connection_headers(cls, parsed_url, keep_alive=False):
"""Get headers for remote request.
Args:
parsed_url: The parsed url
keep_alive: Is this a keep-alive connection (default: False)
"""
headers = {
"Accept": "application/json",
"Content-Type": "application/json;charset=UTF-8",
"User-Agent": cls.user_agent,
}
if parsed_url.username:
warnings.warn(
"Embedding username and password in URL could be insecure, use ClientConfig instead", stacklevel=2
)
base64string = b64encode(f"{parsed_url.username}:{parsed_url.password}".encode())
headers.update({"Authorization": f"Basic {base64string.decode()}"})
if keep_alive:
headers.update({"Connection": "keep-alive"})
if cls.extra_headers:
headers.update(cls.extra_headers)
return headers
def _identify_http_proxy_auth(self):
parsed_url = urlparse(self._proxy_url)
if parsed_url.username and parsed_url.password:
return True
def _separate_http_proxy_auth(self):
parsed_url = urlparse(self._proxy_url)
proxy_without_auth = f"{parsed_url.scheme}://{parsed_url.hostname}:{parsed_url.port}"
auth = f"{parsed_url.username}:{parsed_url.password}"
return proxy_without_auth, auth
def _get_connection_manager(self):
pool_manager_init_args = {"timeout": self._client_config.timeout}
pool_manager_init_args.update(
self._client_config.init_args_for_pool_manager.get("init_args_for_pool_manager", {})
)
if self._client_config.ignore_certificates:
pool_manager_init_args["cert_reqs"] = "CERT_NONE"
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
elif self._client_config.ca_certs:
pool_manager_init_args["cert_reqs"] = "CERT_REQUIRED"
pool_manager_init_args["ca_certs"] = self._client_config.ca_certs
if self._proxy_url:
if self._proxy_url.lower().startswith("sock"):
from urllib3.contrib.socks import SOCKSProxyManager
return SOCKSProxyManager(self._proxy_url, **pool_manager_init_args)
if self._identify_http_proxy_auth():
self._proxy_url, self._basic_proxy_auth = self._separate_http_proxy_auth()
pool_manager_init_args["proxy_headers"] = urllib3.make_headers(
proxy_basic_auth=unquote(self._basic_proxy_auth)
)
return urllib3.ProxyManager(self._proxy_url, **pool_manager_init_args)
return urllib3.PoolManager(**pool_manager_init_args)
def __init__(
self,
remote_server_addr: str | None = None,
keep_alive: bool = True,
ignore_proxy: bool = False,
ignore_certificates: bool | None = False,
init_args_for_pool_manager: dict | None = None,
client_config: ClientConfig | None = None,
):
if client_config:
self._client_config = client_config
elif remote_server_addr:
self._client_config = ClientConfig(
remote_server_addr=remote_server_addr,
keep_alive=keep_alive,
ignore_certificates=ignore_certificates,
init_args_for_pool_manager=init_args_for_pool_manager,
)
else:
raise WebDriverException("Must provide either 'remote_server_addr' or 'client_config'")
# Keep backward compatibility for AppiumConnection - https://github.com/SeleniumHQ/selenium/issues/14694
RemoteConnection._timeout = self._client_config.timeout
RemoteConnection._ca_certs = self._client_config.ca_certs
RemoteConnection._client_config = self._client_config
RemoteConnection.extra_headers = self._client_config.extra_headers or RemoteConnection.extra_headers
RemoteConnection.user_agent = self._client_config.user_agent or RemoteConnection.user_agent
if remote_server_addr:
warnings.warn(
"setting remote_server_addr in RemoteConnection() is deprecated, set in client_config instead",
DeprecationWarning,
stacklevel=2,
)
if not keep_alive:
warnings.warn(
"setting keep_alive in RemoteConnection() is deprecated, set in client_config instead",
DeprecationWarning,
stacklevel=2,
)
if ignore_certificates:
warnings.warn(
"setting ignore_certificates in RemoteConnection() is deprecated, set in client_config instead",
DeprecationWarning,
stacklevel=2,
)
if init_args_for_pool_manager:
warnings.warn(
"setting init_args_for_pool_manager in RemoteConnection() is deprecated, set in client_config instead",
DeprecationWarning,
stacklevel=2,
)
if ignore_proxy:
self._proxy_url = None
else:
self._proxy_url = self._client_config.get_proxy_url()
if self._client_config.keep_alive:
self._conn = self._get_connection_manager()
self._commands = remote_commands
extra_commands: dict[str, str] = {}
def add_command(self, name, method, url):
"""Register a new command."""
self._commands[name] = (method, url)
def get_command(self, name: str):
"""Retrieve a command if it exists."""
return self._commands.get(name)
def execute(self, command, params):
"""Send a command to the remote server.
Any path substitutions required for the URL mapped to the command should be
included in the command parameters.
Args:
command: A string specifying the command to execute.
params: A dictionary of named parameters to send with the command as
its JSON payload.
"""
command_info = self._commands.get(command) or self.extra_commands.get(command)
assert command_info is not None, f"Unrecognised command {command}"
path_string = command_info[1]
path = string.Template(path_string).substitute(params)
substitute_params = {word[1:] for word in path_string.split("/") if word.startswith("$")} # remove dollar sign
if isinstance(params, dict) and substitute_params:
for word in substitute_params:
del params[word]
data = utils.dump_json(params)
url = f"{self._client_config.remote_server_addr}{path}"
trimmed = self._trim_large_entries(params)
LOGGER.debug("%s %s %s", command_info[0], url, str(trimmed))
return self._request(command_info[0], url, body=data)
def _request(self, method, url, body=None) -> dict:
"""Send an HTTP request to the remote server.
Args:
method: A string for the HTTP method to send the request with.
url: A string for the URL to send the request to.
body: A string for request body. Ignored unless method is POST or PUT.
Returns:
A dictionary with the server's parsed JSON response.
"""
parsed_url = parse.urlparse(url)
headers = self.get_remote_connection_headers(parsed_url, self._client_config.keep_alive)
auth_header = self._client_config.get_auth_header()
if auth_header:
headers.update(auth_header)
if body and method not in ("POST", "PUT"):
body = None
if self._client_config.keep_alive:
response = self._conn.request(method, url, body=body, headers=headers, timeout=self._client_config.timeout)
statuscode = response.status
else:
conn = self._get_connection_manager()
with conn as http:
response = http.request(method, url, body=body, headers=headers, timeout=self._client_config.timeout)
statuscode = response.status
data = response.data.decode("UTF-8")
LOGGER.debug("Remote response: status=%s | data=%s | headers=%s", response.status, data, response.headers)
try:
if 300 <= statuscode < 304:
return self._request("GET", response.headers.get("location", None))
if statuscode == 401:
return {"status": statuscode, "value": "Authorization Required"}
if statuscode >= 400:
return {"status": statuscode, "value": response.reason if not data else data.strip()}
content_type = []
if response.headers.get("Content-Type", None):
content_type = response.headers.get("Content-Type", None).split(";")
if not any([x.startswith("image/png") for x in content_type]):
try:
data = utils.load_json(data.strip())
except ValueError:
if 199 < statuscode < 300:
status = ErrorCode.SUCCESS
else:
status = ErrorCode.UNKNOWN_ERROR # type: ignore
return {"status": status, "value": data.strip()}
# Some drivers incorrectly return a response
# with no 'value' field when they should return null.
if "value" not in data:
data["value"] = None
return data
data = {"status": 0, "value": data}
return data
finally:
LOGGER.debug("Finished Request")
response.close()
def close(self):
"""Clean up resources when finished with the remote_connection."""
if hasattr(self, "_conn"):
self._conn.clear()
def _trim_large_entries(self, input_dict, max_length=100) -> dict | str:
"""Truncate string values in a dictionary if they exceed max_length.
Args:
input_dict: Dictionary with potentially large values
max_length: Maximum allowed length of string values
Returns:
Dictionary with truncated string values
"""
output_dictionary = {}
for key, value in input_dict.items():
if isinstance(value, dict):
output_dictionary[key] = self._trim_large_entries(value, max_length)
elif isinstance(value, str) and len(value) > max_length:
output_dictionary[key] = value[:max_length] + "..."
else:
output_dictionary[key] = value
return output_dictionary
@@ -0,0 +1,33 @@
# 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 uuid
class ScriptKey:
def __init__(self, id=None):
self._id = id or uuid.uuid4()
@property
def id(self):
return self._id
def __eq__(self, other):
return self._id == other
def __repr__(self) -> str:
return f"ScriptKey(id={self.id})"
@@ -0,0 +1,225 @@
# 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 collections
import os
import re
import shutil
import socket
import subprocess
import time
import urllib
from selenium.webdriver.common.selenium_manager import SeleniumManager
class Server:
"""Manage a Selenium Grid (Remote) Server in standalone mode.
This class contains functionality for downloading the server and starting/stopping it.
For more information on Selenium Grid, see:
- https://www.selenium.dev/documentation/grid/getting_started/
Args:
host: Hostname or IP address to bind to (determined automatically if not specified).
port: Port to listen on (4444 if not specified).
path: Path/filename of existing server .jar file (Selenium Manager is used if not specified).
version: Version of server to download (latest version if not specified).
log_level: Logging level to control logging output ("INFO" if not specified).
Available levels: "SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", "FINEST".
env: Mapping that defines the environment variables for the server process.
java_path: Path to the java executable to run the server.
"""
def __init__(
self,
host=None,
port=4444,
path=None,
version=None,
log_level="INFO",
env=None,
java_path=None,
startup_timeout=10,
):
if path and version:
raise TypeError("Not allowed to specify a version when using an existing server path")
self.host = host
self.port = port
self.path = path
self.version = version
self.log_level = log_level
self.env = env
self.java_path = java_path
self.startup_timeout = startup_timeout
self.process = None
@property
def startup_timeout(self):
return self._startup_timeout
@startup_timeout.setter
def startup_timeout(self, timeout):
self._startup_timeout = int(timeout)
@property
def status_url(self):
host = self.host if self.host is not None else "localhost"
return f"http://{host}:{self.port}/status"
@property
def path(self):
return self._path
@path.setter
def path(self, path):
if path and not os.path.exists(path):
raise OSError(f"Can't find server .jar located at {path}")
self._path = path
@property
def port(self):
return self._port
@port.setter
def port(self, port):
try:
port = int(port)
except ValueError:
raise TypeError(f"{__class__.__name__}.__init__() got an invalid port: '{port}'")
if not (0 <= port <= 65535):
raise ValueError("port must be 0-65535")
self._port = port
@property
def version(self):
return self._version
@version.setter
def version(self, version):
if version:
if not re.match(r"^\d+\.\d+\.\d+$", str(version)):
raise TypeError(f"{__class__.__name__}.__init__() got an invalid version: '{version}'")
self._version = version
@property
def log_level(self):
return self._log_level
@log_level.setter
def log_level(self, log_level):
levels = ("SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", "FINEST")
if log_level not in levels:
raise TypeError(f"log_level must be one of: {', '.join(levels)}")
self._log_level = log_level
@property
def env(self):
return self._env
@env.setter
def env(self, env):
if env is not None and not isinstance(env, collections.abc.Mapping):
raise TypeError("env must be a mapping of environment variables")
self._env = env
@property
def java_path(self):
return self._java_path
@java_path.setter
def java_path(self, java_path):
if java_path and not os.path.exists(java_path):
raise OSError(f"Can't find java executable located at {java_path}")
self._java_path = java_path
def _wait_for_server(self, timeout=10):
start = time.time()
while time.time() - start < timeout:
try:
urllib.request.urlopen(self.status_url)
return True
except urllib.error.URLError:
time.sleep(0.2)
return False
def download_if_needed(self, version=None):
"""Download the server if it doesn't already exist.
Latest version is downloaded unless specified.
"""
args = ["--grid"]
if version is not None:
args.append(version)
return SeleniumManager().binary_paths(args)["driver_path"]
def start(self):
"""Start the server.
Selenium Manager will detect the server location and download it if necessary,
unless an existing server path was specified.
"""
path = self.download_if_needed(self.version) if self.path is None else self.path
java_path = self.java_path or shutil.which("java")
if java_path is None:
raise OSError("Can't find java on system PATH. JRE is required to run the Selenium server")
command = [
java_path,
"-jar",
path,
"standalone",
"--port",
str(self.port),
"--log-level",
self.log_level,
"--selenium-manager",
"true",
"--enable-managed-downloads",
"true",
]
if self.host is not None:
command.extend(["--host", self.host])
host = self.host if self.host is not None else "localhost"
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((host, self.port))
raise ConnectionError(f"Selenium server is already running, or something else is using port {self.port}")
except ConnectionRefusedError:
print("Starting Selenium server...")
self.process = subprocess.Popen(command, env=self.env)
print(f"Selenium server running as process: {self.process.pid}")
if not self._wait_for_server(timeout=self.startup_timeout):
raise TimeoutError(f"Timed out waiting for Selenium server at {self.status_url}")
print("Selenium server is ready")
return self.process
def stop(self):
"""Stop the server."""
if self.process is None:
raise RuntimeError("Selenium server isn't running")
else:
if self.process.poll() is None:
self.process.terminate()
self.process.wait()
self.process = None
print("Selenium server has been terminated")
@@ -0,0 +1,139 @@
# 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.
from __future__ import annotations
from hashlib import md5 as md5_hash
from typing import TYPE_CHECKING
from selenium.common.exceptions import InvalidSelectorException
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.command import Command
if TYPE_CHECKING:
# we only import these when the module is analyzed for type annotations
# to avoid a circular import when it is run normally
from selenium.webdriver.remote.webelement import WebElement
class ShadowRoot:
# TODO: We should look and see how we can create a search context like Java/.NET
def __init__(self, session, id_) -> None:
self.session = session
self._id = id_
def __eq__(self, other_shadowroot) -> bool:
return self._id == other_shadowroot._id
def __hash__(self) -> int:
return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16)
def __repr__(self) -> str:
return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format(
type(self), self.session.session_id, self._id
)
@property
def id(self) -> str:
return self._id
def find_element(self, by: str = By.ID, value: str | None = None) -> WebElement:
"""Find an element inside a shadow root given a By strategy and locator.
Args:
by: The locating strategy to use. Default is `By.ID`. Supported values include:
- By.ID: Locate by element ID.
- By.NAME: Locate by the `name` attribute.
- By.XPATH: Locate by an XPath expression.
- By.CSS_SELECTOR: Locate by a CSS selector.
- By.CLASS_NAME: Locate by the `class` attribute.
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
- By.LINK_TEXT: Locate a link element by its exact text.
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
value: The locator value to use with the specified `by` strategy.
Returns:
The first matching `WebElement` found on the page.
Example:
>>> element = driver.find_element(By.ID, "foo")
"""
if by == By.ID:
by = By.CSS_SELECTOR
value = f'[id="{value}"]'
elif by == By.CLASS_NAME:
if value and any(char.isspace() for char in value.strip()):
raise InvalidSelectorException("Compound class names are not allowed.")
by = By.CSS_SELECTOR
value = f".{value}"
elif by == By.NAME:
by = By.CSS_SELECTOR
value = f'[name="{value}"]'
return self._execute(Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"]
def find_elements(self, by: str = By.ID, value: str | None = None) -> list[WebElement]:
"""Find elements inside a shadow root given a By strategy and locator.
Args:
by: The locating strategy to use. Default is `By.ID`. Supported values include:
- By.ID: Locate by element ID.
- By.NAME: Locate by the `name` attribute.
- By.XPATH: Locate by an XPath expression.
- By.CSS_SELECTOR: Locate by a CSS selector.
- By.CLASS_NAME: Locate by the `class` attribute.
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
- By.LINK_TEXT: Locate a link element by its exact text.
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
value: The locator value to use with the specified `by` strategy.
Returns:
List of `WebElements` matching locator strategy found on the page.
Example:
>>> element = driver.find_elements(By.ID, "foo")
"""
if by == By.ID:
by = By.CSS_SELECTOR
value = f'[id="{value}"]'
elif by == By.CLASS_NAME:
if value and any(char.isspace() for char in value.strip()):
raise InvalidSelectorException("Compound class names are not allowed.")
by = By.CSS_SELECTOR
value = f".{value}"
elif by == By.NAME:
by = By.CSS_SELECTOR
value = f'[name="{value}"]'
return self._execute(Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"]
# Private Methods
def _execute(self, command, params=None):
"""Executes a command against the underlying HTML element.
Args:
command: The name of the command to _execute as a string.
params: A dictionary of named parameters to send with the command.
Returns:
The command's JSON response loaded into a dictionary object.
"""
if not params:
params = {}
params["shadowId"] = self._id
return self.session.execute(command, params)
@@ -0,0 +1,133 @@
# 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.
from selenium.common.exceptions import NoSuchElementException, NoSuchFrameException, NoSuchWindowException
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.command import Command
from selenium.webdriver.remote.webelement import WebElement
class SwitchTo:
def __init__(self, driver) -> None:
import weakref
self._driver = weakref.proxy(driver)
@property
def active_element(self) -> WebElement:
"""Returns the element with focus, or BODY if nothing has focus.
Example:
element = driver.switch_to.active_element
"""
return self._driver.execute(Command.W3C_GET_ACTIVE_ELEMENT)["value"]
@property
def alert(self) -> Alert:
"""Switches focus to an alert on the page.
Example:
alert = driver.switch_to.alert
"""
alert = Alert(self._driver)
_ = alert.text
return alert
def default_content(self) -> None:
"""Switch focus to the default frame.
Example:
driver.switch_to.default_content()
"""
self._driver.execute(Command.SWITCH_TO_FRAME, {"id": None})
def frame(self, frame_reference: str | int | WebElement) -> None:
"""Switch focus to the specified frame by index, name, or element.
Args:
frame_reference: The name of the frame to switch to, an integer representing the index,
or a WebElement that is an (i)frame to switch to.
Example:
driver.switch_to.frame("frame_name")
driver.switch_to.frame(1)
driver.switch_to.frame(driver.find_elements(By.TAG_NAME, "iframe")[0])
"""
if isinstance(frame_reference, str):
try:
frame_reference = self._driver.find_element(By.ID, frame_reference)
except NoSuchElementException:
try:
frame_reference = self._driver.find_element(By.NAME, frame_reference)
except NoSuchElementException as exc:
raise NoSuchFrameException(frame_reference) from exc
self._driver.execute(Command.SWITCH_TO_FRAME, {"id": frame_reference})
def new_window(self, type_hint: str | None = None) -> None:
"""Switches to a new top-level browsing context.
The type hint can be one of "tab" or "window". If not specified the
browser will automatically select it.
Example:
driver.switch_to.new_window("tab")
"""
value = self._driver.execute(Command.NEW_WINDOW, {"type": type_hint})["value"]
self._w3c_window(value["handle"])
def parent_frame(self) -> None:
"""Switch focus to the parent browsing context.
If the current context is already the top level browsing context, it remains unchanged.
Example:
driver.switch_to.parent_frame()
"""
self._driver.execute(Command.SWITCH_TO_PARENT_FRAME)
def window(self, window_name: str) -> None:
"""Switches focus to the specified window.
Args:
window_name: The name or window handle of the window to switch to.
Example:
driver.switch_to.window("main")
"""
self._w3c_window(window_name)
def _w3c_window(self, window_name: str) -> None:
def send_handle(h):
self._driver.execute(Command.SWITCH_TO_WINDOW, {"handle": h})
try:
# Try using it as a handle first.
send_handle(window_name)
except NoSuchWindowException:
# Check every window to try to find the given window name.
original_handle = self._driver.current_window_handle
handles = self._driver.window_handles
for handle in handles:
send_handle(handle)
current_name = self._driver.execute_script("return window.name")
if window_name == current_name:
return
send_handle(original_handle)
raise
@@ -0,0 +1,27 @@
# 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 json
from typing import Any
def dump_json(json_struct: Any) -> str:
return json.dumps(json_struct)
def load_json(s: str | bytes) -> Any:
return json.loads(s)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,578 @@
# 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.
from __future__ import annotations
import os
import pkgutil
import warnings
import zipfile
from abc import ABCMeta
from base64 import b64decode, encodebytes
from hashlib import md5 as md5_hash
from io import BytesIO
from selenium.common.exceptions import JavascriptException, WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.utils import keys_to_typing
from selenium.webdriver.remote.command import Command
from selenium.webdriver.remote.shadowroot import ShadowRoot
# TODO: Use built in importlib_resources.files.
getAttribute_js = None
isDisplayed_js = None
def _load_js():
global getAttribute_js
global isDisplayed_js
_pkg = ".".join(__name__.split(".")[:-1])
getAttribute_js = pkgutil.get_data(_pkg, "getAttribute.js").decode("utf8")
isDisplayed_js = pkgutil.get_data(_pkg, "isDisplayed.js").decode("utf8")
class BaseWebElement(metaclass=ABCMeta):
"""Abstract Base Class for WebElement.
ABC's will allow custom types to be registered as a WebElement to
pass type checks.
"""
pass
class WebElement(BaseWebElement):
"""Represents a DOM element.
Generally, all interesting operations that interact with a document will be
performed through this interface.
All method calls will do a freshness check to ensure that the element
reference is still valid. This essentially determines whether the
element is still attached to the DOM. If this test fails, then an
`StaleElementReferenceException` is thrown, and all future calls to this
instance will fail.
"""
def __init__(self, parent, id_) -> None:
self._parent = parent
self._id = id_
def __repr__(self):
return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}", element="{self._id}")>'
@property
def session_id(self) -> str:
return self._parent.session_id
@property
def tag_name(self) -> str:
"""This element's `tagName` property.
Returns:
The tag name of the element.
Example:
element = driver.find_element(By.ID, "foo")
"""
return self._execute(Command.GET_ELEMENT_TAG_NAME)["value"]
@property
def text(self) -> str:
"""The text of the element.
Returns:
The text of the element.
Example:
element = driver.find_element(By.ID, "foo")
print(element.text)
"""
return self._execute(Command.GET_ELEMENT_TEXT)["value"]
def click(self) -> None:
"""Clicks the element.
Example:
element = driver.find_element(By.ID, "foo")
element.click()
"""
self._execute(Command.CLICK_ELEMENT)
def submit(self) -> None:
"""Submits a form.
Example:
form = driver.find_element(By.NAME, "login")
form.submit()
"""
script = (
"/* submitForm */var form = arguments[0];\n"
'while (form.nodeName != "FORM" && form.parentNode) {\n'
" form = form.parentNode;\n"
"}\n"
"if (!form) { throw Error('Unable to find containing form element'); }\n"
"if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n"
"var e = form.ownerDocument.createEvent('Event');\n"
"e.initEvent('submit', true, true);\n"
"if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
)
try:
self._parent.execute_script(script, self)
except JavascriptException as exc:
raise WebDriverException("To submit an element, it must be nested inside a form element") from exc
def clear(self) -> None:
"""Clears the text if it's a text entry element.
Example:
text_field = driver.find_element(By.NAME, "username")
text_field.clear()
"""
self._execute(Command.CLEAR_ELEMENT)
def get_property(self, name) -> str | bool | WebElement | dict:
"""Gets the given property of the element.
Args:
name: Name of the property to retrieve.
Returns:
The value of the property.
Example:
text_length = target_element.get_property("text_length")
"""
try:
return self._execute(Command.GET_ELEMENT_PROPERTY, {"name": name})["value"]
except WebDriverException:
# if we hit an end point that doesn't understand getElementProperty lets fake it
return self.parent.execute_script("return arguments[0][arguments[1]]", self, name)
def get_dom_attribute(self, name) -> str:
"""Get the HTML attribute value (not reflected properties) of the element.
Returns only attributes declared in the element's HTML markup, unlike
`selenium.webdriver.remote.BaseWebElement.get_attribute`.
Args:
name: Name of the attribute to retrieve.
Returns:
The value of the attribute.
Example:
text_length = target_element.get_dom_attribute("class")
"""
return self._execute(Command.GET_ELEMENT_ATTRIBUTE, {"name": name})["value"]
def get_attribute(self, name) -> str | None:
"""Gets the given attribute or property of the element.
This method will first try to return the value of a property with the
given name. If a property with that name doesn't exist, it returns the
value of the attribute with the same name. If there's no attribute with
that name, ``None`` is returned.
Values which are considered truthy, that is equals "true" or "false",
are returned as booleans. All other non-``None`` values are returned
as strings. For attributes or properties which do not exist, ``None``
is returned.
To obtain the exact value of the attribute or property,
use :func:`~selenium.webdriver.remote.BaseWebElement.get_dom_attribute` or
:func:`~selenium.webdriver.remote.BaseWebElement.get_property` methods respectively.
Args:
name: Name of the attribute/property to retrieve.
Returns:
The value of the attribute/property.
Example:
# Check if the "active" CSS class is applied to an element.
is_active = "active" in target_element.get_attribute("class")
"""
if getAttribute_js is None:
_load_js()
attribute_value = self.parent.execute_script(
f"/* getAttribute */return ({getAttribute_js}).apply(null, arguments);", self, name
)
return attribute_value
def is_selected(self) -> bool:
"""Returns whether the element is selected.
This method is generally used on checkboxes, options in a select
and radio buttons.
Example:
is_selected = element.is_selected()
"""
return self._execute(Command.IS_ELEMENT_SELECTED)["value"]
def is_enabled(self) -> bool:
"""Returns whether the element is enabled.
Example:
is_enabled = element.is_enabled()
"""
return self._execute(Command.IS_ELEMENT_ENABLED)["value"]
def send_keys(self, *value: str) -> None:
"""Simulates typing into the element.
Use this to send simple key events or to fill out form fields.
This can also be used to set file inputs.
Args:
value: A string for typing, or setting form fields. For setting
file inputs, this could be a local file path.
Examples:
To send a simple key event::
form_textfield = driver.find_element(By.NAME, "username")
form_textfield.send_keys("admin")
or to set a file input field::
file_input = driver.find_element(By.NAME, "profilePic")
file_input.send_keys("path/to/profilepic.gif")
# Generally it's better to wrap the file path in one of the methods
# in os.path to return the actual path to support cross OS testing.
# file_input.send_keys(os.path.abspath("path/to/profilepic.gif"))
"""
# transfer file to another machine only if remote driver is used
# the same behaviour as for java binding
if self.parent._is_remote:
local_files = list(
map(
lambda keys_to_send: self.parent.file_detector.is_local_file(str(keys_to_send)),
"".join(map(str, value)).split("\n"),
)
)
if None not in local_files:
remote_files = []
for file in local_files:
remote_files.append(self._upload(file))
value = tuple("\n".join(remote_files))
self._execute(
Command.SEND_KEYS_TO_ELEMENT, {"text": "".join(keys_to_typing(value)), "value": keys_to_typing(value)}
)
@property
def shadow_root(self) -> ShadowRoot:
"""Get the shadow root attached to this element if present (Chromium, Firefox, Safari).
Returns:
The ShadowRoot object.
Raises:
NoSuchShadowRoot: If no shadow root was attached to element.
Example:
try:
shadow_root = element.shadow_root
except NoSuchShadowRoot:
print("No shadow root attached to element")
"""
return self._execute(Command.GET_SHADOW_ROOT)["value"]
# RenderedWebElement Items
def is_displayed(self) -> bool:
"""Whether the element is visible to a user.
Example:
is_displayed = element.is_displayed()
"""
# Only go into this conditional for browsers that don't use the atom themselves
if isDisplayed_js is None:
_load_js()
return self.parent.execute_script(f"/* isDisplayed */return ({isDisplayed_js}).apply(null, arguments);", self)
@property
def location_once_scrolled_into_view(self) -> dict:
"""Get the element's location on screen after scrolling it into view.
This may change without warning and scrolls the element into view
before calculating coordinates for clicking purposes.
Returns:
The top lefthand corner location on the screen, or zero
coordinates if the element is not visible.
Example:
loc = element.location_once_scrolled_into_view
"""
old_loc = self._execute(
Command.W3C_EXECUTE_SCRIPT,
{
"script": "arguments[0].scrollIntoView(true); return arguments[0].getBoundingClientRect()",
"args": [self],
},
)["value"]
return {"x": round(old_loc["x"]), "y": round(old_loc["y"])}
@property
def size(self) -> dict:
"""Get the size of the element.
Returns:
The width and height of the element.
Example:
size = element.size
"""
size = self._execute(Command.GET_ELEMENT_RECT)["value"]
new_size = {"height": size["height"], "width": size["width"]}
return new_size
def value_of_css_property(self, property_name) -> str:
"""Get the value of a CSS property.
Args:
property_name: The name of the CSS property to get the value of.
Returns:
The value of the CSS property.
Example:
value = element.value_of_css_property("color")
"""
return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, {"propertyName": property_name})["value"]
@property
def location(self) -> dict:
"""Get the location of the element in the renderable canvas.
Returns:
The x and y coordinates of the element.
Example:
loc = element.location
"""
old_loc = self._execute(Command.GET_ELEMENT_RECT)["value"]
new_loc = {"x": round(old_loc["x"]), "y": round(old_loc["y"])}
return new_loc
@property
def rect(self) -> dict:
"""Get the size and location of the element.
Returns:
A dictionary with size and location of the element.
Example:
rect = element.rect
"""
return self._execute(Command.GET_ELEMENT_RECT)["value"]
@property
def aria_role(self) -> str:
"""Get the ARIA role of the current web element.
Returns:
The ARIA role of the element.
Example:
role = element.aria_role
"""
return self._execute(Command.GET_ELEMENT_ARIA_ROLE)["value"]
@property
def accessible_name(self) -> str:
"""Get the ARIA Level of the current webelement.
Returns:
The ARIA Level of the element.
Example:
name = element.accessible_name
"""
return self._execute(Command.GET_ELEMENT_ARIA_LABEL)["value"]
@property
def screenshot_as_base64(self) -> str:
"""Get a base64-encoded screenshot of the current element.
Returns:
The screenshot of the element as a base64 encoded string.
Example:
img_b64 = element.screenshot_as_base64
"""
return self._execute(Command.ELEMENT_SCREENSHOT)["value"]
@property
def screenshot_as_png(self) -> bytes:
"""Get the screenshot of the current element as a binary data.
Returns:
The screenshot of the element as binary data.
Example:
element_png = element.screenshot_as_png
"""
return b64decode(self.screenshot_as_base64.encode("ascii"))
def screenshot(self, filename) -> bool:
"""Save a PNG screenshot of the current element to a file.
Use full paths in your filename.
Args:
filename: The full path you wish to save your screenshot to. This
should end with a `.png` extension.
Returns:
True if the screenshot was saved successfully, False otherwise.
Example:
element.screenshot("/Screenshots/foo.png")
"""
if not filename.lower().endswith(".png"):
warnings.warn(
"name used for saved screenshot does not match file type. It should end with a `.png` extension",
UserWarning,
)
png = self.screenshot_as_png
try:
with open(filename, "wb") as f:
f.write(png)
except OSError:
return False
finally:
del png
return True
@property
def parent(self):
"""Get the WebDriver instance this element was found from.
Example:
element = driver.find_element(By.ID, "foo")
parent_element = element.parent
"""
return self._parent
@property
def id(self) -> str:
"""Get the ID used by selenium.
This is mainly for internal use. Simple use cases such as checking if 2
webelements refer to the same element, can be done using ``==``::
Example:
if element1 == element2:
print("These 2 are equal")
"""
return self._id
def __eq__(self, element):
return hasattr(element, "id") and self._id == element.id
def __ne__(self, element):
return not self.__eq__(element)
# Private Methods
def _execute(self, command, params=None):
"""Executes a command against the underlying HTML element.
Args:
command: The name of the command to _execute as a string.
params: A dictionary of named Parameters to send with the command.
Returns:
The command's JSON response loaded into a dictionary object.
"""
if not params:
params = {}
params["id"] = self._id
return self._parent.execute(command, params)
def find_element(self, by: str = By.ID, value: str | None = None) -> WebElement:
"""Find an element given a By strategy and locator.
Args:
by: The locating strategy to use. Default is `By.ID`. Supported values include:
- By.ID: Locate by element ID.
- By.NAME: Locate by the `name` attribute.
- By.XPATH: Locate by an XPath expression.
- By.CSS_SELECTOR: Locate by a CSS selector.
- By.CLASS_NAME: Locate by the `class` attribute.
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
- By.LINK_TEXT: Locate a link element by its exact text.
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
value: The locator value to use with the specified `by` strategy.
Returns:
The first matching `WebElement` found on the page.
Example:
element = driver.find_element(By.ID, "foo")
"""
by, value = self._parent.locator_converter.convert(by, value)
return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})["value"]
def find_elements(self, by: str = By.ID, value: str | None = None) -> list[WebElement]:
"""Find elements given a By strategy and locator.
Args:
by: The locating strategy to use. Default is `By.ID`. Supported values include:
- By.ID: Locate by element ID.
- By.NAME: Locate by the `name` attribute.
- By.XPATH: Locate by an XPath expression.
- By.CSS_SELECTOR: Locate by a CSS selector.
- By.CLASS_NAME: Locate by the `class` attribute.
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
- By.LINK_TEXT: Locate a link element by its exact text.
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
value: The locator value to use with the specified `by` strategy.
Returns:
List of `WebElements` matching locator strategy found on the page.
Example:
element = driver.find_elements(By.ID, "foo")
"""
by, value = self._parent.locator_converter.convert(by, value)
return self._execute(Command.FIND_CHILD_ELEMENTS, {"using": by, "value": value})["value"]
def __hash__(self) -> int:
return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16)
def _upload(self, filename):
fp = BytesIO()
zipped = zipfile.ZipFile(fp, "w", zipfile.ZIP_DEFLATED)
zipped.write(filename, os.path.split(filename)[1])
zipped.close()
content = encodebytes(fp.getvalue())
if not isinstance(content, str):
content = content.decode("utf-8")
try:
return self._execute(Command.UPLOAD_FILE, {"file": content})["value"]
except WebDriverException as e:
if "Unrecognized command: POST" in str(e):
return filename
if "Command not found: POST " in str(e):
return filename
if '{"status":405,"value":["GET","HEAD","DELETE"]}' in str(e):
return filename
raise
@@ -0,0 +1,159 @@
# 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 json
import logging
from ssl import CERT_NONE
from threading import Thread
from time import sleep
from websocket import WebSocketApp
from selenium.common import WebDriverException
logger = logging.getLogger(__name__)
class WebSocketConnection:
_max_log_message_size = 9999
def __init__(self, url, timeout, interval):
if not isinstance(timeout, (int, float)) or timeout < 0:
raise WebDriverException("timeout must be a positive number")
if not isinstance(interval, (int, float)) or timeout < 0:
raise WebDriverException("interval must be a positive number")
self.url = url
self.response_wait_timeout = timeout
self.response_wait_interval = interval
self.callbacks = {}
self.session_id = None
self._id = 0
self._messages = {}
self._started = False
self._start_ws()
self._wait_until(lambda: self._started)
def close(self):
self._ws_thread.join(timeout=self.response_wait_timeout)
self._ws.close()
self._started = False
self._ws = None
def execute(self, command):
self._id += 1
payload = self._serialize_command(command)
payload["id"] = self._id
if self.session_id:
payload["sessionId"] = self.session_id
data = json.dumps(payload)
logger.debug(f"-> {data}"[: self._max_log_message_size])
self._ws.send(data)
current_id = self._id
self._wait_until(lambda: current_id in self._messages)
response = self._messages.pop(current_id)
if "error" in response:
error = response["error"]
if "message" in response:
error_msg = f"{error}: {response['message']}"
raise WebDriverException(error_msg)
else:
raise WebDriverException(error)
else:
result = response["result"]
return self._deserialize_result(result, command)
def add_callback(self, event, callback):
event_name = event.event_class
if event_name not in self.callbacks:
self.callbacks[event_name] = []
def _callback(params):
callback(event.from_json(params))
self.callbacks[event_name].append(_callback)
return id(_callback)
on = add_callback
def remove_callback(self, event, callback_id):
event_name = event.event_class
if event_name in self.callbacks:
for callback in self.callbacks[event_name]:
if id(callback) == callback_id:
self.callbacks[event_name].remove(callback)
return
def _serialize_command(self, command):
return next(command)
def _deserialize_result(self, result, command):
try:
_ = command.send(result)
raise WebDriverException("The command's generator function did not exit when expected!")
except StopIteration as exit:
return exit.value
def _start_ws(self):
def on_open(ws):
self._started = True
def on_message(ws, message):
self._process_message(message)
def on_error(ws, error):
logger.debug(f"error: {error}")
ws.close()
def run_socket():
if self.url.startswith("wss://"):
self._ws.run_forever(sslopt={"cert_reqs": CERT_NONE}, suppress_origin=True)
else:
self._ws.run_forever(suppress_origin=True)
self._ws = WebSocketApp(self.url, on_open=on_open, on_message=on_message, on_error=on_error)
self._ws_thread = Thread(target=run_socket, daemon=True)
self._ws_thread.start()
def _process_message(self, message):
message = json.loads(message)
logger.debug(f"<- {message}"[: self._max_log_message_size])
if "id" in message:
self._messages[message["id"]] = message
if "method" in message:
params = message["params"]
for callback in self.callbacks.get(message["method"], []):
Thread(target=callback, args=(params,), daemon=True).start()
def _wait_until(self, condition):
timeout = self.response_wait_timeout
interval = self.response_wait_interval
while timeout > 0:
result = condition()
if result:
return result
else:
timeout -= interval
sleep(interval)