From 4b7ea863ddff25193a1240ddd8da055e668bd00c Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Fri, 8 Mar 2024 14:42:58 -0600 Subject: [PATCH] Resolvables (#53) * is_clickable * is_invisible.py * is_visible.py * is_present.py * ensuring messages include punctuation --- screenpy_selenium/resolutions/is_clickable.py | 16 +-- screenpy_selenium/resolutions/is_invisible.py | 16 +-- screenpy_selenium/resolutions/is_present.py | 16 +-- screenpy_selenium/resolutions/is_visible.py | 16 +-- tests/test_resolutions.py | 101 +++++++++++++----- 5 files changed, 110 insertions(+), 55 deletions(-) diff --git a/screenpy_selenium/resolutions/is_clickable.py b/screenpy_selenium/resolutions/is_clickable.py index ad5746a..50f35aa 100644 --- a/screenpy_selenium/resolutions/is_clickable.py +++ b/screenpy_selenium/resolutions/is_clickable.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from screenpy.resolutions.base_resolution import BaseResolution +from screenpy import beat from .custom_matchers import is_clickable_element @@ -12,7 +12,7 @@ from .custom_matchers.is_clickable_element import IsClickableElement -class IsClickable(BaseResolution): +class IsClickable: """Match on a clickable element. Examples:: @@ -20,9 +20,11 @@ class IsClickable(BaseResolution): 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() diff --git a/screenpy_selenium/resolutions/is_invisible.py b/screenpy_selenium/resolutions/is_invisible.py index cd1e1a5..cc4107b 100644 --- a/screenpy_selenium/resolutions/is_invisible.py +++ b/screenpy_selenium/resolutions/is_invisible.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from screenpy.resolutions.base_resolution import BaseResolution +from screenpy import beat from .custom_matchers import is_invisible_element @@ -12,7 +12,7 @@ from .custom_matchers.is_invisible_element import IsInvisibleElement -class IsInvisible(BaseResolution): +class IsInvisible: """Match on an invisible element. Examples:: @@ -20,9 +20,11 @@ class IsInvisible(BaseResolution): 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() diff --git a/screenpy_selenium/resolutions/is_present.py b/screenpy_selenium/resolutions/is_present.py index 4055794..9175d35 100644 --- a/screenpy_selenium/resolutions/is_present.py +++ b/screenpy_selenium/resolutions/is_present.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from screenpy.resolutions.base_resolution import BaseResolution +from screenpy import beat from .custom_matchers import is_present_element @@ -12,7 +12,7 @@ from .custom_matchers.is_present_element import IsPresentElement -class IsPresent(BaseResolution): +class IsPresent: """Match on a present element. Examples:: @@ -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() diff --git a/screenpy_selenium/resolutions/is_visible.py b/screenpy_selenium/resolutions/is_visible.py index 274d0bd..ade2a35 100644 --- a/screenpy_selenium/resolutions/is_visible.py +++ b/screenpy_selenium/resolutions/is_visible.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from screenpy.resolutions.base_resolution import BaseResolution +from screenpy import beat from .custom_matchers import is_visible_element @@ -12,7 +12,7 @@ from .custom_matchers.is_visible_element import IsVisibleElement -class IsVisible(BaseResolution): +class IsVisible: """Match on a visible element. Examples:: @@ -20,9 +20,11 @@ class IsVisible(BaseResolution): 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() diff --git a/tests/test_resolutions.py b/tests/test_resolutions.py index 2502168..8f3f42d 100644 --- a/tests/test_resolutions.py +++ b/tests/test_resolutions.py @@ -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 @@ -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 @@ -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() @@ -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) @@ -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) @@ -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: @@ -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) @@ -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: @@ -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() @@ -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() @@ -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: @@ -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) @@ -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", + ]