Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .flake8

This file was deleted.

12 changes: 6 additions & 6 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
18 changes: 3 additions & 15 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,11 @@ needed in internal code. When docstrings are added, they should follow
`PEP-257`_. See `Documentation`_ section below for more details about
documentation syntax, generating docs, etc.

The code should be formatted with `Black`_ and errors found by `flake8`_
should be fixed. Black and flake8 can be run by using
command::
The code should be formatted and linted with `Ruff`_. Ruff can be run by
using command::

inv lint

By default flake8 ignores line length error E501, but it does not ignore
warning W503. In practice Black formats list access like this::

list[1 : 2]

But flake8 will display an warning about it. This should be manually
fixed to look like::

list[1:2]

Documentation
-------------

Expand Down Expand Up @@ -245,5 +234,4 @@ the same code as your changes. In that case you should
.. _utest/README.rst: https://github.com/robotframework/SeleniumLibrary/blob/master/utest/README.rst
.. _sync your fork: https://help.github.com/articles/syncing-a-fork/
.. _resolve conflicts: https://help.github.com/articles/resolving-a-merge-conflict-from-the-command-line
.. _Black: https://github.com/psf/black
.. _flake8: https://github.com/PyCQA/flake8
.. _Ruff: https://github.com/astral-sh/ruff
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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_.

Expand Down
2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ <h2><a class="toc-backref" href="#toc-entry-1" role="doc-backlink">Introduction<
<p><a class="reference external" href="https://github.com/robotframework/SeleniumLibrary">SeleniumLibrary</a> is a web testing library for <a class="reference external" href="https://robotframework.org">Robot Framework</a> that
utilizes the <a class="reference external" href="https://www.seleniumhq.org/">Selenium</a> tool internally. The project is hosted on <a class="reference external" href="https://github.com/robotframework/SeleniumLibrary">GitHub</a>
and downloads can be found from <a class="reference external" href="https://pypi.python.org/pypi/robotframework-seleniumlibrary">PyPI</a>.</p>
<p>SeleniumLibrary currently works with Selenium 4. It supports Python 3.8 through 3.13.
<p>SeleniumLibrary currently works with Selenium 4. It supports Python 3.10 through 3.13.
In addition to the normal <a class="reference external" href="https://python.org">Python</a> interpreter, it works also
with <a class="reference external" href="https://pypy.org">PyPy</a>.</p>
<p>SeleniumLibrary is based on the &quot;old SeleniumLibrary&quot; that was forked to
Expand Down
62 changes: 46 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
[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
"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]
Expand Down
3 changes: 1 addition & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ pytest-mockito == 0.0.4
pytest-approvaltests == 0.2.4
requests == 2.33.1
robotframework-pabot == 5.2.2
black == 26.3.1
flake8 == 6.1.0
ruff == 0.4.10

# Requirements needed when generating releases. See BUILD.rst for details.
rellu == 0.7
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
53 changes: 31 additions & 22 deletions src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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__,
)
Expand Down Expand Up @@ -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`.

Expand All @@ -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.
Expand Down Expand Up @@ -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 = []
Expand All @@ -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):
Expand All @@ -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 = {
Expand Down
10 changes: 5 additions & 5 deletions src/SeleniumLibrary/base/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand All @@ -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

Expand Down
Loading
Loading