Skip to content

Commit

Permalink
Resolvables (#53)
Browse files Browse the repository at this point in the history
* is_clickable
* is_invisible.py
* is_visible.py
* is_present.py
* ensuring messages include punctuation
  • Loading branch information
bandophahita authored Mar 8, 2024
1 parent 5c602c8 commit 4b7ea86
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 55 deletions.
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.")
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()
101 changes: 74 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,25 @@ 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

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

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


class TestIsInvisible:
Expand All @@ -148,17 +171,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 +192,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 +208,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 +240,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 +259,22 @@ 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

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

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

0 comments on commit 4b7ea86

Please sign in to comment.