diff --git a/py/selenium/webdriver/__init__.py b/py/selenium/webdriver/__init__.py index ecf26bc5041c3..27ccf47385cf9 100644 --- a/py/selenium/webdriver/__init__.py +++ b/py/selenium/webdriver/__init__.py @@ -36,7 +36,7 @@ from .wpewebkit.options import Options as WPEWebKitOptions # noqa from .wpewebkit.webdriver import WebDriver as WPEWebKit # noqa -__version__ = '4.5.0' +__version__ = "4.5.0" # We need an explicit __all__ because the above won't otherwise be exported. __all__ = [ diff --git a/py/selenium/webdriver/chrome/options.py b/py/selenium/webdriver/chrome/options.py index cc12a955403b9..2265d8b335e7a 100644 --- a/py/selenium/webdriver/chrome/options.py +++ b/py/selenium/webdriver/chrome/options.py @@ -22,14 +22,14 @@ class Options(ChromiumOptions): - @property def default_capabilities(self) -> dict: return DesiredCapabilities.CHROME.copy() - def enable_mobile(self, - android_package: str = "com.android.chrome", - android_activity: Optional[str] = None, - device_serial: Optional[str] = None - ) -> None: + def enable_mobile( + self, + android_package: str = "com.android.chrome", + android_activity: Optional[str] = None, + device_serial: Optional[str] = None, + ) -> None: super().enable_mobile(android_package, android_activity, device_serial) diff --git a/py/selenium/webdriver/chrome/service.py b/py/selenium/webdriver/chrome/service.py index 552f7d49d5c6d..58f5a2e863b3f 100644 --- a/py/selenium/webdriver/chrome/service.py +++ b/py/selenium/webdriver/chrome/service.py @@ -27,9 +27,14 @@ class Service(service.ChromiumService): Object that manages the starting and stopping of the ChromeDriver """ - def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, - port: int = 0, service_args: List[str] = None, - log_path: str = None, env: dict = None): + def __init__( + self, + executable_path: str = DEFAULT_EXECUTABLE_PATH, + port: int = 0, + service_args: List[str] = None, + log_path: str = None, + env: dict = None, + ): """ Creates a new instance of the Service @@ -40,9 +45,5 @@ def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, - log_path : Path for the chromedriver service to log to""" super().__init__( - executable_path, - port, - service_args, - log_path, - env, - "Please see https://chromedriver.chromium.org/home") + executable_path, port, service_args, log_path, env, "Please see https://chromedriver.chromium.org/home" + ) diff --git a/py/selenium/webdriver/chrome/webdriver.py b/py/selenium/webdriver/chrome/webdriver.py index 2dc6dba06f384..6c8aa904155c4 100644 --- a/py/selenium/webdriver/chrome/webdriver.py +++ b/py/selenium/webdriver/chrome/webdriver.py @@ -35,10 +35,18 @@ class WebDriver(ChromiumDriver): http://chromedriver.storage.googleapis.com/index.html """ - def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, port=DEFAULT_PORT, - options: Options = None, service_args=None, - desired_capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH, - chrome_options=None, service: Service = None, keep_alive=DEFAULT_KEEP_ALIVE): + def __init__( + self, + executable_path=DEFAULT_EXECUTABLE_PATH, + port=DEFAULT_PORT, + options: Options = None, + service_args=None, + desired_capabilities=None, + service_log_path=DEFAULT_SERVICE_LOG_PATH, + chrome_options=None, + service: Service = None, + keep_alive=DEFAULT_KEEP_ALIVE, + ): """ Creates a new instance of the chrome driver. Starts the service and then creates new instance of chrome driver. @@ -54,22 +62,30 @@ def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, port=DEFAULT_PORT, - service_log_path - Deprecated: Where to log information from the driver. - keep_alive - Deprecated: Whether to configure ChromeRemoteConnection to use HTTP keep-alive. """ - if executable_path != 'chromedriver': - warnings.warn('executable_path has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + if executable_path != "chromedriver": + warnings.warn( + "executable_path has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) if chrome_options: - warnings.warn('use options instead of chrome_options', - DeprecationWarning, stacklevel=2) + warnings.warn("use options instead of chrome_options", DeprecationWarning, stacklevel=2) options = chrome_options if keep_alive != DEFAULT_KEEP_ALIVE: - warnings.warn('keep_alive has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "keep_alive has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) else: keep_alive = True if not service: service = Service(executable_path, port, service_args, service_log_path) - super().__init__(DesiredCapabilities.CHROME['browserName'], "goog", - port, options, - service_args, desired_capabilities, - service_log_path, service, keep_alive) + super().__init__( + DesiredCapabilities.CHROME["browserName"], + "goog", + port, + options, + service_args, + desired_capabilities, + service_log_path, + service, + keep_alive, + ) diff --git a/py/selenium/webdriver/chromium/options.py b/py/selenium/webdriver/chromium/options.py index c100b4c539548..f366540b3427d 100644 --- a/py/selenium/webdriver/chromium/options.py +++ b/py/selenium/webdriver/chromium/options.py @@ -31,7 +31,7 @@ class ChromiumOptions(ArgOptions): def __init__(self) -> None: super().__init__() - self._binary_location = '' + self._binary_location = "" self._extension_files = [] self._extensions = [] self._experimental_options = {} @@ -76,6 +76,7 @@ def extensions(self) -> List[str]: """ :Returns: A list of encoded extensions that will be loaded """ + def _decode(file_data: BinaryIO) -> str: # Should not use base64.encodestring() which inserts newlines every # 76 characters (per RFC 1521). Chromedriver has to remove those @@ -143,7 +144,7 @@ def headless(self) -> bool: """ :Returns: True if the headless argument is set, else False """ - return '--headless' in self._arguments + return "--headless" in self._arguments @headless.setter def headless(self, value: bool) -> None: @@ -152,7 +153,7 @@ def headless(self, value: bool) -> None: :Args: value: boolean value indicating to set the headless option """ - args = {'--headless'} + args = {"--headless"} if value is True: self._arguments.extend(args) else: @@ -165,17 +166,17 @@ def to_capabilities(self) -> dict: """ caps = self._caps chrome_options = self.experimental_options.copy() - if 'w3c' in chrome_options: - if chrome_options['w3c']: + if "w3c" in chrome_options: + if chrome_options["w3c"]: warnings.warn( - "Setting 'w3c: True' is redundant and will no longer be allowed", - DeprecationWarning, - stacklevel=2 + "Setting 'w3c: True' is redundant and will no longer be allowed", DeprecationWarning, stacklevel=2 ) else: - raise AttributeError('setting w3c to False is not allowed, ' - 'Please update to W3C Syntax: ' - 'https://www.selenium.dev/blog/2022/legacy-protocol-support/') + raise AttributeError( + "setting w3c to False is not allowed, " + "Please update to W3C Syntax: " + "https://www.selenium.dev/blog/2022/legacy-protocol-support/" + ) if self.mobile_options: chrome_options.update(self.mobile_options) chrome_options["extensions"] = self.extensions diff --git a/py/selenium/webdriver/chromium/remote_connection.py b/py/selenium/webdriver/chromium/remote_connection.py index cde622b123c57..f3ca4b645c913 100644 --- a/py/selenium/webdriver/chromium/remote_connection.py +++ b/py/selenium/webdriver/chromium/remote_connection.py @@ -20,23 +20,28 @@ class ChromiumRemoteConnection(RemoteConnection): - def __init__(self, - remote_server_addr: str, - vendor_prefix: str, - browser_name: str, - keep_alive: bool = True, - ignore_proxy: typing.Optional[bool] = False): + def __init__( + self, + remote_server_addr: str, + vendor_prefix: str, + browser_name: str, + keep_alive: bool = True, + ignore_proxy: typing.Optional[bool] = False, + ): super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy) self.browser_name = browser_name - self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app') - self._commands["setPermissions"] = ('POST', '/session/$sessionId/permissions') - self._commands["setNetworkConditions"] = ('POST', '/session/$sessionId/chromium/network_conditions') - self._commands["getNetworkConditions"] = ('GET', '/session/$sessionId/chromium/network_conditions') - self._commands["deleteNetworkConditions"] = ('DELETE', '/session/$sessionId/chromium/network_conditions') - self._commands['executeCdpCommand'] = ('POST', f'/session/$sessionId/{vendor_prefix}/cdp/execute') - self._commands['getSinks'] = ('GET', f'/session/$sessionId/{vendor_prefix}/cast/get_sinks') - self._commands['getIssueMessage'] = ('GET', f'/session/$sessionId/{vendor_prefix}/cast/get_issue_message') - self._commands['setSinkToUse'] = ('POST', f'/session/$sessionId/{vendor_prefix}/cast/set_sink_to_use') - self._commands['startDesktopMirroring'] = ('POST', f'/session/$sessionId/{vendor_prefix}/cast/start_desktop_mirroring') - self._commands['startTabMirroring'] = ('POST', f'/session/$sessionId/{vendor_prefix}/cast/start_tab_mirroring') - self._commands['stopCasting'] = ('POST', f'/session/$sessionId/{vendor_prefix}/cast/stop_casting') + self._commands["launchApp"] = ("POST", "/session/$sessionId/chromium/launch_app") + self._commands["setPermissions"] = ("POST", "/session/$sessionId/permissions") + self._commands["setNetworkConditions"] = ("POST", "/session/$sessionId/chromium/network_conditions") + self._commands["getNetworkConditions"] = ("GET", "/session/$sessionId/chromium/network_conditions") + self._commands["deleteNetworkConditions"] = ("DELETE", "/session/$sessionId/chromium/network_conditions") + self._commands["executeCdpCommand"] = ("POST", f"/session/$sessionId/{vendor_prefix}/cdp/execute") + self._commands["getSinks"] = ("GET", f"/session/$sessionId/{vendor_prefix}/cast/get_sinks") + self._commands["getIssueMessage"] = ("GET", f"/session/$sessionId/{vendor_prefix}/cast/get_issue_message") + self._commands["setSinkToUse"] = ("POST", f"/session/$sessionId/{vendor_prefix}/cast/set_sink_to_use") + self._commands["startDesktopMirroring"] = ( + "POST", + f"/session/$sessionId/{vendor_prefix}/cast/start_desktop_mirroring", + ) + self._commands["startTabMirroring"] = ("POST", f"/session/$sessionId/{vendor_prefix}/cast/start_tab_mirroring") + self._commands["stopCasting"] = ("POST", f"/session/$sessionId/{vendor_prefix}/cast/stop_casting") diff --git a/py/selenium/webdriver/chromium/service.py b/py/selenium/webdriver/chromium/service.py index ffd0165514f84..9d7af55e6318f 100644 --- a/py/selenium/webdriver/chromium/service.py +++ b/py/selenium/webdriver/chromium/service.py @@ -25,8 +25,15 @@ class ChromiumService(service.Service): Object that manages the starting and stopping the WebDriver instance of the ChromiumDriver """ - def __init__(self, executable_path: str, port: int = 0, service_args: List[str] = None, - log_path: str = None, env: dict = None, start_error_message: str = None): + def __init__( + self, + executable_path: str, + port: int = 0, + service_args: List[str] = None, + log_path: str = None, + env: dict = None, + start_error_message: str = None, + ): """ Creates a new instance of the Service @@ -38,7 +45,7 @@ def __init__(self, executable_path: str, port: int = 0, service_args: List[str] self.service_args = service_args or [] if log_path: - self.service_args.append('--log-path=%s' % log_path) + self.service_args.append("--log-path=%s" % log_path) if not start_error_message: raise AttributeError("start_error_message should not be empty") diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py index 233e277bfa175..6ab6c447a64e6 100644 --- a/py/selenium/webdriver/chromium/webdriver.py +++ b/py/selenium/webdriver/chromium/webdriver.py @@ -34,10 +34,18 @@ class ChromiumDriver(RemoteWebDriver): Controls the WebDriver instance of ChromiumDriver and allows you to drive the browser. """ - def __init__(self, browser_name, vendor_prefix, - port=DEFAULT_PORT, options: BaseOptions = None, service_args=None, - desired_capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH, - service: Service = None, keep_alive=DEFAULT_KEEP_ALIVE): + def __init__( + self, + browser_name, + vendor_prefix, + port=DEFAULT_PORT, + options: BaseOptions = None, + service_args=None, + desired_capabilities=None, + service_log_path=DEFAULT_SERVICE_LOG_PATH, + service: Service = None, + keep_alive=DEFAULT_KEEP_ALIVE, + ): """ Creates a new WebDriver instance of the ChromiumDriver. Starts the service and then creates new WebDriver instance of ChromiumDriver. @@ -54,18 +62,24 @@ def __init__(self, browser_name, vendor_prefix, - keep_alive - Deprecated: Whether to configure ChromiumRemoteConnection to use HTTP keep-alive. """ if desired_capabilities: - warnings.warn('desired_capabilities has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "desired_capabilities has been deprecated, please pass in a Service object", + DeprecationWarning, + stacklevel=2, + ) if port != DEFAULT_PORT: - warnings.warn('port has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn("port has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2) self.port = port 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) + 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__: - warnings.warn('keep_alive has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "keep_alive has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) else: keep_alive = True @@ -83,7 +97,7 @@ def __init__(self, browser_name, vendor_prefix, _ignore_proxy = options._ignore_local_proxy if not service: - raise AttributeError('service cannot be None') + raise AttributeError("service cannot be None") self.service = service self.service.start() @@ -92,9 +106,13 @@ def __init__(self, browser_name, vendor_prefix, super().__init__( command_executor=ChromiumRemoteConnection( remote_server_addr=self.service.service_url, - browser_name=browser_name, vendor_prefix=vendor_prefix, - keep_alive=keep_alive, ignore_proxy=_ignore_proxy), - options=options) + browser_name=browser_name, + vendor_prefix=vendor_prefix, + keep_alive=keep_alive, + ignore_proxy=_ignore_proxy, + ), + options=options, + ) except Exception: self.quit() raise @@ -102,7 +120,7 @@ def __init__(self, browser_name, vendor_prefix, def launch_app(self, id): """Launches Chromium app specified by id.""" - return self.execute("launchApp", {'id': id}) + return self.execute("launchApp", {"id": id}) def get_network_conditions(self): """ @@ -113,7 +131,7 @@ def get_network_conditions(self): {'latency': 4, 'download_throughput': 2, 'upload_throughput': 2, 'offline': False} """ - return self.execute("getNetworkConditions")['value'] + return self.execute("getNetworkConditions")["value"] def set_network_conditions(self, **network_conditions) -> None: """ @@ -133,9 +151,7 @@ def set_network_conditions(self, **network_conditions) -> None: Note: 'throughput' can be used to set both (for download and upload). """ - self.execute("setNetworkConditions", { - 'network_conditions': network_conditions - }) + self.execute("setNetworkConditions", {"network_conditions": network_conditions}) def delete_network_conditions(self) -> None: """ @@ -155,7 +171,7 @@ def set_permissions(self, name: str, value: str) -> None: :: driver.set_permissions('clipboard-read', 'denied') """ - self.execute("setPermissions", {'descriptor': {'name': name}, 'state': value}) + self.execute("setPermissions", {"descriptor": {"name": name}, "state": value}) def execute_cdp_cmd(self, cmd: str, cmd_args: dict): """ @@ -174,19 +190,19 @@ def execute_cdp_cmd(self, cmd: str, cmd_args: dict): For example to getResponseBody: {'base64Encoded': False, 'body': 'response body string'} """ - return self.execute("executeCdpCommand", {'cmd': cmd, 'params': cmd_args})['value'] + return self.execute("executeCdpCommand", {"cmd": cmd, "params": cmd_args})["value"] def get_sinks(self) -> list: """ :Returns: A list of sinks available for Cast. """ - return self.execute('getSinks')['value'] + return self.execute("getSinks")["value"] def get_issue_message(self): """ :Returns: An error message when there is any issue in a Cast session. """ - return self.execute('getIssueMessage')['value'] + return self.execute("getIssueMessage")["value"] def set_sink_to_use(self, sink_name: str) -> dict: """ @@ -195,7 +211,7 @@ def set_sink_to_use(self, sink_name: str) -> dict: :Args: - sink_name: Name of the sink to use as the target. """ - return self.execute('setSinkToUse', {'sinkName': sink_name}) + return self.execute("setSinkToUse", {"sinkName": sink_name}) def start_desktop_mirroring(self, sink_name: str) -> dict: """ @@ -204,7 +220,7 @@ def start_desktop_mirroring(self, sink_name: str) -> dict: :Args: - sink_name: Name of the sink to use as the target. """ - return self.execute('startDesktopMirroring', {'sinkName': sink_name}) + return self.execute("startDesktopMirroring", {"sinkName": sink_name}) def start_tab_mirroring(self, sink_name: str) -> dict: """ @@ -213,7 +229,7 @@ def start_tab_mirroring(self, sink_name: str) -> dict: :Args: - sink_name: Name of the sink to use as the target. """ - return self.execute('startTabMirroring', {'sinkName': sink_name}) + return self.execute("startTabMirroring", {"sinkName": sink_name}) def stop_casting(self, sink_name: str) -> dict: """ @@ -222,7 +238,7 @@ def stop_casting(self, sink_name: str) -> dict: :Args: - sink_name: Name of the sink to stop the Cast session. """ - return self.execute('stopCasting', {'sinkName': sink_name}) + return self.execute("stopCasting", {"sinkName": sink_name}) def quit(self) -> None: """ diff --git a/py/selenium/webdriver/common/action_chains.py b/py/selenium/webdriver/common/action_chains.py index d0f1903650ef1..38e5b04dbd740 100644 --- a/py/selenium/webdriver/common/action_chains.py +++ b/py/selenium/webdriver/common/action_chains.py @@ -263,15 +263,13 @@ def move_to_element_with_offset(self, to_element, xoffset, yoffset): - yoffset: Y offset to move to. """ - self.w3c_actions.pointer_action.move_to(to_element, - int(xoffset), - int(yoffset)) + self.w3c_actions.pointer_action.move_to(to_element, int(xoffset), int(yoffset)) self.w3c_actions.key_action.pause() return self def pause(self, seconds): - """ Pause all inputs for the specified duration in seconds """ + """Pause all inputs for the specified duration in seconds""" self.w3c_actions.pointer_action.pause(seconds) self.w3c_actions.key_action.pause(seconds) @@ -363,14 +361,15 @@ def scroll_from_origin(self, scroll_origin: ScrollOrigin, delta_x: int, delta_y: """ if not isinstance(scroll_origin, ScrollOrigin): - raise TypeError('Expected object of type ScrollOrigin, got: ' - '{}'.format(type(scroll_origin))) - - self.w3c_actions.wheel_action.scroll(origin=scroll_origin.origin, - x=scroll_origin.x_offset, - y=scroll_origin.y_offset, - delta_x=delta_x, - delta_y=delta_y) + raise TypeError("Expected object of type ScrollOrigin, got: " "{}".format(type(scroll_origin))) + + self.w3c_actions.wheel_action.scroll( + origin=scroll_origin.origin, + x=scroll_origin.x_offset, + y=scroll_origin.y_offset, + delta_x=delta_x, + delta_y=delta_y, + ) return self def scroll(self, x: int, y: int, delta_x: int, delta_y: int, duration: int = 0, origin: str = "viewport"): @@ -386,11 +385,12 @@ def scroll(self, x: int, y: int, delta_x: int, delta_y: int, duration: int = 0, warnings.warn( "scroll() has been deprecated, please use scroll_to_element(), scroll_by_amount() or scroll_from_origin().", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) - self.w3c_actions.wheel_action.scroll(x=x, y=y, delta_x=delta_x, delta_y=delta_y, - duration=duration, origin=origin) + self.w3c_actions.wheel_action.scroll( + x=x, y=y, delta_x=delta_x, delta_y=delta_y, duration=duration, origin=origin + ) return self # Context manager so ActionChains can be used in a 'with .. as' statements. diff --git a/py/selenium/webdriver/common/actions/action_builder.py b/py/selenium/webdriver/common/actions/action_builder.py index 34c54c9782f4a..7d5363e4e883e 100644 --- a/py/selenium/webdriver/common/actions/action_builder.py +++ b/py/selenium/webdriver/common/actions/action_builder.py @@ -85,14 +85,14 @@ def perform(self) -> None: enc = {"actions": []} for device in self.devices: encoded = device.encode() - if encoded['actions']: + if encoded["actions"]: enc["actions"].append(encoded) device.actions = [] self.driver.execute(Command.W3C_ACTIONS, enc) def clear_actions(self) -> None: """ - Clears actions that are already stored on the remote end + Clears actions that are already stored on the remote end """ self.driver.execute(Command.W3C_CLEAR_ACTIONS) diff --git a/py/selenium/webdriver/common/actions/input_device.py b/py/selenium/webdriver/common/actions/input_device.py index d7df9e57ef1f7..79ac5669c9536 100644 --- a/py/selenium/webdriver/common/actions/input_device.py +++ b/py/selenium/webdriver/common/actions/input_device.py @@ -20,8 +20,9 @@ class InputDevice: """ - Describes the input device being used for the action. + Describes the input device being used for the action. """ + def __init__(self, name=None): if not name: self.name = uuid.uuid4() @@ -31,9 +32,7 @@ def __init__(self, name=None): self.actions = [] def add_action(self, action): - """ - - """ + """ """ self.actions.append(action) def clear_actions(self): diff --git a/py/selenium/webdriver/common/actions/interaction.py b/py/selenium/webdriver/common/actions/interaction.py index ae0dcb1cdc840..6e1942a8e76f6 100644 --- a/py/selenium/webdriver/common/actions/interaction.py +++ b/py/selenium/webdriver/common/actions/interaction.py @@ -38,13 +38,9 @@ def __init__(self, source) -> None: class Pause(Interaction): - def __init__(self, source, duration: float = 0) -> None: super().__init__(source) self.duration = duration def encode(self) -> typing.Dict[str, typing.Union[str, int]]: - return { - "type": self.PAUSE, - "duration": int(self.duration * 1000) - } + return {"type": self.PAUSE, "duration": int(self.duration * 1000)} diff --git a/py/selenium/webdriver/common/actions/key_actions.py b/py/selenium/webdriver/common/actions/key_actions.py index 1ec66d0f98d64..1c79013433cbc 100644 --- a/py/selenium/webdriver/common/actions/key_actions.py +++ b/py/selenium/webdriver/common/actions/key_actions.py @@ -21,7 +21,6 @@ class KeyActions(Interaction): - def __init__(self, source=None): if not source: source = KeyInput(KEY) diff --git a/py/selenium/webdriver/common/actions/key_input.py b/py/selenium/webdriver/common/actions/key_input.py index dfd0dcd4d3f31..732799f3cea07 100644 --- a/py/selenium/webdriver/common/actions/key_input.py +++ b/py/selenium/webdriver/common/actions/key_input.py @@ -40,7 +40,6 @@ def create_pause(self, pause_duration=0) -> None: class TypingInteraction(Interaction): - def __init__(self, source, type_, key) -> None: super().__init__(source) self.type = type_ diff --git a/py/selenium/webdriver/common/actions/pointer_actions.py b/py/selenium/webdriver/common/actions/pointer_actions.py index daac5ee92f7ca..286830dad0f6e 100644 --- a/py/selenium/webdriver/common/actions/pointer_actions.py +++ b/py/selenium/webdriver/common/actions/pointer_actions.py @@ -24,7 +24,6 @@ class PointerActions(Interaction): - def __init__(self, source=None, duration=250): """ Args: @@ -37,50 +36,133 @@ def __init__(self, source=None, duration=250): self._duration = duration super().__init__(source) - def pointer_down(self, button=MouseButton.LEFT, width=None, height=None, pressure=None, - tangential_pressure=None, tilt_x=None, tilt_y=None, twist=None, - altitude_angle=None, azimuth_angle=None): - self._button_action("create_pointer_down", button=button, width=width, height=height, - pressure=pressure, tangential_pressure=tangential_pressure, - tilt_x=tilt_x, tilt_y=tilt_y, twist=twist, - altitude_angle=altitude_angle, azimuth_angle=azimuth_angle) + def pointer_down( + self, + button=MouseButton.LEFT, + width=None, + height=None, + pressure=None, + tangential_pressure=None, + tilt_x=None, + tilt_y=None, + twist=None, + altitude_angle=None, + azimuth_angle=None, + ): + self._button_action( + "create_pointer_down", + button=button, + width=width, + height=height, + pressure=pressure, + tangential_pressure=tangential_pressure, + tilt_x=tilt_x, + tilt_y=tilt_y, + twist=twist, + altitude_angle=altitude_angle, + azimuth_angle=azimuth_angle, + ) return self def pointer_up(self, button=MouseButton.LEFT): self._button_action("create_pointer_up", button=button) return self - def move_to(self, element, x=0, y=0, width=None, height=None, pressure=None, - tangential_pressure=None, tilt_x=None, tilt_y=None, twist=None, - altitude_angle=None, azimuth_angle=None): + def move_to( + self, + element, + x=0, + y=0, + width=None, + height=None, + pressure=None, + tangential_pressure=None, + tilt_x=None, + tilt_y=None, + twist=None, + altitude_angle=None, + azimuth_angle=None, + ): if not isinstance(element, WebElement): raise AttributeError("move_to requires a WebElement") - self.source.create_pointer_move(origin=element, duration=self._duration, x=int(x), y=int(y), - width=width, height=height, pressure=pressure, - tangential_pressure=tangential_pressure, - tilt_x=tilt_x, tilt_y=tilt_y, twist=twist, - altitude_angle=altitude_angle, azimuth_angle=azimuth_angle) + self.source.create_pointer_move( + origin=element, + duration=self._duration, + x=int(x), + y=int(y), + width=width, + height=height, + pressure=pressure, + tangential_pressure=tangential_pressure, + tilt_x=tilt_x, + tilt_y=tilt_y, + twist=twist, + altitude_angle=altitude_angle, + azimuth_angle=azimuth_angle, + ) return self - def move_by(self, x, y, width=None, height=None, pressure=None, - tangential_pressure=None, tilt_x=None, tilt_y=None, twist=None, - altitude_angle=None, azimuth_angle=None): - self.source.create_pointer_move(origin=interaction.POINTER, duration=self._duration, x=int(x), y=int(y), - width=width, height=height, pressure=pressure, - tangential_pressure=tangential_pressure, - tilt_x=tilt_x, tilt_y=tilt_y, twist=twist, - altitude_angle=altitude_angle, azimuth_angle=azimuth_angle) + def move_by( + self, + x, + y, + width=None, + height=None, + pressure=None, + tangential_pressure=None, + tilt_x=None, + tilt_y=None, + twist=None, + altitude_angle=None, + azimuth_angle=None, + ): + self.source.create_pointer_move( + origin=interaction.POINTER, + duration=self._duration, + x=int(x), + y=int(y), + width=width, + height=height, + pressure=pressure, + tangential_pressure=tangential_pressure, + tilt_x=tilt_x, + tilt_y=tilt_y, + twist=twist, + altitude_angle=altitude_angle, + azimuth_angle=azimuth_angle, + ) return self - def move_to_location(self, x, y, width=None, height=None, pressure=None, - tangential_pressure=None, tilt_x=None, tilt_y=None, twist=None, - altitude_angle=None, azimuth_angle=None): - self.source.create_pointer_move(origin='viewport', duration=self._duration, x=int(x), y=int(y), - width=width, height=height, pressure=pressure, - tangential_pressure=tangential_pressure, - tilt_x=tilt_x, tilt_y=tilt_y, twist=twist, - altitude_angle=altitude_angle, azimuth_angle=azimuth_angle) + def move_to_location( + self, + x, + y, + width=None, + height=None, + pressure=None, + tangential_pressure=None, + tilt_x=None, + tilt_y=None, + twist=None, + altitude_angle=None, + azimuth_angle=None, + ): + self.source.create_pointer_move( + origin="viewport", + duration=self._duration, + x=int(x), + y=int(y), + width=width, + height=height, + pressure=pressure, + tangential_pressure=tangential_pressure, + tilt_x=tilt_x, + tilt_y=tilt_y, + twist=twist, + altitude_angle=altitude_angle, + azimuth_angle=azimuth_angle, + ) return self def click(self, element=None, button=MouseButton.LEFT): diff --git a/py/selenium/webdriver/common/actions/pointer_input.py b/py/selenium/webdriver/common/actions/pointer_input.py index 75983bf234116..caaf32eea5505 100644 --- a/py/selenium/webdriver/common/actions/pointer_input.py +++ b/py/selenium/webdriver/common/actions/pointer_input.py @@ -36,13 +36,7 @@ def __init__(self, kind, name): self.name = name def create_pointer_move(self, duration=DEFAULT_MOVE_DURATION, x=0, y=0, origin=None, **kwargs): - action = { - "type": "pointerMove", - "duration": duration, - "x": x, - "y": y, - **kwargs - } + action = {"type": "pointerMove", "duration": duration, "x": x, "y": y, **kwargs} if isinstance(origin, WebElement): action["origin"] = {"element-6066-11e4-a52e-4f735466cecf": origin.id} elif origin is not None: @@ -50,11 +44,7 @@ def create_pointer_move(self, duration=DEFAULT_MOVE_DURATION, x=0, y=0, origin=N self.add_action(self._convert_keys(action)) def create_pointer_down(self, **kwargs): - data = { - "type": "pointerDown", - "duration": 0, - **kwargs - } + data = {"type": "pointerDown", "duration": 0, **kwargs} self.add_action(self._convert_keys(data)) def create_pointer_up(self, button): @@ -67,10 +57,7 @@ def create_pause(self, pause_duration): self.add_action({"type": "pause", "duration": int(pause_duration * 1000)}) def encode(self): - return {"type": self.type, - "parameters": {"pointerType": self.kind}, - "id": self.name, - "actions": self.actions} + return {"type": self.type, "parameters": {"pointerType": self.kind}, "id": self.name, "actions": self.actions} def _convert_keys(self, actions: typing.Dict[str, typing.Any]): out = {} @@ -80,7 +67,7 @@ def _convert_keys(self, actions: typing.Dict[str, typing.Any]): if k in ("x", "y"): out[k] = int(v) continue - splits = k.split('_') - new_key = splits[0] + ''.join(v.title() for v in splits[1:]) + splits = k.split("_") + new_key = splits[0] + "".join(v.title() for v in splits[1:]) out[new_key] = v return out diff --git a/py/selenium/webdriver/common/actions/wheel_actions.py b/py/selenium/webdriver/common/actions/wheel_actions.py index 1fdbb8a85c6c0..3f30674aa0743 100644 --- a/py/selenium/webdriver/common/actions/wheel_actions.py +++ b/py/selenium/webdriver/common/actions/wheel_actions.py @@ -19,7 +19,6 @@ class WheelActions(Interaction): - def __init__(self, source: WheelInput = None): if not source: source = WheelInput("wheel") diff --git a/py/selenium/webdriver/common/actions/wheel_input.py b/py/selenium/webdriver/common/actions/wheel_input.py index 2b961438e360c..f0cd8651a849b 100644 --- a/py/selenium/webdriver/common/actions/wheel_input.py +++ b/py/selenium/webdriver/common/actions/wheel_input.py @@ -23,7 +23,6 @@ class ScrollOrigin: - def __init__(self, origin: Union[str, WebElement], x_offset: int, y_offset: int) -> None: self._origin = origin self._x_offset = x_offset @@ -35,7 +34,7 @@ def from_element(cls, element: WebElement, x_offset: int = 0, y_offset: int = 0) @classmethod def from_viewport(cls, x_offset: int = 0, y_offset: int = 0): - return cls('viewport', x_offset, y_offset) + return cls("viewport", x_offset, y_offset) @property def origin(self) -> Union[str, WebElement]: @@ -51,25 +50,28 @@ def y_offset(self) -> int: class WheelInput(InputDevice): - def __init__(self, name) -> None: super().__init__(name=name) self.name = name self.type = interaction.WHEEL def encode(self) -> dict: - return {"type": self.type, - "id": self.name, - "actions": self.actions} + return {"type": self.type, "id": self.name, "actions": self.actions} - def create_scroll(self, x: int, y: int, delta_x: int, - delta_y: int, duration: int, origin) -> None: + def create_scroll(self, x: int, y: int, delta_x: int, delta_y: int, duration: int, origin) -> None: if isinstance(origin, WebElement): origin = {"element-6066-11e4-a52e-4f735466cecf": origin.id} - self.add_action({"type": "scroll", "x": x, "y": y, "deltaX": delta_x, - "deltaY": delta_y, "duration": duration, - "origin": origin}) + self.add_action( + { + "type": "scroll", + "x": x, + "y": y, + "deltaX": delta_x, + "deltaY": delta_y, + "duration": duration, + "origin": origin, + } + ) def create_pause(self, pause_duration: Union[int, float]) -> None: - self.add_action( - {"type": "pause", "duration": int(pause_duration * 1000)}) + self.add_action({"type": "pause", "duration": int(pause_duration * 1000)}) diff --git a/py/selenium/webdriver/common/alert.py b/py/selenium/webdriver/common/alert.py index 12de0e976d7ae..36d787ba5370a 100644 --- a/py/selenium/webdriver/common/alert.py +++ b/py/selenium/webdriver/common/alert.py @@ -87,4 +87,4 @@ def send_keys(self, keysToSend): :Args: - keysToSend: The text to be sent to Alert. """ - self.driver.execute(Command.W3C_SET_ALERT_VALUE, {'value': keys_to_typing(keysToSend), 'text': keysToSend}) + self.driver.execute(Command.W3C_SET_ALERT_VALUE, {"value": keys_to_typing(keysToSend), "text": keysToSend}) diff --git a/py/selenium/webdriver/common/bidi/cdp.py b/py/selenium/webdriver/common/bidi/cdp.py index a45c0e1203b94..4c71763bea670 100644 --- a/py/selenium/webdriver/common/bidi/cdp.py +++ b/py/selenium/webdriver/common/bidi/cdp.py @@ -40,8 +40,8 @@ from trio_websocket import ConnectionClosed as WsConnectionClosed from trio_websocket import connect_websocket_url -logger = logging.getLogger('trio_cdp') -T = typing.TypeVar('T') +logger = logging.getLogger("trio_cdp") +T = typing.TypeVar("T") MAX_WS_MESSAGE_SIZE = 2**24 devtools = None @@ -72,36 +72,36 @@ def import_devtools(ver): return devtools -_connection_context: contextvars.ContextVar = contextvars.ContextVar('connection_context') -_session_context: contextvars.ContextVar = contextvars.ContextVar('session_context') +_connection_context: contextvars.ContextVar = contextvars.ContextVar("connection_context") +_session_context: contextvars.ContextVar = contextvars.ContextVar("session_context") def get_connection_context(fn_name): - ''' + """ Look up the current connection. If there is no current connection, raise a ``RuntimeError`` with a helpful message. - ''' + """ try: return _connection_context.get() except LookupError: - raise RuntimeError(f'{fn_name}() must be called in a connection context.') + raise RuntimeError(f"{fn_name}() must be called in a connection context.") def get_session_context(fn_name): - ''' + """ Look up the current session. If there is no current session, raise a ``RuntimeError`` with a helpful message. - ''' + """ try: return _session_context.get() except LookupError: - raise RuntimeError(f'{fn_name}() must be called in a session context.') + raise RuntimeError(f"{fn_name}() must be called in a session context.") @contextmanager def connection_context(connection): - ''' This context manager installs ``connection`` as the session context for the current - Trio task. ''' + """This context manager installs ``connection`` as the session context for the current + Trio task.""" token = _connection_context.set(connection) try: yield @@ -111,8 +111,8 @@ def connection_context(connection): @contextmanager def session_context(session): - ''' This context manager installs ``session`` as the session context for the current - Trio task. ''' + """This context manager installs ``session`` as the session context for the current + Trio task.""" token = _session_context.set(session) try: yield @@ -121,71 +121,69 @@ def session_context(session): def set_global_connection(connection): - ''' + """ Install ``connection`` in the root context so that it will become the default connection for all tasks. This is generally not recommended, except it may be necessary in certain use cases such as running inside Jupyter notebook. - ''' + """ global _connection_context - _connection_context = contextvars.ContextVar('_connection_context', - default=connection) + _connection_context = contextvars.ContextVar("_connection_context", default=connection) def set_global_session(session): - ''' + """ Install ``session`` in the root context so that it will become the default session for all tasks. This is generally not recommended, except it may be necessary in certain use cases such as running inside Jupyter notebook. - ''' + """ global _session_context - _session_context = contextvars.ContextVar('_session_context', default=session) + _session_context = contextvars.ContextVar("_session_context", default=session) class BrowserError(Exception): - ''' This exception is raised when the browser's response to a command - indicates that an error occurred. ''' + """This exception is raised when the browser's response to a command + indicates that an error occurred.""" def __init__(self, obj): - self.code = obj['code'] - self.message = obj['message'] - self.detail = obj.get('data') + self.code = obj["code"] + self.message = obj["message"] + self.detail = obj.get("data") def __str__(self): - return 'BrowserError {}'.format(self.code, - self.message, self.detail) + return "BrowserError {}".format(self.code, self.message, self.detail) class CdpConnectionClosed(WsConnectionClosed): - ''' Raised when a public method is called on a closed CDP connection. ''' + """Raised when a public method is called on a closed CDP connection.""" def __init__(self, reason): - ''' + """ Constructor. :param reason: :type reason: wsproto.frame_protocol.CloseReason - ''' + """ self.reason = reason def __repr__(self): - ''' Return representation. ''' - return f'{self.__class__.__name__}<{self.reason}>' + """Return representation.""" + return f"{self.__class__.__name__}<{self.reason}>" class InternalError(Exception): - ''' This exception is only raised when there is faulty logic in TrioCDP or - the integration with PyCDP. ''' + """This exception is only raised when there is faulty logic in TrioCDP or + the integration with PyCDP.""" @dataclass class CmEventProxy: - ''' A proxy object returned by :meth:`CdpBase.wait_for()``. After the + """A proxy object returned by :meth:`CdpBase.wait_for()``. After the context manager executes, this proxy object will have a value set that - contains the returned event. ''' + contains the returned event.""" + value: typing.Any = None class CdpBase: - def __init__(self, ws, session_id, target_id): self.ws = ws self.session_id = session_id @@ -196,18 +194,18 @@ def __init__(self, ws, session_id, target_id): self.inflight_result = dict() async def execute(self, cmd: typing.Generator[dict, T, typing.Any]) -> T: - ''' + """ Execute a command on the server and wait for the result. :param cmd: any CDP command :returns: a CDP result - ''' + """ cmd_id = next(self.id_iter) cmd_event = trio.Event() self.inflight_cmd[cmd_id] = cmd, cmd_event request = next(cmd) - request['id'] = cmd_id + request["id"] = cmd_id if self.session_id: - request['sessionId'] = self.session_id + request["sessionId"] = self.session_id request_str = json.dumps(request) try: await self.ws.send_message(request_str) @@ -220,22 +218,21 @@ async def execute(self, cmd: typing.Generator[dict, T, typing.Any]) -> T: return response def listen(self, *event_types, buffer_size=10): - ''' Return an async iterator that iterates over events matching the - indicated types. ''' + """Return an async iterator that iterates over events matching the + indicated types.""" sender, receiver = trio.open_memory_channel(buffer_size) for event_type in event_types: self.channels[event_type].add(sender) return receiver @asynccontextmanager - async def wait_for(self, event_type: typing.Type[T], buffer_size=10) -> \ - typing.AsyncGenerator[CmEventProxy, None]: - ''' + async def wait_for(self, event_type: typing.Type[T], buffer_size=10) -> typing.AsyncGenerator[CmEventProxy, None]: + """ Wait for an event of the given type and return it. This is an async context manager, so you should open it inside an async with block. The block will not exit until the indicated event is received. - ''' + """ sender, receiver = trio.open_memory_channel(buffer_size) self.channels[event_type].add(sender) proxy = CmEventProxy() @@ -245,59 +242,56 @@ async def wait_for(self, event_type: typing.Type[T], buffer_size=10) -> \ proxy.value = event def _handle_data(self, data): - ''' + """ Handle incoming WebSocket data. :param dict data: a JSON dictionary - ''' - if 'id' in data: + """ + if "id" in data: self._handle_cmd_response(data) else: self._handle_event(data) def _handle_cmd_response(self, data): - ''' + """ Handle a response to a command. This will set an event flag that will return control to the task that called the command. :param dict data: response as a JSON dictionary - ''' - cmd_id = data['id'] + """ + cmd_id = data["id"] try: cmd, event = self.inflight_cmd.pop(cmd_id) except KeyError: - logger.warning('Got a message with a command ID that does' - ' not exist: {}'.format(data)) + logger.warning("Got a message with a command ID that does" " not exist: {}".format(data)) return - if 'error' in data: + if "error" in data: # If the server reported an error, convert it to an exception and do # not process the response any further. - self.inflight_result[cmd_id] = BrowserError(data['error']) + self.inflight_result[cmd_id] = BrowserError(data["error"]) else: # Otherwise, continue the generator to parse the JSON result # into a CDP object. try: - response = cmd.send(data['result']) - raise InternalError("The command's generator function " - "did not exit when expected!") + response = cmd.send(data["result"]) + raise InternalError("The command's generator function " "did not exit when expected!") except StopIteration as exit: return_ = exit.value self.inflight_result[cmd_id] = return_ event.set() def _handle_event(self, data): - ''' + """ Handle an event. :param dict data: event as a JSON dictionary - ''' + """ global devtools event = devtools.util.parse_json_event(data) - logger.debug('Received event: %s', event) + logger.debug("Received event: %s", event) to_remove = set() for sender in self.channels[type(event)]: try: sender.send_nowait(event) except trio.WouldBlock: - logger.error('Unable to send event "%r" due to full channel %s', - event, sender) + logger.error('Unable to send event "%r" due to full channel %s', event, sender) except trio.BrokenResourceError: to_remove.add(sender) if to_remove: @@ -305,19 +299,19 @@ def _handle_event(self, data): class CdpSession(CdpBase): - ''' + """ Contains the state for a CDP session. Generally you should not instantiate this object yourself; you should call :meth:`CdpConnection.open_session`. - ''' + """ def __init__(self, ws, session_id, target_id): - ''' + """ Constructor. :param trio_websocket.WebSocketConnection ws: :param devtools.target.SessionID session_id: :param devtools.target.TargetID target_id: - ''' + """ super().__init__(ws, session_id, target_id) self._dom_enable_count = 0 @@ -327,12 +321,12 @@ def __init__(self, ws, session_id, target_id): @asynccontextmanager async def dom_enable(self): - ''' + """ A context manager that executes ``dom.enable()`` when it enters and then calls ``dom.disable()``. This keeps track of concurrent callers and only disables DOM events when all callers have exited. - ''' + """ global devtools async with self._dom_enable_lock: self._dom_enable_count += 1 @@ -348,12 +342,12 @@ async def dom_enable(self): @asynccontextmanager async def page_enable(self): - ''' + """ A context manager that executes ``page.enable()`` when it enters and then calls ``page.disable()`` when it exits. This keeps track of concurrent callers and only disables page events when all callers have exited. - ''' + """ global devtools async with self._page_enable_lock: self._page_enable_count += 1 @@ -369,7 +363,7 @@ async def page_enable(self): class CdpConnection(CdpBase, trio.abc.AsyncResource): - ''' + """ Contains the connection state for a Chrome DevTools Protocol server. CDP can multiplex multiple "sessions" over a single connection. This class corresponds to the "root" session, i.e. the implicitly created session that @@ -378,56 +372,54 @@ class CdpConnection(CdpBase, trio.abc.AsyncResource): handling messages targeted at the root session itself. You should generally call the :func:`open_cdp()` instead of instantiating this class directly. - ''' + """ def __init__(self, ws): - ''' + """ Constructor :param trio_websocket.WebSocketConnection ws: - ''' + """ super().__init__(ws, session_id=None, target_id=None) self.sessions = dict() async def aclose(self): - ''' + """ Close the underlying WebSocket connection. This will cause the reader task to gracefully exit when it tries to read the next message from the WebSocket. All of the public APIs (``execute()``, ``listen()``, etc.) will raise ``CdpConnectionClosed`` after the CDP connection is closed. It is safe to call this multiple times. - ''' + """ await self.ws.aclose() @asynccontextmanager - async def open_session(self, target_id) -> \ - typing.AsyncIterator[CdpSession]: - ''' + async def open_session(self, target_id) -> typing.AsyncIterator[CdpSession]: + """ This context manager opens a session and enables the "simple" style of calling CDP APIs. For example, inside a session context, you can call ``await dom.get_document()`` and it will execute on the current session automatically. - ''' + """ session = await self.connect_session(target_id) with session_context(session): yield session - async def connect_session(self, target_id) -> 'CdpSession': - ''' + async def connect_session(self, target_id) -> "CdpSession": + """ Returns a new :class:`CdpSession` connected to the specified target. - ''' + """ global devtools - session_id = await self.execute(devtools.target.attach_to_target( - target_id, True)) + session_id = await self.execute(devtools.target.attach_to_target(target_id, True)) session = CdpSession(self.ws, session_id, target_id) self.sessions[session_id] = session return session async def _reader_task(self): - ''' + """ Runs in the background and handles incoming messages: dispatching responses to commands and events to listeners. - ''' + """ global devtools while True: try: @@ -441,19 +433,14 @@ async def _reader_task(self): try: data = json.loads(message) except json.JSONDecodeError: - raise BrowserError({ - 'code': -32700, - 'message': 'Client received invalid JSON', - 'data': message - }) - logger.debug('Received message %r', data) - if 'sessionId' in data: - session_id = devtools.target.SessionID(data['sessionId']) + raise BrowserError({"code": -32700, "message": "Client received invalid JSON", "data": message}) + logger.debug("Received message %r", data) + if "sessionId" in data: + session_id = devtools.target.SessionID(data["sessionId"]) try: session = self.sessions[session_id] except KeyError: - raise BrowserError('Browser sent a message for an invalid ' - 'session: {!r}'.format(session_id)) + raise BrowserError("Browser sent a message for an invalid " "session: {!r}".format(session_id)) session._handle_data(data) else: self._handle_data(data) @@ -461,7 +448,7 @@ async def _reader_task(self): @asynccontextmanager async def open_cdp(url) -> typing.AsyncIterator[CdpConnection]: - ''' + """ This async context manager opens a connection to the browser specified by ``url`` before entering the block, then closes the connection when the block exits. @@ -469,7 +456,7 @@ async def open_cdp(url) -> typing.AsyncIterator[CdpConnection]: current task, so that commands like ``await target.get_targets()`` will run on this connection automatically. If you want to use multiple connections concurrently, it is recommended to open each on in a separate task. - ''' + """ async with trio.open_nursery() as nursery: conn = await connect_cdp(nursery, url) @@ -481,7 +468,7 @@ async def open_cdp(url) -> typing.AsyncIterator[CdpConnection]: async def connect_cdp(nursery, url) -> CdpConnection: - ''' + """ Connect to the browser specified by ``url`` and spawn a background task in the specified nursery. The ``open_cdp()`` context manager is preferred in most situations. You should only @@ -492,9 +479,8 @@ async def connect_cdp(nursery, url) -> CdpConnection: If ``set_context`` is True, then the returned connection will be installed as the default connection for the current task. This argument is for unusual use cases, such as running inside of a notebook. - ''' - ws = await connect_websocket_url(nursery, url, - max_message_size=MAX_WS_MESSAGE_SIZE) + """ + ws = await connect_websocket_url(nursery, url, max_message_size=MAX_WS_MESSAGE_SIZE) cdp_conn = CdpConnection(ws) nursery.start_soon(cdp_conn._reader_task) return cdp_conn diff --git a/py/selenium/webdriver/common/html5/application_cache.py b/py/selenium/webdriver/common/html5/application_cache.py index 2654f6eca48e5..d4f7fa6be4911 100644 --- a/py/selenium/webdriver/common/html5/application_cache.py +++ b/py/selenium/webdriver/common/html5/application_cache.py @@ -41,8 +41,11 @@ def __init__(self, driver): :Args: - driver: The WebDriver instance which performs user actions. """ - warnings.warn("Application Cache is being removed from all major browsers. This feature will be removed in future versions", - DeprecationWarning, stacklevel=2) + warnings.warn( + "Application Cache is being removed from all major browsers. This feature will be removed in future versions", + DeprecationWarning, + stacklevel=2, + ) self.driver = driver @property @@ -50,4 +53,4 @@ def status(self): """ Returns a current status of application cache. """ - return self.driver.execute(Command.GET_APP_CACHE_STATUS)['value'] + return self.driver.execute(Command.GET_APP_CACHE_STATUS)["value"] diff --git a/py/selenium/webdriver/common/keys.py b/py/selenium/webdriver/common/keys.py index b48b971507bf9..bb4717428e985 100644 --- a/py/selenium/webdriver/common/keys.py +++ b/py/selenium/webdriver/common/keys.py @@ -25,71 +25,71 @@ class Keys: Set of special keys codes. """ - NULL = '\ue000' - CANCEL = '\ue001' # ^break - HELP = '\ue002' - BACKSPACE = '\ue003' + NULL = "\ue000" + CANCEL = "\ue001" # ^break + HELP = "\ue002" + BACKSPACE = "\ue003" BACK_SPACE = BACKSPACE - TAB = '\ue004' - CLEAR = '\ue005' - RETURN = '\ue006' - ENTER = '\ue007' - SHIFT = '\ue008' + TAB = "\ue004" + CLEAR = "\ue005" + RETURN = "\ue006" + ENTER = "\ue007" + SHIFT = "\ue008" LEFT_SHIFT = SHIFT - CONTROL = '\ue009' + CONTROL = "\ue009" LEFT_CONTROL = CONTROL - ALT = '\ue00a' + ALT = "\ue00a" LEFT_ALT = ALT - PAUSE = '\ue00b' - ESCAPE = '\ue00c' - SPACE = '\ue00d' - PAGE_UP = '\ue00e' - PAGE_DOWN = '\ue00f' - END = '\ue010' - HOME = '\ue011' - LEFT = '\ue012' + PAUSE = "\ue00b" + ESCAPE = "\ue00c" + SPACE = "\ue00d" + PAGE_UP = "\ue00e" + PAGE_DOWN = "\ue00f" + END = "\ue010" + HOME = "\ue011" + LEFT = "\ue012" ARROW_LEFT = LEFT - UP = '\ue013' + UP = "\ue013" ARROW_UP = UP - RIGHT = '\ue014' + RIGHT = "\ue014" ARROW_RIGHT = RIGHT - DOWN = '\ue015' + DOWN = "\ue015" ARROW_DOWN = DOWN - INSERT = '\ue016' - DELETE = '\ue017' - SEMICOLON = '\ue018' - EQUALS = '\ue019' + INSERT = "\ue016" + DELETE = "\ue017" + SEMICOLON = "\ue018" + EQUALS = "\ue019" - NUMPAD0 = '\ue01a' # number pad keys - NUMPAD1 = '\ue01b' - NUMPAD2 = '\ue01c' - NUMPAD3 = '\ue01d' - NUMPAD4 = '\ue01e' - NUMPAD5 = '\ue01f' - NUMPAD6 = '\ue020' - NUMPAD7 = '\ue021' - NUMPAD8 = '\ue022' - NUMPAD9 = '\ue023' - MULTIPLY = '\ue024' - ADD = '\ue025' - SEPARATOR = '\ue026' - SUBTRACT = '\ue027' - DECIMAL = '\ue028' - DIVIDE = '\ue029' + NUMPAD0 = "\ue01a" # number pad keys + NUMPAD1 = "\ue01b" + NUMPAD2 = "\ue01c" + NUMPAD3 = "\ue01d" + NUMPAD4 = "\ue01e" + NUMPAD5 = "\ue01f" + NUMPAD6 = "\ue020" + NUMPAD7 = "\ue021" + NUMPAD8 = "\ue022" + NUMPAD9 = "\ue023" + MULTIPLY = "\ue024" + ADD = "\ue025" + SEPARATOR = "\ue026" + SUBTRACT = "\ue027" + DECIMAL = "\ue028" + DIVIDE = "\ue029" - F1 = '\ue031' # function keys - F2 = '\ue032' - F3 = '\ue033' - F4 = '\ue034' - F5 = '\ue035' - F6 = '\ue036' - F7 = '\ue037' - F8 = '\ue038' - F9 = '\ue039' - F10 = '\ue03a' - F11 = '\ue03b' - F12 = '\ue03c' + F1 = "\ue031" # function keys + F2 = "\ue032" + F3 = "\ue033" + F4 = "\ue034" + F5 = "\ue035" + F6 = "\ue036" + F7 = "\ue037" + F8 = "\ue038" + F9 = "\ue039" + F10 = "\ue03a" + F11 = "\ue03b" + F12 = "\ue03c" - META = '\ue03d' - COMMAND = '\ue03d' - ZENKAKU_HANKAKU = '\ue040' + META = "\ue03d" + COMMAND = "\ue03d" + ZENKAKU_HANKAKU = "\ue040" diff --git a/py/selenium/webdriver/common/log.py b/py/selenium/webdriver/common/log.py index 5c6bcb3a32251..850380962bbf3 100644 --- a/py/selenium/webdriver/common/log.py +++ b/py/selenium/webdriver/common/log.py @@ -31,13 +31,13 @@ def import_cdp(): cdp = import_module("selenium.webdriver.common.bidi.cdp") -class Log(): +class Log: """ - This class allows access to logging APIs that use the new WebDriver Bidi - protocol. + This class allows access to logging APIs that use the new WebDriver Bidi + protocol. - This class is not to be used directly and should be used from the webdriver - base classes. + This class is not to be used directly and should be used from the webdriver + base classes. """ def __init__(self, driver, bidi_session) -> None: @@ -45,8 +45,8 @@ def __init__(self, driver, bidi_session) -> None: self.session = bidi_session.session 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() + _pkg = ".".join(__name__.split(".")[:-1]) + self._mutation_listener_js = pkgutil.get_data(_pkg, "mutation-listener.js").decode("utf8").strip() @asynccontextmanager async def mutation_events(self) -> dict: @@ -67,13 +67,15 @@ async def mutation_events(self) -> dict: assert event["old_value"] == "display:none;" """ - page = self.cdp.get_session_context('page.enable') + page = self.cdp.get_session_context("page.enable") await page.execute(self.devtools.page.enable()) - runtime = self.cdp.get_session_context('runtime.enable') + runtime = self.cdp.get_session_context("runtime.enable") await runtime.execute(self.devtools.runtime.enable()) await runtime.execute(self.devtools.runtime.add_binding("__webdriver_attribute")) self.driver.pin_script(self._mutation_listener_js) - script_key = await page.execute(self.devtools.page.add_script_to_evaluate_on_new_document(self._mutation_listener_js)) + 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 = {} @@ -85,9 +87,9 @@ async def mutation_events(self) -> dict: if not elements: elements.append(None) event["element"] = elements[0] - event["attribute_name"] = payload['name'] - event["current_value"] = payload['value'] - event["old_value"] = payload['oldValue'] + event["attribute_name"] = payload["name"] + event["current_value"] = payload["value"] + event["old_value"] = payload["oldValue"] @asynccontextmanager async def add_js_error_listener(self): @@ -103,9 +105,9 @@ async def add_js_error_listener(self): assert error.exception_details.stack_trace.call_frames[0].function_name == "onmouseover" """ - session = self.cdp.get_session_context('page.enable') + session = self.cdp.get_session_context("page.enable") await session.execute(self.devtools.page.enable()) - session = self.cdp.get_session_context('runtime.enable') + session = self.cdp.get_session_context("runtime.enable") await session.execute(self.devtools.runtime.enable()) js_exception = self.devtools.runtime.ExceptionThrown(None, None) async with session.wait_for(self.devtools.runtime.ExceptionThrown) as exception: @@ -130,14 +132,12 @@ async def add_listener(self, event_type) -> dict: """ from selenium.webdriver.common.bidi.console import Console - session = self.cdp.get_session_context('page.enable') + + session = self.cdp.get_session_context("page.enable") await session.execute(self.devtools.page.enable()) - session = self.cdp.get_session_context('runtime.enable') + session = self.cdp.get_session_context("runtime.enable") await session.execute(self.devtools.runtime.enable()) - console = { - "message": None, - "level": None - } + console = {"message": None, "level": None} async with session.wait_for(self.devtools.runtime.ConsoleAPICalled) as messages: yield console diff --git a/py/selenium/webdriver/common/options.py b/py/selenium/webdriver/common/options.py index ac36d74c0e0f6..946acf9bc89c2 100644 --- a/py/selenium/webdriver/common/options.py +++ b/py/selenium/webdriver/common/options.py @@ -38,7 +38,7 @@ def capabilities(self): return self._caps def set_capability(self, name, value) -> None: - """ Sets a capability """ + """Sets a capability""" self._caps[name] = value @property @@ -112,8 +112,10 @@ def unhandled_prompt_behavior(self, behavior: str) -> None: if behavior in ["dismiss", "accept", "dismiss and notify", "accept and notify", "ignore"]: self.set_capability("unhandledPromptBehavior", behavior) else: - raise ValueError("Behavior can only be one of the following: dismiss, accept, dismiss and notify, " - "accept and notify, ignore") + raise ValueError( + "Behavior can only be one of the following: dismiss, accept, dismiss and notify, " + "accept and notify, ignore" + ) @property def timeouts(self) -> dict: @@ -135,19 +137,21 @@ 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: typing.Optional[str] = None, + android_activity: typing.Optional[str] = None, + device_serial: typing.Optional[str] = None, + ) -> None: """ - Enables mobile browser use for browsers that support it + Enables mobile browser use for browsers that support it - :Args: - android_activity: The name of the android package to start + :Args: + android_activity: The name of the android package to start """ if not android_package: raise AttributeError("android_package must be passed in") - self.mobile_options = { - "androidPackage": android_package - } + self.mobile_options = {"androidPackage": android_package} if android_activity: self.mobile_options["androidActivity"] = android_activity if device_serial: @@ -158,7 +162,7 @@ def accept_insecure_certs(self) -> bool: """ :returns: whether the session accepts insecure certificates """ - return self._caps.get('acceptInsecureCerts', False) + return self._caps.get("acceptInsecureCerts", False) @accept_insecure_certs.setter def accept_insecure_certs(self, value: bool) -> None: @@ -168,14 +172,14 @@ def accept_insecure_certs(self, value: bool) -> None: :param value: whether to accept insecure certificates """ - self._caps['acceptInsecureCerts'] = value + self._caps["acceptInsecureCerts"] = value @property def strict_file_interactability(self) -> bool: """ :returns: whether session is strict about file interactability """ - return self._caps.get('strictFileInteractability', False) + return self._caps.get("strictFileInteractability", False) @strict_file_interactability.setter def strict_file_interactability(self, value: bool) -> None: @@ -184,14 +188,14 @@ def strict_file_interactability(self, value: bool) -> None: :param value: whether file interactability is strict """ - self._caps['strictFileInteractability'] = value + self._caps["strictFileInteractability"] = value @property def set_window_rect(self) -> bool: """ :returns: whether the remote end supports setting window size and position """ - return self._caps.get('setWindowRect', False) + return self._caps.get("setWindowRect", False) @set_window_rect.setter def set_window_rect(self, value: bool) -> None: @@ -201,7 +205,7 @@ def set_window_rect(self, value: bool) -> None: :param value: whether remote end must support setting window resizing and repositioning """ - self._caps['setWindowRect'] = value + self._caps["setWindowRect"] = value @property def proxy(self) -> Proxy: @@ -227,7 +231,6 @@ def default_capabilities(self): class ArgOptions(BaseOptions): - def __init__(self) -> None: super().__init__() self._arguments = [] @@ -250,11 +253,11 @@ def add_argument(self, argument): if argument: self._arguments.append(argument) else: - raise ValueError('argument can not be null') + raise ValueError("argument can not be null") def ignore_local_proxy_environment_variables(self) -> None: """ - By calling this you will ignore HTTP_PROXY and HTTPS_PROXY from being picked up and used. + By calling this you will ignore HTTP_PROXY and HTTPS_PROXY from being picked up and used. """ self._ignore_local_proxy = True diff --git a/py/selenium/webdriver/common/print_page_options.py b/py/selenium/webdriver/common/print_page_options.py index 539c50b53394c..d7ab71340aad3 100644 --- a/py/selenium/webdriver/common/print_page_options.py +++ b/py/selenium/webdriver/common/print_page_options.py @@ -30,7 +30,7 @@ from typing_extensions import Literal from typing_extensions import TypedDict - Orientation = Literal['portrait', 'landscape'] + Orientation = Literal["portrait", "landscape"] class _MarginOpts(TypedDict, total=False): left: float @@ -50,6 +50,7 @@ class _PrintOpts(TypedDict, total=False): scale: float shrinkToFit: bool pageRanges: List[str] + else: from typing import Any from typing import Dict @@ -59,7 +60,7 @@ class _PrintOpts(TypedDict, total=False): class PrintOptions: - ORIENTATION_VALUES = ['portrait', 'landscape'] + ORIENTATION_VALUES = ["portrait", "landscape"] def __init__(self) -> None: self._print_options: _PrintOpts = {} @@ -77,7 +78,7 @@ def orientation(self) -> Optional[Orientation]: """ :Returns: Orientation that was set for the page """ - return self._print_options.get('orientation', None) + return self._print_options.get("orientation", None) @orientation.setter def orientation(self, value: Orientation) -> None: @@ -87,16 +88,16 @@ def orientation(self, value: Orientation) -> None: - value: Either portrait or landscape """ if value not in self.ORIENTATION_VALUES: - raise ValueError(f'Orientation value must be one of {self.ORIENTATION_VALUES}') + raise ValueError(f"Orientation value must be one of {self.ORIENTATION_VALUES}") - self._print_options['orientation'] = value + self._print_options["orientation"] = value @property def scale(self) -> Optional[float]: """ :Returns: Scale that was set for the page """ - return self._print_options.get('scale', None) + return self._print_options.get("scale", None) @scale.setter def scale(self, value: float) -> None: @@ -105,19 +106,19 @@ def scale(self, value: float) -> None: :Args: - value: integer or float between 0.1 and 2 """ - self.__validate_num_property('Scale', value) + self.__validate_num_property("Scale", value) if value < 0.1 or value > 2: - raise ValueError('Scale value should be between 0.1 and 2') + raise ValueError("Scale value should be between 0.1 and 2") - self._print_options['scale'] = value + self._print_options["scale"] = value @property def background(self) -> Optional[bool]: """ :Returns: Background value that was set """ - return self._print_options.get('background', None) + return self._print_options.get("background", None) @background.setter def background(self, value: bool) -> None: @@ -127,15 +128,15 @@ def background(self, value: bool) -> None: - value: Boolean """ if not isinstance(value, bool): - raise ValueError('Set background value should be a boolean') - self._print_options['background'] = value + raise ValueError("Set background value should be a boolean") + self._print_options["background"] = value @property def page_width(self) -> Optional[float]: """ :Returns: Page width that was set """ - return self._page.get('width', None) + return self._page.get("width", None) @page_width.setter def page_width(self, value: float) -> None: @@ -144,17 +145,17 @@ def page_width(self, value: float) -> None: :Args: - value: A positive integer or float """ - self.__validate_num_property('Page Width', value) + self.__validate_num_property("Page Width", value) - self._page['width'] = value - self._print_options['page'] = self._page + self._page["width"] = value + self._print_options["page"] = self._page @property def page_height(self) -> Optional[float]: """ :Returns: Page height that was set """ - return self._page.get('height', None) + return self._page.get("height", None) @page_height.setter def page_height(self, value: float) -> None: @@ -163,17 +164,17 @@ def page_height(self, value: float) -> None: :Args: - value: A positive integer or float """ - self.__validate_num_property('Page Height', value) + self.__validate_num_property("Page Height", value) - self._page['height'] = value - self._print_options['page'] = self._page + self._page["height"] = value + self._print_options["page"] = self._page @property def margin_top(self) -> Optional[float]: """ :Returns: Top margin of the page """ - return self._margin.get('top', None) + return self._margin.get("top", None) @margin_top.setter def margin_top(self, value: float) -> None: @@ -182,17 +183,17 @@ def margin_top(self, value: float) -> None: :Args: - value: A positive integer or float """ - self.__validate_num_property('Margin top', value) + self.__validate_num_property("Margin top", value) - self._margin['top'] = value - self._print_options['margin'] = self._margin + self._margin["top"] = value + self._print_options["margin"] = self._margin @property def margin_left(self) -> Optional[float]: """ :Returns: Left margin of the page """ - return self._margin.get('left', None) + return self._margin.get("left", None) @margin_left.setter def margin_left(self, value: float) -> None: @@ -201,17 +202,17 @@ def margin_left(self, value: float) -> None: :Args: - value: A positive integer or float """ - self.__validate_num_property('Margin left', value) + self.__validate_num_property("Margin left", value) - self._margin['left'] = value - self._print_options['margin'] = self._margin + self._margin["left"] = value + self._print_options["margin"] = self._margin @property def margin_bottom(self) -> Optional[float]: """ :Returns: Bottom margin of the page """ - return self._margin.get('bottom', None) + return self._margin.get("bottom", None) @margin_bottom.setter def margin_bottom(self, value: float) -> None: @@ -220,17 +221,17 @@ def margin_bottom(self, value: float) -> None: :Args: - value: A positive integer or float """ - self.__validate_num_property('Margin bottom', value) + self.__validate_num_property("Margin bottom", value) - self._margin['bottom'] = value - self._print_options['margin'] = self._margin + self._margin["bottom"] = value + self._print_options["margin"] = self._margin @property def margin_right(self) -> Optional[float]: """ :Returns: Right margin of the page """ - return self._margin.get('right', None) + return self._margin.get("right", None) @margin_right.setter def margin_right(self, value: float) -> None: @@ -239,17 +240,17 @@ def margin_right(self, value: float) -> None: :Args: - value: A positive integer or float """ - self.__validate_num_property('Margin right', value) + self.__validate_num_property("Margin right", value) - self._margin['right'] = value - self._print_options['margin'] = self._margin + self._margin["right"] = value + self._print_options["margin"] = self._margin @property def shrink_to_fit(self) -> Optional[bool]: """ :Returns: Value set for shrinkToFit """ - return self._print_options.get('shrinkToFit', None) + return self._print_options.get("shrinkToFit", None) @shrink_to_fit.setter def shrink_to_fit(self, value: bool) -> None: @@ -259,15 +260,15 @@ def shrink_to_fit(self, value: bool) -> None: - value: Boolean """ if not isinstance(value, bool): - raise ValueError('Set shrink to fit value should be a boolean') - self._print_options['shrinkToFit'] = value + raise ValueError("Set shrink to fit value should be a boolean") + self._print_options["shrinkToFit"] = value @property def page_ranges(self) -> Optional[List[str]]: """ :Returns: value set for pageRanges """ - return self._print_options.get('pageRanges', None) + return self._print_options.get("pageRanges", None) @page_ranges.setter def page_ranges(self, value: List[str]) -> None: @@ -277,15 +278,15 @@ def page_ranges(self, value: List[str]) -> None: - value: A list of page ranges. Eg: ['1-2'] """ if not isinstance(value, list): - raise ValueError('Page ranges should be a list') - self._print_options['pageRanges'] = value + raise ValueError("Page ranges should be a list") + self._print_options["pageRanges"] = value def __validate_num_property(self, property_name: str, value: float) -> None: """ Helper function to validate some of the properties """ if not isinstance(value, (int, float)): - raise ValueError(f'{property_name} should be an integer or a float') + raise ValueError(f"{property_name} should be an integer or a float") if value < 0: - raise ValueError(f'{property_name} cannot be less then 0') + raise ValueError(f"{property_name} cannot be less then 0") diff --git a/py/selenium/webdriver/common/proxy.py b/py/selenium/webdriver/common/proxy.py index 4106d0932abb1..1ad5a7b667240 100644 --- a/py/selenium/webdriver/common/proxy.py +++ b/py/selenium/webdriver/common/proxy.py @@ -27,7 +27,7 @@ class ProxyTypeFactory: @staticmethod def make(ff_value, string): - return {'ff_value': ff_value, 'string': string} + return {"ff_value": ff_value, "string": string} class ProxyType: @@ -39,22 +39,22 @@ class ProxyType: 'string' is id of proxy type. """ - DIRECT = ProxyTypeFactory.make(0, 'DIRECT') # Direct connection, no proxy (default on Windows). - MANUAL = ProxyTypeFactory.make(1, 'MANUAL') # Manual proxy settings (e.g., for httpProxy). - PAC = ProxyTypeFactory.make(2, 'PAC') # Proxy autoconfiguration from URL. - RESERVED_1 = ProxyTypeFactory.make(3, 'RESERVED1') # Never used. - AUTODETECT = ProxyTypeFactory.make(4, 'AUTODETECT') # Proxy autodetection (presumably with WPAD). - SYSTEM = ProxyTypeFactory.make(5, 'SYSTEM') # Use system settings (default on Linux). - UNSPECIFIED = ProxyTypeFactory.make(6, 'UNSPECIFIED') # Not initialized (for internal use). + DIRECT = ProxyTypeFactory.make(0, "DIRECT") # Direct connection, no proxy (default on Windows). + MANUAL = ProxyTypeFactory.make(1, "MANUAL") # Manual proxy settings (e.g., for httpProxy). + PAC = ProxyTypeFactory.make(2, "PAC") # Proxy autoconfiguration from URL. + RESERVED_1 = ProxyTypeFactory.make(3, "RESERVED1") # Never used. + AUTODETECT = ProxyTypeFactory.make(4, "AUTODETECT") # Proxy autodetection (presumably with WPAD). + SYSTEM = ProxyTypeFactory.make(5, "SYSTEM") # Use system settings (default on Linux). + UNSPECIFIED = ProxyTypeFactory.make(6, "UNSPECIFIED") # Not initialized (for internal use). @classmethod def load(cls, value): - if isinstance(value, dict) and 'string' in value: - value = value['string'] + if isinstance(value, dict) and "string" in value: + value = value["string"] value = str(value).upper() for attr in dir(cls): attr_value = getattr(cls, attr) - if isinstance(attr_value, dict) and 'string' in attr_value and attr_value['string'] == value: + if isinstance(attr_value, dict) and "string" in attr_value and attr_value["string"] == value: return attr_value raise Exception(f"No proxy type is found for {value}") @@ -66,14 +66,14 @@ class Proxy: proxyType = ProxyType.UNSPECIFIED autodetect = False - ftpProxy = '' - httpProxy = '' - noProxy = '' - proxyAutoconfigUrl = '' - sslProxy = '' - socksProxy = '' - socksUsername = '' - socksPassword = '' + ftpProxy = "" + httpProxy = "" + noProxy = "" + proxyAutoconfigUrl = "" + sslProxy = "" + socksProxy = "" + socksUsername = "" + socksPassword = "" socksVersion = None def __init__(self, raw=None): @@ -84,28 +84,28 @@ def __init__(self, raw=None): - raw: raw proxy data. If None, default class values are used. """ if raw: - if 'proxyType' in raw and raw['proxyType']: - self.proxy_type = ProxyType.load(raw['proxyType']) - if 'ftpProxy' in raw and raw['ftpProxy']: - self.ftp_proxy = raw['ftpProxy'] - if 'httpProxy' in raw and raw['httpProxy']: - self.http_proxy = raw['httpProxy'] - if 'noProxy' in raw and raw['noProxy']: - self.no_proxy = raw['noProxy'] - if 'proxyAutoconfigUrl' in raw and raw['proxyAutoconfigUrl']: - self.proxy_autoconfig_url = raw['proxyAutoconfigUrl'] - if 'sslProxy' in raw and raw['sslProxy']: - self.sslProxy = raw['sslProxy'] - if 'autodetect' in raw and raw['autodetect']: - self.auto_detect = raw['autodetect'] - if 'socksProxy' in raw and raw['socksProxy']: - self.socks_proxy = raw['socksProxy'] - if 'socksUsername' in raw and raw['socksUsername']: - self.socks_username = raw['socksUsername'] - if 'socksPassword' in raw and raw['socksPassword']: - self.socks_password = raw['socksPassword'] - if 'socksVersion' in raw and raw['socksVersion']: - self.socks_version = raw['socksVersion'] + if "proxyType" in raw and raw["proxyType"]: + self.proxy_type = ProxyType.load(raw["proxyType"]) + if "ftpProxy" in raw and raw["ftpProxy"]: + self.ftp_proxy = raw["ftpProxy"] + if "httpProxy" in raw and raw["httpProxy"]: + self.http_proxy = raw["httpProxy"] + if "noProxy" in raw and raw["noProxy"]: + self.no_proxy = raw["noProxy"] + if "proxyAutoconfigUrl" in raw and raw["proxyAutoconfigUrl"]: + self.proxy_autoconfig_url = raw["proxyAutoconfigUrl"] + if "sslProxy" in raw and raw["sslProxy"]: + self.sslProxy = raw["sslProxy"] + if "autodetect" in raw and raw["autodetect"]: + self.auto_detect = raw["autodetect"] + if "socksProxy" in raw and raw["socksProxy"]: + self.socks_proxy = raw["socksProxy"] + if "socksUsername" in raw and raw["socksUsername"]: + self.socks_username = raw["socksUsername"] + if "socksPassword" in raw and raw["socksPassword"]: + self.socks_password = raw["socksPassword"] + if "socksVersion" in raw and raw["socksVersion"]: + self.socks_version = raw["socksVersion"] @property def proxy_type(self): @@ -321,7 +321,9 @@ def socks_version(self, value) -> None: def _verify_proxy_type_compatibility(self, compatible_proxy): if self.proxyType not in (ProxyType.UNSPECIFIED, compatible_proxy): - raise Exception(f"Specified proxy type ({compatible_proxy}) not compatible with current setting ({self.proxyType})") + raise Exception( + f"Specified proxy type ({compatible_proxy}) not compatible with current setting ({self.proxyType})" + ) def add_to_capabilities(self, capabilities): """ @@ -331,25 +333,25 @@ def add_to_capabilities(self, capabilities): - capabilities: The capabilities to which proxy will be added. """ proxy_caps = {} - proxy_caps['proxyType'] = self.proxyType['string'] + proxy_caps["proxyType"] = self.proxyType["string"] if self.autodetect: - proxy_caps['autodetect'] = self.autodetect + proxy_caps["autodetect"] = self.autodetect if self.ftpProxy: - proxy_caps['ftpProxy'] = self.ftpProxy + proxy_caps["ftpProxy"] = self.ftpProxy if self.httpProxy: - proxy_caps['httpProxy'] = self.httpProxy + proxy_caps["httpProxy"] = self.httpProxy if self.proxyAutoconfigUrl: - proxy_caps['proxyAutoconfigUrl'] = self.proxyAutoconfigUrl + proxy_caps["proxyAutoconfigUrl"] = self.proxyAutoconfigUrl if self.sslProxy: - proxy_caps['sslProxy'] = self.sslProxy + proxy_caps["sslProxy"] = self.sslProxy if self.noProxy: - proxy_caps['noProxy'] = self.noProxy + proxy_caps["noProxy"] = self.noProxy if self.socksProxy: - proxy_caps['socksProxy'] = self.socksProxy + proxy_caps["socksProxy"] = self.socksProxy if self.socksUsername: - proxy_caps['socksUsername'] = self.socksUsername + proxy_caps["socksUsername"] = self.socksUsername if self.socksPassword: - proxy_caps['socksPassword'] = self.socksPassword + proxy_caps["socksPassword"] = self.socksPassword if self.socksVersion: - proxy_caps['socksVersion'] = self.socksVersion - capabilities['proxy'] = proxy_caps + proxy_caps["socksVersion"] = self.socksVersion + capabilities["proxy"] = proxy_caps diff --git a/py/selenium/webdriver/common/timeouts.py b/py/selenium/webdriver/common/timeouts.py index 72dc0a71a2330..0f22987e8ff21 100644 --- a/py/selenium/webdriver/common/timeouts.py +++ b/py/selenium/webdriver/common/timeouts.py @@ -20,6 +20,7 @@ if TYPE_CHECKING: import sys + if sys.version_info >= (3, 8): from typing import TypedDict else: @@ -32,11 +33,11 @@ class JSONTimeouts(TypedDict, total=False): else: from typing import Dict + JSONTimeouts = Dict[str, int] class Timeouts: - def __init__(self, implicit_wait: float = 0, page_load: float = 0, script: float = 0) -> None: """ Create a new Timeout object. diff --git a/py/selenium/webdriver/common/utils.py b/py/selenium/webdriver/common/utils.py index f76f7019cc188..652079dc6997d 100644 --- a/py/selenium/webdriver/common/utils.py +++ b/py/selenium/webdriver/common/utils.py @@ -36,7 +36,7 @@ def free_port() -> int: Determines a free port using sockets. """ free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - free_socket.bind(('127.0.0.1', 0)) + free_socket.bind(("127.0.0.1", 0)) free_socket.listen(5) port: int = free_socket.getsockname()[1] free_socket.close() @@ -92,9 +92,9 @@ def join_host_port(host: str, port: int) -> str: - port - An integer port. """ - if ':' in host and not host.startswith('['): - return '[%s]:%d' % (host, port) - return '%s:%d' % (host, port) + if ":" in host and not host.startswith("["): + return "[%s]:%d" % (host, port) + return "%s:%d" % (host, port) def is_connectable(port: int, host: Optional[str] = "localhost") -> bool: diff --git a/py/selenium/webdriver/common/virtual_authenticator.py b/py/selenium/webdriver/common/virtual_authenticator.py index 47596184f05f4..33fe0598161be 100644 --- a/py/selenium/webdriver/common/virtual_authenticator.py +++ b/py/selenium/webdriver/common/virtual_authenticator.py @@ -26,6 +26,7 @@ class Protocol(Enum): """ Protocol to communicate with the authenticator. """ + CTAP2 = "ctap2" U2F = "ctap1/u2f" @@ -34,6 +35,7 @@ class Transport(Enum): """ Transport method to communicate with the authenticator. """ + BLE = "ble" USB = "usb" NFC = "nfc" @@ -48,13 +50,13 @@ class VirtualAuthenticatorOptions: def __init__(self) -> None: """Constructor. Initialize VirtualAuthenticatorOptions object. - :default: - - protocol: Protocol.CTAP2 - - transport: Transport.USB - - hasResidentKey: False - - hasUserVerification: False - - isUserConsenting: True - - isUserVerified: False + :default: + - protocol: Protocol.CTAP2 + - transport: Transport.USB + - hasResidentKey: False + - hasUserVerification: False + - isUserConsenting: True + - isUserVerified: False """ self._protocol: Protocol = Protocol.CTAP2 @@ -119,12 +121,20 @@ def to_dict(self) -> typing.Dict[str, typing.Any]: "hasResidentKey": self.has_resident_key, "hasUserVerification": self.has_user_verification, "isUserConsenting": self.is_user_consenting, - "isUserVerified": self.is_user_verified + "isUserVerified": self.is_user_verified, } class Credential: - def __init__(self, credential_id: bytes, is_resident_credential: bool, rp_id: str, user_handle: typing.Optional[bytes], private_key: bytes, sign_count: int): + def __init__( + self, + credential_id: bytes, + is_resident_credential: bool, + rp_id: str, + user_handle: typing.Optional[bytes], + private_key: bytes, + sign_count: int, + ): """Constructor. A credential stored in a virtual authenticator. https://w3c.github.io/webauthn/#credential-parameters @@ -170,59 +180,60 @@ def sign_count(self) -> int: return self._sign_count @classmethod - def create_non_resident_credential(cls, id: bytes, rp_id: str, private_key: bytes, sign_count: int) -> 'Credential': + def create_non_resident_credential(cls, id: bytes, rp_id: str, private_key: bytes, sign_count: int) -> "Credential": """Creates a non-resident (i.e. stateless) credential. - :Args: - - id (bytes): Unique base64 encoded string. - - rp_id (str): Relying party identifier. - - private_key (bytes): Base64 encoded PKCS - - sign_count (int): intital value for a signature counter. + :Args: + - id (bytes): Unique base64 encoded string. + - rp_id (str): Relying party identifier. + - private_key (bytes): Base64 encoded PKCS + - sign_count (int): intital value for a signature counter. - :Returns: - - Credential: A non-resident credential. + :Returns: + - Credential: A non-resident credential. """ return cls(id, False, rp_id, None, private_key, sign_count) @classmethod - def create_resident_credential(cls, id: bytes, rp_id: str, user_handle: typing.Optional[bytes], private_key: bytes, sign_count: int) -> 'Credential': + def create_resident_credential( + cls, id: bytes, rp_id: str, user_handle: typing.Optional[bytes], private_key: bytes, sign_count: int + ) -> "Credential": """Creates a resident (i.e. stateful) credential. - :Args: - - id (bytes): Unique base64 encoded string. - - rp_id (str): Relying party identifier. - - user_handle (bytes): userHandle associated to the credential. Must be Base64 encoded string. - - private_key (bytes): Base64 encoded PKCS - - sign_count (int): intital value for a signature counter. - - :returns: - - Credential: A resident credential. + :Args: + - id (bytes): Unique base64 encoded string. + - rp_id (str): Relying party identifier. + - user_handle (bytes): userHandle associated to the credential. Must be Base64 encoded string. + - private_key (bytes): Base64 encoded PKCS + - sign_count (int): intital value for a signature counter. + + :returns: + - Credential: A resident credential. """ return cls(id, True, rp_id, user_handle, private_key, sign_count) def to_dict(self) -> typing.Dict[str, typing.Any]: credential_data = { - 'credentialId': self.id, - 'isResidentCredential': self._is_resident_credential, - 'rpId': self.rp_id, - 'privateKey': self.private_key, - 'signCount': self.sign_count, + "credentialId": self.id, + "isResidentCredential": self._is_resident_credential, + "rpId": self.rp_id, + "privateKey": self.private_key, + "signCount": self.sign_count, } if self.user_handle: - credential_data['userHandle'] = self.user_handle + credential_data["userHandle"] = self.user_handle return credential_data @classmethod - def from_dict(cls, data: typing.Dict[str, typing.Any]) -> 'Credential': + def from_dict(cls, data: typing.Dict[str, typing.Any]) -> "Credential": _id = urlsafe_b64decode(f"{data['credentialId']}==") - is_resident_credential = bool(data['isResidentCredential']) - rp_id = data.get('rpId', None) + is_resident_credential = bool(data["isResidentCredential"]) + rp_id = data.get("rpId", None) private_key = urlsafe_b64decode(f"{data['privateKey']}==") - sign_count = int(data['signCount']) - user_handle = urlsafe_b64decode(f"{data['userHandle']}==") \ - if data.get('userHandle', None) else None + sign_count = int(data["signCount"]) + user_handle = urlsafe_b64decode(f"{data['userHandle']}==") if data.get("userHandle", None) else None return cls(_id, is_resident_credential, rp_id, user_handle, private_key, sign_count) @@ -235,10 +246,15 @@ def required_chromium_based_browser(func): """ A decorator to ensure that the client used is a chromium based browser. """ + @functools.wraps(func) def wrapper(self, *args, **kwargs): - assert self.caps["browserName"].lower() not in ["firefox", "safari"], "This only currently works in Chromium based browsers" + assert self.caps["browserName"].lower() not in [ + "firefox", + "safari", + ], "This only currently works in Chromium based browsers" return func(self, *args, **kwargs) + return wrapper @@ -246,12 +262,12 @@ def required_virtual_authenticator(func): """ A decorator to ensure that the function is called with a virtual authenticator. """ + @functools.wraps(func) @required_chromium_based_browser def wrapper(self, *args, **kwargs): if not self.virtual_authenticator_id: - raise ValueError( - "This function requires a virtual authenticator to be set." - ) + raise ValueError("This function requires a virtual authenticator to be set.") return func(self, *args, **kwargs) + return wrapper diff --git a/py/selenium/webdriver/common/window.py b/py/selenium/webdriver/common/window.py index 08ecd4e520115..e4886f1d85e1d 100644 --- a/py/selenium/webdriver/common/window.py +++ b/py/selenium/webdriver/common/window.py @@ -23,5 +23,5 @@ class WindowTypes: """Set of supported window types.""" - TAB = 'tab' - WINDOW = 'window' + TAB = "tab" + WINDOW = "window" diff --git a/py/selenium/webdriver/edge/options.py b/py/selenium/webdriver/edge/options.py index 86848f2386a22..521b1d275d410 100644 --- a/py/selenium/webdriver/edge/options.py +++ b/py/selenium/webdriver/edge/options.py @@ -41,7 +41,7 @@ def to_capabilities(self) -> dict: """ caps = super().to_capabilities() if self._use_webview: - caps['browserName'] = 'webview2' + caps["browserName"] = "webview2" return caps diff --git a/py/selenium/webdriver/edge/service.py b/py/selenium/webdriver/edge/service.py index f70c8b1b7e301..76375cbf2352d 100644 --- a/py/selenium/webdriver/edge/service.py +++ b/py/selenium/webdriver/edge/service.py @@ -19,14 +19,19 @@ from selenium.webdriver.chromium import service -DEFAULT_EXECUTABLE_PATH = 'msedgedriver' +DEFAULT_EXECUTABLE_PATH = "msedgedriver" class Service(service.ChromiumService): - - def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, - port: int = 0, verbose: bool = False, log_path: str = None, - service_args: List[str] = None, env=None): + def __init__( + self, + executable_path: str = DEFAULT_EXECUTABLE_PATH, + port: int = 0, + verbose: bool = False, + log_path: str = None, + service_args: List[str] = None, + env=None, + ): """ Creates a new instance of the EdgeDriver service. EdgeDriver provides an interface for Microsoft WebDriver to use @@ -52,4 +57,5 @@ def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, service_args, log_path, env, - "Please download from https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/") + "Please download from https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/", + ) diff --git a/py/selenium/webdriver/edge/webdriver.py b/py/selenium/webdriver/edge/webdriver.py index 1d5a234c37428..d1fcc9355a1dc 100644 --- a/py/selenium/webdriver/edge/webdriver.py +++ b/py/selenium/webdriver/edge/webdriver.py @@ -34,10 +34,18 @@ class WebDriver(ChromiumDriver): https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ """ - def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, port=DEFAULT_PORT, - options: Options = Options(), service_args=None, - capabilities=None, service_log_path=DEFAULT_SERVICE_LOG_PATH, - service: Service = None, keep_alive=False, verbose=False): + def __init__( + self, + executable_path=DEFAULT_EXECUTABLE_PATH, + port=DEFAULT_PORT, + options: Options = Options(), + service_args=None, + capabilities=None, + service_log_path=DEFAULT_SERVICE_LOG_PATH, + service: Service = None, + keep_alive=False, + verbose=False, + ): """ Creates a new instance of the edge driver. Starts the service and then creates new instance of edge driver. @@ -53,18 +61,26 @@ def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, port=DEFAULT_PORT, - service - Service object for handling the browser driver if you need to pass extra details - keep_alive - Whether to configure EdgeRemoteConnection to use HTTP keep-alive. - verbose - whether to set verbose logging in the service. - """ - if executable_path != 'msedgedriver': - warnings.warn('executable_path has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + """ + if executable_path != "msedgedriver": + warnings.warn( + "executable_path has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) if not service: service = Service(executable_path, port, service_args, service_log_path) - super().__init__(DesiredCapabilities.EDGE['browserName'], "ms", - port, options, - service_args, capabilities, - service_log_path, service, keep_alive) + super().__init__( + DesiredCapabilities.EDGE["browserName"], + "ms", + port, + options, + service_args, + capabilities, + service_log_path, + service, + keep_alive, + ) def create_options(self) -> Options: return Options() diff --git a/py/selenium/webdriver/firefox/extension_connection.py b/py/selenium/webdriver/firefox/extension_connection.py index 1c72fb6d918f1..56e5bfee9721c 100644 --- a/py/selenium/webdriver/firefox/extension_connection.py +++ b/py/selenium/webdriver/firefox/extension_connection.py @@ -54,20 +54,19 @@ def __init__(self, host, firefox_profile, firefox_binary=None, timeout=30): super().__init__(_URL, keep_alive=True) def quit(self, sessionId=None): - self.execute(Command.QUIT, {'sessionId': sessionId}) + self.execute(Command.QUIT, {"sessionId": sessionId}) while self.is_connectable(): LOGGER.info("waiting to quit") time.sleep(1) def connect(self): """Connects to the extension and retrieves the session id.""" - return self.execute(Command.NEW_SESSION, - {'desiredCapabilities': DesiredCapabilities.FIREFOX}) + return self.execute(Command.NEW_SESSION, {"desiredCapabilities": DesiredCapabilities.FIREFOX}) @classmethod def connect_and_quit(cls): """Connects to an running browser and quit immediately.""" - cls._request('%s/extensions/firefox/quit' % _URL) + cls._request("%s/extensions/firefox/quit" % _URL) @classmethod def is_connectable(cls): @@ -80,4 +79,5 @@ class ExtensionConnectionError(Exception): Might be caused by bad input or bugs in webdriver """ + pass diff --git a/py/selenium/webdriver/firefox/firefox_binary.py b/py/selenium/webdriver/firefox/firefox_binary.py index b1a04326d60d0..a96c746478071 100644 --- a/py/selenium/webdriver/firefox/firefox_binary.py +++ b/py/selenium/webdriver/firefox/firefox_binary.py @@ -55,7 +55,8 @@ def __init__(self, firefox_path=None, log_file=None): "selenium.webdriver.firefox.firefox_binary import " "FirefoxBinary\n\nbinary = " "FirefoxBinary('/path/to/binary')\ndriver = " - "webdriver.Firefox(firefox_binary=binary)") + "webdriver.Firefox(firefox_binary=binary)" + ) # Rather than modifying the environment of the calling Python process # copy it and modify as needed. self._firefox_env = os.environ.copy() @@ -87,15 +88,13 @@ def kill(self): def _start_from_profile_path(self, path): self._firefox_env["XRE_PROFILE_PATH"] = path - if self.platform == 'linux': + if self.platform == "linux": self._modify_link_library_path() command = [self._start_cmd, "-foreground"] if self.command_line: for cli in self.command_line: command.append(cli) - self.process = Popen( - command, stdout=self._log_file, stderr=STDOUT, - env=self._firefox_env) + self.process = Popen(command, stdout=self._log_file, stderr=STDOUT, env=self._firefox_env) def _wait_until_connectable(self, timeout=30): """Blocks until the extension is connectable in the firefox.""" @@ -106,15 +105,16 @@ def _wait_until_connectable(self, timeout=30): raise WebDriverException( "The browser appears to have exited " "before we could connect. If you specified a log_file in " - "the FirefoxBinary constructor, check it for details.") + "the FirefoxBinary constructor, check it for details." + ) if count >= timeout: self.kill() raise WebDriverException( "Can't load the profile. Possible firefox version mismatch. " "You must use GeckoDriver instead for Firefox 48+. Profile " "Dir: %s If you specified a log_file in the " - "FirefoxBinary constructor, check it for details." - % (self.profile.path)) + "FirefoxBinary constructor, check it for details." % (self.profile.path) + ) count += 1 time.sleep(1) return True @@ -128,8 +128,11 @@ def _find_exe_in_registry(self): except ImportError: from winreg import OpenKey, QueryValue, HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER import shlex - keys = (r"SOFTWARE\Classes\FirefoxHTML\shell\open\command", - r"SOFTWARE\Classes\Applications\firefox.exe\shell\open\command") + + keys = ( + r"SOFTWARE\Classes\FirefoxHTML\shell\open\command", + r"SOFTWARE\Classes\Applications\firefox.exe\shell\open\command", + ) command = "" for path in keys: try: @@ -164,8 +167,8 @@ def _get_firefox_start_cmd(self): if not os.path.exists(start_cmd): start_cmd = os.path.expanduser("~") + start_cmd elif self.platform == "windows": # same - start_cmd = (self._find_exe_in_registry() or self._default_windows_location()) - elif self.platform == 'java' and os._name == 'nt': + start_cmd = self._find_exe_in_registry() or self._default_windows_location() + elif self.platform == "java" and os._name == "nt": start_cmd = self._default_windows_location() else: for ffname in ["firefox", "iceweasel"]: @@ -176,12 +179,15 @@ def _get_firefox_start_cmd(self): # couldn't find firefox on the system path raise RuntimeError( "Could not find firefox in your system PATH." - " Please specify the firefox binary location or install firefox") + " Please specify the firefox binary location or install firefox" + ) return start_cmd def _default_windows_location(self): - program_files = [os.getenv("PROGRAMFILES", r"C:\Program Files"), - os.getenv("PROGRAMFILES(X86)", r"C:\Program Files (x86)")] + program_files = [ + os.getenv("PROGRAMFILES", r"C:\Program Files"), + os.getenv("PROGRAMFILES(X86)", r"C:\Program Files (x86)"), + ] for path in program_files: binary_path = os.path.join(path, r"Mozilla Firefox\firefox.exe") if os.access(binary_path, os.X_OK): @@ -189,15 +195,14 @@ def _default_windows_location(self): return "" def _modify_link_library_path(self): - existing_ld_lib_path = os.environ.get('LD_LIBRARY_PATH', '') + existing_ld_lib_path = os.environ.get("LD_LIBRARY_PATH", "") - new_ld_lib_path = self._extract_and_check( - self.profile, self.NO_FOCUS_LIBRARY_NAME, "x86", "amd64") + new_ld_lib_path = self._extract_and_check(self.profile, self.NO_FOCUS_LIBRARY_NAME, "x86", "amd64") new_ld_lib_path += existing_ld_lib_path self._firefox_env["LD_LIBRARY_PATH"] = new_ld_lib_path - self._firefox_env['LD_PRELOAD'] = self.NO_FOCUS_LIBRARY_NAME + self._firefox_env["LD_PRELOAD"] = self.NO_FOCUS_LIBRARY_NAME def _extract_and_check(self, profile, no_focus_so_name, x86, amd64): @@ -208,11 +213,8 @@ def _extract_and_check(self, profile, no_focus_so_name, x86, amd64): if not os.path.exists(library_path): os.makedirs(library_path) import shutil - shutil.copy(os.path.join( - os.path.dirname(__file__), - path, - self.NO_FOCUS_LIBRARY_NAME), - library_path) + + shutil.copy(os.path.join(os.path.dirname(__file__), path, self.NO_FOCUS_LIBRARY_NAME), library_path) built_path += library_path + ":" return built_path @@ -220,7 +222,7 @@ def _extract_and_check(self, profile, no_focus_so_name, x86, amd64): def which(self, fname): """Returns the fully qualified path by searching Path of the given name""" - for pe in os.environ['PATH'].split(os.pathsep): + for pe in os.environ["PATH"].split(os.pathsep): checkname = os.path.join(pe, fname) if os.access(checkname, os.X_OK) and not os.path.isdir(checkname): return checkname diff --git a/py/selenium/webdriver/firefox/firefox_profile.py b/py/selenium/webdriver/firefox/firefox_profile.py index 3e83400cef831..9760f1dbff5d0 100644 --- a/py/selenium/webdriver/firefox/firefox_profile.py +++ b/py/selenium/webdriver/firefox/firefox_profile.py @@ -54,15 +54,16 @@ def __init__(self, profile_directory=None): This defaults to None and will create a new directory when object is created. """ - warnings.warn('firefox_profile has been deprecated, please use an Options object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "firefox_profile has been deprecated, please use an Options object", DeprecationWarning, stacklevel=2 + ) if not FirefoxProfile.DEFAULT_PREFERENCES: - with open(os.path.join(os.path.dirname(__file__), - WEBDRIVER_PREFERENCES), encoding='utf-8') as default_prefs: + with open( + os.path.join(os.path.dirname(__file__), WEBDRIVER_PREFERENCES), encoding="utf-8" + ) as default_prefs: FirefoxProfile.DEFAULT_PREFERENCES = json.load(default_prefs) - self.default_preferences = copy.deepcopy( - FirefoxProfile.DEFAULT_PREFERENCES['mutable']) + self.default_preferences = copy.deepcopy(FirefoxProfile.DEFAULT_PREFERENCES["mutable"]) self.profile_dir = profile_directory self.tempfolder = None if not self.profile_dir: @@ -70,8 +71,9 @@ def __init__(self, profile_directory=None): else: self.tempfolder = tempfile.mkdtemp() newprof = os.path.join(self.tempfolder, "webdriver-py-profilecopy") - shutil.copytree(self.profile_dir, newprof, - ignore=shutil.ignore_patterns("parent.lock", "lock", ".parentlock")) + shutil.copytree( + self.profile_dir, newprof, ignore=shutil.ignore_patterns("parent.lock", "lock", ".parentlock") + ) self.profile_dir = newprof os.chmod(self.profile_dir, 0o755) self._read_existing_userjs(os.path.join(self.profile_dir, "user.js")) @@ -91,7 +93,7 @@ def add_extension(self, extension=WEBDRIVER_EXT): self._install_extension(extension) def update_preferences(self): - for key, value in FirefoxProfile.DEFAULT_PREFERENCES['frozen'].items(): + for key, value in FirefoxProfile.DEFAULT_PREFERENCES["frozen"].items(): # Do not update key that is being set by the user using # set_preference as users are unaware of the freeze properties # and it leads to an inconsistent behavior @@ -160,13 +162,13 @@ def encoded(self) -> str: """ self.update_preferences() fp = BytesIO() - with zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED) as zipped: + with zipfile.ZipFile(fp, "w", zipfile.ZIP_DEFLATED) as zipped: path_root = len(self.path) + 1 # account for trailing slash for base, _, files in os.walk(self.path): for fyle in files: filename = os.path.join(base, fyle) zipped.write(filename, filename[path_root:]) - return base64.b64encode(fp.getvalue()).decode('UTF-8') + return base64.b64encode(fp.getvalue()).decode("UTF-8") def _create_tempfolder(self): """ @@ -178,7 +180,7 @@ def _write_user_prefs(self, user_prefs): """ writes the current user prefs dictionary to disk """ - with open(self.userPrefs, "w", encoding='utf-8') as f: + with open(self.userPrefs, "w", encoding="utf-8") as f: for key, value in user_prefs.items(): f.write(f'user_pref("{key}", {json.dumps(value)});\n') @@ -186,58 +188,60 @@ def _read_existing_userjs(self, userjs): pref_pattern = re.compile(r'user_pref\("(.*)",\s(.*)\)') try: - with open(userjs, encoding='utf-8') as f: + with open(userjs, encoding="utf-8") as f: for usr in f: matches = pref_pattern.search(usr) try: self.default_preferences[matches.group(1)] = json.loads(matches.group(2)) except Exception: - warnings.warn("(skipping) failed to json.loads existing preference: %s" % - matches.group(1) + matches.group(2)) + warnings.warn( + "(skipping) failed to json.loads existing preference: %s" % matches.group(1) + + matches.group(2) + ) except Exception: # The profile given hasn't had any changes made, i.e no users.js pass def _install_extension(self, addon, unpack=True): """ - Installs addon from a filepath, url - or directory of addons in the profile. - - path: url, absolute path to .xpi, or directory of addons - - unpack: whether to unpack unless specified otherwise in the install.rdf + Installs addon from a filepath, url + or directory of addons in the profile. + - path: url, absolute path to .xpi, or directory of addons + - unpack: whether to unpack unless specified otherwise in the install.rdf """ if addon == WEBDRIVER_EXT: addon = os.path.join(os.path.dirname(__file__), WEBDRIVER_EXT) tmpdir = None xpifile = None - if addon.endswith('.xpi'): - tmpdir = tempfile.mkdtemp(suffix='.' + os.path.split(addon)[-1]) - compressed_file = zipfile.ZipFile(addon, 'r') + if addon.endswith(".xpi"): + tmpdir = tempfile.mkdtemp(suffix="." + os.path.split(addon)[-1]) + compressed_file = zipfile.ZipFile(addon, "r") for name in compressed_file.namelist(): - if name.endswith('/'): + if name.endswith("/"): if not os.path.isdir(os.path.join(tmpdir, name)): os.makedirs(os.path.join(tmpdir, name)) else: if not os.path.isdir(os.path.dirname(os.path.join(tmpdir, name))): os.makedirs(os.path.dirname(os.path.join(tmpdir, name))) data = compressed_file.read(name) - with open(os.path.join(tmpdir, name), 'wb') as f: + with open(os.path.join(tmpdir, name), "wb") as f: f.write(data) xpifile = addon addon = tmpdir # determine the addon id addon_details = self._addon_details(addon) - addon_id = addon_details.get('id') - assert addon_id, 'The addon id could not be found: %s' % addon + addon_id = addon_details.get("id") + assert addon_id, "The addon id could not be found: %s" % addon # copy the addon to the profile addon_path = os.path.join(self.extensionsDir, addon_id) - if not unpack and not addon_details['unpack'] and xpifile: + if not unpack and not addon_details["unpack"] and xpifile: if not os.path.exists(self.extensionsDir): os.makedirs(self.extensionsDir) os.chmod(self.extensionsDir, 0o755) - shutil.copy(xpifile, addon_path + '.xpi') + shutil.copy(xpifile, addon_path + ".xpi") else: if not os.path.exists(addon_path): shutil.copytree(addon, addon_path, symlinks=True) @@ -260,12 +264,7 @@ def _addon_details(self, addon_path): 'unpack': False } # whether to unpack the addon """ - details = { - 'id': None, - 'unpack': False, - 'name': None, - 'version': None - } + details = {"id": None, "unpack": False, "name": None, "version": None} def get_namespace_id(doc, url): attributes = doc.documentElement.attributes @@ -274,7 +273,7 @@ def get_namespace_id(doc, url): if attributes.item(i).value == url: if ":" in attributes.item(i).name: # If the namespace is not the default one remove 'xlmns:' - namespace = attributes.item(i).name.split(':')[1] + ":" + namespace = attributes.item(i).name.split(":")[1] + ":" break return namespace @@ -284,24 +283,24 @@ def get_text(element): for node in element.childNodes: if node.nodeType == node.TEXT_NODE: rc.append(node.data) - return ''.join(rc).strip() + return "".join(rc).strip() def parse_manifest_json(content): """Extracts the details from the contents of a WebExtensions `manifest.json` file.""" manifest = json.loads(content) try: - id = manifest['applications']['gecko']['id'] + id = manifest["applications"]["gecko"]["id"] except KeyError: - id = manifest['name'].replace(" ", "") + "@" + manifest['version'] + id = manifest["name"].replace(" ", "") + "@" + manifest["version"] return { - 'id': id, - 'version': manifest['version'], - 'name': manifest['version'], - 'unpack': False, + "id": id, + "version": manifest["version"], + "name": manifest["version"], + "unpack": False, } if not os.path.exists(addon_path): - raise OSError('Add-on path does not exist: %s' % addon_path) + raise OSError("Add-on path does not exist: %s" % addon_path) try: if zipfile.is_zipfile(addon_path): @@ -309,23 +308,23 @@ def parse_manifest_json(content): # it will cause an exception thrown in Python 2.6. # TODO: use with statement when Python 2.x is no longer supported try: - compressed_file = zipfile.ZipFile(addon_path, 'r') - if 'manifest.json' in compressed_file.namelist(): - return parse_manifest_json(compressed_file.read('manifest.json')) + compressed_file = zipfile.ZipFile(addon_path, "r") + if "manifest.json" in compressed_file.namelist(): + return parse_manifest_json(compressed_file.read("manifest.json")) - manifest = compressed_file.read('install.rdf') + manifest = compressed_file.read("install.rdf") finally: compressed_file.close() elif os.path.isdir(addon_path): - manifest_json_filename = os.path.join(addon_path, 'manifest.json') + manifest_json_filename = os.path.join(addon_path, "manifest.json") if os.path.exists(manifest_json_filename): - with open(manifest_json_filename, encoding='utf-8') as f: + with open(manifest_json_filename, encoding="utf-8") as f: return parse_manifest_json(f.read()) - with open(os.path.join(addon_path, 'install.rdf'), encoding='utf-8') as f: + with open(os.path.join(addon_path, "install.rdf"), encoding="utf-8") as f: manifest = f.read() else: - raise OSError('Add-on path is neither an XPI nor a directory: %s' % addon_path) + raise OSError("Add-on path is neither an XPI nor a directory: %s" % addon_path) except (OSError, KeyError) as e: raise AddonFormatError(str(e), sys.exc_info()[2]) @@ -333,31 +332,31 @@ def parse_manifest_json(content): doc = minidom.parseString(manifest) # Get the namespaces abbreviations - em = get_namespace_id(doc, 'http://www.mozilla.org/2004/em-rdf#') - rdf = get_namespace_id(doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#') + em = get_namespace_id(doc, "http://www.mozilla.org/2004/em-rdf#") + rdf = get_namespace_id(doc, "http://www.w3.org/1999/02/22-rdf-syntax-ns#") - description = doc.getElementsByTagName(rdf + 'Description').item(0) + description = doc.getElementsByTagName(rdf + "Description").item(0) if not description: - description = doc.getElementsByTagName('Description').item(0) + description = doc.getElementsByTagName("Description").item(0) for node in description.childNodes: # Remove the namespace prefix from the tag for comparison entry = node.nodeName.replace(em, "") if entry in details: details.update({entry: get_text(node)}) - if not details.get('id'): + if not details.get("id"): for i in range(description.attributes.length): attribute = description.attributes.item(i) - if attribute.name == em + 'id': - details.update({'id': attribute.value}) + if attribute.name == em + "id": + details.update({"id": attribute.value}) except Exception as e: raise AddonFormatError(str(e), sys.exc_info()[2]) # turn unpack into a true/false value - if isinstance(details['unpack'], str): - details['unpack'] = details['unpack'].lower() == 'true' + if isinstance(details["unpack"], str): + details["unpack"] = details["unpack"].lower() == "true" # If no ID is set, the add-on is invalid - if not details.get('id'): - raise AddonFormatError('Add-on id could not be found.') + if not details.get("id"): + raise AddonFormatError("Add-on id could not be found.") return details diff --git a/py/selenium/webdriver/firefox/options.py b/py/selenium/webdriver/firefox/options.py index d600c0d9daf52..4c8792a1632d3 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 """ + """Sets the location of the browser binary by string""" self.binary = value @property @@ -86,11 +86,7 @@ def profile(self) -> FirefoxProfile: :Returns: The Firefox profile to use. """ if self._profile: - warnings.warn( - "Getting a profile has been deprecated.", - DeprecationWarning, - stacklevel=2 - ) + warnings.warn("Getting a profile has been deprecated.", DeprecationWarning, stacklevel=2) return self._profile @profile.setter @@ -102,7 +98,7 @@ def profile(self, new_profile: Union[str, FirefoxProfile]) -> None: warnings.warn( "Setting a profile has been deprecated. Please use the set_preference and install_addons methods", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) if not isinstance(new_profile, FirefoxProfile): new_profile = FirefoxProfile(new_profile) @@ -113,7 +109,7 @@ def headless(self) -> bool: """ :Returns: True if the headless argument is set, else False """ - return '-headless' in self._arguments + return "-headless" in self._arguments @headless.setter def headless(self, value: bool) -> None: @@ -124,9 +120,9 @@ def headless(self, value: bool) -> None: value: boolean value indicating to set the headless option """ if value: - self._arguments.append('-headless') - elif '-headless' in self._arguments: - self._arguments.remove('-headless') + self._arguments.append("-headless") + 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): super().enable_mobile(android_package, android_activity, device_serial) diff --git a/py/selenium/webdriver/firefox/remote_connection.py b/py/selenium/webdriver/firefox/remote_connection.py index 927b80a4fa02e..8df6ae9fbb818 100644 --- a/py/selenium/webdriver/firefox/remote_connection.py +++ b/py/selenium/webdriver/firefox/remote_connection.py @@ -21,16 +21,13 @@ class FirefoxRemoteConnection(RemoteConnection): - browser_name = DesiredCapabilities.FIREFOX['browserName'] + browser_name = DesiredCapabilities.FIREFOX["browserName"] def __init__(self, remote_server_addr, keep_alive=True, ignore_proxy=False): super().__init__(remote_server_addr, keep_alive, ignore_proxy=ignore_proxy) - self._commands["GET_CONTEXT"] = ('GET', '/session/$sessionId/moz/context') + self._commands["GET_CONTEXT"] = ("GET", "/session/$sessionId/moz/context") self._commands["SET_CONTEXT"] = ("POST", "/session/$sessionId/moz/context") - self._commands["INSTALL_ADDON"] = \ - ("POST", "/session/$sessionId/moz/addon/install") - self._commands["UNINSTALL_ADDON"] = \ - ("POST", "/session/$sessionId/moz/addon/uninstall") - self._commands["FULL_PAGE_SCREENSHOT"] = \ - ("GET", "/session/$sessionId/moz/screenshot/full") + self._commands["INSTALL_ADDON"] = ("POST", "/session/$sessionId/moz/addon/install") + self._commands["UNINSTALL_ADDON"] = ("POST", "/session/$sessionId/moz/addon/uninstall") + self._commands["FULL_PAGE_SCREENSHOT"] = ("GET", "/session/$sessionId/moz/screenshot/full") diff --git a/py/selenium/webdriver/firefox/service.py b/py/selenium/webdriver/firefox/service.py index 85e9c4914df7d..45ed1bd615711 100644 --- a/py/selenium/webdriver/firefox/service.py +++ b/py/selenium/webdriver/firefox/service.py @@ -27,9 +27,14 @@ class Service(service.Service): """Object that manages the starting and stopping of the GeckoDriver.""" - def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, - port: int = 0, service_args: List[str] = None, - log_path: str = "geckodriver.log", env: dict = None): + def __init__( + self, + executable_path: str = DEFAULT_EXECUTABLE_PATH, + port: int = 0, + service_args: List[str] = None, + log_path: str = "geckodriver.log", + env: dict = None, + ): """Creates a new instance of the GeckoDriver remote service proxy. GeckoDriver provides a HTTP interface speaking the W3C WebDriver @@ -47,13 +52,13 @@ def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, in the services' environment. """ - log_file = open(log_path, "a+", encoding='utf-8') if log_path else None + log_file = open(log_path, "a+", encoding="utf-8") if log_path else None super().__init__(executable_path, port=port, log_file=log_file, env=env) self.service_args = service_args or [] # Set a port for CDP - if '--connect-existing' not in self.service_args: + if "--connect-existing" not in self.service_args: self.service_args.append("--websocket-port") self.service_args.append("%d" % utils.free_port()) diff --git a/py/selenium/webdriver/firefox/webdriver.py b/py/selenium/webdriver/firefox/webdriver.py index 17180abe5ff0c..9336a1ca6b747 100644 --- a/py/selenium/webdriver/firefox/webdriver.py +++ b/py/selenium/webdriver/firefox/webdriver.py @@ -42,12 +42,21 @@ class WebDriver(RemoteWebDriver): CONTEXT_CHROME = "chrome" CONTEXT_CONTENT = "content" - def __init__(self, firefox_profile=None, firefox_binary=None, - capabilities=None, proxy=None, - executable_path=DEFAULT_EXECUTABLE_PATH, options=None, - service_log_path=DEFAULT_SERVICE_LOG_PATH, - service_args=None, service=None, desired_capabilities=None, - log_path=DEFAULT_LOG_PATH, keep_alive=True): + def __init__( + self, + firefox_profile=None, + firefox_binary=None, + capabilities=None, + proxy=None, + executable_path=DEFAULT_EXECUTABLE_PATH, + options=None, + service_log_path=DEFAULT_SERVICE_LOG_PATH, + service_args=None, + service=None, + desired_capabilities=None, + log_path=DEFAULT_LOG_PATH, + keep_alive=True, + ): """Starts a new local session of Firefox. Based on the combination and specificity of the various keyword @@ -99,31 +108,44 @@ def __init__(self, firefox_profile=None, firefox_binary=None, """ if executable_path != DEFAULT_EXECUTABLE_PATH: - warnings.warn('executable_path has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "executable_path has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) if capabilities or desired_capabilities: - warnings.warn('capabilities and desired_capabilities have been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "capabilities and desired_capabilities have been deprecated, please pass in a Service object", + DeprecationWarning, + stacklevel=2, + ) if firefox_binary: - warnings.warn('firefox_binary has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "firefox_binary has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) self.binary = None if firefox_profile: - warnings.warn('firefox_profile has been deprecated, please pass in an Options object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "firefox_profile has been deprecated, please pass in an Options object", + DeprecationWarning, + stacklevel=2, + ) self.profile = None if log_path != DEFAULT_LOG_PATH: - warnings.warn('log_path has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "log_path has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) # Service Arguments being deprecated. 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) + warnings.warn( + "service_log_path has been deprecated, please pass in a Service object", + DeprecationWarning, + stacklevel=2, + ) if service_args: - warnings.warn('service_args has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "service_args has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) self.service = service @@ -166,19 +188,13 @@ def __init__(self, firefox_profile=None, firefox_binary=None, options.accept_insecure_certs = False if not self.service: - self.service = Service( - executable_path, - service_args=service_args, - log_path=service_log_path) + self.service = Service(executable_path, service_args=service_args, log_path=service_log_path) self.service.start() executor = FirefoxRemoteConnection( - remote_server_addr=self.service.service_url, - ignore_proxy=options._ignore_local_proxy) - super().__init__( - command_executor=executor, - options=options, - keep_alive=True) + remote_server_addr=self.service.service_url, ignore_proxy=options._ignore_local_proxy + ) + super().__init__(command_executor=executor, options=options, keep_alive=True) self._is_remote = False @@ -224,7 +240,7 @@ def context(self, context): # chrome scope ... do stuff ... """ - initial_context = self.execute('GET_CONTEXT').pop('value') + initial_context = self.execute("GET_CONTEXT").pop("value") self.set_context(context) try: yield @@ -249,15 +265,15 @@ def install_addon(self, path, temporary=False) -> str: if os.path.isdir(path): fp = BytesIO() path_root = len(path) + 1 # account for trailing slash - with zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED) as zipped: + with zipfile.ZipFile(fp, "w", zipfile.ZIP_DEFLATED) as zipped: for base, dirs, files in os.walk(path): for fyle in files: filename = os.path.join(base, fyle) zipped.write(filename, filename[path_root:]) - addon = base64.b64encode(fp.getvalue()).decode('UTF-8') + addon = base64.b64encode(fp.getvalue()).decode("UTF-8") else: - with open(path, 'rb') as file: - addon = (base64.b64encode(file.read()).decode('UTF-8')) + with open(path, "rb") as file: + addon = base64.b64encode(file.read()).decode("UTF-8") payload = {"addon": addon, "temporary": temporary} return self.execute("INSTALL_ADDON", payload)["value"] @@ -288,12 +304,14 @@ def get_full_page_screenshot_as_file(self, filename) -> bool: driver.get_full_page_screenshot_as_file('/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) + 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.get_full_page_screenshot_as_png() try: - with open(filename, 'wb') as f: + with open(filename, "wb") as f: f.write(png) except OSError: return False @@ -327,7 +345,7 @@ def get_full_page_screenshot_as_png(self) -> str: driver.get_full_page_screenshot_as_png() """ - return base64.b64decode(self.get_full_page_screenshot_as_base64().encode('ascii')) + return base64.b64decode(self.get_full_page_screenshot_as_base64().encode("ascii")) def get_full_page_screenshot_as_base64(self) -> str: """ @@ -339,4 +357,4 @@ def get_full_page_screenshot_as_base64(self) -> str: driver.get_full_page_screenshot_as_base64() """ - return self.execute("FULL_PAGE_SCREENSHOT")['value'] + return self.execute("FULL_PAGE_SCREENSHOT")["value"] diff --git a/py/selenium/webdriver/ie/options.py b/py/selenium/webdriver/ie/options.py index 1d40c919bf572..4c443bb879146 100644 --- a/py/selenium/webdriver/ie/options.py +++ b/py/selenium/webdriver/ie/options.py @@ -27,26 +27,26 @@ class ElementScrollBehavior(Enum): class Options(ArgOptions): - KEY = 'se:ieOptions' - SWITCHES = 'ie.browserCommandLineSwitches' - - BROWSER_ATTACH_TIMEOUT = 'browserAttachTimeout' - ELEMENT_SCROLL_BEHAVIOR = 'elementScrollBehavior' - ENSURE_CLEAN_SESSION = 'ie.ensureCleanSession' - FILE_UPLOAD_DIALOG_TIMEOUT = 'ie.fileUploadDialogTimeout' - FORCE_CREATE_PROCESS_API = 'ie.forceCreateProcessApi' - FORCE_SHELL_WINDOWS_API = 'ie.forceShellWindowsApi' - FULL_PAGE_SCREENSHOT = 'ie.enableFullPageScreenshot' - IGNORE_PROTECTED_MODE_SETTINGS = 'ignoreProtectedModeSettings' - IGNORE_ZOOM_LEVEL = 'ignoreZoomSetting' - INITIAL_BROWSER_URL = 'initialBrowserUrl' - NATIVE_EVENTS = 'nativeEvents' - PERSISTENT_HOVER = 'enablePersistentHover' - REQUIRE_WINDOW_FOCUS = 'requireWindowFocus' - USE_PER_PROCESS_PROXY = 'ie.usePerProcessProxy' - USE_LEGACY_FILE_UPLOAD_DIALOG_HANDLING = 'ie.useLegacyFileUploadDialogHandling' - ATTACH_TO_EDGE_CHROME = 'ie.edgechromium' - EDGE_EXECUTABLE_PATH = 'ie.edgepath' + KEY = "se:ieOptions" + SWITCHES = "ie.browserCommandLineSwitches" + + BROWSER_ATTACH_TIMEOUT = "browserAttachTimeout" + ELEMENT_SCROLL_BEHAVIOR = "elementScrollBehavior" + ENSURE_CLEAN_SESSION = "ie.ensureCleanSession" + FILE_UPLOAD_DIALOG_TIMEOUT = "ie.fileUploadDialogTimeout" + FORCE_CREATE_PROCESS_API = "ie.forceCreateProcessApi" + FORCE_SHELL_WINDOWS_API = "ie.forceShellWindowsApi" + FULL_PAGE_SCREENSHOT = "ie.enableFullPageScreenshot" + IGNORE_PROTECTED_MODE_SETTINGS = "ignoreProtectedModeSettings" + IGNORE_ZOOM_LEVEL = "ignoreZoomSetting" + INITIAL_BROWSER_URL = "initialBrowserUrl" + NATIVE_EVENTS = "nativeEvents" + PERSISTENT_HOVER = "enablePersistentHover" + REQUIRE_WINDOW_FOCUS = "requireWindowFocus" + USE_PER_PROCESS_PROXY = "ie.usePerProcessProxy" + USE_LEGACY_FILE_UPLOAD_DIALOG_HANDLING = "ie.useLegacyFileUploadDialogHandling" + ATTACH_TO_EDGE_CHROME = "ie.edgechromium" + EDGE_EXECUTABLE_PATH = "ie.edgepath" def __init__(self): super().__init__() @@ -55,7 +55,7 @@ def __init__(self): @property def options(self) -> dict: - """:Returns: A dictionary of browser options """ + """:Returns: A dictionary of browser options""" return self._options @property @@ -75,12 +75,12 @@ def browser_attach_timeout(self, value: int) -> None: """ if not isinstance(value, int): - raise ValueError('Browser Attach Timeout must be an integer.') + raise ValueError("Browser Attach Timeout must be an integer.") self._options[self.BROWSER_ATTACH_TIMEOUT] = value @property def element_scroll_behavior(self) -> ElementScrollBehavior: - """:Returns: The options Element Scroll Behavior value """ + """:Returns: The options Element Scroll Behavior value""" return self._options.get(self.ELEMENT_SCROLL_BEHAVIOR) @element_scroll_behavior.setter @@ -93,12 +93,12 @@ def element_scroll_behavior(self, value: ElementScrollBehavior) -> None: """ if value not in [ElementScrollBehavior.TOP, ElementScrollBehavior.BOTTOM]: - raise ValueError('Element Scroll Behavior out of range.') + raise ValueError("Element Scroll Behavior out of range.") self._options[self.ELEMENT_SCROLL_BEHAVIOR] = value @property def ensure_clean_session(self) -> bool: - """:Returns: The options Ensure Clean Session value """ + """:Returns: The options Ensure Clean Session value""" return self._options.get(self.ENSURE_CLEAN_SESSION) @ensure_clean_session.setter @@ -114,7 +114,7 @@ def ensure_clean_session(self, value: bool) -> None: @property def file_upload_dialog_timeout(self) -> int: - """:Returns: The options File Upload Dialog Timeout in milliseconds """ + """:Returns: The options File Upload Dialog Timeout in milliseconds""" return self._options.get(self.FILE_UPLOAD_DIALOG_TIMEOUT) @file_upload_dialog_timeout.setter @@ -127,12 +127,12 @@ def file_upload_dialog_timeout(self, value: int) -> None: """ if not isinstance(value, int): - raise ValueError('File Upload Dialog Timeout must be an integer.') + raise ValueError("File Upload Dialog Timeout must be an integer.") self._options[self.FILE_UPLOAD_DIALOG_TIMEOUT] = value @property def force_create_process_api(self) -> bool: - """:Returns: The options Force Create Process Api value """ + """:Returns: The options Force Create Process Api value""" return self._options.get(self.FORCE_CREATE_PROCESS_API) @force_create_process_api.setter @@ -148,7 +148,7 @@ def force_create_process_api(self, value: bool) -> None: @property def force_shell_windows_api(self) -> bool: - """:Returns: The options Force Shell Windows Api value """ + """:Returns: The options Force Shell Windows Api value""" return self._options.get(self.FORCE_SHELL_WINDOWS_API) @force_shell_windows_api.setter @@ -164,7 +164,7 @@ def force_shell_windows_api(self, value: bool) -> None: @property def full_page_screenshot(self) -> bool: - """:Returns: The options Full Page Screenshot value """ + """:Returns: The options Full Page Screenshot value""" return self._options.get(self.FULL_PAGE_SCREENSHOT) @full_page_screenshot.setter @@ -180,7 +180,7 @@ def full_page_screenshot(self, value: bool) -> None: @property def ignore_protected_mode_settings(self) -> bool: - """:Returns: The options Ignore Protected Mode Settings value """ + """:Returns: The options Ignore Protected Mode Settings value""" return self._options.get(self.IGNORE_PROTECTED_MODE_SETTINGS) @ignore_protected_mode_settings.setter @@ -196,7 +196,7 @@ def ignore_protected_mode_settings(self, value: bool) -> None: @property def ignore_zoom_level(self) -> bool: - """:Returns: The options Ignore Zoom Level value """ + """:Returns: The options Ignore Zoom Level value""" return self._options.get(self.IGNORE_ZOOM_LEVEL) @ignore_zoom_level.setter @@ -212,7 +212,7 @@ def ignore_zoom_level(self, value: bool) -> None: @property def initial_browser_url(self) -> str: - """:Returns: The options Initial Browser Url value """ + """:Returns: The options Initial Browser Url value""" return self._options.get(self.INITIAL_BROWSER_URL) @initial_browser_url.setter @@ -228,7 +228,7 @@ def initial_browser_url(self, value: str) -> None: @property def native_events(self) -> bool: - """:Returns: The options Native Events value """ + """:Returns: The options Native Events value""" return self._options.get(self.NATIVE_EVENTS) @native_events.setter @@ -244,7 +244,7 @@ def native_events(self, value: bool) -> None: @property def persistent_hover(self) -> bool: - """:Returns: The options Persistent Hover value """ + """:Returns: The options Persistent Hover value""" return self._options.get(self.PERSISTENT_HOVER) @persistent_hover.setter @@ -260,7 +260,7 @@ def persistent_hover(self, value: bool) -> None: @property def require_window_focus(self: bool): - """:Returns: The options Require Window Focus value """ + """:Returns: The options Require Window Focus value""" return self._options.get(self.REQUIRE_WINDOW_FOCUS) @require_window_focus.setter @@ -276,7 +276,7 @@ def require_window_focus(self, value: bool) -> None: @property def use_per_process_proxy(self) -> bool: - """:Returns: The options User Per Process Proxy value """ + """:Returns: The options User Per Process Proxy value""" return self._options.get(self.USE_PER_PROCESS_PROXY) @use_per_process_proxy.setter @@ -292,7 +292,7 @@ def use_per_process_proxy(self, value: bool) -> None: @property def use_legacy_file_upload_dialog_handling(self) -> bool: - """:Returns: The options Use Legacy File Upload Dialog Handling value """ + """:Returns: The options Use Legacy File Upload Dialog Handling value""" return self._options.get(self.USE_LEGACY_FILE_UPLOAD_DIALOG_HANDLING) @use_legacy_file_upload_dialog_handling.setter @@ -308,7 +308,7 @@ def use_legacy_file_upload_dialog_handling(self, value: bool) -> None: @property def attach_to_edge_chrome(self) -> bool: - """:Returns: The options Attach to Edge Chrome value """ + """:Returns: The options Attach to Edge Chrome value""" return self._options.get(self.ATTACH_TO_EDGE_CHROME) @attach_to_edge_chrome.setter @@ -324,7 +324,7 @@ def attach_to_edge_chrome(self, value: bool) -> None: @property def edge_executable_path(self) -> str: - """:Returns: The options Edge Executable Path value """ + """:Returns: The options Edge Executable Path value""" return self._options.get(self.EDGE_EXECUTABLE_PATH) @edge_executable_path.setter @@ -340,7 +340,7 @@ def edge_executable_path(self, value: str) -> None: @property def additional_options(self) -> dict: - """:Returns: The additional options """ + """:Returns: The additional options""" return self._additional def add_additional_option(self, name: str, value): @@ -360,7 +360,7 @@ def to_capabilities(self) -> dict: opts = self._options.copy() if len(self._arguments) > 0: - opts[self.SWITCHES] = ' '.join(self._arguments) + opts[self.SWITCHES] = " ".join(self._arguments) if len(self._additional) > 0: opts.update(self._additional) diff --git a/py/selenium/webdriver/ie/service.py b/py/selenium/webdriver/ie/service.py index ee8a1cc56ddac..25579c967503d 100644 --- a/py/selenium/webdriver/ie/service.py +++ b/py/selenium/webdriver/ie/service.py @@ -19,7 +19,7 @@ from selenium.webdriver.common import service -DEFAULT_EXECUTABLE_PATH = 'IEDriverServer.exe' +DEFAULT_EXECUTABLE_PATH = "IEDriverServer.exe" class Service(service.Service): @@ -27,9 +27,14 @@ class Service(service.Service): Object that manages the starting and stopping of the IEDriver """ - def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, - port: int = 0, host: str = None, - log_level: str = None, log_file: str = None): + def __init__( + self, + executable_path: str = DEFAULT_EXECUTABLE_PATH, + port: int = 0, + host: str = None, + log_level: str = None, + log_file: str = None, + ): """ Creates a new instance of the Service @@ -49,8 +54,11 @@ def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, if log_file: self.service_args.append("--log-file=%s" % log_file) - super().__init__(executable_path, port=port, - start_error_message="Please download from https://www.selenium.dev/downloads/ and read up at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver") + super().__init__( + executable_path, + port=port, + start_error_message="Please download from https://www.selenium.dev/downloads/ and read up at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver", + ) def command_line_args(self) -> List[str]: return ["--port=%d" % self.port] + self.service_args diff --git a/py/selenium/webdriver/ie/webdriver.py b/py/selenium/webdriver/ie/webdriver.py index 576f51b57e060..bc11c7104e416 100644 --- a/py/selenium/webdriver/ie/webdriver.py +++ b/py/selenium/webdriver/ie/webdriver.py @@ -33,13 +33,22 @@ class WebDriver(RemoteWebDriver): - """ Controls the IEServerDriver and allows you to drive Internet Explorer """ - - def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, capabilities=None, - port=DEFAULT_PORT, timeout=DEFAULT_TIMEOUT, host=DEFAULT_HOST, - log_level=DEFAULT_LOG_LEVEL, service_log_path=DEFAULT_SERVICE_LOG_PATH, - options: Options = None, service: Service = None, - desired_capabilities=None, keep_alive=DEFAULT_KEEP_ALIVE): + """Controls the IEServerDriver and allows you to drive Internet Explorer""" + + def __init__( + self, + executable_path=DEFAULT_EXECUTABLE_PATH, + capabilities=None, + port=DEFAULT_PORT, + timeout=DEFAULT_TIMEOUT, + host=DEFAULT_HOST, + log_level=DEFAULT_LOG_LEVEL, + service_log_path=DEFAULT_SERVICE_LOG_PATH, + options: Options = None, + service: Service = None, + desired_capabilities=None, + keep_alive=DEFAULT_KEEP_ALIVE, + ): """ Creates a new instance of the Ie driver. @@ -57,35 +66,45 @@ def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, capabilities=None, - desired_capabilities - Deprecated: alias of capabilities; this will make the signature consistent with RemoteWebDriver. - keep_alive - Deprecated: Whether to configure RemoteConnection to use HTTP keep-alive. """ - if executable_path != 'IEDriverServer.exe': - warnings.warn('executable_path has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + if executable_path != "IEDriverServer.exe": + warnings.warn( + "executable_path has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) if capabilities: - warnings.warn('capabilities has been deprecated, please pass in an Options object.' - 'This field will be ignored.', - DeprecationWarning, stacklevel=2) + warnings.warn( + "capabilities has been deprecated, please pass in an Options object." "This field will be ignored.", + DeprecationWarning, + stacklevel=2, + ) if port != DEFAULT_PORT: - warnings.warn('port has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn("port has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2) if timeout != DEFAULT_TIMEOUT: - warnings.warn('timeout has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "timeout has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) if host != DEFAULT_HOST: - warnings.warn('host has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn("host has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2) if log_level != DEFAULT_LOG_LEVEL: - warnings.warn('log_level has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "log_level has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) 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) + warnings.warn( + "service_log_path has been deprecated, please pass in a Service object", + DeprecationWarning, + stacklevel=2, + ) if desired_capabilities: - warnings.warn('desired_capabilities has been deprecated, please pass in an Options object.' - 'This field will be ignored', - DeprecationWarning, stacklevel=2) + warnings.warn( + "desired_capabilities has been deprecated, please pass in an Options object." + "This field will be ignored", + DeprecationWarning, + stacklevel=2, + ) if keep_alive != DEFAULT_KEEP_ALIVE: - warnings.warn('keep_alive has been deprecated, please pass in a Service object', - DeprecationWarning, stacklevel=2) + warnings.warn( + "keep_alive has been deprecated, please pass in a Service object", DeprecationWarning, stacklevel=2 + ) else: keep_alive = True @@ -101,18 +120,12 @@ def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, capabilities=None, self.iedriver = service else: self.iedriver = Service( - executable_path, - port=self.port, - host=self.host, - log_level=log_level, - log_file=service_log_path) + executable_path, port=self.port, host=self.host, log_level=log_level, log_file=service_log_path + ) self.iedriver.start() - super().__init__( - command_executor=self.iedriver.service_url, - options=options, - keep_alive=keep_alive) + super().__init__(command_executor=self.iedriver.service_url, options=options, keep_alive=keep_alive) self._is_remote = False def quit(self) -> None: diff --git a/py/selenium/webdriver/remote/bidi_connection.py b/py/selenium/webdriver/remote/bidi_connection.py index 3018d03bc5a4d..da3df0da6f0ad 100644 --- a/py/selenium/webdriver/remote/bidi_connection.py +++ b/py/selenium/webdriver/remote/bidi_connection.py @@ -15,8 +15,8 @@ # specific language governing permissions and limitations # under the License. -class BidiConnection: +class BidiConnection: def __init__(self, session, cdp, devtools_import) -> None: self.session = session self.cdp = cdp diff --git a/py/selenium/webdriver/remote/errorhandler.py b/py/selenium/webdriver/remote/errorhandler.py index 8988c9e5a7f89..15b739df46a35 100644 --- a/py/selenium/webdriver/remote/errorhandler.py +++ b/py/selenium/webdriver/remote/errorhandler.py @@ -54,46 +54,47 @@ class ErrorCode: """ Error codes defined in the WebDriver wire protocol. """ + # Keep in sync with org.openqa.selenium.remote.ErrorCodes and errorcodes.h SUCCESS = 0 - NO_SUCH_ELEMENT = [7, 'no such element'] - NO_SUCH_FRAME = [8, 'no such frame'] + NO_SUCH_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'] + 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: @@ -111,7 +112,7 @@ def check_response(self, response: Dict[str, Any]) -> None: :Raises: If the response contains an error message. """ - status = response.get('status', None) + status = response.get("status", None) if not status or status == ErrorCode.SUCCESS: return value = None @@ -119,22 +120,23 @@ def check_response(self, response: Dict[str, Any]) -> None: screen: str = response.get("screen", "") stacktrace = None if isinstance(status, int): - value_json = response.get('value', None) + 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) + 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') + message = message.get("message") else: - message = value.get('message', None) + message = value.get("message", None) except ValueError: pass @@ -153,9 +155,11 @@ def check_response(self, response: Dict[str, Any]) -> None: 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: + 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 @@ -204,21 +208,21 @@ def check_response(self, response: Dict[str, Any]) -> None: else: exception_class = WebDriverException if not value: - value = response['value'] + value = response["value"] if isinstance(value, str): raise exception_class(value) - if message == "" and 'message' in value: - message = value['message'] + if message == "" and "message" in value: + message = value["message"] screen = None # type: ignore[assignment] - if 'screen' in value: - screen = value['screen'] + if "screen" in value: + screen = value["screen"] stacktrace = None - st_value = value.get('stackTrace') or value.get('stacktrace') + st_value = value.get("stackTrace") or value.get("stacktrace") if st_value: if isinstance(st_value, str): - stacktrace = st_value.split('\n') + stacktrace = st_value.split("\n") else: stacktrace = [] try: @@ -227,9 +231,9 @@ def check_response(self, response: Dict[str, Any]) -> None: file = frame.get("fileName", "") if line: file = f"{file}:{line}" - meth = frame.get('methodName', '') - if 'className' in frame: - meth = "{}.{}".format(frame['className'], meth) + meth = frame.get("methodName", "") + if "className" in frame: + meth = "{}.{}".format(frame["className"], meth) msg = " at %s (%s)" msg = msg % (meth, file) stacktrace.append(msg) @@ -237,9 +241,9 @@ def check_response(self, response: Dict[str, Any]) -> None: 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') + 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) diff --git a/py/selenium/webdriver/remote/file_detector.py b/py/selenium/webdriver/remote/file_detector.py index 43eb3c4488186..9370ce12c85b5 100644 --- a/py/selenium/webdriver/remote/file_detector.py +++ b/py/selenium/webdriver/remote/file_detector.py @@ -50,7 +50,7 @@ class LocalFileDetector(FileDetector): """ def is_local_file(self, *keys: AnyKey) -> Optional[str]: - file_path = ''.join(keys_to_typing(keys)) + file_path = "".join(keys_to_typing(keys)) if not file_path: return None diff --git a/py/selenium/webdriver/remote/mobile.py b/py/selenium/webdriver/remote/mobile.py index 63b6b3e9ccbbf..d57b3440278da 100644 --- a/py/selenium/webdriver/remote/mobile.py +++ b/py/selenium/webdriver/remote/mobile.py @@ -19,9 +19,7 @@ class Mobile: - class ConnectionType: - def __init__(self, mask): self.mask = mask @@ -44,11 +42,12 @@ def data(self): 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']) + return self.ConnectionType(self._driver.execute(Command.GET_NETWORK_CONNECTION)["value"]) def set_network_connection(self, network): """ @@ -59,10 +58,11 @@ def set_network_connection(self, network): 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']) + return self.ConnectionType( + self._driver.execute( + Command.SET_NETWORK_CONNECTION, {"name": "network_connection", "parameters": {"type": mode}} + )["value"] + ) @property def context(self): diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index 85cf633b33384..98bb6db5b70d2 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -105,59 +105,53 @@ def get_remote_connection_headers(cls, parsed_url, keep_alive=False): system = "mac" headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json;charset=UTF-8', - 'User-Agent': f'selenium/{__version__} (python {system})' + "Accept": "application/json", + "Content-Type": "application/json;charset=UTF-8", + "User-Agent": f"selenium/{__version__} (python {system})", } if parsed_url.username: - base64string = b64encode('{0.username}:{0.password}'.format(parsed_url).encode()) - headers.update({ - 'Authorization': f'Basic {base64string.decode()}' - }) + base64string = b64encode("{0.username}:{0.password}".format(parsed_url).encode()) + headers.update({"Authorization": f"Basic {base64string.decode()}"}) if keep_alive: - headers.update({ - 'Connection': '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')) - if self._url.startswith('http://'): - return os.environ.get('http_proxy', os.environ.get('HTTP_PROXY')) + if self._url.startswith("https://"): + return os.environ.get("https_proxy", os.environ.get("HTTPS_PROXY")) + if 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 "@" in url and len(url[:url.find('@')]) > 0 + url = url[url.find(":") + 3 :] + return "@" in url and len(url[: url.find("@")]) > 0 def _separate_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:] + 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() - } + 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 + 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'): + 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._separate_http_proxy_auth() - pool_manager_init_args['proxy_headers'] = urllib3.make_headers( - proxy_basic_auth=self._basic_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) @@ -167,9 +161,9 @@ def __init__(self, remote_server_addr, keep_alive=False, ignore_proxy: typing.Op 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')) + _no_proxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY")) if _no_proxy: - for npu in _no_proxy.split(','): + for npu in _no_proxy.split(","): npu = npu.strip() if npu == "*": ignore_proxy = True @@ -190,135 +184,93 @@ def __init__(self, remote_server_addr, keep_alive=False, ignore_proxy: typing.Op 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'), + 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): @@ -334,10 +286,10 @@ def execute(self, command, params): its JSON payload. """ command_info = self._commands[command] - assert command_info is not None, 'Unrecognised command %s' % 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'] + 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) @@ -370,22 +322,22 @@ def _request(self, method, url, body=None): response = http.request(method, url, body=body, headers=headers) statuscode = response.status - if not hasattr(response, 'getheader'): - if hasattr(response.headers, 'getheader'): + if not hasattr(response, "getheader"): + if hasattr(response.headers, "getheader"): response.getheader = lambda x: response.headers.getheader(x) - elif hasattr(response.headers, 'get'): + elif hasattr(response.headers, "get"): response.getheader = lambda x: response.headers.get(x) - data = response.data.decode('UTF-8') + 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')) + return self._request("GET", response.getheader("location")) if 399 < statuscode <= 500: - return {'status': statuscode, 'value': data} + 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]): + 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()) @@ -394,14 +346,14 @@ def _request(self, method, url, body=None): status = ErrorCode.SUCCESS else: status = ErrorCode.UNKNOWN_ERROR - return {'status': status, 'value': data.strip()} + 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 + if "value" not in data: + data["value"] = None return data - data = {'status': 0, 'value': data} + data = {"status": 0, "value": data} return data finally: LOGGER.debug("Finished Request") @@ -411,5 +363,5 @@ def close(self): """ Clean up resources when finished with the remote_connection """ - if hasattr(self, '_conn'): + if hasattr(self, "_conn"): self._conn.clear() diff --git a/py/selenium/webdriver/remote/script_key.py b/py/selenium/webdriver/remote/script_key.py index 9cd94bb7f2f6b..930b699c7d79b 100644 --- a/py/selenium/webdriver/remote/script_key.py +++ b/py/selenium/webdriver/remote/script_key.py @@ -19,7 +19,6 @@ class ScriptKey: - def __init__(self, id=None): self._id = id or uuid.uuid4() diff --git a/py/selenium/webdriver/remote/shadowroot.py b/py/selenium/webdriver/remote/shadowroot.py index 4c9e6e28ef0b4..d0216b4dcb002 100644 --- a/py/selenium/webdriver/remote/shadowroot.py +++ b/py/selenium/webdriver/remote/shadowroot.py @@ -51,9 +51,7 @@ def find_element(self, by: str = By.ID, value: str = None): by = By.CSS_SELECTOR value = '[name="%s"]' % value - return self._execute( - Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value} - )["value"] + return self._execute(Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"] def find_elements(self, by: str = By.ID, value: str = None): if by == By.ID: @@ -66,9 +64,7 @@ def find_elements(self, by: str = By.ID, value: str = None): by = By.CSS_SELECTOR value = '[name="%s"]' % value - return self._execute( - Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by, "value": value} - )["value"] + return self._execute(Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"] # Private Methods def _execute(self, command, params=None): diff --git a/py/selenium/webdriver/remote/switch_to.py b/py/selenium/webdriver/remote/switch_to.py index 29890a1e767b2..98bcd01609553 100644 --- a/py/selenium/webdriver/remote/switch_to.py +++ b/py/selenium/webdriver/remote/switch_to.py @@ -28,6 +28,7 @@ class SwitchTo: def __init__(self, driver): import weakref + self._driver = weakref.proxy(driver) @property @@ -40,7 +41,7 @@ def active_element(self) -> WebElement: element = driver.switch_to.active_element """ - return self._driver.execute(Command.W3C_GET_ACTIVE_ELEMENT)['value'] + return self._driver.execute(Command.W3C_GET_ACTIVE_ELEMENT)["value"] @property def alert(self) -> Alert: @@ -65,7 +66,7 @@ def default_content(self) -> None: driver.switch_to.default_content() """ - self._driver.execute(Command.SWITCH_TO_FRAME, {'id': None}) + self._driver.execute(Command.SWITCH_TO_FRAME, {"id": None}) def frame(self, frame_reference) -> None: """ @@ -91,7 +92,7 @@ def frame(self, frame_reference) -> None: except NoSuchElementException: raise NoSuchFrameException(frame_reference) - self._driver.execute(Command.SWITCH_TO_FRAME, {'id': 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. @@ -104,8 +105,8 @@ def new_window(self, type_hint=None) -> None: driver.switch_to.new_window('tab') """ - value = self._driver.execute(Command.NEW_WINDOW, {'type': type_hint})['value'] - self._w3c_window(value['handle']) + value = self._driver.execute(Command.NEW_WINDOW, {"type": type_hint})["value"] + self._w3c_window(value["handle"]) def parent_frame(self) -> None: """ @@ -135,7 +136,7 @@ def window(self, window_name) -> None: def _w3c_window(self, window_name): def send_handle(h): - self._driver.execute(Command.SWITCH_TO_WINDOW, {'handle': h}) + self._driver.execute(Command.SWITCH_TO_WINDOW, {"handle": h}) try: # Try using it as a handle first. @@ -146,7 +147,7 @@ def send_handle(h): handles = self._driver.window_handles for handle in handles: send_handle(handle) - current_name = self._driver.execute_script('return window.name') + current_name = self._driver.execute_script("return window.name") if window_name == current_name: return send_handle(original_handle) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index fd5545573ec56..bcc0c7f371275 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -62,25 +62,23 @@ from .switch_to import SwitchTo from .webelement import WebElement -_W3C_CAPABILITY_NAMES = frozenset([ - 'acceptInsecureCerts', - 'browserName', - 'browserVersion', - 'pageLoadStrategy', - 'platformName', - 'proxy', - 'setWindowRect', - 'strictFileInteractability', - 'timeouts', - 'unhandledPromptBehavior', - 'webSocketUrl' -]) - -_OSS_W3C_CONVERSION = { - 'acceptSslCerts': 'acceptInsecureCerts', - 'version': 'browserVersion', - 'platform': 'platformName' -} +_W3C_CAPABILITY_NAMES = frozenset( + [ + "acceptInsecureCerts", + "browserName", + "browserVersion", + "pageLoadStrategy", + "platformName", + "proxy", + "setWindowRect", + "strictFileInteractability", + "timeouts", + "unhandledPromptBehavior", + "webSocketUrl", + ] +) + +_OSS_W3C_CONVERSION = {"acceptSslCerts": "acceptInsecureCerts", "version": "browserVersion", "platform": "platformName"} cdp = None @@ -104,27 +102,31 @@ def _make_w3c_caps(caps): - caps - A dictionary of capabilities requested by the caller. """ caps = copy.deepcopy(caps) - profile = caps.get('firefox_profile') + profile = caps.get("firefox_profile") always_match = {} - if caps.get('proxy') and caps['proxy'].get('proxyType'): - caps['proxy']['proxyType'] = caps['proxy']['proxyType'].lower() + if caps.get("proxy") and caps["proxy"].get("proxyType"): + caps["proxy"]["proxyType"] = caps["proxy"]["proxyType"].lower() for k, v in caps.items(): if v and k in _OSS_W3C_CONVERSION: # Todo: Remove in 4.7.0 (Deprecated in 4.5.0) w3c_equivalent = _OSS_W3C_CONVERSION[k] - warnings.warn(f"{k} is not a w3c capability. use `{w3c_equivalent}` instead. This will no longer be" - f" converted in 4.7.0", DeprecationWarning, stacklevel=2) - always_match[w3c_equivalent] = v.lower() if k == 'platform' else v - if k in _W3C_CAPABILITY_NAMES or ':' in k: + warnings.warn( + f"{k} is not a w3c capability. use `{w3c_equivalent}` instead. This will no longer be" + f" converted in 4.7.0", + DeprecationWarning, + stacklevel=2, + ) + always_match[w3c_equivalent] = v.lower() if k == "platform" else v + if k in _W3C_CAPABILITY_NAMES or ":" in k: always_match[k] = v if profile: - moz_opts = always_match.get('moz:firefoxOptions', {}) + moz_opts = always_match.get("moz:firefoxOptions", {}) # If it's already present, assume the caller did that intentionally. - if 'profile' not in moz_opts: + if "profile" not in moz_opts: # Don't mutate the original capabilities. new_opts = copy.deepcopy(moz_opts) - new_opts['profile'] = profile - always_match['moz:firefoxOptions'] = new_opts + new_opts["profile"] = profile + always_match["moz:firefoxOptions"] = new_opts return {"firstMatch": [{}], "alwaysMatch": always_match} @@ -134,10 +136,7 @@ def get_remote_connection(capabilities, command_executor, keep_alive, ignore_loc from selenium.webdriver.safari.remote_connection import SafariRemoteConnection candidates = [RemoteConnection, ChromiumRemoteConnection, SafariRemoteConnection, FirefoxRemoteConnection] - handler = next( - (c for c in candidates if c.browser_name == capabilities.get('browserName')), - RemoteConnection - ) + handler = next((c for c in candidates if c.browser_name == capabilities.get("browserName")), RemoteConnection) return handler(command_executor, keep_alive=keep_alive, ignore_proxy=ignore_local_proxy) @@ -202,9 +201,16 @@ class WebDriver(BaseWebDriver): _web_element_cls = WebElement _shadowroot_cls = ShadowRoot - def __init__(self, command_executor='http://127.0.0.1:4444', - desired_capabilities=None, browser_profile=None, proxy=None, - keep_alive=True, file_detector=None, options: Union[BaseOptions, List[BaseOptions]] = None): + def __init__( + self, + command_executor="http://127.0.0.1:4444", + desired_capabilities=None, + browser_profile=None, + proxy=None, + keep_alive=True, + file_detector=None, + options: Union[BaseOptions, List[BaseOptions]] = None, + ): """ Create a new driver that will issue commands using the wire protocol. @@ -227,25 +233,25 @@ def __init__(self, command_executor='http://127.0.0.1:4444', warnings.warn( "desired_capabilities has been deprecated, please pass in an Options object with options kwarg", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) if browser_profile: warnings.warn( "browser_profile has been deprecated, please pass in an Firefox Options object with options kwarg", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) if proxy: warnings.warn( "proxy has been deprecated, please pass in an Options object with options kwarg", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) if not keep_alive: warnings.warn( "keep_alive has been deprecated. We will be using True as the default value as we start removing it.", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) capabilities = {} # If we get a list we can assume that no capabilities @@ -263,9 +269,12 @@ def __init__(self, command_executor='http://127.0.0.1:4444', capabilities.update(desired_capabilities) self.command_executor = command_executor if isinstance(self.command_executor, (str, bytes)): - self.command_executor = get_remote_connection(capabilities, command_executor=command_executor, - keep_alive=keep_alive, - ignore_local_proxy=_ignore_local_proxy) + self.command_executor = get_remote_connection( + capabilities, + command_executor=command_executor, + keep_alive=keep_alive, + ignore_local_proxy=_ignore_local_proxy, + ) self._is_remote = True self.session_id = None self.caps = {} @@ -279,16 +288,17 @@ def __init__(self, command_executor='http://127.0.0.1:4444', self.start_session(capabilities, browser_profile) def __repr__(self): - return '<{0.__module__}.{0.__name__} (session="{1}")>'.format( - type(self), self.session_id) + return '<{0.__module__}.{0.__name__} (session="{1}")>'.format(type(self), self.session_id) def __enter__(self): return self - def __exit__(self, - exc_type: typing.Optional[typing.Type[BaseException]], - exc: typing.Optional[BaseException], - traceback: typing.Optional[types.TracebackType]): + def __exit__( + self, + exc_type: typing.Optional[typing.Type[BaseException]], + exc: typing.Optional[BaseException], + traceback: typing.Optional[types.TracebackType], + ): self.quit() @contextmanager @@ -333,9 +343,9 @@ def name(self) -> str: name = driver.name """ - if 'browserName' in self.caps: - return self.caps['browserName'] - raise KeyError('browserName not specified in session capabilities') + if "browserName" in self.caps: + return self.caps["browserName"] + raise KeyError("browserName not specified in session capabilities") def start_client(self): """ @@ -365,19 +375,19 @@ def start_session(self, capabilities: dict, browser_profile=None) -> None: if "moz:firefoxOptions" in capabilities: capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded else: - capabilities.update({'firefox_profile': browser_profile.encoded}) + capabilities.update({"firefox_profile": browser_profile.encoded}) w3c_caps = _make_w3c_caps(capabilities) parameters = {"capabilities": w3c_caps} response = self.execute(Command.NEW_SESSION, parameters) - if 'sessionId' not in response: - response = response['value'] - self.session_id = response['sessionId'] - self.caps = response.get('value') + 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 = response.get("capabilities") def _wrap_value(self, value): if isinstance(value, dict): @@ -386,9 +396,9 @@ def _wrap_value(self, value): converted[key] = self._wrap_value(val) return converted elif isinstance(value, self._web_element_cls): - return {'element-6066-11e4-a52e-4f735466cecf': value.id} + return {"element-6066-11e4-a52e-4f735466cecf": value.id} elif isinstance(value, self._shadowroot_cls): - return {'shadow-6066-11e4-a52e-4f735466cecf': value.id} + return {"shadow-6066-11e4-a52e-4f735466cecf": value.id} elif isinstance(value, list): return list(self._wrap_value(item) for item in value) else: @@ -400,10 +410,10 @@ def create_web_element(self, element_id: str) -> WebElement: def _unwrap_value(self, value): if isinstance(value, dict): - if 'element-6066-11e4-a52e-4f735466cecf' in value: - return self.create_web_element(value['element-6066-11e4-a52e-4f735466cecf']) - elif 'shadow-6066-11e4-a52e-4f735466cecf' in value: - return self._shadowroot_cls(self, value['shadow-6066-11e4-a52e-4f735466cecf']) + if "element-6066-11e4-a52e-4f735466cecf" in value: + return self.create_web_element(value["element-6066-11e4-a52e-4f735466cecf"]) + elif "shadow-6066-11e4-a52e-4f735466cecf" in value: + return self._shadowroot_cls(self, value["shadow-6066-11e4-a52e-4f735466cecf"]) else: for key, val in value.items(): value[key] = self._unwrap_value(val) @@ -426,26 +436,25 @@ def execute(self, driver_command: str, params: dict = None) -> dict: """ if self.session_id: if not params: - params = {'sessionId': self.session_id} - elif 'sessionId' not in params: - params['sessionId'] = self.session_id + params = {"sessionId": self.session_id} + elif "sessionId" not in params: + params["sessionId"] = self.session_id params = self._wrap_value(params) response = self.command_executor.execute(driver_command, params) if response: self.error_handler.check_response(response) - response['value'] = self._unwrap_value( - response.get('value', None)) + response["value"] = self._unwrap_value(response.get("value", None)) return response # If the server doesn't send a response, assume the command was # a success - return {'success': 0, 'value': None, 'sessionId': self.session_id} + return {"success": 0, "value": None, "sessionId": self.session_id} def get(self, url: str) -> None: """ Loads a web page in the current browser session. """ - self.execute(Command.GET, {'url': url}) + self.execute(Command.GET, {"url": url}) @property def title(self) -> str: @@ -496,9 +505,7 @@ def execute_script(self, script, *args): converted_args = list(args) command = Command.W3C_EXECUTE_SCRIPT - return self.execute(command, { - 'script': script, - 'args': converted_args})['value'] + return self.execute(command, {"script": script, "args": converted_args})["value"] def execute_async_script(self, script: str, *args): """ @@ -518,9 +525,7 @@ def execute_async_script(self, script: str, *args): converted_args = list(args) command = Command.W3C_EXECUTE_SCRIPT_ASYNC - return self.execute(command, { - 'script': script, - 'args': converted_args})['value'] + return self.execute(command, {"script": script, "args": converted_args})["value"] @property def current_url(self) -> str: @@ -532,7 +537,7 @@ def current_url(self) -> str: driver.current_url """ - return self.execute(Command.GET_CURRENT_URL)['value'] + return self.execute(Command.GET_CURRENT_URL)["value"] @property def page_source(self) -> str: @@ -544,7 +549,7 @@ def page_source(self) -> str: driver.page_source """ - return self.execute(Command.GET_PAGE_SOURCE)['value'] + return self.execute(Command.GET_PAGE_SOURCE)["value"] def close(self) -> None: """ @@ -582,7 +587,7 @@ def current_window_handle(self) -> str: driver.current_window_handle """ - return self.execute(Command.W3C_GET_CURRENT_WINDOW_HANDLE)['value'] + return self.execute(Command.W3C_GET_CURRENT_WINDOW_HANDLE)["value"] @property def window_handles(self) -> List[str]: @@ -594,7 +599,7 @@ def window_handles(self) -> List[str]: driver.window_handles """ - return self.execute(Command.W3C_GET_WINDOW_HANDLES)['value'] + return self.execute(Command.W3C_GET_WINDOW_HANDLES)["value"] def maximize_window(self) -> None: """ @@ -624,7 +629,7 @@ def print_page(self, print_options: Optional[PrintOptions] = None) -> str: if print_options: options = print_options.to_dict() - return self.execute(Command.PRINT_PAGE, options)['value'] + return self.execute(Command.PRINT_PAGE, options)["value"] @property def switch_to(self) -> SwitchTo: @@ -690,7 +695,7 @@ def get_cookies(self) -> List[dict]: driver.get_cookies() """ - return self.execute(Command.GET_ALL_COOKIES)['value'] + return self.execute(Command.GET_ALL_COOKIES)["value"] def get_cookie(self, name) -> typing.Optional[typing.Dict]: """ @@ -702,7 +707,7 @@ def get_cookie(self, name) -> typing.Optional[typing.Dict]: driver.get_cookie('my_cookie') """ with contextlib.suppress(NoSuchCookieException): - return self.execute(Command.GET_COOKIE, {"name": name})['value'] + return self.execute(Command.GET_COOKIE, {"name": name})["value"] def delete_cookie(self, name) -> None: """ @@ -713,7 +718,7 @@ def delete_cookie(self, name) -> None: driver.delete_cookie('my_cookie') """ - self.execute(Command.DELETE_COOKIE, {'name': name}) + self.execute(Command.DELETE_COOKIE, {"name": name}) def delete_all_cookies(self) -> None: """ @@ -741,11 +746,11 @@ def add_cookie(self, cookie_dict) -> None: driver.add_cookie({'name': 'foo', 'value': 'bar', 'sameSite': 'Strict'}) """ - if 'sameSite' in cookie_dict: - assert cookie_dict['sameSite'] in ['Strict', 'Lax', 'None'] - self.execute(Command.ADD_COOKIE, {'cookie': cookie_dict}) + if "sameSite" in cookie_dict: + assert cookie_dict["sameSite"] in ["Strict", "Lax", "None"] + self.execute(Command.ADD_COOKIE, {"cookie": cookie_dict}) else: - self.execute(Command.ADD_COOKIE, {'cookie': cookie_dict}) + self.execute(Command.ADD_COOKIE, {"cookie": cookie_dict}) # Timeouts def implicitly_wait(self, time_to_wait: float) -> None: @@ -763,8 +768,7 @@ def implicitly_wait(self, time_to_wait: float) -> None: driver.implicitly_wait(30) """ - self.execute(Command.SET_TIMEOUTS, { - 'implicit': int(float(time_to_wait) * 1000)}) + self.execute(Command.SET_TIMEOUTS, {"implicit": int(float(time_to_wait) * 1000)}) def set_script_timeout(self, time_to_wait: float) -> None: """ @@ -779,8 +783,7 @@ def set_script_timeout(self, time_to_wait: float) -> None: driver.set_script_timeout(30) """ - self.execute(Command.SET_TIMEOUTS, { - 'script': int(float(time_to_wait) * 1000)}) + self.execute(Command.SET_TIMEOUTS, {"script": int(float(time_to_wait) * 1000)}) def set_page_load_timeout(self, time_to_wait: float) -> None: """ @@ -796,12 +799,9 @@ def set_page_load_timeout(self, time_to_wait: float) -> None: driver.set_page_load_timeout(30) """ try: - self.execute(Command.SET_TIMEOUTS, { - 'pageLoad': int(float(time_to_wait) * 1000)}) + self.execute(Command.SET_TIMEOUTS, {"pageLoad": int(float(time_to_wait) * 1000)}) except WebDriverException: - self.execute(Command.SET_TIMEOUTS, { - 'ms': float(time_to_wait) * 1000, - 'type': 'page load'}) + self.execute(Command.SET_TIMEOUTS, {"ms": float(time_to_wait) * 1000, "type": "page load"}) @property def timeouts(self) -> Timeouts: @@ -813,7 +813,7 @@ def timeouts(self) -> Timeouts: driver.timeouts :rtype: Timeout """ - timeouts = self.execute(Command.GET_TIMEOUTS)['value'] + timeouts = self.execute(Command.GET_TIMEOUTS)["value"] timeouts["implicit_wait"] = timeouts.pop("implicit") / 1000 timeouts["page_load"] = timeouts.pop("pageLoad") / 1000 timeouts["script"] = timeouts.pop("script") / 1000 @@ -831,7 +831,7 @@ def timeouts(self, timeouts) -> None: my_timeouts.implicit_wait = 10 driver.timeouts = my_timeouts """ - _ = self.execute(Command.SET_TIMEOUTS, timeouts._to_json())['value'] + _ = self.execute(Command.SET_TIMEOUTS, timeouts._to_json())["value"] def find_element(self, by=By.ID, value: Optional[str] = None) -> WebElement: """ @@ -860,9 +860,7 @@ def find_element(self, by=By.ID, value: Optional[str] = None) -> WebElement: by = By.CSS_SELECTOR value = '[name="%s"]' % value - return self.execute(Command.FIND_ELEMENT, { - 'using': by, - 'value': value})['value'] + return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"] def find_elements(self, by=By.ID, value: Optional[str] = None) -> List[WebElement]: """ @@ -876,8 +874,8 @@ def find_elements(self, by=By.ID, value: Optional[str] = None) -> List[WebElemen :rtype: list of WebElement """ if isinstance(by, RelativeBy): - _pkg = '.'.join(__name__.split('.')[:-1]) - raw_function = pkgutil.get_data(_pkg, 'findElements.js').decode('utf8') + _pkg = ".".join(__name__.split(".")[:-1]) + raw_function = 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()) @@ -893,17 +891,14 @@ def find_elements(self, by=By.ID, value: Optional[str] = None) -> List[WebElemen # Return empty list if driver returns null # See https://github.com/SeleniumHQ/selenium/issues/4555 - return self.execute(Command.FIND_ELEMENTS, { - 'using': by, - 'value': value})['value'] or [] + return self.execute(Command.FIND_ELEMENTS, {"using": by, "value": value})["value"] or [] @property def desired_capabilities(self) -> dict: """ returns the drivers current desired capabilities being used """ - warnings.warn("desired_capabilities is deprecated. Please call capabilities.", - DeprecationWarning, stacklevel=2) + warnings.warn("desired_capabilities is deprecated. Please call capabilities.", DeprecationWarning, stacklevel=2) return self.caps @property @@ -928,12 +923,14 @@ def get_screenshot_as_file(self, filename) -> bool: driver.get_screenshot_as_file('/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) + 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.get_screenshot_as_png() try: - with open(filename, 'wb') as f: + with open(filename, "wb") as f: f.write(png) except OSError: return False @@ -967,7 +964,7 @@ def get_screenshot_as_png(self) -> bytes: driver.get_screenshot_as_png() """ - return b64decode(self.get_screenshot_as_base64().encode('ascii')) + return b64decode(self.get_screenshot_as_base64().encode("ascii")) def get_screenshot_as_base64(self) -> str: """ @@ -979,9 +976,9 @@ def get_screenshot_as_base64(self) -> str: driver.get_screenshot_as_base64() """ - return self.execute(Command.SCREENSHOT)['value'] + return self.execute(Command.SCREENSHOT)["value"] - def set_window_size(self, width, height, windowHandle: str = 'current') -> None: + def set_window_size(self, width, height, windowHandle: str = "current") -> None: """ Sets the width and height of the current window. (window.resizeTo) @@ -994,11 +991,11 @@ def set_window_size(self, width, height, windowHandle: str = 'current') -> None: driver.set_window_size(800,600) """ - if windowHandle != 'current': + if windowHandle != "current": warnings.warn("Only 'current' window is supported for W3C compatible browsers.") self.set_window_rect(width=int(width), height=int(height)) - def get_window_size(self, windowHandle: str = 'current') -> dict: + def get_window_size(self, windowHandle: str = "current") -> dict: """ Gets the width and height of the current window. @@ -1008,16 +1005,16 @@ def get_window_size(self, windowHandle: str = 'current') -> dict: driver.get_window_size() """ - if windowHandle != 'current': + if windowHandle != "current": warnings.warn("Only 'current' window is supported for W3C compatible browsers.") size = self.get_window_rect() - if size.get('value', None): - size = size['value'] + if size.get("value", None): + size = size["value"] - return {k: size[k] for k in ('width', 'height')} + return {k: size[k] for k in ("width", "height")} - def set_window_position(self, x, y, windowHandle: str = 'current') -> dict: + def set_window_position(self, x, y, windowHandle: str = "current") -> dict: """ Sets the x,y position of the current window. (window.moveTo) @@ -1030,11 +1027,11 @@ def set_window_position(self, x, y, windowHandle: str = 'current') -> dict: driver.set_window_position(0,0) """ - if windowHandle != 'current': + if windowHandle != "current": warnings.warn("Only 'current' window is supported for W3C compatible browsers.") return self.set_window_rect(x=int(x), y=int(y)) - def get_window_position(self, windowHandle='current') -> dict: + def get_window_position(self, windowHandle="current") -> dict: """ Gets the x,y position of the current window. @@ -1044,11 +1041,11 @@ def get_window_position(self, windowHandle='current') -> dict: driver.get_window_position() """ - if windowHandle != 'current': + if windowHandle != "current": warnings.warn("Only 'current' window is supported for W3C compatible browsers.") position = self.get_window_rect() - return {k: position[k] for k in ('x', 'y')} + return {k: position[k] for k in ("x", "y")} def get_window_rect(self) -> dict: """ @@ -1060,7 +1057,7 @@ def get_window_rect(self) -> dict: driver.get_window_rect() """ - return self.execute(Command.GET_WINDOW_RECT)['value'] + return self.execute(Command.GET_WINDOW_RECT)["value"] def set_window_rect(self, x=None, y=None, width=None, height=None) -> dict: """ @@ -1080,9 +1077,7 @@ def set_window_rect(self, x=None, y=None, width=None, height=None) -> dict: if (x is None and y is None) and (not height and not width): raise InvalidArgumentException("x and y or height and width need values") - return self.execute(Command.SET_WINDOW_RECT, {"x": x, "y": y, - "width": width, - "height": height})['value'] + return self.execute(Command.SET_WINDOW_RECT, {"x": x, "y": y, "width": width, "height": height})["value"] @property def file_detector(self) -> FileDetector: @@ -1117,7 +1112,7 @@ def orientation(self): orientation = driver.orientation """ - return self.execute(Command.GET_SCREEN_ORIENTATION)['value'] + return self.execute(Command.GET_SCREEN_ORIENTATION)["value"] @orientation.setter def orientation(self, value) -> None: @@ -1132,15 +1127,15 @@ def orientation(self, value) -> None: driver.orientation = 'landscape' """ - allowed_values = ['LANDSCAPE', 'PORTRAIT'] + allowed_values = ["LANDSCAPE", "PORTRAIT"] if value.upper() in allowed_values: - self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value}) + self.execute(Command.SET_SCREEN_ORIENTATION, {"orientation": value}) else: raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'") @property def application_cache(self): - """ Returns a ApplicationCache Object to interact with the browser app cache""" + """Returns a ApplicationCache Object to interact with the browser app cache""" return ApplicationCache(self) @property @@ -1153,7 +1148,7 @@ def log_types(self): driver.log_types """ - return self.execute(Command.GET_AVAILABLE_LOG_TYPES)['value'] + return self.execute(Command.GET_AVAILABLE_LOG_TYPES)["value"] def get_log(self, log_type): """ @@ -1170,7 +1165,7 @@ def get_log(self, log_type): driver.get_log('client') driver.get_log('server') """ - return self.execute(Command.GET_LOG, {'type': log_type})['value'] + return self.execute(Command.GET_LOG, {"type": log_type})["value"] @asynccontextmanager async def bidi_connection(self): @@ -1201,17 +1196,19 @@ def _get_cdp_details(self): _firefox = False if self.caps.get("browserName") == "chrome": debugger_address = self.caps.get(f"{self.vendor_prefix}:{self.caps.get('browserName')}Options").get( - "debuggerAddress") + "debuggerAddress" + ) else: _firefox = True debugger_address = self.caps.get("moz:debuggerAddress") - res = http.request('GET', f"http://{debugger_address}/json/version") + res = http.request("GET", f"http://{debugger_address}/json/version") data = json.loads(res.data) browser_version = data.get("Browser") websocket_url = data.get("webSocketDebuggerUrl") import re + if _firefox: # Mozilla Automation Team asked to only support 85 # until WebDriver Bidi is available. @@ -1226,7 +1223,7 @@ 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, options.to_dict())["value"] @property def virtual_authenticator_id(self) -> str: @@ -1241,7 +1238,7 @@ def remove_virtual_authenticator(self) -> None: Removes a previously added virtual authenticator. The authenticator is no longer valid after removal, so no methods may be called. """ - self.execute(Command.REMOVE_VIRTUAL_AUTHENTICATOR, {'authenticatorId': self._authenticator_id}) + self.execute(Command.REMOVE_VIRTUAL_AUTHENTICATOR, {"authenticatorId": self._authenticator_id}) self._authenticator_id = None @required_virtual_authenticator @@ -1249,18 +1246,15 @@ def add_credential(self, credential: Credential) -> None: """ Injects a credential into the authenticator. """ - self.execute( - Command.ADD_CREDENTIAL, - {**credential.to_dict(), 'authenticatorId': self._authenticator_id} - ) + self.execute(Command.ADD_CREDENTIAL, {**credential.to_dict(), "authenticatorId": self._authenticator_id}) @required_virtual_authenticator def get_credentials(self) -> List[Credential]: """ Returns the list of credentials owned by the authenticator. """ - credential_data = self.execute(Command.GET_CREDENTIALS, {'authenticatorId': self._authenticator_id}) - return [Credential.from_dict(credential) for credential in credential_data['value']] + credential_data = self.execute(Command.GET_CREDENTIALS, {"authenticatorId": self._authenticator_id}) + return [Credential.from_dict(credential) for credential in credential_data["value"]] @required_virtual_authenticator def remove_credential(self, credential_id: Union[str, bytearray]) -> None: @@ -1272,8 +1266,7 @@ def remove_credential(self, credential_id: Union[str, bytearray]) -> None: credential_id = urlsafe_b64encode(credential_id).decode() self.execute( - Command.REMOVE_CREDENTIAL, - {'credentialId': credential_id, 'authenticatorId': self._authenticator_id} + Command.REMOVE_CREDENTIAL, {"credentialId": credential_id, "authenticatorId": self._authenticator_id} ) @required_virtual_authenticator @@ -1281,7 +1274,7 @@ def remove_all_credentials(self) -> None: """ Removes all credentials from the authenticator. """ - self.execute(Command.REMOVE_ALL_CREDENTIALS, {'authenticatorId': self._authenticator_id}) + self.execute(Command.REMOVE_ALL_CREDENTIALS, {"authenticatorId": self._authenticator_id}) @required_virtual_authenticator def set_user_verified(self, verified: bool) -> None: @@ -1289,7 +1282,4 @@ def set_user_verified(self, verified: bool) -> None: Sets whether the authenticator will simulate success or fail on user verification. verified: True if the authenticator will pass user verification, False otherwise. """ - self.execute( - Command.SET_USER_VERIFIED, - {'authenticatorId': self._authenticator_id, 'isUserVerified': verified} - ) + self.execute(Command.SET_USER_VERIFIED, {"authenticatorId": self._authenticator_id, "isUserVerified": verified}) diff --git a/py/selenium/webdriver/remote/webelement.py b/py/selenium/webdriver/remote/webelement.py index d486651b787b0..c60ad0471b119 100644 --- a/py/selenium/webdriver/remote/webelement.py +++ b/py/selenium/webdriver/remote/webelement.py @@ -43,9 +43,9 @@ def _load_js(): global getAttribute_js global isDisplayed_js - _pkg = '.'.join(__name__.split('.')[:-1]) - getAttribute_js = pkgutil.get_data(_pkg, 'getAttribute.js').decode('utf8') - isDisplayed_js = pkgutil.get_data(_pkg, 'isDisplayed.js').decode('utf8') + _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): @@ -53,6 +53,7 @@ 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 @@ -74,17 +75,18 @@ def __init__(self, parent, id_): def __repr__(self): return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format( - type(self), self._parent.session_id, self._id) + 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'] + 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'] + return self._execute(Command.GET_ELEMENT_TEXT)["value"] def click(self) -> None: """Clicks the element.""" @@ -92,15 +94,17 @@ def click(self) -> None: def submit(self): """Submits a form.""" - script = "var form = arguments[0];\n" \ - "while (form.nodeName != \"FORM\" && form.parentNode) {\n" \ - " form = form.parentNode;\n" \ - "}\n" \ - "if (!form) { throw Error('Unable to find containing form element'); }\n" \ - "if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" \ - "var e = form.ownerDocument.createEvent('Event');\n" \ - "e.initEvent('submit', true, true);\n" \ - "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n" + script = ( + "var form = arguments[0];\n" + 'while (form.nodeName != "FORM" && form.parentNode) {\n' + " form = form.parentNode;\n" + "}\n" + "if (!form) { throw Error('Unable to find containing form element'); }\n" + "if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" + "var e = form.ownerDocument.createEvent('Event');\n" + "e.initEvent('submit', true, true);\n" + "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n" + ) try: self._parent.execute_script(script, self) @@ -127,7 +131,7 @@ def get_property(self, name) -> str | bool | WebElement | dict: 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) + return self.parent.execute_script("return arguments[0][arguments[1]]", self, name) def get_dom_attribute(self, name) -> str: """ @@ -173,8 +177,8 @@ def get_attribute(self, name) -> str: if getAttribute_js is None: _load_js() attribute_value = self.parent.execute_script( - "return (%s).apply(null, arguments);" % getAttribute_js, - self, name) + "return (%s).apply(null, arguments);" % getAttribute_js, self, name + ) return attribute_value def is_selected(self) -> bool: @@ -182,11 +186,11 @@ def is_selected(self) -> bool: Can be used to check if a checkbox or radio button is selected. """ - return self._execute(Command.IS_ELEMENT_SELECTED)['value'] + 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'] + return self._execute(Command.IS_ELEMENT_ENABLED)["value"] def send_keys(self, *value) -> None: """Simulates typing into the element. @@ -214,35 +218,42 @@ def send_keys(self, *value) -> None: # 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'))) + 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) + value = "\n".join(remote_files) - self._execute(Command.SEND_KEYS_TO_ELEMENT, - {'text': "".join(keys_to_typing(value)), - 'value': keys_to_typing(value)}) + 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 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 + :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'] + 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: @@ -250,9 +261,7 @@ def is_displayed(self) -> bool: # Only go into this conditional for browsers that don't use the atom themselves if isDisplayed_js is None: _load_js() - return self.parent.execute_script( - "return (%s).apply(null, arguments);" % isDisplayed_js, - self) + return self.parent.execute_script("return (%s).apply(null, arguments);" % isDisplayed_js, self) @property def location_once_scrolled_into_view(self) -> dict: @@ -264,47 +273,47 @@ def location_once_scrolled_into_view(self) -> dict: 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'])} + 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"]} + 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'] + 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'])} + 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'] + 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'] + """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'] + return self._execute(Command.GET_ELEMENT_ARIA_LABEL)["value"] @property def screenshot_as_base64(self) -> str: @@ -316,7 +325,7 @@ def screenshot_as_base64(self) -> str: img_b64 = element.screenshot_as_base64 """ - return self._execute(Command.ELEMENT_SCREENSHOT)['value'] + return self._execute(Command.ELEMENT_SCREENSHOT)["value"] @property def screenshot_as_png(self) -> bytes: @@ -328,7 +337,7 @@ def screenshot_as_png(self) -> bytes: element_png = element.screenshot_as_png """ - return b64decode(self.screenshot_as_base64.encode('ascii')) + return b64decode(self.screenshot_as_base64.encode("ascii")) def screenshot(self, filename) -> bool: """ @@ -345,12 +354,14 @@ def screenshot(self, filename) -> bool: 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) + 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: + with open(filename, "wb") as f: f.write(png) except OSError: return False @@ -377,7 +388,7 @@ def id(self) -> str: return self._id def __eq__(self, element): - return hasattr(element, 'id') and self._id == element.id + return hasattr(element, "id") and self._id == element.id def __ne__(self, element): return not self.__eq__(element) @@ -395,7 +406,7 @@ def _execute(self, command, params=None): """ if not params: params = {} - params['id'] = self._id + params["id"] = self._id return self._parent.execute(command, params) def find_element(self, by=By.ID, value=None) -> WebElement: @@ -419,8 +430,7 @@ def find_element(self, by=By.ID, value=None) -> WebElement: by = By.CSS_SELECTOR value = '[name="%s"]' % value - return self._execute(Command.FIND_CHILD_ELEMENT, - {"using": by, "value": value})['value'] + return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})["value"] def find_elements(self, by=By.ID, value=None) -> list[WebElement]: """ @@ -443,22 +453,21 @@ def find_elements(self, by=By.ID, value=None) -> list[WebElement]: by = By.CSS_SELECTOR value = '[name="%s"]' % value - return self._execute(Command.FIND_CHILD_ELEMENTS, - {"using": by, "value": value})['value'] + return self._execute(Command.FIND_CHILD_ELEMENTS, {"using": by, "value": value})["value"] def __hash__(self) -> int: - return int(md5_hash(self._id.encode('utf-8')).hexdigest(), 16) + 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 = 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') + content = content.decode("utf-8") try: - return self._execute(Command.UPLOAD_FILE, {'file': content})['value'] + return self._execute(Command.UPLOAD_FILE, {"file": content})["value"] except WebDriverException as e: if "Unrecognized command: POST" in str(e): return filename diff --git a/py/selenium/webdriver/support/color.py b/py/selenium/webdriver/support/color.py index 5152f8be2cdbe..a585a453535c1 100644 --- a/py/selenium/webdriver/support/color.py +++ b/py/selenium/webdriver/support/color.py @@ -40,7 +40,9 @@ ParseableInt = Any RGB_PATTERN = r"^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$" -RGB_PCT_PATTERN = r"^\s*rgb\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*\)\s*$" +RGB_PCT_PATTERN = ( + r"^\s*rgb\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*\)\s*$" +) RGBA_PATTERN = r"^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0\.\d+)\s*\)\s*$" RGBA_PCT_PATTERN = r"^\s*rgba\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(0|1|0\.\d+)\s*\)\s*$" HEX_PATTERN = r"#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})" @@ -92,8 +94,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]] + [m.groups[3]]) # type: ignore return cls(*rgba) elif m.match(HEX_PATTERN, str_): rgb = tuple(int(each, 16) for each in m.groups) @@ -109,8 +110,7 @@ def groups(self) -> Sequence[str]: raise ValueError("Could not convert %s into color" % str_) @classmethod - def _from_hsl(cls, h: ParseableFloat, s: ParseableFloat, light: ParseableFloat, - a: ParseableFloat = 1) -> Color: + def _from_hsl(cls, h: ParseableFloat, s: ParseableFloat, light: ParseableFloat, a: ParseableFloat = 1) -> Color: h = float(h) / 360 s = float(s) / 100 _l = float(light) / 100 @@ -144,8 +144,7 @@ def hue_to_rgb(lum1: float, lum2: float, hue: float) -> float: return cls(round(r * 255), round(g * 255), round(b * 255), a) - def __init__(self, red: ParseableInt, green: ParseableInt, blue: ParseableInt, - alpha: ParseableFloat = 1) -> None: + def __init__(self, red: ParseableInt, green: ParseableInt, blue: ParseableInt, alpha: ParseableFloat = 1) -> None: self.red = int(red) self.green = int(green) self.blue = int(blue) @@ -178,8 +177,7 @@ def __hash__(self) -> int: return hash((self.red, self.green, self.blue, self.alpha)) def __repr__(self) -> str: - return "Color(red=%d, green=%d, blue=%d, alpha=%s)" % ( - self.red, self.green, self.blue, self.alpha) + return "Color(red=%d, green=%d, blue=%d, alpha=%s)" % (self.red, self.green, self.blue, self.alpha) def __str__(self) -> str: return f"Color: {self.rgba}" @@ -336,5 +334,5 @@ def __str__(self) -> str: "WHITE": Color(255, 255, 255), "WHITESMOKE": Color(245, 245, 245), "YELLOW": Color(255, 255, 0), - "YELLOWGREEN": Color(154, 205, 50) + "YELLOWGREEN": Color(154, 205, 50), } diff --git a/py/selenium/webdriver/support/event_firing_webdriver.py b/py/selenium/webdriver/support/event_firing_webdriver.py index 0dbd227a377e4..eecc0d9e115ae 100644 --- a/py/selenium/webdriver/support/event_firing_webdriver.py +++ b/py/selenium/webdriver/support/event_firing_webdriver.py @@ -109,11 +109,9 @@ def find_element(self, by=By.ID, value=None) -> WebElement: def find_elements(self, by=By.ID, value=None) -> typing.List[WebElement]: return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value)) - def _dispatch(self, - l_call: str, - l_args: typing.Tuple[typing.Any, ...], - d_call: str, - d_args: typing.Tuple[typing.Any, ...]): + def _dispatch( + self, l_call: str, l_args: typing.Tuple[typing.Any, ...], d_call: str, d_args: typing.Tuple[typing.Any, ...] + ): getattr(self._listener, f"before_{l_call}")(*l_args) try: result = getattr(self._driver, d_call)(*d_args) @@ -166,7 +164,7 @@ def _wrap(*args, **kwargs): class EventFiringWebElement: - """" + """ " A wrapper around WebElement instance which supports firing events """ diff --git a/py/selenium/webdriver/support/expected_conditions.py b/py/selenium/webdriver/support/expected_conditions.py index 55a58b817f511..c6ce47700b7fe 100644 --- a/py/selenium/webdriver/support/expected_conditions.py +++ b/py/selenium/webdriver/support/expected_conditions.py @@ -42,7 +42,7 @@ def _predicate(driver): def title_contains(title): - """ An expectation for checking that the title contains a case-sensitive + """An expectation for checking that the title contains a case-sensitive substring. title is the fragment of title expected returns True when the title matches, False otherwise """ @@ -54,7 +54,7 @@ def _predicate(driver): def presence_of_element_located(locator): - """ An expectation for checking that an element is present on the DOM + """An expectation for checking that an element is present on the DOM of a page. This does not necessarily mean that the element is visible. locator - used to find the element returns the WebElement once it is located @@ -67,7 +67,7 @@ def _predicate(driver): def url_contains(url): - """ An expectation for checking that the current url contains a + """An expectation for checking that the current url contains a case-sensitive substring. url is the fragment of url expected, returns True when the url matches, False otherwise @@ -113,7 +113,7 @@ def _predicate(driver): def visibility_of_element_located(locator): - """ An expectation for checking that an element is present on the DOM of a + """An expectation for checking that an element is present on the DOM of a page and visible. Visibility means that the element is not only displayed but also has a height and width that is greater than 0. locator - used to find the element @@ -130,7 +130,7 @@ def _predicate(driver): def visibility_of(element): - """ An expectation for checking that an element, known to be present on the + """An expectation for checking that an element, known to be present on the DOM of a page, is visible. Visibility means that the element is not only displayed but also has a height and width that is greater than 0. element is the WebElement @@ -148,7 +148,7 @@ def _element_if_visible(element, visibility=True): def presence_of_all_elements_located(locator): - """ An expectation for checking that there is at least one element present + """An expectation for checking that there is at least one element present on a web page. locator is used to find the element returns the list of WebElements once they are located @@ -161,7 +161,7 @@ def _predicate(driver): def visibility_of_any_elements_located(locator): - """ An expectation for checking that there is at least one element visible + """An expectation for checking that there is at least one element visible on a web page. locator is used to find the element returns the list of WebElements once they are located @@ -174,7 +174,7 @@ def _predicate(driver): def visibility_of_all_elements_located(locator): - """ An expectation for checking that all elements are present on the DOM of a + """An expectation for checking that all elements are present on the DOM of a page and visible. Visibility means that the elements are not only displayed but also has a height and width that is greater than 0. locator - used to find the elements @@ -195,7 +195,7 @@ def _predicate(driver): def text_to_be_present_in_element(locator, text_): - """ An expectation for checking if the given text is present in the + """An expectation for checking if the given text is present in the specified element. locator, text """ @@ -245,14 +245,14 @@ def _predicate(driver): def frame_to_be_available_and_switch_to_it(locator): - """ An expectation for checking whether the given frame is available to + """An expectation for checking whether the given frame is available to switch to. If the frame is available it switches the given driver to the specified frame. """ def _predicate(driver): try: - if hasattr(locator, '__iter__') and not isinstance(locator, str): + if hasattr(locator, "__iter__") and not isinstance(locator, str): driver.switch_to.frame(driver.find_element(*locator)) else: driver.switch_to.frame(locator) @@ -264,7 +264,7 @@ def _predicate(driver): def invisibility_of_element_located(locator): - """ An Expectation for checking that an element is either invisible or not + """An Expectation for checking that an element is either invisible or not present on the DOM. locator used to find the element @@ -288,7 +288,7 @@ def _predicate(driver): def invisibility_of_element(element): - """ An Expectation for checking that an element is either invisible or not + """An Expectation for checking that an element is either invisible or not present on the DOM. element is either a locator (text) or an WebElement @@ -319,7 +319,7 @@ def _predicate(driver): def staleness_of(element): - """ Wait until an element is no longer attached to the DOM. + """Wait until an element is no longer attached to the DOM. element is the element to wait for. returns False if the element is still attached to the DOM, true otherwise. """ @@ -336,7 +336,7 @@ def _predicate(_): def element_to_be_selected(element): - """ An expectation for checking the selection is selected. + """An expectation for checking the selection is selected. element is WebElement object """ @@ -357,7 +357,7 @@ def _predicate(driver): def element_selection_state_to_be(element, is_selected): - """ An expectation for checking if the given element is selected. + """An expectation for checking if the given element is selected. element is WebElement object is_selected is a Boolean. """ @@ -369,7 +369,7 @@ def _predicate(_): def element_located_selection_state_to_be(locator, is_selected): - """ An expectation to locate an element and check if the selection state + """An expectation to locate an element and check if the selection state specified is in that state. locator is a tuple of (by, path) is_selected is a boolean @@ -386,7 +386,7 @@ def _predicate(driver): def number_of_windows_to_be(num_windows): - """ An expectation for the number of windows to be a certain value.""" + """An expectation for the number of windows to be a certain value.""" def _predicate(driver): return len(driver.window_handles) == num_windows @@ -395,7 +395,7 @@ def _predicate(driver): def new_window_is_opened(current_handles): - """ An expectation that a new window will be opened and have the number of + """An expectation that a new window will be opened and have the number of windows handles increase""" def _predicate(driver): @@ -415,7 +415,7 @@ def _predicate(driver): def element_attribute_to_include(locator, attribute_): - """ An expectation for checking if the given attribute is included in the + """An expectation for checking if the given attribute is included in the specified element. locator, attribute """ @@ -431,9 +431,9 @@ def _predicate(driver): def any_of(*expected_conditions): - """ An expectation that any of multiple expected conditions is true. + """An expectation that any of multiple expected conditions is true. Equivalent to a logical 'OR'. - Returns results of the first matching condition, or False if none do. """ + Returns results of the first matching condition, or False if none do.""" def any_of_condition(driver): for expected_condition in expected_conditions: @@ -449,10 +449,10 @@ def any_of_condition(driver): def all_of(*expected_conditions): - """ An expectation that all of multiple expected conditions is true. + """An expectation that all of multiple expected conditions is true. Equivalent to a logical 'AND'. Returns: When any ExpectedCondition is not met: False. - When all ExpectedConditions are met: A List with each ExpectedCondition's return value. """ + When all ExpectedConditions are met: A List with each ExpectedCondition's return value.""" def all_of_condition(driver): results = [] @@ -470,9 +470,9 @@ def all_of_condition(driver): def none_of(*expected_conditions): - """ An expectation that none of 1 or multiple expected conditions is true. + """An expectation that none of 1 or multiple expected conditions is true. Equivalent to a logical 'NOT-OR'. - Returns a Boolean """ + Returns a Boolean""" def none_of_condition(driver): for expected_condition in expected_conditions: diff --git a/py/selenium/webdriver/support/relative_locator.py b/py/selenium/webdriver/support/relative_locator.py index a421531f08fd4..cd549b0b7a5e7 100644 --- a/py/selenium/webdriver/support/relative_locator.py +++ b/py/selenium/webdriver/support/relative_locator.py @@ -27,15 +27,15 @@ def with_tag_name(tag_name: str) -> "RelativeBy": """ - Start searching for relative objects using a tag name. - - Note: This method may be removed in future versions, please use - `locate_with` instead. - :Args: - - tag_name: the DOM tag of element to start searching. - :Returns: - - RelativeBy - use this object to create filters within a - `find_elements` call. + Start searching for relative objects using a tag name. + + Note: This method may be removed in future versions, please use + `locate_with` instead. + :Args: + - tag_name: the DOM tag of element to start searching. + :Returns: + - RelativeBy - use this object to create filters within a + `find_elements` call. """ if not tag_name: raise WebDriverException("tag_name can not be null") @@ -44,14 +44,14 @@ def with_tag_name(tag_name: str) -> "RelativeBy": def locate_with(by: By, using: str) -> "RelativeBy": """ - Start searching for relative objects your search criteria with By. - - :Args: - - by: The value from `By` passed in. - - using: search term to find the element with. - :Returns: - - RelativeBy - use this object to create filters within a - `find_elements` call. + Start searching for relative objects your search criteria with By. + + :Args: + - by: The value from `By` passed in. + - using: search term to find the element with. + :Returns: + - RelativeBy - use this object to create filters within a + `find_elements` call. """ assert by is not None, "Please pass in a by argument" assert using is not None, "Please pass in a using argument" @@ -60,37 +60,37 @@ def locate_with(by: By, using: str) -> "RelativeBy": class RelativeBy: """ - Gives the opportunity to find elements based on their relative location - on the page from a root elelemt. It is recommended that you use the helper - function to create it. + Gives the opportunity to find elements based on their relative location + on the page from a root elelemt. It is recommended that you use the helper + function to create it. - Example: - lowest = driver.find_element(By.ID, "below") + Example: + lowest = driver.find_element(By.ID, "below") - elements = driver.find_elements(locate_with(By.CSS_SELECTOR, "p").above(lowest)) + elements = driver.find_elements(locate_with(By.CSS_SELECTOR, "p").above(lowest)) - ids = [el.get_attribute('id') for el in elements] - assert "above" in ids - assert "mid" in ids + ids = [el.get_attribute('id') for el in elements] + assert "above" in ids + assert "mid" in ids """ def __init__(self, root: Dict[By, str] = None, filters: List = None): """ - Creates a new RelativeBy object. It is preferred if you use the - `locate_with` method as this signature could change. - :Args: - root - A dict with `By` enum as the key and the search query as the value - filters - A list of the filters that will be searched. If none are passed - in please use the fluent API on the object to create the filters + Creates a new RelativeBy object. It is preferred if you use the + `locate_with` method as this signature could change. + :Args: + root - A dict with `By` enum as the key and the search query as the value + filters - A list of the filters that will be searched. If none are passed + in please use the fluent API on the object to create the filters """ self.root = root self.filters = filters or [] def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": """ - Add a filter to look for elements above. - :Args: - - element_or_locator: Element to look above + Add a filter to look for elements above. + :Args: + - element_or_locator: Element to look above """ if not element_or_locator: raise WebDriverException("Element or locator must be given when calling above method") @@ -100,9 +100,9 @@ def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "Relative def below(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": """ - Add a filter to look for elements below. - :Args: - - element_or_locator: Element to look below + Add a filter to look for elements below. + :Args: + - element_or_locator: Element to look below """ if not element_or_locator: raise WebDriverException("Element or locator must be given when calling above method") @@ -112,9 +112,9 @@ def below(self, element_or_locator: Union[WebElement, Dict] = None) -> "Relative 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: - - element_or_locator: Element to look to the left of + Add a filter to look for elements to the left of. + :Args: + - element_or_locator: Element to look to the left of """ if not element_or_locator: raise WebDriverException("Element or locator must be given when calling above method") @@ -124,9 +124,9 @@ def to_left_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Rel def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": """ - Add a filter to look for elements right of. - :Args: - - element_or_locator: Element to look right of + Add a filter to look for elements right of. + :Args: + - element_or_locator: Element to look right of """ if not element_or_locator: raise WebDriverException("Element or locator must be given when calling above method") @@ -136,9 +136,9 @@ def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Re def near(self, element_or_locator_distance: Union[WebElement, Dict, int] = None) -> "RelativeBy": """ - Add a filter to look for elements near. - :Args: - - element_or_locator_distance: Element to look near by the element or within a distance + Add a filter to look for elements near. + :Args: + - element_or_locator_distance: Element to look near by the element or within a distance """ if not element_or_locator_distance: raise WebDriverException("Element or locator or distance must be given when calling above method") @@ -148,11 +148,11 @@ def near(self, element_or_locator_distance: Union[WebElement, Dict, int] = None) def to_dict(self) -> Dict: """ - Create a dict that will be passed to the driver to start searching for the element + Create a dict that will be passed to the driver to start searching for the element """ return { - 'relative': { - 'root': self.root, - 'filters': self.filters, + "relative": { + "root": self.root, + "filters": self.filters, } } diff --git a/py/selenium/webdriver/support/select.py b/py/selenium/webdriver/support/select.py index 8c62dca927f97..355ea8a306b53 100644 --- a/py/selenium/webdriver/support/select.py +++ b/py/selenium/webdriver/support/select.py @@ -21,7 +21,6 @@ class Select: - def __init__(self, webelement) -> None: """ Constructor. A check is made that the given element is, indeed, a SELECT tag. If it is not, @@ -36,8 +35,8 @@ def __init__(self, webelement) -> None: """ if webelement.tag_name.lower() != "select": raise UnexpectedTagNameException( - "Select only works on elements, not on <%s>" % webelement.tag_name + ) if not webelement.is_enabled(): raise NotImplementedError("Select element is disabled and may not be used.") self._el = webelement @@ -47,7 +46,7 @@ def __init__(self, webelement) -> None: @property def options(self): """Returns a list of all options belonging to this select tag""" - return self._el.find_elements(By.TAG_NAME, 'option') + return self._el.find_elements(By.TAG_NAME, "option") @property def all_selected_options(self): @@ -65,15 +64,15 @@ def first_selected_option(self): def select_by_value(self, value): """Select all options that have a value matching the argument. That is, when given "foo" this - would select an option like: + would select an option like: - + - :Args: - - value - The value to match against + :Args: + - value - The value to match against - throws NoSuchElementException If there is no option with specified value in SELECT - """ + throws NoSuchElementException If there is no option with specified value in SELECT + """ css = "option[value =%s]" % self._escape_string(value) opts = self._el.find_elements(By.CSS_SELECTOR, css) matched = False @@ -87,13 +86,13 @@ def select_by_value(self, value): def select_by_index(self, index): """Select the option at the given index. This is done by examining the "index" attribute of an - element, and not merely by counting. + element, and not merely by counting. - :Args: - - index - The option at this index will be selected + :Args: + - index - The option at this index will be selected - throws NoSuchElementException If there is no option with specified index in SELECT - """ + throws NoSuchElementException If there is no option with specified index in SELECT + """ match = str(index) for opt in self.options: if opt.get_attribute("index") == match: @@ -103,15 +102,15 @@ def select_by_index(self, index): def select_by_visible_text(self, text): """Select all options that display text matching the argument. That is, when given "Bar" this - would select an option like: + would select an option like: - + - :Args: - - text - The visible text to match against + :Args: + - text - The visible text to match against - throws NoSuchElementException If there is no option with specified text in SELECT - """ + throws NoSuchElementException If there is no option with specified text in SELECT + """ xpath = ".//option[normalize-space(.) = %s]" % self._escape_string(text) opts = self._el.find_elements(By.XPATH, xpath) matched = False @@ -140,7 +139,7 @@ def select_by_visible_text(self, text): def deselect_all(self) -> None: """Clear all selected entries. This is only valid when the SELECT supports multiple selections. - throws NotImplementedError If the SELECT does not support multiple selections + throws NotImplementedError If the SELECT does not support multiple selections """ if not self.is_multiple: raise NotImplementedError("You may only deselect all options of a multi-select") @@ -149,14 +148,14 @@ def deselect_all(self) -> None: def deselect_by_value(self, value): """Deselect all options that have a value matching the argument. That is, when given "foo" this - would deselect an option like: + would deselect an option like: - + - :Args: - - value - The value to match against + :Args: + - value - The value to match against - throws NoSuchElementException If there is no option with specified value in SELECT + throws NoSuchElementException If there is no option with specified value in SELECT """ if not self.is_multiple: raise NotImplementedError("You may only deselect options of a multi-select") @@ -171,12 +170,12 @@ def deselect_by_value(self, value): def deselect_by_index(self, index): """Deselect the option at the given index. This is done by examining the "index" attribute of an - element, and not merely by counting. + element, and not merely by counting. - :Args: - - index - The option at this index will be deselected + :Args: + - index - The option at this index will be deselected - throws NoSuchElementException If there is no option with specified index in SELECT + throws NoSuchElementException If there is no option with specified index in SELECT """ if not self.is_multiple: raise NotImplementedError("You may only deselect options of a multi-select") @@ -188,12 +187,12 @@ def deselect_by_index(self, index): def deselect_by_visible_text(self, text): """Deselect all options that display text matching the argument. That is, when given "Bar" this - would deselect an option like: + would deselect an option like: - + - :Args: - - text - The visible text to match against + :Args: + - text - The visible text to match against """ if not self.is_multiple: raise NotImplementedError("You may only deselect options of a multi-select") @@ -218,10 +217,10 @@ def _unset_selected(self, option) -> None: def _escape_string(self, value: str) -> str: if '"' in value and "'" in value: - substrings = value.split("\"") + substrings = value.split('"') result = ["concat("] for substring in substrings: - result.append("\"%s\"" % substring) + result.append('"%s"' % substring) result.append(", '\"', ") result = result[0:-1] if value.endswith('"'): @@ -231,7 +230,7 @@ def _escape_string(self, value: str) -> str: if '"' in value: return "'%s'" % value - return "\"%s\"" % value + return '"%s"' % value def _get_longest_token(self, value: str) -> str: items = value.split(" ") diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index c6ee8ca85c1bd..1b63cafc5a6f6 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -27,23 +27,29 @@ 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: typing.Optional[WaitExcTypes] = None, + ): """Constructor, takes a WebDriver instance and timeout in seconds. - :Args: - - driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote) - - timeout - Number of seconds before timing out - - poll_frequency - sleep interval between calls - By default, it is 0.5 second. - - ignored_exceptions - iterable structure of exception classes ignored during calls. - By default, it contains NoSuchElementException only. + :Args: + - driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote) + - timeout - Number of seconds before timing out + - poll_frequency - sleep interval between calls + By default, it is 0.5 second. + - ignored_exceptions - iterable structure of exception classes ignored during calls. + By default, it contains NoSuchElementException only. - Example:: + Example:: - from selenium.webdriver.support.wait import WebDriverWait \n - element = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "someId")) \n - is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).\\ \n - until_not(lambda x: x.find_element(By.ID, "someId").is_displayed()) + from selenium.webdriver.support.wait import WebDriverWait \n + element = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "someId")) \n + is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).\\ \n + until_not(lambda x: x.find_element(By.ID, "someId").is_displayed()) """ self._driver = driver self._timeout = float(timeout) @@ -60,8 +66,7 @@ def __init__(self, driver, timeout: float, poll_frequency: float = POLL_FREQUENC self._ignored_exceptions = tuple(exceptions) def __repr__(self): - return '<{0.__module__}.{0.__name__} (session="{1}")>'.format( - type(self), self._driver.session_id) + return '<{0.__module__}.{0.__name__} (session="{1}")>'.format(type(self), self._driver.session_id) def until(self, method, message: str = ""): """Calls the method provided with the driver as an argument until the \ @@ -82,8 +87,8 @@ def until(self, method, message: str = ""): if value: return value except self._ignored_exceptions as exc: - screen = getattr(exc, 'screen', None) - stacktrace = getattr(exc, 'stacktrace', None) + screen = getattr(exc, "screen", None) + stacktrace = getattr(exc, "stacktrace", None) time.sleep(self._poll) if time.monotonic() > end_time: break diff --git a/py/selenium/webdriver/webkitgtk/options.py b/py/selenium/webdriver/webkitgtk/options.py index 968de846103d3..9f82e8830eade 100644 --- a/py/selenium/webdriver/webkitgtk/options.py +++ b/py/selenium/webdriver/webkitgtk/options.py @@ -20,11 +20,11 @@ class Options(ArgOptions): - KEY = 'webkitgtk:browserOptions' + KEY = "webkitgtk:browserOptions" def __init__(self) -> None: super().__init__() - self._binary_location = '' + self._binary_location = "" self._overlay_scrollbars_enabled = True @property diff --git a/py/selenium/webdriver/webkitgtk/service.py b/py/selenium/webdriver/webkitgtk/service.py index caae223ee991f..eb97e8d5235d6 100644 --- a/py/selenium/webdriver/webkitgtk/service.py +++ b/py/selenium/webdriver/webkitgtk/service.py @@ -25,8 +25,7 @@ class Service(service.Service): Object that manages the starting and stopping of the WebKitGTKDriver """ - def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, - port=0, log_path=None): + def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, port=0, log_path=None): """ Creates a new instance of the Service diff --git a/py/selenium/webdriver/webkitgtk/webdriver.py b/py/selenium/webdriver/webkitgtk/webdriver.py index ff25d2bdae564..4780f93f087b0 100644 --- a/py/selenium/webdriver/webkitgtk/webdriver.py +++ b/py/selenium/webdriver/webkitgtk/webdriver.py @@ -29,9 +29,15 @@ class WebDriver(RemoteWebDriver): Controls the WebKitGTKDriver and allows you to drive the browser. """ - def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, - port=0, options=None, desired_capabilities=None, - service_log_path=None, keep_alive=False): + def __init__( + self, + executable_path=DEFAULT_EXECUTABLE_PATH, + port=0, + options=None, + desired_capabilities=None, + service_log_path=None, + keep_alive=False, + ): """ Creates a new instance of the WebKitGTK driver. @@ -58,9 +64,8 @@ def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, self.service.start() super().__init__( - command_executor=self.service.service_url, - desired_capabilities=desired_capabilities, - keep_alive=keep_alive) + command_executor=self.service.service_url, desired_capabilities=desired_capabilities, keep_alive=keep_alive + ) self._is_remote = False def quit(self): diff --git a/py/selenium/webdriver/wpewebkit/options.py b/py/selenium/webdriver/wpewebkit/options.py index e3e03a6b63f98..c30977e54512d 100644 --- a/py/selenium/webdriver/wpewebkit/options.py +++ b/py/selenium/webdriver/wpewebkit/options.py @@ -20,11 +20,11 @@ class Options(ArgOptions): - KEY = 'wpe:browserOptions' + KEY = "wpe:browserOptions" def __init__(self) -> None: super().__init__() - self._binary_location = '' + self._binary_location = "" self._caps = DesiredCapabilities.WPEWEBKIT.copy() @property diff --git a/py/selenium/webdriver/wpewebkit/service.py b/py/selenium/webdriver/wpewebkit/service.py index 85781b12bd582..128e5fbb6a8db 100644 --- a/py/selenium/webdriver/wpewebkit/service.py +++ b/py/selenium/webdriver/wpewebkit/service.py @@ -25,8 +25,7 @@ class Service(service.Service): Object that manages the starting and stopping of the WPEWebKitDriver """ - def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, - port=0, log_path=None): + def __init__(self, executable_path: str = DEFAULT_EXECUTABLE_PATH, port=0, log_path=None): """ Creates a new instance of the Service diff --git a/py/selenium/webdriver/wpewebkit/webdriver.py b/py/selenium/webdriver/wpewebkit/webdriver.py index 1c0322409c4c0..437aa92b8b7f2 100644 --- a/py/selenium/webdriver/wpewebkit/webdriver.py +++ b/py/selenium/webdriver/wpewebkit/webdriver.py @@ -29,10 +29,14 @@ class WebDriver(RemoteWebDriver): Controls the WPEWebKitDriver and allows you to drive the browser. """ - def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, - port=0, options=None, - desired_capabilities=DesiredCapabilities.WPEWEBKIT, - service_log_path=None): + def __init__( + self, + executable_path=DEFAULT_EXECUTABLE_PATH, + port=0, + options=None, + desired_capabilities=DesiredCapabilities.WPEWEBKIT, + service_log_path=None, + ): """ Creates a new instance of the WPEWebKit driver. @@ -53,9 +57,7 @@ def __init__(self, executable_path=DEFAULT_EXECUTABLE_PATH, self.service = Service(executable_path, port=port, log_path=service_log_path) self.service.start() - super().__init__( - command_executor=self.service.service_url, - desired_capabilities=desired_capabilities) + super().__init__(command_executor=self.service.service_url, desired_capabilities=desired_capabilities) self._is_remote = False def quit(self): diff --git a/py/tox.ini b/py/tox.ini index 00fbb4b16bcbe..30eb0b0ac41e1 100644 --- a/py/tox.ini +++ b/py/tox.ini @@ -42,7 +42,7 @@ commands = ; execute isort in check only mode. isort --check-only --diff selenium/ test/ ; execute black in check only mode with diff. - black --check --diff test/ selenium/common/ selenium/webdriver/safari -l 120 + black --check --diff selenium/ test/ -l 120 flake8 selenium/ test/ --min-python-version=3.7 [testenv:linting] @@ -57,5 +57,5 @@ deps = flake8-typing-imports==1.13.0 commands = isort selenium/ test/ - black test/ selenium/common/ selenium/webdriver/safari selenium/webdriver/common/service.py -l 120 + black selenium/ test/ -l 120 flake8 selenium/ test/ --min-python-version=3.7