Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ dev = [
"ruff>=0.2.0",
"robotframework-robocop>=6",
"lxml>=4.9.3",
"lxml-stubs>=0.4.0",
"pytest-xdist>=3.5.0",
"pytest-github-actions-annotate-failures>=0.2.0",
"robotcode[analyze]>=0.97.0",
Expand All @@ -43,6 +42,7 @@ dev = [
# versions. see https://github.com/astral-sh/uv/issues/16650#issuecomment-3508516822
"pytest>=7a0",
"robotframework>=6.1a0",
"types-lxml>=2026.2.16",
]

[project.urls]
Expand Down
57 changes: 36 additions & 21 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
from os import PathLike

from _typeshed import StrPath
from lxml.etree import (
_AnyStr, # pyright: ignore[reportPrivateUsage]
_NonDefaultNSMapArg, # pyright: ignore[reportPrivateUsage]
_XPathObject, # pyright: ignore[reportPrivateUsage]
from lxml._types import ( # pyright:ignore[reportMissingModuleSource] https://github.com/DetachHead/basedpyright/issues/615
_TextArg, # pyright: ignore[reportPrivateUsage]
_XPathExtFuncArg, # pyright: ignore[reportPrivateUsage]
_XPathNSArg, # pyright: ignore[reportPrivateUsage]
_XPathVarArg, # pyright: ignore[reportPrivateUsage]
)
from typing_extensions import Never

Expand Down Expand Up @@ -133,7 +134,8 @@ def _is_dunder(name: str) -> bool:
return len(name) > 4 and name[:2] == name[-2:] == "__" and name[2] != "_" and name[-3] != "_"


def _is_element_list(xpath_object: _XPathObject) -> TypeGuard[list[_Element]]:
# ideally should be _XPathObject, see https://github.com/abelcheung/types-lxml/issues/125
def _is_element_list(xpath_object: object) -> TypeGuard[list[_Element]]:
if isinstance(xpath_object, list):
if xpath_object:
return isinstance(xpath_object[0], _Element)
Expand All @@ -149,7 +151,7 @@ def __init__(self, element: _Element) -> None:

@override
def __getattribute__(self, /, name: str) -> object:
if _is_dunder(name) or name not in vars(_Element):
if _is_dunder(name) or name in vars(_XmlElement) or name in vars(self):
return super().__getattribute__(name) # pyright:ignore[reportAny]
return getattr(self._proxied, name) # pyright:ignore[reportAny]

Expand All @@ -169,17 +171,28 @@ def __iter__(self) -> Iterator[_XmlElement]:

def xpath(
self,
_path: _AnyStr,
namespaces: _NonDefaultNSMapArg | None = None,
extensions: object = ...,
_path: _TextArg,
namespaces: _XPathNSArg | None = None,
extensions: _XPathExtFuncArg | None = None,
*,
smart_strings: bool = True,
**_variables: _XPathObject,
) -> _XPathObject:
result = self._proxied.xpath(_path, namespaces, extensions, smart_strings, **_variables)
**_variables: _XPathVarArg,
):
# https://github.com/abelcheung/types-lxml/issues/125
result = cast(
object,
self._proxied.xpath(
_path,
namespaces=namespaces,
extensions=extensions,
smart_strings=smart_strings,
**_variables,
),
)
if _is_element_list(result):
# variance moment, but we aren't storing the value anywhere so it's fine
return [_XmlElement(element) for element in result] # pyright:ignore[reportReturnType] # ty:ignore[invalid-return-type]
# once the issue linked above resolved, a variance related type error will probably
# appear here, but we aren't storing the value anywhere it's fine to ignore.
return [_XmlElement(element) for element in result]
return result

def count_children(self) -> int:
Expand All @@ -194,13 +207,14 @@ class XmlElement(_Element):
behavior
"""

def __init__(self, element: _Element) -> None: ...
# https://github.com/DetachHead/basedpyright/issues/615
def __init__(self, element: _Element) -> None: ... # pyright:ignore[reportMissingSuperCall]

def __bool__(self) -> Literal[True]: # pyright:ignore[reportReturnType]
def __bool__(self) -> Literal[True]: # pyright:ignore[reportReturnType] see issue above
"""normally this returns `True` only if it has children"""

@override
def __len__(self) -> Never: # pyright:ignore[reportReturnType]
def __len__(self) -> Never: # pyright:ignore[reportReturnType] see issue above
"""
normally this returns how many children it has. but if you want to check than then
call `count_children` instead
Expand All @@ -217,11 +231,12 @@ def output_xml() -> XmlElement:


def xpath(xml: _Element, query: str) -> XmlElement:
results = xml.xpath(query)
# https://github.com/abelcheung/types-lxml/issues/125
results = cast(object, xml.xpath(query))
assert isinstance(results, list)
(result,) = results
assert isinstance(result, _Element)
return XmlElement(result)
(result,) = results # pyright:ignore[reportUnknownVariableType] see issue above
assert isinstance(result, XmlElement)
return result


def assert_robot_total_stats(*, passed: int = 0, skipped: int = 0, failed: int = 0):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ def test_error_moment_exitonerror_multiple_tests(pr: PytestRobotTester):
)
assert (
bool(
xml.xpath(
xml.xpath( # pyright:ignore[reportAny] https://github.com/abelcheung/types-lxml/issues/125
".//test[@name='test_bar']/status[@status='FAIL' and .='Error occurred"
" and exit-on-error mode is in use.']"
)
Expand Down
13 changes: 7 additions & 6 deletions tests/test_robot.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def test_tags(pr: PytestRobotTester):


def test_tags_in_settings(pr: PytestRobotTester):
pr.run_and_assert_result("-m", "m1", passed=2)
pr.run_and_assert_result("-m", "m1", passed=2, subprocess=True)
pr.assert_log_file_exists()
xml = output_xml()
assert xml.xpath(".//test[@name='Foo']/tag[.='m1']")
Expand Down Expand Up @@ -218,9 +218,7 @@ def test_run_keyword_and_ignore_error(pr: PytestRobotTester):
def test_init_file(pr: PytestRobotTester):
pr.run_and_assert_result(passed=1)
pr.assert_log_file_exists()
assert cast(str, xpath(output_xml(), "/robot/suite").attrib["name"]).startswith(
"Test Init File"
)
assert xpath(output_xml(), "/robot/suite").attrib["name"].startswith("Test Init File")


def test_init_file_nested(pr: PytestRobotTester):
Expand Down Expand Up @@ -347,6 +345,9 @@ def test_pass_execution(pr: PytestRobotTester):
def test_keyword_called_twice_in_robot_test(pr: PytestRobotTester):
pr.run_and_assert_result(passed=1)
pr.assert_log_file_exists()
results = output_xml().xpath("//kw[@name='Run Test']/kw[@name='Bar']/kw[@name='Foo']")
# https://github.com/abelcheung/types-lxml/issues/125
results = cast(
object, output_xml().xpath("//kw[@name='Run Test']/kw[@name='Bar']/kw[@name='Foo']")
)
assert isinstance(results, list)
assert len(results) == 2
assert len(results) == 2 # pyright:ignore[reportUnknownArgumentType] see issue above
59 changes: 47 additions & 12 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading