Skip to content

Commit

Permalink
Merge pull request #16 from ScreenPyHQ/add-is-present
Browse files Browse the repository at this point in the history
Add `IsPresent` Resolution.
  • Loading branch information
perrygoy authored Jul 16, 2024
2 parents 744e73c + 787afe9 commit 3ec1d3f
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 286 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ repos:
language_version: python3.12
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.4.4
rev: v0.5.1
hooks:
- id: ruff
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
rev: v1.10.1
hooks:
- id: mypy
language_version: python3.12
14 changes: 14 additions & 0 deletions docs/extended_api/resolutions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,22 @@ These are the Resolutions added in ScreenPy Playwright.
.. module:: screenpy_playwright.resolutions


IsPresent
---------

**Aliases**:
``Present``
``Exists``
``Exist``

.. autoclass:: IsPresent
:members:

IsVisible
---------

**Aliases**:
``Visible``

.. autoclass:: IsVisible
:members:
556 changes: 276 additions & 280 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions screenpy_playwright/resolutions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"""Resolutions provide answers to the Actor's Questions."""

from .is_present import IsPresent
from .is_visible import IsVisible

# Natural-language-enabling aliases
Visible = IsVisible
Exists = Exist = Present = IsPresent

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

from .is_present_element import is_present_element
from .is_visible_element import is_visible_element

__all__ = ["is_visible_element"]
__all__ = [
"is_present_element",
"is_visible_element",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
A matcher that matches a present element.
For example:
assert_that(driver.find_element_by_id("search"), is_present_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 IsPresentElement(BaseMatcher[Optional[Locator]]):
"""Match a locator which finds at least 1 element."""

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

def describe_to(self, description: Description) -> None:
"""Describe what is needed to pass this test."""
description.append_text("an element which is present")

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

def describe_mismatch(
self, _: Locator | None, mismatch_description: Description
) -> None:
"""Describe the failing case."""
mismatch_description.append_text("was not present")


def is_present_element() -> IsPresentElement:
"""Match a locator which finds at least 1 element present on the page."""
return IsPresentElement()
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


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

def _matches(self, item: Locator | None) -> bool:
if item is None:
Expand All @@ -44,5 +44,5 @@ def describe_mismatch(


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

from __future__ import annotations

from typing import TYPE_CHECKING

from screenpy import beat

from .custom_matchers import is_present_element

if TYPE_CHECKING:
from .custom_matchers.is_present_element import IsPresentElement


class IsPresent:
"""Match on a present element.
The element does not need to be visible, only that it exists.
Examples::
the_actor.should(See.the(Element(WELCOME_BANNER), IsPresent()))
"""

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

@beat("... hoping it's present.")
def resolve(self) -> IsPresentElement:
"""Produce the Matcher to make the assertion."""
return is_present_element()
8 changes: 8 additions & 0 deletions tests/test_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ def test_screenpy_playwright() -> None:
"Element",
"Enter",
"Enters",
"Exist",
"Exists",
"GoesTo",
"GoTo",
"IsPresent",
"IsVisible",
"NoPageError",
"Number",
"Open",
"Opens",
"PageObject",
"Present",
"RefreshesThePage",
"RefreshThePage",
"SaveAScreenshot",
Expand Down Expand Up @@ -95,7 +99,11 @@ def test_questions() -> None:

def test_resolutions() -> None:
expected = [
"Exist",
"Exists",
"IsPresent",
"IsVisible",
"Present",
"Visible",
]
assert sorted(screenpy_playwright.resolutions.__all__) == sorted(expected)
36 changes: 35 additions & 1 deletion tests/test_resolutions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
from screenpy_playwright import IsVisible
from screenpy_playwright import IsPresent, IsVisible
from screenpy_playwright.resolutions.custom_matchers.is_present_element import (
IsPresentElement,
)
from screenpy_playwright.resolutions.custom_matchers.is_visible_element import (
IsVisibleElement,
)

from .useful_mocks import get_mocked_locator


class TestIsPresent:
def test_can_be_instantiated(self) -> None:
ip = IsPresent()

assert isinstance(ip, IsPresent)

def test_describe(self) -> None:
assert IsPresent().describe() == "present"

def test_resolve(self) -> None:
assert isinstance(IsPresent().resolve(), IsPresentElement)

def test_the_test(self) -> None:
single_locator = get_mocked_locator()
multi_locator = get_mocked_locator()
single_locator.count.return_value = 1
multi_locator.count.return_value = 1337

assert IsVisible().resolve().matches(single_locator)
assert IsVisible().resolve().matches(multi_locator)

def test_the_test_fails(self) -> None:
locator = get_mocked_locator()
locator.count.return_value = 0

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

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


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

0 comments on commit 3ec1d3f

Please sign in to comment.