initial commit
This commit is contained in:
@@ -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.
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+23
@@ -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
|
||||
+156
@@ -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"
|
||||
+232
@@ -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)
|
||||
+49
@@ -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
|
||||
+68
@@ -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);}
|
||||
+17
@@ -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);}
|
||||
+33
@@ -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)
|
||||
+495
@@ -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
|
||||
+159
@@ -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)
|
||||
Reference in New Issue
Block a user