Skip to content

Commit

Permalink
move no-page logic to the Ability (h/t @bandophahita).
Browse files Browse the repository at this point in the history
  • Loading branch information
perrygoy committed May 14, 2024
1 parent 76e7255 commit 1002b70
Show file tree
Hide file tree
Showing 14 changed files with 69 additions and 34 deletions.
1 change: 1 addition & 0 deletions docs/extended_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ through Playwright.
extended_api/questions
extended_api/resolutions
extended_api/target
extended_api/exceptions
21 changes: 21 additions & 0 deletions docs/extended_api/exceptions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
=====================
Additional Exceptions
=====================

ScreenPy Playwright adds
a few additional exceptions
to the ScreenPy library.

.. module:: screenpy_playwright.exceptions

NoPageError
-----------

.. autoclass:: NoPageError
:members:

TargetingError
--------------

.. autoclass:: TargetingError
:members:
9 changes: 7 additions & 2 deletions screenpy_playwright/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@
from . import abilities, actions, questions, resolutions
from .abilities import * # noqa: F403
from .actions import * # noqa: F403
from .exceptions import TargetingError
from .exceptions import NoPageError, TargetingError
from .protocols import PageObject
from .questions import * # noqa: F403
from .resolutions import * # noqa: F403
from .target import Target

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

__all__ += abilities.__all__ + actions.__all__ + questions.__all__ + resolutions.__all__
23 changes: 21 additions & 2 deletions screenpy_playwright/abilities/browse_the_web_synchronously.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from playwright.sync_api import sync_playwright

from ..exceptions import NoPageError

if TYPE_CHECKING:
from typing import TypeVar

Expand Down Expand Up @@ -33,7 +35,7 @@ class BrowseTheWebSynchronously:
"""

playwright: Playwright | None = None
current_page: Page | None
_current_page: Page | None
pages: list[Page]

@classmethod
Expand Down Expand Up @@ -73,6 +75,23 @@ def using_webkit(
cls.playwright = sync_playwright().start()
return cls(cls.playwright.webkit.launch())

@property
def current_page(self) -> Page:
"""Get the current page.
Raises a :class:`~screenpy_playwright.exceptions.NoPageError` if there
is no current page.
"""
if self._current_page is None:
msg = "There is no current page. Did you forget to `Open` a page?"
raise NoPageError(msg)
return self._current_page

@current_page.setter
def current_page(self, page: Page) -> None:
"""Set the current page."""
self._current_page = page

def forget(self: SelfBrowseTheWebSynchronously) -> None:
"""Forget everything you knew about being a playwright."""
self.browser.close()
Expand All @@ -85,5 +104,5 @@ def __init__(
browser: Browser | BrowserContext,
) -> None:
self.browser = browser
self.current_page = None
self._current_page = None
self.pages = []
9 changes: 1 addition & 8 deletions screenpy_playwright/actions/refresh_the_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from typing import TYPE_CHECKING, Literal, TypedDict

from screenpy import UnableToAct, beat
from screenpy import beat

from screenpy_playwright.abilities import BrowseTheWebSynchronously

Expand Down Expand Up @@ -42,11 +42,4 @@ def describe(self) -> str:
def perform_as(self, the_actor: Actor) -> None:
"""Direct the Actor to refresh the page."""
page = the_actor.ability_to(BrowseTheWebSynchronously).current_page
if page is None:
msg = (
"Actor does not have a current page to refresh."
" Did you forget to `Open` a page?"
)
raise UnableToAct(msg)

page.reload(**self.kwargs)
6 changes: 5 additions & 1 deletion screenpy_playwright/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Exceptions thrown by ScreenPy: Playwright."""

from screenpy.exceptions import ScreenPyError
from screenpy.exceptions import AbilityError, ScreenPyError


class NoPageError(AbilityError):
"""The Actor has no open pages."""


class TargetingError(ScreenPyError):
Expand Down
8 changes: 1 addition & 7 deletions screenpy_playwright/questions/browser_url.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Ask about the current browser URL."""

from screenpy import Actor, UnableToAnswer, beat
from screenpy import Actor, beat

from screenpy_playwright.abilities import BrowseTheWebSynchronously

Expand All @@ -23,12 +23,6 @@ def describe(self) -> str:
def answered_by(self, the_actor: Actor) -> str:
"""Ask the Actor about the current browser URL."""
page = the_actor.ability_to(BrowseTheWebSynchronously).current_page
if page is None:
msg = (
"Actor does not have a current page from which to get the URL."
" Did you forget to `Open` a page?"
)
raise UnableToAnswer(msg)

# On single-page applications, after clicking an element which redirects
# the user, the browser URL is not immediately updated in Playwright.
Expand Down
4 changes: 1 addition & 3 deletions screenpy_playwright/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ def found_by(self, the_actor: Actor) -> Locator:
The Locator which describes the element.
"""
browse_the_web = the_actor.ability_to(BrowseTheWebSynchronously)
if browse_the_web.current_page is None:
msg = f"There is no active page! {the_actor} cannot find the {self}."
raise TargetingError(msg)

if self.locator is None:
msg = f"{self} does not have a locator set."
raise TargetingError(msg)
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def Tester() -> AnActor:
"""Provide an Actor with mocked web browsing abilities."""
BrowseTheWeb_Mocked = mock.Mock(spec=BrowseTheWebSynchronously)
BrowseTheWeb_Mocked.pages = []
BrowseTheWeb_Mocked.current_page = None
BrowseTheWeb_Mocked._current_page = None
BrowseTheWeb_Mocked.browser = mock.Mock(spec=Browser)

return AnActor.named("Tester").who_can(BrowseTheWeb_Mocked)
9 changes: 8 additions & 1 deletion tests/test_abilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import pytest
from screenpy import Forgettable

from screenpy_playwright import BrowseTheWebSynchronously
from screenpy_playwright import BrowseTheWebSynchronously, NoPageError

from .useful_mocks import get_mocked_playwright_and_browser

Expand Down Expand Up @@ -56,3 +56,10 @@ def test_can_have_separate_instance_attribute(self) -> None:
assert mock_playwright.chromium.launch.call_count == 1
assert mock_playwright.firefox.launch.call_count == 1
assert mock_playwright.webkit.launch.call_count == 1

def test_raises_when_no_current_page(self) -> None:
_, browser = get_mocked_playwright_and_browser()

with pytest.raises(NoPageError):
# accessing the attribute will raise if it's None
BrowseTheWebSynchronously(browser).current_page # noqa: B018
4 changes: 0 additions & 4 deletions tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,6 @@ def test_perform_refresh(self, Tester: Actor) -> None:

current_page.reload.assert_called_once_with(timeout=20)

def test_raises_when_no_current_page(self, Tester: Actor) -> None:
with pytest.raises(UnableToAct, match="current page to refresh"):
RefreshThePage().perform_as(Tester)


class TestSaveScreenshot:

Expand Down
1 change: 1 addition & 0 deletions tests/test_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def test_screenpy_playwright() -> None:
"Element",
"Enter",
"IsVisible",
"NoPageError",
"Number",
"Open",
"PageObject",
Expand Down
4 changes: 0 additions & 4 deletions tests/test_questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ def test_ask_browser_url(self, Tester: Actor) -> None:

assert BrowserURL().answered_by(Tester) == url

def test_raises_error_if_no_page(self, Tester: Actor) -> None:
with pytest.raises(UnableToAnswer):
BrowserURL().answered_by(Tester)


class TestElement:
def test_can_be_instantiated(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def test_found_by_raises_if_no_locator(Tester: Actor) -> None:
test_name = "John Cleese"

with pytest.raises(TargetingError) as excinfo:
Target.the(test_name).located_by("*").found_by(Tester)
Target.the(test_name).found_by(Tester)

assert test_name in str(excinfo.value)

Expand Down

0 comments on commit 1002b70

Please sign in to comment.