Skip to content

Commit

Permalink
add IsVisible Resolution.
Browse files Browse the repository at this point in the history
  • Loading branch information
perrygoy committed May 6, 2024
1 parent 0eeb6b4 commit e0e221a
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/extended_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ through Playwright.
extended_api/abilities
extended_api/actions
extended_api/questions
extended_api/resolutions
extended_api/target
14 changes: 14 additions & 0 deletions docs/extended_api/resolutions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
======================
Additional Resolutions
======================

These are the Resolutions added in ScreenPy Playwright.

.. module:: screenpy_playwright.resolutions


IsVisible
---------

.. autoclass:: IsVisible
:members:
3 changes: 2 additions & 1 deletion screenpy_playwright/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
from .exceptions import TargetingError
from .protocols import PageObject
from .questions import * # noqa: F403
from .resolutions import * # noqa: F403
from .target import Target

__all__ = ["Target", "TargetingError", "PageObject"]

__all__ += abilities.__all__ + actions.__all__ + questions.__all__
__all__ += abilities.__all__ + actions.__all__ + questions.__all__ + resolutions.__all__
4 changes: 2 additions & 2 deletions screenpy_playwright/actions/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def the_option(cls, *args: str, **kwargs: Unpack[SelectTypes]) -> Self:
method in Playwright.
Aliases:
- the_options
- ``the_options``
"""
return cls(*args, **kwargs)

Expand All @@ -74,7 +74,7 @@ def from_the(self, target: Target) -> Self:
"""Specify the dropdown or multi-select field to select from.
Aliases:
- from_
- ``from_``
"""
self.target = target
return self
Expand Down
3 changes: 2 additions & 1 deletion screenpy_playwright/questions/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class Attribute:
Abilities Required:
:class:`~screenpy_playwright.abilities.BrowseTheWebSynchronously`
Examples:
Examples::
the_actor.should(
See.the(
Attribute("aria-label").of_the(LOGIN_LINK),
Expand Down
11 changes: 11 additions & 0 deletions screenpy_playwright/resolutions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Resolutions provide answers to the Actor's Questions."""

from .is_visible import IsVisible

# Natural-language-enabling aliases
Visible = IsVisible

__all__ = [
"IsVisible",
"Visible",
]
5 changes: 5 additions & 0 deletions screenpy_playwright/resolutions/custom_matchers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Custom matchers for ScreenPy: Playwright Resolutions."""

from .is_visible_element import is_visible_element

__all__ = ["is_visible_element"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
A matcher that matches a visible element.
For example:
assert_that(driver.find_element_by_id("search"), is_visible_element())
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Optional

from hamcrest.core.base_matcher import BaseMatcher
from playwright.sync_api import Locator

if TYPE_CHECKING:
from hamcrest.core.description import Description


class IsVisibleElement(BaseMatcher[Optional[Locator]]):
"""Matches an element whose ``is_visible`` method returns True."""

def _matches(self, item: Locator | None) -> bool:
if item is None:
return False
return item.is_visible()

def describe_to(self, description: Description) -> None:
"""Describe the passing case."""
description.append_text("the element is visible")

def describe_match(self, _: Locator | None, match_description: Description) -> None:
"""Describe the matching case."""
match_description.append_text("it was visible")

def describe_mismatch(
self, item: Locator | None, mismatch_description: Description
) -> None:
"""Describe the failing case."""
if item is None:
mismatch_description.append_text("was not even present")
return
mismatch_description.append_text("was not visible")


def is_visible_element() -> IsVisibleElement:
"""This matcher matches any element that is visible."""
return IsVisibleElement()
30 changes: 30 additions & 0 deletions screenpy_playwright/resolutions/is_visible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Matches against a visible WebElement."""

from __future__ import annotations

from typing import TYPE_CHECKING

from screenpy import beat

from .custom_matchers import is_visible_element

if TYPE_CHECKING:
from .custom_matchers.is_visible_element import IsVisibleElement


class IsVisible:
"""Match on a visible element.
Examples::
the_actor.should(See.the(Element(WELCOME_BANNER), IsVisible()))
"""

def describe(self) -> str:
"""Describe the Resolution's expectation."""
return "visible"

@beat("... hoping it's visible.")
def resolve(self) -> IsVisibleElement:
"""Produce the Matcher to make the assertion."""
return is_visible_element()
10 changes: 10 additions & 0 deletions tests/test_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def test_screenpy_playwright() -> None:
"Click",
"Element",
"Enter",
"IsVisible",
"Number",
"Open",
"PageObject",
Expand All @@ -18,6 +19,7 @@ def test_screenpy_playwright() -> None:
"Target",
"TargetingError",
"Text",
"Visible",
"Visit",
]
assert sorted(screenpy_playwright.__all__) == sorted(expected)
Expand Down Expand Up @@ -52,3 +54,11 @@ def test_questions() -> None:
"Text",
]
assert sorted(screenpy_playwright.questions.__all__) == sorted(expected)


def test_resolutions() -> None:
expected = [
"IsVisible",
"Visible",
]
assert sorted(screenpy_playwright.resolutions.__all__) == sorted(expected)
34 changes: 34 additions & 0 deletions tests/test_resolutions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from screenpy_playwright import IsVisible
from screenpy_playwright.resolutions.custom_matchers.is_visible_element import (
IsVisibleElement,
)

from .useful_mocks import get_mocked_locator


class TestIsVisible:
def test_can_be_instantiated(self) -> None:
iv = IsVisible()

assert isinstance(iv, IsVisible)

def test_describe(self) -> None:
assert IsVisible().describe() == "visible"

def test_resolve(self) -> None:
assert isinstance(IsVisible().resolve(), IsVisibleElement)

def test_the_test(self) -> None:
locator = get_mocked_locator()
locator.is_visible.return_value = True

assert IsVisible().resolve().matches(locator)

def test_the_test_fails(self) -> None:
locator = get_mocked_locator()
locator.is_visible.return_value = False

assert not IsVisible().resolve().matches(locator)

def test_the_test_fails_for_none(self) -> None:
assert not IsVisible().resolve().matches(None)

0 comments on commit e0e221a

Please sign in to comment.