diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index 9e39a0d0b9961..a1df36c8d9fa9 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -77,7 +77,7 @@ jobs: pip install tox==2.4.1 - name: Test with tox run: | - tox -c py/tox.ini -- --cobertura-xml-report ci || true + tox -c py/tox.ini -- --cobertura-xml-report ci bash <(curl -s https://codecov.io/bash) -f py/ci/cobertura.xml env: TOXENV: mypy diff --git a/.mailmap b/.mailmap index 27d944a97709f..1997006a681be 100644 --- a/.mailmap +++ b/.mailmap @@ -7,7 +7,6 @@ - @@ -23,6 +22,8 @@ Andreas Tolfsen Andreas Tolf Tolfsen Andreas Tolfsen Aslak Hellesøy Aslak Hellesoy Chethana Paniyadi CHETANA PANIYADI +colons +colons Daniel Davison ddavison David Burns David Burns Eran Messeri Eran Mes diff --git a/py/conftest.py b/py/conftest.py index 60735ec318a86..91e2eb7826506 100644 --- a/py/conftest.py +++ b/py/conftest.py @@ -25,7 +25,8 @@ from selenium import webdriver from selenium.webdriver import DesiredCapabilities -from test.selenium.webdriver.common.webserver import SimpleWebServer +from selenium.webdriver.remote.webdriver import WebDriver +from test.selenium.webdriver.common.webserver import Pages, SimpleWebServer from test.selenium.webdriver.common.network import get_lan_ip from urllib.request import urlopen @@ -185,14 +186,8 @@ def pytest_exception_interact(node, call, report): @pytest.fixture -def pages(driver, webserver): - class Pages: - def url(self, name, localhost=False): - return webserver.where_is(name, localhost) - - def load(self, name): - driver.get(self.url(name)) - return Pages() +def pages(driver: WebDriver, webserver: SimpleWebServer) -> Pages: + return Pages(driver, webserver) @pytest.fixture(autouse=True, scope='session') diff --git a/py/generate.py b/py/generate.py index 7b7689dd85c4b..ac9c2b48cc5d4 100644 --- a/py/generate.py +++ b/py/generate.py @@ -38,7 +38,7 @@ from textwrap import dedent, indent as tw_indent import typing -import inflection # type: ignore +import inflection log_level = getattr(logging, os.environ.get('LOG_LEVEL', 'warning').upper()) diff --git a/py/mypy.ini b/py/mypy.ini index ae0d60cc721eb..de2b38891809f 100644 --- a/py/mypy.ini +++ b/py/mypy.ini @@ -1,16 +1,4 @@ [mypy] -files = selenium -ignore_missing_imports = False -warn_unused_configs = True -disallow_subclassing_any = True -disallow_any_generics = True -disallow_untyped_calls = True -disallow_untyped_defs = True -disallow_incomplete_defs = True -check_untyped_defs = True -disallow_untyped_decorators = True -no_implicit_optional = True -warn_redundant_casts = True +files = selenium,generate.py,setup.py,test +ignore_missing_imports = True warn_unused_ignores = True -warn_return_any = True -warn_unreachable = True diff --git a/py/selenium/types.py b/py/selenium/types.py index 96d3830ba47ed..299aa64c48998 100644 --- a/py/selenium/types.py +++ b/py/selenium/types.py @@ -17,8 +17,7 @@ """Selenium type definitions.""" -import typing +from typing import Union -AnyKey = typing.Union[str, int, float] -WaitExcTypes = typing.Iterable[typing.Type[Exception]] +AnyKey = Union[str, int, float] diff --git a/py/selenium/webdriver/chromium/options.py b/py/selenium/webdriver/chromium/options.py index f33f5b14b469a..bcf8df5e979d3 100644 --- a/py/selenium/webdriver/chromium/options.py +++ b/py/selenium/webdriver/chromium/options.py @@ -18,7 +18,7 @@ import base64 import os import warnings -from typing import List, Union, BinaryIO +from typing import Any, BinaryIO, Dict, List, Optional, Union from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.options import ArgOptions @@ -30,10 +30,10 @@ class ChromiumOptions(ArgOptions): def __init__(self) -> None: super().__init__() self._binary_location = '' - self._extension_files = [] - self._extensions = [] - self._experimental_options = {} - self._debugger_address = None + self._extension_files: List[str] = [] + self._extensions: List[str] = [] + self._experimental_options: Dict[str, Any] = {} + self._debugger_address: Optional[str] = None @property def binary_location(self) -> str: @@ -52,7 +52,7 @@ def binary_location(self, value: str) -> None: self._binary_location = value @property - def debugger_address(self) -> str: + def debugger_address(self) -> Optional[str]: """ :Returns: The address of the remote devtools instance """ diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py index 03d1e6a013575..c57ff3243e49b 100644 --- a/py/selenium/webdriver/chromium/webdriver.py +++ b/py/selenium/webdriver/chromium/webdriver.py @@ -22,7 +22,7 @@ import warnings from selenium.webdriver.chromium.remote_connection import ChromiumRemoteConnection -from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.webdriver.remote.webdriver import ExecuteResponse, WebDriver as RemoteWebDriver DEFAULT_PORT = 0 DEFAULT_SERVICE_LOG_PATH = None @@ -63,7 +63,7 @@ def __init__(self, browser_name, vendor_prefix, if service_log_path != DEFAULT_SERVICE_LOG_PATH: warnings.warn('service_log_path has been deprecated, please pass in a Service object', DeprecationWarning, stacklevel=2) - if keep_alive != DEFAULT_KEEP_ALIVE and type(self) == __class__: + if keep_alive != DEFAULT_KEEP_ALIVE and type(self) == ChromiumDriver: warnings.warn('keep_alive has been deprecated, please pass in a Service object', DeprecationWarning, stacklevel=2) else: @@ -188,7 +188,7 @@ def get_issue_message(self): """ return self.execute('getIssueMessage')['value'] - def set_sink_to_use(self, sink_name: str) -> dict: + def set_sink_to_use(self, sink_name: str) -> ExecuteResponse: """ Sets a specific sink, using its name, as a Cast session receiver target. @@ -197,7 +197,7 @@ def set_sink_to_use(self, sink_name: str) -> dict: """ return self.execute('setSinkToUse', {'sinkName': sink_name}) - def start_desktop_mirroring(self, sink_name: str) -> dict: + def start_desktop_mirroring(self, sink_name: str) -> ExecuteResponse: """ Starts a desktop mirroring session on a specific receiver target. @@ -206,7 +206,7 @@ def start_desktop_mirroring(self, sink_name: str) -> dict: """ return self.execute('startDesktopMirroring', {'sinkName': sink_name}) - def start_tab_mirroring(self, sink_name: str) -> dict: + def start_tab_mirroring(self, sink_name: str) -> ExecuteResponse: """ Starts a tab mirroring session on a specific receiver target. @@ -215,7 +215,7 @@ def start_tab_mirroring(self, sink_name: str) -> dict: """ return self.execute('startTabMirroring', {'sinkName': sink_name}) - def stop_casting(self, sink_name: str) -> dict: + def stop_casting(self, sink_name: str) -> ExecuteResponse: """ Stops the existing Cast session on a specific receiver target. diff --git a/py/selenium/webdriver/common/actions/action_builder.py b/py/selenium/webdriver/common/actions/action_builder.py index f5d1c16314406..3b68ddee1cabe 100644 --- a/py/selenium/webdriver/common/actions/action_builder.py +++ b/py/selenium/webdriver/common/actions/action_builder.py @@ -15,9 +15,10 @@ # specific language governing permissions and limitations # under the License. -from typing import Union, List +from typing import Any, Dict, Optional, List from selenium.webdriver.remote.command import Command from . import interaction +from .input_device import InputDevice from .key_actions import KeyActions from .key_input import KeyInput from .pointer_actions import PointerActions @@ -26,7 +27,9 @@ from .wheel_actions import WheelActions -class ActionBuilder: +class ActionBuilder(object): + devices: List[InputDevice] + def __init__(self, driver, mouse=None, wheel=None, keyboard=None, duration=250) -> None: if not mouse: mouse = PointerInput(interaction.POINTER_MOUSE, "mouse") @@ -40,16 +43,16 @@ def __init__(self, driver, mouse=None, wheel=None, keyboard=None, duration=250) self._wheel_action = WheelActions(wheel) self.driver = driver - def get_device_with(self, name) -> Union["WheelInput", "PointerInput", "KeyInput"]: + def get_device_with(self, name) -> Optional[InputDevice]: return next(filter(lambda x: x == name, self.devices), None) @property def pointer_inputs(self) -> List[PointerInput]: - return [device for device in self.devices if device.type == interaction.POINTER] + return [device for device in self.devices if isinstance(device, PointerInput)] @property def key_inputs(self) -> List[KeyInput]: - return [device for device in self.devices if device.type == interaction.KEY] + return [device for device in self.devices if isinstance(device, KeyInput)] @property def key_action(self) -> KeyActions: @@ -79,7 +82,7 @@ def add_wheel_input(self, name) -> WheelInput: return new_input def perform(self) -> None: - enc = {"actions": []} + enc: Dict[str, List[Dict[str, Any]]] = {"actions": []} for device in self.devices: encoded = device.encode() if encoded['actions']: diff --git a/py/selenium/webdriver/common/actions/input_device.py b/py/selenium/webdriver/common/actions/input_device.py index d7df9e57ef1f7..0ad947196cdb5 100644 --- a/py/selenium/webdriver/common/actions/input_device.py +++ b/py/selenium/webdriver/common/actions/input_device.py @@ -15,6 +15,8 @@ # specific language governing permissions and limitations # under the License. +from abc import abstractmethod +from typing import Any, Dict, Union import uuid @@ -39,5 +41,9 @@ def add_action(self, action): def clear_actions(self): self.actions = [] - def create_pause(self, duration=0): + def create_pause(self, duration: Union[int, float] = 0) -> None: pass + + @abstractmethod + def encode(self) -> Dict[str, Any]: + raise NotImplementedError() diff --git a/py/selenium/webdriver/common/actions/wheel_input.py b/py/selenium/webdriver/common/actions/wheel_input.py index c20089ec2f7e9..43400c4d72f40 100644 --- a/py/selenium/webdriver/common/actions/wheel_input.py +++ b/py/selenium/webdriver/common/actions/wheel_input.py @@ -69,6 +69,6 @@ def create_scroll(self, x: int, y: int, delta_x: int, "deltaY": delta_y, "duration": duration, "origin": origin}) - def create_pause(self, pause_duration: Union[int, float]) -> None: + def create_pause(self, duration: Union[int, float] = 0) -> None: self.add_action( - {"type": "pause", "duration": int(pause_duration * 1000)}) + {"type": "pause", "duration": int(duration * 1000)}) diff --git a/py/selenium/webdriver/common/bidi/cdp.py b/py/selenium/webdriver/common/bidi/cdp.py index d5b9726d36e81..761a9bb56dab8 100644 --- a/py/selenium/webdriver/common/bidi/cdp.py +++ b/py/selenium/webdriver/common/bidi/cdp.py @@ -418,6 +418,7 @@ async def connect_session(self, target_id) -> 'CdpSession': Returns a new :class:`CdpSession` connected to the specified target. ''' global devtools + assert devtools is not None, "Devtools have not been imported. Call import_devtools(ver) first." session_id = await self.execute(devtools.target.attach_to_target( target_id, True)) session = CdpSession(self.ws, session_id, target_id) diff --git a/py/selenium/webdriver/common/by.py b/py/selenium/webdriver/common/by.py index 905c32567b203..5848b8bc1e190 100644 --- a/py/selenium/webdriver/common/by.py +++ b/py/selenium/webdriver/common/by.py @@ -19,8 +19,14 @@ The By implementation. """ +from __future__ import annotations -class By: +from enum import Enum + +from selenium.common.exceptions import WebDriverException + + +class By(Enum): """ Set of supported locator strategies. """ @@ -33,3 +39,20 @@ class By: TAG_NAME = "tag name" CLASS_NAME = "class name" CSS_SELECTOR = "css selector" + + @classmethod + def from_str(cls, by_str: str) -> By: + """ + Take the string representation of a locator strategy ("id", "css + selector", etc.), and return the appropriate By enum value for it. + Throw a WebDriverException if no such By type exists. + """ + + for value in cls.__members__.values(): + if value.value == by_str: + return value + + raise WebDriverException( + f"{by_str!r} is not a valid locator strategy. Use a member of the By enum directly or one of: " + f"{', '.join(repr(v.value) for v in cls.__members__.values())}" + ) diff --git a/py/selenium/webdriver/common/desired_capabilities.py b/py/selenium/webdriver/common/desired_capabilities.py index 706012ec02641..e4ffe1c18aa53 100644 --- a/py/selenium/webdriver/common/desired_capabilities.py +++ b/py/selenium/webdriver/common/desired_capabilities.py @@ -20,7 +20,10 @@ """ -class DesiredCapabilities: +FIREFOX_NAME = "firefox" + + +class DesiredCapabilities(object): """ Set of default supported desired capabilities. @@ -48,7 +51,7 @@ class DesiredCapabilities: """ FIREFOX = { - "browserName": "firefox", + "browserName": FIREFOX_NAME, "acceptInsecureCerts": True, "moz:debuggerAddress": True, } diff --git a/py/selenium/webdriver/common/log.py b/py/selenium/webdriver/common/log.py index 24f460ed6c067..a94219ee9c841 100644 --- a/py/selenium/webdriver/common/log.py +++ b/py/selenium/webdriver/common/log.py @@ -17,6 +17,7 @@ import json import pkgutil +from typing import Any, AsyncIterator, Dict, cast from contextlib import asynccontextmanager from importlib import import_module @@ -47,10 +48,10 @@ def __init__(self, driver, bidi_session) -> None: self.cdp = bidi_session.cdp self.devtools = bidi_session.devtools _pkg = '.'.join(__name__.split('.')[:-1]) - self._mutation_listener_js = pkgutil.get_data(_pkg, 'mutation-listener.js').decode('utf8').strip() + self._mutation_listener_js = cast(bytes, pkgutil.get_data(_pkg, 'mutation-listener.js')).decode('utf8').strip() @asynccontextmanager - async def mutation_events(self) -> dict: + async def mutation_events(self) -> AsyncIterator[Dict[str, Any]]: """ Listen for mutation events and emit them as they are found. @@ -77,7 +78,7 @@ async def mutation_events(self) -> dict: script_key = await page.execute(self.devtools.page.add_script_to_evaluate_on_new_document(self._mutation_listener_js)) self.driver.pin_script(self._mutation_listener_js, script_key) self.driver.execute_script(f"return {self._mutation_listener_js}") - event = {} + event: Dict[str, Any] = {} async with runtime.wait_for(self.devtools.runtime.BindingCalled) as evnt: yield event @@ -115,7 +116,7 @@ async def add_js_error_listener(self): js_exception.exception_details = exception.value.exception_details @asynccontextmanager - async def add_listener(self, event_type) -> dict: + async def add_listener(self, event_type) -> AsyncIterator[Dict[str, Any]]: """ Listen for certain events that are passed in. diff --git a/py/selenium/webdriver/common/options.py b/py/selenium/webdriver/common/options.py index 4b63ddc1d6894..46f3d52f0eb0f 100644 --- a/py/selenium/webdriver/common/options.py +++ b/py/selenium/webdriver/common/options.py @@ -14,8 +14,8 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import typing from abc import ABCMeta, abstractmethod +from typing import List, Optional from selenium.common.exceptions import InvalidArgumentException from selenium.webdriver.common.proxy import Proxy @@ -30,7 +30,8 @@ def __init__(self) -> None: super().__init__() self._caps = self.default_capabilities self.set_capability("pageLoadStrategy", "normal") - self.mobile_options = None + self.mobile_options: Optional[dict] = None + self._ignore_local_proxy: bool = False @property def capabilities(self): @@ -134,8 +135,8 @@ def timeouts(self, timeouts: dict) -> None: else: raise ValueError("Timeout keys can only be one of the following: implicit, pageLoad, script") - def enable_mobile(self, android_package: typing.Optional[str] = None, android_activity: typing.Optional[str] = None, - device_serial: typing.Optional[str] = None) -> None: + def enable_mobile(self, android_package: str = "", android_activity: Optional[str] = None, + device_serial: Optional[str] = None) -> None: """ Enables mobile browser use for browsers that support it @@ -226,11 +227,9 @@ def default_capabilities(self): class ArgOptions(BaseOptions): - def __init__(self) -> None: super().__init__() - self._arguments = [] - self._ignore_local_proxy = False + self._arguments: List[str] = [] @property def arguments(self): @@ -239,7 +238,7 @@ def arguments(self): """ return self._arguments - def add_argument(self, argument): + def add_argument(self, argument: str) -> None: """ Adds an argument to the list diff --git a/py/selenium/webdriver/common/utils.py b/py/selenium/webdriver/common/utils.py index c001e024fdc59..e22d72a05c223 100644 --- a/py/selenium/webdriver/common/utils.py +++ b/py/selenium/webdriver/common/utils.py @@ -23,7 +23,6 @@ import socket from selenium.types import AnyKey -from selenium.webdriver.common.keys import Keys _is_connectable_exceptions = (socket.error, ConnectionResetError) @@ -138,9 +137,7 @@ def keys_to_typing(value: Iterable[AnyKey]) -> List[str]: """Processes the values that will be typed in the element.""" typing: List[str] = [] for val in value: - if isinstance(val, Keys): - typing.append(val) - elif isinstance(val, int) or isinstance(val, float): + if isinstance(val, int) or isinstance(val, float): val = str(val) for i in range(len(val)): typing.append(val[i]) diff --git a/py/selenium/webdriver/common/virtual_authenticator.py b/py/selenium/webdriver/common/virtual_authenticator.py index 6d44ceeb6a7fa..cedb9e7c966bf 100644 --- a/py/selenium/webdriver/common/virtual_authenticator.py +++ b/py/selenium/webdriver/common/virtual_authenticator.py @@ -15,6 +15,8 @@ # specific language governing permissions and limitations # under the License. +from __future__ import annotations + import functools from base64 import urlsafe_b64encode, urlsafe_b64decode @@ -40,10 +42,19 @@ class Transport(Enum): INTERNAL = "internal" +class VirtualAuthenticatorOptionsDict(typing.TypedDict): + protocol: str + transport: str + hasResidentKey: bool + hasUserVerification: bool + isUserConsenting: bool + isUserVerified: bool + + class VirtualAuthenticatorOptions: - Protocol = Protocol - Transport = Transport + Protocol: typing.TypeAlias = Protocol + Transport: typing.TypeAlias = Transport def __init__(self) -> None: """Constructor. Initialize VirtualAuthenticatorOptions object. @@ -65,16 +76,16 @@ def __init__(self) -> None: self._is_user_verified: bool = False @property - def protocol(self) -> str: - return self._protocol.value + def protocol(self) -> Protocol: + return self._protocol @protocol.setter def protocol(self, protocol: Protocol) -> None: self._protocol = protocol @property - def transport(self) -> str: - return self._transport.value + def transport(self) -> Transport: + return self._transport @transport.setter def transport(self, transport: Transport) -> None: @@ -112,10 +123,10 @@ def is_user_verified(self) -> bool: def is_user_verified(self, value: bool) -> None: self._is_user_verified = value - def to_dict(self) -> typing.Dict[str, typing.Any]: + def to_dict(self) -> VirtualAuthenticatorOptionsDict: return { - "protocol": self.protocol, - "transport": self.transport, + "protocol": self.protocol.value, + "transport": self.transport.value, "hasResidentKey": self.has_resident_key, "hasUserVerification": self.has_user_verification, "isUserConsenting": self.is_user_consenting, diff --git a/py/selenium/webdriver/firefox/options.py b/py/selenium/webdriver/firefox/options.py index db5c21878c625..0e9a070653fe6 100644 --- a/py/selenium/webdriver/firefox/options.py +++ b/py/selenium/webdriver/firefox/options.py @@ -68,7 +68,7 @@ def binary_location(self) -> str: @binary_location.setter # noqa def binary_location(self, value: str) -> None: """ Sets the location of the browser binary by string """ - self.binary = value + self.binary = FirefoxBinary(value) @property def preferences(self) -> dict: @@ -127,7 +127,9 @@ def headless(self, value: bool) -> None: elif '-headless' in self._arguments: self._arguments.remove('-headless') - def enable_mobile(self, android_package: str = "org.mozilla.firefox", android_activity=None, device_serial=None): + def enable_mobile( + self, android_package: str = "org.mozilla.firefox", android_activity: str = None, device_serial: str = None + ): super().enable_mobile(android_package, android_activity, device_serial) def to_capabilities(self) -> dict: diff --git a/py/selenium/webdriver/firefox/remote_connection.py b/py/selenium/webdriver/firefox/remote_connection.py index eb359d5eb4746..cbdd6df539bc3 100644 --- a/py/selenium/webdriver/firefox/remote_connection.py +++ b/py/selenium/webdriver/firefox/remote_connection.py @@ -16,12 +16,12 @@ # under the License. from selenium.webdriver.remote.remote_connection import RemoteConnection -from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.common.desired_capabilities import FIREFOX_NAME class FirefoxRemoteConnection(RemoteConnection): - browser_name = DesiredCapabilities.FIREFOX['browserName'] + browser_name = FIREFOX_NAME def __init__(self, remote_server_addr, keep_alive=True, ignore_proxy=False): super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy) diff --git a/py/selenium/webdriver/firefox/webdriver.py b/py/selenium/webdriver/firefox/webdriver.py index b2ecf1e72d310..923c24c239b1a 100644 --- a/py/selenium/webdriver/firefox/webdriver.py +++ b/py/selenium/webdriver/firefox/webdriver.py @@ -317,7 +317,7 @@ def save_full_page_screenshot(self, filename) -> bool: """ return self.get_full_page_screenshot_as_file(filename) - def get_full_page_screenshot_as_png(self) -> str: + def get_full_page_screenshot_as_png(self) -> bytes: """ Gets the full document screenshot of the current window as a binary data. diff --git a/py/selenium/webdriver/ie/options.py b/py/selenium/webdriver/ie/options.py index 1d40c919bf572..55fba0b91e4fb 100644 --- a/py/selenium/webdriver/ie/options.py +++ b/py/selenium/webdriver/ie/options.py @@ -259,7 +259,7 @@ def persistent_hover(self, value: bool) -> None: self._options[self.PERSISTENT_HOVER] = value @property - def require_window_focus(self: bool): + def require_window_focus(self) -> bool: """:Returns: The options Require Window Focus value """ return self._options.get(self.REQUIRE_WINDOW_FOCUS) diff --git a/py/selenium/webdriver/remote/errorhandler.py b/py/selenium/webdriver/remote/errorhandler.py index 676b99afab53d..945601155b88f 100644 --- a/py/selenium/webdriver/remote/errorhandler.py +++ b/py/selenium/webdriver/remote/errorhandler.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -from typing import Any, Dict, Type +from typing import Any, Dict, Optional, Type from selenium.common.exceptions import (ElementClickInterceptedException, ElementNotInteractableException, @@ -114,7 +114,6 @@ def check_response(self, response: Dict[str, Any]) -> None: return value = None message = response.get("message", "") - screen: str = response.get("screen", "") stacktrace = None if isinstance(status, int): value_json = response.get('value', None) @@ -208,7 +207,7 @@ def check_response(self, response: Dict[str, Any]) -> None: if message == "" and 'message' in value: message = value['message'] - screen = None # type: ignore[assignment] + screen: Optional[str] = None if 'screen' in value: screen = value['screen'] @@ -233,11 +232,11 @@ def check_response(self, response: Dict[str, Any]) -> None: stacktrace.append(msg) except TypeError: pass - if exception_class == UnexpectedAlertPresentException: + if issubclass(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, alert_text) raise exception_class(message, screen, stacktrace) diff --git a/py/selenium/webdriver/remote/mobile.py b/py/selenium/webdriver/remote/mobile.py index 63b6b3e9ccbbf..ef67bad1487c8 100644 --- a/py/selenium/webdriver/remote/mobile.py +++ b/py/selenium/webdriver/remote/mobile.py @@ -71,16 +71,16 @@ def context(self): """ 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) -> 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) diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index 8bd022eef53f9..e9409c9c75ee0 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -18,6 +18,8 @@ import logging import socket import string +from typing import Dict, Optional +from urllib.parse import ParseResult import os import typing @@ -43,8 +45,9 @@ class RemoteConnection: Communicates with the server using the WebDriver wire protocol: https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol""" - browser_name = None - _timeout = socket._GLOBAL_DEFAULT_TIMEOUT + browser_name: Optional[str] = None + # this is not part of socket's public API: + _timeout = socket._GLOBAL_DEFAULT_TIMEOUT # type: ignore [attr-defined] _ca_certs = certifi.where() @classmethod @@ -92,7 +95,7 @@ def set_certificate_bundle_path(cls, path): cls._ca_certs = path @classmethod - def get_remote_connection_headers(cls, parsed_url, keep_alive=False): + def get_remote_connection_headers(cls, parsed_url: ParseResult, keep_alive: bool = False) -> Dict[str, str]: """ Get headers for remote request. diff --git a/py/selenium/webdriver/remote/shadowroot.py b/py/selenium/webdriver/remote/shadowroot.py index 72db4d2940c88..8eeb2dd7d29e4 100644 --- a/py/selenium/webdriver/remote/shadowroot.py +++ b/py/selenium/webdriver/remote/shadowroot.py @@ -15,6 +15,8 @@ # specific language governing permissions and limitations # under the License. +from __future__ import annotations + from hashlib import md5 as md5_hash from .command import Command @@ -40,7 +42,10 @@ def __repr__(self) -> str: type(self), self.session.session_id, self._id ) - def find_element(self, by: str = By.ID, value: str = None): + def find_element(self, by: By | str = By.ID, value: str = None): + if isinstance(by, str): + by = By.from_str(by) + if by == By.ID: by = By.CSS_SELECTOR value = '[id="%s"]' % value @@ -52,10 +57,13 @@ def find_element(self, by: str = By.ID, value: str = None): value = '[name="%s"]' % value return self._execute( - Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value} + Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by.value, "value": value} )["value"] - def find_elements(self, by: str = By.ID, value: str = None): + def find_elements(self, by: By | str = By.ID, value: str = None): + if isinstance(by, str): + by = By.from_str(by) + if by == By.ID: by = By.CSS_SELECTOR value = '[id="%s"]' % value @@ -67,7 +75,7 @@ def find_elements(self, by: str = By.ID, value: str = None): value = '[name="%s"]' % value return self._execute( - Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by, "value": value} + Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by.value, "value": value} )["value"] # Private Methods diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 25e244478d765..6fe84259ee2e6 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -16,17 +16,24 @@ # under the License. """The WebDriver implementation.""" + +from __future__ import annotations + import contextlib import copy import pkgutil import types -import typing import warnings from abc import ABCMeta from base64 import b64decode, urlsafe_b64encode from contextlib import asynccontextmanager, contextmanager from importlib import import_module -from typing import Dict, List, Optional, Union +import sys +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Type, Union, cast +if sys.version_info >= (3, 8): + from typing import TypedDict +else: + from typing_extensions import TypedDict from selenium.common.exceptions import (InvalidArgumentException, JavascriptException, @@ -55,6 +62,9 @@ from .switch_to import SwitchTo from .webelement import WebElement +if TYPE_CHECKING: + from selenium.webdriver.common.print_page_options import _PrintOpts + _W3C_CAPABILITY_NAMES = frozenset([ 'acceptInsecureCerts', 'browserName', @@ -132,9 +142,9 @@ def get_remote_connection(capabilities, command_executor, keep_alive, ignore_loc return handler(command_executor, keep_alive=keep_alive, ignore_proxy=ignore_local_proxy) -def create_matches(options: List[BaseOptions]) -> Dict: - capabilities = {"capabilities": {}} - opts = [] +def create_matches(options: List[BaseOptions]) -> Dict[str, Dict[str, Any]]: + capabilities: Dict[str, Dict[str, Any]] = {"capabilities": {}} + opts: List[dict] = [] for opt in options: opts.append(opt.to_capabilities()) opts_size = len(opts) @@ -156,9 +166,9 @@ def create_matches(options: List[BaseOptions]) -> Dict: for k, v in samesies.items(): always[k] = v - for i in opts: + for o in opts: for k in always.keys(): - del i[k] + del o[k] capabilities["capabilities"]["alwaysMatch"] = always capabilities["capabilities"]["firstMatch"] = opts @@ -166,6 +176,12 @@ def create_matches(options: List[BaseOptions]) -> Dict: return capabilities +class ExecuteResponse(TypedDict): + success: int + value: Any + sessionId: str + + class BaseWebDriver(metaclass=ABCMeta): """ Abstract Base Class for all Webdriver subtypes. @@ -259,8 +275,8 @@ def __init__(self, command_executor='http://127.0.0.1:4444', ignore_local_proxy=_ignore_local_proxy) self._is_remote = True self.session_id = None - self.caps = {} - self.pinned_scripts = {} + self.caps: Dict[str, Any] = {} + self.pinned_scripts: Dict[str, Any] = {} self.error_handler = ErrorHandler() self._switch_to = SwitchTo(self) self._mobile = Mobile(self) @@ -277,9 +293,9 @@ def __enter__(self): return self def __exit__(self, - exc_type: typing.Optional[typing.Type[BaseException]], - exc: typing.Optional[BaseException], - traceback: typing.Optional[types.TracebackType]): + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + traceback: Optional[types.TracebackType]): self.quit() @contextmanager @@ -364,12 +380,12 @@ def start_session(self, capabilities: dict, browser_profile=None) -> None: if 'sessionId' not in response: response = response['value'] self.session_id = response['sessionId'] - self.caps = response.get('value') - # if capabilities is none we are probably speaking to - # a W3C endpoint - if not self.caps: - self.caps = response.get('capabilities') + self.caps = cast(Dict[str, Any], response.get( + 'value', + # if capabilities is none we are probably speaking to a W3C endpoint + response.get('capabilities') + )) def _wrap_value(self, value): if isinstance(value, dict): @@ -405,7 +421,7 @@ def _unwrap_value(self, value): else: return value - def execute(self, driver_command: str, params: dict = None) -> dict: + def execute(self, driver_command: str, params: dict = None) -> ExecuteResponse: """ Sends a command to be executed by a command.CommandExecutor. @@ -612,11 +628,11 @@ def print_page(self, print_options: Optional[PrintOptions] = None) -> str: Takes PDF of the current page. The driver makes a best effort to return a PDF based on the provided parameters. """ - options = {} + options: '_PrintOpts' = {} if print_options: options = print_options.to_dict() - return self.execute(Command.PRINT_PAGE, options)['value'] + return self.execute(Command.PRINT_PAGE, cast(dict, options))['value'] @property def switch_to(self) -> SwitchTo: @@ -684,7 +700,7 @@ def get_cookies(self) -> List[dict]: """ return self.execute(Command.GET_ALL_COOKIES)['value'] - def get_cookie(self, name) -> typing.Optional[typing.Dict]: + def get_cookie(self, name) -> Optional[ExecuteResponse]: """ Get a single cookie by name. Returns the cookie if found, None if not. @@ -695,6 +711,7 @@ def get_cookie(self, name) -> typing.Optional[typing.Dict]: """ with contextlib.suppress(NoSuchCookieException): return self.execute(Command.GET_COOKIE, {"name": name})['value'] + return None def delete_cookie(self, name) -> None: """ @@ -825,7 +842,7 @@ def timeouts(self, timeouts) -> None: """ _ = self.execute(Command.SET_TIMEOUTS, timeouts._to_json())['value'] - def find_element(self, by=By.ID, value=None) -> WebElement: + def find_element(self, by: By | RelativeBy | str = By.ID, value=None) -> WebElement: """ Find an element given a By strategy and locator. @@ -839,8 +856,10 @@ def find_element(self, by=By.ID, value=None) -> WebElement: if isinstance(by, RelativeBy): elements = self.find_elements(by=by, value=value) if not elements: - raise NoSuchElementException(f"Cannot locate relative element with: {by.root}") + raise NoSuchElementException(f"Cannot locate relative element with: {by}") return elements[0] + elif isinstance(by, str): + by = By.from_str(by) if by == By.ID: by = By.CSS_SELECTOR @@ -853,10 +872,10 @@ def find_element(self, by=By.ID, value=None) -> WebElement: value = '[name="%s"]' % value return self.execute(Command.FIND_ELEMENT, { - 'using': by, + 'using': by.value, 'value': value})['value'] - def find_elements(self, by=By.ID, value=None) -> List[WebElement]: + def find_elements(self, by: By | RelativeBy | str = By.ID, value=None) -> List[WebElement]: """ Find elements given a By strategy and locator. @@ -869,9 +888,11 @@ def find_elements(self, by=By.ID, value=None) -> List[WebElement]: """ if isinstance(by, RelativeBy): _pkg = '.'.join(__name__.split('.')[:-1]) - raw_function = pkgutil.get_data(_pkg, 'findElements.js').decode('utf8') + raw_function = cast(bytes, pkgutil.get_data(_pkg, 'findElements.js')).decode('utf8') find_element_js = f"return ({raw_function}).apply(null, arguments);" return self.execute_script(find_element_js, by.to_dict()) + elif isinstance(by, str): + by = By.from_str(by) if by == By.ID: by = By.CSS_SELECTOR @@ -886,7 +907,7 @@ def find_elements(self, by=By.ID, value=None) -> List[WebElement]: # Return empty list if driver returns null # See https://github.com/SeleniumHQ/selenium/issues/4555 return self.execute(Command.FIND_ELEMENTS, { - 'using': by, + 'using': by.value, 'value': value})['value'] or [] @property @@ -1216,10 +1237,10 @@ def add_virtual_authenticator(self, options: VirtualAuthenticatorOptions) -> Non """ Adds a virtual authenticator with the given options. """ - self._authenticator_id = self.execute(Command.ADD_VIRTUAL_AUTHENTICATOR, options.to_dict())['value'] + self._authenticator_id = self.execute(Command.ADD_VIRTUAL_AUTHENTICATOR, cast(dict, options.to_dict()))['value'] @property - def virtual_authenticator_id(self) -> str: + def virtual_authenticator_id(self) -> Optional[str]: """ Returns the id of the virtual authenticator. """ @@ -1253,7 +1274,7 @@ def get_credentials(self) -> List[Credential]: return [Credential.from_dict(credential) for credential in credential_data['value']] @required_virtual_authenticator - def remove_credential(self, credential_id: Union[str, bytearray]) -> None: + def remove_credential(self, credential_id: str | bytearray) -> None: """ Removes a credential from the authenticator. """ diff --git a/py/selenium/webdriver/remote/webelement.py b/py/selenium/webdriver/remote/webelement.py index a2c76733b2421..dcfda60fd4852 100644 --- a/py/selenium/webdriver/remote/webelement.py +++ b/py/selenium/webdriver/remote/webelement.py @@ -24,8 +24,10 @@ import zipfile from abc import ABCMeta from io import BytesIO +from typing import Iterable from selenium.common.exceptions import WebDriverException, JavascriptException +from selenium.types import AnyKey from selenium.webdriver.common.by import By from selenium.webdriver.common.utils import keys_to_typing from .command import Command @@ -185,7 +187,7 @@ def is_enabled(self) -> bool: """Returns whether the element is enabled.""" return self._execute(Command.IS_ELEMENT_ENABLED)['value'] - def send_keys(self, *value) -> None: + def send_keys(self, *value: AnyKey) -> None: """Simulates typing into the element. :Args: @@ -210,6 +212,9 @@ def send_keys(self, *value) -> None: """ # transfer file to another machine only if remote driver is used # the same behaviour as for java binding + + output: Iterable[AnyKey] = value + if self.parent._is_remote: local_files = list(map(lambda keys_to_send: self.parent.file_detector.is_local_file(str(keys_to_send)), @@ -218,11 +223,11 @@ def send_keys(self, *value) -> None: remote_files = [] for file in local_files: remote_files.append(self._upload(file)) - value = '\n'.join(remote_files) + output = '\n'.join(remote_files) self._execute(Command.SEND_KEYS_TO_ELEMENT, - {'text': "".join(keys_to_typing(value)), - 'value': keys_to_typing(value)}) + {'text': "".join(keys_to_typing(output)), + 'value': keys_to_typing(output)}) @property def shadow_root(self) -> ShadowRoot: @@ -395,7 +400,7 @@ def _execute(self, command, params=None): params['id'] = self._id return self._parent.execute(command, params) - def find_element(self, by=By.ID, value=None) -> WebElement: + def find_element(self, by: By | str = By.ID, value=None) -> WebElement: """ Find an element given a By strategy and locator. @@ -406,6 +411,10 @@ def find_element(self, by=By.ID, value=None) -> WebElement: :rtype: WebElement """ + + if isinstance(by, str): + by = By.from_str(by) + if by == By.ID: by = By.CSS_SELECTOR value = '[id="%s"]' % value @@ -417,9 +426,9 @@ def find_element(self, by=By.ID, value=None) -> WebElement: value = '[name="%s"]' % value return self._execute(Command.FIND_CHILD_ELEMENT, - {"using": by, "value": value})['value'] + {"using": by.value, "value": value})['value'] - def find_elements(self, by=By.ID, value=None) -> list[WebElement]: + def find_elements(self, by: By | str = By.ID, value=None) -> list[WebElement]: """ Find elements given a By strategy and locator. @@ -430,6 +439,10 @@ def find_elements(self, by=By.ID, value=None) -> list[WebElement]: :rtype: list of WebElement """ + + if isinstance(by, str): + by = By.from_str(by) + if by == By.ID: by = By.CSS_SELECTOR value = '[id="%s"]' % value @@ -441,7 +454,7 @@ def find_elements(self, by=By.ID, value=None) -> list[WebElement]: value = '[name="%s"]' % value return self._execute(Command.FIND_CHILD_ELEMENTS, - {"using": by, "value": value})['value'] + {"using": by.value, "value": value})['value'] def __hash__(self): return int(md5_hash(self._id.encode('utf-8')).hexdigest(), 16) diff --git a/py/selenium/webdriver/safari/service.py b/py/selenium/webdriver/safari/service.py index 7ec8c421a00fb..7ef45ccfeafef 100644 --- a/py/selenium/webdriver/safari/service.py +++ b/py/selenium/webdriver/safari/service.py @@ -52,9 +52,7 @@ def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, self.service_args = service_args or [] self.quiet = quiet - log = PIPE - if quiet: - log = open(os.devnull, 'w') + log = open(os.devnull, 'w') if quiet else PIPE super().__init__(executable_path, port, log) def command_line_args(self): diff --git a/py/selenium/webdriver/support/color.py b/py/selenium/webdriver/support/color.py index 429b1517fa0e2..ea781938a43c9 100644 --- a/py/selenium/webdriver/support/color.py +++ b/py/selenium/webdriver/support/color.py @@ -87,8 +87,7 @@ def groups(self) -> Sequence[str]: elif m.match(RGBA_PATTERN, str_): return cls(*m.groups) elif m.match(RGBA_PCT_PATTERN, str_): - rgba = tuple( - [float(each) / 100 * 255 for each in m.groups[:3]] + [m.groups[3]]) # type: ignore + rgba = tuple(float(each) / 100 * 255 for each in m.groups[:3]) + (float(m.groups[3]),) return cls(*rgba) elif m.match(HEX_PATTERN, str_): rgb = tuple(int(each, 16) for each in m.groups) diff --git a/py/selenium/webdriver/support/relative_locator.py b/py/selenium/webdriver/support/relative_locator.py index 8363e5d0f79ff..df3602db46651 100644 --- a/py/selenium/webdriver/support/relative_locator.py +++ b/py/selenium/webdriver/support/relative_locator.py @@ -15,8 +15,9 @@ # specific language governing permissions and limitations # under the License. +from __future__ import annotations -from typing import Dict, List, Union +from typing import Literal, Optional, TypedDict, Union from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement @@ -36,10 +37,10 @@ def with_tag_name(tag_name: str) -> "RelativeBy": """ if not tag_name: raise WebDriverException("tag_name can not be null") - return RelativeBy({"css selector": tag_name}) + return RelativeBy({By.CSS_SELECTOR: tag_name}) -def locate_with(by: By, using: str) -> "RelativeBy": +def locate_with(by: By | str, using: str) -> "RelativeBy": """ Start searching for relative objects your search criteria with By. @@ -52,9 +53,27 @@ def locate_with(by: By, using: str) -> "RelativeBy": """ assert by is not None, "Please pass in a by argument" assert using is not None, "Please pass in a using argument" + + if isinstance(by, str): + by = By.from_str(by) + return RelativeBy({by: using}) +class Filter(TypedDict): + kind: Literal["above", "below", "left", "right", "near"] + args: list[WebElement | dict | int] + + +class RelativeByDictRelative(TypedDict): + root: Optional[dict[str, str]] + filters: list[Filter] + + +class RelativeByDict(TypedDict): + relative: RelativeByDictRelative + + class RelativeBy: """ Gives the opportunity to find elements based on their relative location @@ -71,7 +90,7 @@ class RelativeBy: assert "mid" in ids """ - def __init__(self, root: Dict[By, str] = None, filters: List = None): + def __init__(self, root: dict[By, str] = None, filters: list[Filter] = None) -> None: """ Creates a new RelativeBy object. It is preferred if you use the `locate_with` method as this signature could change. @@ -83,7 +102,10 @@ def __init__(self, root: Dict[By, str] = None, filters: List = None): self.root = root self.filters = filters or [] - def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": + def __str__(self) -> str: + return str(self.to_dict()["relative"]["root"]) + + def above(self, element_or_locator: Union[WebElement, dict] = None) -> "RelativeBy": """ Add a filter to look for elements above. :Args: @@ -95,7 +117,7 @@ def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "Relative self.filters.append({"kind": "above", "args": [element_or_locator]}) return self - def below(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": + def below(self, element_or_locator: Union[WebElement, dict] = None) -> "RelativeBy": """ Add a filter to look for elements below. :Args: @@ -107,7 +129,7 @@ def below(self, element_or_locator: Union[WebElement, Dict] = None) -> "Relative self.filters.append({"kind": "below", "args": [element_or_locator]}) return self - def to_left_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": + def to_left_of(self, element_or_locator: Union[WebElement, dict] = None) -> "RelativeBy": """ Add a filter to look for elements to the left of. :Args: @@ -119,7 +141,7 @@ def to_left_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Rel self.filters.append({"kind": "left", "args": [element_or_locator]}) return self - def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": + def to_right_of(self, element_or_locator: Union[WebElement, dict] = None) -> "RelativeBy": """ Add a filter to look for elements right of. :Args: @@ -131,7 +153,7 @@ def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Re self.filters.append({"kind": "right", "args": [element_or_locator]}) return self - def near(self, element_or_locator_distance: Union[WebElement, Dict, int] = None) -> "RelativeBy": + def near(self, element_or_locator_distance: Union[WebElement, dict, int] = None) -> "RelativeBy": """ Add a filter to look for elements near. :Args: @@ -143,13 +165,13 @@ def near(self, element_or_locator_distance: Union[WebElement, Dict, int] = None) self.filters.append({"kind": "near", "args": [element_or_locator_distance]}) return self - def to_dict(self) -> Dict: + def to_dict(self) -> RelativeByDict: """ Create a dict that will be passed to the driver to start searching for the element """ return { 'relative': { - 'root': self.root, + 'root': {k.value: v for k, v in self.root.items()} if self.root is not None else None, 'filters': self.filters, } } diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 4952d4c8575c8..ce98b0902cb3a 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -16,18 +16,18 @@ # under the License. import time -import typing +from typing import Iterable, Optional, Tuple, Type, Union -from selenium.types import WaitExcTypes from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import TimeoutException POLL_FREQUENCY: float = 0.5 # How long to sleep in between calls to the method -IGNORED_EXCEPTIONS: typing.Tuple[typing.Type[Exception]] = (NoSuchElementException,) # default to be ignored. +IGNORED_EXCEPTIONS: Tuple[Type[Exception]] = (NoSuchElementException,) # default to be ignored. class WebDriverWait: - def __init__(self, driver, timeout: float, poll_frequency: float = POLL_FREQUENCY, ignored_exceptions: typing.Optional[WaitExcTypes] = None): + def __init__(self, driver, timeout: float, poll_frequency: float = POLL_FREQUENCY, + ignored_exceptions: Optional[Union[Iterable[Type[Exception]], Type[Exception]]] = None): """Constructor, takes a WebDriver instance and timeout in seconds. :Args: @@ -53,9 +53,9 @@ def __init__(self, driver, timeout: float, poll_frequency: float = POLL_FREQUENC self._poll = POLL_FREQUENCY exceptions = list(IGNORED_EXCEPTIONS) if ignored_exceptions: - try: + if isinstance(ignored_exceptions, Iterable): exceptions.extend(iter(ignored_exceptions)) - except TypeError: # ignored_exceptions is not iterable + else: exceptions.append(ignored_exceptions) self._ignored_exceptions = tuple(exceptions) diff --git a/py/test/selenium/webdriver/chrome/proxy_tests.py b/py/test/selenium/webdriver/chrome/proxy_tests.py index 49cec9c5124fb..da793f0047938 100644 --- a/py/test/selenium/webdriver/chrome/proxy_tests.py +++ b/py/test/selenium/webdriver/chrome/proxy_tests.py @@ -21,7 +21,7 @@ from selenium import webdriver -def test_bad_proxy_doesnt_interfere(): +def test_bad_proxy_doesnt_interfere() -> None: # these values should be ignored if ignore_local_proxy_environment_variables() is called. os.environ['https_proxy'] = 'bad' @@ -30,8 +30,7 @@ def test_bad_proxy_doesnt_interfere(): options.ignore_local_proxy_environment_variables() - chrome_kwargs = {'options': options} - driver = webdriver.Chrome(**chrome_kwargs) + driver = webdriver.Chrome(options=options) assert hasattr(driver, 'command_executor') assert hasattr(driver.command_executor, '_proxy_url') diff --git a/py/test/selenium/webdriver/common/alerts_tests.py b/py/test/selenium/webdriver/common/alerts_tests.py index 6bc191488ca36..9d73fd2ffe152 100644 --- a/py/test/selenium/webdriver/common/alerts_tests.py +++ b/py/test/selenium/webdriver/common/alerts_tests.py @@ -16,9 +16,11 @@ # under the License. import sys +from typing import Iterator import pytest +from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait @@ -26,10 +28,11 @@ InvalidElementStateException, NoAlertPresentException, UnexpectedAlertPresentException) +from test.selenium.webdriver.common.webserver import Pages @pytest.fixture(autouse=True) -def close_alert(driver): +def close_alert(driver) -> Iterator[None]: yield try: driver.switch_to.alert.dismiss() @@ -37,7 +40,7 @@ def close_alert(driver): pass -def test_should_be_able_to_override_the_window_alert_method(driver, pages): +def test_should_be_able_to_override_the_window_alert_method(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.execute_script( "window.alert = function(msg) { document.getElementById('text').innerHTML = msg; }") @@ -54,7 +57,7 @@ def test_should_be_able_to_override_the_window_alert_method(driver, pages): raise e -def test_should_allow_users_to_accept_an_alert_manually(driver, pages): +def test_should_allow_users_to_accept_an_alert_manually(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(by=By.ID, value="alert").click() alert = _wait_for_alert(driver) @@ -63,7 +66,7 @@ def test_should_allow_users_to_accept_an_alert_manually(driver, pages): assert "Testing Alerts" == driver.title -def test_should_allow_users_to_accept_an_alert_with_no_text_manually(driver, pages): +def test_should_allow_users_to_accept_an_alert_with_no_text_manually(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "empty-alert").click() alert = _wait_for_alert(driver) @@ -73,7 +76,7 @@ def test_should_allow_users_to_accept_an_alert_with_no_text_manually(driver, pag assert "Testing Alerts" == driver.title -def test_should_get_text_of_alert_opened_in_set_timeout(driver, pages): +def test_should_get_text_of_alert_opened_in_set_timeout(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "slow-alert").click() @@ -88,7 +91,7 @@ def test_should_get_text_of_alert_opened_in_set_timeout(driver, pages): alert.accept() -def test_should_allow_users_to_dismiss_an_alert_manually(driver, pages): +def test_should_allow_users_to_dismiss_an_alert_manually(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(by=By.ID, value="alert").click() alert = _wait_for_alert(driver) @@ -97,7 +100,7 @@ def test_should_allow_users_to_dismiss_an_alert_manually(driver, pages): assert "Testing Alerts" == driver.title -def test_should_allow_auser_to_accept_aprompt(driver, pages): +def test_should_allow_auser_to_accept_aprompt(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(by=By.ID, value="prompt").click() alert = _wait_for_alert(driver) @@ -107,7 +110,7 @@ def test_should_allow_auser_to_accept_aprompt(driver, pages): assert "Testing Alerts" == driver.title -def test_should_allow_auser_to_dismiss_aprompt(driver, pages): +def test_should_allow_auser_to_dismiss_aprompt(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(by=By.ID, value="prompt").click() alert = _wait_for_alert(driver) @@ -117,7 +120,7 @@ def test_should_allow_auser_to_dismiss_aprompt(driver, pages): assert "Testing Alerts" == driver.title -def test_should_allow_auser_to_set_the_value_of_aprompt(driver, pages): +def test_should_allow_auser_to_set_the_value_of_aprompt(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(by=By.ID, value="prompt").click() alert = _wait_for_alert(driver) @@ -130,7 +133,7 @@ def test_should_allow_auser_to_set_the_value_of_aprompt(driver, pages): @pytest.mark.xfail_firefox @pytest.mark.xfail_remote -def test_setting_the_value_of_an_alert_throws(driver, pages): +def test_setting_the_value_of_an_alert_throws(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "alert").click() @@ -148,7 +151,7 @@ def test_setting_the_value_of_an_alert_throws(driver, pages): condition=sys.platform == 'darwin', reason='https://bugs.chromium.org/p/chromedriver/issues/detail?id=26', run=False) -def test_alert_should_not_allow_additional_commands_if_dimissed(driver, pages): +def test_alert_should_not_allow_additional_commands_if_dimissed(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "alert").click() @@ -162,7 +165,7 @@ def test_alert_should_not_allow_additional_commands_if_dimissed(driver, pages): @pytest.mark.xfail_firefox(reason='Fails on travis') @pytest.mark.xfail_remote(reason='Fails on travis') @pytest.mark.xfail_safari -def test_should_allow_users_to_accept_an_alert_in_aframe(driver, pages): +def test_should_allow_users_to_accept_an_alert_in_aframe(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.switch_to.frame(driver.find_element(By.NAME, "iframeWithAlert")) driver.find_element(By.ID, "alertInFrame").click() @@ -176,7 +179,7 @@ def test_should_allow_users_to_accept_an_alert_in_aframe(driver, pages): @pytest.mark.xfail_firefox(reason='Fails on travis') @pytest.mark.xfail_remote(reason='Fails on travis') @pytest.mark.xfail_safari -def test_should_allow_users_to_accept_an_alert_in_anested_frame(driver, pages): +def test_should_allow_users_to_accept_an_alert_in_anested_frame(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.switch_to.frame(driver.find_element(By.NAME, "iframeWithIframe")) driver.switch_to.frame(driver.find_element(By.NAME, "iframeWithAlert")) @@ -194,7 +197,7 @@ def test_should_throw_an_exception_if_an_alert_has_not_been_dealt_with_and_dismi # //TODO(David) Complete this test -def test_prompt_should_use_default_value_if_no_keys_sent(driver, pages): +def test_prompt_should_use_default_value_if_no_keys_sent(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "prompt-with-default").click() @@ -205,7 +208,7 @@ def test_prompt_should_use_default_value_if_no_keys_sent(driver, pages): assert "This is a default value" == txt -def test_prompt_should_have_null_value_if_dismissed(driver, pages): +def test_prompt_should_have_null_value_if_dismissed(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "prompt-with-default").click() alert = _wait_for_alert(driver) @@ -214,7 +217,7 @@ def test_prompt_should_have_null_value_if_dismissed(driver, pages): assert "null" == driver.find_element(By.ID, "text").text -def test_handles_two_alerts_from_one_interaction(driver, pages): +def test_handles_two_alerts_from_one_interaction(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "double-prompt").click() @@ -232,7 +235,7 @@ def test_handles_two_alerts_from_one_interaction(driver, pages): @pytest.mark.xfail_safari -def test_should_handle_alert_on_page_load(driver, pages): +def test_should_handle_alert_on_page_load(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "open-page-with-onload-alert").click() alert = _wait_for_alert(driver) @@ -241,7 +244,7 @@ def test_should_handle_alert_on_page_load(driver, pages): assert "onload" == value -def test_should_handle_alert_on_page_load_using_get(driver, pages): +def test_should_handle_alert_on_page_load_using_get(driver: WebDriver, pages: Pages) -> None: pages.load("pageWithOnLoad.html") alert = _wait_for_alert(driver) value = alert.text @@ -253,7 +256,7 @@ def test_should_handle_alert_on_page_load_using_get(driver, pages): @pytest.mark.xfail_chrome(reason='Non W3C conformant') @pytest.mark.xfail_chromiumedge(reason='Non W3C conformant') -def test_should_handle_alert_on_page_before_unload(driver, pages): +def test_should_handle_alert_on_page_before_unload(driver: WebDriver, pages: Pages) -> None: pages.load("pageWithOnBeforeUnloadMessage.html") element = driver.find_element(By.ID, "navigate") @@ -261,7 +264,7 @@ def test_should_handle_alert_on_page_before_unload(driver, pages): WebDriverWait(driver, 3).until(EC.title_is("Testing Alerts")) -def test_should_allow_the_user_to_get_the_text_of_an_alert(driver, pages): +def test_should_allow_the_user_to_get_the_text_of_an_alert(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(by=By.ID, value="alert").click() alert = _wait_for_alert(driver) @@ -270,7 +273,7 @@ def test_should_allow_the_user_to_get_the_text_of_an_alert(driver, pages): assert "cheese" == value -def test_should_allow_the_user_to_get_the_text_of_aprompt(driver, pages): +def test_should_allow_the_user_to_get_the_text_of_aprompt(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "prompt").click() @@ -281,7 +284,7 @@ def test_should_allow_the_user_to_get_the_text_of_aprompt(driver, pages): assert "Enter something" == value -def test_alert_should_not_allow_additional_commands_if_dismissed(driver, pages): +def test_alert_should_not_allow_additional_commands_if_dismissed(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(By.ID, "alert").click() @@ -297,7 +300,7 @@ def test_alert_should_not_allow_additional_commands_if_dismissed(driver, pages): @pytest.mark.xfail_remote( reason='https://bugzilla.mozilla.org/show_bug.cgi?id=1279211') @pytest.mark.xfail_chrome -def test_unexpected_alert_present_exception_contains_alert_text(driver, pages): +def test_unexpected_alert_present_exception_contains_alert_text(driver: WebDriver, pages: Pages) -> None: pages.load("alerts.html") driver.find_element(by=By.ID, value="alert").click() alert = _wait_for_alert(driver) diff --git a/py/test/selenium/webdriver/common/children_finding_tests.py b/py/test/selenium/webdriver/common/children_finding_tests.py index 6f667b2bd14e8..d171d3e2fa3e9 100755 --- a/py/test/selenium/webdriver/common/children_finding_tests.py +++ b/py/test/selenium/webdriver/common/children_finding_tests.py @@ -155,6 +155,20 @@ def test_should_find_elements_by_tag_name(driver, pages): assert 2 == len(elements) +def test_should_find_element_by_tag_name_with_string_by(driver, pages): + pages.load("nestedElements.html") + parent = driver.find_element("name", "div1") + element = parent.find_element("tag name", "a") + assert "link1" == element.get_attribute("name") + + +def test_should_find_elements_by_tag_name_with_string_by(driver, pages): + pages.load("nestedElements.html") + parent = driver.find_element("name", "div1") + elements = parent.find_elements("tag name", "a") + assert 2 == len(elements) + + def test_should_be_able_to_find_an_element_by_css_selector(driver, pages): pages.load("nestedElements.html") parent = driver.find_element(By.NAME, "form2") diff --git a/py/test/selenium/webdriver/common/executing_javascript_tests.py b/py/test/selenium/webdriver/common/executing_javascript_tests.py index c29b8ea19e966..d28c5347aecf5 100644 --- a/py/test/selenium/webdriver/common/executing_javascript_tests.py +++ b/py/test/selenium/webdriver/common/executing_javascript_tests.py @@ -21,11 +21,6 @@ from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement -try: - str = unicode -except NameError: - pass - def test_should_be_able_to_execute_simple_javascript_and_return_astring(driver, pages): pages.load("xhtmlTest.html") diff --git a/py/test/selenium/webdriver/common/virtual_authenticator_tests.py b/py/test/selenium/webdriver/common/virtual_authenticator_tests.py index 914bba375baed..0d7071cfacb2d 100644 --- a/py/test/selenium/webdriver/common/virtual_authenticator_tests.py +++ b/py/test/selenium/webdriver/common/virtual_authenticator_tests.py @@ -56,7 +56,7 @@ }]).then(arguments[arguments.length - 1]);''' -def create_rk_enabled_u2f_authenticator(driver) -> WebDriver: +def create_rk_enabled_u2f_authenticator(driver: WebDriver) -> WebDriver: options = VirtualAuthenticatorOptions() options.protocol = VirtualAuthenticatorOptions.Protocol.U2F diff --git a/py/test/selenium/webdriver/common/web_components_tests.py b/py/test/selenium/webdriver/common/web_components_tests.py index 0e91aa16e066b..f369d002615f6 100644 --- a/py/test/selenium/webdriver/common/web_components_tests.py +++ b/py/test/selenium/webdriver/common/web_components_tests.py @@ -17,7 +17,7 @@ import pytest -from selenium.common.exceptions import NoSuchShadowRootException +from selenium.common.exceptions import NoSuchShadowRootException, WebDriverException from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.remote.shadowroot import ShadowRoot @@ -75,3 +75,27 @@ def test_can_find_elements_in_a_shadow_root(driver, pages): assert len(elements) == 1 assert isinstance(elements[0], WebElement) + + +@pytest.mark.xfail_safari +@pytest.mark.xfail_firefox +@pytest.mark.xfail_remote +def test_can_find_elements_in_a_shadow_root_using_a_string_locator(driver, pages): + pages.load("webComponents.html") + custom_element = driver.find_element("css selector", "custom-checkbox-element") + shadow_root = custom_element.shadow_root + elements = shadow_root.find_elements("css selector", "input") + assert len(elements) == 1 + + assert isinstance(elements[0], WebElement) + + +@pytest.mark.xfail_safari +@pytest.mark.xfail_firefox +@pytest.mark.xfail_remote +def test_finding_element_in_a_shadowroot_fails_with_invalid_string_locator(driver, pages): + pages.load("webComponents.html") + custom_element = driver.find_element("css selector", "custom-checkbox-element") + shadow_root = custom_element.shadow_root + with pytest.raises(WebDriverException): + shadow_root.find_elements("not a real locator strategy", "value") diff --git a/py/test/selenium/webdriver/common/webserver.py b/py/test/selenium/webdriver/common/webserver.py index c2c7ac41b18e4..c233a18a93f00 100644 --- a/py/test/selenium/webdriver/common/webserver.py +++ b/py/test/selenium/webdriver/common/webserver.py @@ -25,13 +25,15 @@ try: from urllib import request as urllib_request except ImportError: - import urllib as urllib_request + import urllib as urllib_request # type: ignore [no-redef] try: from http.server import BaseHTTPRequestHandler, HTTPServer from socketserver import ThreadingMixIn except ImportError: - from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer - from SocketServer import ThreadingMixIn + from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer # type: ignore [no-redef] + from SocketServer import ThreadingMixIn # type: ignore [no-redef] + +from selenium.webdriver.remote.webdriver import WebDriver def updir(): @@ -169,6 +171,18 @@ def where_is(self, path, localhost=False) -> str: return f"http://{self.host}:{self.port}/{path}" +class Pages: + def __init__(self, driver: WebDriver, webserver: SimpleWebServer) -> None: + self._driver = driver + self._webserver = webserver + + def url(self, name: str, localhost: bool = False) -> str: + return self._webserver.where_is(name, localhost) + + def load(self, name: str): + return self._driver.get(self.url(name)) + + def main(argv=None): from optparse import OptionParser from time import sleep diff --git a/py/test/selenium/webdriver/support/event_firing_webdriver_tests.py b/py/test/selenium/webdriver/support/event_firing_webdriver_tests.py index e9f5e7e1e4a84..4307ce472fde2 100644 --- a/py/test/selenium/webdriver/support/event_firing_webdriver_tests.py +++ b/py/test/selenium/webdriver/support/event_firing_webdriver_tests.py @@ -17,6 +17,7 @@ from io import BytesIO +from typing import Iterator import pytest @@ -25,35 +26,37 @@ from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.actions.action_builder import ActionBuilder +from selenium.webdriver.remote.webdriver import WebDriver +from test.selenium.webdriver.common.webserver import Pages @pytest.fixture -def log(): +def log() -> Iterator[BytesIO]: log = BytesIO() yield log log.close() -def test_should_fire_navigation_events(driver, log, pages): +def test_should_fire_navigation_events(driver: WebDriver, log: BytesIO, pages: Pages) -> None: class EventListener(AbstractEventListener): - def before_navigate_to(self, url, driver): + def before_navigate_to(self, url: str, driver: WebDriver) -> None: log.write(("before_navigate_to %s" % url.split("/")[-1]).encode()) - def after_navigate_to(self, url, driver): + def after_navigate_to(self, url: str, driver: WebDriver) -> None: log.write(("after_navigate_to %s" % url.split("/")[-1]).encode()) - def before_navigate_back(self, driver): + def before_navigate_back(self, driver: WebDriver) -> None: log.write(b"before_navigate_back") - def after_navigate_back(self, driver): + def after_navigate_back(self, driver: WebDriver) -> None: log.write(b"after_navigate_back") - def before_navigate_forward(self, driver): + def before_navigate_forward(self, driver: WebDriver) -> None: log.write(b"before_navigate_forward") - def after_navigate_forward(self, driver): + def after_navigate_forward(self, driver: WebDriver) -> None: log.write(b"after_navigate_forward") ef_driver = EventFiringWebDriver(driver, EventListener()) @@ -77,7 +80,7 @@ def after_navigate_forward(self, driver): @pytest.mark.xfail_safari -def test_should_fire_click_event(driver, log, pages): +def test_should_fire_click_event(driver: WebDriver, log: BytesIO, pages: Pages) -> None: class EventListener(AbstractEventListener): @@ -95,7 +98,7 @@ def after_click(self, element, driver): assert b"before_click" + b"after_click" == log.getvalue() -def test_should_fire_change_value_event(driver, log, pages): +def test_should_fire_change_value_event(driver: WebDriver, log: BytesIO, pages: Pages) -> None: class EventListener(AbstractEventListener): @@ -122,15 +125,15 @@ def after_change_value_of(self, element, driver): b"after_change_value_of") == log.getvalue() -def test_should_fire_find_event(driver, log, pages): +def test_should_fire_find_event(driver: WebDriver, log: BytesIO, pages: Pages) -> None: class EventListener(AbstractEventListener): - def before_find(self, by, value, driver): - log.write((f"before_find by {by} {value}").encode()) + def before_find(self, by: By, value: str, driver: WebDriver): + log.write((f"before_find by {by.value} {value}").encode()) - def after_find(self, by, value, driver): - log.write((f"after_find by {by} {value}").encode()) + def after_find(self, by: By, value: str, driver: WebDriver): + log.write((f"after_find by {by.value} {value}").encode()) ef_driver = EventFiringWebDriver(driver, EventListener()) ef_driver.get(pages.url("simpleTest.html")) @@ -154,10 +157,10 @@ def after_find(self, by, value, driver): b"after_find by css selector frame#sixth") == log.getvalue() -def test_should_call_listener_when_an_exception_is_thrown(driver, log, pages): +def test_should_call_listener_when_an_exception_is_thrown(driver: WebDriver, log, pages: Pages) -> None: class EventListener(AbstractEventListener): - def on_exception(self, exception, driver): + def on_exception(self, exception: BaseException, driver: WebDriver) -> None: if isinstance(exception, NoSuchElementException): log.write(b"NoSuchElementException is thrown") @@ -168,7 +171,7 @@ def on_exception(self, exception, driver): assert b"NoSuchElementException is thrown" == log.getvalue() -def test_should_unwrap_element_args_when_calling_scripts(driver, log, pages): +def test_should_unwrap_element_args_when_calling_scripts(driver: WebDriver, log: BytesIO, pages: Pages) -> None: ef_driver = EventFiringWebDriver(driver, AbstractEventListener()) ef_driver.get(pages.url("javascriptPage.html")) button = ef_driver.find_element(By.ID, "plainButton") @@ -178,7 +181,7 @@ def test_should_unwrap_element_args_when_calling_scripts(driver, log, pages): assert "plainButton" == value -def test_should_unwrap_element_args_when_switching_frames(driver, log, pages): +def test_should_unwrap_element_args_when_switching_frames(driver: WebDriver, log: BytesIO, pages: Pages) -> None: ef_driver = EventFiringWebDriver(driver, AbstractEventListener()) ef_driver.get(pages.url("iframes.html")) frame = ef_driver.find_element(By.ID, "iframe1") @@ -186,7 +189,7 @@ def test_should_unwrap_element_args_when_switching_frames(driver, log, pages): assert "click me!" == ef_driver.find_element(By.ID, "imageButton").get_attribute("alt") -def test_should_be_able_to_access_wrapped_instance_from_event_calls(driver): +def test_should_be_able_to_access_wrapped_instance_from_event_calls(driver: WebDriver) -> None: class EventListener(AbstractEventListener): def before_navigate_to(url, d): @@ -197,7 +200,7 @@ def before_navigate_to(url, d): assert driver is wrapped_driver -def test_using_kwargs(driver, pages): +def test_using_kwargs(driver: WebDriver, pages: Pages) -> None: ef_driver = EventFiringWebDriver(driver, AbstractEventListener()) ef_driver.get(pages.url("javascriptPage.html")) ef_driver.get_cookie(name="cookie_name") @@ -205,7 +208,7 @@ def test_using_kwargs(driver, pages): element.get_attribute(name="id") -def test_missing_attributes_raise_error(driver, pages): +def test_missing_attributes_raise_error(driver: WebDriver, pages: Pages) -> None: ef_driver = EventFiringWebDriver(driver, AbstractEventListener()) with pytest.raises(AttributeError): @@ -215,10 +218,10 @@ def test_missing_attributes_raise_error(driver, pages): element = ef_driver.find_element(By.ID, "writableTextInput") with pytest.raises(AttributeError): - element.attribute_should_not_exist + element.attribute_should_not_exist # type: ignore [attr-defined] -def test_can_use_pointer_input_with_event_firing_webdriver(driver, pages): +def test_can_use_pointer_input_with_event_firing_webdriver(driver: WebDriver, pages: Pages) -> None: ef_driver = EventFiringWebDriver(driver, AbstractEventListener()) pages.load("javascriptPage.html") to_click = ef_driver.find_element(By.ID, "clickField") @@ -232,7 +235,7 @@ def test_can_use_pointer_input_with_event_firing_webdriver(driver, pages): @pytest.mark.xfail_safari -def test_can_use_key_input_with_event_firing_webdriver(driver, pages): +def test_can_use_key_input_with_event_firing_webdriver(driver: WebDriver, pages: Pages) -> None: ef_driver = EventFiringWebDriver(driver, AbstractEventListener()) pages.load("javascriptPage.html") ef_driver.find_element(By.ID, "keyUp").click() diff --git a/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py b/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py index 8bfdfab38231c..2b2cc7cd12bd8 100644 --- a/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py +++ b/py/test/unit/selenium/webdriver/remote/remote_connection_tests.py @@ -166,20 +166,6 @@ def test_get_socks_proxy_when_set(mock_socks_proxy_settings): assert type(conn) == SOCKSProxyManager -class MockResponse: - code = 200 - headers = [] - - def read(self): - return b"{}" - - def close(self): - pass - - def getheader(self, *args, **kwargs): - pass - - @pytest.fixture(scope="function") def mock_proxy_settings_missing(monkeypatch): monkeypatch.delenv("HTTPS_PROXY", raising=False) diff --git a/py/test/unit/selenium/webdriver/virtual_authenticator/virtual_authenticator_options_tests.py b/py/test/unit/selenium/webdriver/virtual_authenticator/virtual_authenticator_options_tests.py index d99427765b41e..3846c13e91336 100644 --- a/py/test/unit/selenium/webdriver/virtual_authenticator/virtual_authenticator_options_tests.py +++ b/py/test/unit/selenium/webdriver/virtual_authenticator/virtual_authenticator_options_tests.py @@ -30,22 +30,22 @@ def options(): def test_set_transport(options): options.transport = VirtualAuthenticatorOptions.Transport.USB - assert options.transport == VirtualAuthenticatorOptions.Transport.USB.value + assert options.transport == VirtualAuthenticatorOptions.Transport.USB def test_get_transport(options): options._transport = VirtualAuthenticatorOptions.Transport.NFC - assert options.transport == VirtualAuthenticatorOptions.Transport.NFC.value + assert options.transport == VirtualAuthenticatorOptions.Transport.NFC def test_set_protocol(options): options.protocol = VirtualAuthenticatorOptions.Protocol.U2F - assert options.protocol == VirtualAuthenticatorOptions.Protocol.U2F.value + assert options.protocol == VirtualAuthenticatorOptions.Protocol.U2F def test_get_protocol(options): options._protocol = VirtualAuthenticatorOptions.Protocol.CTAP2 - assert options.protocol == VirtualAuthenticatorOptions.Protocol.CTAP2.value + assert options.protocol == VirtualAuthenticatorOptions.Protocol.CTAP2 def test_set_has_resident_key(options): diff --git a/py/tox.ini b/py/tox.ini index 32951267f7232..81e7cb8b470d6 100644 --- a/py/tox.ini +++ b/py/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = docs, flake8 +envlist = docs, flake8, mypy [testenv:docs] skip_install = true