From 98d464061004eefd1c4c238ec42a91f4e59a232f Mon Sep 17 00:00:00 2001 From: Simon K Date: Mon, 28 Aug 2023 09:11:48 +0100 Subject: [PATCH] Drop py37 support (#12400) * [py]: Upgrading syntax to be `python3.8+` * [py]: Upgrade infrastructure for `python3.8+` * [py] Remove accidental commit of pre commit config used for upgrading * [py]: Remove py37 classifiers from bazel python packaging recipes * [py]: Apply `flake8` with py3.8 - update docstrings inline with `PEP-257` * [py]: Use more python3.8+ syntax * [py]: Remove old python3.7 comments in code * [py] Apply more python3.8+ type hints * [py]: Fix conflicts and make `CHANGES` accurate --------- Co-authored-by: Diego Molina --- .github/workflows/ci-python.yml | 12 +++--- py/BUILD.bazel | 3 +- py/CHANGES | 4 +- py/docs/source/index.rst | 4 +- py/selenium/common/exceptions.py | 1 - py/selenium/types.py | 1 - py/selenium/webdriver/chromium/options.py | 37 ++++++---------- py/selenium/webdriver/chromium/webdriver.py | 16 +++---- py/selenium/webdriver/common/action_chains.py | 43 +++++++++---------- .../webdriver/common/actions/key_actions.py | 7 +-- py/selenium/webdriver/common/alert.py | 1 - py/selenium/webdriver/common/by.py | 1 - .../webdriver/common/desired_capabilities.py | 1 - .../common/html5/application_cache.py | 1 - py/selenium/webdriver/common/keys.py | 1 - py/selenium/webdriver/common/options.py | 19 +++----- .../webdriver/common/print_page_options.py | 14 ++---- py/selenium/webdriver/common/proxy.py | 1 - .../webdriver/common/selenium_manager.py | 14 +++--- py/selenium/webdriver/common/timeouts.py | 11 +---- py/selenium/webdriver/common/utils.py | 1 - py/selenium/webdriver/common/window.py | 1 - py/selenium/webdriver/edge/options.py | 6 +-- .../webdriver/firefox/firefox_profile.py | 4 +- py/selenium/webdriver/firefox/options.py | 12 ++---- py/selenium/webdriver/ie/options.py | 4 +- py/selenium/webdriver/remote/mobile.py | 6 +-- .../webdriver/remote/remote_connection.py | 16 ++++--- py/selenium/webdriver/remote/webdriver.py | 5 +-- py/selenium/webdriver/remote/webelement.py | 3 +- py/selenium/webdriver/safari/permissions.py | 1 - py/selenium/webdriver/support/color.py | 3 +- .../webdriver/support/expected_conditions.py | 1 - .../webdriver/support/relative_locator.py | 24 +++++------ py/selenium/webdriver/webkitgtk/options.py | 9 ++-- .../webdriver/chrome/chrome_service_tests.py | 8 ++-- .../firefox/firefox_service_tests.py | 6 +-- py/tox.ini | 26 +++++------ 38 files changed, 127 insertions(+), 201 deletions(-) diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index 28fe08ac90f20..10f58695eee70 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -20,10 +20,10 @@ jobs: steps: - name: Checkout source tree uses: actions/checkout@v3 - - name: Set up Python 3.7 + - name: Set up Python 3.8 uses: actions/setup-python@v4 with: - python-version: 3.7.10 + python-version: 3.8 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -40,10 +40,10 @@ jobs: steps: - name: Checkout source tree uses: actions/checkout@v3 - - name: Set up Python 3.7 + - name: Set up Python 3.8 uses: actions/setup-python@v4 with: - python-version: 3.7.10 + python-version: 3.8 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -60,10 +60,10 @@ jobs: steps: - name: Checkout source tree uses: actions/checkout@v3 - - name: Set up Python 3.7 + - name: Set up Python 3.8 uses: actions/setup-python@v4 with: - python-version: 3.7.10 + python-version: 3.8 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/py/BUILD.bazel b/py/BUILD.bazel index a319676391691..a2704bf2c9bf2 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -203,7 +203,6 @@ py_wheel( "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -212,7 +211,7 @@ py_wheel( distribution = "selenium", homepage = "https://www.selenium.dev", license = "Apache 2.0", - python_requires = ">=3.7", + python_requires = ">=3.8", python_tag = "py3", requires = [ "urllib3[socks]>=1.26,<3", diff --git a/py/CHANGES b/py/CHANGES index daedd72302741..ca19d997267be 100644 --- a/py/CHANGES +++ b/py/CHANGES @@ -7,8 +7,8 @@ Selenium 4.12.0 * fix bug in common options (#12499) * allow setting http client certifications with REQUESTS_CA_BUNDLE env (#11957) * support conda installation of selenium manager (#12536) - - +* Drop support for `python3.7` +* Fixed a bug where `Popen.wait()` calls caught the wrong exceptions when timing out Selenium 4.11.2 * better bug fix for #12454 diff --git a/py/docs/source/index.rst b/py/docs/source/index.rst index c0c7b8672853a..b8e699bfd2020 100755 --- a/py/docs/source/index.rst +++ b/py/docs/source/index.rst @@ -24,7 +24,7 @@ Several browsers/drivers are supported (Firefox, Chrome, Internet Explorer), as Supported Python Versions ========================= -* Python 3.7+ +* Python 3.8+ Installing ========== @@ -148,7 +148,7 @@ Contributing - Create a branch for your work - Ensure `tox` is installed (using a `virtualenv` is recommended) - - `python3.7 -m venv .venv && . .venv/bin/activate && pip install tox` + - `python3.8 -m venv .venv && . .venv/bin/activate && pip install tox` - After making changes, before committing execute `tox -e linting` - If tox exits `0`, commit and push otherwise fix the newly introduced breakages. - `flake8` requires manual fixes diff --git a/py/selenium/common/exceptions.py b/py/selenium/common/exceptions.py index 806cec21f16e7..931eb307f809f 100644 --- a/py/selenium/common/exceptions.py +++ b/py/selenium/common/exceptions.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """Exceptions that may happen in all the webdriver code.""" from typing import Optional diff --git a/py/selenium/types.py b/py/selenium/types.py index 2c4753964bf94..a776904b4a4bd 100644 --- a/py/selenium/types.py +++ b/py/selenium/types.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """Selenium type definitions.""" import typing diff --git a/py/selenium/webdriver/chromium/options.py b/py/selenium/webdriver/chromium/options.py index 06c936a6eeec5..12fe1b5b52b28 100644 --- a/py/selenium/webdriver/chromium/options.py +++ b/py/selenium/webdriver/chromium/options.py @@ -39,15 +39,13 @@ def __init__(self) -> None: @property def binary_location(self) -> str: - """ - :Returns: The location of the binary, otherwise an empty string - """ + """:Returns: The location of the binary, otherwise an empty string.""" return self._binary_location @binary_location.setter def binary_location(self, value: str) -> None: - """ - Allows you to set where the chromium binary lives + """Allows you to set where the chromium binary lives. + :Args: - value: path to the Chromium binary """ @@ -57,17 +55,14 @@ def binary_location(self, value: str) -> None: @property def debugger_address(self) -> str: - """ - :Returns: The address of the remote devtools instance - """ + """:Returns: The address of the remote devtools instance.""" return self._debugger_address @debugger_address.setter def debugger_address(self, value: str) -> None: - """ - Allows you to set the address of the remote devtools instance - that the ChromeDriver instance will try to connect to during an - active wait. + """Allows you to set the address of the remote devtools instance that + the ChromeDriver instance will try to connect to during an active wait. + :Args: - value: address of remote devtools instance if any (hostname[:port]) """ @@ -77,9 +72,7 @@ def debugger_address(self, value: str) -> None: @property def extensions(self) -> List[str]: - """ - :Returns: A list of encoded extensions that will be loaded - """ + """: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 @@ -124,9 +117,7 @@ def add_encoded_extension(self, extension: str) -> None: @property def experimental_options(self) -> dict: - """ - :Returns: A dictionary of experimental options for chromium - """ + """:Returns: A dictionary of experimental options for chromium.""" return self._experimental_options def add_experimental_option(self, name: str, value: Union[str, int, dict, List[str]]) -> None: @@ -140,9 +131,7 @@ def add_experimental_option(self, name: str, value: Union[str, int, dict, List[s @property def headless(self) -> bool: - """ - :Returns: True if the headless argument is set, else False - """ + """:Returns: True if the headless argument is set, else False.""" warnings.warn( "headless property is deprecated, instead check for '--headless' in arguments", DeprecationWarning, @@ -176,10 +165,8 @@ def headless(self, value: bool) -> None: self._arguments = list(set(self._arguments) - args) def to_capabilities(self) -> dict: - """ - Creates a capabilities with all the options that have been set - :Returns: A dictionary with everything - """ + """Creates a capabilities with all the options that have been set + :Returns: A dictionary with everything.""" caps = self._caps chrome_options = self.experimental_options.copy() if self.mobile_options: diff --git a/py/selenium/webdriver/chromium/webdriver.py b/py/selenium/webdriver/chromium/webdriver.py index 71f127dd0b8aa..f3fe538d58737 100644 --- a/py/selenium/webdriver/chromium/webdriver.py +++ b/py/selenium/webdriver/chromium/webdriver.py @@ -75,10 +75,9 @@ def launch_app(self, id): def get_network_conditions(self): """Gets Chromium network emulation settings. - :Returns: - A dict. For example: - {'latency': 4, 'download_throughput': 2, 'upload_throughput': 2, - 'offline': False} + :Returns: A dict. For example: {'latency': 4, + 'download_throughput': 2, 'upload_throughput': 2, 'offline': + False} """ return self.execute("getNetworkConditions")["value"] @@ -140,15 +139,12 @@ def execute_cdp_cmd(self, cmd: str, cmd_args: dict): return self.execute("executeCdpCommand", {"cmd": cmd, "params": cmd_args})["value"] def get_sinks(self) -> list: - """ - :Returns: A list of sinks available for Cast. - """ + """:Returns: A list of sinks available for Cast.""" return self.execute("getSinks")["value"] def get_issue_message(self): - """ - :Returns: An error message when there is any issue in a Cast session. - """ + """:Returns: An error message when there is any issue in a Cast + session.""" return self.execute("getIssueMessage")["value"] def set_sink_to_use(self, sink_name: str) -> dict: diff --git a/py/selenium/webdriver/common/action_chains.py b/py/selenium/webdriver/common/action_chains.py index 015cad2c07f01..a0a0f2cd2d5ce 100644 --- a/py/selenium/webdriver/common/action_chains.py +++ b/py/selenium/webdriver/common/action_chains.py @@ -14,13 +14,10 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The ActionChains implementation.""" from __future__ import annotations from typing import TYPE_CHECKING -from typing import List -from typing import Optional from typing import Union from selenium.webdriver.remote.webelement import WebElement @@ -71,7 +68,7 @@ class ActionChains: another. """ - def __init__(self, driver: WebDriver, duration: int = 250, devices: Optional[List[AnyDevice]] = None) -> None: + def __init__(self, driver: WebDriver, duration: int = 250, devices: list[AnyDevice] | None = None) -> None: """Creates a new ActionChains. :Args: @@ -103,7 +100,7 @@ def reset_actions(self) -> None: for device in self.w3c_actions.devices: device.clear_actions() - def click(self, on_element: Optional[WebElement] = None) -> "ActionChains": + def click(self, on_element: WebElement | None = None) -> ActionChains: """Clicks an element. :Args: @@ -119,7 +116,7 @@ def click(self, on_element: Optional[WebElement] = None) -> "ActionChains": return self - def click_and_hold(self, on_element: Optional[WebElement] = None) -> "ActionChains": + def click_and_hold(self, on_element: WebElement | None = None) -> ActionChains: """Holds down the left mouse button on an element. :Args: @@ -134,7 +131,7 @@ def click_and_hold(self, on_element: Optional[WebElement] = None) -> "ActionChai return self - def context_click(self, on_element: Optional[WebElement] = None) -> "ActionChains": + def context_click(self, on_element: WebElement | None = None) -> ActionChains: """Performs a context-click (right click) on an element. :Args: @@ -150,7 +147,7 @@ def context_click(self, on_element: Optional[WebElement] = None) -> "ActionChain return self - def double_click(self, on_element: Optional[WebElement] = None) -> "ActionChains": + def double_click(self, on_element: WebElement | None = None) -> ActionChains: """Double-clicks an element. :Args: @@ -166,7 +163,7 @@ def double_click(self, on_element: Optional[WebElement] = None) -> "ActionChains return self - def drag_and_drop(self, source: WebElement, target: WebElement) -> "ActionChains": + def drag_and_drop(self, source: WebElement, target: WebElement) -> ActionChains: """Holds down the left mouse button on the source element, then moves to the target element and releases the mouse button. @@ -178,7 +175,7 @@ def drag_and_drop(self, source: WebElement, target: WebElement) -> "ActionChains self.release(target) return self - def drag_and_drop_by_offset(self, source: WebElement, xoffset: int, yoffset: int) -> "ActionChains": + def drag_and_drop_by_offset(self, source: WebElement, xoffset: int, yoffset: int) -> ActionChains: """Holds down the left mouse button on the source element, then moves to the target offset and releases the mouse button. @@ -192,7 +189,7 @@ def drag_and_drop_by_offset(self, source: WebElement, xoffset: int, yoffset: int self.release() return self - def key_down(self, value: str, element: Optional[WebElement] = None) -> "ActionChains": + def key_down(self, value: str, element: WebElement | None = None) -> ActionChains: """Sends a key press only, without releasing it. Should only be used with modifier keys (Control, Alt and Shift). @@ -213,7 +210,7 @@ def key_down(self, value: str, element: Optional[WebElement] = None) -> "ActionC return self - def key_up(self, value: str, element: Optional[WebElement] = None) -> "ActionChains": + def key_up(self, value: str, element: WebElement | None = None) -> ActionChains: """Releases a modifier key. :Args: @@ -233,7 +230,7 @@ def key_up(self, value: str, element: Optional[WebElement] = None) -> "ActionCha return self - def move_by_offset(self, xoffset: int, yoffset: int) -> "ActionChains": + def move_by_offset(self, xoffset: int, yoffset: int) -> ActionChains: """Moving the mouse to an offset from current mouse position. :Args: @@ -246,7 +243,7 @@ def move_by_offset(self, xoffset: int, yoffset: int) -> "ActionChains": return self - def move_to_element(self, to_element: WebElement) -> "ActionChains": + def move_to_element(self, to_element: WebElement) -> ActionChains: """Moving the mouse to the middle of an element. :Args: @@ -258,7 +255,7 @@ def move_to_element(self, to_element: WebElement) -> "ActionChains": return self - def move_to_element_with_offset(self, to_element: WebElement, xoffset: int, yoffset: int) -> "ActionChains": + def move_to_element_with_offset(self, to_element: WebElement, xoffset: int, yoffset: int) -> ActionChains: """Move the mouse by an offset of the specified element. Offsets are relative to the in-view center point of the element. @@ -273,7 +270,7 @@ def move_to_element_with_offset(self, to_element: WebElement, xoffset: int, yoff return self - def pause(self, seconds: Union[float, int]) -> "ActionChains": + def pause(self, seconds: float | int) -> ActionChains: """Pause all inputs for the specified duration in seconds.""" self.w3c_actions.pointer_action.pause(seconds) @@ -281,7 +278,7 @@ def pause(self, seconds: Union[float, int]) -> "ActionChains": return self - def release(self, on_element: Optional[WebElement] = None) -> "ActionChains": + def release(self, on_element: WebElement | None = None) -> ActionChains: """Releasing a held mouse button on an element. :Args: @@ -296,7 +293,7 @@ def release(self, on_element: Optional[WebElement] = None) -> "ActionChains": return self - def send_keys(self, *keys_to_send: str) -> "ActionChains": + def send_keys(self, *keys_to_send: str) -> ActionChains: """Sends keys to current focused element. :Args: @@ -311,7 +308,7 @@ def send_keys(self, *keys_to_send: str) -> "ActionChains": return self - def send_keys_to_element(self, element: WebElement, *keys_to_send: str) -> "ActionChains": + def send_keys_to_element(self, element: WebElement, *keys_to_send: str) -> ActionChains: """Sends keys to an element. :Args: @@ -323,7 +320,7 @@ def send_keys_to_element(self, element: WebElement, *keys_to_send: str) -> "Acti self.send_keys(*keys_to_send) return self - def scroll_to_element(self, element: WebElement) -> "ActionChains": + def scroll_to_element(self, element: WebElement) -> ActionChains: """If the element is outside the viewport, scrolls the bottom of the element to the bottom of the viewport. @@ -334,7 +331,7 @@ def scroll_to_element(self, element: WebElement) -> "ActionChains": self.w3c_actions.wheel_action.scroll(origin=element) return self - def scroll_by_amount(self, delta_x: int, delta_y: int) -> "ActionChains": + def scroll_by_amount(self, delta_x: int, delta_y: int) -> ActionChains: """Scrolls by provided amounts with the origin in the top left corner of the viewport. @@ -346,7 +343,7 @@ def scroll_by_amount(self, delta_x: int, delta_y: int) -> "ActionChains": self.w3c_actions.wheel_action.scroll(delta_x=delta_x, delta_y=delta_y) return self - def scroll_from_origin(self, scroll_origin: ScrollOrigin, delta_x: int, delta_y: int) -> "ActionChains": + def scroll_from_origin(self, scroll_origin: ScrollOrigin, delta_x: int, delta_y: int) -> ActionChains: """Scrolls by provided amount based on a provided origin. The scroll origin is either the center of an element or the upper left of the viewport plus any offsets. If the origin is an element, and the element @@ -376,7 +373,7 @@ def scroll_from_origin(self, scroll_origin: ScrollOrigin, delta_x: int, delta_y: # Context manager so ActionChains can be used in a 'with .. as' statements. - def __enter__(self) -> "ActionChains": + def __enter__(self) -> ActionChains: return self # Return created instance of self. def __exit__(self, _type, _value, _traceback) -> None: diff --git a/py/selenium/webdriver/common/actions/key_actions.py b/py/selenium/webdriver/common/actions/key_actions.py index d3a220315f84e..5f29d724f8d38 100644 --- a/py/selenium/webdriver/common/actions/key_actions.py +++ b/py/selenium/webdriver/common/actions/key_actions.py @@ -16,9 +16,6 @@ # under the License. from __future__ import annotations -from typing import Optional -from typing import Union - from ..utils import keys_to_typing from .interaction import KEY from .interaction import Interaction @@ -28,7 +25,7 @@ class KeyActions(Interaction): - def __init__(self, source: Optional[KeyInput, PointerInput, WheelInput] = None) -> None: + def __init__(self, source: KeyInput | PointerInput | WheelInput | None = None) -> None: if not source: source = KeyInput(KEY) self.source = source @@ -43,7 +40,7 @@ def key_up(self, letter: str) -> KeyActions: def pause(self, duration: int = 0) -> KeyActions: return self._key_action("create_pause", duration) - def send_keys(self, text: Union[str, list]) -> KeyActions: + def send_keys(self, text: str | list) -> KeyActions: if not isinstance(text, list): text = keys_to_typing(text) for letter in text: diff --git a/py/selenium/webdriver/common/alert.py b/py/selenium/webdriver/common/alert.py index ae2675e505540..e7fd64b72786b 100644 --- a/py/selenium/webdriver/common/alert.py +++ b/py/selenium/webdriver/common/alert.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The Alert implementation.""" from selenium.webdriver.common.utils import keys_to_typing diff --git a/py/selenium/webdriver/common/by.py b/py/selenium/webdriver/common/by.py index b3b7fa8860952..f24a113ddb1be 100644 --- a/py/selenium/webdriver/common/by.py +++ b/py/selenium/webdriver/common/by.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The By implementation.""" diff --git a/py/selenium/webdriver/common/desired_capabilities.py b/py/selenium/webdriver/common/desired_capabilities.py index 760eba727704c..cd5fd21aa3f46 100644 --- a/py/selenium/webdriver/common/desired_capabilities.py +++ b/py/selenium/webdriver/common/desired_capabilities.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The Desired Capabilities implementation.""" diff --git a/py/selenium/webdriver/common/html5/application_cache.py b/py/selenium/webdriver/common/html5/application_cache.py index 1590f1950a738..2b8e0f3abede4 100644 --- a/py/selenium/webdriver/common/html5/application_cache.py +++ b/py/selenium/webdriver/common/html5/application_cache.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The ApplicationCache implementation.""" diff --git a/py/selenium/webdriver/common/keys.py b/py/selenium/webdriver/common/keys.py index 87ac1077f834d..c2e7e6e822e48 100644 --- a/py/selenium/webdriver/common/keys.py +++ b/py/selenium/webdriver/common/keys.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The Keys implementation.""" diff --git a/py/selenium/webdriver/common/options.py b/py/selenium/webdriver/common/options.py index 61f7cc6b96e6e..f08c8db6c326b 100644 --- a/py/selenium/webdriver/common/options.py +++ b/py/selenium/webdriver/common/options.py @@ -104,9 +104,7 @@ def __set__(self, obj, value): class _ProxyDescriptor: - """ - :Returns: Proxy if set, otherwise None. - """ + """:Returns: Proxy if set, otherwise None.""" def __init__(self, name): self.name = name @@ -235,8 +233,7 @@ class BaseOptions(metaclass=ABCMeta): """ # Creating _PageLoadStrategy descriptor page_load_strategy = _PageLoadStrategyDescriptor("pageLoadStrategy") - """ - :Gets and Sets page load strategy, the default is "normal". + """:Gets and Sets page load strategy, the default is "normal". Usage ----- @@ -258,8 +255,8 @@ class BaseOptions(metaclass=ABCMeta): """ # Creating _UnHandledPromptBehavior descriptor unhandled_prompt_behavior = _UnHandledPromptBehaviorDescriptor("unhandledPromptBehavior") - """ - :Gets and Sets unhandled prompt behavior, the default is "dismiss and notify" + """:Gets and Sets unhandled prompt behavior, the default is "dismiss and + notify". Usage ----- @@ -282,8 +279,8 @@ class BaseOptions(metaclass=ABCMeta): # Creating _Timeouts descriptor timeouts = _TimeoutsDescriptor("timeouts") - """ - :Gets and Sets implicit timeout, pageLoad timeout and script timeout if set (in milliseconds) + """:Gets and Sets implicit timeout, pageLoad timeout and script timeout if + set (in milliseconds) Usage ----- @@ -381,9 +378,7 @@ def __init__(self) -> None: @property def arguments(self): - """ - :Returns: A list of arguments needed for the browser - """ + """:Returns: A list of arguments needed for the browser.""" return self._arguments def add_argument(self, argument): diff --git a/py/selenium/webdriver/common/print_page_options.py b/py/selenium/webdriver/common/print_page_options.py index 0bbef2bdbeb67..8673f58610f04 100644 --- a/py/selenium/webdriver/common/print_page_options.py +++ b/py/selenium/webdriver/common/print_page_options.py @@ -16,19 +16,13 @@ # under the License. -import sys from typing import TYPE_CHECKING from typing import List from typing import Optional -# necessary to support types for Python 3.7 if TYPE_CHECKING: - if sys.version_info >= (3, 8): - from typing import Literal - from typing import TypedDict - else: - from typing_extensions import Literal - from typing_extensions import TypedDict + from typing import Literal + from typing import TypedDict Orientation = Literal["portrait", "landscape"] @@ -412,9 +406,7 @@ def __init__(self) -> None: self._margin: _MarginOpts = {} def to_dict(self) -> _PrintOpts: - """ - :Returns: A hash of print options configured - """ + """:Returns: A hash of print options configured.""" return self._print_options def _validate_num_property(self, property_name: str, value: float) -> None: diff --git a/py/selenium/webdriver/common/proxy.py b/py/selenium/webdriver/common/proxy.py index 9c96351ced3f7..145b5fb9548c9 100644 --- a/py/selenium/webdriver/common/proxy.py +++ b/py/selenium/webdriver/common/proxy.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The Proxy implementation.""" diff --git a/py/selenium/webdriver/common/selenium_manager.py b/py/selenium/webdriver/common/selenium_manager.py index 25790bacb323a..ce6a07dcc2081 100644 --- a/py/selenium/webdriver/common/selenium_manager.py +++ b/py/selenium/webdriver/common/selenium_manager.py @@ -66,8 +66,8 @@ def get_binary() -> Path: return path def driver_location(self, options: BaseOptions) -> str: - """ - Determines the path of the correct driver. + """Determines the path of the correct driver. + :Args: - browser: which browser to get the driver path for. :Returns: The driver path to use @@ -106,8 +106,8 @@ def driver_location(self, options: BaseOptions) -> str: @staticmethod def run(args: List[str]) -> dict: - """ - Executes the Selenium Manager Binary. + """Executes the Selenium Manager Binary. + :Args: - args: the components of the command being executed. :Returns: The log string containing the driver location. @@ -121,11 +121,9 @@ def run(args: List[str]) -> dict: logger.debug(f"Executing process: {command}") try: if sys.platform == "win32": - completed_proc = subprocess.run( - args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW - ) + completed_proc = subprocess.run(args, capture_output=True, creationflags=subprocess.CREATE_NO_WINDOW) else: - completed_proc = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + completed_proc = subprocess.run(args, capture_output=True) stdout = completed_proc.stdout.decode("utf-8").rstrip("\n") stderr = completed_proc.stderr.decode("utf-8").rstrip("\n") output = json.loads(stdout) diff --git a/py/selenium/webdriver/common/timeouts.py b/py/selenium/webdriver/common/timeouts.py index 12519c9d6f72e..10423ad3cb19c 100644 --- a/py/selenium/webdriver/common/timeouts.py +++ b/py/selenium/webdriver/common/timeouts.py @@ -18,12 +18,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - import sys - - if sys.version_info >= (3, 8): - from typing import TypedDict - else: - from typing_extensions import TypedDict + from typing import TypedDict class JSONTimeouts(TypedDict, total=False): implicit: int @@ -39,9 +34,7 @@ class JSONTimeouts(TypedDict, total=False): class _TimeoutsDescriptor: """Get or set the value of the attributes listed below. - _implicit_wait - _page_load - _script + _implicit_wait _page_load _script This does not set the value on the remote end. """ diff --git a/py/selenium/webdriver/common/utils.py b/py/selenium/webdriver/common/utils.py index 1f9e28a8b35e7..6b63b9ff4cb08 100644 --- a/py/selenium/webdriver/common/utils.py +++ b/py/selenium/webdriver/common/utils.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The Utils methods.""" import socket diff --git a/py/selenium/webdriver/common/window.py b/py/selenium/webdriver/common/window.py index 80b61c26fb6df..64c72510e3b22 100644 --- a/py/selenium/webdriver/common/window.py +++ b/py/selenium/webdriver/common/window.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The WindowTypes implementation.""" diff --git a/py/selenium/webdriver/edge/options.py b/py/selenium/webdriver/edge/options.py index 949378223c57a..bb4fe42df6e89 100644 --- a/py/selenium/webdriver/edge/options.py +++ b/py/selenium/webdriver/edge/options.py @@ -35,10 +35,8 @@ def use_webview(self, value: bool) -> None: self._use_webview = bool(value) def to_capabilities(self) -> dict: - """ - Creates a capabilities with all the options that have been set and - :Returns: A dictionary with everything - """ + """Creates a capabilities with all the options that have been set and + :Returns: A dictionary with everything.""" caps = super().to_capabilities() if self._use_webview: caps["browserName"] = "webview2" diff --git a/py/selenium/webdriver/firefox/firefox_profile.py b/py/selenium/webdriver/firefox/firefox_profile.py index 570b03a06d116..3b6865f025de9 100644 --- a/py/selenium/webdriver/firefox/firefox_profile.py +++ b/py/selenium/webdriver/firefox/firefox_profile.py @@ -83,7 +83,7 @@ def __init__(self, profile_directory=None): # Public Methods def set_preference(self, key, value): - """sets the preference that we want in the profile.""" + """Sets the preference that we want in the profile.""" self.default_preferences[key] = value def add_extension(self, extension=WEBDRIVER_EXT): @@ -164,7 +164,7 @@ def _create_tempfolder(self): return tempfile.mkdtemp() def _write_user_prefs(self, user_prefs): - """writes the current user prefs dictionary to disk.""" + """Writes the current user prefs dictionary to disk.""" 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') diff --git a/py/selenium/webdriver/firefox/options.py b/py/selenium/webdriver/firefox/options.py index 5d312d0d0c73c..d3c20764a9aa5 100644 --- a/py/selenium/webdriver/firefox/options.py +++ b/py/selenium/webdriver/firefox/options.py @@ -59,9 +59,7 @@ def binary(self, new_binary: Union[str, FirefoxBinary]) -> None: @property def binary_location(self) -> str: - """ - :Returns: The location of the binary. - """ + """:Returns: The location of the binary.""" return self.binary._start_cmd @binary_location.setter # noqa @@ -82,9 +80,7 @@ def set_preference(self, name: str, value: Union[str, int, bool]): @property def profile(self) -> FirefoxProfile: - """ - :Returns: The Firefox profile to use. - """ + """:Returns: The Firefox profile to use.""" if self._profile: warnings.warn("Getting a profile has been deprecated.", DeprecationWarning, stacklevel=2) return self._profile @@ -104,9 +100,7 @@ def profile(self, new_profile: Union[str, FirefoxProfile]) -> None: @property def headless(self) -> bool: - """ - :Returns: True if the headless argument is set, else False - """ + """:Returns: True if the headless argument is set, else False.""" warnings.warn( "headless property is deprecated, instead check for '-headless' in arguments", DeprecationWarning, diff --git a/py/selenium/webdriver/ie/options.py b/py/selenium/webdriver/ie/options.py index 6fce1c0b3b623..4fcef362bdbb7 100644 --- a/py/selenium/webdriver/ie/options.py +++ b/py/selenium/webdriver/ie/options.py @@ -367,12 +367,12 @@ def __init__(self) -> None: @property def options(self) -> dict: - """:Returns: A dictionary of browser options""" + """:Returns: A dictionary of browser options.""" return self._options @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): diff --git a/py/selenium/webdriver/remote/mobile.py b/py/selenium/webdriver/remote/mobile.py index a8e476d6939cc..bdab75860b876 100644 --- a/py/selenium/webdriver/remote/mobile.py +++ b/py/selenium/webdriver/remote/mobile.py @@ -67,15 +67,15 @@ def set_network_connection(self, network): @property def context(self): - """returns the current context (Native or WebView).""" + """Returns the current context (Native or WebView).""" return self._driver.execute(Command.CURRENT_CONTEXT_HANDLE) @context.setter def context(self, new_context) -> None: - """sets the current context.""" + """Sets the current context.""" self._driver.execute(Command.SWITCH_TO_CONTEXT, {"name": new_context}) @property def contexts(self): - """returns a list of available contexts.""" + """Returns a list of available contexts.""" return self._driver.execute(Command.CONTEXT_HANDLES) diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index 2a4fec6768f33..1bc6ec5f4c41c 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -138,9 +138,10 @@ class RemoteConnection: @classmethod def get_timeout(cls): - """ - :Returns: - Timeout value in seconds for all http requests made to the Remote Connection + """:Returns: + + Timeout value in seconds for all http requests made to the + Remote Connection """ return None if cls._timeout == socket._GLOBAL_DEFAULT_TIMEOUT else cls._timeout @@ -160,10 +161,11 @@ def reset_timeout(cls): @classmethod def get_certificate_bundle_path(cls): - """ - :Returns: - Paths of the .pem encoded certificate to verify connection to command executor. Defaults - to certifi.where() or REQUESTS_CA_BUNDLE env variable if set. + """:Returns: + + Paths of the .pem encoded certificate to verify connection to + command executor. Defaults to certifi.where() or + REQUESTS_CA_BUNDLE env variable if set. """ return cls._ca_certs diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index de3c030d0e19b..6abc5f35657ea 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The WebDriver implementation.""" import contextlib import copy @@ -770,13 +769,13 @@ def find_elements(self, by=By.ID, value: Optional[str] = None) -> List[WebElemen @property def desired_capabilities(self) -> dict: - """returns the drivers current desired capabilities being used.""" + """Returns the drivers current desired capabilities being used.""" warnings.warn("desired_capabilities is deprecated. Please call capabilities.", DeprecationWarning, stacklevel=2) return self.caps @property def capabilities(self) -> dict: - """returns the drivers current capabilities being used.""" + """Returns the drivers current capabilities being used.""" return self.caps def get_screenshot_as_file(self, filename) -> bool: diff --git a/py/selenium/webdriver/remote/webelement.py b/py/selenium/webdriver/remote/webelement.py index fe2626a8f2817..319268b8c2175 100644 --- a/py/selenium/webdriver/remote/webelement.py +++ b/py/selenium/webdriver/remote/webelement.py @@ -25,7 +25,6 @@ from base64 import encodebytes from hashlib import md5 as md5_hash from io import BytesIO -from typing import List from selenium.common.exceptions import JavascriptException from selenium.common.exceptions import WebDriverException @@ -416,7 +415,7 @@ def find_element(self, by=By.ID, value=None) -> WebElement: return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})["value"] - def find_elements(self, by=By.ID, value=None) -> List[WebElement]: + def find_elements(self, by=By.ID, value=None) -> list[WebElement]: """Find elements given a By strategy and locator. :Usage: diff --git a/py/selenium/webdriver/safari/permissions.py b/py/selenium/webdriver/safari/permissions.py index 91751fa76c9db..0e3a37dc8f14d 100644 --- a/py/selenium/webdriver/safari/permissions.py +++ b/py/selenium/webdriver/safari/permissions.py @@ -14,7 +14,6 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - """The Permission implementation.""" diff --git a/py/selenium/webdriver/support/color.py b/py/selenium/webdriver/support/color.py index 55a8819afd91f..f6c31578c4b5b 100644 --- a/py/selenium/webdriver/support/color.py +++ b/py/selenium/webdriver/support/color.py @@ -28,11 +28,10 @@ if TYPE_CHECKING: from typing import SupportsFloat + from typing import SupportsIndex from typing import SupportsInt from typing import Union - from typing_extensions import SupportsIndex - ParseableFloat = Union[SupportsFloat, SupportsIndex, str, bytes, bytearray] ParseableInt = Union[SupportsInt, SupportsIndex, str, bytes] else: diff --git a/py/selenium/webdriver/support/expected_conditions.py b/py/selenium/webdriver/support/expected_conditions.py index 27e669638ec93..65006552996dc 100644 --- a/py/selenium/webdriver/support/expected_conditions.py +++ b/py/selenium/webdriver/support/expected_conditions.py @@ -36,7 +36,6 @@ # All driver types AnyDriver = Union[Chrome, Firefox, Safari, Ie, Edge] - """ * Canned "Expected Conditions" which are generally useful within webdriver * tests. diff --git a/py/selenium/webdriver/support/relative_locator.py b/py/selenium/webdriver/support/relative_locator.py index 41992102c412d..e1455790dbbd5 100644 --- a/py/selenium/webdriver/support/relative_locator.py +++ b/py/selenium/webdriver/support/relative_locator.py @@ -71,9 +71,9 @@ class RelativeBy: """ def __init__(self, root: Optional[Dict[Union[By, str], str]] = None, filters: Optional[List] = None): - """ - Creates a new RelativeBy object. It is preferred if you use the + """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 @@ -83,8 +83,8 @@ def __init__(self, root: Optional[Dict[Union[By, str], str]] = None, filters: Op self.filters = filters or [] def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": - """ - Add a filter to look for elements above. + """Add a filter to look for elements above. + :Args: - element_or_locator: Element to look above """ @@ -95,8 +95,8 @@ def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "Relative return self def below(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": - """ - Add a filter to look for elements below. + """Add a filter to look for elements below. + :Args: - element_or_locator: Element to look below """ @@ -107,8 +107,8 @@ def below(self, element_or_locator: Union[WebElement, Dict] = None) -> "Relative return self def to_left_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": - """ - Add a filter to look for elements 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 """ @@ -119,8 +119,8 @@ def to_left_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Rel return self def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy": - """ - Add a filter to look for elements right of. + """Add a filter to look for elements right of. + :Args: - element_or_locator: Element to look right of """ @@ -131,8 +131,8 @@ def to_right_of(self, element_or_locator: Union[WebElement, Dict] = None) -> "Re return self def near(self, element_or_locator_distance: Union[WebElement, Dict, int] = None) -> "RelativeBy": - """ - Add a filter to look for elements near. + """Add a filter to look for elements near. + :Args: - element_or_locator_distance: Element to look near by the element or within a distance """ diff --git a/py/selenium/webdriver/webkitgtk/options.py b/py/selenium/webdriver/webkitgtk/options.py index 23fab29708eaa..bb2d82d64d81e 100644 --- a/py/selenium/webdriver/webkitgtk/options.py +++ b/py/selenium/webdriver/webkitgtk/options.py @@ -29,9 +29,8 @@ def __init__(self) -> None: @property def binary_location(self) -> str: - """ - :Returns: The location of the browser binary otherwise an empty string - """ + """:Returns: The location of the browser binary otherwise an empty + string.""" return self._binary_location @binary_location.setter @@ -45,9 +44,7 @@ def binary_location(self, value: str) -> None: @property def overlay_scrollbars_enabled(self): - """ - :Returns: Whether overlay scrollbars should be enabled - """ + """:Returns: Whether overlay scrollbars should be enabled.""" return self._overlay_scrollbars_enabled @overlay_scrollbars_enabled.setter diff --git a/py/test/selenium/webdriver/chrome/chrome_service_tests.py b/py/test/selenium/webdriver/chrome/chrome_service_tests.py index 1b9595f2ad4f2..1e03990006d2f 100644 --- a/py/test/selenium/webdriver/chrome/chrome_service_tests.py +++ b/py/test/selenium/webdriver/chrome/chrome_service_tests.py @@ -39,10 +39,10 @@ def test_uses_chromedriver_logging() -> None: service = Service(log_output=log_file, service_args=service_args) try: driver1 = Chrome(service=service) - with open(log_file, "r") as fp: + with open(log_file) as fp: lines = len(fp.readlines()) driver2 = Chrome(service=service) - with open(log_file, "r") as fp: + with open(log_file) as fp: assert len(fp.readlines()) >= 2 * lines finally: driver1.quit() @@ -55,7 +55,7 @@ def test_log_output_as_filename() -> None: service = Service(log_output=log_file) try: driver = Chrome(service=service) - with open(log_file, "r") as fp: + with open(log_file) as fp: assert "Starting ChromeDriver" in fp.readline() finally: driver.quit() @@ -69,7 +69,7 @@ def test_log_output_as_file() -> None: try: driver = Chrome(service=service) time.sleep(1) - with open(log_name, "r") as fp: + with open(log_name) as fp: assert "Starting ChromeDriver" in fp.readline() finally: driver.quit() diff --git a/py/test/selenium/webdriver/firefox/firefox_service_tests.py b/py/test/selenium/webdriver/firefox/firefox_service_tests.py index 9ef1f55330f14..601c749c7c306 100644 --- a/py/test/selenium/webdriver/firefox/firefox_service_tests.py +++ b/py/test/selenium/webdriver/firefox/firefox_service_tests.py @@ -36,7 +36,7 @@ def test_log_output_as_filename() -> None: service = Service(log_output=log_file) try: driver = Firefox(service=service) - with open(log_file, "r") as fp: + with open(log_file) as fp: assert "geckodriver\tINFO\tListening" in fp.readline() finally: driver.quit() @@ -49,7 +49,7 @@ def test_log_output_as_file() -> None: service = Service(log_output=log_file) try: driver = Firefox(service=service) - with open(log_name, "r") as fp: + with open(log_name) as fp: assert "geckodriver\tINFO\tListening" in fp.readline() finally: driver.quit() @@ -73,7 +73,7 @@ def test_log_output_default_deprecated() -> None: try: with pytest.warns(match=msg, expected_warning=DeprecationWarning): driver = Firefox() - with open(log_name, "r") as fp: + with open(log_name) as fp: assert "geckodriver\tINFO\tListening" in fp.readline() finally: driver.quit() diff --git a/py/tox.ini b/py/tox.ini index 937a908c6a373..12bb6319b8ff7 100644 --- a/py/tox.ini +++ b/py/tox.ini @@ -27,7 +27,7 @@ commands = mypy --install-types {posargs} ; PEP recommended sections (https://peps.python.org/pep-0008/#imports) ; files or individual lines can be ignored via `# isort:skip|# isort:skip_file`. profile = black -py_version=37 +py_version=38 force_single_line = True @@ -35,19 +35,17 @@ force_single_line = True ; checks linting for CI with stricter exiting when failing. skip_install = true deps = - ; isort 5.12+ requires python3.8+ specifically. - isort==5.11.5 - black==23.1.0 - ; flake8 6+ requires python3.8+ specifically. - flake8==5.0.4 + isort==5.12.0 + black==23.7.0 + flake8==6.0.0 flake8-typing-imports==1.14.0 - docformatter==1.5.1 + docformatter==1.7.5 commands = ; execute isort in check only mode. isort --check-only --diff selenium/ test/ conftest.py ; execute black in check only mode with diff. black --check --diff selenium/ test/ conftest.py -l 120 - flake8 selenium/ test/ --min-python-version=3.7 + flake8 selenium/ test/ --min-python-version=3.8 docformatter --check -r selenium/ [testenv:linting] @@ -56,15 +54,13 @@ commands = ; IMPORTANT: black & isort rewrite files, flake8 merely alerts to the failure. skip_install = true deps = - ; isort 5.12+ requires python3.8+ specifically. - isort==5.11.5 - black==23.1.0 - ; flake8 6+ requires python3.8+ specifically. - flake8==5.0.4 + isort==5.12.0 + black==23.7.0 + flake8==6.0.0 flake8-typing-imports==1.14.0 - docformatter==1.5.1 + docformatter==1.7.5 commands = isort selenium/ test/ conftest.py black selenium/ test/ conftest.py -l 120 - flake8 selenium/ test/ --min-python-version=3.7 + flake8 selenium/ test/ --min-python-version=3.8 docformatter --in-place -r selenium/