From 3cf3262e9bbda4ad2866fca570b1e5fb31d7e3ce Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Mon, 13 Apr 2026 18:58:44 +0200 Subject: [PATCH 01/13] Migrate from flake8 to Ruff for linting and formatting; update Python support to 3.10+ --- .flake8 | 4 ---- .github/workflows/CI.yml | 12 ++++++------ CONTRIBUTING.rst | 18 +++--------------- README.rst | 2 +- docs/index.html | 2 +- pyproject.toml | 26 ++++++++++---------------- requirements-dev.txt | 3 +-- setup.py | 2 +- tasks.py | 8 ++++---- 9 files changed, 27 insertions(+), 50 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 6959f26dc..000000000 --- a/.flake8 +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -exclude = - __pycache__, -ignore = E501 diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 93b6c0bbe..82fcad60d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -9,8 +9,8 @@ jobs: continue-on-error: true strategy: matrix: - python-version: [3.9.23, 3.13.5, 3.14.0-rc.3] # pypy-3.9 - # python-version: [{earliest: 3.9}, {latest: 3.13.0}] # pypy-3.9 + python-version: [3.10.16, 3.13.5, 3.14.0-rc.3, pypy-3.10] + # python-version: [{earliest: 3.10}, {latest: 3.14.0-rc.3}, {pypy: pypy-3.10}] rf-version: [6.1.1, 7.3.2] selenium-version: [4.28.1, 4.29.0, 4.30.0, 4.31.0, 4.32.0, 4.33.0, 4.34.2] browser: [chrome] # firefox, chrome, headlesschrome, edge @@ -44,12 +44,12 @@ jobs: export DISPLAY=:99.0 Xvfb -ac :99 -screen 0 1280x1024x16 > /dev/null 2>&1 & - name: Install dependencies - if: matrix.python-version != 'pypy-3.7' + if: matrix.python-version != 'pypy-3.10' run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Install dependencies for pypy - if: matrix.python-version == 'pypy-3.9' + if: matrix.python-version == 'pypy-3.10' run: | python -m pip install --upgrade pip pip install -r requirements.txt @@ -68,7 +68,7 @@ jobs: echo "WEBDRIVERPATH=$($SELENIUM_MANAGER_EXE --browser chrome --debug | awk '/INFO[[:space:]]Driver path:/ {print $NF;exit}')" >> "$GITHUB_ENV" echo "$WEBDRIVERPATH" - name: Generate stub file for ${{ matrix.python-version }} - if: matrix.python-version != 'pypy-3.9' + if: matrix.python-version != 'pypy-3.10' run: | invoke gen-stub @@ -89,7 +89,7 @@ jobs: xvfb-run --auto-servernum python atest/run.py --zip ${{ matrix.browser }} # - name: Run tests with Selenium Grid - # if: matrix.python-version == '3.11' && matrix.rf-version == '3.2.2' && matrix.python-version != 'pypy-3.9' + # if: matrix.python-version == '3.11' && matrix.rf-version == '3.2.2' && matrix.python-version != 'pypy-3.10' # run: | # wget --no-verbose --output-document=./selenium-server-standalone.jar http://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar # sudo chmod u+x ./selenium-server-standalone.jar diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ffb9d6ff8..401671781 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -118,22 +118,11 @@ needed in internal code. When docstrings are added, they should follow `PEP-257`_. See `Documentation`_ section below for more details about documentation syntax, generating docs, etc. -The code should be formatted with `Black`_ and errors found by `flake8`_ -should be fixed. Black and flake8 can be run by using -command:: +The code should be formatted and linted with `Ruff`_. Ruff can be run by +using command:: inv lint -By default flake8 ignores line length error E501, but it does not ignore -warning W503. In practice Black formats list access like this:: - - list[1 : 2] - -But flake8 will display an warning about it. This should be manually -fixed to look like:: - - list[1:2] - Documentation ------------- @@ -245,5 +234,4 @@ the same code as your changes. In that case you should .. _utest/README.rst: https://github.com/robotframework/SeleniumLibrary/blob/master/utest/README.rst .. _sync your fork: https://help.github.com/articles/syncing-a-fork/ .. _resolve conflicts: https://help.github.com/articles/resolving-a-merge-conflict-from-the-command-line -.. _Black: https://github.com/psf/black -.. _flake8: https://github.com/PyCQA/flake8 \ No newline at end of file +.. _Ruff: https://github.com/astral-sh/ruff \ No newline at end of file diff --git a/README.rst b/README.rst index 1f4346024..2ec34f134 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ SeleniumLibrary_ is a web testing library for `Robot Framework`_ that utilizes the Selenium_ tool internally. The project is hosted on GitHub_ and downloads can be found from PyPI_. -SeleniumLibrary currently works with Selenium 4. It supports Python 3.8 through 3.13. +SeleniumLibrary currently works with Selenium 4. It supports Python 3.10 through 3.13. In addition to the normal Python_ interpreter, it works also with PyPy_. diff --git a/docs/index.html b/docs/index.html index 128edf2fa..15053c378 100644 --- a/docs/index.html +++ b/docs/index.html @@ -29,7 +29,7 @@

Introduction<

SeleniumLibrary is a web testing library for Robot Framework that utilizes the Selenium tool internally. The project is hosted on GitHub and downloads can be found from PyPI.

-

SeleniumLibrary currently works with Selenium 4. It supports Python 3.8 through 3.13. +

SeleniumLibrary currently works with Selenium 4. It supports Python 3.10 through 3.13. In addition to the normal Python interpreter, it works also with PyPy.

SeleniumLibrary is based on the "old SeleniumLibrary" that was forked to diff --git a/pyproject.toml b/pyproject.toml index dd1b9775e..fffbff219 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,14 @@ -[tool.black] -target-version = ['py36'] -exclude = ''' -/( - | \.git - | \.venv - | _build - | dist - | generated - | src/SeleniumLibrary/__init__\.pyi -)/ -''' +[tool.ruff] +target-version = "py310" +line-length = 88 +extend-exclude = ["_build", "generated", "src/SeleniumLibrary/__init__.pyi"] -[tool.isort] -profile = "black" -src_paths="." -skip_glob = ["src/SeleniumLibrary/__init__.pyi"] +[tool.ruff.lint] +select = ["E", "F", "W", "I"] +ignore = ["E501"] + +[tool.ruff.format] +quote-style = "double" [tool.pytest.ini_options] diff --git a/requirements-dev.txt b/requirements-dev.txt index 90b82779d..975e6fb6b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,8 +15,7 @@ pytest-mockito == 0.0.4 pytest-approvaltests == 0.2.4 requests == 2.33.1 robotframework-pabot == 5.2.2 -black == 26.3.1 -flake8 == 6.1.0 +ruff == 0.4.10 # Requirements needed when generating releases. See BUILD.rst for details. rellu == 0.7 diff --git a/setup.py b/setup.py index f24a0b8a9..9652afb5d 100755 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ keywords = 'robotframework testing testautomation selenium webdriver web', platforms = 'any', classifiers = CLASSIFIERS, - python_requires = '>=3.8', + python_requires = '>=3.10', install_requires = REQUIREMENTS, package_dir = {'': 'src'}, packages = find_packages('src'), diff --git a/tasks.py b/tasks.py index 84c093b98..2948421b0 100644 --- a/tasks.py +++ b/tasks.py @@ -187,9 +187,9 @@ def init_labels(ctx, username=None, password=None): @task def lint(ctx): - """Runs black and flake8 for project Python code.""" - ctx.run("black --config pyproject.toml tasks.py src/ utest/ atest/") - ctx.run("flake8 --config .flake8 tasks.py src/ utest/ atest/") + """Runs Ruff format check and linter for project Python code.""" + ctx.run(f"{sys.executable} -m ruff format --check tasks.py src/ utest/ atest/") + ctx.run(f"{sys.executable} -m ruff check tasks.py src/ utest/ atest/") @task @@ -207,7 +207,7 @@ def atest(ctx, suite=None): Args: suite: Select which suite to run. - + Example: inv utest --suite keywords/test_browsermanagement.py inv utest --suite keywords/test_selenium_options_parser.py::test_create_chrome_with_options From a401b04d56f21a90beb65e6cf2c97128f7904c3e Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sat, 25 Apr 2026 16:53:41 +0200 Subject: [PATCH 02/13] Refactor and optimize code across multiple modules - Updated type hints to use the new syntax (e.g., `list[str]` instead of `List[str]`). - Improved readability by replacing `isinstance` checks with more concise conditions. - Simplified exception handling and error messages for better clarity. - Enhanced the `WindowInfo` class by switching from `namedtuple` to `NamedTuple`. - Streamlined the `get_window_infos` method to reduce redundancy. - Replaced assertions in unit tests with assert statements for consistency. - Updated the linting task to allow automatic fixing of issues. - Removed unnecessary imports and cleaned up code formatting. --- pyproject.toml | 42 ++++++- src/SeleniumLibrary/__init__.py | 53 ++++---- src/SeleniumLibrary/base/context.py | 10 +- src/SeleniumLibrary/base/librarycomponent.py | 16 +-- src/SeleniumLibrary/entry/__main__.py | 14 +-- src/SeleniumLibrary/entry/get_versions.py | 2 +- src/SeleniumLibrary/entry/translation.py | 7 +- src/SeleniumLibrary/errors.py | 2 +- src/SeleniumLibrary/keywords/alert.py | 19 ++- .../keywords/browsermanagement.py | 47 ++++---- src/SeleniumLibrary/keywords/cookie.py | 22 ++-- src/SeleniumLibrary/keywords/element.py | 114 +++++++++--------- .../keywords/expectedconditions.py | 22 ++-- src/SeleniumLibrary/keywords/formelement.py | 29 +++-- src/SeleniumLibrary/keywords/javascript.py | 29 ++--- src/SeleniumLibrary/keywords/runonfailure.py | 7 +- src/SeleniumLibrary/keywords/screenshot.py | 39 +++--- src/SeleniumLibrary/keywords/selectelement.py | 17 ++- src/SeleniumLibrary/keywords/waiting.py | 65 +++++----- .../webdrivertools/sl_file_detector.py | 5 +- .../keywords/webdrivertools/webdrivertools.py | 50 ++++---- src/SeleniumLibrary/keywords/window.py | 29 +++-- src/SeleniumLibrary/locators/customlocator.py | 5 +- src/SeleniumLibrary/locators/elementfinder.py | 21 ++-- src/SeleniumLibrary/locators/windowmanager.py | 43 ++++--- src/SeleniumLibrary/utils/__init__.py | 4 +- src/SeleniumLibrary/utils/events/__init__.py | 5 +- src/SeleniumLibrary/utils/events/event.py | 10 +- src/SeleniumLibrary/utils/types.py | 11 +- tasks.py | 11 +- .../test/api/test_accessing_keywod_methods.py | 16 +-- utest/test/api/test_event_firing_webdriver.py | 2 +- utest/test/api/test_plugin_keyword_tags.py | 16 +-- utest/test/api/test_plugins.py | 62 +++++----- .../test_input_text_file_decorator.py | 4 +- ...est_keyword_arguments_browsermanagement.py | 4 +- utest/test/keywords/test_webdrivercache.py | 70 +++++------ .../test_windowmananger_window_info.py | 24 ++-- utest/test/locators/test_windowmanager.py | 69 ++++------- utest/test/utils/test_package.py | 9 +- 40 files changed, 504 insertions(+), 522 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fffbff219..94a6558ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,47 @@ [tool.ruff] target-version = "py310" line-length = 88 -extend-exclude = ["_build", "generated", "src/SeleniumLibrary/__init__.pyi"] +exclude = [ + "src/SeleniumLibrary/__init__.pyi", +] [tool.ruff.lint] -select = ["E", "F", "W", "I"] -ignore = ["E501"] +select = [ + "E", + "F", + "W", + "C90", + "I", + "N", + "B", + "PYI", + "PL", + "UP", + "A", + "C4", + "DTZ", + "ISC", + "ICN", + "INP", + "PIE", + "T20", + "PYI", + "PT", + "RSE", + "RET", + "SIM", + "RUF" +] +ignore = [ + "E501", # line too long + "N803", # argument name should be lowercase + "N812", # lowercase imported as non lowercase + "N999", # Invalid module name: 'SeleniumLibrary' + "PLR0913", # too many arguments + "DTZ006", # No timezone specified + "PTH", # Use Path instead of os.path -> maybe soon + "N818", # exception naming convention +] [tool.ruff.format] quote-style = "double" diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index f371f3b1d..0a07be000 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -13,19 +13,17 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from collections import namedtuple -from datetime import timedelta import importlib +import pkgutil +from datetime import timedelta from inspect import getdoc, isclass from pathlib import Path -import pkgutil -from typing import Optional, List, Union +from typing import NamedTuple from robot.api import logger from robot.errors import DataError from robot.libraries.BuiltIn import BuiltIn from robot.utils.importer import Importer - from robotlibcore import DynamicCore from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webelement import WebElement @@ -49,10 +47,14 @@ WebDriverCache, WindowKeywords, ) -from SeleniumLibrary.keywords.screenshot import EMBED, BASE64 +from SeleniumLibrary.keywords.screenshot import BASE64, EMBED from SeleniumLibrary.locators import ElementFinder -from SeleniumLibrary.utils import LibraryListener, is_truthy, _convert_timeout, _convert_delay - +from SeleniumLibrary.utils import ( + LibraryListener, + _convert_delay, + _convert_timeout, + is_truthy, +) __version__ = "6.8.0" @@ -597,12 +599,12 @@ def __init__( timeout=timedelta(seconds=5), implicit_wait=timedelta(seconds=0), run_on_failure="Capture Page Screenshot", - screenshot_root_directory: Optional[str] = None, - plugins: Optional[str] = None, - event_firing_webdriver: Optional[str] = None, + screenshot_root_directory: str | None = None, + plugins: str | None = None, + event_firing_webdriver: str | None = None, page_load_timeout=timedelta(minutes=5), action_chain_delay=timedelta(seconds=0.25), - language: Optional[str] = None, + language: str | None = None, ): """SeleniumLibrary can be imported with several optional arguments. @@ -689,10 +691,13 @@ def get_keyword_documentation(self, name: str) -> str: return self._get_intro_documentation() return DynamicCore.get_keyword_documentation(self, name) + class Doc(NamedTuple): + doc: str + name: str + def _parse_plugin_doc(self): - Doc = namedtuple("Doc", "doc, name") for plugin in self._plugins: - yield Doc( + yield self.Doc( doc=getdoc(plugin) or "No plugin documentation found.", name=plugin.__class__.__name__, ) @@ -751,7 +756,7 @@ def driver(self) -> WebDriver: return self._drivers.current def find_element( - self, locator: str, parent: Optional[WebElement] = None + self, locator: str, parent: WebElement | None = None ) -> WebElement: """Find element matching `locator`. @@ -769,7 +774,7 @@ def find_element( def find_elements( self, locator: str, parent: WebElement = None - ) -> List[WebElement]: + ) -> list[WebElement]: """Find all elements matching `locator`. :param locator: Locator to use when searching the element. @@ -817,12 +822,15 @@ def _parse_listener(self, event_firing_webdriver): raise DataError(message) return listener + class Module(NamedTuple): + module: str + args: list + kw_args: dict + def _string_to_modules(self, modules): - Module = namedtuple("Module", "module, args, kw_args") parsed_modules = [] for module in modules.split(","): - module = module.strip() - module_and_args = module.split(";") + module_and_args = module.strip().split(";") module_name = module_and_args.pop(0) kw_args = {} args = [] @@ -832,8 +840,9 @@ def _string_to_modules(self, modules): kw_args[key] = value else: args.append(argument) - module = Module(module=module_name, args=args, kw_args=kw_args) - parsed_modules.append(module) + parsed_modules.append( + self.Module(module=module_name, args=args, kw_args=kw_args) + ) return parsed_modules def _store_plugin_keywords(self, plugin): @@ -849,7 +858,7 @@ def _resolve_screenshot_root_directory(self): self.screenshot_root_directory = BASE64 @staticmethod - def _get_translation(language: Union[str, None]) -> Union[Path, None]: + def _get_translation(language: str | None) -> Path | None: if not language: return None discovered_plugins = { diff --git a/src/SeleniumLibrary/base/context.py b/src/SeleniumLibrary/base/context.py index e55aecdc4..d8fbddffc 100644 --- a/src/SeleniumLibrary/base/context.py +++ b/src/SeleniumLibrary/base/context.py @@ -13,7 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Optional, List +from typing import Any from selenium.webdriver.remote.webelement import WebElement @@ -56,7 +56,7 @@ def event_firing_webdriver(self, event_firing_webdriver: Any): def find_element( self, locator: str, - tag: Optional[str] = None, + tag: str | None = None, required: bool = True, parent: WebElement = None, ) -> WebElement: @@ -82,8 +82,8 @@ def find_element( return self.element_finder.find(locator, tag, True, required, parent) def find_elements( - self, locator: str, tag: Optional[str] = None, parent: WebElement = None - ) -> List[WebElement]: + self, locator: str, tag: str | None = None, parent: WebElement = None + ) -> list[WebElement]: """Find all elements matching `locator`. :param locator: Locator to use when searching the element. @@ -103,7 +103,7 @@ def is_text_present(self, text: str): locator = f"xpath://*[contains(., {escape_xpath_value(text)})]" return self.find_element(locator, required=False) is not None - def is_element_enabled(self, locator: str, tag: Optional[str] = None) -> bool: + def is_element_enabled(self, locator: str, tag: str | None = None) -> bool: element = self.find_element(locator, tag) return element.is_enabled() and element.get_attribute("readonly") is None diff --git a/src/SeleniumLibrary/base/librarycomponent.py b/src/SeleniumLibrary/base/librarycomponent.py index d7174f4de..ae24d57c8 100644 --- a/src/SeleniumLibrary/base/librarycomponent.py +++ b/src/SeleniumLibrary/base/librarycomponent.py @@ -16,14 +16,14 @@ import os from datetime import timedelta -from typing import Optional, Union -from SeleniumLibrary.utils import is_noney from robot.api import logger from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError -from .context import ContextAware +from SeleniumLibrary.utils import is_noney + from ..utils import _convert_timeout +from .context import ContextAware class LibraryComponent(ContextAware): @@ -46,8 +46,8 @@ def log_source(self, loglevel: str = "INFO"): def assert_page_contains( self, locator: str, - tag: Optional[str] = None, - message: Optional[str] = None, + tag: str | None = None, + message: str | None = None, loglevel: str = "TRACE", ): tag_message = tag or "element" @@ -63,8 +63,8 @@ def assert_page_contains( def assert_page_not_contains( self, locator: str, - tag: Optional[str] = None, - message: Optional[str] = None, + tag: str | None = None, + message: str | None = None, loglevel: str = "TRACE", ): tag_message = tag or "element" @@ -75,7 +75,7 @@ def assert_page_not_contains( raise AssertionError(message) logger.info(f"Current page does not contain {tag_message} '{locator}'.") - def get_timeout(self, timeout: Union[str, int, timedelta, None] = None) -> float: + def get_timeout(self, timeout: str | int | timedelta | None = None) -> float: if timeout is None: return self.ctx.timeout return _convert_timeout(timeout) diff --git a/src/SeleniumLibrary/entry/__main__.py b/src/SeleniumLibrary/entry/__main__.py index 47e049233..61c96462e 100644 --- a/src/SeleniumLibrary/entry/__main__.py +++ b/src/SeleniumLibrary/entry/__main__.py @@ -16,13 +16,12 @@ import json from pathlib import Path -from typing import Optional + import click from .get_versions import get_version from .translation import compare_translation, get_library_translation - CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]} VERSION = get_version() @@ -39,7 +38,6 @@ def cli(): See each command argument help for more details what (optional) arguments that command supports. """ - pass @cli.command() @@ -63,7 +61,7 @@ def cli(): ) def translation( filename: Path, - plugins: Optional[str] = None, + plugins: str | None = None, compare: bool = False, ): """Default translation file from library keywords. @@ -88,17 +86,17 @@ def translation( lib_translation = get_library_translation(plugins) if compare: if table := compare_translation(filename, lib_translation): - print( + click.echo( "Found differences between translation and library, see below for details." ) for line in table: - print(line) + click.echo(line) else: - print("Translation is valid, no updated needed.") + click.echo("Translation is valid, no updated needed.") else: with filename.open("w") as file: json.dump(lib_translation, file, indent=4) - print(f"Translation file created in {filename.absolute()}") + click.echo(f"Translation file created in {filename.absolute()}") if __name__ == "__main__": diff --git a/src/SeleniumLibrary/entry/get_versions.py b/src/SeleniumLibrary/entry/get_versions.py index 51e68da7a..4ad9a7ce5 100644 --- a/src/SeleniumLibrary/entry/get_versions.py +++ b/src/SeleniumLibrary/entry/get_versions.py @@ -14,10 +14,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pathlib import Path import re import subprocess import sys +from pathlib import Path from selenium import __version__ diff --git a/src/SeleniumLibrary/entry/translation.py b/src/SeleniumLibrary/entry/translation.py index 3c98dddaa..0cfdb0e7b 100644 --- a/src/SeleniumLibrary/entry/translation.py +++ b/src/SeleniumLibrary/entry/translation.py @@ -18,7 +18,6 @@ import inspect import json from pathlib import Path -from typing import List, Optional KEYWORD_NAME = "Keyword name" DOC_CHANGED = "Documentation update needed" @@ -33,8 +32,8 @@ ) -def get_library_translation(plugins: Optional[str] = None) -> dict: - from SeleniumLibrary import SeleniumLibrary +def get_library_translation(plugins: str | None = None) -> dict: + from SeleniumLibrary import SeleniumLibrary # noqa: PLC0415 selib = SeleniumLibrary(plugins=plugins) translation = {} @@ -65,7 +64,7 @@ def _max_kw_name_length(project_translation: dict) -> int: return max_lenght -def _get_heading(max_kw_length: int) -> List[str]: +def _get_heading(max_kw_length: int) -> list[str]: heading = f"| {KEYWORD_NAME} " next_line = f"| {'-' * len(KEYWORD_NAME)}" if (padding := max_kw_length - len(KEYWORD_NAME)) > 0: diff --git a/src/SeleniumLibrary/errors.py b/src/SeleniumLibrary/errors.py index 5dd4310d1..636a13a87 100644 --- a/src/SeleniumLibrary/errors.py +++ b/src/SeleniumLibrary/errors.py @@ -40,4 +40,4 @@ class PluginError(SeleniumLibraryException): class UnkownExpectedCondition(SeleniumLibraryException): - pass \ No newline at end of file + pass diff --git a/src/SeleniumLibrary/keywords/alert.py b/src/SeleniumLibrary/keywords/alert.py index 406946d0f..c201ae673 100644 --- a/src/SeleniumLibrary/keywords/alert.py +++ b/src/SeleniumLibrary/keywords/alert.py @@ -14,13 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. from datetime import timedelta -from typing import Optional from selenium.common.exceptions import TimeoutException, WebDriverException from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait -from SeleniumLibrary.base import keyword, LibraryComponent +from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.utils import secs_to_timestr @@ -32,7 +31,7 @@ class AlertKeywords(LibraryComponent): @keyword def input_text_into_alert( - self, text: str, action: str = ACCEPT, timeout: Optional[timedelta] = None + self, text: str, action: str = ACCEPT, timeout: timedelta | None = None ): """Types the given ``text`` into an input field in an alert. @@ -53,7 +52,7 @@ def alert_should_be_present( self, text: str = "", action: str = ACCEPT, - timeout: Optional[timedelta] = None, + timeout: timedelta | None = None, ): """Verifies that an alert is present and by default, accepts it. @@ -72,12 +71,12 @@ def alert_should_be_present( message = self.handle_alert(action, timeout) if text and text != message: raise AssertionError( - f"Alert message should have been '{text}' but it " f"was '{message}'." + f"Alert message should have been '{text}' but it was '{message}'." ) @keyword def alert_should_not_be_present( - self, action: str = ACCEPT, timeout: Optional[timedelta] = None + self, action: str = ACCEPT, timeout: timedelta | None = None ): """Verifies that no alert is present. @@ -101,7 +100,7 @@ def alert_should_not_be_present( raise AssertionError(f"Alert with message '{text}' present.") @keyword - def handle_alert(self, action: str = ACCEPT, timeout: Optional[timedelta] = None): + def handle_alert(self, action: str = ACCEPT, timeout: timedelta | None = None): """Handles the current alert and returns its message. By default, the alert is accepted, but this can be controlled @@ -146,7 +145,7 @@ def _wait_alert(self, timeout=None): wait = WebDriverWait(self.driver, timeout) try: return wait.until(EC.alert_is_present()) - except TimeoutException: - raise AssertionError(f"Alert not found in {secs_to_timestr(timeout)}.") + except TimeoutException as original_exception: + raise AssertionError(f"Alert not found in {secs_to_timestr(timeout)}.") from original_exception except WebDriverException as err: - raise AssertionError(f"An exception occurred waiting for alert: {err}") + raise AssertionError(f"An exception occurred waiting for alert: {err}") from err diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 042d12d50..c2038f367 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -17,15 +17,20 @@ import time import types from datetime import timedelta -from typing import Optional, Union, Any, List +from typing import Any from selenium import webdriver from selenium.webdriver import FirefoxProfile from selenium.webdriver.support.event_firing_webdriver import EventFiringWebDriver -from SeleniumLibrary.base import keyword, LibraryComponent +from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.locators import WindowManager -from SeleniumLibrary.utils import timestr_to_secs, secs_to_timestr, _convert_timeout, _convert_delay +from SeleniumLibrary.utils import ( + _convert_delay, + _convert_timeout, + secs_to_timestr, + timestr_to_secs, +) from .webdrivertools import WebDriverCreator @@ -59,15 +64,15 @@ def close_browser(self): @keyword def open_browser( self, - url: Optional[str] = None, + url: str | None = None, browser: str = "firefox", - alias: Optional[str] = None, - remote_url: Union[bool, str] = False, - desired_capabilities: Union[dict, None, str] = None, - ff_profile_dir: Union[FirefoxProfile, str, None] = None, + alias: str | None = None, + remote_url: bool | str = False, + desired_capabilities: dict | None | str = None, + ff_profile_dir: FirefoxProfile | str | None = None, options: Any = None, - service_log_path: Optional[str] = None, - executable_path: Optional[str] = None, + service_log_path: str | None = None, + executable_path: str | None = None, service: Any = None, ) -> str: """Opens a new browser instance to the optional ``url``. @@ -275,7 +280,7 @@ def _make_new_browser( @keyword def create_webdriver( - self, driver_name: str, alias: Optional[str] = None, kwargs: Optional[dict] = None, **init_kwargs + self, driver_name: str, alias: str | None = None, kwargs: dict | None = None, **init_kwargs ) -> str: """Creates an instance of Selenium WebDriver. @@ -314,8 +319,8 @@ def create_webdriver( driver_name = driver_name.strip() try: creation_func = getattr(webdriver, driver_name) - except AttributeError: - raise RuntimeError(f"'{driver_name}' is not a valid WebDriver name.") + except AttributeError as original_exception: + raise RuntimeError(f"'{driver_name}' is not a valid WebDriver name.") from original_exception self.info(f"Creating an instance of the {driver_name} WebDriver.") driver = creation_func(**init_kwargs) self.debug( @@ -359,16 +364,16 @@ def switch_browser(self, index_or_alias: str): """ try: self.drivers.switch(index_or_alias) - except RuntimeError: + except RuntimeError as original_exception: raise RuntimeError( f"No browser with index or alias '{index_or_alias}' found." - ) + ) from original_exception self.debug( f"Switched to browser with Selenium session id {self.driver.session_id}." ) @keyword - def get_browser_ids(self) -> List[str]: + def get_browser_ids(self) -> list[str]: """Returns index of all active browser as list. Example: @@ -385,7 +390,7 @@ def get_browser_ids(self) -> List[str]: return self.drivers.active_driver_ids @keyword - def get_browser_aliases(self) -> List[str]: + def get_browser_aliases(self) -> list[str]: """Returns aliases of all active browser that has an alias as NormalizedDict. The dictionary contains the aliases as keys and the index as value. This can be accessed as dictionary ``${aliases.key}`` or as list ``@{aliases}[0]``. @@ -429,7 +434,7 @@ def get_location(self) -> str: return self.driver.current_url @keyword - def location_should_be(self, url: str, message: Optional[str] = None): + def location_should_be(self, url: str, message: str | None = None): """Verifies that the current URL is exactly ``url``. The ``url`` argument contains the exact url that should exist in browser. @@ -442,12 +447,12 @@ def location_should_be(self, url: str, message: Optional[str] = None): actual = self.get_location() if actual != url: if message is None: - message = f"Location should have been '{url}' but " f"was '{actual}'." + message = f"Location should have been '{url}' but was '{actual}'." raise AssertionError(message) self.info(f"Current location is '{url}'.") @keyword - def location_should_contain(self, expected: str, message: Optional[str] = None): + def location_should_contain(self, expected: str, message: str | None = None): """Verifies that the current URL contains ``expected``. The ``expected`` argument contains the expected value in url. @@ -494,7 +499,7 @@ def log_title(self) -> str: return title @keyword - def title_should_be(self, title: str, message: Optional[str] = None): + def title_should_be(self, title: str, message: str | None = None): """Verifies that the current page title equals ``title``. The ``message`` argument can be used to override the default error diff --git a/src/SeleniumLibrary/keywords/cookie.py b/src/SeleniumLibrary/keywords/cookie.py index c2c49e8c7..32af0f861 100644 --- a/src/SeleniumLibrary/keywords/cookie.py +++ b/src/SeleniumLibrary/keywords/cookie.py @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. from datetime import datetime -from typing import Union, Optional from robot.libraries.DateTime import convert_date from robot.utils import DotDict @@ -45,7 +44,7 @@ def __init__( self.extra = extra def __str__(self): - items = "name value path domain secure httpOnly expiry".split() + items = ["name", "value", "path", "domain", "secure", "httpOnly", "expiry"] string = "\n".join(f"{item}={getattr(self, item)}" for item in items) if self.extra: string = f"{string}\nextra={self.extra}\n" @@ -67,7 +66,7 @@ def delete_cookie(self, name): self.driver.delete_cookie(name) @keyword - def get_cookies(self, as_dict: bool = False) -> Union[str, dict]: + def get_cookies(self, as_dict: bool = False) -> str | dict: """Returns all cookies of the current page. If ``as_dict`` argument evaluates as false, see `Boolean arguments` @@ -87,11 +86,10 @@ def get_cookies(self, as_dict: bool = False) -> Union[str, dict]: for cookie in self.driver.get_cookies(): pairs.append(f"{cookie['name']}={cookie['value']}") return "; ".join(pairs) - else: - pairs = DotDict() - for cookie in self.driver.get_cookies(): - pairs[cookie["name"]] = cookie["value"] - return pairs + pairs = DotDict() + for cookie in self.driver.get_cookies(): + pairs[cookie["name"]] = cookie["value"] + return pairs @keyword def get_cookie(self, name: str) -> CookieInformation: @@ -144,10 +142,10 @@ def add_cookie( self, name: str, value: str, - path: Optional[str] = None, - domain: Optional[str] = None, - secure: Optional[bool] = None, - expiry: Optional[str] = None, + path: str | None = None, + domain: str | None = None, + secure: bool | None = None, + expiry: str | None = None, ): """Adds a cookie to your current session. diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index c61076286..d53a1be8b 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -14,18 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import namedtuple -from typing import List, Optional, Tuple, Union +from typing import NamedTuple -from SeleniumLibrary.utils import is_noney -from robot.utils import plural_or_not, is_truthy +from robot.utils import is_truthy, plural_or_not from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.errors import ElementNotFound -from SeleniumLibrary.utils.types import type_converter, Locator +from SeleniumLibrary.utils import is_noney +from SeleniumLibrary.utils.types import Locator, type_converter class ElementKeywords(LibraryComponent): @@ -39,7 +38,7 @@ def get_webelement(self, locator: Locator) -> WebElement: return self.find_element(locator) @keyword(name="Get WebElements") - def get_webelements(self, locator: Locator) -> List[WebElement]: + def get_webelements(self, locator: Locator) -> list[WebElement]: """Returns a list of WebElement objects matching the ``locator``. See the `Locating elements` section for details about the locator @@ -55,8 +54,8 @@ def get_webelements(self, locator: Locator) -> List[WebElement]: def element_should_contain( self, locator: Locator, - expected: Union[None, str], - message: Optional[str] = None, + expected: None | str, + message: str | None = None, ignore_case: bool = False, ): """Verifies that element ``locator`` contains text ``expected``. @@ -93,8 +92,8 @@ def element_should_contain( def element_should_not_contain( self, locator: Locator, - expected: Union[None, str], - message: Optional[str] = None, + expected: None | str, + message: str | None = None, ignore_case: bool = False, ): """Verifies that element ``locator`` does not contain text ``expected``. @@ -151,9 +150,9 @@ def page_should_contain(self, text: str, loglevel: str = "TRACE"): def page_should_contain_element( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", - limit: Optional[int] = None, + limit: int | None = None, ): """Verifies that element ``locator`` is found on the current page. @@ -186,14 +185,14 @@ def page_should_contain_element( count = len(self.find_elements(locator)) if count == limit: self.info(f"Current page contains {count} element(s).") - else: - if message is None: - message = ( - f'Page should have contained "{limit}" element(s), ' - f'but it did contain "{count}" element(s).' - ) - self.ctx.log_source(loglevel) - raise AssertionError(message) + return None + if message is None: + message = ( + f'Page should have contained "{limit}" element(s), ' + f'but it did contain "{count}" element(s).' + ) + self.ctx.log_source(loglevel) + raise AssertionError(message) @keyword def page_should_not_contain(self, text: str, loglevel: str = "TRACE"): @@ -211,7 +210,7 @@ def page_should_not_contain(self, text: str, loglevel: str = "TRACE"): def page_should_not_contain_element( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies that element ``locator`` is not found on the current page. @@ -225,7 +224,7 @@ def page_should_not_contain_element( self.assert_page_not_contains(locator, message=message, loglevel=loglevel) @keyword - def assign_id_to_element(self, locator: Locator, id: str): + def assign_id_to_element(self, locator: Locator, id: str): # noqa: A002 """Assigns a temporary ``id`` to the element specified by ``locator``. This is mainly useful if the locator is complicated and/or slow XPath @@ -288,7 +287,7 @@ def element_should_be_focused(self, locator: Locator): @keyword def element_should_be_visible( - self, locator: Locator, message: Optional[str] = None + self, locator: Locator, message: str | None = None ): """Verifies that the element identified by ``locator`` is visible. @@ -311,7 +310,7 @@ def element_should_be_visible( @keyword def element_should_not_be_visible( - self, locator: Locator, message: Optional[str] = None + self, locator: Locator, message: str | None = None ): """Verifies that the element identified by ``locator`` is NOT visible. @@ -332,8 +331,8 @@ def element_should_not_be_visible( def element_text_should_be( self, locator: Locator, - expected: Union[None, str], - message: Optional[str] = None, + expected: None | str, + message: str | None = None, ignore_case: bool = False, ): """Verifies that element ``locator`` contains exact the text ``expected``. @@ -368,8 +367,8 @@ def element_text_should_be( def element_text_should_not_be( self, locator: Locator, - not_expected: Union[None, str], - message: Optional[str] = None, + not_expected: None | str, + message: str | None = None, ignore_case: bool = False, ): """Verifies that element ``locator`` does not contain exact the text ``not_expected``. @@ -435,7 +434,7 @@ def get_dom_attribute( @keyword def get_property( - self, locator: Locator, property: str + self, locator: Locator, property: str # noqa: A002 ) -> str: """Returns the value of ``property`` from the element ``locator``. @@ -453,8 +452,8 @@ def element_attribute_value_should_be( self, locator: Locator, attribute: str, - expected: Union[None, str], - message: Optional[str] = None, + expected: None | str, + message: str | None = None, ): """Verifies element identified by ``locator`` contains expected attribute value. @@ -494,7 +493,7 @@ def get_horizontal_position(self, locator: Locator) -> int: return self.find_element(locator).location["x"] @keyword - def get_element_size(self, locator: Locator) -> Tuple[int, int]: + def get_element_size(self, locator: Locator) -> tuple[int, int]: """Returns width and height of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -583,7 +582,7 @@ def get_vertical_position(self, locator: Locator) -> int: @keyword def click_button( - self, locator: Locator, modifier: Union[bool, str] = False + self, locator: Locator, modifier: bool | str = False ): """Clicks the button identified by ``locator``. @@ -607,7 +606,7 @@ def click_button( @keyword def click_image( - self, locator: Locator, modifier: Union[bool, str] = False + self, locator: Locator, modifier: bool | str = False ): """Clicks an image identified by ``locator``. @@ -632,7 +631,7 @@ def click_image( @keyword def click_link( - self, locator: Locator, modifier: Union[bool, str] = False + self, locator: Locator, modifier: bool | str = False ): """Clicks a link identified by ``locator``. @@ -655,7 +654,7 @@ def click_link( def click_element( self, locator: Locator, - modifier: Union[bool, str] = False, + modifier: bool | str = False, action_chain: bool = False, ): """Click the element identified by ``locator``. @@ -1025,7 +1024,7 @@ def _special_key_up(self, actions, parsed_key): actions.key_up(key.converted) @keyword - def get_all_links(self) -> List[str]: + def get_all_links(self) -> list[str]: """Returns a list containing ids of all links found in current page. If a link has no id, an empty string will be in the list instead. @@ -1049,7 +1048,7 @@ def mouse_down_on_link(self, locator: Locator): def page_should_contain_link( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies link identified by ``locator`` is found from current page. @@ -1067,7 +1066,7 @@ def page_should_contain_link( def page_should_not_contain_link( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies link identified by ``locator`` is not found from current page. @@ -1097,7 +1096,7 @@ def mouse_down_on_image(self, locator: Locator): def page_should_contain_image( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies image identified by ``locator`` is found from current page. @@ -1115,7 +1114,7 @@ def page_should_contain_image( def page_should_not_contain_image( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies image identified by ``locator`` is not found from current page. @@ -1172,7 +1171,7 @@ def remove_location_strategy(self, strategy_name: str): self.element_finder.unregister(strategy_name) def _map_ascii_key_code_to_key(self, key_code): - map = { + key_map = { 0: Keys.NULL, 8: Keys.BACK_SPACE, 9: Keys.TAB, @@ -1191,7 +1190,7 @@ def _map_ascii_key_code_to_key(self, key_code): 61: Keys.EQUALS, 127: Keys.DELETE, } - key = map.get(key_code) + key = key_map.get(key_code) if key is None: key = chr(key_code) return key @@ -1199,10 +1198,10 @@ def _map_ascii_key_code_to_key(self, key_code): def _map_named_key_code_to_special_key(self, key_name): try: return getattr(Keys, key_name) - except AttributeError: + except AttributeError as original_exception: message = f"Unknown key named '{key_name}'." self.debug(message) - raise ValueError(message) + raise ValueError(message) from original_exception def _page_contains(self, text): self.driver.switch_to.default_content() @@ -1225,12 +1224,11 @@ def parse_modifier(self, modifier): modifiers = modifier.split("+") keys = [] for item in modifiers: - item = item.strip() - item = self._parse_aliases(item) - if hasattr(Keys, item): - keys.append(getattr(Keys, item)) + modifier = self._parse_aliases(item.strip()) + if hasattr(Keys, modifier): + keys.append(getattr(Keys, modifier)) else: - raise ValueError(f"'{item}' modifier does not match to Selenium Keys") + raise ValueError(f"'{modifier}' modifier does not match to Selenium Keys") return keys def _parse_keys(self, *keys): @@ -1263,15 +1261,19 @@ def _separate_key(self, key): list_keys.append(one_key) return list_keys + class KeysRecord(NamedTuple): + converted: object + original: str + special: bool + def _convert_special_keys(self, keys): - KeysRecord = namedtuple("KeysRecord", "converted, original special") converted_keys = [] for key in keys: - key = self._parse_aliases(key) - if self._selenium_keys_has_attr(key): - converted_keys.append(KeysRecord(getattr(Keys, key), key, True)) + resolved_key = self._parse_aliases(key) + if self._selenium_keys_has_attr(resolved_key): + converted_keys.append(self.KeysRecord(getattr(Keys, resolved_key), resolved_key, True)) else: - converted_keys.append(KeysRecord(key, key, False)) + converted_keys.append(self.KeysRecord(resolved_key, resolved_key, False)) return converted_keys def _selenium_keys_has_attr(self, key): @@ -1293,4 +1295,4 @@ def get_css_property_value( | ${color}= | `Get CSS Property Value` | css:button.submit | background-color | | ${size}= | `Get CSS Property Value` | id:username | font-size | """ - return self.find_element(locator).value_of_css_property(css_property) \ No newline at end of file + return self.find_element(locator).value_of_css_property(css_property) diff --git a/src/SeleniumLibrary/keywords/expectedconditions.py b/src/SeleniumLibrary/keywords/expectedconditions.py index c0272ae75..351a0c4f1 100644 --- a/src/SeleniumLibrary/keywords/expectedconditions.py +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -12,16 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. import string -from typing import Optional + +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.errors import UnkownExpectedCondition -from selenium.webdriver.support.wait import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC + class ExpectedConditionKeywords(LibraryComponent): @keyword - def wait_for_expected_condition(self, condition: string, *args, timeout: Optional[float]=10): + def wait_for_expected_condition(self, condition: string, *args, timeout: float | None=10): """Waits until ``condition`` is true or ``timeout`` expires. The condition must be one of selenium's expected condition which @@ -51,13 +52,10 @@ def wait_for_expected_condition(self, condition: string, *args, timeout: Optiona condition = self._parse_condition(condition) wait = WebDriverWait(self.driver, timeout, 0.1) try: - c = getattr(EC, condition) - except: - # ToDo: provide hints as to what is avaialbel or find closet match - raise UnkownExpectedCondition(f"{condition} is an unknown expected condition") - result = wait.until(c(*args), message="Expected Condition not met within set timeout of " + str(timeout)) - return result + condition_func = getattr(EC, condition) + except AttributeError as original_exception: + raise UnkownExpectedCondition(f"{condition} is an unknown expected condition") from original_exception + return wait.until(condition_func(*args), message=f"Expected Condition not met within set timeout of {timeout}s") def _parse_condition(self, condition: string): - parsed = condition.replace(' ','_').lower() - return parsed \ No newline at end of file + return condition.replace(' ','_').lower() diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index 43816e04f..a0d521fbd 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -15,7 +15,6 @@ # limitations under the License. import os -from typing import Optional from robot.libraries.BuiltIn import BuiltIn @@ -70,7 +69,7 @@ def checkbox_should_not_be_selected(self, locator: Locator): def page_should_contain_checkbox( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies checkbox ``locator`` is found from the current page. @@ -87,7 +86,7 @@ def page_should_contain_checkbox( def page_should_not_contain_checkbox( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies checkbox ``locator`` is not found from the current page. @@ -132,7 +131,7 @@ def unselect_checkbox(self, locator: Locator): def page_should_contain_radio_button( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies radio button ``locator`` is found from current page. @@ -150,7 +149,7 @@ def page_should_contain_radio_button( def page_should_not_contain_radio_button( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies radio button ``locator`` is not found from current page. @@ -300,7 +299,7 @@ def input_text( def page_should_contain_textfield( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies text field ``locator`` is found from current page. @@ -317,7 +316,7 @@ def page_should_contain_textfield( def page_should_not_contain_textfield( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies text field ``locator`` is not found from current page. @@ -335,7 +334,7 @@ def textfield_should_contain( self, locator: Locator, expected: str, - message: Optional[str] = None, + message: str | None = None, ): """Verifies text field ``locator`` contains text ``expected``. @@ -359,7 +358,7 @@ def textfield_value_should_be( self, locator: Locator, expected: str, - message: Optional[str] = None, + message: str | None = None, ): """Verifies text field ``locator`` has exactly text ``expected``. @@ -383,7 +382,7 @@ def textarea_should_contain( self, locator: Locator, expected: str, - message: Optional[str] = None, + message: str | None = None, ): """Verifies text area ``locator`` contains text ``expected``. @@ -407,7 +406,7 @@ def textarea_value_should_be( self, locator: Locator, expected: str, - message: Optional[str] = None, + message: str | None = None, ): """Verifies text area ``locator`` has exactly text ``expected``. @@ -430,7 +429,7 @@ def textarea_value_should_be( def page_should_contain_button( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies button ``locator`` is found from current page. @@ -451,7 +450,7 @@ def page_should_contain_button( def page_should_not_contain_button( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies button ``locator`` is not found from current page. @@ -488,11 +487,11 @@ def _get_radio_button_with_value(self, group_name, value): self.debug(f"Radio group locator: {xpath}") try: return self.find_element(xpath) - except ElementNotFound: + except ElementNotFound as original_exception: raise ElementNotFound( f"No radio button with name '{group_name}' " f"and value '{value}' found." - ) + ) from original_exception def _get_value_from_radio_buttons(self, elements): for element in elements: diff --git a/src/SeleniumLibrary/keywords/javascript.py b/src/SeleniumLibrary/keywords/javascript.py index 9c2bb1c90..80b71ca48 100644 --- a/src/SeleniumLibrary/keywords/javascript.py +++ b/src/SeleniumLibrary/keywords/javascript.py @@ -15,12 +15,10 @@ # limitations under the License. import os -from collections import namedtuple -from typing import Any, Union +from typing import Any, NamedTuple from robot.utils import plural_or_not, seq2str -from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import LibraryComponent, keyword @@ -130,11 +128,9 @@ def _separate_code_and_args(self, code): return code[index.js + 1 :], [] if self.js_marker not in code: return code[0 : index.arg], code[index.arg + 1 :] - else: - if index.js == 0: - return code[index.js + 1 : index.arg], code[index.arg + 1 :] - else: - return code[index.js + 1 :], code[index.arg + 1 : index.js] + if index.js == 0: + return code[index.js + 1 : index.arg], code[index.arg + 1 :] + return code[index.js + 1 :], code[index.arg + 1 : index.js] def _check_marker_error(self, code): if not code: @@ -151,17 +147,14 @@ def _check_marker_error(self, code): if message: raise ValueError(message) + class Index(NamedTuple): + js: int + arg: int + def _get_marker_index(self, code): - Index = namedtuple("Index", "js arg") - if self.js_marker in code: - js = code.index(self.js_marker) - else: - js = -1 - if self.arg_marker in code: - arg = code.index(self.arg_marker) - else: - arg = -1 - return Index(js=js, arg=arg) + js = code.index(self.js_marker) if self.js_marker in code else -1 + arg = code.index(self.arg_marker) if self.arg_marker in code else -1 + return self.Index(js=js, arg=arg) def _read_javascript_from_file(self, path): self.info( diff --git a/src/SeleniumLibrary/keywords/runonfailure.py b/src/SeleniumLibrary/keywords/runonfailure.py index 4d23c5ade..73595ac07 100644 --- a/src/SeleniumLibrary/keywords/runonfailure.py +++ b/src/SeleniumLibrary/keywords/runonfailure.py @@ -13,14 +13,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional from SeleniumLibrary.base import LibraryComponent, keyword class RunOnFailureKeywords(LibraryComponent): @keyword - def register_keyword_to_run_on_failure(self, keyword: Optional[str]) -> str: + def register_keyword_to_run_on_failure(self, keyword: str | None) -> str: """Sets the keyword to execute, when a SeleniumLibrary keyword fails. ``keyword`` is the name of a keyword that will be executed if a @@ -65,8 +64,8 @@ def resolve_keyword(name): if name is None: return None if ( - isinstance(name, str) - and name.upper() == "NOTHING" + (isinstance(name, str) + and name.upper() == "NOTHING") or name.upper() == "NONE" ): return None diff --git a/src/SeleniumLibrary/keywords/screenshot.py b/src/SeleniumLibrary/keywords/screenshot.py index 6963b4e28..c3daabde8 100644 --- a/src/SeleniumLibrary/keywords/screenshot.py +++ b/src/SeleniumLibrary/keywords/screenshot.py @@ -15,11 +15,10 @@ # limitations under the License. import os -from typing import Optional, Union from base64 import b64decode from robot.utils import get_link_path -from selenium.webdriver.common.print_page_options import PrintOptions, Orientation +from selenium.webdriver.common.print_page_options import Orientation, PrintOptions from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.utils.path_formatter import _format_path @@ -35,7 +34,7 @@ class ScreenshotKeywords(LibraryComponent): @keyword - def set_screenshot_directory(self, path: Union[None, str]) -> str: + def set_screenshot_directory(self, path: None | str) -> str: """Sets the directory for captured screenshots. ``path`` argument specifies the absolute path to a directory where @@ -123,7 +122,7 @@ def capture_page_screenshot(self, filename: str = DEFAULT_FILENAME_PAGE) -> str: """ if not self.drivers.current: self.info("Cannot capture screenshot because no browser is open.") - return + return None is_embedded, method = self._decide_embedded(filename) if is_embedded: return self._capture_page_screen_to_log(method) @@ -179,7 +178,7 @@ def capture_element_screenshot( self.info( "Cannot capture screenshot from element because no browser is open." ) - return + return None element = self.find_element(locator, required=True) is_embedded, method = self._decide_embedded(filename) if is_embedded: @@ -263,21 +262,21 @@ def _embed_to_log_as_file(self, path, width): f'', html=True, ) - + @keyword - def print_page_as_pdf(self, + def print_page_as_pdf(self, # noqa: C901, PLR0912 filename: str = DEFAULT_FILENAME_PDF, - background: Optional[bool] = None, - margin_bottom: Optional[float] = None, - margin_left: Optional[float] = None, - margin_right: Optional[float] = None, - margin_top: Optional[float] = None, - orientation: Optional[Orientation] = None, - page_height: Optional[float] = None, - page_ranges: Optional[list] = None, - page_width: Optional[float] = None, - scale: Optional[float] = None, - shrink_to_fit: Optional[bool] = None, + background: bool | None = None, + margin_bottom: float | None = None, + margin_left: float | None = None, + margin_right: float | None = None, + margin_top: float | None = None, + orientation: Orientation | None = None, + page_height: float | None = None, + page_ranges: list | None = None, + page_width: float | None = None, + scale: float | None = None, + shrink_to_fit: bool | None = None, # path_to_file=None, ): """ Print the current page as a PDF @@ -332,7 +331,7 @@ def print_page_as_pdf(self, if not self.drivers.current: self.info("Cannot print page to pdf because no browser is open.") - return + return None return self._print_page_as_pdf_to_file(filename, print_options) def _print_page_as_pdf_to_file(self, filename, options): @@ -340,7 +339,7 @@ def _print_page_as_pdf_to_file(self, filename, options): self._create_directory(path) pdfdata = self.driver.print_page(options) if not pdfdata: - raise RuntimeError(f"Failed to print page.") + raise RuntimeError("Failed to print page.") self._save_pdf_to_file(pdfdata, path) return path diff --git a/src/SeleniumLibrary/keywords/selectelement.py b/src/SeleniumLibrary/keywords/selectelement.py index 68290f1fd..be87863cc 100644 --- a/src/SeleniumLibrary/keywords/selectelement.py +++ b/src/SeleniumLibrary/keywords/selectelement.py @@ -13,9 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, Optional, Union -from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support.ui import Select from SeleniumLibrary.base import LibraryComponent, keyword @@ -27,7 +25,7 @@ class SelectElementKeywords(LibraryComponent): @keyword def get_list_items( self, locator: Locator, values: bool = False - ) -> List[str]: + ) -> list[str]: """Returns all labels or values of selection list ``locator``. See the `Locating elements` section for details about the locator @@ -46,8 +44,7 @@ def get_list_items( options = self._get_options(locator) if is_truthy(values): return self._get_values(options) - else: - return self._get_labels(options) + return self._get_labels(options) @keyword def get_selected_list_label(self, locator: Locator) -> str: @@ -63,7 +60,7 @@ def get_selected_list_label(self, locator: Locator) -> str: return select.first_selected_option.text @keyword - def get_selected_list_labels(self, locator: Locator) -> List[str]: + def get_selected_list_labels(self, locator: Locator) -> list[str]: """Returns labels of selected options from selection list ``locator``. Starting from SeleniumLibrary 3.0, returns an empty list if there @@ -89,7 +86,7 @@ def get_selected_list_value(self, locator: Locator) -> str: return select.first_selected_option.get_attribute("value") @keyword - def get_selected_list_values(self, locator: Locator) -> List[str]: + def get_selected_list_values(self, locator: Locator) -> list[str]: """Returns values of selected options from selection list ``locator``. Starting from SeleniumLibrary 3.0, returns an empty list if there @@ -136,7 +133,7 @@ def list_selection_should_be(self, locator: Locator, *expected: str): ) def _format_selection(self, labels, values): - return " | ".join(f"{label} ({value})" for label, value in zip(labels, values)) + return " | ".join(f"{label} ({value})" for label, value in zip(labels, values, strict=True)) @keyword def list_should_have_no_selections(self, locator: Locator): @@ -160,7 +157,7 @@ def list_should_have_no_selections(self, locator: Locator): def page_should_contain_list( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies selection list ``locator`` is found from current page. @@ -177,7 +174,7 @@ def page_should_contain_list( def page_should_not_contain_list( self, locator: Locator, - message: Optional[str] = None, + message: str | None = None, loglevel: str = "TRACE", ): """Verifies selection list ``locator`` is not found from current page. diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index b8bb9473e..bac1d8a21 100644 --- a/src/SeleniumLibrary/keywords/waiting.py +++ b/src/SeleniumLibrary/keywords/waiting.py @@ -16,7 +16,6 @@ import time from datetime import timedelta -from typing import Optional from selenium.common.exceptions import StaleElementReferenceException @@ -31,8 +30,8 @@ class WaitingKeywords(LibraryComponent): def wait_for_condition( self, condition: str, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, + timeout: timedelta | None = None, + error: str | None = None, ): """Waits until ``condition`` is true or ``timeout`` expires. @@ -66,8 +65,8 @@ def wait_for_condition( def wait_until_location_is( self, expected: str, - timeout: Optional[timedelta] = None, - message: Optional[str] = None, + timeout: timedelta | None = None, + message: str | None = None, ): """Waits until the current URL is ``expected``. @@ -95,8 +94,8 @@ def wait_until_location_is( def wait_until_location_is_not( self, location: str, - timeout: Optional[timedelta] = None, - message: Optional[str] = None, + timeout: timedelta | None = None, + message: str | None = None, ): """Waits until the current URL is not ``location``. @@ -123,8 +122,8 @@ def wait_until_location_is_not( def wait_until_location_contains( self, expected: str, - timeout: Optional[timedelta] = None, - message: Optional[str] = None, + timeout: timedelta | None = None, + message: str | None = None, ): """Waits until the current URL contains ``expected``. @@ -151,8 +150,8 @@ def wait_until_location_contains( def wait_until_location_does_not_contain( self, location: str, - timeout: Optional[timedelta] = None, - message: Optional[str] = None, + timeout: timedelta | None = None, + message: str | None = None, ): """Waits until the current URL does not contains ``location``. @@ -179,8 +178,8 @@ def wait_until_location_does_not_contain( def wait_until_page_contains( self, text: str, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, + timeout: timedelta | None = None, + error: str | None = None, ): """Waits until ``text`` appears on the current page. @@ -201,8 +200,8 @@ def wait_until_page_contains( def wait_until_page_does_not_contain( self, text: str, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, + timeout: timedelta | None = None, + error: str | None = None, ): """Waits until ``text`` disappears from the current page. @@ -223,9 +222,9 @@ def wait_until_page_does_not_contain( def wait_until_page_contains_element( self, locator: Locator, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, - limit: Optional[int] = None, + timeout: timedelta | None = None, + error: str | None = None, + limit: int | None = None, ): """Waits until the element ``locator`` appears on the current page. @@ -244,7 +243,7 @@ def wait_until_page_contains_element( ``limit`` is new in SeleniumLibrary 4.4 """ if limit is None: - return self._wait_until( + self._wait_until( lambda: self.find_element(locator, required=False) is not None, f"Element '{locator}' did not appear in .", timeout, @@ -261,9 +260,9 @@ def wait_until_page_contains_element( def wait_until_page_does_not_contain_element( self, locator: Locator, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, - limit: Optional[int] = None, + timeout: timedelta | None = None, + error: str | None = None, + limit: int | None = None, ): """Waits until the element ``locator`` disappears from the current page. @@ -282,7 +281,7 @@ def wait_until_page_does_not_contain_element( ``limit`` is new in SeleniumLibrary 4.4 """ if limit is None: - return self._wait_until( + self._wait_until( lambda: self.find_element(locator, required=False) is None, f"Element '{locator}' did not disappear in .", timeout, @@ -299,8 +298,8 @@ def wait_until_page_does_not_contain_element( def wait_until_element_is_visible( self, locator: Locator, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, + timeout: timedelta | None = None, + error: str | None = None, ): """Waits until the element ``locator`` is visible. @@ -322,8 +321,8 @@ def wait_until_element_is_visible( def wait_until_element_is_not_visible( self, locator: Locator, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, + timeout: timedelta | None = None, + error: str | None = None, ): """Waits until the element ``locator`` is not visible. @@ -345,8 +344,8 @@ def wait_until_element_is_not_visible( def wait_until_element_is_enabled( self, locator: Locator, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, + timeout: timedelta | None = None, + error: str | None = None, ): """Waits until the element ``locator`` is enabled. @@ -374,8 +373,8 @@ def wait_until_element_contains( self, locator: Locator, text: str, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, + timeout: timedelta | None = None, + error: str | None = None, ): """Waits until the element ``locator`` contains ``text``. @@ -398,8 +397,8 @@ def wait_until_element_does_not_contain( self, locator: Locator, text: str, - timeout: Optional[timedelta] = None, - error: Optional[str] = None, + timeout: timedelta | None = None, + error: str | None = None, ): """Waits until the element ``locator`` does not contain ``text``. diff --git a/src/SeleniumLibrary/keywords/webdrivertools/sl_file_detector.py b/src/SeleniumLibrary/keywords/webdrivertools/sl_file_detector.py index 73d26f539..841c357c6 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools/sl_file_detector.py +++ b/src/SeleniumLibrary/keywords/webdrivertools/sl_file_detector.py @@ -33,12 +33,11 @@ def choose_file(self): sl = self._get_sl() except Exception: sl = None - if sl and sl._running_keyword == "choose_file": - return True - return False + return bool(sl and sl._running_keyword == "choose_file") def _get_sl(self): libraries = BuiltIn().get_library_instance(all=True) for library in libraries: if isinstance(libraries[library], SeleniumLibrary.SeleniumLibrary): return libraries[library] + return None diff --git a/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py index 3e05294ae..18ea243c2 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py @@ -18,7 +18,7 @@ import inspect import os import token -import warnings +from inspect import signature from io import StringIO from tokenize import generate_tokens @@ -26,7 +26,6 @@ from robot.utils import ConnectionCache from selenium import webdriver from selenium.webdriver import FirefoxProfile - from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.edge.service import Service as EdgeService from selenium.webdriver.firefox.service import Service as FirefoxService @@ -41,21 +40,22 @@ class WebDriverCreator: - browser_names = { - "googlechrome": "chrome", - "gc": "chrome", - "chrome": "chrome", - "headlesschrome": "headless_chrome", - "ff": "firefox", - "firefox": "firefox", - "headlessfirefox": "headless_firefox", - "ie": "ie", - "internetexplorer": "ie", - "edge": "edge", - "safari": "safari", - } + def __init__(self, log_dir): + self.browser_names = { + "googlechrome": "chrome", + "gc": "chrome", + "chrome": "chrome", + "headlesschrome": "headless_chrome", + "ff": "firefox", + "firefox": "firefox", + "headlessfirefox": "headless_firefox", + "ie": "ie", + "internetexplorer": "ie", + "edge": "edge", + "safari": "safari", + } self.log_dir = log_dir self.selenium_options = SeleniumOptions() self.selenium_service = SeleniumService() @@ -80,10 +80,7 @@ def create_driver( if service_log_path: logger.info(f"Browser driver log file created to: {service_log_path}") self._create_directory(service_log_path) - if ( - creation_method == self.create_firefox - or creation_method == self.create_headless_firefox - ): + if creation_method in [self.create_firefox, self.create_headless_firefox]: return creation_method( desired_capabilities, remote_url, @@ -137,12 +134,10 @@ def _remote_capabilities_resolver(self, set_capabilities, default_capabilities): def _get_log_method(self, service_cls, service_log_path): # -- temporary fix to transition selenium to v4.13 from v4.11 and prior - from inspect import signature sig = signature(service_cls) if 'log_output' in str(sig): return {'log_output': service_log_path} - else: - return {'log_path': service_log_path} + return {'log_path': service_log_path} # -- def create_chrome( @@ -480,8 +475,7 @@ def create(self, browser, service): if key not in service_parameters: service_module = '.'.join((selenium_service.__module__, selenium_service.__qualname__)) raise ValueError(f"{key} is not a member of {service_module} Service class") - selenium_service_inst = selenium_service(**attrs) - return selenium_service_inst + return selenium_service(**attrs) def _parse(self, service): """The service argument parses slightly different than the options argument. As of @@ -493,8 +487,8 @@ def _parse(self, service): try: attr, val = self._split(item, '=') result[attr]=ast.literal_eval(val) - except (ValueError, SyntaxError): - raise ValueError(f'Unable to parse service: "{item}"') + except (ValueError, SyntaxError) as original_exception: + raise ValueError(f'Unable to parse service: "{item}"') from original_exception return result def _import_service(self, browser): @@ -575,8 +569,8 @@ def _parse(self, options): for item in self._split(options): try: result.append(self._parse_to_tokens(item)) - except (ValueError, SyntaxError): - raise ValueError(f'Unable to parse option: "{item}"') + except (ValueError, SyntaxError) as original_exception: + raise ValueError(f'Unable to parse option: "{item}"') from original_exception return result def _parse_to_tokens(self, item): diff --git a/src/SeleniumLibrary/keywords/window.py b/src/SeleniumLibrary/keywords/window.py index f2b086223..c0d70a816 100644 --- a/src/SeleniumLibrary/keywords/window.py +++ b/src/SeleniumLibrary/keywords/window.py @@ -14,14 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. import time -from typing import Optional, List, Tuple, Union -from SeleniumLibrary.utils import is_truthy, is_falsy, timestr_to_secs from selenium.common.exceptions import NoSuchWindowException -from SeleniumLibrary.base import keyword, LibraryComponent +from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.locators import WindowManager -from SeleniumLibrary.utils import plural_or_not +from SeleniumLibrary.utils import is_falsy, is_truthy, plural_or_not, timestr_to_secs class WindowKeywords(LibraryComponent): @@ -32,8 +30,8 @@ def __init__(self, ctx): @keyword def switch_window( self, - locator: Union[list, str] = "MAIN", - timeout: Optional[str] = None, + locator: list | str = "MAIN", + timeout: str | None = None, browser: str = "CURRENT", ): """Switches to browser window matching ``locator``. @@ -117,7 +115,7 @@ def switch_window( except NoSuchWindowException: pass finally: - if not isinstance(browser, str) or not browser.upper() == "CURRENT": + if not isinstance(browser, str) or browser.upper() != "CURRENT": self.drivers.switch(browser) self._window_manager.select(locator, timeout) @@ -127,7 +125,7 @@ def close_window(self): self.driver.close() @keyword - def get_window_handles(self, browser: str = "CURRENT") -> List[str]: + def get_window_handles(self, browser: str = "CURRENT") -> list[str]: """Returns all child window handles of the selected browser as a list. Can be used as a list of windows to exclude with `Select Window`. @@ -139,7 +137,7 @@ def get_window_handles(self, browser: str = "CURRENT") -> List[str]: return self._window_manager.get_window_handles(browser) @keyword - def get_window_identifiers(self, browser: str = "CURRENT") -> List: + def get_window_identifiers(self, browser: str = "CURRENT") -> list: """Returns and logs id attributes of all windows of the selected browser. How to select the ``browser`` scope of this keyword, see `Get Locations`.""" @@ -147,7 +145,7 @@ def get_window_identifiers(self, browser: str = "CURRENT") -> List: return self._log_list(ids) @keyword - def get_window_names(self, browser: str = "CURRENT") -> List[str]: + def get_window_names(self, browser: str = "CURRENT") -> list[str]: """Returns and logs names of all windows of the selected browser. How to select the ``browser`` scope of this keyword, see `Get Locations`.""" @@ -155,7 +153,7 @@ def get_window_names(self, browser: str = "CURRENT") -> List[str]: return self._log_list(names) @keyword - def get_window_titles(self, browser: str = "CURRENT") -> List[str]: + def get_window_titles(self, browser: str = "CURRENT") -> list[str]: """Returns and logs titles of all windows of the selected browser. How to select the ``browser`` scope of this keyword, see `Get Locations`.""" @@ -163,7 +161,7 @@ def get_window_titles(self, browser: str = "CURRENT") -> List[str]: return self._log_list(titles) @keyword - def get_locations(self, browser: str = "CURRENT") -> List[str]: + def get_locations(self, browser: str = "CURRENT") -> list[str]: """Returns and logs URLs of all windows of the selected browser. *Browser Scope:* @@ -192,7 +190,7 @@ def minimize_browser_window(self): self.driver.minimize_window() @keyword - def get_window_size(self, inner: bool = False) -> Tuple[float, float]: + def get_window_size(self, inner: bool = False) -> tuple[float, float]: """Returns current window width and height as integers. See also `Set Window Size`. @@ -239,7 +237,8 @@ def set_window_size(self, width: int, height: int, inner: bool = False): | `Set Window Size` | 800 | 600 | True | """ if is_falsy(inner): - return self.driver.set_window_size(width, height) + self.driver.set_window_size(width, height) + return self.driver.set_window_size(width, height) inner_width = int(self.driver.execute_script("return window.innerWidth;")) inner_height = int(self.driver.execute_script("return window.innerHeight;")) @@ -258,7 +257,7 @@ def set_window_size(self, width: int, height: int, inner: bool = False): raise AssertionError("Keyword failed setting correct window size.") @keyword - def get_window_position(self) -> Tuple[int, int]: + def get_window_position(self) -> tuple[int, int]: """Returns current window position. The position is relative to the top left corner of the screen. Returned diff --git a/src/SeleniumLibrary/locators/customlocator.py b/src/SeleniumLibrary/locators/customlocator.py index ab967e0e2..96555c7a2 100644 --- a/src/SeleniumLibrary/locators/customlocator.py +++ b/src/SeleniumLibrary/locators/customlocator.py @@ -31,7 +31,7 @@ def find(self, criteria, tag, constraints, parent): element = BuiltIn().run_keyword( self.finder, parent, criteria, tag, constraints ) - elif hasattr(self.finder, "__call__"): + elif callable(self.finder): element = self.finder(parent, criteria, tag, constraints) else: raise AttributeError( @@ -41,5 +41,4 @@ def find(self, criteria, tag, constraints, parent): # Always return an array if hasattr(element, "__len__") and not isinstance(element, str): return element - else: - return [element] + return [element] diff --git a/src/SeleniumLibrary/locators/elementfinder.py b/src/SeleniumLibrary/locators/elementfinder.py index e48f63f87..b3749b522 100644 --- a/src/SeleniumLibrary/locators/elementfinder.py +++ b/src/SeleniumLibrary/locators/elementfinder.py @@ -14,13 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. import re -from typing import Union from robot.api import logger from robot.utils import NormalizedDict +from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement -from selenium.webdriver.common.by import By from SeleniumLibrary.base import ContextAware from SeleniumLibrary.errors import ElementNotFound @@ -78,7 +77,7 @@ def __init__(self, ctx): def find( self, - locator: Union[str, list], + locator: str | list, tag=None, first_only=True, required=True, @@ -92,7 +91,7 @@ def find( ) return self._find(locators[-1], tag, first_only, required, element) - def _split_locator(self, locator: Union[str, list]) -> list: + def _split_locator(self, locator: str | list) -> list: if isinstance(locator, list): return locator if not isinstance(locator, str): @@ -226,10 +225,10 @@ def _find_by_data_locator(self, criteria, tag, constraints, parent): name, value = criteria.split(":", 1) if "" in [name, value]: raise ValueError - except ValueError: + except ValueError as original_exception: raise ValueError( f"Provided selector ({criteria}) is malformed. Correct format: name:value." - ) + ) from original_exception local_criteria = f'//*[@data-{name}="{value}"]' return self._find_by_xpath(local_criteria, tag, constraints, parent) @@ -257,20 +256,18 @@ def _find_by_default(self, criteria, tag, constraints, parent): return self._normalize(parent.find_elements(By.XPATH, xpath)) def _get_xpath_constraints(self, constraints): - xpath_constraints = [ + return [ self._get_xpath_constraint(name, value) for name, value in constraints.items() ] - return xpath_constraints def _get_xpath_constraint(self, name, value): if isinstance(value, list): value = "' or . = '".join(value) return f"@{name}[. = '{value}']" - else: - return f"@{name}='{value}'" + return f"@{name}='{value}'" - def _get_tag_and_constraints(self, tag): + def _get_tag_and_constraints(self, tag): # noqa: C901 if tag is None: return None, {} tag = tag.lower() @@ -331,7 +328,7 @@ def _get_locator_separator_index(self, locator): return min(locator.find("="), locator.find(":")) def _element_matches(self, element, tag, constraints): - if not element.tag_name.lower() == tag: + if element.tag_name.lower() != tag: return False for name in constraints: if isinstance(constraints[name], list): diff --git a/src/SeleniumLibrary/locators/windowmanager.py b/src/SeleniumLibrary/locators/windowmanager.py index a785babbd..967d4be54 100644 --- a/src/SeleniumLibrary/locators/windowmanager.py +++ b/src/SeleniumLibrary/locators/windowmanager.py @@ -15,7 +15,7 @@ # limitations under the License. import time -from collections import namedtuple +from typing import NamedTuple from selenium.common.exceptions import NoSuchWindowException, WebDriverException @@ -23,7 +23,12 @@ from SeleniumLibrary.errors import WindowNotFound -WindowInfo = namedtuple("WindowInfo", "handle, id, name, title, url") +class WindowInfo(NamedTuple): + handle: str + id: object + name: str + title: str + url: str class WindowManager(ContextAware): @@ -40,19 +45,18 @@ def get_window_handles(self, browser): if isinstance(browser, str) and browser == "ALL": handles = [] current_index = self.drivers.current_index - for index, driver in enumerate(self.drivers, 1): + for index, _driver in enumerate(self.drivers, 1): self.drivers.switch(index) handles.extend(self.driver.window_handles) self.drivers.switch(current_index) return handles - elif isinstance(browser, str) and browser == "CURRENT": + if isinstance(browser, str) and browser == "CURRENT": return self.driver.window_handles - else: - current_index = self.drivers.current_index - self.drivers.switch(browser) - handles = self.driver.window_handles - self.drivers.switch(current_index) - return handles + current_index = self.drivers.current_index + self.drivers.switch(browser) + handles = self.driver.window_handles + self.drivers.switch(current_index) + return handles def get_window_infos(self, browser="CURRENT"): try: @@ -61,18 +65,17 @@ def get_window_infos(self, browser="CURRENT"): current_index = None if isinstance(browser, str) and browser.upper() == "ALL": infos = [] - for index, driver in enumerate(self.drivers, 1): + for index, _driver in enumerate(self.drivers, 1): self.drivers.switch(index) infos.extend(self._get_window_infos()) self.drivers.switch(current_index) return infos - elif isinstance(browser, str) and browser.upper() == "CURRENT": + if isinstance(browser, str) and browser.upper() == "CURRENT": return self._get_window_infos() - else: - self.drivers.switch(browser) - infos = self._get_window_infos() - self.drivers.switch(current_index) - return infos + self.drivers.switch(browser) + infos = self._get_window_infos() + self.drivers.switch(current_index) + return infos def _get_window_infos(self): infos = [] @@ -196,14 +199,14 @@ def _select_matching(self, matcher, error): def _get_current_window_info(self): try: - id, name = self.driver.execute_script("return [ window.id, window.name ];") + window_id, name = self.driver.execute_script("return [ window.id, window.name ];") except WebDriverException: # The webdriver implementation doesn't support Javascript so we # can't get window id or name this way. - id = name = None + window_id = name = None return WindowInfo( self.driver.current_window_handle, - id if id is not None else "undefined", + window_id if window_id is not None else "undefined", name or "undefined", self.driver.title or "undefined", self.driver.current_url or "undefined", diff --git a/src/SeleniumLibrary/utils/__init__.py b/src/SeleniumLibrary/utils/__init__.py index 68ba94e1b..b134b8c2f 100644 --- a/src/SeleniumLibrary/utils/__init__.py +++ b/src/SeleniumLibrary/utils/__init__.py @@ -17,14 +17,14 @@ from robot.utils import plural_or_not, secs_to_timestr, timestr_to_secs # noqa from .librarylistener import LibraryListener # noqa -from .types import ( +from .types import ( #noqa is_falsy, is_noney, is_truthy, WINDOWS, _convert_timeout, _convert_delay, -) # noqa +) def escape_xpath_value(value: str): diff --git a/src/SeleniumLibrary/utils/events/__init__.py b/src/SeleniumLibrary/utils/events/__init__.py index edbae3fb5..d1f90519d 100644 --- a/src/SeleniumLibrary/utils/events/__init__.py +++ b/src/SeleniumLibrary/utils/events/__init__.py @@ -14,10 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .scope_event import ScopeStart, ScopeEnd +from .scope_event import ScopeEnd, ScopeStart - -__all__ = ["on", "dispatch", "register_event"] +__all__ = ["dispatch", "on", "register_event"] _registered_events = [ScopeStart, ScopeEnd] _events = [] diff --git a/src/SeleniumLibrary/utils/events/event.py b/src/SeleniumLibrary/utils/events/event.py index 711ea7af4..ec5381548 100644 --- a/src/SeleniumLibrary/utils/events/event.py +++ b/src/SeleniumLibrary/utils/events/event.py @@ -15,8 +15,8 @@ # limitations under the License. import abc -from selenium.webdriver.support.event_firing_webdriver import EventFiringWebElement -from robot.api import logger + +import selenium class Event: @@ -26,8 +26,4 @@ def trigger(self, *args, **kwargs): def selenium_major_version(): - import selenium - - selenium_version = selenium.__version__ - (major, *sub_versions) = selenium_version.split(".") - return int(major) + return int(selenium.__version__.split(".", 1)[0]) diff --git a/src/SeleniumLibrary/utils/types.py b/src/SeleniumLibrary/utils/types.py index 04ecc8f40..4c0579481 100644 --- a/src/SeleniumLibrary/utils/types.py +++ b/src/SeleniumLibrary/utils/types.py @@ -17,11 +17,9 @@ from datetime import timedelta from typing import Any, TypeAlias -from robot.utils import timestr_to_secs -from robot.utils import is_truthy, is_falsy # noqa +from robot.utils import is_falsy, is_truthy, timestr_to_secs # noqa from selenium.webdriver.remote.webelement import WebElement - # Need only for unit tests and can be removed when Approval tests fixes: # https://github.com/approvals/ApprovalTests.Python/issues/41 WINDOWS = os.name == "nt" @@ -29,14 +27,13 @@ Locator: TypeAlias = WebElement | str | list['Locator'] def is_noney(item): - return item is None or isinstance(item, str) and item.upper() == "NONE" + return item is None or (isinstance(item, str) and item.upper() == "NONE") def _convert_delay(delay): if isinstance(delay, timedelta): return delay.microseconds // 1000 - else: - x = timestr_to_secs(delay) - return int( x * 1000) + x = timestr_to_secs(delay) + return int( x * 1000) def _convert_timeout(timeout): diff --git a/tasks.py b/tasks.py index 2948421b0..699c68bf3 100644 --- a/tasks.py +++ b/tasks.py @@ -186,11 +186,12 @@ def init_labels(ctx, username=None, password=None): @task -def lint(ctx): +def lint(ctx, fix=False): """Runs Ruff format check and linter for project Python code.""" - ctx.run(f"{sys.executable} -m ruff format --check tasks.py src/ utest/ atest/") - ctx.run(f"{sys.executable} -m ruff check tasks.py src/ utest/ atest/") - + ruff_cmd = f"{sys.executable} -m ruff check --config pyproject.toml src/" # utest/" # atest/" + if fix: + ruff_cmd = f"{ruff_cmd} --fix" + ctx.run(ruff_cmd) @task def gen_stub(ctx): @@ -212,7 +213,7 @@ def atest(ctx, suite=None): inv utest --suite keywords/test_browsermanagement.py inv utest --suite keywords/test_selenium_options_parser.py::test_create_chrome_with_options """ - command = "python atest/run.py headlesschrome" + command = f"{sys.executable} atest/run.py headlesschrome" if suite: command = f"{command} --suite {suite}" ctx.run(command) diff --git a/utest/test/api/test_accessing_keywod_methods.py b/utest/test/api/test_accessing_keywod_methods.py index 9576a7f4c..85325396a 100644 --- a/utest/test/api/test_accessing_keywod_methods.py +++ b/utest/test/api/test_accessing_keywod_methods.py @@ -9,10 +9,10 @@ def setUpClass(cls): cls.selib = SeleniumLibrary() def test_kw_with_method_name(self): - self.assertTrue(self.selib.keywords["add_cookie"]) - self.assertTrue(self.selib.attributes["add_cookie"]) - self.assertTrue(self.selib.keywords["page_should_contain_image"]) - self.assertTrue(self.selib.attributes["page_should_contain_image"]) + assert self.selib.keywords["add_cookie"] + assert self.selib.attributes["add_cookie"] + assert self.selib.keywords["page_should_contain_image"] + assert self.selib.attributes["page_should_contain_image"] def test_kw_with_methods_name_do_not_have_kw_name(self): with self.assertRaises(KeyError): @@ -21,7 +21,7 @@ def test_kw_with_methods_name_do_not_have_kw_name(self): self.selib.keywords["Page Should Contain Image"] def test_kw_with_decorated_name(self): - self.assertTrue(self.selib.attributes["get_webelement"]) - self.assertTrue(self.selib.keywords["Get WebElement"]) - self.assertTrue(self.selib.attributes["get_webelements"]) - self.assertTrue(self.selib.keywords["Get WebElements"]) + assert self.selib.attributes["get_webelement"] + assert self.selib.keywords["Get WebElement"] + assert self.selib.attributes["get_webelements"] + assert self.selib.keywords["Get WebElements"] diff --git a/utest/test/api/test_event_firing_webdriver.py b/utest/test/api/test_event_firing_webdriver.py index 205eace61..a4b71e23c 100644 --- a/utest/test/api/test_event_firing_webdriver.py +++ b/utest/test/api/test_event_firing_webdriver.py @@ -19,7 +19,7 @@ def test_import_event_firing_webdriver(self): def test_no_event_firing_webdriver(self): sl = SeleniumLibrary() - self.assertIsNone(sl.event_firing_webdriver) + assert sl.event_firing_webdriver is None def test_import_event_firing_webdriver_error_module(self): listener = os.path.join(self.root_dir, "MyListenerWrongName.py") diff --git a/utest/test/api/test_plugin_keyword_tags.py b/utest/test/api/test_plugin_keyword_tags.py index 368f67783..745d1ba68 100644 --- a/utest/test/api/test_plugin_keyword_tags.py +++ b/utest/test/api/test_plugin_keyword_tags.py @@ -16,33 +16,33 @@ def setUpClass(cls): def test_no_plugin(self): sl = SeleniumLibrary() tags = sl.get_keyword_tags("open_browser") - self.assertFalse(tags) + assert not tags def test_store_plugin_keywords(self): sl = SeleniumLibrary() sl._store_plugin_keywords(my_lib("0")) - self.assertEqual(sl._plugin_keywords, ["bar", "foo"]) + assert sl._plugin_keywords == ["bar", "foo"] def test_store_plugin_keywords_with_args(self): sl = SeleniumLibrary() sl._store_plugin_keywords(my_lib_args("000", "111", "222")) - self.assertEqual(sl._plugin_keywords, ["add_cookie", "bar_2", "foo_1"]) + assert sl._plugin_keywords == ["add_cookie", "bar_2", "foo_1"] def test_tags_in_plugin(self): sl = SeleniumLibrary(plugins=self.plugin) tags = sl.get_keyword_tags("foo") - self.assertEqual(tags, ["plugin"]) + assert tags == ["plugin"] tags = sl.get_keyword_tags("open_browser") - self.assertFalse(tags) + assert not tags def test_tags_in_plugin_args(self): sl = SeleniumLibrary(plugins=f"{self.plugin_varargs};foo;bar") tags = sl.get_keyword_tags("foo_1") - self.assertEqual(tags, ["MyTag", "plugin"]) + assert tags == ["MyTag", "plugin"] tags = sl.get_keyword_tags("open_browser") - self.assertFalse(tags) + assert not tags tags = sl.get_keyword_tags("add_cookie") - self.assertEqual(tags, ["plugin"]) + assert tags == ["plugin"] diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py index 16f5bd154..3e50e5ad0 100644 --- a/utest/test/api/test_plugins.py +++ b/utest/test/api/test_plugins.py @@ -22,62 +22,62 @@ def setUpClass(cls): def test_no_libraries(self): for item in [None, "None", ""]: sl = SeleniumLibrary(plugins=item) - self.assertEqual(len(sl.get_keyword_names()), 183) + assert len(sl.get_keyword_names()) == 183 def test_parse_library(self): plugin = "path.to.MyLibrary" plugins = self.sl._string_to_modules(plugin) - self.assertEqual(len(plugins), 1) - self.assertEqual(plugins[0].module, plugin) - self.assertEqual(plugins[0].args, []) - self.assertEqual(plugins[0].kw_args, {}) + assert len(plugins) == 1 + assert plugins[0].module == plugin + assert plugins[0].args == [] + assert plugins[0].kw_args == {} def test_parse_libraries(self): plugin = "path.to.MyLibrary,path.to.OtherLibrary" plugins = self.sl._string_to_modules(plugin) - self.assertEqual(len(plugins), 2) - self.assertEqual(plugins[0].module, plugin.split(",")[0]) - self.assertEqual(plugins[0].args, []) - self.assertEqual(plugins[1].module, plugin.split(",")[1]) - self.assertEqual(plugins[1].args, []) + assert len(plugins) == 2 + assert plugins[0].module == plugin.split(",")[0] + assert plugins[0].args == [] + assert plugins[1].module == plugin.split(",")[1] + assert plugins[1].args == [] def test_comma_and_space(self): plugin = "path.to.MyLibrary , path.to.OtherLibrary" plugins = self.sl._string_to_modules(plugin) - self.assertEqual(len(plugins), 2) - self.assertEqual(plugins[0].module, "path.to.MyLibrary") - self.assertEqual(plugins[0].args, []) - self.assertEqual(plugins[1].module, "path.to.OtherLibrary") - self.assertEqual(plugins[1].args, []) + assert len(plugins) == 2 + assert plugins[0].module == "path.to.MyLibrary" + assert plugins[0].args == [] + assert plugins[1].module == "path.to.OtherLibrary" + assert plugins[1].args == [] def test_comma_and_space_with_arg(self): plugin = "path.to.MyLibrary;foo;bar , path.to.OtherLibrary" plugins = self.sl._string_to_modules(plugin) - self.assertEqual(len(plugins), 2) - self.assertEqual(plugins[0].module, "path.to.MyLibrary") - self.assertEqual(plugins[0].args, ["foo", "bar"]) - self.assertEqual(plugins[1].module, "path.to.OtherLibrary") - self.assertEqual(plugins[1].args, []) + assert len(plugins) == 2 + assert plugins[0].module == "path.to.MyLibrary" + assert plugins[0].args == ["foo", "bar"] + assert plugins[1].module == "path.to.OtherLibrary" + assert plugins[1].args == [] def test_parse_library_with_args(self): plugin = "path.to.MyLibrary" plugin_args = "arg1;arg2" parsed_plugins = self.sl._string_to_modules(f"{plugin};{plugin_args}") parsed_plugin = parsed_plugins[0] - self.assertEqual(len(parsed_plugins), 1) - self.assertEqual(parsed_plugin.module, plugin) - self.assertEqual(parsed_plugin.args, [arg for arg in plugin_args.split(";")]) - self.assertEqual(parsed_plugin.kw_args, {}) + assert len(parsed_plugins) == 1 + assert parsed_plugin.module == plugin + assert parsed_plugin.args == [arg for arg in plugin_args.split(";")] + assert parsed_plugin.kw_args == {} def test_parse_plugin_with_kw_args(self): plugin = "PluginWithKwArgs.py" plugin_args = "kw1=Text1;kw2=Text2" parsed_plugins = self.sl._string_to_modules(f"{plugin};{plugin_args}") parsed_plugin = parsed_plugins[0] - self.assertEqual(len(parsed_plugins), 1) - self.assertEqual(parsed_plugin.module, plugin) - self.assertEqual(parsed_plugin.args, []) - self.assertEqual(parsed_plugin.kw_args, {"kw1": "Text1", "kw2": "Text2"}) + assert len(parsed_plugins) == 1 + assert parsed_plugin.module == plugin + assert parsed_plugin.args == [] + assert parsed_plugin.kw_args == {"kw1": "Text1", "kw2": "Text2"} def test_plugin_does_not_exist(self): not_here = os.path.join(self.root_dir, "not_here.py") @@ -117,12 +117,12 @@ def test_plugin_as_last_in_init(self): sl = SeleniumLibrary( plugins=plugin_file, event_firing_webdriver=event_firing_wd ) - self.assertEqual(sl.event_firing_webdriver, "should be last") + assert sl.event_firing_webdriver == "should be last" def test_easier_event_firing_webdriver_from_plugin(self): plugin_file = os.path.join( self.root_dir, "plugin_with_event_firing_webdriver.py" ) sl = SeleniumLibrary(plugins=plugin_file) - self.assertEqual(sl._plugin_keywords, ["tidii"]) - self.assertEqual(sl.event_firing_webdriver, "event_firing_webdriver") + assert sl._plugin_keywords == ["tidii"] + assert sl.event_firing_webdriver == "event_firing_webdriver" diff --git a/utest/test/keywords/test_input_text_file_decorator.py b/utest/test/keywords/test_input_text_file_decorator.py index cd748e785..4ef4c4c87 100644 --- a/utest/test/keywords/test_input_text_file_decorator.py +++ b/utest/test/keywords/test_input_text_file_decorator.py @@ -17,8 +17,8 @@ def tearDown(self): def test_file_decorator_not_file(self): when(self.file).choose_file().thenReturn(False) - self.assertEqual(self.file.is_local_file("some string"), None) + assert self.file.is_local_file("some string") == None def test_file_decodator_is_file_choose_file(self): when(self.file).choose_file().thenReturn(True) - self.assertEqual(self.file.is_local_file("some_file"), None) + assert self.file.is_local_file("some_file") == None diff --git a/utest/test/keywords/test_keyword_arguments_browsermanagement.py b/utest/test/keywords/test_keyword_arguments_browsermanagement.py index 0d730878a..6daffc7c2 100644 --- a/utest/test/keywords/test_keyword_arguments_browsermanagement.py +++ b/utest/test/keywords/test_keyword_arguments_browsermanagement.py @@ -25,13 +25,13 @@ def test_open_browser(self): "firefox", None, None, False, None, None, None, None ).thenReturn(browser) alias = self.brorser.open_browser(url) - self.assertEqual(alias, None) + assert alias == None when(self.brorser)._make_driver( "firefox", None, None, remote_url, None, None, None, None ).thenReturn(browser) alias = self.brorser.open_browser(url, alias="None", remote_url=remote_url) - self.assertEqual(alias, None) + assert alias == None def test_same_alias(self): url = "https://github.com/robotframework" diff --git a/utest/test/keywords/test_webdrivercache.py b/utest/test/keywords/test_webdrivercache.py index f138565f2..26e0db927 100644 --- a/utest/test/keywords/test_webdrivercache.py +++ b/utest/test/keywords/test_webdrivercache.py @@ -16,7 +16,7 @@ def test_no_current_message(self): try: self.assertRaises(RuntimeError, cache.current.anyMember()) except RuntimeError as e: - self.assertEqual(str(e), "No current browser") + assert str(e) == "No current browser" def test_browsers_property(self): cache = WebDriverCache() @@ -29,13 +29,13 @@ def test_browsers_property(self): index2 = cache.register(driver2) index3 = cache.register(driver3) - self.assertEqual(len(cache.drivers), 3) - self.assertEqual(cache.drivers[0], driver1) - self.assertEqual(cache.drivers[1], driver2) - self.assertEqual(cache.drivers[2], driver3) - self.assertEqual(index1, 1) - self.assertEqual(index2, 2) - self.assertEqual(index3, 3) + assert len(cache.drivers) == 3 + assert cache.drivers[0] == driver1 + assert cache.drivers[1] == driver2 + assert cache.drivers[2] == driver3 + assert index1 == 1 + assert index2 == 2 + assert index3 == 3 def test_get_open_browsers(self): cache = WebDriverCache() @@ -49,16 +49,16 @@ def test_get_open_browsers(self): cache.register(driver3) drivers = cache.active_drivers - self.assertEqual(len(drivers), 3) - self.assertEqual(drivers[0], driver1) - self.assertEqual(drivers[1], driver2) - self.assertEqual(drivers[2], driver3) + assert len(drivers) == 3 + assert drivers[0] == driver1 + assert drivers[1] == driver2 + assert drivers[2] == driver3 cache.close() drivers = cache.active_drivers - self.assertEqual(len(drivers), 2) - self.assertEqual(drivers[0], driver1) - self.assertEqual(drivers[1], driver2) + assert len(drivers) == 2 + assert drivers[0] == driver1 + assert drivers[1] == driver2 def test_close(self): cache = WebDriverCache() @@ -96,19 +96,19 @@ def test_resolve_alias_or_index(self): cache.register(mock()) index = cache.get_index("foo") - self.assertEqual(index, 1) + assert index == 1 index = cache.get_index(1) - self.assertEqual(index, 1) + assert index == 1 index = cache.get_index(3) - self.assertEqual(index, 3) + assert index == 3 index = cache.get_index(None) - self.assertEqual(index, None) + assert index == None index = cache.get_index("None") - self.assertEqual(index, None) + assert index == None def test_resolve_alias_or_index_with_none(self): cache = WebDriverCache() @@ -117,13 +117,13 @@ def test_resolve_alias_or_index_with_none(self): cache.register(mock(), "None") index = cache.get_index("foo") - self.assertEqual(index, 1) + assert index == 1 index = cache.get_index(1) - self.assertEqual(index, 1) + assert index == 1 index = cache.get_index(None) - self.assertEqual(index, None) + assert index == None def test_resolve_alias_or_index_error(self): cache = WebDriverCache() @@ -132,13 +132,13 @@ def test_resolve_alias_or_index_error(self): cache.register(mock()) index = cache.get_index("bar") - self.assertEqual(index, None) + assert index == None index = cache.get_index(12) - self.assertEqual(index, None) + assert index == None index = cache.get_index(-1) - self.assertEqual(index, None) + assert index == None def test_close_and_same_alias(self): cache = WebDriverCache() @@ -147,13 +147,13 @@ def test_close_and_same_alias(self): cache.register(mock(), "bar") cache.close() index = cache.get_index("bar") - self.assertEqual(index, None) + assert index == None def test_same_alias_new_browser(self): cache = WebDriverCache() cache.close() index = cache.get_index("bar") - self.assertEqual(index, None) + assert index == None def test_close_all_cache_first_quite_fails(self): cache = WebDriverCache() @@ -219,8 +219,8 @@ def test_close_quite_fails(self): cache.register(driver, "bar") with self.assertRaises(TimeoutException): cache.close() - self.assertTrue(isinstance(cache.current, NoConnection)) - self.assertTrue(driver in cache._closed) + assert isinstance(cache.current, NoConnection) + assert driver in cache._closed def test_close_no_error(self): cache = WebDriverCache() @@ -228,10 +228,10 @@ def test_close_no_error(self): when(driver).quit().thenReturn(None) cache.register(driver, "bar") cache.close() - self.assertTrue(isinstance(cache.current, NoConnection)) - self.assertTrue(driver in cache._closed) + assert isinstance(cache.current, NoConnection) + assert driver in cache._closed def verify_cache(self, cache): - self.assertEqual(cache._connections, []) - self.assertEqual(cache._aliases, {}) - self.assertTrue(isinstance(cache.current, NoConnection)) + assert cache._connections == [] + assert cache._aliases == {} + assert isinstance(cache.current, NoConnection) diff --git a/utest/test/keywords/test_windowmananger_window_info.py b/utest/test/keywords/test_windowmananger_window_info.py index 16abd144e..c1a5c90ac 100644 --- a/utest/test/keywords/test_windowmananger_window_info.py +++ b/utest/test/keywords/test_windowmananger_window_info.py @@ -28,50 +28,48 @@ def mock_window_info(self, id_, name, title, url): def test_window_info_values_are_strings(self): self.mock_window_info("id", "name", "title", "url") info = self.manager._get_current_window_info() - self.assertEqual(info, (HANDLE, "id", "name", "title", "url")) + assert info == (HANDLE, "id", "name", "title", "url") def test_window_info_values_are_none(self): self.mock_window_info(None, None, None, None) info = self.manager._get_current_window_info() - self.assertEqual( - info, (HANDLE, "undefined", "undefined", "undefined", "undefined") - ) + assert info == (HANDLE, "undefined", "undefined", "undefined", "undefined") def test_window_info_values_are_empty_strings(self): self.mock_window_info("", "", "", "") info = self.manager._get_current_window_info() - self.assertEqual(info, (HANDLE, "", "undefined", "undefined", "undefined")) + assert info == (HANDLE, "", "undefined", "undefined", "undefined") def test_window_id_is_bool(self): self.mock_window_info(True, "", "", "") info = self.manager._get_current_window_info() - self.assertEqual(info[1], True) + assert info[1] == True self.mock_window_info(False, "", "", "") info = self.manager._get_current_window_info() - self.assertEqual(info[1], False) + assert info[1] == False def test_window_id_is_web_element(self): elem = mock() self.mock_window_info(*[elem, "", "", ""]) info = self.manager._get_current_window_info() - self.assertEqual(info[1], elem) + assert info[1] == elem def test_window_id_is_container(self): self.mock_window_info(*[["1"], "", "", ""]) info = self.manager._get_current_window_info() - self.assertEqual(info[1], ["1"]) + assert info[1] == ["1"] self.mock_window_info(*[{"a": 2}, "", "", ""]) info = self.manager._get_current_window_info() - self.assertEqual(info[1], {"a": 2}) + assert info[1] == {"a": 2} def test_window_id_is_empty_container(self): self.mock_window_info(*[[], "", "", ""]) info = self.manager._get_current_window_info() - self.assertEqual(info[1], []) + assert info[1] == [] self.mock_window_info(*[{}, "", "", ""]) info = self.manager._get_current_window_info() - self.assertEqual(info[1], {}) + assert info[1] == {} def test_no_javascript_support(self): when(self.driver).execute_script(SCRIPT).thenRaise(WebDriverException) @@ -79,4 +77,4 @@ def test_no_javascript_support(self): self.driver.current_url = "url" self.driver.current_window_handle = HANDLE info = self.manager._get_current_window_info() - self.assertEqual(info, (HANDLE, "undefined", "undefined", "title", "url")) + assert info == (HANDLE, "undefined", "undefined", "title", "url") diff --git a/utest/test/locators/test_windowmanager.py b/utest/test/locators/test_windowmanager.py index f40770ae4..b4cd0768e 100644 --- a/utest/test/locators/test_windowmanager.py +++ b/utest/test/locators/test_windowmanager.py @@ -12,10 +12,7 @@ def test_select_with_invalid_prefix(self): manager = WindowManagerWithMockBrowser() with self.assertRaises(WindowNotFound) as context: manager.select("something=test1") - self.assertEqual( - str(context.exception), - "No window matching handle, name, title or URL 'something=test1' found.", - ) + assert str(context.exception) == "No window matching handle, name, title or URL 'something=test1' found." def test_select_by_title(self): manager = WindowManagerWithMockBrowser( @@ -24,7 +21,7 @@ def test_select_by_title(self): {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) manager.select("title=Title 2") - self.assertEqual(manager.driver.current_window.name, "win2") + assert manager.driver.current_window.name == "win2" def test_select_by_title_with_multiple_matches(self): manager = WindowManagerWithMockBrowser( @@ -41,7 +38,7 @@ def test_select_by_title_with_multiple_matches(self): }, ) manager.select("title=Title 2") - self.assertEqual(manager.driver.current_window.name, "win2a") + assert manager.driver.current_window.name == "win2a" def test_select_by_title_no_match(self): manager = WindowManagerWithMockBrowser( @@ -51,9 +48,7 @@ def test_select_by_title_no_match(self): ) with self.assertRaises(WindowNotFound) as context: manager.select("title=Title -1") - self.assertEqual( - str(context.exception), "Unable to locate window with title 'Title -1'." - ) + assert str(context.exception) == "Unable to locate window with title 'Title -1'." def test_select_by_name(self): manager = WindowManagerWithMockBrowser( @@ -62,7 +57,7 @@ def test_select_by_name(self): {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) manager.select("name=win2") - self.assertEqual(manager.driver.current_window.name, "win2") + assert manager.driver.current_window.name == "win2" def test_select_by_name_no_match(self): manager = WindowManagerWithMockBrowser( @@ -72,9 +67,7 @@ def test_select_by_name_no_match(self): ) with self.assertRaises(WindowNotFound) as context: manager.select("name=win-1") - self.assertEqual( - str(context.exception), "Unable to locate window with name 'win-1'." - ) + assert str(context.exception) == "Unable to locate window with name 'win-1'." def test_select_by_url(self): manager = WindowManagerWithMockBrowser( @@ -83,7 +76,7 @@ def test_select_by_url(self): {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) manager.select("url=http://localhost/page2.html") - self.assertEqual(manager.driver.current_window.name, "win2") + assert manager.driver.current_window.name == "win2" def test_select_by_url_with_multiple_matches(self): manager = WindowManagerWithMockBrowser( @@ -100,7 +93,7 @@ def test_select_by_url_with_multiple_matches(self): }, ) manager.select("url=http://localhost/page2.html") - self.assertEqual(manager.driver.current_window.name, "win2a") + assert manager.driver.current_window.name == "win2a" def test_select_by_url_no_match(self): manager = WindowManagerWithMockBrowser( @@ -110,10 +103,7 @@ def test_select_by_url_no_match(self): ) with self.assertRaises(WindowNotFound) as context: manager.select("url=http://localhost/page-1.html") - self.assertEqual( - str(context.exception), - "Unable to locate window with URL 'http://localhost/page-1.html'.", - ) + assert str(context.exception) == "Unable to locate window with URL 'http://localhost/page-1.html'." def test_select_main_window(self): manager = WindowManagerWithMockBrowser( @@ -122,11 +112,11 @@ def test_select_main_window(self): {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) manager.select("name=win2") - self.assertEqual(manager.driver.current_window.name, "win2") + assert manager.driver.current_window.name == "win2" manager.select("main") - self.assertEqual(manager.driver.current_window.name, "win1") + assert manager.driver.current_window.name == "win1" manager.select("MAIN") - self.assertEqual(manager.driver.current_window.name, "win1") + assert manager.driver.current_window.name == "win1" def test_select_by_default_with_name(self): manager = WindowManagerWithMockBrowser( @@ -135,7 +125,7 @@ def test_select_by_default_with_name(self): {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) manager.select("win2") - self.assertEqual(manager.driver.current_window.name, "win2") + assert manager.driver.current_window.name == "win2" def test_select_by_default_with_title(self): manager = WindowManagerWithMockBrowser( @@ -144,7 +134,7 @@ def test_select_by_default_with_title(self): {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) manager.select("Title 2") - self.assertEqual(manager.driver.current_window.name, "win2") + assert manager.driver.current_window.name == "win2" def test_select_by_default_no_match(self): manager = WindowManagerWithMockBrowser( @@ -154,10 +144,7 @@ def test_select_by_default_no_match(self): ) with self.assertRaises(WindowNotFound) as context: manager.select("foobar") - self.assertEqual( - str(context.exception), - "No window matching handle, name, title or URL 'foobar' found.", - ) + assert str(context.exception) == "No window matching handle, name, title or URL 'foobar' found." def test_prefix_is_case_sensitive(self): manager = WindowManagerWithMockBrowser( @@ -166,13 +153,10 @@ def test_prefix_is_case_sensitive(self): {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) manager.select("name=win2") - self.assertEqual(manager.driver.current_window.name, "win2") + assert manager.driver.current_window.name == "win2" with self.assertRaises(WindowNotFound) as context: manager.select("nAmE=win2") - self.assertEqual( - str(context.exception), - "No window matching handle, name, title or URL 'nAmE=win2' found.", - ) + assert str(context.exception) == "No window matching handle, name, title or URL 'nAmE=win2' found." def test_get_window_infos(self): manager = WindowManagerWithMockBrowser( @@ -180,21 +164,10 @@ def test_get_window_infos(self): {"id": "id2", "name": "win2", "title": "Title 2", "url": "http://url.2"}, {"name": "win3", "title": "Title 3", "url": "http://url.3"}, ) - self.assertEqual( - [info.id for info in manager.get_window_infos()], - ["id1", "id2", "undefined"], - ) - self.assertEqual( - [info.name for info in manager.get_window_infos()], ["win1", "win2", "win3"] - ) - self.assertEqual( - [info.title for info in manager.get_window_infos()], - ["Title 1", "Title 2", "Title 3"], - ) - self.assertEqual( - [info.url for info in manager.get_window_infos()], - ["http://url.1", "http://url.2", "http://url.3"], - ) + assert [info.id for info in manager.get_window_infos()] == ["id1", "id2", "undefined"] + assert [info.name for info in manager.get_window_infos()] == ["win1", "win2", "win3"] + assert [info.title for info in manager.get_window_infos()] == ["Title 1", "Title 2", "Title 3"] + assert [info.url for info in manager.get_window_infos()] == ["http://url.1", "http://url.2", "http://url.3"] class WindowManagerWithMockBrowser(WindowManager): diff --git a/utest/test/utils/test_package.py b/utest/test/utils/test_package.py index 2043421f7..c5b0a4468 100644 --- a/utest/test/utils/test_package.py +++ b/utest/test/utils/test_package.py @@ -5,13 +5,10 @@ class UtilsPackageTests(unittest.TestCase): def test_escape_xpath_value_with_apos(self): - self.assertEqual(escape_xpath_value("test '1'"), "\"test '1'\"") + assert escape_xpath_value("test '1'") == "\"test '1'\"" def test_escape_xpath_value_with_quote(self): - self.assertEqual(escape_xpath_value('test "1"'), "'test \"1\"'") + assert escape_xpath_value('test "1"') == "'test \"1\"'" def test_escape_xpath_value_with_quote_and_apos(self): - self.assertEqual( - escape_xpath_value("test \"1\" and '2'"), - "concat('test \"1\" and ', \"'\", '2', \"'\", '')", - ) + assert escape_xpath_value("test \"1\" and '2'") == "concat('test \"1\" and ', \"'\", '2', \"'\", '')" From 9538508693a97945afddb3034c04348a4978855b Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sat, 25 Apr 2026 17:41:52 +0200 Subject: [PATCH 03/13] Add early return in wait_until_location_is for improved flow control --- src/SeleniumLibrary/keywords/waiting.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index bac1d8a21..2d6cfe81e 100644 --- a/src/SeleniumLibrary/keywords/waiting.py +++ b/src/SeleniumLibrary/keywords/waiting.py @@ -249,6 +249,7 @@ def wait_until_page_contains_element( timeout, error, ) + return self._wait_until( lambda: len(self.find_elements(locator)) == limit, f'Page should have contained "{limit}" {locator} element(s) within .', @@ -287,6 +288,7 @@ def wait_until_page_does_not_contain_element( timeout, error, ) + return self._wait_until( lambda: len(self.find_elements(locator)) != limit, f'Page should have not contained "{limit}" {locator} element(s) within .', From a6e6f62daa2ca45805c7dd2857f4e30027be351c Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 10:32:10 +0200 Subject: [PATCH 04/13] Refactor tests to use pytest and improve error handling - Updated test_event_firing_webdriver.py to use pytest for exception assertions. - Refactored test_plugin_keyword_tags.py to utilize MyLib and MyLibArgs classes. - Changed test_plugins.py to use NamedTuple for plugin representation and improved exception handling with pytest. - Modified test_entry_point.py to include pathlib import. - Reordered imports and improved exception assertions in various test files. - Enhanced test_webdrivercreator.py and test_webdrivercreator_executable_path.py for better readability and consistency. - Updated test_browsermanagement.py to use pytest for exception assertions and improved assertions. - Refactored test_click_modifier.py to streamline invalid modifier tests. - Improved test_cookie.py and test_expectedconditions.py for consistency and clarity. - Enhanced test_firefox_profile_parsing.py and test_input_text_file_decorator.py for better readability. - Refactored test_keyword_arguments_browsermanagement.py and test_keyword_arguments_element.py for consistency. - Updated test_windowmanager.py and test_elementfinder.py to improve exception handling and assertions. - Added translation tests and improved type handling in test_types.py. Co-authored-by: Copilot --- pyproject.toml | 1 + src/SeleniumLibrary/__init__.py | 2 +- tasks.py | 2 +- utest/run.py | 8 ++--- utest/test/api/my_lib.py | 8 +++-- utest/test/api/my_lib_args.py | 4 ++- utest/test/api/my_lib_not_inherit.py | 4 ++- utest/test/api/my_lib_wrong_name.py | 2 +- utest/test/api/plugin_tester.py | 4 ++- .../api/plugin_with_event_firing_webdriver.py | 10 +++--- .../test/api/test_accessing_keywod_methods.py | 6 ++-- utest/test/api/test_event_firing_webdriver.py | 5 +-- utest/test/api/test_plugin_keyword_tags.py | 9 ++--- utest/test/api/test_plugins.py | 20 ++++++----- utest/test/entry/test_entry_point.py | 2 +- .../keywords/IGNOREDtest_webdrivercreator.py | 10 +++--- ...REtest_webdrivercreator_executable_path.py | 6 ++-- ...Etest_webdrivercreator_service_log_path.py | 20 +++++------ utest/test/keywords/test_browsermanagement.py | 31 +++++++--------- utest/test/keywords/test_click_modifier.py | 15 +++----- utest/test/keywords/test_cookie.py | 9 +++-- .../test/keywords/test_expectedconditions.py | 4 +-- .../keywords/test_firefox_profile_parsing.py | 5 ++- .../test_input_text_file_decorator.py | 4 +-- ...est_keyword_arguments_browsermanagement.py | 6 ++-- .../test_keyword_arguments_element.py | 7 ++-- .../test_keyword_arguments_formelement.py | 5 ++- .../test_keyword_arguments_selectelement.py | 1 - .../test_keyword_arguments_waiting.py | 1 - utest/test/keywords/test_press_keys.py | 2 +- .../keywords/test_runonfailure_from_lib.py | 2 +- utest/test/keywords/test_screen_shot.py | 2 +- .../keywords/test_selenium_options_parser.py | 13 ++++--- .../keywords/test_selenium_service_parser.py | 7 ++-- utest/test/keywords/test_tablekeywords.py | 2 +- ...ting_stale_element_refereance_exception.py | 2 +- utest/test/keywords/test_webdrivercache.py | 36 +++++++++---------- .../test_windowmananger_window_info.py | 8 ++--- utest/test/locators/test_elementfinder.py | 22 ++++++------ utest/test/locators/test_windowmanager.py | 26 +++++++------- .../__init__.py | 2 +- utest/test/translation/__init__.py | 0 utest/test/translation/test_translation.py | 4 +-- utest/test/utils/test_types.py | 2 +- 44 files changed, 170 insertions(+), 171 deletions(-) create mode 100644 utest/test/translation/__init__.py diff --git a/pyproject.toml b/pyproject.toml index 94a6558ab..6a8f881ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ ignore = [ "N812", # lowercase imported as non lowercase "N999", # Invalid module name: 'SeleniumLibrary' "PLR0913", # too many arguments + "PLR2004", # Magic value used in comparison "DTZ006", # No timezone specified "PTH", # Use Path instead of os.path -> maybe soon "N818", # exception naming convention diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index 0a07be000..bdddc45e7 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -812,7 +812,7 @@ def _parse_listener(self, event_firing_webdriver): listener_module = self._string_to_modules(event_firing_webdriver) listener_count = len(listener_module) if listener_count > 1: - message = f"Is is possible import only one listener but there was {listener_count} listeners." + message = f"It is possible to import only one listener but there were {listener_count} listeners." raise ValueError(message) listener_module = listener_module[0] importer = Importer("test library") diff --git a/tasks.py b/tasks.py index 699c68bf3..f3518a823 100644 --- a/tasks.py +++ b/tasks.py @@ -188,7 +188,7 @@ def init_labels(ctx, username=None, password=None): @task def lint(ctx, fix=False): """Runs Ruff format check and linter for project Python code.""" - ruff_cmd = f"{sys.executable} -m ruff check --config pyproject.toml src/" # utest/" # atest/" + ruff_cmd = f"{sys.executable} -m ruff check --config pyproject.toml src/ utest/" # atest/" if fix: ruff_cmd = f"{ruff_cmd} --fix" ctx.run(ruff_cmd) diff --git a/utest/run.py b/utest/run.py index f201c61f2..4f4785dce 100755 --- a/utest/run.py +++ b/utest/run.py @@ -1,13 +1,13 @@ #!/usr/bin/env python import argparse +import logging import os import shutil import sys from os.path import join from pathlib import Path -from pytest import main as py_main - +import pytest CURDIR = Path(__file__).parent SRC = join(CURDIR, os.pardir, "src") @@ -38,9 +38,9 @@ def run_unit_tests(reporter, reporter_args, suite, verbose): if reporter_args: py_args.insert(1, f"--approvaltests-add-reporter-args={reporter_args}") try: - result = py_main(py_args) + result = pytest.main(py_args) except Exception as error: - print(f"Suppressed error: {error}") + logging.exception(f"Suppressed error: {error}") result = 254 finally: sys.path.pop(0) diff --git a/utest/test/api/my_lib.py b/utest/test/api/my_lib.py index b5c45c932..07883588d 100644 --- a/utest/test/api/my_lib.py +++ b/utest/test/api/my_lib.py @@ -1,14 +1,14 @@ from SeleniumLibrary.base import LibraryComponent, keyword -class my_lib(LibraryComponent): +class MyLib(LibraryComponent): """Some dummy documentation. - = my_lib Heading 1 = + = MyLib Heading 1 = This is heading 1 documentation. - == my_lib Heading 2 == + == MyLib Heading 2 == This is heading 2 documentation. """ @@ -20,3 +20,5 @@ def foo(self): @keyword def bar(self, arg): self.info(arg) + +my_lib = MyLib diff --git a/utest/test/api/my_lib_args.py b/utest/test/api/my_lib_args.py index e1736ca02..39786e1f7 100644 --- a/utest/test/api/my_lib_args.py +++ b/utest/test/api/my_lib_args.py @@ -1,7 +1,7 @@ from SeleniumLibrary.base import LibraryComponent, keyword -class my_lib_args(LibraryComponent): +class MyLibArgs(LibraryComponent): def __init__(self, ctx, arg1, arg2, *args, **kwargs): LibraryComponent.__init__(self, ctx) self.arg1 = arg1 @@ -21,3 +21,5 @@ def bar_2(self, arg): def add_cookie(self, foo, bar): self.info(foo) self.info(bar) + +my_lib_args = MyLibArgs diff --git a/utest/test/api/my_lib_not_inherit.py b/utest/test/api/my_lib_not_inherit.py index 37210be63..804c29009 100644 --- a/utest/test/api/my_lib_not_inherit.py +++ b/utest/test/api/my_lib_not_inherit.py @@ -1,10 +1,12 @@ from SeleniumLibrary.base import keyword -class my_lib_not_inherit: +class MyLibNotInherit: def __init__(self, ctx): self.ctx = ctx @keyword def bar(self, arg): self.info(arg) + +my_lib_not_inherit = MyLibNotInherit diff --git a/utest/test/api/my_lib_wrong_name.py b/utest/test/api/my_lib_wrong_name.py index 8d23a5ba3..56eff8293 100644 --- a/utest/test/api/my_lib_wrong_name.py +++ b/utest/test/api/my_lib_wrong_name.py @@ -1,7 +1,7 @@ from SeleniumLibrary.base import LibraryComponent, keyword -class my_lib(LibraryComponent): +class MyLib(LibraryComponent): @keyword def tidii(self, arg): self.info(arg) diff --git a/utest/test/api/plugin_tester.py b/utest/test/api/plugin_tester.py index 1df9b23e6..2db5585e6 100644 --- a/utest/test/api/plugin_tester.py +++ b/utest/test/api/plugin_tester.py @@ -1,7 +1,7 @@ from SeleniumLibrary.base import LibraryComponent, keyword -class plugin_tester(LibraryComponent): +class PluginTester(LibraryComponent): def __init__(self, ctx): LibraryComponent.__init__(self, ctx) ctx.event_firing_webdriver = "should be last" @@ -13,3 +13,5 @@ def foo(self): @keyword def bar(self, arg): self.info(arg) + +plugin_tester = PluginTester diff --git a/utest/test/api/plugin_with_event_firing_webdriver.py b/utest/test/api/plugin_with_event_firing_webdriver.py index 4246838e6..ddb4103ef 100644 --- a/utest/test/api/plugin_with_event_firing_webdriver.py +++ b/utest/test/api/plugin_with_event_firing_webdriver.py @@ -1,9 +1,9 @@ from SeleniumLibrary.base import LibraryComponent, keyword -class plugin_with_event_firing_webdriver(LibraryComponent): +class PluginWithEventFiringWebdriver(LibraryComponent): - """This is example for plugin_with_event_firing_webdriver plugin documentation. + """This is example for PluginWithEventFiringWebdriver plugin documentation. It may contains many chapters and there might be many words in the documentation. This is really boring example but let @@ -11,11 +11,11 @@ class plugin_with_event_firing_webdriver(LibraryComponent): There might be reference to keywords, like `Open Browser` - == plugin_with_event_firing_webdriver Heading 2 part 1 == + == PluginWithEventFiringWebdriver Heading 2 part 1 == This is chapter in heading 2. - == plugin_with_event_firing_webdriver Heading 2 part 2== + == PluginWithEventFiringWebdriver Heading 2 part 2== This is another chapter in heading 2 """ @@ -32,3 +32,5 @@ def __init__(self, ctx): @keyword def tidii(self): self.info("foo") + +plugin_with_event_firing_webdriver = PluginWithEventFiringWebdriver diff --git a/utest/test/api/test_accessing_keywod_methods.py b/utest/test/api/test_accessing_keywod_methods.py index 85325396a..8049c676a 100644 --- a/utest/test/api/test_accessing_keywod_methods.py +++ b/utest/test/api/test_accessing_keywod_methods.py @@ -1,5 +1,7 @@ import unittest +import pytest + from SeleniumLibrary import SeleniumLibrary @@ -15,9 +17,9 @@ def test_kw_with_method_name(self): assert self.selib.attributes["page_should_contain_image"] def test_kw_with_methods_name_do_not_have_kw_name(self): - with self.assertRaises(KeyError): + with pytest.raises(KeyError): self.selib.keywords["Add Cookie"] - with self.assertRaises(KeyError): + with pytest.raises(KeyError): self.selib.keywords["Page Should Contain Image"] def test_kw_with_decorated_name(self): diff --git a/utest/test/api/test_event_firing_webdriver.py b/utest/test/api/test_event_firing_webdriver.py index a4b71e23c..c5fa4af96 100644 --- a/utest/test/api/test_event_firing_webdriver.py +++ b/utest/test/api/test_event_firing_webdriver.py @@ -1,6 +1,7 @@ import os import unittest +import pytest from robot.errors import DataError from selenium.webdriver.support.events import AbstractEventListener @@ -23,9 +24,9 @@ def test_no_event_firing_webdriver(self): def test_import_event_firing_webdriver_error_module(self): listener = os.path.join(self.root_dir, "MyListenerWrongName.py") - with self.assertRaises(DataError): + with pytest.raises(DataError, match=r"Importing test Selenium lister class '.*' failed."): SeleniumLibrary(event_firing_webdriver=listener) def test_too_many_event_firing_webdriver(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError, match=r"It is possible to import only one listener but there were 2 listeners."): SeleniumLibrary(event_firing_webdriver=f"{self.listener},{self.listener}") diff --git a/utest/test/api/test_plugin_keyword_tags.py b/utest/test/api/test_plugin_keyword_tags.py index 745d1ba68..46063f266 100644 --- a/utest/test/api/test_plugin_keyword_tags.py +++ b/utest/test/api/test_plugin_keyword_tags.py @@ -2,8 +2,9 @@ import unittest from SeleniumLibrary import SeleniumLibrary -from .my_lib import my_lib -from .my_lib_args import my_lib_args + +from .my_lib import MyLib +from .my_lib_args import MyLibArgs class PluginKeywordTags(unittest.TestCase): @@ -20,12 +21,12 @@ def test_no_plugin(self): def test_store_plugin_keywords(self): sl = SeleniumLibrary() - sl._store_plugin_keywords(my_lib("0")) + sl._store_plugin_keywords(MyLib("0")) assert sl._plugin_keywords == ["bar", "foo"] def test_store_plugin_keywords_with_args(self): sl = SeleniumLibrary() - sl._store_plugin_keywords(my_lib_args("000", "111", "222")) + sl._store_plugin_keywords(MyLibArgs("000", "111", "222")) assert sl._plugin_keywords == ["add_cookie", "bar_2", "foo_1"] def test_tags_in_plugin(self): diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py index 3e50e5ad0..bfc195fcb 100644 --- a/utest/test/api/test_plugins.py +++ b/utest/test/api/test_plugins.py @@ -1,7 +1,8 @@ -from collections import namedtuple import os import unittest +from typing import NamedTuple +import pytest from robot.errors import DataError from SeleniumLibrary import SeleniumLibrary @@ -13,7 +14,10 @@ class ExtendingSeleniumLibrary(unittest.TestCase): def setUpClass(cls): cls.sl = SeleniumLibrary() cls.root_dir = os.path.dirname(os.path.abspath(__file__)) - Plugin = namedtuple("Plugin", "plugin, args, kw_args") + class Plugin(NamedTuple): + plugin: str + args: list + kw_args: dict lib = Plugin( plugin=os.path.join(cls.root_dir, "my_lib.py"), args=[], kw_args={} ) @@ -36,7 +40,7 @@ def test_parse_libraries(self): plugin = "path.to.MyLibrary,path.to.OtherLibrary" plugins = self.sl._string_to_modules(plugin) assert len(plugins) == 2 - assert plugins[0].module == plugin.split(",")[0] + assert plugins[0].module == plugin.split(",", maxsplit=1)[0] assert plugins[0].args == [] assert plugins[1].module == plugin.split(",")[1] assert plugins[1].args == [] @@ -66,7 +70,7 @@ def test_parse_library_with_args(self): parsed_plugin = parsed_plugins[0] assert len(parsed_plugins) == 1 assert parsed_plugin.module == plugin - assert parsed_plugin.args == [arg for arg in plugin_args.split(";")] + assert parsed_plugin.args == plugin_args.split(";") assert parsed_plugin.kw_args == {} def test_parse_plugin_with_kw_args(self): @@ -81,16 +85,16 @@ def test_parse_plugin_with_kw_args(self): def test_plugin_does_not_exist(self): not_here = os.path.join(self.root_dir, "not_here.py") - with self.assertRaises(DataError): + with pytest.raises(DataError): SeleniumLibrary(plugins=not_here) - with self.assertRaises(DataError): + with pytest.raises(DataError): SeleniumLibrary(plugins="SeleniumLibrary.NotHere") def test_plugin_wrong_import_with_path(self): my_lib = os.path.join(self.root_dir, "my_lib.py") wrong_name = os.path.join(self.root_dir, "my_lib_wrong_name.py") - with self.assertRaises(DataError): + with pytest.raises(DataError): SeleniumLibrary(plugins=f"{my_lib}, {wrong_name}") def test_sl_with_kw_args_plugin(self): @@ -108,7 +112,7 @@ def test_sl_with_kw_args_plugin(self): def test_no_library_component_inherit(self): no_inherit = os.path.join(self.root_dir, "my_lib_not_inherit.py") - with self.assertRaises(PluginError): + with pytest.raises(PluginError): SeleniumLibrary(plugins=no_inherit) def test_plugin_as_last_in_init(self): diff --git a/utest/test/entry/test_entry_point.py b/utest/test/entry/test_entry_point.py index fcbc10bbf..2d94a64c0 100644 --- a/utest/test/entry/test_entry_point.py +++ b/utest/test/entry/test_entry_point.py @@ -1,6 +1,6 @@ import json -from pathlib import Path import sys +from pathlib import Path from approvaltests import verify_all diff --git a/utest/test/keywords/IGNOREDtest_webdrivercreator.py b/utest/test/keywords/IGNOREDtest_webdrivercreator.py index dd33da50b..0d3bed35b 100644 --- a/utest/test/keywords/IGNOREDtest_webdrivercreator.py +++ b/utest/test/keywords/IGNOREDtest_webdrivercreator.py @@ -1,7 +1,7 @@ import os import pytest -from mockito import mock, verify, when, unstub, ANY +from mockito import ANY, mock, unstub, verify, when from selenium import webdriver from SeleniumLibrary.keywords import WebDriverCreator @@ -36,9 +36,8 @@ def test_get_creator_method(creator): method = creator._get_creator_method("firefox") assert method - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match=r"foobar is not a supported browser\."): creator._get_creator_method("foobar") - assert "foobar is not a supported browser." in str(error.value) def test_parse_capabilities(creator): @@ -172,7 +171,7 @@ def test_chrome_remote_no_caps(creator): def test_chrome_remote_caps(creator): url = "http://localhost:4444/wd/hub" expected_webdriver = mock() - # capabilities = {"browserName": "chrome"} + capabilities = {"browserName": "chrome"} file_detector = mock_file_detector(creator) when(webdriver).Remote( command_executor=url, @@ -205,7 +204,6 @@ def test_chrome_headless(creator): expected_webdriver = mock() options = mock() when(webdriver).ChromeOptions().thenReturn(options) - service = mock() when(webdriver).ChromeOptions().thenReturn(options) when(webdriver).Chrome( options=options, service=ANY # service=None # service_log_path=None, executable_path="chromedriver" @@ -265,7 +263,7 @@ def test_get_ff_profile_no_path(creator): assert profile == profile_mock -def test_get_ff_profile_instance_FirefoxProfile(creator): +def test_get_ff_profile_instance_FirefoxProfile(creator): # noqa: N802 input_profile = webdriver.FirefoxProfile() profile = creator._get_ff_profile(input_profile) assert profile == input_profile diff --git a/utest/test/keywords/IGNOREtest_webdrivercreator_executable_path.py b/utest/test/keywords/IGNOREtest_webdrivercreator_executable_path.py index c4f05b547..5ac5bed01 100644 --- a/utest/test/keywords/IGNOREtest_webdrivercreator_executable_path.py +++ b/utest/test/keywords/IGNOREtest_webdrivercreator_executable_path.py @@ -1,12 +1,11 @@ import os import pytest -from mockito import mock, unstub, when, ANY +from mockito import ANY, mock, unstub, when from selenium import webdriver from SeleniumLibrary.keywords import WebDriverCreator - LOG_DIR = "/log/dir" @@ -247,5 +246,4 @@ def mock_file_detector(creator): def get_geckodriver_log(): # return os.path.join(LOG_DIR, "geckodriver-1.log") # print(f"{os.getcwd()}") - cwd = os.getcwd() - return cwd \ No newline at end of file + return os.getcwd() diff --git a/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py b/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py index eb9fe6c5f..3bde08e87 100644 --- a/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py +++ b/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py @@ -1,25 +1,25 @@ import os -from collections import namedtuple +from typing import NamedTuple import pytest - -from mockito import mock, when, unstub, ANY +from mockito import ANY, mock, unstub, when from selenium import webdriver -from selenium.webdriver import chrome -#from selenium.webdriver.chrome.service import Service as ChromeService -from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome import service as chromeservice +#from selenium.webdriver.chrome.service import Service as ChromeService from SeleniumLibrary.keywords import WebDriverCreator from SeleniumLibrary.utils import WINDOWS @pytest.fixture(scope="module") +class Creator(NamedTuple): + creator: WebDriverCreator + output_dir: str + def creator(): curr_dir = os.path.dirname(os.path.abspath(__file__)) output_dir = os.path.abspath(os.path.join(curr_dir, "..", "..", "output_dir")) creator = WebDriverCreator(output_dir) - Creator = namedtuple("Creator", "creator, output_dir") return Creator(creator, output_dir) @@ -95,7 +95,7 @@ def test_create_headlesschrome_with_service_log_path_real_path(creator): def test_create_firefox_with_service_log_path_none(creator): - log_file = os.path.join(creator.output_dir, "geckodriver-1.log") + # log_file = os.path.join(creator.output_dir, "geckodriver-1.log") expected_webdriver = mock() options = mock() when(webdriver).FirefoxOptions().thenReturn(options) @@ -169,7 +169,7 @@ def test_create_ie_with_service_log_path_real_path(creator): def test_create_edge_with_service_log_path_real_path(creator): - executable_path = "msedgedriver" + # executable_path = "msedgedriver" log_file = os.path.join(creator.output_dir, "edge-1.log") expected_webdriver = mock() when(webdriver).Edge( @@ -197,4 +197,4 @@ def test_create_edge_with_service_log_path_real_path(creator): # expected_webdriver # ) # driver = creator.creator.create_safari({}, None, service_log_path=log_file) -# assert driver == expected_webdriver \ No newline at end of file +# assert driver == expected_webdriver diff --git a/utest/test/keywords/test_browsermanagement.py b/utest/test/keywords/test_browsermanagement.py index 9dcc44dfc..0f048c0ac 100644 --- a/utest/test/keywords/test_browsermanagement.py +++ b/utest/test/keywords/test_browsermanagement.py @@ -1,11 +1,9 @@ import pytest -from mockito import when, mock, verify, verifyNoUnwantedInteractions, ANY +from mockito import ANY, mock, verify, verifyNoUnwantedInteractions, when from selenium import webdriver -from selenium.webdriver.chrome.service import Service as ChromeService -from selenium.webdriver.chrome.service import Service -from SeleniumLibrary.keywords import BrowserManagementKeywords from SeleniumLibrary import SeleniumLibrary +from SeleniumLibrary.keywords import BrowserManagementKeywords def test_set_selenium_timeout_only_affects_open_browsers(): @@ -27,16 +25,16 @@ def test_set_selenium_timeout_only_affects_open_browsers(): def test_action_chain_delay_default(): sl = SeleniumLibrary() - assert sl.action_chain_delay == 250, f"Delay should have 250" + assert sl.action_chain_delay == 250, "Delay should have 250" def test_set_action_chain_delay_default(): sl = SeleniumLibrary() sl.set_action_chain_delay("3.0") - assert sl.action_chain_delay == 3000, f"Delay should have 3000" + assert sl.action_chain_delay == 3000, "Delay should have 3000" sl.set_action_chain_delay("258 milliseconds") - assert sl.action_chain_delay == 258, f"Delay should have 258" + assert sl.action_chain_delay == 258, "Delay should have 258" def test_get_action_chain_delay_default(): @@ -59,10 +57,10 @@ def test_set_selenium_implicit_wait(): def test_selenium_implicit_wait_error(): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"Invalid time string 'False'\."): SeleniumLibrary(implicit_wait="False") sl = SeleniumLibrary(implicit_wait="3") - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"Invalid time string '1 vuosi'\."): sl.set_selenium_implicit_wait("1 vuosi") @@ -106,23 +104,20 @@ def test_get_selenium_page_load_timeout(): def test_bad_browser_name(): ctx = mock() bm = BrowserManagementKeywords(ctx) - try: + with pytest.raises(ValueError, match=r"fireox is not a supported browser\."): bm._make_driver("fireox") - raise ValueError("Exception not raised") - except ValueError as e: - assert str(e) == "fireox is not a supported browser." def test_create_webdriver(): ctx = mock() ctx.event_firing_webdriver = None bm = BrowserManagementKeywords(ctx) - FakeWebDriver = mock() + fake_webdriver = mock() driver = mock() - when(FakeWebDriver).__call__(some_arg=1).thenReturn(driver) - when(FakeWebDriver).__call__(some_arg=2).thenReturn(driver) + when(fake_webdriver).__call__(some_arg=1).thenReturn(driver) + when(fake_webdriver).__call__(some_arg=2).thenReturn(driver) when(ctx).register_driver(driver, "fake1").thenReturn(0) - webdriver.FakeWebDriver = FakeWebDriver + webdriver.FakeWebDriver = fake_webdriver try: index = bm.create_webdriver("FakeWebDriver", "fake1", some_arg=1) verify(ctx).register_driver(driver, "fake1") @@ -220,7 +215,7 @@ def test_create_webdriver_speed(): # E # E Chrome(options=None, service=) #which does seem closer .. - + #Tried: # service = Service(executable_path="chromedriver", log_path=None) # when(webdriver).Chrome( diff --git a/utest/test/keywords/test_click_modifier.py b/utest/test/keywords/test_click_modifier.py index 0b651c276..5b3e0560e 100644 --- a/utest/test/keywords/test_click_modifier.py +++ b/utest/test/keywords/test_click_modifier.py @@ -40,24 +40,19 @@ def test_parsing_multiple_modifiers(element): def test_invalid_modifier(element): - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match="'FOO' modifier "): element.parse_modifier("FOO") - assert "'FOO' modifier " in str(error.value) - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match="'FOO' modifier "): element.parse_modifier("FOO+CTRL") - assert "'FOO' modifier " in str(error.value) - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match="'FOO' modifier "): element.parse_modifier("CTRL+FOO") - assert "'FOO' modifier " in str(error.value) - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match="'CTRLFOO' modifier "): element.parse_modifier("CTRLFOO") - assert "'CTRLFOO' modifier " in str(error.value) def test_invalid_key_separator(element): - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match="'CTRL-CTRL' modifier "): element.parse_modifier("CTRL-CTRL") - assert "'CTRL-CTRL' modifier " in str(error.value) diff --git a/utest/test/keywords/test_cookie.py b/utest/test/keywords/test_cookie.py index 1fe110efa..52d42a681 100644 --- a/utest/test/keywords/test_cookie.py +++ b/utest/test/keywords/test_cookie.py @@ -6,7 +6,6 @@ from SeleniumLibrary.keywords import CookieKeywords from SeleniumLibrary.keywords.cookie import CookieInformation - ALL_ARGS = { "name": "foo", "value": "123", @@ -20,24 +19,24 @@ pytestmark = pytest.mark.usefixtures("unstub") -@pytest.fixture() +@pytest.fixture def driver(): return mock() -@pytest.fixture() +@pytest.fixture def ctx(driver): ctx = mock() ctx.driver = driver return ctx -@pytest.fixture() +@pytest.fixture def default_cookie(): return {"name": "name", "value": "value"} -@pytest.fixture() +@pytest.fixture def cookie(ctx): return CookieKeywords(ctx) diff --git a/utest/test/keywords/test_expectedconditions.py b/utest/test/keywords/test_expectedconditions.py index 3ade2e5fa..927f361ce 100644 --- a/utest/test/keywords/test_expectedconditions.py +++ b/utest/test/keywords/test_expectedconditions.py @@ -20,12 +20,12 @@ # Element\ To\ Be\ Clickable # Element${SPACE}To${SPACE}Be${SPACE}Clickable -class ExpectedConditionKeywords(unittest.TestCase): +class TestExpectedConditionKeywords(unittest.TestCase): @classmethod def setUpClass(cls): cls.ec_keywords = ExpectedConditionKeywords(None) - def WorkInProgresstest_parse_condition(self): + def workinprogresstest_parse_condition(self): results = [] results.append(self.ec_keywords._parse_condition("Element To Be Clickable")) results.append(self.ec_keywords._parse_condition("eLEment TO be ClIcKable")) diff --git a/utest/test/keywords/test_firefox_profile_parsing.py b/utest/test/keywords/test_firefox_profile_parsing.py index 3a7e895e2..37d3ce08c 100644 --- a/utest/test/keywords/test_firefox_profile_parsing.py +++ b/utest/test/keywords/test_firefox_profile_parsing.py @@ -69,8 +69,7 @@ def _get_preferences_attribute(self, result): # sig = signature(result) if hasattr(result,'default_preferences'): return result.default_preferences - elif hasattr(result,'_desired_preferences'): + if hasattr(result,'_desired_preferences'): return result._desired_preferences - else: - return None + return None # -- diff --git a/utest/test/keywords/test_input_text_file_decorator.py b/utest/test/keywords/test_input_text_file_decorator.py index 4ef4c4c87..a0ec8fa13 100644 --- a/utest/test/keywords/test_input_text_file_decorator.py +++ b/utest/test/keywords/test_input_text_file_decorator.py @@ -17,8 +17,8 @@ def tearDown(self): def test_file_decorator_not_file(self): when(self.file).choose_file().thenReturn(False) - assert self.file.is_local_file("some string") == None + assert self.file.is_local_file("some string") is None def test_file_decodator_is_file_choose_file(self): when(self.file).choose_file().thenReturn(True) - assert self.file.is_local_file("some_file") == None + assert self.file.is_local_file("some_file") is None diff --git a/utest/test/keywords/test_keyword_arguments_browsermanagement.py b/utest/test/keywords/test_keyword_arguments_browsermanagement.py index 6daffc7c2..6237a89cb 100644 --- a/utest/test/keywords/test_keyword_arguments_browsermanagement.py +++ b/utest/test/keywords/test_keyword_arguments_browsermanagement.py @@ -1,6 +1,6 @@ import unittest -from mockito import mock, unstub, when, verify, ANY +from mockito import ANY, mock, unstub, verify, when from SeleniumLibrary.keywords import BrowserManagementKeywords @@ -25,13 +25,13 @@ def test_open_browser(self): "firefox", None, None, False, None, None, None, None ).thenReturn(browser) alias = self.brorser.open_browser(url) - assert alias == None + assert alias is None when(self.brorser)._make_driver( "firefox", None, None, remote_url, None, None, None, None ).thenReturn(browser) alias = self.brorser.open_browser(url, alias="None", remote_url=remote_url) - assert alias == None + assert alias is None def test_same_alias(self): url = "https://github.com/robotframework" diff --git a/utest/test/keywords/test_keyword_arguments_element.py b/utest/test/keywords/test_keyword_arguments_element.py index c35b402ec..2894f88ad 100644 --- a/utest/test/keywords/test_keyword_arguments_element.py +++ b/utest/test/keywords/test_keyword_arguments_element.py @@ -1,10 +1,11 @@ import pytest -from mockito import mock, unstub, when, matchers -from SeleniumLibrary.keywords import ElementKeywords +from mockito import matchers, mock, unstub, when + import SeleniumLibrary.keywords.element as SUT +from SeleniumLibrary.keywords import ElementKeywords -@pytest.fixture(scope="function") +@pytest.fixture def element(): ctx = mock() ctx._browser = mock() diff --git a/utest/test/keywords/test_keyword_arguments_formelement.py b/utest/test/keywords/test_keyword_arguments_formelement.py index 193ba0dac..322ae6475 100644 --- a/utest/test/keywords/test_keyword_arguments_formelement.py +++ b/utest/test/keywords/test_keyword_arguments_formelement.py @@ -3,11 +3,10 @@ from SeleniumLibrary.keywords import FormElementKeywords - FALSES = ["False", False, "", None, "NONE"] -@pytest.fixture(scope="function") +@pytest.fixture def form(): ctx = mock() ctx.driver = mock() @@ -21,7 +20,7 @@ def teardown_function(): def test_submit_form_false(form): element = mock() when(form).find_element("tag:form", tag="form").thenReturn(element) - for false in FALSES: + for _false in FALSES: form.submit_form() form.submit_form() diff --git a/utest/test/keywords/test_keyword_arguments_selectelement.py b/utest/test/keywords/test_keyword_arguments_selectelement.py index 8e8c78999..bfe671ae0 100644 --- a/utest/test/keywords/test_keyword_arguments_selectelement.py +++ b/utest/test/keywords/test_keyword_arguments_selectelement.py @@ -2,7 +2,6 @@ from mockito import mock, unstub, when - from SeleniumLibrary.keywords import SelectElementKeywords diff --git a/utest/test/keywords/test_keyword_arguments_waiting.py b/utest/test/keywords/test_keyword_arguments_waiting.py index 9809b8314..26ccf28a5 100644 --- a/utest/test/keywords/test_keyword_arguments_waiting.py +++ b/utest/test/keywords/test_keyword_arguments_waiting.py @@ -20,7 +20,6 @@ def teardown_module(): def test_wait_for_condition(waiting): condition = 'return document.getElementById("intro")' - error = "did not become true" with pytest.raises(AssertionError) as error: waiting.wait_for_condition(condition) assert "did not become true" in str(error.value) diff --git a/utest/test/keywords/test_press_keys.py b/utest/test/keywords/test_press_keys.py index d168814dc..6150b80f8 100644 --- a/utest/test/keywords/test_press_keys.py +++ b/utest/test/keywords/test_press_keys.py @@ -1,5 +1,5 @@ -import unittest import os +import unittest from approvaltests.approvals import verify_all from approvaltests.reporters.generic_diff_reporter_factory import ( diff --git a/utest/test/keywords/test_runonfailure_from_lib.py b/utest/test/keywords/test_runonfailure_from_lib.py index 8034a4002..530820d3d 100644 --- a/utest/test/keywords/test_runonfailure_from_lib.py +++ b/utest/test/keywords/test_runonfailure_from_lib.py @@ -1,6 +1,6 @@ import unittest -from mockito import when, unstub, verify +from mockito import unstub, verify, when from SeleniumLibrary import SeleniumLibrary diff --git a/utest/test/keywords/test_screen_shot.py b/utest/test/keywords/test_screen_shot.py index 2ea09cb30..16879c6fb 100644 --- a/utest/test/keywords/test_screen_shot.py +++ b/utest/test/keywords/test_screen_shot.py @@ -1,4 +1,4 @@ -from os.path import dirname, abspath, join +from os.path import abspath, dirname, join import pytest from mockito import mock, unstub diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index b61fff029..57b7f5231 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -7,7 +7,7 @@ from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from mockito import mock, when, unstub, ANY +from mockito import ANY, mock, unstub, when from robot.utils import WINDOWS from selenium import webdriver @@ -202,7 +202,7 @@ def error_formatter(method, arg, full=False): except Exception as error: if full: return f"{arg} {error}" - return "{} {}".format(arg, error.__str__()[:15]) + return f"{arg} {error.__str__()[:15]}" @pytest.fixture(scope="module") @@ -215,8 +215,7 @@ def creator(): @pytest.fixture(scope="module") def output_dir(): curr_dir = os.path.dirname(os.path.abspath(__file__)) - output_dir = os.path.abspath(os.path.join(curr_dir, "..", "..", "output_dir")) - return output_dir + return os.path.abspath(os.path.join(curr_dir, "..", "..", "output_dir")) def test_create_chrome_with_options(creator): @@ -261,7 +260,7 @@ def test_create_headless_chrome_with_options(creator): def test_create_firefox_with_options(creator, output_dir): - log_file = os.path.join(output_dir, "geckodriver-1.log") + # log_file = os.path.join(output_dir, "geckodriver-1.log") options = mock() profile = mock() expected_webdriver = mock() @@ -297,7 +296,7 @@ def test_create_firefox_with_options_and_remote_url(creator): def test_create_headless_firefox_with_options(creator, output_dir): - log_file = os.path.join(output_dir, "geckodriver-1.log") + # log_file = os.path.join(output_dir, "geckodriver-1.log") options = mock() profile = mock() expected_webdriver = mock() @@ -386,7 +385,7 @@ def test_create_driver_chrome(creator): def test_create_driver_firefox(creator, output_dir): - log_file = os.path.join(output_dir, "geckodriver-1.log") + # log_file = os.path.join(output_dir, "geckodriver-1.log") str_options = "add_argument:--disable-dev-shm-usage" options = mock() profile = mock() diff --git a/utest/test/keywords/test_selenium_service_parser.py b/utest/test/keywords/test_selenium_service_parser.py index 095a8c2c2..1808f066e 100644 --- a/utest/test/keywords/test_selenium_service_parser.py +++ b/utest/test/keywords/test_selenium_service_parser.py @@ -7,11 +7,10 @@ from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from mockito import mock, when, unstub, ANY +from mockito import unstub from robot.utils import WINDOWS -from selenium import webdriver -from SeleniumLibrary.keywords.webdrivertools import SeleniumService, WebDriverCreator +from SeleniumLibrary.keywords.webdrivertools import SeleniumService @pytest.fixture(scope="module") @@ -138,4 +137,4 @@ def error_formatter(method, arg, full=False): except Exception as error: if full: return f"{arg} {error}" - return "{} {}".format(arg, error.__str__()[:15]) \ No newline at end of file + return f"{arg} {error.__str__()[:15]}" diff --git a/utest/test/keywords/test_tablekeywords.py b/utest/test/keywords/test_tablekeywords.py index 6e8c1cf32..87275bc31 100644 --- a/utest/test/keywords/test_tablekeywords.py +++ b/utest/test/keywords/test_tablekeywords.py @@ -1,6 +1,6 @@ import unittest -from mockito import mock, when, unstub +from mockito import mock, unstub, when from SeleniumLibrary.keywords import TableElementKeywords diff --git a/utest/test/keywords/test_waiting_stale_element_refereance_exception.py b/utest/test/keywords/test_waiting_stale_element_refereance_exception.py index 910d9cb6c..3b5b53e1a 100644 --- a/utest/test/keywords/test_waiting_stale_element_refereance_exception.py +++ b/utest/test/keywords/test_waiting_stale_element_refereance_exception.py @@ -1,6 +1,6 @@ import pytest +from mockito import mock, unstub, when from selenium.common.exceptions import StaleElementReferenceException -from mockito import mock, when, unstub from SeleniumLibrary.keywords import WaitingKeywords diff --git a/utest/test/keywords/test_webdrivercache.py b/utest/test/keywords/test_webdrivercache.py index 26e0db927..900aaefb1 100644 --- a/utest/test/keywords/test_webdrivercache.py +++ b/utest/test/keywords/test_webdrivercache.py @@ -1,6 +1,7 @@ import unittest -from mockito import mock, verify, when, unstub +import pytest +from mockito import mock, unstub, verify, when from robot.utils.connectioncache import NoConnection from selenium.common.exceptions import TimeoutException, WebDriverException @@ -13,10 +14,9 @@ def tearDown(self): def test_no_current_message(self): cache = WebDriverCache() - try: - self.assertRaises(RuntimeError, cache.current.anyMember()) - except RuntimeError as e: - assert str(e) == "No current browser" + with pytest.raises(RuntimeError) as e: + cache.current.anyMember() + assert str(e.value) == "No current browser" def test_browsers_property(self): cache = WebDriverCache() @@ -105,10 +105,10 @@ def test_resolve_alias_or_index(self): assert index == 3 index = cache.get_index(None) - assert index == None + assert index is None index = cache.get_index("None") - assert index == None + assert index is None def test_resolve_alias_or_index_with_none(self): cache = WebDriverCache() @@ -123,7 +123,7 @@ def test_resolve_alias_or_index_with_none(self): assert index == 1 index = cache.get_index(None) - assert index == None + assert index is None def test_resolve_alias_or_index_error(self): cache = WebDriverCache() @@ -132,13 +132,13 @@ def test_resolve_alias_or_index_error(self): cache.register(mock()) index = cache.get_index("bar") - assert index == None + assert index is None index = cache.get_index(12) - assert index == None + assert index is None index = cache.get_index(-1) - assert index == None + assert index is None def test_close_and_same_alias(self): cache = WebDriverCache() @@ -147,20 +147,20 @@ def test_close_and_same_alias(self): cache.register(mock(), "bar") cache.close() index = cache.get_index("bar") - assert index == None + assert index is None def test_same_alias_new_browser(self): cache = WebDriverCache() cache.close() index = cache.get_index("bar") - assert index == None + assert index is None def test_close_all_cache_first_quite_fails(self): cache = WebDriverCache() driver = mock() when(driver).quit().thenRaise(TimeoutException("timeout.")) cache.register(driver, "bar") - with self.assertRaises(TimeoutException): + with pytest.raises(TimeoutException): cache.close_all() self.verify_cache(cache) @@ -173,7 +173,7 @@ def test_close_all_cache_middle_quite_fails(self): cache.register(driver0, "bar0") cache.register(driver1, "bar1") cache.register(driver2, "bar2") - with self.assertRaises(TimeoutException): + with pytest.raises(TimeoutException): cache.close_all() self.verify_cache(cache) @@ -186,7 +186,7 @@ def test_close_all_cache_all_quite_fails(self): cache.register(driver0, "bar0") cache.register(driver1, "bar1") cache.register(driver2, "bar2") - with self.assertRaises(TimeoutException): + with pytest.raises(TimeoutException): cache.close_all() self.verify_cache(cache) @@ -199,7 +199,7 @@ def test_close_all_cache_not_selenium_error(self): cache.register(driver0, "bar0") cache.register(driver1, "bar1") cache.register(driver2, "bar2") - with self.assertRaises(TimeoutException): + with pytest.raises(TimeoutException): cache.close_all() self.verify_cache(cache) @@ -217,7 +217,7 @@ def test_close_quite_fails(self): driver = mock() when(driver).quit().thenRaise(TimeoutException("timeout.")) cache.register(driver, "bar") - with self.assertRaises(TimeoutException): + with pytest.raises(TimeoutException): cache.close() assert isinstance(cache.current, NoConnection) assert driver in cache._closed diff --git a/utest/test/keywords/test_windowmananger_window_info.py b/utest/test/keywords/test_windowmananger_window_info.py index c1a5c90ac..f58300c9a 100644 --- a/utest/test/keywords/test_windowmananger_window_info.py +++ b/utest/test/keywords/test_windowmananger_window_info.py @@ -1,9 +1,9 @@ import unittest -from mockito import mock, when, unstub +from mockito import mock, unstub, when +from selenium.common.exceptions import WebDriverException from SeleniumLibrary.locators.windowmanager import WindowManager -from selenium.common.exceptions import WebDriverException SCRIPT = "return [ window.id, window.name ];" HANDLE = "17c3dc18-0443-478b-aec6-ed7e2a5da7e1" @@ -43,10 +43,10 @@ def test_window_info_values_are_empty_strings(self): def test_window_id_is_bool(self): self.mock_window_info(True, "", "", "") info = self.manager._get_current_window_info() - assert info[1] == True + assert info[1] self.mock_window_info(False, "", "", "") info = self.manager._get_current_window_info() - assert info[1] == False + assert not info[1] def test_window_id_is_web_element(self): elem = mock() diff --git a/utest/test/locators/test_elementfinder.py b/utest/test/locators/test_elementfinder.py index 50472be20..3df31b9f9 100644 --- a/utest/test/locators/test_elementfinder.py +++ b/utest/test/locators/test_elementfinder.py @@ -3,14 +3,15 @@ import pytest from approvaltests import verify_all from approvaltests.reporters import GenericDiffReporterFactory -from mockito import any, mock, verify, when, unstub +from mockito import any as mockito_any +from mockito import mock, unstub, verify, when from selenium.webdriver.common.by import By from SeleniumLibrary.errors import ElementNotFound from SeleniumLibrary.locators.elementfinder import ElementFinder -@pytest.fixture(scope="function") +@pytest.fixture def finder(): ctx = mock() ctx.driver = mock() @@ -105,7 +106,7 @@ def _verify_parse_locator(locator, prefix, criteria, finder=None): def test_parent_is_not_webelement(finder): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=r"^Parent must be Selenium WebElement"): finder.find("//div", parent="//button") @@ -155,9 +156,8 @@ def test_find_by_dom__parent_is_webelement(finder): when(finder)._disallow_webelement_parent(webelement).thenRaise( ValueError("This method does not allow webelement as parent") ) - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match="not allow webelement as parent"): finder.find("dom=value", parent=webelement) - assert "not allow webelement as parent" in str(error.value) def test_find_by_sizzle_parent_is_webelement(finder): @@ -167,9 +167,8 @@ def test_find_by_sizzle_parent_is_webelement(finder): when(finder)._disallow_webelement_parent(webelement).thenRaise( ValueError("This method does not allow webelement as parent") ) - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match="not allow webelement as parent"): finder.find("sizzle=div.class", parent=webelement) - assert "not allow webelement as parent" in str(error.value) def test_find_by_link_text_parent_is_webelement(finder): @@ -224,9 +223,8 @@ def test_find_sc_locator_parent_is_webelement(finder): when(finder)._disallow_webelement_parent(webelement).thenRaise( ValueError("This method does not allow webelement as parent") ) - with pytest.raises(ValueError) as error: + with pytest.raises(ValueError, match="not allow webelement as parent"): finder.find("scLocator=div", parent=webelement) - assert "not allow webelement as parent" in str(error.value) def test_find_by_default_parent_is_webelement(finder): @@ -250,13 +248,13 @@ def test_non_existing_prefix(finder): def test_find_with_no_tag(finder): driver = _get_driver(finder) finder.find("test1", required=False) - verify(driver).find_elements(By.XPATH, "//*[(@id='test1' or " "@name='test1')]") + verify(driver).find_elements(By.XPATH, "//*[(@id='test1' or @name='test1')]") def test_find_with_explicit_default_strategy(finder): driver = _get_driver(finder) finder.find("default=test1", required=False) - verify(driver).find_elements(By.XPATH, "//*[(@id='test1' or " "@name='test1')]") + verify(driver).find_elements(By.XPATH, "//*[(@id='test1' or @name='test1')]") def test_find_with_explicit_default_strategy_and_equals(finder): @@ -655,7 +653,7 @@ def test_find_returns_bad_values(finder): for bad_value in (None, {"": None}): for locator_strategy in locator_strategies: when_find_func = getattr(when(driver), func_name) - when_find_func(locator_strategy, any()).thenReturn(bad_value) + when_find_func(locator_strategy, mockito_any()).thenReturn(bad_value) for locator in ( "identifier=it", "id=it", diff --git a/utest/test/locators/test_windowmanager.py b/utest/test/locators/test_windowmanager.py index b4cd0768e..3e809e5b3 100644 --- a/utest/test/locators/test_windowmanager.py +++ b/utest/test/locators/test_windowmanager.py @@ -1,6 +1,7 @@ import unittest import uuid +import pytest from mockito import mock from SeleniumLibrary.errors import WindowNotFound @@ -10,9 +11,9 @@ class WindowManagerTests(unittest.TestCase): def test_select_with_invalid_prefix(self): manager = WindowManagerWithMockBrowser() - with self.assertRaises(WindowNotFound) as context: + with pytest.raises(WindowNotFound) as context: manager.select("something=test1") - assert str(context.exception) == "No window matching handle, name, title or URL 'something=test1' found." + assert str(context.value) == "No window matching handle, name, title or URL 'something=test1' found." def test_select_by_title(self): manager = WindowManagerWithMockBrowser( @@ -46,9 +47,9 @@ def test_select_by_title_no_match(self): {"name": "win2", "title": "Title 2", "url": "http://localhost/page2.html"}, {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) - with self.assertRaises(WindowNotFound) as context: + with pytest.raises(WindowNotFound) as context: manager.select("title=Title -1") - assert str(context.exception) == "Unable to locate window with title 'Title -1'." + assert str(context.value) == "Unable to locate window with title 'Title -1'." def test_select_by_name(self): manager = WindowManagerWithMockBrowser( @@ -65,9 +66,9 @@ def test_select_by_name_no_match(self): {"name": "win2", "title": "Title 2", "url": "http://localhost/page2.html"}, {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) - with self.assertRaises(WindowNotFound) as context: + with pytest.raises(WindowNotFound) as context: manager.select("name=win-1") - assert str(context.exception) == "Unable to locate window with name 'win-1'." + assert str(context.value) == "Unable to locate window with name 'win-1'." def test_select_by_url(self): manager = WindowManagerWithMockBrowser( @@ -101,9 +102,9 @@ def test_select_by_url_no_match(self): {"name": "win2", "title": "Title 2", "url": "http://localhost/page2.html"}, {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) - with self.assertRaises(WindowNotFound) as context: + with pytest.raises(WindowNotFound) as context: manager.select("url=http://localhost/page-1.html") - assert str(context.exception) == "Unable to locate window with URL 'http://localhost/page-1.html'." + assert str(context.value) == "Unable to locate window with URL 'http://localhost/page-1.html'." def test_select_main_window(self): manager = WindowManagerWithMockBrowser( @@ -142,9 +143,9 @@ def test_select_by_default_no_match(self): {"name": "win2", "title": "Title 2", "url": "http://localhost/page2.html"}, {"name": "win3", "title": "Title 3", "url": "http://localhost/page3.html"}, ) - with self.assertRaises(WindowNotFound) as context: + with pytest.raises(WindowNotFound) as context: manager.select("foobar") - assert str(context.exception) == "No window matching handle, name, title or URL 'foobar' found." + assert str(context.value) == "No window matching handle, name, title or URL 'foobar' found." def test_prefix_is_case_sensitive(self): manager = WindowManagerWithMockBrowser( @@ -154,9 +155,9 @@ def test_prefix_is_case_sensitive(self): ) manager.select("name=win2") assert manager.driver.current_window.name == "win2" - with self.assertRaises(WindowNotFound) as context: + with pytest.raises(WindowNotFound) as context: manager.select("nAmE=win2") - assert str(context.exception) == "No window matching handle, name, title or URL 'nAmE=win2' found." + assert str(context.value) == "No window matching handle, name, title or URL 'nAmE=win2' found." def test_get_window_infos(self): manager = WindowManagerWithMockBrowser( @@ -211,6 +212,7 @@ def execute_script(script): handle_ = driver.session_id if handle_ in driver.window_handles: return window_infos[handle_][:2] + return None driver.execute_script = execute_script return driver diff --git a/utest/test/robotframework_seleniumlibrary_translation_list/__init__.py b/utest/test/robotframework_seleniumlibrary_translation_list/__init__.py index 4ccfe3f24..29129a9a9 100644 --- a/utest/test/robotframework_seleniumlibrary_translation_list/__init__.py +++ b/utest/test/robotframework_seleniumlibrary_translation_list/__init__.py @@ -12,4 +12,4 @@ def get_language() -> list: "language": "swe", "path": curr_dir / "translate2.json" } - ] \ No newline at end of file + ] diff --git a/utest/test/translation/__init__.py b/utest/test/translation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utest/test/translation/test_translation.py b/utest/test/translation/test_translation.py index 0281189dc..18df6ea8d 100644 --- a/utest/test/translation/test_translation.py +++ b/utest/test/translation/test_translation.py @@ -1,12 +1,12 @@ -from pathlib import Path import sys +from pathlib import Path import pytest from SeleniumLibrary import SeleniumLibrary -@pytest.fixture() +@pytest.fixture def sl() -> SeleniumLibrary: sys.path.append(str(Path(__file__).parent.parent.absolute())) return SeleniumLibrary(language="FI") diff --git a/utest/test/utils/test_types.py b/utest/test/utils/test_types.py index 5dfca9e00..7a093ca53 100644 --- a/utest/test/utils/test_types.py +++ b/utest/test/utils/test_types.py @@ -6,5 +6,5 @@ def test_is_noney(): for item in [None, "None", "NONE", "none"]: assert is_noney(item) - for item in TRUTHY + [False, 0, "False", "", [], {}, ()]: + for item in [*TRUTHY, False, 0, "False", "", [], {}, ()]: assert is_noney(item) is False From b68eb404e18b47d2ca73eb3de05cca438bb7d517 Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 10:54:17 +0200 Subject: [PATCH 05/13] Update linting configuration and remove unnecessary skip conditions for Windows --- .gitattibutes | 1 + .../PluginDocumentation.test_many_plugins.approved.txt | 8 ++++---- utest/test/api/test_filepath_unusual_characters.py | 1 - utest/test/api/test_plugin_documentation.py | 3 --- utest/test/keywords/test_firefox_profile_parsing.py | 1 - utest/test/keywords/test_javascript.py | 6 ------ utest/test/keywords/test_press_keys.py | 4 ---- utest/test/keywords/test_selenium_options_parser.py | 10 ---------- utest/test/keywords/test_selenium_service_parser.py | 9 --------- utest/test/utils/test_xpath_escape.py | 1 - 10 files changed, 5 insertions(+), 39 deletions(-) create mode 100644 .gitattibutes diff --git a/.gitattibutes b/.gitattibutes new file mode 100644 index 000000000..f7d2b9c6a --- /dev/null +++ b/.gitattibutes @@ -0,0 +1 @@ +*.approved.txt text eol=lf \ No newline at end of file diff --git a/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt b/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt index ce7ebb397..ec9e23125 100644 --- a/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt +++ b/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt @@ -528,18 +528,18 @@ Example project for translation can be found from [https://github.com/MarketSquare/robotframework-seleniumlibrary-translation-fi | robotframework-seleniumlibrary-translation-fi] repository. -= Plugin: my_lib = += Plugin: MyLib = Some dummy documentation. -= my_lib Heading 1 = += MyLib Heading 1 = This is heading 1 documentation. -== my_lib Heading 2 == +== MyLib Heading 2 == This is heading 2 documentation. -= Plugin: my_lib_args = += Plugin: MyLibArgs = No plugin documentation found. diff --git a/utest/test/api/test_filepath_unusual_characters.py b/utest/test/api/test_filepath_unusual_characters.py index fdfc5fa9d..0435996c4 100644 --- a/utest/test/api/test_filepath_unusual_characters.py +++ b/utest/test/api/test_filepath_unusual_characters.py @@ -21,7 +21,6 @@ def reporter(): return factory.get_first_working() -@pytest.mark.skipif(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_normal_file_path(reporter): results = [] results.append(_format_path("/foo/file.log", 1)) diff --git a/utest/test/api/test_plugin_documentation.py b/utest/test/api/test_plugin_documentation.py index b03a18fe4..c411915fd 100644 --- a/utest/test/api/test_plugin_documentation.py +++ b/utest/test/api/test_plugin_documentation.py @@ -30,19 +30,16 @@ def setUp(self): factory.get_first_working(), PythonNativeReporter() ) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_many_plugins(self): sl = SeleniumLibrary( plugins=f"{self.plugin_1}, {self.plugin_3};arg1=Text1;arg2=Text2" ) verify(sl.get_keyword_documentation("__intro__"), self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_parse_plugin_init_doc(self): sl = SeleniumLibrary(plugins=f"{self.plugin_3};arg1=Text1;arg2=Text2") verify(sl.get_keyword_documentation("__init__"), self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_parse_plugin_kw_doc(self): sl = SeleniumLibrary(plugins=f"{self.plugin_3};arg1=Text1;arg2=Text2") verify(sl.get_keyword_documentation("execute_javascript"), self.reporter) diff --git a/utest/test/keywords/test_firefox_profile_parsing.py b/utest/test/keywords/test_firefox_profile_parsing.py index 37d3ce08c..f4e7d5541 100644 --- a/utest/test/keywords/test_firefox_profile_parsing.py +++ b/utest/test/keywords/test_firefox_profile_parsing.py @@ -27,7 +27,6 @@ def setUpClass(cls): def setUp(self): self.results = [] - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_single_method(self): self._parse_result( self.creator._get_ff_profile('set_preference("key1", "arg1")') diff --git a/utest/test/keywords/test_javascript.py b/utest/test/keywords/test_javascript.py index 1b4b469c5..9acbcd1db 100644 --- a/utest/test/keywords/test_javascript.py +++ b/utest/test/keywords/test_javascript.py @@ -12,7 +12,6 @@ class JavaScriptKeywordsTest(unittest.TestCase): @classmethod - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def setUpClass(cls): cls.code_examples = [ (), @@ -40,13 +39,11 @@ def setUpClass(cls): factory.load(reporter_json) cls.reporter = factory.get_first_working() - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_get_javascript(self): code, args = self.js._get_javascript_to_execute(("code", "here")) result = f"{code} + {args}" verify(result, self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_get_javascript_no_code(self): code = ("ARGUMENTS", "arg1", "arg1") try: @@ -55,21 +52,18 @@ def test_get_javascript_no_code(self): result = str(error) verify(result, self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_separate_code_and_args(self): all_results = [] for code in self.code_examples: all_results.append(self.js_reporter(code)) verify_all("code and args", all_results, reporter=self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_indexing(self): all_results = [] for code in self.code_examples: all_results.append(self.js._get_marker_index(code)) verify_all("index", all_results, reporter=self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_check_marker_error(self): examples = [ (), diff --git a/utest/test/keywords/test_press_keys.py b/utest/test/keywords/test_press_keys.py index 6150b80f8..d80fd7189 100644 --- a/utest/test/keywords/test_press_keys.py +++ b/utest/test/keywords/test_press_keys.py @@ -24,7 +24,6 @@ def setUp(self): factory.load(reporter_json) self.reporter = factory.get_first_working() - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_parse_keys(self): results = [] results.append(self.element_keywords._parse_keys("A", "B", "C")) @@ -41,7 +40,6 @@ def test_parse_keys(self): results.append(self.element_keywords._parse_keys("IS", "ALT", "HERE")) verify_all("index", results, reporter=self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_parse_keys_aliases(self): results = [] results.append(self.element_keywords._parse_aliases("CTRL")) @@ -51,7 +49,6 @@ def test_parse_keys_aliases(self): results.append(self.element_keywords._parse_aliases("END")) verify_all("Alias testing", results, reporter=self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_separate_key(self): results = [] results.append(self.element_keywords._separate_key("BB")) @@ -65,7 +62,6 @@ def test_separate_key(self): results.append(self.element_keywords._separate_key("+++")) verify_all("Separate key", results, reporter=self.reporter) - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_convert_key(self): results = [] results.append(self.element_keywords._convert_special_keys(["B"])) diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 57b7f5231..950da63c3 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -33,7 +33,6 @@ def teardown_function(): unstub() -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_parse_options_string(options, reporter): results = [] results.append(options._parse('method("arg1")')) @@ -72,7 +71,6 @@ def test_parse_options_string(options, reporter): verify_all("Selenium options string to dict", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_index_of_separator(options, reporter): results = [] results.append(options._get_arument_index('method({"key": "value"})')) @@ -82,7 +80,6 @@ def test_index_of_separator(options, reporter): verify_all("Get argument index", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_parse_complex_object(options, reporter): results = [] results.append(options._parse_to_tokens('method({"key": "value"})')) @@ -92,7 +89,6 @@ def test_parse_complex_object(options, reporter): verify_all("Parse complex Python object", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_parse_arguemnts(options, reporter): results = [] results.append(options._parse_arguments(("arg1",), True)) @@ -103,7 +99,6 @@ def test_parse_arguemnts(options, reporter): verify_all("Parse arguments from complex object", results, reporter=reporter) -@pytest.mark.skipif(WINDOWS, reason="ApprovalTest do not support different line feeds") @pytest.mark.skipif(sys.version_info > (3, 11), reason="Errors change with Python 3.12") def test_parse_options_string_errors(options, reporter): results = [] @@ -116,7 +111,6 @@ def test_parse_options_string_errors(options, reporter): verify_all("Selenium options string errors", results, reporter=reporter) -@pytest.mark.skipif(WINDOWS, reason="ApprovalTest do not support different line feeds") @pytest.mark.skipif(sys.version_info < (3, 12), reason="Errors change with Python 3.12") def test_parse_options_string_errors_py3_12(options, reporter): results = [] @@ -129,7 +123,6 @@ def test_parse_options_string_errors_py3_12(options, reporter): verify_all("Selenium options string errors", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_split_options(options, reporter): results = [] results.append(options._split('method("arg1");method("arg2")')) @@ -143,7 +136,6 @@ def test_split_options(options, reporter): verify_all("Selenium options string splitting", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_options_create(options, reporter): results = [] options_str = 'add_argument("--disable-dev-shm-usage")' @@ -175,7 +167,6 @@ def test_options_create(options, reporter): verify_all("Selenium options", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_get_options(options, reporter): options_str = 'add_argument("--proxy-server=66.97.38.58:80")' sel_options = options.create("chrome", options_str) @@ -183,7 +174,6 @@ def test_get_options(options, reporter): verify_all("Selenium options with string.", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_importer(options, reporter): results = [] results.append(options._import_options("firefox")) diff --git a/utest/test/keywords/test_selenium_service_parser.py b/utest/test/keywords/test_selenium_service_parser.py index 1808f066e..8c287411a 100644 --- a/utest/test/keywords/test_selenium_service_parser.py +++ b/utest/test/keywords/test_selenium_service_parser.py @@ -32,7 +32,6 @@ def teardown_function(): unstub() -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_parse_service_string(service, reporter): results = [] results.append(service._parse('attribute="arg1"')) @@ -53,9 +52,6 @@ def test_parse_service_string(service, reporter): verify_all("Selenium service string to dict", results, reporter=reporter) -# @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") -# @unittest.skipIf(sys.version_info > (3, 11), reason="Errors change with Python 3.12") -@pytest.mark.skipif(WINDOWS, reason="ApprovalTest do not support different line feeds") @pytest.mark.skipif(sys.version_info > (3, 11), reason="Errors change with Python 3.12") def test_parse_service_string_errors(service, reporter): results = [] @@ -68,7 +64,6 @@ def test_parse_service_string_errors(service, reporter): verify_all("Selenium service string errors", results, reporter=reporter) -@pytest.mark.skipif(WINDOWS, reason="ApprovalTest do not support different line feeds") @pytest.mark.skipif(sys.version_info < (3, 12), reason="Errors change with Python 3.12") def test_parse_service_string_errors_py3_12(service, reporter): results = [] @@ -81,7 +76,6 @@ def test_parse_service_string_errors_py3_12(service, reporter): verify_all("Selenium service string errors", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_split_service(service, reporter): results = [] results.append(service._split("attribute='arg1'", ';')) @@ -91,7 +85,6 @@ def test_split_service(service, reporter): verify_all("Selenium service string splitting", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_split_attribute(service, reporter): results = [] results.append(service._split("attribute='arg1'", '=')) @@ -100,7 +93,6 @@ def test_split_attribute(service, reporter): verify_all("Selenium service attribute string splitting", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_service_create(service, reporter): results = [] service_str = "service_args=['--log-level=DEBUG']" @@ -118,7 +110,6 @@ def test_service_create(service, reporter): verify_all("Selenium service", results, reporter=reporter) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_importer(service, reporter): results = [] results.append(service._import_service("firefox")) diff --git a/utest/test/utils/test_xpath_escape.py b/utest/test/utils/test_xpath_escape.py index 5f1defae7..9ff51280b 100644 --- a/utest/test/utils/test_xpath_escape.py +++ b/utest/test/utils/test_xpath_escape.py @@ -21,7 +21,6 @@ def reporter(): return factory.get_first_working() -@pytest.mark.skipif(WINDOWS, reason="ApprovalTest do not support different line feeds") def test_string(reporter): results = [] results.append(escape_xpath_value("tidii")) From d49a0c33bbd9cef19719401658606323ed3f274e Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 10:58:57 +0200 Subject: [PATCH 06/13] Add new test configuration for Firefox --- .github/workflows/Select.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Select.yml b/.github/workflows/Select.yml index dcb89c7e4..c3d7b8472 100644 --- a/.github/workflows/Select.yml +++ b/.github/workflows/Select.yml @@ -24,7 +24,11 @@ jobs: rf-version: 6.1.1 selenium-version: 4.37.0 browser: chrome - + - description: latest + python-version: 3.13.10 + rf-version: 7.4.1 + selenium-version: 4.39.0 + browser: firefox steps: - uses: actions/checkout@v4 - name: Configuration Description From e234c341d74bc882871c10466abaa97d90f2ee24 Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 11:00:27 +0200 Subject: [PATCH 07/13] Fix variable interpolation in artifact name for better clarity --- .github/workflows/Select.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Select.yml b/.github/workflows/Select.yml index c3d7b8472..d9c2b86cd 100644 --- a/.github/workflows/Select.yml +++ b/.github/workflows/Select.yml @@ -78,6 +78,6 @@ jobs: - uses: actions/upload-artifact@v4 if: failure() with: - name: sl_$${{ matrix.config.python-version }}_$${{ matrix.config.rf-version }}_$${{ matrix.config.selenium-version }}_$${{ matrix.config.browser }} + name: sl_${{ matrix.config.python-version }}_${{ matrix.config.rf-version }}_${{ matrix.config.selenium-version }}_${{ matrix.config.browser }} path: atest/zip_results overwrite: true \ No newline at end of file From b33a8f1c165ad0aed869754042d0a4597a741abd Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 11:09:31 +0200 Subject: [PATCH 08/13] Update linting and formatting workflows with new actions versions --- .github/workflows/LintFormatCheck.yml | 28 +++++++++++++++++++++++++++ .github/workflows/Select.yml | 6 +++--- 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/LintFormatCheck.yml diff --git a/.github/workflows/LintFormatCheck.yml b/.github/workflows/LintFormatCheck.yml new file mode 100644 index 000000000..3b49d92d8 --- /dev/null +++ b/.github/workflows/LintFormatCheck.yml @@ -0,0 +1,28 @@ +name: Lint and Format Check + +on: [push, pull_request] + +jobs: + ruff: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dev dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Ruff format check + run: | + python -m ruff format --check src/ utest/ + + - name: Ruff lint + run: | + python -m invoke lint \ No newline at end of file diff --git a/.github/workflows/Select.yml b/.github/workflows/Select.yml index d9c2b86cd..135570e51 100644 --- a/.github/workflows/Select.yml +++ b/.github/workflows/Select.yml @@ -30,17 +30,17 @@ jobs: selenium-version: 4.39.0 browser: firefox steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Configuration Description run: | echo "${{ matrix.config.description }} configuration" echo "Testing with RF v${{ matrix.config.rf-version }}, Selenium v${{ matrix.config.selenium-version}}, Python v${{ matrix.config.python-version }} under ${{ matrix.config.browser }}" - name: Set up Python ${{ matrix.config.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.config.python-version }} - name: Setup ${{ matrix.config.browser }} browser - uses: browser-actions/setup-chrome@v1 + uses: browser-actions/setup-chrome@v2 with: chrome-version: latest install-dependencies: true From b8a0d3e717064d309b632a6f3c7b2a0b54d46476 Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 11:19:48 +0200 Subject: [PATCH 09/13] Enhance browser opening logic to handle different browser names and prevent password manager leak detection in Chrome --- atest/acceptance/keywords/textfields.robot | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/atest/acceptance/keywords/textfields.robot b/atest/acceptance/keywords/textfields.robot index 6c3a41a3e..b8f225e1a 100644 --- a/atest/acceptance/keywords/textfields.robot +++ b/atest/acceptance/keywords/textfields.robot @@ -81,5 +81,10 @@ Attempt Clear Element Text On Non-Editable Field Open Browser To Start Page Disabling Chrome Leaked Password Detection [Arguments] ${alias}=${None} - Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} - ... options=add_experimental_option("prefs", {"profile.password_manager_leak_detection": False}) alias=${alias} \ No newline at end of file + ${browser}= Evaluate "${BROWSER}".replace(" ", "").lower() + IF "${browser}" in ["chrome", "googlechrome", "gc", "headlesschrome"] + Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} + ... options=add_experimental_option("prefs", {"profile.password_manager_leak_detection": False}) alias=${alias} + ELSE + Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL} alias=${alias} + END \ No newline at end of file From 984d2a3f5f4cc63612816bfbdf0b00215af46df4 Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 11:26:15 +0200 Subject: [PATCH 10/13] ruff format fixes --- .../acceptance/1-plugin/OpenBrowserExample.py | 7 +- atest/resources/testlibs/get_driver_path.py | 5 +- atest/run.py | 11 ++- src/SeleniumLibrary/keywords/alert.py | 8 ++- .../keywords/browsermanagement.py | 25 +++++-- src/SeleniumLibrary/keywords/element.py | 66 +++++++++--------- .../keywords/expectedconditions.py | 15 ++-- src/SeleniumLibrary/keywords/formelement.py | 11 +-- src/SeleniumLibrary/keywords/javascript.py | 1 - src/SeleniumLibrary/keywords/runonfailure.py | 6 +- src/SeleniumLibrary/keywords/screenshot.py | 39 ++++++----- src/SeleniumLibrary/keywords/selectelement.py | 20 ++---- .../keywords/webdrivertools/webdrivertools.py | 69 ++++++++++++------- src/SeleniumLibrary/keywords/window.py | 2 +- src/SeleniumLibrary/locators/windowmanager.py | 4 +- src/SeleniumLibrary/utils/__init__.py | 2 +- src/SeleniumLibrary/utils/types.py | 8 ++- utest/test/api/my_lib.py | 1 + utest/test/api/my_lib_args.py | 1 + utest/test/api/my_lib_not_inherit.py | 1 + utest/test/api/plugin_tester.py | 1 + .../api/plugin_with_event_firing_webdriver.py | 2 +- utest/test/api/test_event_firing_webdriver.py | 9 ++- .../api/test_filepath_unusual_characters.py | 1 - utest/test/api/test_plugin_documentation.py | 1 - utest/test/api/test_plugins.py | 2 + .../keywords/IGNOREDtest_webdrivercreator.py | 6 +- ...REtest_webdrivercreator_executable_path.py | 11 +-- ...Etest_webdrivercreator_service_log_path.py | 22 ++++-- utest/test/keywords/test_browsermanagement.py | 53 +++++++------- .../test/keywords/test_expectedconditions.py | 1 + .../keywords/test_firefox_profile_parsing.py | 5 +- utest/test/keywords/test_javascript.py | 1 - .../test_keyword_arguments_element.py | 8 +-- utest/test/keywords/test_press_keys.py | 1 - utest/test/keywords/test_screen_shot.py | 1 + .../keywords/test_selenium_options_parser.py | 28 ++++---- .../keywords/test_selenium_service_parser.py | 37 +++++----- utest/test/locators/test_elementfinder.py | 5 +- utest/test/locators/test_windowmanager.py | 44 +++++++++--- .../__init__.py | 5 +- .../__init__.py | 10 +-- utest/test/utils/test_package.py | 5 +- utest/test/utils/test_xpath_escape.py | 1 - 44 files changed, 328 insertions(+), 234 deletions(-) diff --git a/atest/acceptance/1-plugin/OpenBrowserExample.py b/atest/acceptance/1-plugin/OpenBrowserExample.py index 2cb006f24..ab04db3bf 100644 --- a/atest/acceptance/1-plugin/OpenBrowserExample.py +++ b/atest/acceptance/1-plugin/OpenBrowserExample.py @@ -121,7 +121,12 @@ def create_driver( ) def create_seleniumwire( - self, desired_capabilities, remote_url, options=None, service_log_path=None, service=None, + self, + desired_capabilities, + remote_url, + options=None, + service_log_path=None, + service=None, ): logger.info(self.extra_dictionary) return webdriver.Chrome() diff --git a/atest/resources/testlibs/get_driver_path.py b/atest/resources/testlibs/get_driver_path.py index 95c11e924..19d838e0e 100644 --- a/atest/resources/testlibs/get_driver_path.py +++ b/atest/resources/testlibs/get_driver_path.py @@ -18,6 +18,7 @@ def _import_options(self, browser): return options.Options """ + from selenium import webdriver from selenium.webdriver.common import driver_finder import importlib @@ -30,12 +31,12 @@ def get_driver_path(browser): options = importlib.import_module(f"selenium.webdriver.{browser}.options") args = inspect.signature(driver_finder.DriverFinder.__init__).parameters.keys() - if ('service' in args) and ('options' in args): + if ("service" in args) and ("options" in args): # Selenium V4.20.0 or greater finder = driver_finder.DriverFinder(service.Service(), options.Options()) return finder.get_driver_path() else: # Selenium v4.19.0 and prior finder = driver_finder.DriverFinder() - func = getattr(finder, 'get_path') + func = getattr(finder, "get_path") return finder.get_path(service.Service(), options.Options()) diff --git a/atest/run.py b/atest/run.py index d386ea7db..351172ad0 100755 --- a/atest/run.py +++ b/atest/run.py @@ -181,7 +181,7 @@ def _grid_status(status=False, role="hub"): @contextmanager -def http_server(interpreter, port:int): +def http_server(interpreter, port: int): serverlog = open(os.path.join(RESULTS_DIR, "serverlog.txt"), "w") interpreter = "python" if not interpreter else interpreter process = subprocess.Popen( @@ -215,7 +215,12 @@ def execute_tests(interpreter, browser, rf_options, grid, event_firing, port): options.extend([opt.format(browser=browser) for opt in ROBOT_OPTIONS]) if rf_options: options += rf_options - options += ["--exclude", f"known issue {browser.replace('headless', '')}", "--exclude", "triage"] + options += [ + "--exclude", + f"known issue {browser.replace('headless', '')}", + "--exclude", + "triage", + ] command = runner if grid: command += [ @@ -259,7 +264,7 @@ def process_output(browser): return exit.code -def create_zip(browser = None): +def create_zip(browser=None): if os.path.exists(ZIP_DIR): shutil.rmtree(ZIP_DIR) os.mkdir(ZIP_DIR) diff --git a/src/SeleniumLibrary/keywords/alert.py b/src/SeleniumLibrary/keywords/alert.py index c201ae673..9484360e1 100644 --- a/src/SeleniumLibrary/keywords/alert.py +++ b/src/SeleniumLibrary/keywords/alert.py @@ -146,6 +146,10 @@ def _wait_alert(self, timeout=None): try: return wait.until(EC.alert_is_present()) except TimeoutException as original_exception: - raise AssertionError(f"Alert not found in {secs_to_timestr(timeout)}.") from original_exception + raise AssertionError( + f"Alert not found in {secs_to_timestr(timeout)}." + ) from original_exception except WebDriverException as err: - raise AssertionError(f"An exception occurred waiting for alert: {err}") from err + raise AssertionError( + f"An exception occurred waiting for alert: {err}" + ) from err diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index c2038f367..102695317 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -217,11 +217,17 @@ def open_browser( self.go_to(url) return index if desired_capabilities: - self.warn("desired_capabilities has been deprecated and removed. Please use options to configure browsers as per documentation.") + self.warn( + "desired_capabilities has been deprecated and removed. Please use options to configure browsers as per documentation." + ) if service_log_path: - self.warn("service_log_path is being deprecated. Please use service to configure log_output or equivalent service attribute.") + self.warn( + "service_log_path is being deprecated. Please use service to configure log_output or equivalent service attribute." + ) if executable_path: - self.warn("executable_path is being deprecated. Please use service to configure the driver's executable_path as per documentation.") + self.warn( + "executable_path is being deprecated. Please use service to configure the driver's executable_path as per documentation." + ) return self._make_new_browser( url, browser, @@ -280,7 +286,11 @@ def _make_new_browser( @keyword def create_webdriver( - self, driver_name: str, alias: str | None = None, kwargs: dict | None = None, **init_kwargs + self, + driver_name: str, + alias: str | None = None, + kwargs: dict | None = None, + **init_kwargs, ) -> str: """Creates an instance of Selenium WebDriver. @@ -320,7 +330,9 @@ def create_webdriver( try: creation_func = getattr(webdriver, driver_name) except AttributeError as original_exception: - raise RuntimeError(f"'{driver_name}' is not a valid WebDriver name.") from original_exception + raise RuntimeError( + f"'{driver_name}' is not a valid WebDriver name." + ) from original_exception self.info(f"Creating an instance of the {driver_name} WebDriver.") driver = creation_func(**init_kwargs) self.debug( @@ -659,8 +671,7 @@ def set_action_chain_delay(self, value: timedelta) -> str: @keyword def get_action_chain_delay(self): - """Gets the currently stored value for chain_delay_value in timestr format. - """ + """Gets the currently stored value for chain_delay_value in timestr format.""" return timestr_to_secs(f"{self.ctx.action_chain_delay} milliseconds") @keyword diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index d53a1be8b..c74bb1d5a 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -224,7 +224,7 @@ def page_should_not_contain_element( self.assert_page_not_contains(locator, message=message, loglevel=loglevel) @keyword - def assign_id_to_element(self, locator: Locator, id: str): # noqa: A002 + def assign_id_to_element(self, locator: Locator, id: str): # noqa: A002 """Assigns a temporary ``id`` to the element specified by ``locator``. This is mainly useful if the locator is complicated and/or slow XPath @@ -286,9 +286,7 @@ def element_should_be_focused(self, locator: Locator): raise AssertionError(f"Element '{locator}' does not have focus.") @keyword - def element_should_be_visible( - self, locator: Locator, message: str | None = None - ): + def element_should_be_visible(self, locator: Locator, message: str | None = None): """Verifies that the element identified by ``locator`` is visible. Herein, visible means that the element is logically visible, not @@ -398,9 +396,7 @@ def element_text_should_not_be( raise AssertionError(message) @keyword - def get_element_attribute( - self, locator: Locator, attribute: str - ) -> str: + def get_element_attribute(self, locator: Locator, attribute: str) -> str: """Returns the value of ``attribute`` from the element ``locator``. See the `Locating elements` section for details about the locator @@ -416,9 +412,7 @@ def get_element_attribute( return self.find_element(locator).get_attribute(attribute) @keyword - def get_dom_attribute( - self, locator: Locator, attribute: str - ) -> str: + def get_dom_attribute(self, locator: Locator, attribute: str) -> str: """Returns the value of ``attribute`` from the element ``locator``. `Get DOM Attribute` keyword only returns attributes declared within the element's HTML markup. If the requested attribute is not there, the keyword returns ${None}. @@ -434,7 +428,9 @@ def get_dom_attribute( @keyword def get_property( - self, locator: Locator, property: str # noqa: A002 + self, + locator: Locator, + property: str, # noqa: A002 ) -> str: """Returns the value of ``property`` from the element ``locator``. @@ -581,9 +577,7 @@ def get_vertical_position(self, locator: Locator) -> int: return self.find_element(locator).location["y"] @keyword - def click_button( - self, locator: Locator, modifier: bool | str = False - ): + def click_button(self, locator: Locator, modifier: bool | str = False): """Clicks the button identified by ``locator``. See the `Locating elements` section for details about the locator @@ -605,9 +599,7 @@ def click_button( self._click_with_modifier(locator, ["button", "input"], modifier) @keyword - def click_image( - self, locator: Locator, modifier: bool | str = False - ): + def click_image(self, locator: Locator, modifier: bool | str = False): """Clicks an image identified by ``locator``. See the `Locating elements` section for details about the locator @@ -630,9 +622,7 @@ def click_image( self._click_with_modifier(locator, ["image", "input"], modifier) @keyword - def click_link( - self, locator: Locator, modifier: bool | str = False - ): + def click_link(self, locator: Locator, modifier: bool | str = False): """Clicks a link identified by ``locator``. See the `Locating elements` section for details about the locator @@ -774,12 +764,12 @@ def scroll_element_into_view(self, locator: Locator): New in SeleniumLibrary 3.2.0 """ element = self.find_element(locator) - ActionChains(self.driver, duration=self.ctx.action_chain_delay).move_to_element(element).perform() + ActionChains(self.driver, duration=self.ctx.action_chain_delay).move_to_element( + element + ).perform() @keyword - def drag_and_drop( - self, locator: Locator, target: Locator - ): + def drag_and_drop(self, locator: Locator, target: Locator): """Drags the element identified by ``locator`` into the ``target`` element. The ``locator`` argument is the locator of the dragged element @@ -795,9 +785,7 @@ def drag_and_drop( action.drag_and_drop(element, target).perform() @keyword - def drag_and_drop_by_offset( - self, locator: Locator, xoffset: int, yoffset: int - ): + def drag_and_drop_by_offset(self, locator: Locator, xoffset: int, yoffset: int): """Drags the element identified with ``locator`` by ``xoffset/yoffset``. See the `Locating elements` section for details about the locator @@ -869,7 +857,9 @@ def mouse_up(self, locator: Locator): """ self.info(f"Simulating Mouse Up on element '{locator}'.") element = self.find_element(locator) - ActionChains(self.driver, duration=self.ctx.action_chain_delay).release(element).perform() + ActionChains(self.driver, duration=self.ctx.action_chain_delay).release( + element + ).perform() @keyword def open_context_menu(self, locator: Locator): @@ -988,7 +978,9 @@ def press_keys(self, locator: Locator | None = None, *keys: str): if not is_noney(locator): self.info(f"Sending key(s) {keys} to {locator} element.") element = self.find_element(locator) - ActionChains(self.driver, duration=self.ctx.action_chain_delay).click(element).perform() + ActionChains(self.driver, duration=self.ctx.action_chain_delay).click( + element + ).perform() else: self.info(f"Sending key(s) {keys} to page.") element = None @@ -1228,7 +1220,9 @@ def parse_modifier(self, modifier): if hasattr(Keys, modifier): keys.append(getattr(Keys, modifier)) else: - raise ValueError(f"'{modifier}' modifier does not match to Selenium Keys") + raise ValueError( + f"'{modifier}' modifier does not match to Selenium Keys" + ) return keys def _parse_keys(self, *keys): @@ -1271,18 +1265,20 @@ def _convert_special_keys(self, keys): for key in keys: resolved_key = self._parse_aliases(key) if self._selenium_keys_has_attr(resolved_key): - converted_keys.append(self.KeysRecord(getattr(Keys, resolved_key), resolved_key, True)) + converted_keys.append( + self.KeysRecord(getattr(Keys, resolved_key), resolved_key, True) + ) else: - converted_keys.append(self.KeysRecord(resolved_key, resolved_key, False)) + converted_keys.append( + self.KeysRecord(resolved_key, resolved_key, False) + ) return converted_keys def _selenium_keys_has_attr(self, key): return hasattr(Keys, key) @keyword("Get CSS Property Value") - def get_css_property_value( - self, locator: Locator, css_property: str - ) -> str: + def get_css_property_value(self, locator: Locator, css_property: str) -> str: """Returns the computed value of ``css_property`` from the element ``locator``. See the `Locating elements` section for details about the locator syntax. diff --git a/src/SeleniumLibrary/keywords/expectedconditions.py b/src/SeleniumLibrary/keywords/expectedconditions.py index 351a0c4f1..016d239a4 100644 --- a/src/SeleniumLibrary/keywords/expectedconditions.py +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -22,7 +22,9 @@ class ExpectedConditionKeywords(LibraryComponent): @keyword - def wait_for_expected_condition(self, condition: string, *args, timeout: float | None=10): + def wait_for_expected_condition( + self, condition: string, *args, timeout: float | None = 10 + ): """Waits until ``condition`` is true or ``timeout`` expires. The condition must be one of selenium's expected condition which @@ -54,8 +56,13 @@ def wait_for_expected_condition(self, condition: string, *args, timeout: float | try: condition_func = getattr(EC, condition) except AttributeError as original_exception: - raise UnkownExpectedCondition(f"{condition} is an unknown expected condition") from original_exception - return wait.until(condition_func(*args), message=f"Expected Condition not met within set timeout of {timeout}s") + raise UnkownExpectedCondition( + f"{condition} is an unknown expected condition" + ) from original_exception + return wait.until( + condition_func(*args), + message=f"Expected Condition not met within set timeout of {timeout}s", + ) def _parse_condition(self, condition: string): - return condition.replace(' ','_').lower() + return condition.replace(" ", "_").lower() diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index a0d521fbd..cbf774190 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -238,9 +238,7 @@ def choose_file(self, locator: Locator, file_path: str): self.ctx._running_keyword = None @keyword - def input_password( - self, locator: Locator, password: str, clear: bool = True - ): + def input_password(self, locator: Locator, password: str, clear: bool = True): """Types the given password into the text field identified by ``locator``. See the `Locating elements` section for details about the locator @@ -268,9 +266,7 @@ def input_password( self._input_text_into_text_field(locator, password, clear, disable_log=True) @keyword - def input_text( - self, locator: Locator, text: str, clear: bool = True - ): + def input_text(self, locator: Locator, text: str, clear: bool = True): """Types the given ``text`` into the text field identified by ``locator``. When ``clear`` is true, the input element is cleared before @@ -489,8 +485,7 @@ def _get_radio_button_with_value(self, group_name, value): return self.find_element(xpath) except ElementNotFound as original_exception: raise ElementNotFound( - f"No radio button with name '{group_name}' " - f"and value '{value}' found." + f"No radio button with name '{group_name}' and value '{value}' found." ) from original_exception def _get_value_from_radio_buttons(self, elements): diff --git a/src/SeleniumLibrary/keywords/javascript.py b/src/SeleniumLibrary/keywords/javascript.py index 80b71ca48..7166ad573 100644 --- a/src/SeleniumLibrary/keywords/javascript.py +++ b/src/SeleniumLibrary/keywords/javascript.py @@ -23,7 +23,6 @@ class JavaScriptKeywords(LibraryComponent): - js_marker = "JAVASCRIPT" arg_marker = "ARGUMENTS" diff --git a/src/SeleniumLibrary/keywords/runonfailure.py b/src/SeleniumLibrary/keywords/runonfailure.py index 73595ac07..98f454578 100644 --- a/src/SeleniumLibrary/keywords/runonfailure.py +++ b/src/SeleniumLibrary/keywords/runonfailure.py @@ -64,9 +64,7 @@ def resolve_keyword(name): if name is None: return None if ( - (isinstance(name, str) - and name.upper() == "NOTHING") - or name.upper() == "NONE" - ): + isinstance(name, str) and name.upper() == "NOTHING" + ) or name.upper() == "NONE": return None return name diff --git a/src/SeleniumLibrary/keywords/screenshot.py b/src/SeleniumLibrary/keywords/screenshot.py index c3daabde8..4a4c7da0e 100644 --- a/src/SeleniumLibrary/keywords/screenshot.py +++ b/src/SeleniumLibrary/keywords/screenshot.py @@ -264,22 +264,23 @@ def _embed_to_log_as_file(self, path, width): ) @keyword - def print_page_as_pdf(self, # noqa: C901, PLR0912 - filename: str = DEFAULT_FILENAME_PDF, - background: bool | None = None, - margin_bottom: float | None = None, - margin_left: float | None = None, - margin_right: float | None = None, - margin_top: float | None = None, - orientation: Orientation | None = None, - page_height: float | None = None, - page_ranges: list | None = None, - page_width: float | None = None, - scale: float | None = None, - shrink_to_fit: bool | None = None, - # path_to_file=None, - ): - """ Print the current page as a PDF + def print_page_as_pdf( # noqa : PLR0912 C901 + self, + filename: str = DEFAULT_FILENAME_PDF, + background: bool | None = None, + margin_bottom: float | None = None, + margin_left: float | None = None, + margin_right: float | None = None, + margin_top: float | None = None, + orientation: Orientation | None = None, + page_height: float | None = None, + page_ranges: list | None = None, + page_width: float | None = None, + scale: float | None = None, + shrink_to_fit: bool | None = None, + # path_to_file=None, + ): + """Print the current page as a PDF ``page_ranges`` defaults to `['-']` or "all" pages. ``page_ranges`` takes a list of strings indicating the ranges. @@ -303,11 +304,11 @@ def print_page_as_pdf(self, # noqa: C901, PLR0912 """ if page_ranges is None: - page_ranges = ['-'] + page_ranges = ["-"] print_options = PrintOptions() if background is not None: - print_options.background = background + print_options.background = background if margin_bottom is not None: print_options.margin_bottom = margin_bottom if margin_left is not None: @@ -345,7 +346,7 @@ def _print_page_as_pdf_to_file(self, filename, options): def _save_pdf_to_file(self, pdfbase64, path): pdfdata = b64decode(pdfbase64) - with open(path, mode='wb') as pdf: + with open(path, mode="wb") as pdf: pdf.write(pdfdata) def _get_pdf_path(self, filename): diff --git a/src/SeleniumLibrary/keywords/selectelement.py b/src/SeleniumLibrary/keywords/selectelement.py index be87863cc..24b45978f 100644 --- a/src/SeleniumLibrary/keywords/selectelement.py +++ b/src/SeleniumLibrary/keywords/selectelement.py @@ -23,9 +23,7 @@ class SelectElementKeywords(LibraryComponent): @keyword - def get_list_items( - self, locator: Locator, values: bool = False - ) -> list[str]: + def get_list_items(self, locator: Locator, values: bool = False) -> list[str]: """Returns all labels or values of selection list ``locator``. See the `Locating elements` section for details about the locator @@ -133,7 +131,9 @@ def list_selection_should_be(self, locator: Locator, *expected: str): ) def _format_selection(self, labels, values): - return " | ".join(f"{label} ({value})" for label, value in zip(labels, values, strict=True)) + return " | ".join( + f"{label} ({value})" for label, value in zip(labels, values, strict=True) + ) @keyword def list_should_have_no_selections(self, locator: Locator): @@ -290,9 +290,7 @@ def unselect_all_from_list(self, locator: Locator): select.deselect_all() @keyword - def unselect_from_list_by_index( - self, locator: Locator, *indexes: str - ): + def unselect_from_list_by_index(self, locator: Locator, *indexes: str): """Unselects options from selection list ``locator`` by ``indexes``. Indexes of list options start from 0. This keyword works only with @@ -317,9 +315,7 @@ def unselect_from_list_by_index( select.deselect_by_index(int(index)) @keyword - def unselect_from_list_by_value( - self, locator: Locator, *values: str - ): + def unselect_from_list_by_value(self, locator: Locator, *values: str): """Unselects options from selection list ``locator`` by ``values``. This keyword works only with multi-selection lists. @@ -342,9 +338,7 @@ def unselect_from_list_by_value( select.deselect_by_value(value) @keyword - def unselect_from_list_by_label( - self, locator: Locator, *labels: str - ): + def unselect_from_list_by_label(self, locator: Locator, *labels: str): """Unselects options from selection list ``locator`` by ``labels``. This keyword works only with multi-selection lists. diff --git a/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py b/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py index 18ea243c2..695717f77 100644 --- a/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py +++ b/src/SeleniumLibrary/keywords/webdrivertools/webdrivertools.py @@ -39,9 +39,6 @@ class WebDriverCreator: - - - def __init__(self, log_dir): self.browser_names = { "googlechrome": "chrome", @@ -135,9 +132,9 @@ def _remote_capabilities_resolver(self, set_capabilities, default_capabilities): def _get_log_method(self, service_cls, service_log_path): # -- temporary fix to transition selenium to v4.13 from v4.11 and prior sig = signature(service_cls) - if 'log_output' in str(sig): - return {'log_output': service_log_path} - return {'log_path': service_log_path} + if "log_output" in str(sig): + return {"log_output": service_log_path} + return {"log_path": service_log_path} # -- def create_chrome( @@ -154,7 +151,9 @@ def create_chrome( options = webdriver.ChromeOptions() return self._remote(remote_url, options=options) if not executable_path: - executable_path = self._get_executable_path(webdriver.chrome.service.Service) + executable_path = self._get_executable_path( + webdriver.chrome.service.Service + ) log_method = self._get_log_method(ChromeService, service_log_path) if not service: service = ChromeService(executable_path=executable_path, **log_method) @@ -174,9 +173,14 @@ def create_headless_chrome( ): if not options: options = webdriver.ChromeOptions() - options.add_argument('--headless=new') + options.add_argument("--headless=new") return self.create_chrome( - desired_capabilities, remote_url, options, service_log_path, executable_path, service + desired_capabilities, + remote_url, + options, + service_log_path, + executable_path, + service, ) def _get_executable_path(self, webdriver): @@ -210,8 +214,12 @@ def create_firefox( if remote_url: return self._remote(remote_url, options) if not executable_path: - executable_path = self._get_executable_path(webdriver.firefox.service.Service) - log_method = self._get_log_method(FirefoxService, service_log_path or self._geckodriver_log) + executable_path = self._get_executable_path( + webdriver.firefox.service.Service + ) + log_method = self._get_log_method( + FirefoxService, service_log_path or self._geckodriver_log + ) if service is None: service = FirefoxService(executable_path=executable_path, **log_method) return webdriver.Firefox( @@ -258,7 +266,7 @@ def create_headless_firefox( ): if not options: options = webdriver.FirefoxOptions() - options.add_argument('-headless') + options.add_argument("-headless") return self.create_firefox( desired_capabilities, remote_url, @@ -290,7 +298,7 @@ def create_ie( return webdriver.Ie( options=options, service=service, - #**desired_capabilities, + # **desired_capabilities, ) def _has_options(self, web_driver): @@ -318,7 +326,7 @@ def create_edge( return webdriver.Edge( options=options, service=service, - #**desired_capabilities, + # **desired_capabilities, ) def create_safari( @@ -456,10 +464,10 @@ def _get_index(self, alias_or_index): except ValueError: return None + class SeleniumService: - """ + """ """ - """ def create(self, browser, service): if not service: return None @@ -473,8 +481,12 @@ def create(self, browser, service): service_parameters = inspect.signature(selenium_service).parameters for key in attrs: if key not in service_parameters: - service_module = '.'.join((selenium_service.__module__, selenium_service.__qualname__)) - raise ValueError(f"{key} is not a member of {service_module} Service class") + service_module = ".".join( + (selenium_service.__module__, selenium_service.__qualname__) + ) + raise ValueError( + f"{key} is not a member of {service_module} Service class" + ) return selenium_service(**attrs) def _parse(self, service): @@ -483,12 +495,14 @@ def _parse(self, service): instantiation. Thus each item is split instead parsed as done with options. """ result = {} - for item in self._split(service,';'): + for item in self._split(service, ";"): try: - attr, val = self._split(item, '=') - result[attr]=ast.literal_eval(val) + attr, val = self._split(item, "=") + result[attr] = ast.literal_eval(val) except (ValueError, SyntaxError) as original_exception: - raise ValueError(f'Unable to parse service: "{item}"') from original_exception + raise ValueError( + f'Unable to parse service: "{item}"' + ) from original_exception return result def _import_service(self, browser): @@ -508,6 +522,7 @@ def _split(self, service_or_attr, splittok): split_string.append(service_or_attr[start_position:]) return split_string + class SeleniumOptions: def create(self, browser, options): if not options: @@ -519,8 +534,10 @@ def create(self, browser, options): selenium_options = selenium_options() for option in options: for key in option: - if key == '' and option[key]==[]: - logger.warn('Empty selenium option found and ignored. Suggested you review options passed to `Open Browser` keyword') + if key == "" and option[key] == []: + logger.warn( + "Empty selenium option found and ignored. Suggested you review options passed to `Open Browser` keyword" + ) continue attr = getattr(selenium_options, key) if callable(attr): @@ -570,7 +587,9 @@ def _parse(self, options): try: result.append(self._parse_to_tokens(item)) except (ValueError, SyntaxError) as original_exception: - raise ValueError(f'Unable to parse option: "{item}"') from original_exception + raise ValueError( + f'Unable to parse option: "{item}"' + ) from original_exception return result def _parse_to_tokens(self, item): diff --git a/src/SeleniumLibrary/keywords/window.py b/src/SeleniumLibrary/keywords/window.py index c0d70a816..e0393cbe9 100644 --- a/src/SeleniumLibrary/keywords/window.py +++ b/src/SeleniumLibrary/keywords/window.py @@ -121,7 +121,7 @@ def switch_window( @keyword def close_window(self): - """Closes currently opened and selected browser window/tab. """ + """Closes currently opened and selected browser window/tab.""" self.driver.close() @keyword diff --git a/src/SeleniumLibrary/locators/windowmanager.py b/src/SeleniumLibrary/locators/windowmanager.py index 967d4be54..8244c8236 100644 --- a/src/SeleniumLibrary/locators/windowmanager.py +++ b/src/SeleniumLibrary/locators/windowmanager.py @@ -199,7 +199,9 @@ def _select_matching(self, matcher, error): def _get_current_window_info(self): try: - window_id, name = self.driver.execute_script("return [ window.id, window.name ];") + window_id, name = self.driver.execute_script( + "return [ window.id, window.name ];" + ) except WebDriverException: # The webdriver implementation doesn't support Javascript so we # can't get window id or name this way. diff --git a/src/SeleniumLibrary/utils/__init__.py b/src/SeleniumLibrary/utils/__init__.py index b134b8c2f..7440fe29d 100644 --- a/src/SeleniumLibrary/utils/__init__.py +++ b/src/SeleniumLibrary/utils/__init__.py @@ -17,7 +17,7 @@ from robot.utils import plural_or_not, secs_to_timestr, timestr_to_secs # noqa from .librarylistener import LibraryListener # noqa -from .types import ( #noqa +from .types import ( # noqa is_falsy, is_noney, is_truthy, diff --git a/src/SeleniumLibrary/utils/types.py b/src/SeleniumLibrary/utils/types.py index 4c0579481..e12657d53 100644 --- a/src/SeleniumLibrary/utils/types.py +++ b/src/SeleniumLibrary/utils/types.py @@ -24,16 +24,18 @@ # https://github.com/approvals/ApprovalTests.Python/issues/41 WINDOWS = os.name == "nt" -Locator: TypeAlias = WebElement | str | list['Locator'] +Locator: TypeAlias = WebElement | str | list["Locator"] + def is_noney(item): return item is None or (isinstance(item, str) and item.upper() == "NONE") + def _convert_delay(delay): if isinstance(delay, timedelta): return delay.microseconds // 1000 - x = timestr_to_secs(delay) - return int( x * 1000) + x = timestr_to_secs(delay) + return int(x * 1000) def _convert_timeout(timeout): diff --git a/utest/test/api/my_lib.py b/utest/test/api/my_lib.py index 07883588d..3dc65ee95 100644 --- a/utest/test/api/my_lib.py +++ b/utest/test/api/my_lib.py @@ -21,4 +21,5 @@ def foo(self): def bar(self, arg): self.info(arg) + my_lib = MyLib diff --git a/utest/test/api/my_lib_args.py b/utest/test/api/my_lib_args.py index 39786e1f7..572bd2abc 100644 --- a/utest/test/api/my_lib_args.py +++ b/utest/test/api/my_lib_args.py @@ -22,4 +22,5 @@ def add_cookie(self, foo, bar): self.info(foo) self.info(bar) + my_lib_args = MyLibArgs diff --git a/utest/test/api/my_lib_not_inherit.py b/utest/test/api/my_lib_not_inherit.py index 804c29009..ec4c37a27 100644 --- a/utest/test/api/my_lib_not_inherit.py +++ b/utest/test/api/my_lib_not_inherit.py @@ -9,4 +9,5 @@ def __init__(self, ctx): def bar(self, arg): self.info(arg) + my_lib_not_inherit = MyLibNotInherit diff --git a/utest/test/api/plugin_tester.py b/utest/test/api/plugin_tester.py index 2db5585e6..39480f1b6 100644 --- a/utest/test/api/plugin_tester.py +++ b/utest/test/api/plugin_tester.py @@ -14,4 +14,5 @@ def foo(self): def bar(self, arg): self.info(arg) + plugin_tester = PluginTester diff --git a/utest/test/api/plugin_with_event_firing_webdriver.py b/utest/test/api/plugin_with_event_firing_webdriver.py index ddb4103ef..d90c34ab7 100644 --- a/utest/test/api/plugin_with_event_firing_webdriver.py +++ b/utest/test/api/plugin_with_event_firing_webdriver.py @@ -2,7 +2,6 @@ class PluginWithEventFiringWebdriver(LibraryComponent): - """This is example for PluginWithEventFiringWebdriver plugin documentation. It may contains many chapters and there might be many words @@ -33,4 +32,5 @@ def __init__(self, ctx): def tidii(self): self.info("foo") + plugin_with_event_firing_webdriver = PluginWithEventFiringWebdriver diff --git a/utest/test/api/test_event_firing_webdriver.py b/utest/test/api/test_event_firing_webdriver.py index c5fa4af96..d9c03ef1d 100644 --- a/utest/test/api/test_event_firing_webdriver.py +++ b/utest/test/api/test_event_firing_webdriver.py @@ -24,9 +24,14 @@ def test_no_event_firing_webdriver(self): def test_import_event_firing_webdriver_error_module(self): listener = os.path.join(self.root_dir, "MyListenerWrongName.py") - with pytest.raises(DataError, match=r"Importing test Selenium lister class '.*' failed."): + with pytest.raises( + DataError, match=r"Importing test Selenium lister class '.*' failed." + ): SeleniumLibrary(event_firing_webdriver=listener) def test_too_many_event_firing_webdriver(self): - with pytest.raises(ValueError, match=r"It is possible to import only one listener but there were 2 listeners."): + with pytest.raises( + ValueError, + match=r"It is possible to import only one listener but there were 2 listeners.", + ): SeleniumLibrary(event_firing_webdriver=f"{self.listener},{self.listener}") diff --git a/utest/test/api/test_filepath_unusual_characters.py b/utest/test/api/test_filepath_unusual_characters.py index 0435996c4..395d1e87a 100644 --- a/utest/test/api/test_filepath_unusual_characters.py +++ b/utest/test/api/test_filepath_unusual_characters.py @@ -5,7 +5,6 @@ from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from robot.utils import WINDOWS from SeleniumLibrary.utils.path_formatter import _format_path diff --git a/utest/test/api/test_plugin_documentation.py b/utest/test/api/test_plugin_documentation.py index c411915fd..06c5a6e1f 100644 --- a/utest/test/api/test_plugin_documentation.py +++ b/utest/test/api/test_plugin_documentation.py @@ -7,7 +7,6 @@ GenericDiffReporterFactory, ) from approvaltests.reporters.python_native_reporter import PythonNativeReporter -from robot.utils import WINDOWS from SeleniumLibrary import SeleniumLibrary diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py index bfc195fcb..317cc6dd5 100644 --- a/utest/test/api/test_plugins.py +++ b/utest/test/api/test_plugins.py @@ -14,10 +14,12 @@ class ExtendingSeleniumLibrary(unittest.TestCase): def setUpClass(cls): cls.sl = SeleniumLibrary() cls.root_dir = os.path.dirname(os.path.abspath(__file__)) + class Plugin(NamedTuple): plugin: str args: list kw_args: dict + lib = Plugin( plugin=os.path.join(cls.root_dir, "my_lib.py"), args=[], kw_args={} ) diff --git a/utest/test/keywords/IGNOREDtest_webdrivercreator.py b/utest/test/keywords/IGNOREDtest_webdrivercreator.py index 0d3bed35b..c74852517 100644 --- a/utest/test/keywords/IGNOREDtest_webdrivercreator.py +++ b/utest/test/keywords/IGNOREDtest_webdrivercreator.py @@ -134,7 +134,8 @@ def test_capabilities_resolver_chrome(creator): def test_chrome(creator): expected_webdriver = mock() when(webdriver).Chrome( - options=None, service=None # service=ANY # service_log_path=None, executable_path="chromedriver" + options=None, + service=None, # service=ANY # service_log_path=None, executable_path="chromedriver" ).thenReturn(expected_webdriver) driver = creator.create_chrome({}, None) assert driver == expected_webdriver @@ -206,7 +207,8 @@ def test_chrome_headless(creator): when(webdriver).ChromeOptions().thenReturn(options) when(webdriver).ChromeOptions().thenReturn(options) when(webdriver).Chrome( - options=options, service=ANY # service=None # service_log_path=None, executable_path="chromedriver" + options=options, + service=ANY, # service=None # service_log_path=None, executable_path="chromedriver" ).thenReturn(expected_webdriver) driver = creator.create_headless_chrome({}, None) assert options.headless is True diff --git a/utest/test/keywords/IGNOREtest_webdrivercreator_executable_path.py b/utest/test/keywords/IGNOREtest_webdrivercreator_executable_path.py index 5ac5bed01..d51d23c0b 100644 --- a/utest/test/keywords/IGNOREtest_webdrivercreator_executable_path.py +++ b/utest/test/keywords/IGNOREtest_webdrivercreator_executable_path.py @@ -23,7 +23,8 @@ def teardown_function(): def test_create_chrome_executable_path_set(creator): expected_webdriver = mock() when(webdriver).Chrome( - options=None, service=ANY, # service_log_path=None, executable_path="/path/to/chromedriver" + options=None, + service=ANY, # service_log_path=None, executable_path="/path/to/chromedriver" ).thenReturn(expected_webdriver) driver = creator.create_chrome({}, None, executable_path="/path/to/chromedriver") assert driver == expected_webdriver @@ -32,7 +33,8 @@ def test_create_chrome_executable_path_set(creator): def test_create_chrome_executable_path_not_set(creator): expected_webdriver = mock() when(webdriver).Chrome( - options=None, service=ANY, # service_log_path=None, executable_path="chromedriver" + options=None, + service=ANY, # service_log_path=None, executable_path="chromedriver" ).thenReturn(expected_webdriver) when(creator)._get_executable_path(ANY).thenReturn("chromedriver") driver = creator.create_chrome({}, None, executable_path=None) @@ -71,7 +73,8 @@ def test_create_heasless_chrome_executable_path_set(creator): options = mock() when(webdriver).ChromeOptions().thenReturn(options) when(webdriver).Chrome( - options=options, service = ANY # service_log_path=None, executable_path="/path/to/chromedriver" + options=options, + service=ANY, # service_log_path=None, executable_path="/path/to/chromedriver" ).thenReturn(expected_webdriver) driver = creator.create_headless_chrome( {}, None, executable_path="/path/to/chromedriver" @@ -91,7 +94,7 @@ def test_create_firefox_executable_path_set(creator): when(webdriver).Firefox( options=options, # firefox_profile=profile, - service = ANY, + service=ANY, # service_log_path=log_file, # executable_path=executable, ).thenReturn(expected_webdriver) diff --git a/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py b/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py index 3bde08e87..c96c32c49 100644 --- a/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py +++ b/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py @@ -6,7 +6,7 @@ from selenium import webdriver from selenium.webdriver.chrome import service as chromeservice -#from selenium.webdriver.chrome.service import Service as ChromeService +# from selenium.webdriver.chrome.service import Service as ChromeService from SeleniumLibrary.keywords import WebDriverCreator from SeleniumLibrary.utils import WINDOWS @@ -16,6 +16,7 @@ class Creator(NamedTuple): creator: WebDriverCreator output_dir: str + def creator(): curr_dir = os.path.dirname(os.path.abspath(__file__)) output_dir = os.path.abspath(os.path.join(curr_dir, "..", "..", "output_dir")) @@ -57,7 +58,9 @@ def test_log_file_with_index_exist(creator): def test_create_chrome_with_service_log_path_none(creator): expected_webdriver = mock() service = mock() - when(chromeservice).Service(log_path=None, executable_path="chromedriver").thenReturn(service) + when(chromeservice).Service( + log_path=None, executable_path="chromedriver" + ).thenReturn(service) # when(chrome).service(log_path=None, executable_path="chromedriver").thenReturn(service) # service = ChromeService(log_path=None, executable_path="chromedriver") # service = Service(log_path=None, executable_path="chromedriver") @@ -65,7 +68,8 @@ def test_create_chrome_with_service_log_path_none(creator): # when(webdriver).chrome.service().thenReturn(service) when(webdriver).Chrome( # options=None, service_log_path=None, executable_path="chromedriver" - options=None, service=ANY, + options=None, + service=ANY, # options=None, service=service, ).thenReturn(expected_webdriver) driver = creator.creator.create_chrome({}, None, service_log_path=None) @@ -76,7 +80,8 @@ def test_create_chrome_with_service_log_path_real_path(creator): log_file = os.path.join(creator.output_dir, "firefox-{index}.log") expected_webdriver = mock() when(webdriver).Chrome( - options=None, service=ANY, + options=None, + service=ANY, ).thenReturn(expected_webdriver) driver = creator.creator.create_chrome({}, None, service_log_path=log_file) assert driver == expected_webdriver @@ -88,7 +93,8 @@ def test_create_headlesschrome_with_service_log_path_real_path(creator): options = mock() when(webdriver).ChromeOptions().thenReturn(options) when(webdriver).Chrome( - options=options, service=ANY, + options=options, + service=ANY, ).thenReturn(expected_webdriver) driver = creator.creator.create_headless_chrome({}, None, service_log_path=log_file) assert driver == expected_webdriver @@ -162,7 +168,8 @@ def test_create_ie_with_service_log_path_real_path(creator): log_file = os.path.join(creator.output_dir, "ie-1.log") expected_webdriver = mock() when(webdriver).Ie( - options=None, service=ANY, + options=None, + service=ANY, ).thenReturn(expected_webdriver) driver = creator.creator.create_ie({}, None, service_log_path=log_file) assert driver == expected_webdriver @@ -173,7 +180,8 @@ def test_create_edge_with_service_log_path_real_path(creator): log_file = os.path.join(creator.output_dir, "edge-1.log") expected_webdriver = mock() when(webdriver).Edge( - options=None, service=ANY, + options=None, + service=ANY, ).thenReturn(expected_webdriver) driver = creator.creator.create_edge({}, None, service_log_path=log_file) assert driver == expected_webdriver diff --git a/utest/test/keywords/test_browsermanagement.py b/utest/test/keywords/test_browsermanagement.py index 0f048c0ac..5b9e03213 100644 --- a/utest/test/keywords/test_browsermanagement.py +++ b/utest/test/keywords/test_browsermanagement.py @@ -42,6 +42,7 @@ def test_get_action_chain_delay_default(): sl.set_action_chain_delay("300 milliseconds") assert sl.get_action_chain_delay() == 0.3 + def test_selenium_implicit_wait_default(): sl = SeleniumLibrary() assert sl.implicit_wait == 0.0, "Wait should have 0.0" @@ -75,7 +76,9 @@ def test_selenium_implicit_wait_get(): def test_selenium_page_load_timeout_with_default(): sl = SeleniumLibrary() - assert sl.page_load_timeout == 300.0, "Default page load timeout should be 5 minutes" + assert sl.page_load_timeout == 300.0, ( + "Default page load timeout should be 5 minutes" + ) def test_set_selenium_page_load_timeout(): @@ -137,7 +140,8 @@ def test_open_browser_speed(): browser = mock() executable_path = "chromedriver" when(webdriver).Chrome( - options=None, service=ANY, + options=None, + service=ANY, ).thenReturn(browser) bm = BrowserManagementKeywords(ctx) when(bm._webdriver_creator)._get_executable_path(ANY).thenReturn(executable_path) @@ -152,17 +156,17 @@ def test_create_webdriver_speed(): ctx.speed = 0.0 browser = mock() executable_path = "chromedriver" - #Original code: + # Original code: # when(webdriver).Chrome( # options=None, service_log_path=None, executable_path=executable_path # ).thenReturn(browser) - #Tried: + # Tried: # service = ChromeService(executable_path="chromedriver", log_path=None) # when(webdriver).Chrome( # options=None, service=Service, # ).thenReturn(browser) - #Results in .. + # Results in .. # E mockito.invocation.InvocationError: # E Called but not expected: # E @@ -172,11 +176,11 @@ def test_create_webdriver_speed(): # E # E Chrome(options=None, service=) - #Tried: + # Tried: # when(webdriver).Chrome( # options=None, service=None, # ).thenReturn(browser) - #Results in .. + # Results in .. # E mockito.invocation.InvocationError: # E Called but not expected: # E @@ -186,7 +190,7 @@ def test_create_webdriver_speed(): # E # E Chrome(options=None, service=None) - #Tried: + # Tried: # service = mock() # when(webdriver.chrome.service).Service( # executable_path="chromedriver", log_path=None, @@ -194,10 +198,10 @@ def test_create_webdriver_speed(): # when(webdriver).Chrome( # options=None, service=service, # ).thenReturn(browser) - #Results in .. + # Results in .. # ... - #Tried: + # Tried: # service = ChromeService(executable_path="chromedriver", log_path=None) # when(webdriver.chrome.service).Service( # executable_path="chromedriver", log_path=None, @@ -205,7 +209,7 @@ def test_create_webdriver_speed(): # when(webdriver).Chrome( # options=None, service=service, # ).thenReturn(browser) - #Results in .. + # Results in .. # E mockito.invocation.InvocationError: # E Called but not expected: # E @@ -214,16 +218,16 @@ def test_create_webdriver_speed(): # E Stubbed invocations are: # E # E Chrome(options=None, service=) - #which does seem closer .. + # which does seem closer .. - #Tried: + # Tried: # service = Service(executable_path="chromedriver", log_path=None) # when(webdriver).Chrome( # options=None, service=service, # ).thenReturn(browser) - #Results in .. + # Results in .. - #Tried: + # Tried: # service = mock() # ## when(Service).__init__( # when(Chrome).Service( @@ -232,36 +236,37 @@ def test_create_webdriver_speed(): # when(webdriver).Chrome( # options=None, service=service, # ).thenReturn(browser) - #Results in .. + # Results in .. - #Tried: + # Tried: when(webdriver).Chrome( - options=None, service=ANY, + options=None, + service=ANY, ).thenReturn(browser) - #Results in .. + # Results in .. # .. passed ?? Is this truely correct? - #Also tried: + # Also tried: # service_log_path = None # service = ChromeService(executable_path=executable_path, log_path=service_log_path) # when(webdriver).Chrome( # options=None, service=service, # ).thenReturn(browser) - #Also tried: + # Also tried: # service = ChromeService() # when(webdriver).Chrome( # options=None, service=service, # ).thenReturn(browser) - #Also tried: + # Also tried: # service = mock(ChromeService) # when(webdriver).Chrome( # options=None, service=service, # ).thenReturn(browser) - #Also tried: - #service = mock(Service) + # Also tried: + # service = mock(Service) # when(webdriver).Chrome( # options=None, service=service, # ).thenReturn(browser) diff --git a/utest/test/keywords/test_expectedconditions.py b/utest/test/keywords/test_expectedconditions.py index 927f361ce..d6052e08c 100644 --- a/utest/test/keywords/test_expectedconditions.py +++ b/utest/test/keywords/test_expectedconditions.py @@ -20,6 +20,7 @@ # Element\ To\ Be\ Clickable # Element${SPACE}To${SPACE}Be${SPACE}Clickable + class TestExpectedConditionKeywords(unittest.TestCase): @classmethod def setUpClass(cls): diff --git a/utest/test/keywords/test_firefox_profile_parsing.py b/utest/test/keywords/test_firefox_profile_parsing.py index f4e7d5541..ad3864d5f 100644 --- a/utest/test/keywords/test_firefox_profile_parsing.py +++ b/utest/test/keywords/test_firefox_profile_parsing.py @@ -5,7 +5,6 @@ from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from robot.utils import WINDOWS from selenium import webdriver from SeleniumLibrary.keywords import WebDriverCreator @@ -66,9 +65,9 @@ def _get_preferences_attribute(self, result): # -- temporary fix to transition selenium to v4.17.2 from v4.16.0 and prior # from inspect import signature # sig = signature(result) - if hasattr(result,'default_preferences'): + if hasattr(result, "default_preferences"): return result.default_preferences - if hasattr(result,'_desired_preferences'): + if hasattr(result, "_desired_preferences"): return result._desired_preferences return None # -- diff --git a/utest/test/keywords/test_javascript.py b/utest/test/keywords/test_javascript.py index 9acbcd1db..2e35bae4e 100644 --- a/utest/test/keywords/test_javascript.py +++ b/utest/test/keywords/test_javascript.py @@ -5,7 +5,6 @@ from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from robot.utils import WINDOWS from SeleniumLibrary.keywords import JavaScriptKeywords diff --git a/utest/test/keywords/test_keyword_arguments_element.py b/utest/test/keywords/test_keyword_arguments_element.py index 2894f88ad..d6662e40a 100644 --- a/utest/test/keywords/test_keyword_arguments_element.py +++ b/utest/test/keywords/test_keyword_arguments_element.py @@ -31,7 +31,6 @@ def test_element_text_should_be(element): assert "foobar" in str(error.value) - def test_action_chain_delay_in_elements(element): locator = "//div" webelement = mock() @@ -41,8 +40,7 @@ def test_action_chain_delay_in_elements(element): expected_delay_in_ms = 1000 element.ctx.action_chain_delay = expected_delay_in_ms when(chain_mock).move_to_element(matchers.ANY).thenReturn(mock()) - when(SUT).ActionChains(matchers.ANY, duration=expected_delay_in_ms).thenReturn(chain_mock) + when(SUT).ActionChains(matchers.ANY, duration=expected_delay_in_ms).thenReturn( + chain_mock + ) element.scroll_element_into_view(locator) - - - diff --git a/utest/test/keywords/test_press_keys.py b/utest/test/keywords/test_press_keys.py index d80fd7189..ebea52ff6 100644 --- a/utest/test/keywords/test_press_keys.py +++ b/utest/test/keywords/test_press_keys.py @@ -5,7 +5,6 @@ from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from robot.utils import WINDOWS from SeleniumLibrary.keywords import ElementKeywords diff --git a/utest/test/keywords/test_screen_shot.py b/utest/test/keywords/test_screen_shot.py index 16879c6fb..128d98230 100644 --- a/utest/test/keywords/test_screen_shot.py +++ b/utest/test/keywords/test_screen_shot.py @@ -10,6 +10,7 @@ EMBED = "EMBED" BASE64 = "BASE64" + @pytest.fixture(scope="module") def screen_shot(): ctx = mock() diff --git a/utest/test/keywords/test_selenium_options_parser.py b/utest/test/keywords/test_selenium_options_parser.py index 950da63c3..77a3ed9a2 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -1,6 +1,5 @@ import os import sys -import unittest import pytest from approvaltests.approvals import verify_all @@ -8,7 +7,6 @@ GenericDiffReporterFactory, ) from mockito import ANY, mock, unstub, when -from robot.utils import WINDOWS from selenium import webdriver from SeleniumLibrary.keywords.webdrivertools import SeleniumOptions, WebDriverCreator @@ -18,6 +16,7 @@ def options(): return SeleniumOptions() + @pytest.fixture(scope="module") def reporter(): path = os.path.dirname(__file__) @@ -216,7 +215,8 @@ def test_create_chrome_with_options(creator): # executable_path=ANY, log_path=ANY, # ).thenReturn(service) when(webdriver).Chrome( - options=options, service=ANY # service_log_path=None, executable_path="chromedriver" + options=options, + service=ANY, # service_log_path=None, executable_path="chromedriver" ).thenReturn(expected_webdriver) driver = creator.create_chrome({}, None, options=options) assert driver == expected_webdriver @@ -224,13 +224,13 @@ def test_create_chrome_with_options(creator): def test_create_chrome_with_options_and_remote_url(creator): url = "http://localhost:4444/wd/hub" - #caps = webdriver.DesiredCapabilities.CHROME.copy() + # caps = webdriver.DesiredCapabilities.CHROME.copy() options = mock() expected_webdriver = mock() file_detector = mock_file_detector(creator) when(webdriver).Remote( command_executor=url, - #desired_capabilities=caps, + # desired_capabilities=caps, # browser_profile=None, options=options, file_detector=file_detector, @@ -243,7 +243,8 @@ def test_create_headless_chrome_with_options(creator): options = mock() expected_webdriver = mock() when(webdriver).Chrome( - options=options, service=ANY # service_log_path=None, options=options, executable_path="chromedriver" + options=options, + service=ANY, # service_log_path=None, options=options, executable_path="chromedriver" ).thenReturn(expected_webdriver) driver = creator.create_headless_chrome({}, None, options=options) assert driver == expected_webdriver @@ -257,7 +258,7 @@ def test_create_firefox_with_options(creator, output_dir): when(webdriver).FirefoxProfile().thenReturn(profile) when(webdriver).Firefox( options=options, - service=ANY + service=ANY, # firefox_profile=profile, # executable_path="geckodriver", # service_log_path=log_file, @@ -293,7 +294,7 @@ def test_create_headless_firefox_with_options(creator, output_dir): when(webdriver).FirefoxProfile().thenReturn(profile) when(webdriver).Firefox( options=options, - service=ANY + service=ANY, # firefox_profile=profile, # executable_path="geckodriver", # service_log_path=log_file, @@ -306,7 +307,8 @@ def test_create_ie_with_options(creator): options = mock() expected_webdriver = mock() when(webdriver).Ie( - options=options, service=ANY # service_log_path=None, options=options, executable_path="IEDriverServer.exe" + options=options, + service=ANY, # service_log_path=None, options=options, executable_path="IEDriverServer.exe" ).thenReturn(expected_webdriver) driver = creator.create_ie({}, None, options=options) assert driver == expected_webdriver @@ -333,7 +335,8 @@ def test_create_ie_with_options_and_log_path(creator): options = mock() expected_webdriver = mock() when(webdriver).Ie( - options=options, service=ANY # service_log_path=None, executable_path="IEDriverServer.exe" + options=options, + service=ANY, # service_log_path=None, executable_path="IEDriverServer.exe" ).thenReturn(expected_webdriver) driver = creator.create_ie({}, None, options=options) assert driver == expected_webdriver @@ -366,7 +369,8 @@ def test_create_driver_chrome(creator): executable_path = "chromedriver" when(creator)._get_executable_path(ANY).thenReturn(executable_path) when(webdriver).Chrome( - options=options, service=ANY # service_log_path=None, options=options, executable_path=executable_path + options=options, + service=ANY, # service_log_path=None, options=options, executable_path=executable_path ).thenReturn(expected_webdriver) driver = creator.create_driver( "Chrome", desired_capabilities={}, remote_url=None, options=str_options @@ -386,7 +390,7 @@ def test_create_driver_firefox(creator, output_dir): when(creator)._get_executable_path(ANY).thenReturn(executable_path) when(webdriver).Firefox( options=options, - service=ANY + service=ANY, # firefox_profile=profile, # executable_path=executable_path, # service_log_path=log_file, diff --git a/utest/test/keywords/test_selenium_service_parser.py b/utest/test/keywords/test_selenium_service_parser.py index 8c287411a..309b2203e 100644 --- a/utest/test/keywords/test_selenium_service_parser.py +++ b/utest/test/keywords/test_selenium_service_parser.py @@ -1,6 +1,5 @@ import os import sys -import unittest import pytest from approvaltests.approvals import verify_all @@ -8,7 +7,6 @@ GenericDiffReporterFactory, ) from mockito import unstub -from robot.utils import WINDOWS from SeleniumLibrary.keywords.webdrivertools import SeleniumService @@ -17,6 +15,7 @@ def service(): return SeleniumService() + @pytest.fixture(scope="module") def reporter(): path = os.path.dirname(__file__) @@ -37,16 +36,14 @@ def test_parse_service_string(service, reporter): results.append(service._parse('attribute="arg1"')) # results.append(service._parse(" attribute = True ")) # need to resolve issues with spaces in service string. results.append(service._parse('attribute="arg1";attribute=True')) - results.append(service._parse('attribute=["arg1","arg2","arg3"] ; attribute=True ; attribute="arg4"')) results.append( service._parse( - 'attribute="C:\\\\path\\to\\\\profile"' + 'attribute=["arg1","arg2","arg3"] ; attribute=True ; attribute="arg4"' ) ) + results.append(service._parse('attribute="C:\\\\path\\to\\\\profile"')) results.append( - service._parse( - r'attribute="arg1"; attribute="C:\\path\\to\\profile"' - ) + service._parse(r'attribute="arg1"; attribute="C:\\path\\to\\profile"') ) results.append(service._parse("attribute=None")) verify_all("Selenium service string to dict", results, reporter=reporter) @@ -60,7 +57,9 @@ def test_parse_service_string_errors(service, reporter): results.append(error_formatter(service._parse, "attribute=['arg1'", True)) results.append(error_formatter(service._parse, "attribute=['arg1';'arg2']", True)) results.append(error_formatter(service._parse, "attribute['arg1']", True)) - results.append(error_formatter(service._parse, "attribute=['arg1'] attribute=['arg2']", True)) + results.append( + error_formatter(service._parse, "attribute=['arg1'] attribute=['arg2']", True) + ) verify_all("Selenium service string errors", results, reporter=reporter) @@ -72,25 +71,29 @@ def test_parse_service_string_errors_py3_12(service, reporter): results.append(error_formatter(service._parse, "attribute=['arg1'", True)) results.append(error_formatter(service._parse, "attribute=['arg1';'arg2']", True)) results.append(error_formatter(service._parse, "attribute['arg1']", True)) - results.append(error_formatter(service._parse, "attribute=['arg1'] attribute=['arg2']", True)) + results.append( + error_formatter(service._parse, "attribute=['arg1'] attribute=['arg2']", True) + ) verify_all("Selenium service string errors", results, reporter=reporter) def test_split_service(service, reporter): results = [] - results.append(service._split("attribute='arg1'", ';')) - results.append(service._split("attribute='arg1';attribute='arg2'", ';')) - results.append(service._split("attribute=['arg1','arg2'];attribute='arg3'", ';')) - results.append(service._split(" attribute = 'arg1' ; attribute = 'arg2' ", ';')) + results.append(service._split("attribute='arg1'", ";")) + results.append(service._split("attribute='arg1';attribute='arg2'", ";")) + results.append(service._split("attribute=['arg1','arg2'];attribute='arg3'", ";")) + results.append(service._split(" attribute = 'arg1' ; attribute = 'arg2' ", ";")) verify_all("Selenium service string splitting", results, reporter=reporter) def test_split_attribute(service, reporter): results = [] - results.append(service._split("attribute='arg1'", '=')) - results.append(service._split("attribute=['arg1','arg2']", '=')) - results.append(service._split(" attribute = [ 'arg1' , 'arg2' ]", '=')) - verify_all("Selenium service attribute string splitting", results, reporter=reporter) + results.append(service._split("attribute='arg1'", "=")) + results.append(service._split("attribute=['arg1','arg2']", "=")) + results.append(service._split(" attribute = [ 'arg1' , 'arg2' ]", "=")) + verify_all( + "Selenium service attribute string splitting", results, reporter=reporter + ) def test_service_create(service, reporter): diff --git a/utest/test/locators/test_elementfinder.py b/utest/test/locators/test_elementfinder.py index 3df31b9f9..21792ea33 100644 --- a/utest/test/locators/test_elementfinder.py +++ b/utest/test/locators/test_elementfinder.py @@ -281,10 +281,13 @@ def test_find_with_data(finder): finder.find("data:id:my_id", tag="div", required=False) verify(driver).find_elements(By.XPATH, '//*[@data-id="my_id"]') + def test_find_with_data_multiple_colons(finder): driver = _get_driver(finder) elements = _make_mock_elements("div", "a", "span", "a") - when(driver).find_elements(By.XPATH, '//*[@data-automation-id="foo:bar"]').thenReturn(elements) + when(driver).find_elements( + By.XPATH, '//*[@data-automation-id="foo:bar"]' + ).thenReturn(elements) result = finder.find("data:automation-id:foo:bar", first_only=False) assert result == elements diff --git a/utest/test/locators/test_windowmanager.py b/utest/test/locators/test_windowmanager.py index 3e809e5b3..393faad60 100644 --- a/utest/test/locators/test_windowmanager.py +++ b/utest/test/locators/test_windowmanager.py @@ -13,7 +13,10 @@ def test_select_with_invalid_prefix(self): manager = WindowManagerWithMockBrowser() with pytest.raises(WindowNotFound) as context: manager.select("something=test1") - assert str(context.value) == "No window matching handle, name, title or URL 'something=test1' found." + assert ( + str(context.value) + == "No window matching handle, name, title or URL 'something=test1' found." + ) def test_select_by_title(self): manager = WindowManagerWithMockBrowser( @@ -104,7 +107,10 @@ def test_select_by_url_no_match(self): ) with pytest.raises(WindowNotFound) as context: manager.select("url=http://localhost/page-1.html") - assert str(context.value) == "Unable to locate window with URL 'http://localhost/page-1.html'." + assert ( + str(context.value) + == "Unable to locate window with URL 'http://localhost/page-1.html'." + ) def test_select_main_window(self): manager = WindowManagerWithMockBrowser( @@ -145,7 +151,10 @@ def test_select_by_default_no_match(self): ) with pytest.raises(WindowNotFound) as context: manager.select("foobar") - assert str(context.value) == "No window matching handle, name, title or URL 'foobar' found." + assert ( + str(context.value) + == "No window matching handle, name, title or URL 'foobar' found." + ) def test_prefix_is_case_sensitive(self): manager = WindowManagerWithMockBrowser( @@ -157,7 +166,10 @@ def test_prefix_is_case_sensitive(self): assert manager.driver.current_window.name == "win2" with pytest.raises(WindowNotFound) as context: manager.select("nAmE=win2") - assert str(context.value) == "No window matching handle, name, title or URL 'nAmE=win2' found." + assert ( + str(context.value) + == "No window matching handle, name, title or URL 'nAmE=win2' found." + ) def test_get_window_infos(self): manager = WindowManagerWithMockBrowser( @@ -165,10 +177,26 @@ def test_get_window_infos(self): {"id": "id2", "name": "win2", "title": "Title 2", "url": "http://url.2"}, {"name": "win3", "title": "Title 3", "url": "http://url.3"}, ) - assert [info.id for info in manager.get_window_infos()] == ["id1", "id2", "undefined"] - assert [info.name for info in manager.get_window_infos()] == ["win1", "win2", "win3"] - assert [info.title for info in manager.get_window_infos()] == ["Title 1", "Title 2", "Title 3"] - assert [info.url for info in manager.get_window_infos()] == ["http://url.1", "http://url.2", "http://url.3"] + assert [info.id for info in manager.get_window_infos()] == [ + "id1", + "id2", + "undefined", + ] + assert [info.name for info in manager.get_window_infos()] == [ + "win1", + "win2", + "win3", + ] + assert [info.title for info in manager.get_window_infos()] == [ + "Title 1", + "Title 2", + "Title 3", + ] + assert [info.url for info in manager.get_window_infos()] == [ + "http://url.1", + "http://url.2", + "http://url.3", + ] class WindowManagerWithMockBrowser(WindowManager): diff --git a/utest/test/robotframework_seleniumlibrary_translation_fi/__init__.py b/utest/test/robotframework_seleniumlibrary_translation_fi/__init__.py index 369b7c953..55f6e3a2d 100644 --- a/utest/test/robotframework_seleniumlibrary_translation_fi/__init__.py +++ b/utest/test/robotframework_seleniumlibrary_translation_fi/__init__.py @@ -3,7 +3,4 @@ def get_language() -> dict: curr_dir = Path(__file__).parent.absolute() - return { - "language": "fi", - "path": curr_dir / "translate.json" - } + return {"language": "fi", "path": curr_dir / "translate.json"} diff --git a/utest/test/robotframework_seleniumlibrary_translation_list/__init__.py b/utest/test/robotframework_seleniumlibrary_translation_list/__init__.py index 29129a9a9..a3758c18a 100644 --- a/utest/test/robotframework_seleniumlibrary_translation_list/__init__.py +++ b/utest/test/robotframework_seleniumlibrary_translation_list/__init__.py @@ -4,12 +4,6 @@ def get_language() -> list: curr_dir = Path(__file__).parent.absolute() return [ - { - "language": "eng", - "path": curr_dir / "translate1.json" - }, - { - "language": "swe", - "path": curr_dir / "translate2.json" - } + {"language": "eng", "path": curr_dir / "translate1.json"}, + {"language": "swe", "path": curr_dir / "translate2.json"}, ] diff --git a/utest/test/utils/test_package.py b/utest/test/utils/test_package.py index c5b0a4468..8fe174a84 100644 --- a/utest/test/utils/test_package.py +++ b/utest/test/utils/test_package.py @@ -11,4 +11,7 @@ def test_escape_xpath_value_with_quote(self): assert escape_xpath_value('test "1"') == "'test \"1\"'" def test_escape_xpath_value_with_quote_and_apos(self): - assert escape_xpath_value("test \"1\" and '2'") == "concat('test \"1\" and ', \"'\", '2', \"'\", '')" + assert ( + escape_xpath_value("test \"1\" and '2'") + == "concat('test \"1\" and ', \"'\", '2', \"'\", '')" + ) diff --git a/utest/test/utils/test_xpath_escape.py b/utest/test/utils/test_xpath_escape.py index 9ff51280b..1eda8f15c 100644 --- a/utest/test/utils/test_xpath_escape.py +++ b/utest/test/utils/test_xpath_escape.py @@ -5,7 +5,6 @@ from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from robot.utils import WINDOWS from SeleniumLibrary.utils import escape_xpath_value From c3cad3f27b5416c776aad459ffc75ffbfe58b25f Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 11:31:52 +0200 Subject: [PATCH 11/13] Mouse Over error Firefox known issue Co-authored-by: Copilot --- atest/acceptance/keywords/mouse.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/acceptance/keywords/mouse.robot b/atest/acceptance/keywords/mouse.robot index 5aff5c109..734eb2d01 100644 --- a/atest/acceptance/keywords/mouse.robot +++ b/atest/acceptance/keywords/mouse.robot @@ -15,7 +15,7 @@ Mouse Over ... Mouse Over not_there Mouse Over Error - [Tags] Known Issue Safari + [Tags] Known Issue Safari Known Issue Firefox Mouse Over el_for_mouseover Sleep 0.1secs Textfield Value Should Be el_for_mouseover mouseover el_for_mouseover From 02618143a4ef050c0f047167105af2adf05dfc52 Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 11:36:10 +0200 Subject: [PATCH 12/13] Update Ruff format check to include 'atest/' directory for comprehensive checks --- .github/workflows/LintFormatCheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/LintFormatCheck.yml b/.github/workflows/LintFormatCheck.yml index 3b49d92d8..5cc975027 100644 --- a/.github/workflows/LintFormatCheck.yml +++ b/.github/workflows/LintFormatCheck.yml @@ -21,7 +21,7 @@ jobs: - name: Ruff format check run: | - python -m ruff format --check src/ utest/ + python -m ruff format --check --diff src/ utest/ atest/ - name: Ruff lint run: | From da2f1b83c7046f3e736d0d362d5d85a82b90480c Mon Sep 17 00:00:00 2001 From: Yuri Verweij Date: Sun, 26 Apr 2026 12:39:52 +0200 Subject: [PATCH 13/13] Update linting and formatting setup to use latest actions and enhance commands Co-authored-by: Copilot --- .github/workflows/LintFormatCheck.yml | 21 +++++++++++++++++---- CONTRIBUTING.rst | 21 +++++++++++++++++---- requirements-dev.txt | 2 +- tasks.py | 25 +++++++++++++++++++++---- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/.github/workflows/LintFormatCheck.yml b/.github/workflows/LintFormatCheck.yml index 5cc975027..516ab75d8 100644 --- a/.github/workflows/LintFormatCheck.yml +++ b/.github/workflows/LintFormatCheck.yml @@ -7,10 +7,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" @@ -20,9 +20,22 @@ jobs: pip install -r requirements-dev.txt - name: Ruff format check + id: format + continue-on-error: true run: | - python -m ruff format --check --diff src/ utest/ atest/ + python -m invoke format --check - name: Ruff lint + id: lint + continue-on-error: true run: | - python -m invoke lint \ No newline at end of file + python -m invoke lint + + - name: Fail if any Ruff step failed + if: always() + run: | + echo "format outcome: ${{ steps.format.outcome }}" + echo "lint outcome: ${{ steps.lint.outcome }}" + if [ "${{ steps.format.outcome }}" != "success" ] || [ "${{ steps.lint.outcome }}" != "success" ]; then + exit 1 + fi \ No newline at end of file diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 401671781..ad8b13b4e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -118,10 +118,23 @@ needed in internal code. When docstrings are added, they should follow `PEP-257`_. See `Documentation`_ section below for more details about documentation syntax, generating docs, etc. -The code should be formatted and linted with `Ruff`_. Ruff can be run by -using command:: +The code should be formatted and linted with `Ruff`_. See Development commands below for more details. - inv lint +Development commands +~~~~~~~~~~~~~~~~~~~~ + +Use `invoke`_ tasks for common local checks and test runs:: + + inv format --check # Check formatting with Ruff + inv format # Format source files with Ruff + inv lint # Run Ruff lint checks + inv lint --fix # Apply safe Ruff lint fixes + inv utest # Run unit tests + inv atest # Run acceptance tests (headlesschrome) + +Run these before opening a pull request so local results are close to CI. +Use the project virtual environment and pinned dependencies from +``requirements-dev.txt`` for consistent results across local runs and CI. Documentation ------------- @@ -149,7 +162,7 @@ individual keywords. Keyword documentation can be easily created using `invoke`_ task:: - inv keyword_documentation + inv kw-docs Resulting docs should be verified before the code is committed. diff --git a/requirements-dev.txt b/requirements-dev.txt index 975e6fb6b..71bc8bb10 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,7 +15,7 @@ pytest-mockito == 0.0.4 pytest-approvaltests == 0.2.4 requests == 2.33.1 robotframework-pabot == 5.2.2 -ruff == 0.4.10 +ruff == 0.15.12 # Requirements needed when generating releases. See BUILD.rst for details. rellu == 0.7 diff --git a/tasks.py b/tasks.py index f3518a823..0f578fe37 100644 --- a/tasks.py +++ b/tasks.py @@ -187,11 +187,28 @@ def init_labels(ctx, username=None, password=None): @task def lint(ctx, fix=False): - """Runs Ruff format check and linter for project Python code.""" - ruff_cmd = f"{sys.executable} -m ruff check --config pyproject.toml src/ utest/" # atest/" + """Run Ruff lint checkse. + + Args: + fix: Apply safe fixes when True. Defaults to False. + """ + cmd = f"{sys.executable} -m ruff check --config pyproject.toml src/ utest/" # atest/" if fix: - ruff_cmd = f"{ruff_cmd} --fix" - ctx.run(ruff_cmd) + cmd = f"{cmd} --fix" + ctx.run(cmd) + +@task +def format(ctx, check=False): + """Run Ruff formatter. + + Args: + check: When True, only check formatting and show diff. + When False, apply formatting changes. + """ + cmd = f"{sys.executable} -m ruff format --config pyproject.toml src/ utest/ atest/" + if check: + cmd = f"{cmd} --check --diff" + ctx.run(cmd) @task def gen_stub(ctx):