second commit

This commit is contained in:
Александр Геннадьевич Сальный
2022-10-15 21:01:12 +03:00
commit 7caeeaaff5
1329 changed files with 489315 additions and 0 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -0,0 +1,123 @@
# 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(object):
"""
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://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
"""
# Keep in sync with org.openqa.selenium.remote.DriverCommand
NEW_SESSION = "newSession"
DELETE_SESSION = "deleteSession"
NEW_WINDOW = "newWindow"
CLOSE = "close"
QUIT = "quit"
GET = "get"
GO_BACK = "goBack"
GO_FORWARD = "goForward"
REFRESH = "refresh"
ADD_COOKIE = "addCookie"
GET_COOKIE = "getCookie"
GET_ALL_COOKIES = "getCookies"
DELETE_COOKIE = "deleteCookie"
DELETE_ALL_COOKIES = "deleteAllCookies"
FIND_ELEMENT = "findElement"
FIND_ELEMENTS = "findElements"
FIND_CHILD_ELEMENT = "findChildElement"
FIND_CHILD_ELEMENTS = "findChildElements"
CLEAR_ELEMENT = "clearElement"
CLICK_ELEMENT = "clickElement"
SEND_KEYS_TO_ELEMENT = "sendKeysToElement"
UPLOAD_FILE = "uploadFile"
W3C_GET_CURRENT_WINDOW_HANDLE = "w3cGetCurrentWindowHandle"
W3C_GET_WINDOW_HANDLES = "w3cGetWindowHandles"
SET_WINDOW_RECT = "setWindowRect"
GET_WINDOW_RECT = "getWindowRect"
SWITCH_TO_WINDOW = "switchToWindow"
SWITCH_TO_FRAME = "switchToFrame"
SWITCH_TO_PARENT_FRAME = "switchToParentFrame"
W3C_GET_ACTIVE_ELEMENT = "w3cGetActiveElement"
GET_CURRENT_URL = "getCurrentUrl"
GET_PAGE_SOURCE = "getPageSource"
GET_TITLE = "getTitle"
W3C_EXECUTE_SCRIPT = "w3cExecuteScript"
W3C_EXECUTE_SCRIPT_ASYNC = "w3cExecuteScriptAsync"
GET_ELEMENT_TEXT = "getElementText"
GET_ELEMENT_TAG_NAME = "getElementTagName"
IS_ELEMENT_SELECTED = "isElementSelected"
IS_ELEMENT_ENABLED = "isElementEnabled"
GET_ELEMENT_RECT = "getElementRect"
GET_ELEMENT_ATTRIBUTE = "getElementAttribute"
GET_ELEMENT_PROPERTY = "getElementProperty"
GET_ELEMENT_VALUE_OF_CSS_PROPERTY = "getElementValueOfCssProperty"
GET_ELEMENT_ARIA_ROLE = "getElementAriaRole"
GET_ELEMENT_ARIA_LABEL = "getElementAriaLabel"
SCREENSHOT = "screenshot"
ELEMENT_SCREENSHOT = "elementScreenshot"
EXECUTE_ASYNC_SCRIPT = "executeAsyncScript"
SET_TIMEOUTS = "setTimeouts"
GET_TIMEOUTS = "getTimeouts"
W3C_MAXIMIZE_WINDOW = "w3cMaximizeWindow"
GET_LOG = "getLog"
GET_AVAILABLE_LOG_TYPES = "getAvailableLogTypes"
FULLSCREEN_WINDOW = "fullscreenWindow"
MINIMIZE_WINDOW = "minimizeWindow"
PRINT_PAGE = 'printPage'
# Alerts
W3C_DISMISS_ALERT = "w3cDismissAlert"
W3C_ACCEPT_ALERT = "w3cAcceptAlert"
W3C_SET_ALERT_VALUE = "w3cSetAlertValue"
W3C_GET_ALERT_TEXT = "w3cGetAlertText"
# Advanced user interactions
W3C_ACTIONS = "actions"
W3C_CLEAR_ACTIONS = "clearActionState"
# Screen Orientation
SET_SCREEN_ORIENTATION = "setScreenOrientation"
GET_SCREEN_ORIENTATION = "getScreenOrientation"
# Mobile
GET_NETWORK_CONNECTION = "getNetworkConnection"
SET_NETWORK_CONNECTION = "setNetworkConnection"
CURRENT_CONTEXT_HANDLE = "getCurrentContextHandle"
CONTEXT_HANDLES = "getContextHandles"
SWITCH_TO_CONTEXT = "switchToContext"
# Web Components
GET_SHADOW_ROOT = "getShadowRoot"
FIND_ELEMENT_FROM_SHADOW_ROOT = "findElementFromShadowRoot"
FIND_ELEMENTS_FROM_SHADOW_ROOT = "findElementsFromShadowRoot"
# Virtual Authenticator
ADD_VIRTUAL_AUTHENTICATOR = "addVirtualAuthenticator"
REMOVE_VIRTUAL_AUTHENTICATOR = "removeVirtualAuthenticator"
ADD_CREDENTIAL = "addCredential"
GET_CREDENTIALS = "getCredentials"
REMOVE_CREDENTIAL = "removeCredential"
REMOVE_ALL_CREDENTIALS = "removeAllCredentials"
SET_USER_VERIFIED = "setUserVerified"

View File

@@ -0,0 +1,250 @@
# 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 typing import Any, Dict, Mapping, Type, TypeVar
from selenium.common.exceptions import (ElementClickInterceptedException,
ElementNotInteractableException,
ElementNotSelectableException,
ElementNotVisibleException,
InsecureCertificateException,
InvalidCoordinatesException,
InvalidElementStateException,
InvalidSessionIdException,
InvalidSelectorException,
ImeNotAvailableException,
ImeActivationFailedException,
InvalidArgumentException,
InvalidCookieDomainException,
JavascriptException,
MoveTargetOutOfBoundsException,
NoSuchCookieException,
NoSuchElementException,
NoSuchFrameException,
NoSuchShadowRootException,
NoSuchWindowException,
NoAlertPresentException,
ScreenshotException,
SessionNotCreatedException,
StaleElementReferenceException,
TimeoutException,
UnableToSetCookieException,
UnexpectedAlertPresentException,
UnknownMethodException,
WebDriverException)
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
class ErrorCode(object):
"""
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']
METHOD_NOT_ALLOWED = [405, 'unsupported operation']
class ErrorHandler(object):
"""
Handles errors returned by the WebDriver server.
"""
def check_response(self, response: Dict[str, Any]) -> None:
"""
Checks 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: 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):
import json
try:
value = json.loads(value_json)
if len(value.keys()) == 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')
else:
message = value.get('message', None)
except ValueError:
pass
exception_class: Type[WebDriverException]
if status in ErrorCode.NO_SUCH_ELEMENT:
exception_class = NoSuchElementException
elif status in ErrorCode.NO_SUCH_FRAME:
exception_class = NoSuchFrameException
elif status in ErrorCode.NO_SUCH_SHADOW_ROOT:
exception_class = NoSuchShadowRootException
elif status in ErrorCode.NO_SUCH_WINDOW:
exception_class = NoSuchWindowException
elif status in ErrorCode.STALE_ELEMENT_REFERENCE:
exception_class = StaleElementReferenceException
elif status in ErrorCode.ELEMENT_NOT_VISIBLE:
exception_class = ElementNotVisibleException
elif status in ErrorCode.INVALID_ELEMENT_STATE:
exception_class = InvalidElementStateException
elif status in ErrorCode.INVALID_SELECTOR \
or status in ErrorCode.INVALID_XPATH_SELECTOR \
or status in ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER:
exception_class = InvalidSelectorException
elif status in ErrorCode.ELEMENT_IS_NOT_SELECTABLE:
exception_class = ElementNotSelectableException
elif status in ErrorCode.ELEMENT_NOT_INTERACTABLE:
exception_class = ElementNotInteractableException
elif status in ErrorCode.INVALID_COOKIE_DOMAIN:
exception_class = InvalidCookieDomainException
elif status in ErrorCode.UNABLE_TO_SET_COOKIE:
exception_class = UnableToSetCookieException
elif status in ErrorCode.TIMEOUT:
exception_class = TimeoutException
elif status in ErrorCode.SCRIPT_TIMEOUT:
exception_class = TimeoutException
elif status in ErrorCode.UNKNOWN_ERROR:
exception_class = WebDriverException
elif status in ErrorCode.UNEXPECTED_ALERT_OPEN:
exception_class = UnexpectedAlertPresentException
elif status in ErrorCode.NO_ALERT_OPEN:
exception_class = NoAlertPresentException
elif status in ErrorCode.IME_NOT_AVAILABLE:
exception_class = ImeNotAvailableException
elif status in ErrorCode.IME_ENGINE_ACTIVATION_FAILED:
exception_class = ImeActivationFailedException
elif status in ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS:
exception_class = MoveTargetOutOfBoundsException
elif status in ErrorCode.JAVASCRIPT_ERROR:
exception_class = JavascriptException
elif status in ErrorCode.SESSION_NOT_CREATED:
exception_class = SessionNotCreatedException
elif status in ErrorCode.INVALID_ARGUMENT:
exception_class = InvalidArgumentException
elif status in ErrorCode.NO_SUCH_COOKIE:
exception_class = NoSuchCookieException
elif status in ErrorCode.UNABLE_TO_CAPTURE_SCREEN:
exception_class = ScreenshotException
elif status in ErrorCode.ELEMENT_CLICK_INTERCEPTED:
exception_class = ElementClickInterceptedException
elif status in ErrorCode.INSECURE_CERTIFICATE:
exception_class = InsecureCertificateException
elif status in ErrorCode.INVALID_COORDINATES:
exception_class = InvalidCoordinatesException
elif status in ErrorCode.INVALID_SESSION_ID:
exception_class = InvalidSessionIdException
elif status in ErrorCode.UNKNOWN_METHOD:
exception_class = UnknownMethodException
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 = self._value_or_default(frame, 'lineNumber', '')
file = self._value_or_default(frame, 'fileName', '<anonymous>')
if line:
file = "%s:%s" % (file, line)
meth = self._value_or_default(frame, 'methodName', '<anonymous>')
if 'className' in frame:
meth = "%s.%s" % (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) # type: ignore[call-arg] # mypy is not smart enough here
raise exception_class(message, screen, stacktrace)
def _value_or_default(self, obj: Mapping[_KT, _VT], key: _KT, default: _VT) -> _VT:
return obj[key] if key in obj else default

View File

@@ -0,0 +1,61 @@
# 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
import os
from typing import Optional
from selenium.types import AnyKey
from selenium.webdriver.common.utils import keys_to_typing
class FileDetector(metaclass=ABCMeta):
"""
Used for identifying whether a sequence of chars represents the path to a
file.
"""
@abstractmethod
def is_local_file(self, *keys: AnyKey) -> Optional[str]:
return None
class UselessFileDetector(FileDetector):
"""
A file detector that never finds anything.
"""
def is_local_file(self, *keys: AnyKey) -> Optional[str]:
return None
class LocalFileDetector(FileDetector):
"""
Detects files on the local disk.
"""
def is_local_file(self, *keys: AnyKey) -> Optional[str]:
file_path = ''.join(keys_to_typing(keys))
if not file_path:
return None
try:
if os.path.isfile(file_path):
return file_path
except Exception:
pass
return None

View File

@@ -0,0 +1,86 @@
# 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 .command import Command
class Mobile(object):
class ConnectionType(object):
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
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)
@property
def contexts(self):
"""
returns a list of available contexts
"""
return self._driver.execute(Command.CONTEXT_HANDLES)
@context.setter
def context(self, new_context):
"""
sets the current context
"""
self._driver.execute(Command.SWITCH_TO_CONTEXT, {"name": new_context})

View File

@@ -0,0 +1,420 @@
# 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 socket
import string
import os
import certifi
import urllib3
import platform
from base64 import b64encode
from urllib import parse
from selenium import __version__
from .command import Command
from .errorhandler import ErrorCode
from . import utils
LOGGER = logging.getLogger(__name__)
class RemoteConnection(object):
"""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 = None
_timeout = socket._GLOBAL_DEFAULT_TIMEOUT
_ca_certs = certifi.where()
@classmethod
def get_timeout(cls):
"""
:Returns:
Timeout value in seconds for all http requests made to the Remote Connection
"""
return None if cls._timeout == socket._GLOBAL_DEFAULT_TIMEOUT else cls._timeout
@classmethod
def set_timeout(cls, timeout):
"""
Override the default timeout
:Args:
- timeout - timeout value for http requests in seconds
"""
cls._timeout = timeout
@classmethod
def reset_timeout(cls):
"""
Reset the http request timeout to socket._GLOBAL_DEFAULT_TIMEOUT
"""
cls._timeout = socket._GLOBAL_DEFAULT_TIMEOUT
@classmethod
def get_certificate_bundle_path(cls):
"""
:Returns:
Paths of the .pem encoded certificate to verify connection to command executor
"""
return cls._ca_certs
@classmethod
def set_certificate_bundle_path(cls, path):
"""
Set the path to the certificate bundle to verify connection to command executor.
Can also be set to None to disable certificate validation.
:Args:
- path - path of a .pem encoded certificate chain.
"""
cls._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 (Boolean) - Is this a keep-alive connection (default: False)
"""
system = platform.system().lower()
if system == "darwin":
system = "mac"
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
'User-Agent': 'selenium/{} (python {})'.format(__version__, system)
}
if parsed_url.username:
base64string = b64encode('{0.username}:{0.password}'.format(parsed_url).encode())
headers.update({
'Authorization': 'Basic {}'.format(base64string.decode())
})
if keep_alive:
headers.update({
'Connection': 'keep-alive'
})
return headers
def _get_proxy_url(self):
if self._url.startswith('https://'):
return os.environ.get('https_proxy', os.environ.get('HTTPS_PROXY'))
elif self._url.startswith('http://'):
return os.environ.get('http_proxy', os.environ.get('HTTP_PROXY'))
def _identify_http_proxy_auth(self):
url = self._proxy_url
url = url[url.find(":") + 3:]
return True if "@" in url and len(url[:url.find('@')]) > 0 else False
def _seperate_http_proxy_auth(self):
url = self._proxy_url
protocol = url[:url.find(":") + 3]
no_protocol = url[len(protocol):]
auth = no_protocol[:no_protocol.find('@')]
proxy_without_auth = protocol + no_protocol[len(auth) + 1:]
return proxy_without_auth, auth
def _get_connection_manager(self):
pool_manager_init_args = {
'timeout': self.get_timeout()
}
if self._ca_certs:
pool_manager_init_args['cert_reqs'] = 'CERT_REQUIRED'
pool_manager_init_args['ca_certs'] = self._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)
elif self._identify_http_proxy_auth():
self._proxy_url, self._basic_proxy_auth = self._seperate_http_proxy_auth()
pool_manager_init_args['proxy_headers'] = urllib3.make_headers(
proxy_basic_auth=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, keep_alive=False, resolve_ip=None, ignore_proxy=False):
if resolve_ip:
import warnings
warnings.warn(
"'resolve_ip' option removed; ip addresses are now always resolved by urllib3.",
DeprecationWarning)
self.keep_alive = keep_alive
self._url = remote_server_addr
# Env var NO_PROXY will override this part of the code
_no_proxy = os.environ.get('no_proxy', os.environ.get('NO_PROXY'))
if _no_proxy:
for npu in _no_proxy.split(','):
npu = npu.strip()
if npu == "*":
ignore_proxy = True
break
n_url = parse.urlparse(npu)
remote_add = parse.urlparse(self._url)
if n_url.netloc:
if remote_add.netloc == n_url.netloc:
ignore_proxy = True
break
else:
if n_url.path in remote_add.netloc:
ignore_proxy = True
break
self._proxy_url = self._get_proxy_url() if not ignore_proxy else None
if keep_alive:
self._conn = self._get_connection_manager()
self._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.UPLOAD_FILE: ('POST', "/session/$sessionId/se/file"),
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'),
}
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[command]
assert command_info is not None, 'Unrecognised command %s' % command
path = string.Template(command_info[1]).substitute(params)
if isinstance(params, dict) and 'sessionId' in params:
del params['sessionId']
data = utils.dump_json(params)
url = f"{self._url}{path}"
return self._request(command_info[0], url, body=data)
def _request(self, method, url, body=None):
"""
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.
"""
LOGGER.debug(f"{method} {url} {body}")
parsed_url = parse.urlparse(url)
headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
response = None
if body and method not in ("POST", "PUT"):
body = None
if self.keep_alive:
response = self._conn.request(method, url, body=body, headers=headers)
statuscode = response.status
else:
conn = self._get_connection_manager()
with conn as http:
response = http.request(method, url, body=body, headers=headers)
statuscode = response.status
if not hasattr(response, 'getheader'):
if hasattr(response.headers, 'getheader'):
response.getheader = lambda x: response.headers.getheader(x)
elif hasattr(response.headers, 'get'):
response.getheader = lambda x: response.headers.get(x)
data = response.data.decode('UTF-8')
LOGGER.debug(f"Remote response: status={response.status} | data={data} | headers={response.headers}")
try:
if 300 <= statuscode < 304:
return self._request('GET', response.getheader('location'))
if 399 < statuscode <= 500:
return {'status': statuscode, 'value': data}
content_type = []
if response.getheader('Content-Type'):
content_type = response.getheader('Content-Type').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
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
else:
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()

View File

@@ -0,0 +1,30 @@
# 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

View File

@@ -0,0 +1,90 @@
# 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 hashlib import md5 as md5_hash
from .command import Command
from ..common.by import By
class ShadowRoot:
# TODO: We should look and see how we can create a search context like Java/.NET
def __init__(self, session, id_):
self.session = session
self._id = id_
def __eq__(self, other_shadowroot):
return self._id == other_shadowroot._id
def __hash__(self):
return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16)
def __ne__(self, other_shadowroot):
return not self.__eq__(other_shadowroot)
def __repr__(self):
return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format(
type(self), self.session.session_id, self._id
)
def find_element(self, by: By = By.ID, value: str = None):
if by == By.ID:
by = By.CSS_SELECTOR
value = '[id="%s"]' % value
elif by == By.CLASS_NAME:
by = By.CSS_SELECTOR
value = ".%s" % value
elif by == By.NAME:
by = By.CSS_SELECTOR
value = '[name="%s"]' % value
return self._execute(
Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value}
)["value"]
def find_elements(self, by: By = By.ID, value: str = None):
if by == By.ID:
by = By.CSS_SELECTOR
value = '[id="%s"]' % value
elif by == By.CLASS_NAME:
by = By.CSS_SELECTOR
value = ".%s" % value
elif by == By.NAME:
by = By.CSS_SELECTOR
value = '[name="%s"]' % 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)

View File

@@ -0,0 +1,154 @@
# 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 .command import Command
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.webelement import WebElement
class SwitchTo:
def __init__(self, driver):
import weakref
self._driver = weakref.proxy(driver)
@property
def active_element(self) -> WebElement:
"""
Returns the element with focus, or BODY if nothing has focus.
:Usage:
::
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.
:Usage:
::
alert = driver.switch_to.alert
"""
alert = Alert(self._driver)
alert.text
return alert
def default_content(self) -> None:
"""
Switch focus to the default frame.
:Usage:
::
driver.switch_to.default_content()
"""
self._driver.execute(Command.SWITCH_TO_FRAME, {'id': None})
def frame(self, frame_reference) -> None:
"""
Switches focus to the specified frame, by index, name, or webelement.
:Args:
- frame_reference: The name of the window to switch to, an integer representing the index,
or a webelement that is an (i)frame to switch to.
:Usage:
::
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:
raise NoSuchFrameException(frame_reference)
self._driver.execute(Command.SWITCH_TO_FRAME, {'id': frame_reference})
def new_window(self, type_hint=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.
:Usage:
::
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:
"""
Switches focus to the parent context. If the current context is the top
level browsing context, the context remains unchanged.
:Usage:
::
driver.switch_to.parent_frame()
"""
self._driver.execute(Command.SWITCH_TO_PARENT_FRAME)
def window(self, window_name) -> None:
"""
Switches focus to the specified window.
:Args:
- window_name: The name or window handle of the window to switch to.
:Usage:
::
driver.switch_to.window('main')
"""
self._w3c_window(window_name)
return
def _w3c_window(self, window_name):
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 as e:
# 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 e

View File

@@ -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, Union
def dump_json(json_struct: Any) -> str:
return json.dumps(json_struct)
def load_json(s: Union[str, bytes]) -> Any:
return json.loads(s)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,811 @@
# 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 os
from base64 import b64decode, encodebytes
from hashlib import md5 as md5_hash
import pkgutil
import warnings
import zipfile
from abc import ABCMeta
from io import BytesIO
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.utils import keys_to_typing
from .command import Command
from .shadowroot import ShadowRoot
# TODO: When moving to supporting python 3.9 as the minimum version we can
# use built in importlib_resources.files.
_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 or not 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_):
self._parent = parent
self._id = id_
def __repr__(self):
return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format(
type(self), self._parent.session_id, self._id)
@property
def tag_name(self) -> str:
"""This element's ``tagName`` property."""
return self._execute(Command.GET_ELEMENT_TAG_NAME)['value']
@property
def text(self) -> str:
"""The text of the element."""
return self._execute(Command.GET_ELEMENT_TEXT)['value']
def click(self) -> None:
"""Clicks the element."""
self._execute(Command.CLICK_ELEMENT)
def submit(self):
"""Submits a form."""
form = self.find_element(By.XPATH, "./ancestor-or-self::form")
self._parent.execute_script(
"var e = arguments[0].ownerDocument.createEvent('Event');"
"e.initEvent('submit', true, true);"
"if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }", form)
def clear(self) -> None:
"""Clears the text if it's a text entry element."""
self._execute(Command.CLEAR_ELEMENT)
def get_property(self, name) -> str:
"""
Gets the given property of the element.
:Args:
- name - Name of the property to retrieve.
:Usage:
::
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:
"""
Gets the given attribute of the element. Unlike :func:`~selenium.webdriver.remote.BaseWebElement.get_attribute`,
this method only returns attributes declared in the element's HTML markup.
:Args:
- name - Name of the attribute to retrieve.
:Usage:
::
text_length = target_element.get_dom_attribute("class")
"""
return self._execute(Command.GET_ELEMENT_ATTRIBUTE, {"name": name})["value"]
def get_attribute(self, name) -> str:
"""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.
Example::
# Check if the "active" CSS class is applied to an element.
is_active = "active" in target_element.get_attribute("class")
"""
attribute_value = self.parent.execute_script(
"return (%s).apply(null, arguments);" % getAttribute_js,
self, name)
return attribute_value
def is_selected(self) -> bool:
"""Returns whether the element is selected.
Can be used to check if a checkbox or radio button is selected.
"""
return self._execute(Command.IS_ELEMENT_SELECTED)['value']
def is_enabled(self) -> bool:
"""Returns whether the element is enabled."""
return self._execute(Command.IS_ELEMENT_ENABLED)['value']
def find_element_by_id(self, id_):
"""Finds element within this element's children by ID.
:Args:
- id\\_ - ID of child element to locate.
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
::
foo_element = element.find_element_by_id('foo')
"""
warnings.warn("find_element_by_id is deprecated. Please use find_element(by=By.ID, value=id_) instead",
DeprecationWarning,
stacklevel=2)
return self.find_element(by=By.ID, value=id_)
def find_elements_by_id(self, id_):
"""Finds a list of elements within this element's children by ID.
Will return a list of webelements if found, or an empty list if not.
:Args:
- id\\_ - Id of child element to find.
:Returns:
- list of WebElement - a list with elements if any was found. An
empty list if not
:Usage:
::
elements = element.find_elements_by_id('foo')
"""
warnings.warn("find_elements_by_id is deprecated. Please use find_elements(by=By.ID, value=id_) instead",
DeprecationWarning,
stacklevel=2)
return self.find_elements(by=By.ID, value=id_)
def find_element_by_name(self, name):
"""Finds element within this element's children by name.
:Args:
- name - name property of the element to find.
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
::
element = element.find_element_by_name('foo')
"""
warnings.warn("find_element_by_name is deprecated. Please use find_element(by=By.NAME, value=name) instead",
DeprecationWarning,
stacklevel=2)
return self.find_element(by=By.NAME, value=name)
def find_elements_by_name(self, name):
"""Finds a list of elements within this element's children by name.
:Args:
- name - name property to search for.
:Returns:
- list of webelement - a list with elements if any was found. an
empty list if not
:Usage:
::
elements = element.find_elements_by_name('foo')
"""
warnings.warn("find_elements_by_name is deprecated. Please use find_elements(by=By.NAME, value=name) instead",
DeprecationWarning,
stacklevel=2)
return self.find_elements(by=By.NAME, value=name)
def find_element_by_link_text(self, link_text):
"""Finds element within this element's children by visible link text.
:Args:
- link_text - Link text string to search for.
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
::
element = element.find_element_by_link_text('Sign In')
"""
warnings.warn("find_element_by_link_text is deprecated. Please use find_element(by=By.LINK_TEXT, value=link_text) instead",
DeprecationWarning,
stacklevel=2)
return self.find_element(by=By.LINK_TEXT, value=link_text)
def find_elements_by_link_text(self, link_text):
"""Finds a list of elements within this element's children by visible link text.
:Args:
- link_text - Link text string to search for.
:Returns:
- list of webelement - a list with elements if any was found. an
empty list if not
:Usage:
::
elements = element.find_elements_by_link_text('Sign In')
"""
warnings.warn("find_elements_by_link_text is deprecated. Please use find_elements(by=By.LINK_TEXT, value=text) instead",
DeprecationWarning,
stacklevel=2)
return self.find_elements(by=By.LINK_TEXT, value=link_text)
def find_element_by_partial_link_text(self, link_text):
"""Finds element within this element's children by partially visible link text.
:Args:
- link_text: The text of the element to partially match on.
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
::
element = element.find_element_by_partial_link_text('Sign')
"""
warnings.warn("find_element_by_partial_link_text is deprecated. Please use find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) instead",
DeprecationWarning,
stacklevel=2)
return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text)
def find_elements_by_partial_link_text(self, link_text):
"""Finds a list of elements within this element's children by link text.
:Args:
- link_text: The text of the element to partial match on.
:Returns:
- list of webelement - a list with elements if any was found. an
empty list if not
:Usage:
::
elements = element.find_elements_by_partial_link_text('Sign')
"""
warnings.warn("find_elements_by_partial_link_text is deprecated. Please use find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) instead",
DeprecationWarning,
stacklevel=2)
return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text)
def find_element_by_tag_name(self, name):
"""Finds element within this element's children by tag name.
:Args:
- name - name of html tag (eg: h1, a, span)
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
::
element = element.find_element_by_tag_name('h1')
"""
warnings.warn("find_element_by_tag_name is deprecated. Please use find_element(by=By.TAG_NAME, value=name) instead",
DeprecationWarning,
stacklevel=2)
return self.find_element(by=By.TAG_NAME, value=name)
def find_elements_by_tag_name(self, name):
"""Finds a list of elements within this element's children by tag name.
:Args:
- name - name of html tag (eg: h1, a, span)
:Returns:
- list of WebElement - a list with elements if any was found. An
empty list if not
:Usage:
::
elements = element.find_elements_by_tag_name('h1')
"""
warnings.warn("find_elements_by_tag_name is deprecated. Please use find_elements(by=By.TAG_NAME, value=name) instead",
DeprecationWarning,
stacklevel=2)
return self.find_elements(by=By.TAG_NAME, value=name)
def find_element_by_xpath(self, xpath):
"""Finds element by xpath.
:Args:
- xpath - xpath of element to locate. "//input[@class='myelement']"
Note: The base path will be relative to this element's location.
This will select the first link under this element.
::
myelement.find_element_by_xpath(".//a")
However, this will select the first link on the page.
::
myelement.find_element_by_xpath("//a")
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
::
element = element.find_element_by_xpath('//div/td[1]')
"""
warnings.warn("find_element_by_xpath is deprecated. Please use find_element(by=By.XPATH, value=xpath) instead",
DeprecationWarning,
stacklevel=2)
return self.find_element(by=By.XPATH, value=xpath)
def find_elements_by_xpath(self, xpath):
"""Finds elements within the element by xpath.
:Args:
- xpath - xpath locator string.
Note: The base path will be relative to this element's location.
This will select all links under this element.
::
myelement.find_elements_by_xpath(".//a")
However, this will select all links in the page itself.
::
myelement.find_elements_by_xpath("//a")
:Returns:
- list of WebElement - a list with elements if any was found. An
empty list if not
:Usage:
::
elements = element.find_elements_by_xpath("//div[contains(@class, 'foo')]")
"""
warnings.warn("find_elements_by_xpath is deprecated. Please use find_elements(by=By.XPATH, value=xpath) instead",
DeprecationWarning,
stacklevel=2)
return self.find_elements(by=By.XPATH, value=xpath)
def find_element_by_class_name(self, name):
"""Finds element within this element's children by class name.
:Args:
- name: The class name of the element to find.
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
::
element = element.find_element_by_class_name('foo')
"""
warnings.warn("find_element_by_class_name is deprecated. Please use find_element(by=By.CLASS_NAME, value=name) instead",
DeprecationWarning,
stacklevel=2)
return self.find_element(by=By.CLASS_NAME, value=name)
def find_elements_by_class_name(self, name):
"""Finds a list of elements within this element's children by class name.
:Args:
- name: The class name of the elements to find.
:Returns:
- list of WebElement - a list with elements if any was found. An
empty list if not
:Usage:
::
elements = element.find_elements_by_class_name('foo')
"""
warnings.warn("find_elements_by_class_name is deprecated. Please use find_elements(by=By.CLASS_NAME, value=name) instead", DeprecationWarning)
return self.find_elements(by=By.CLASS_NAME, value=name)
def find_element_by_css_selector(self, css_selector):
"""Finds element within this element's children by CSS selector.
:Args:
- css_selector - CSS selector string, ex: 'a.nav#home'
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
::
element = element.find_element_by_css_selector('#foo')
"""
warnings.warn("find_element_by_css_selector is deprecated. Please use find_element(by=By.CSS_SELECTOR, value=css_selector) instead",
DeprecationWarning,
stacklevel=2)
return self.find_element(by=By.CSS_SELECTOR, value=css_selector)
def find_elements_by_css_selector(self, css_selector):
"""Finds a list of elements within this element's children by CSS selector.
:Args:
- css_selector - CSS selector string, ex: 'a.nav#home'
:Returns:
- list of WebElement - a list with elements if any was found. An
empty list if not
:Usage:
::
elements = element.find_elements_by_css_selector('.foo')
"""
warnings.warn("find_elements_by_css_selector is deprecated. Please use find_elements(by=By.CSS_SELECTOR, value=css_selector) instead",
DeprecationWarning,
stacklevel=2)
return self.find_elements(by=By.CSS_SELECTOR, value=css_selector)
def send_keys(self, *value) -> None:
"""Simulates typing into the element.
:Args:
- value - A string for typing, or setting form fields. For setting
file inputs, this could be a local file path.
Use this to send simple key events or to fill out form fields::
form_textfield = driver.find_element(By.NAME, 'username')
form_textfield.send_keys("admin")
This can also be used to set file inputs.
::
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 = '\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:
"""
Returns a shadow root of the element if there is one or an error. Only works from
Chromium 96 onwards. Previous versions of Chromium based browsers will throw an
assertion exception.
:Returns:
- ShadowRoot object or
- NoSuchShadowRoot - if no shadow root was attached to element
"""
browser_main_version = int(self._parent.caps["browserVersion"].split(".")[0])
assert self._parent.caps["browserName"].lower() not in ["firefox", "safari"], "This only currently works in Chromium based browsers"
assert not browser_main_version <= 95, f"Please use Chromium based browsers with version 96 or later. Version used {self._parent.caps['browserVersion']}"
return self._execute(Command.GET_SHADOW_ROOT)['value']
# RenderedWebElement Items
def is_displayed(self) -> bool:
"""Whether the element is visible to a user."""
# Only go into this conditional for browsers that don't use the atom themselves
return self.parent.execute_script(
"return (%s).apply(null, arguments);" % isDisplayed_js,
self)
@property
def location_once_scrolled_into_view(self) -> dict:
"""THIS PROPERTY MAY CHANGE WITHOUT WARNING. Use this to discover
where on the screen an element is so that we can click it. This method
should cause the element to be scrolled into view.
Returns the top lefthand corner location on the screen, or ``None`` if
the element is not visible.
"""
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:
"""The size of the element."""
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:
"""The value of a CSS property."""
return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, {
'propertyName': property_name})['value']
@property
def location(self) -> dict:
"""The location of the element in the renderable canvas."""
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:
"""A dictionary with the size and location of the element."""
return self._execute(Command.GET_ELEMENT_RECT)['value']
@property
def aria_role(self) -> str:
""" Returns the ARIA role of the current web element"""
return self._execute(Command.GET_ELEMENT_ARIA_ROLE)['value']
@property
def accessible_name(self) -> str:
"""Returns the ARIA Level of the current webelement"""
return self._execute(Command.GET_ELEMENT_ARIA_LABEL)['value']
@property
def screenshot_as_base64(self) -> str:
"""
Gets the screenshot of the current element as a base64 encoded string.
:Usage:
::
img_b64 = element.screenshot_as_base64
"""
return self._execute(Command.ELEMENT_SCREENSHOT)['value']
@property
def screenshot_as_png(self) -> str:
"""
Gets the screenshot of the current element as a binary data.
:Usage:
::
element_png = element.screenshot_as_png
"""
return b64decode(self.screenshot_as_base64.encode('ascii'))
def screenshot(self, filename) -> bool:
"""
Saves a screenshot of the current element to a PNG image file. Returns
False if there is any IOError, else returns True. 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.
:Usage:
::
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 IOError:
return False
finally:
del png
return True
@property
def parent(self):
"""Internal reference to the WebDriver instance this element was found from."""
return self._parent
@property
def id(self) -> str:
"""Internal 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 ``==``::
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=By.ID, value=None):
"""
Find an element given a By strategy and locator.
:Usage:
::
element = element.find_element(By.ID, 'foo')
:rtype: WebElement
"""
if by == By.ID:
by = By.CSS_SELECTOR
value = '[id="%s"]' % value
elif by == By.CLASS_NAME:
by = By.CSS_SELECTOR
value = ".%s" % value
elif by == By.NAME:
by = By.CSS_SELECTOR
value = '[name="%s"]' % value
return self._execute(Command.FIND_CHILD_ELEMENT,
{"using": by, "value": value})['value']
def find_elements(self, by=By.ID, value=None):
"""
Find elements given a By strategy and locator.
:Usage:
::
element = element.find_elements(By.CLASS_NAME, 'foo')
:rtype: list of WebElement
"""
if by == By.ID:
by = By.CSS_SELECTOR
value = '[id="%s"]' % value
elif by == By.CLASS_NAME:
by = By.CSS_SELECTOR
value = ".%s" % value
elif by == By.NAME:
by = By.CSS_SELECTOR
value = '[name="%s"]' % value
return self._execute(Command.FIND_CHILD_ELEMENTS,
{"using": by, "value": value})['value']
def __hash__(self):
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 e.__str__():
return filename
elif "Command not found: POST " in e.__str__():
return filename
elif '{"status":405,"value":["GET","HEAD","DELETE"]}' in e.__str__():
return filename
else:
raise e