Skip to content

Commit

Permalink
Merge pull request #15 from ScreenPyHQ/fix-typing
Browse files Browse the repository at this point in the history
Fix typing errors with Target, Enter.
  • Loading branch information
perrygoy committed May 22, 2024
2 parents d30cbcf + db040c6 commit 52d37a9
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 54 deletions.
61 changes: 30 additions & 31 deletions poetry.lock

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

5 changes: 4 additions & 1 deletion screenpy_playwright/actions/enter.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ def the_secret(cls, text: str, **kwargs: Unpack[EnterTypes]) -> Self:
"""
return cls(text, mask=True, **kwargs)

the_password = the_secret
@classmethod
def the_password(cls, text: str, **kwargs: Unpack[EnterTypes]) -> Self:
"""Alias for ``the_secret``, recreated for mypy."""
return cls.the_secret(text, **kwargs)

def into_the(self, target: Target, **kwargs: Unpack[EnterTypes]) -> Enter:
"""Target the element to enter text into.
Expand Down
35 changes: 29 additions & 6 deletions screenpy_playwright/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from collections import UserString
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Pattern, Tuple, TypedDict, Union

from playwright.sync_api import Locator

Expand All @@ -13,7 +13,26 @@

if TYPE_CHECKING:
from screenpy import Actor
from typing_extensions import Self
from typing_extensions import NotRequired, Self, Unpack

_ManipulationArgsType = Tuple[Union[str, int, None], ...]

class _ManipulationKwargsType(TypedDict):
"""Types for kwargs that are passed to Playwright's locator methods."""

has_text: NotRequired[str | Pattern[str] | None]
has_not_text: NotRequired[str | Pattern[str] | None]
has: NotRequired[Locator | None]
has_not: NotRequired[Locator | None]
exact: NotRequired[bool | None]
checked: NotRequired[bool | None]
disabled: NotRequired[bool | None]
expanded: NotRequired[bool | None]
include_hidden: NotRequired[bool | None]
level: NotRequired[int | None]
name: NotRequired[str | Pattern[str] | None]
pressed: NotRequired[bool | None]
selected: NotRequired[bool | None]


@dataclass
Expand All @@ -29,8 +48,8 @@ class _Manipulation(UserString):

target: Target
name: str
args: tuple | None = None
kwargs: dict | None = None
args: _ManipulationArgsType | None = None
kwargs: _ManipulationKwargsType | None = None

def __hash__(self) -> int:
"""Appear as the name, in case this is an attribute and not a method."""
Expand All @@ -44,7 +63,11 @@ def __getattr__(self, name: str) -> Target | _Manipulation:
"""Defer back to the Target for unknown attributes."""
return getattr(self.target, name)

def __call__(self, *args: str, **kwargs: str) -> Target:
def __call__(
self,
*args: Unpack[_ManipulationArgsType],
**kwargs: Unpack[_ManipulationKwargsType],
) -> Target:
"""Add args and kwargs to the manipulation."""
self.args = args
self.kwargs = kwargs
Expand Down Expand Up @@ -90,7 +113,7 @@ class Target:
)
# Using Playwright strategies directly
Target().frame_locator("#todoframe").get_by_label("todo")
Target("To-Do list").frame_locator("#todoframe").get_by_label("todo")
"""

manipulations: list[_Manipulation]
Expand Down
2 changes: 2 additions & 0 deletions tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ def test_can_be_instantiated(self) -> None:
e2 = Enter.the_text("")
e3 = Enter.the_secret("")
e4 = Enter.the_text("").into_the(TARGET)
e5 = Enter.the_password("").into_the(TARGET)

assert isinstance(e1, Enter)
assert isinstance(e2, Enter)
assert isinstance(e3, Enter)
assert isinstance(e4, Enter)
assert isinstance(e5, Enter)

def test_implements_protocol(self) -> None:
e = Enter("")
Expand Down
48 changes: 32 additions & 16 deletions tests/test_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,24 @@
if TYPE_CHECKING:
from screenpy import Actor

from screenpy_playwright.target import (
_ManipulationArgsType,
_ManipulationKwargsType,
)


class Test_Manipulations:
def test_proper_display(self) -> None:
name = "viking"
args = ("spam", "eggs")
kwargs = {"sausage": "spam"}
args_str = "'spam', 'eggs'"
kwargs_str = "sausage='spam'"
args: _ManipulationArgsType = ("spam", "eggs", 1, None)
kwargs: _ManipulationKwargsType = {
"has_text": "spam",
"exact": True,
"include_hidden": None,
"level": 1,
}
args_str = "'spam', 'eggs', 1, None"
kwargs_str = "has_text='spam', exact=True, include_hidden=None, level=1"

m_for_attribute = _Manipulation(Target(), name)
m_with_neither = _Manipulation(Target(), name, (), {})
Expand Down Expand Up @@ -46,12 +56,14 @@ def test_can_be_instantiated(self) -> None:
t3 = Target("test")
t4 = Target().located_by("test")
t5 = Target()
t6 = Target("test").get_by_label("test", exact=True)

assert isinstance(t1, Target)
assert isinstance(t2, Target)
assert isinstance(t3, Target)
assert isinstance(t4, Target)
assert isinstance(t5, Target)
assert isinstance(t6, Target)

def test_auto_describe(self) -> None:
t1 = Target().located_by("#yellow")
Expand Down Expand Up @@ -96,29 +108,33 @@ def test_found_by_with_frames(self, Tester: Actor) -> None:

# list from https://playwright.dev/python/docs/locators
@pytest.mark.parametrize(
"strategy",
("strategy", "args", "kwargs"),
[
"get_by_role",
"get_by_text",
"get_by_label",
"get_by_placeholder",
"get_by_alt_text",
"get_by_title",
"get_by_test_id",
("get_by_role", ("button",), {}),
("get_by_text", ("Log In",), {"exact": True}),
("get_by_label", ("spam",), {"level": 1, "exact": None}),
("get_by_placeholder", ("eggs",), {}),
("get_by_alt_text", ("sausage",), {}),
("get_by_title", ("baked beans",), {}),
("get_by_test_id", ("spam",), {}),
("nth", (1,), {}),
],
)
def test_found_by_with_playwright_strategies(
self, Tester: Actor, strategy: str
self,
Tester: Actor,
strategy: str,
args: _ManipulationArgsType,
kwargs: _ManipulationKwargsType,
) -> None:
test_value = "Eeuugh!"
mocked_btws = Tester.ability_to(BrowseTheWebSynchronously)
mocked_btws.current_page = mock.Mock()

target = Target.the("test")
getattr(target, strategy)(test_value).found_by(Tester)
getattr(target, strategy)(*args, **kwargs).found_by(Tester)

func = getattr(mocked_btws.current_page.locator("html"), strategy)
func.assert_called_once_with(test_value)
func.assert_called_once_with(*args, **kwargs)

def test_found_by_chain(self, Tester: Actor) -> None:
test_locator = "#spam>baked-beans>eggs>sausage+spam"
Expand Down

0 comments on commit 52d37a9

Please sign in to comment.