Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolvables #53

Merged
merged 6 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
16 changes: 9 additions & 7 deletions screenpy_selenium/resolutions/is_clickable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@

from typing import TYPE_CHECKING

from screenpy.resolutions.base_resolution import BaseResolution
from screenpy import beat

from .custom_matchers import is_clickable_element

if TYPE_CHECKING:
from .custom_matchers.is_clickable_element import IsClickableElement


class IsClickable(BaseResolution):
class IsClickable:
"""Match on a clickable element.

Examples::

the_actor.should(See.the(Element(LOGIN_BUTTON), IsClickable()))
"""

matcher: IsClickableElement
line = "clickable"
matcher_function = is_clickable_element
def describe(self) -> str:
"""Describe the Resolution's expectation."""
return "clickable"

def __init__(self) -> None: # pylint: disable=useless-super-delegation
super().__init__()
@beat("... hoping it's clickable")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these need punctuation at the end, i.e.

@beat("... hoping it's clickable.")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. I also somehow missed adding two tests for confirming these messages.

def resolve(self) -> IsClickableElement:
"""Produce the Matcher to make the assertion."""
return is_clickable_element()
16 changes: 9 additions & 7 deletions screenpy_selenium/resolutions/is_invisible.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@

from typing import TYPE_CHECKING

from screenpy.resolutions.base_resolution import BaseResolution
from screenpy import beat

from .custom_matchers import is_invisible_element

if TYPE_CHECKING:
from .custom_matchers.is_invisible_element import IsInvisibleElement


class IsInvisible(BaseResolution):
class IsInvisible:
"""Match on an invisible element.

Examples::

the_actor.should(See.the(Element(WELCOME_BANNER), IsInvisible()))
"""

matcher: IsInvisibleElement
line = "invisible"
matcher_function = is_invisible_element
def describe(self) -> str:
"""Describe the Resolution's expectation."""
return "invisible"

def __init__(self) -> None: # pylint: disable=useless-super-delegation
super().__init__()
@beat("... hoping it's invisible")
def resolve(self) -> IsInvisibleElement:
"""Produce the Matcher to make the assertion."""
return is_invisible_element()
16 changes: 9 additions & 7 deletions screenpy_selenium/resolutions/is_present.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

from typing import TYPE_CHECKING

from screenpy.resolutions.base_resolution import BaseResolution
from screenpy import beat

from .custom_matchers import is_present_element

if TYPE_CHECKING:
from .custom_matchers.is_present_element import IsPresentElement


class IsPresent(BaseResolution):
class IsPresent:
"""Match on a present element.

Examples::
Expand All @@ -22,9 +22,11 @@ class IsPresent(BaseResolution):
the_actor.should(See.the(Element(BUTTON), DoesNot(Exist())))
"""

matcher: IsPresentElement
line = "present"
matcher_function = is_present_element
def describe(self) -> str:
"""Describe the Resolution's expectation."""
return "present"

def __init__(self) -> None: # pylint: disable=useless-super-delegation
super().__init__()
@beat("... hoping it's present")
def resolve(self) -> IsPresentElement:
"""Produce the Matcher to make the assertion."""
return is_present_element()
16 changes: 9 additions & 7 deletions screenpy_selenium/resolutions/is_visible.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@

from typing import TYPE_CHECKING

from screenpy.resolutions.base_resolution import BaseResolution
from screenpy import beat

from .custom_matchers import is_visible_element

if TYPE_CHECKING:
from .custom_matchers.is_visible_element import IsVisibleElement


class IsVisible(BaseResolution):
class IsVisible:
"""Match on a visible element.

Examples::

the_actor.should(See.the(Element(WELCOME_BANNER), IsVisible()))
"""

matcher: IsVisibleElement
line = "visible"
matcher_function = is_visible_element
def describe(self) -> str:
"""Describe the Resolution's expectation."""
return "visible"

def __init__(self) -> None: # pylint: disable=useless-super-delegation
super().__init__()
@beat("... hoping it's visible")
def resolve(self) -> IsVisibleElement:
"""Produce the Matcher to make the assertion."""
return is_visible_element()
83 changes: 56 additions & 27 deletions tests/test_resolutions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

import pytest
from hamcrest.core.string_description import StringDescription
Expand All @@ -23,7 +24,7 @@
from .useful_mocks import get_mocked_element

if TYPE_CHECKING:
from screenpy import BaseResolution
from hamcrest.core.matcher import Matcher
from selenium.webdriver.remote.webelement import WebElement


Expand All @@ -36,7 +37,7 @@ class ExpectedDescriptions:


def _assert_descriptions(
obj: BaseResolution, element: WebElement, expected: ExpectedDescriptions
obj: Matcher[Any], element: WebElement, expected: ExpectedDescriptions
) -> None:
describe_to = StringDescription()
describe_match = StringDescription()
Expand Down Expand Up @@ -64,7 +65,7 @@ def test_matches_a_clickable_element(self) -> None:
element = get_mocked_element()
element.is_enabled.return_value = True
element.is_displayed.return_value = True
ic = IsClickable()
ic = IsClickable().resolve()

assert ic._matches(element)

Expand All @@ -76,7 +77,7 @@ def test_does_not_match_unclickable_element(self) -> None:
invisible_element.is_enabled.return_value = True
inactive_element.is_displayed.return_value = True
inactive_element.is_enabled.return_value = False
ic = IsClickable()
ic = IsClickable().resolve()

assert not ic._matches(None) # element was not found by Element()
assert not ic._matches(invisible_element)
Expand All @@ -90,14 +91,25 @@ def test_descriptions(self) -> None:
describe_mismatch="was not enabled/clickable",
describe_none="was not even present",
)
ic = IsClickable()

_assert_descriptions(IsClickable(), element, expected)
assert ic.describe() == "clickable"
_assert_descriptions(ic.resolve(), element, expected)

def test_type_hint(self) -> None:
ic = IsClickable()
annotation = ic.__annotations__["matcher"]
annotation = ic.resolve.__annotations__["return"]
assert annotation == "IsClickableElement"
assert type(ic.matcher) == IsClickableElement
assert type(ic.resolve()) == IsClickableElement

def test_beat_logging(self, caplog: pytest.LogCaptureFixture) -> None:
caplog.set_level(logging.INFO)
IsClickable().resolve()

assert [r.msg for r in caplog.records] == [
"... hoping it's clickable",
" => the element is enabled/clickable",
]


class TestIsVisible:
Expand All @@ -109,14 +121,14 @@ def test_can_be_instantiated(self) -> None:
def test_matches_a_visible_element(self) -> None:
element = get_mocked_element()
element.is_displayed.return_value = True
iv = IsVisible()
iv = IsVisible().resolve()

assert iv._matches(element)

def test_does_not_match_invisible_element(self) -> None:
element = get_mocked_element()
element.is_displayed.return_value = False
iv = IsVisible()
iv = IsVisible().resolve()

assert not iv._matches(None) # element was not found by Element()
assert not iv._matches(element)
Expand All @@ -129,14 +141,16 @@ def test_descriptions(self) -> None:
describe_mismatch="was not visible",
describe_none="was not even present",
)
iv = IsVisible()

_assert_descriptions(IsVisible(), element, expected)
assert iv.describe() == "visible"
_assert_descriptions(iv.resolve(), element, expected)

def test_type_hint(self) -> None:
iv = IsVisible()
annotation = iv.__annotations__["matcher"]
annotation = iv.resolve.__annotations__["return"]
assert annotation == "IsVisibleElement"
assert type(iv.matcher) == IsVisibleElement
assert type(iv.resolve()) == IsVisibleElement


class TestIsInvisible:
Expand All @@ -148,17 +162,17 @@ def test_can_be_instantiated(self) -> None:
def test_matches_an_invisible_element(self) -> None:
element = get_mocked_element()
element.is_displayed.return_value = False
ii = IsInvisible()
ii = IsInvisible().resolve()

assert ii._matches(element)
assert ii._matches(None) # element was not found by Element()
assert ii.matches(element)
assert ii.matches(None) # element was not found by Element()

def test_does_not_match_visible_element(self) -> None:
element = get_mocked_element()
element.is_displayed.return_value = True
ii = IsInvisible()
ii = IsInvisible().resolve()

assert not ii._matches(element)
assert not ii.matches(element)

def test_descriptions(self) -> None:
element = get_mocked_element()
Expand All @@ -169,7 +183,7 @@ def test_descriptions(self) -> None:
describe_none="it was invisible",
)

obj = IsInvisible()
obj = IsInvisible().resolve()
describe_to = StringDescription()
describe_match = StringDescription()
describe_mismatch = StringDescription()
Expand All @@ -185,11 +199,24 @@ def test_descriptions(self) -> None:
assert describe_mismatch.out == expected.describe_mismatch
assert describe_none.out == expected.describe_none

ii = IsInvisible()

assert ii.describe() == "invisible"

def test_type_hint(self) -> None:
ii = IsInvisible()
annotation = ii.__annotations__["matcher"]
annotation = ii.resolve.__annotations__["return"]
assert annotation == "IsInvisibleElement"
assert type(ii.matcher) == IsInvisibleElement
assert type(ii.resolve()) == IsInvisibleElement

def test_beat_logging(self, caplog: pytest.LogCaptureFixture) -> None:
caplog.set_level(logging.INFO)
IsInvisible().resolve()

assert [r.msg for r in caplog.records] == [
"... hoping it's invisible",
" => the element is invisible",
]


class TestIsPresent:
Expand All @@ -204,14 +231,14 @@ def test_can_be_instantiated(self) -> None:
)
def test_matches_a_present_element(self, enabled: bool, displayed: bool) -> None:
element = get_mocked_element()
ic = IsPresent()

element.is_enabled.return_value = enabled
element.is_displayed.return_value = displayed
ic = IsPresent().resolve()

assert ic._matches(element)

def test_does_not_match_missing_element(self) -> None:
ic = IsPresent()
ic = IsPresent().resolve()

assert not ic._matches(None)

Expand All @@ -223,11 +250,13 @@ def test_descriptions(self) -> None:
describe_mismatch="was not present",
describe_none="was not present",
)
ip = IsPresent()

_assert_descriptions(IsPresent(), element, expected)
assert ip.describe() == "present"
_assert_descriptions(ip.resolve(), element, expected)

def test_type_hint(self) -> None:
ip = IsPresent()
annotation = ip.__annotations__["matcher"]
annotation = ip.resolve.__annotations__["return"]
assert annotation == "IsPresentElement"
assert type(ip.matcher) == IsPresentElement
assert type(ip.resolve()) == IsPresentElement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have a test_matches_protocols test that we run? Maybe that's just for Actions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we don't have those tests in screenpy for Resolutions, hm...