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/.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/.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/.github/workflows/LintFormatCheck.yml b/.github/workflows/LintFormatCheck.yml new file mode 100644 index 000000000..516ab75d8 --- /dev/null +++ b/.github/workflows/LintFormatCheck.yml @@ -0,0 +1,41 @@ +name: Lint and Format Check + +on: [push, pull_request] + +jobs: + ruff: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + 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 + id: format + continue-on-error: true + run: | + python -m invoke format --check + + - name: Ruff lint + id: lint + continue-on-error: true + run: | + 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/.github/workflows/Select.yml b/.github/workflows/Select.yml index dcb89c7e4..135570e51 100644 --- a/.github/workflows/Select.yml +++ b/.github/workflows/Select.yml @@ -24,19 +24,23 @@ 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 + - 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 @@ -74,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 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ffb9d6ff8..ad8b13b4e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -118,21 +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 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`_. See Development commands below for more details. - inv lint +Development commands +~~~~~~~~~~~~~~~~~~~~ -By default flake8 ignores line length error E501, but it does not ignore -warning W503. In practice Black formats list access like this:: +Use `invoke`_ tasks for common local checks and test runs:: - list[1 : 2] + 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) -But flake8 will display an warning about it. This should be manually -fixed to look like:: - - list[1:2] +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 ------------- @@ -160,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. @@ -245,5 +247,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/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/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 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 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/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..6a8f881ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,51 @@ -[tool.black] -target-version = ['py36'] -exclude = ''' -/( - | \.git - | \.venv - | _build - | dist - | generated - | src/SeleniumLibrary/__init__\.pyi -)/ -''' +[tool.ruff] +target-version = "py310" +line-length = 88 +exclude = [ + "src/SeleniumLibrary/__init__.pyi", +] + +[tool.ruff.lint] +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 + "PLR2004", # Magic value used in comparison + "DTZ006", # No timezone specified + "PTH", # Use Path instead of os.path -> maybe soon + "N818", # exception naming convention +] -[tool.isort] -profile = "black" -src_paths="." -skip_glob = ["src/SeleniumLibrary/__init__.pyi"] +[tool.ruff.format] +quote-style = "double" [tool.pytest.ini_options] diff --git a/requirements-dev.txt b/requirements-dev.txt index 90b82779d..71bc8bb10 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.15.12 # 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/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index f371f3b1d..bdddc45e7 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. @@ -807,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") @@ -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..9484360e1 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,11 @@ 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..102695317 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``. @@ -212,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, @@ -275,7 +286,11 @@ 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 +329,10 @@ 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 +376,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 +402,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 +446,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 +459,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 +511,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 @@ -654,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/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..c74bb1d5a 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 @@ -287,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: Optional[str] = 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 @@ -311,7 +308,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 +329,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 +365,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``. @@ -399,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 @@ -417,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}. @@ -435,7 +428,9 @@ 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 +448,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 +489,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 @@ -582,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: Union[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 @@ -606,9 +599,7 @@ def click_button( self._click_with_modifier(locator, ["button", "input"], modifier) @keyword - def click_image( - self, locator: Locator, modifier: Union[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 @@ -631,9 +622,7 @@ def click_image( self._click_with_modifier(locator, ["image", "input"], modifier) @keyword - def click_link( - self, locator: Locator, modifier: Union[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 @@ -655,7 +644,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``. @@ -775,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 @@ -796,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 @@ -870,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): @@ -989,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 @@ -1025,7 +1016,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 +1040,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 +1058,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 +1088,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 +1106,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 +1163,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 +1182,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 +1190,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 +1216,13 @@ 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,24 +1255,30 @@ 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): 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. @@ -1293,4 +1291,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..016d239a4 100644 --- a/src/SeleniumLibrary/keywords/expectedconditions.py +++ b/src/SeleniumLibrary/keywords/expectedconditions.py @@ -12,16 +12,19 @@ # 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 +54,15 @@ 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..cbf774190 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. @@ -239,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 @@ -269,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 @@ -300,7 +295,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 +312,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 +330,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 +354,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 +378,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 +402,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 +425,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 +446,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 +483,10 @@ 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." - ) + f"No radio button with name '{group_name}' 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..7166ad573 100644 --- a/src/SeleniumLibrary/keywords/javascript.py +++ b/src/SeleniumLibrary/keywords/javascript.py @@ -15,17 +15,14 @@ # 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 class JavaScriptKeywords(LibraryComponent): - js_marker = "JAVASCRIPT" arg_marker = "ARGUMENTS" @@ -130,11 +127,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 +146,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..98f454578 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,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 6963b4e28..4a4c7da0e 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,24 +262,25 @@ def _embed_to_log_as_file(self, path, width): f'', html=True, ) - + @keyword - def print_page_as_pdf(self, - 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, - # 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. @@ -304,11 +304,11 @@ def print_page_as_pdf(self, """ 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: @@ -332,7 +332,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,13 +340,13 @@ 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 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 68290f1fd..24b45978f 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 @@ -25,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 @@ -46,8 +42,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 +58,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 +84,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 +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)) + 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. @@ -293,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 @@ -320,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. @@ -345,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/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index b8bb9473e..2d6cfe81e 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,12 +243,13 @@ 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, error, ) + return self._wait_until( lambda: len(self.find_elements(locator)) == limit, f'Page should have contained "{limit}" {locator} element(s) within .', @@ -261,9 +261,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,12 +282,13 @@ 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, error, ) + return self._wait_until( lambda: len(self.find_elements(locator)) != limit, f'Page should have not contained "{limit}" {locator} element(s) within .', @@ -299,8 +300,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 +323,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 +346,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 +375,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 +399,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..695717f77 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 @@ -40,22 +39,20 @@ 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 +77,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 +131,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} + if "log_output" in str(sig): + return {"log_output": service_log_path} + return {"log_path": service_log_path} # -- def create_chrome( @@ -159,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) @@ -179,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): @@ -215,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( @@ -263,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, @@ -295,7 +298,7 @@ def create_ie( return webdriver.Ie( options=options, service=service, - #**desired_capabilities, + # **desired_capabilities, ) def _has_options(self, web_driver): @@ -323,7 +326,7 @@ def create_edge( return webdriver.Edge( options=options, service=service, - #**desired_capabilities, + # **desired_capabilities, ) def create_safari( @@ -461,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 @@ -478,10 +481,13 @@ 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") - selenium_service_inst = selenium_service(**attrs) - return selenium_service_inst + 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): """The service argument parses slightly different than the options argument. As of @@ -489,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) - except (ValueError, SyntaxError): - raise ValueError(f'Unable to parse service: "{item}"') + 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 return result def _import_service(self, browser): @@ -514,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: @@ -525,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): @@ -575,8 +586,10 @@ 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..e0393cbe9 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,17 +115,17 @@ 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) @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 - 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..8244c8236 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,16 @@ 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..7440fe29d 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..e12657d53 100644 --- a/src/SeleniumLibrary/utils/types.py +++ b/src/SeleniumLibrary/utils/types.py @@ -17,26 +17,25 @@ 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" -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" + 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 84c093b98..0f578fe37 100644 --- a/tasks.py +++ b/tasks.py @@ -186,11 +186,29 @@ 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/") +def lint(ctx, fix=False): + """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: + 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): @@ -207,12 +225,12 @@ 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 """ - 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/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/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/my_lib.py b/utest/test/api/my_lib.py index b5c45c932..3dc65ee95 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,6 @@ 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..572bd2abc 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,6 @@ 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..ec4c37a27 100644 --- a/utest/test/api/my_lib_not_inherit.py +++ b/utest/test/api/my_lib_not_inherit.py @@ -1,10 +1,13 @@ 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..39480f1b6 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,6 @@ 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..d90c34ab7 100644 --- a/utest/test/api/plugin_with_event_firing_webdriver.py +++ b/utest/test/api/plugin_with_event_firing_webdriver.py @@ -1,9 +1,8 @@ from SeleniumLibrary.base import LibraryComponent, keyword -class plugin_with_event_firing_webdriver(LibraryComponent): - - """This is example for plugin_with_event_firing_webdriver plugin documentation. +class PluginWithEventFiringWebdriver(LibraryComponent): + """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 +10,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 +31,6 @@ 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 9576a7f4c..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 @@ -9,19 +11,19 @@ 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): + 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): - 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..d9c03ef1d 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 @@ -19,13 +20,18 @@ 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") - 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_filepath_unusual_characters.py b/utest/test/api/test_filepath_unusual_characters.py index fdfc5fa9d..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 @@ -21,7 +20,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..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 @@ -30,19 +29,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/api/test_plugin_keyword_tags.py b/utest/test/api/test_plugin_keyword_tags.py index 368f67783..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): @@ -16,33 +17,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"]) + 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")) - self.assertEqual(sl._plugin_keywords, ["add_cookie", "bar_2", "foo_1"]) + sl._store_plugin_keywords(MyLibArgs("000", "111", "222")) + 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..317cc6dd5 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,12 @@ 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={} ) @@ -22,75 +28,75 @@ 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(",", maxsplit=1)[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 == 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") - 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 +114,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): @@ -117,12 +123,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/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..c74852517 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): @@ -135,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 @@ -172,7 +172,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,10 +205,10 @@ 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" + 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 @@ -265,7 +265,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..d51d23c0b 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" @@ -24,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 @@ -33,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) @@ -72,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" @@ -92,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) @@ -247,5 +249,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..c96c32c49 100644 --- a/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py +++ b/utest/test/keywords/IGNOREtest_webdrivercreator_service_log_path.py @@ -1,25 +1,26 @@ 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) @@ -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,14 +93,15 @@ 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 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) @@ -162,18 +168,20 @@ 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 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( - 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 @@ -197,4 +205,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..5b9e03213 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(): @@ -44,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" @@ -59,10 +58,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") @@ -77,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(): @@ -106,23 +107,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") @@ -142,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) @@ -157,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 @@ -177,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 @@ -191,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, @@ -199,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, @@ -210,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 @@ -219,16 +218,16 @@ def test_create_webdriver_speed(): # E Stubbed invocations are: # E # E Chrome(options=None, service=) - #which does seem closer .. - - #Tried: + # which does seem closer .. + + # 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( @@ -237,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_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..d6052e08c 100644 --- a/utest/test/keywords/test_expectedconditions.py +++ b/utest/test/keywords/test_expectedconditions.py @@ -20,12 +20,13 @@ # 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..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 @@ -27,7 +26,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")') @@ -67,10 +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 - 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 cd748e785..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) - self.assertEqual(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) - self.assertEqual(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_javascript.py b/utest/test/keywords/test_javascript.py index 1b4b469c5..2e35bae4e 100644 --- a/utest/test/keywords/test_javascript.py +++ b/utest/test/keywords/test_javascript.py @@ -5,14 +5,12 @@ from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from robot.utils import WINDOWS from SeleniumLibrary.keywords import JavaScriptKeywords class JavaScriptKeywordsTest(unittest.TestCase): @classmethod - @unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") def setUpClass(cls): cls.code_examples = [ (), @@ -40,13 +38,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 +51,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_keyword_arguments_browsermanagement.py b/utest/test/keywords/test_keyword_arguments_browsermanagement.py index 0d730878a..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) - self.assertEqual(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) - self.assertEqual(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..d6662e40a 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() @@ -30,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() @@ -40,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_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..ebea52ff6 100644 --- a/utest/test/keywords/test_press_keys.py +++ b/utest/test/keywords/test_press_keys.py @@ -1,11 +1,10 @@ -import unittest import os +import unittest from approvaltests.approvals import verify_all from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from robot.utils import WINDOWS from SeleniumLibrary.keywords import ElementKeywords @@ -24,7 +23,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 +39,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 +48,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 +61,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_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..128d98230 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 @@ -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 b61fff029..77a3ed9a2 100644 --- a/utest/test/keywords/test_selenium_options_parser.py +++ b/utest/test/keywords/test_selenium_options_parser.py @@ -1,14 +1,12 @@ import os import sys -import unittest import pytest from approvaltests.approvals import verify_all from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from mockito import mock, when, unstub, ANY -from robot.utils import WINDOWS +from mockito import ANY, mock, unstub, when 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__) @@ -33,7 +32,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 +70,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 +79,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 +88,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 +98,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 +110,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 +122,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 +135,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 +166,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 +173,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")) @@ -202,7 +191,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 +204,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): @@ -227,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 @@ -235,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, @@ -254,21 +243,22 @@ 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 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() 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, @@ -297,14 +287,14 @@ 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() 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, @@ -317,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 @@ -344,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 @@ -377,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 +379,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() @@ -397,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 095a8c2c2..309b2203e 100644 --- a/utest/test/keywords/test_selenium_service_parser.py +++ b/utest/test/keywords/test_selenium_service_parser.py @@ -1,23 +1,21 @@ import os import sys -import unittest import pytest from approvaltests.approvals import verify_all from approvaltests.reporters.generic_diff_reporter_factory import ( GenericDiffReporterFactory, ) -from mockito import mock, when, unstub, ANY -from robot.utils import WINDOWS -from selenium import webdriver +from mockito import unstub -from SeleniumLibrary.keywords.webdrivertools import SeleniumService, WebDriverCreator +from SeleniumLibrary.keywords.webdrivertools import SeleniumService @pytest.fixture(scope="module") def service(): return SeleniumService() + @pytest.fixture(scope="module") def reporter(): path = os.path.dirname(__file__) @@ -33,30 +31,24 @@ 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"')) # 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) -# @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 = [] @@ -65,11 +57,12 @@ 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) -@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 = [] @@ -78,30 +71,31 @@ 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) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") 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) -@unittest.skipIf(WINDOWS, reason="ApprovalTest do not support different line feeds") 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 + ) -@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']" @@ -119,7 +113,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")) @@ -138,4 +131,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 f138565f2..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: - self.assertEqual(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() @@ -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 is None index = cache.get_index("None") - self.assertEqual(index, None) + assert index is 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 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") - self.assertEqual(index, None) + assert index is None index = cache.get_index(12) - self.assertEqual(index, None) + assert index is None index = cache.get_index(-1) - self.assertEqual(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") - self.assertEqual(index, None) + assert index is None def test_same_alias_new_browser(self): cache = WebDriverCache() cache.close() index = cache.get_index("bar") - self.assertEqual(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,10 +217,10 @@ 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() - 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..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" @@ -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] self.mock_window_info(False, "", "", "") info = self.manager._get_current_window_info() - self.assertEqual(info[1], False) + assert not info[1] 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_elementfinder.py b/utest/test/locators/test_elementfinder.py index 50472be20..21792ea33 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): @@ -283,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 @@ -655,7 +656,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 f40770ae4..393faad60 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,11 +11,11 @@ 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") - self.assertEqual( - 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): @@ -24,7 +25,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 +42,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( @@ -49,11 +50,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") - self.assertEqual( - 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( @@ -62,7 +61,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( @@ -70,11 +69,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") - self.assertEqual( - 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( @@ -83,7 +80,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 +97,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( @@ -108,11 +105,11 @@ 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") - self.assertEqual( - 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): @@ -122,11 +119,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 +132,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 +141,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( @@ -152,11 +149,11 @@ 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") - self.assertEqual( - 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): @@ -166,12 +163,12 @@ 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") - with self.assertRaises(WindowNotFound) as context: + assert manager.driver.current_window.name == "win2" + with pytest.raises(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.value) + == "No window matching handle, name, title or URL 'nAmE=win2' found." ) def test_get_window_infos(self): @@ -180,21 +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"}, ) - 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): @@ -238,6 +240,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_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 4ccfe3f24..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" - } - ] \ No newline at end of file + {"language": "eng", "path": curr_dir / "translate1.json"}, + {"language": "swe", "path": curr_dir / "translate2.json"}, + ] 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_package.py b/utest/test/utils/test_package.py index 2043421f7..8fe174a84 100644 --- a/utest/test/utils/test_package.py +++ b/utest/test/utils/test_package.py @@ -5,13 +5,13 @@ 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', \"'\", '')" ) 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 diff --git a/utest/test/utils/test_xpath_escape.py b/utest/test/utils/test_xpath_escape.py index 5f1defae7..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 @@ -21,7 +20,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"))