Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.5.1
rev: v0.5.4
hooks:
- id: ruff
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.1
rev: v1.11.0
hooks:
- id: mypy
language_version: python3.12
13 changes: 13 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
Release History
===============

0.0.6 (2024-07-16)
------------------

### New Features

- Added `IsPresent` Resolution, which checks if an element, uh, is present.

### Improvements

- Addressed several issues with the Target class, such as them not appearing as Locators. We had to do some pretty spooky magicks back here, but don't worry; you'll get autocompletion and proper typing!
- Added wrapping around interacting with Playwright Locators to make the error messages a little more readable from a higher level.


0.0.5 (2024-05-22)
------------------

Expand Down
10 changes: 10 additions & 0 deletions docs/extended_api/actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ RefreshThePage
:members:


SaveConsoleLog
--------------

**Aliases:**
``SavesConsoleLog``,

.. autoclass:: SaveConsoleLog
:members:


SaveScreenshot
--------------

Expand Down
116 changes: 58 additions & 58 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ split-on-trailing-comma = false

[tool.poetry]
name = "screenpy_playwright"
version = "0.0.5"
version = "0.0.6"
description = "ScreenPy extension to enable interacting with Playwright."
authors = ["Perry Goy <[email protected]>"]
maintainers = ["Marcel Wilson"]
Expand Down
49 changes: 42 additions & 7 deletions screenpy_playwright/abilities/browse_the_web_synchronously.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@

from __future__ import annotations

from typing import TYPE_CHECKING
from collections import defaultdict
from typing import TYPE_CHECKING, Callable

from playwright.sync_api import sync_playwright

from ..exceptions import NoPageError

if TYPE_CHECKING:
from playwright.sync_api import Browser, BrowserContext, Page, Playwright
from playwright.sync_api import (
Browser,
BrowserContext,
ConsoleMessage,
Error as PlaywrightError,
Page,
Playwright,
)
from typing_extensions import Self


Expand All @@ -32,6 +40,7 @@ class BrowseTheWebSynchronously:
playwright: Playwright | None = None
_current_page: Page | None
pages: list[Page]
console_logs: dict[Page, list[ConsoleMessage | PlaywrightError]]

@classmethod
def using(cls, playwright: Playwright, browser: Browser | BrowserContext) -> Self:
Expand Down Expand Up @@ -60,6 +69,12 @@ def using_webkit(cls) -> Self:
cls.playwright = sync_playwright().start()
return cls(cls.playwright.webkit.launch())

def __init__(self, browser: Browser | BrowserContext) -> None:
self.browser = browser
self._current_page = None
self.pages = []
self.console_logs = defaultdict(list)

@property
def current_page(self) -> Page:
"""Get the current page.
Expand All @@ -77,11 +92,31 @@ def current_page(self, page: Page) -> None:
"""Set the current page."""
self._current_page = page

def _add_console_log_factory(self, page: Page) -> Callable[[ConsoleMessage], None]:
def _add_console_log(msg: ConsoleMessage) -> None:
self.console_logs[page].append(msg)

return _add_console_log

def _add_page_error_factory(self, page: Page) -> Callable[[PlaywrightError], None]:
def _add_page_error(err: PlaywrightError) -> None:
self.console_logs[page].append(err)

return _add_page_error

def new_page(self) -> Page:
"""Create a new page and make it the current page."""
page = self.browser.new_page()
console_callback = self._add_console_log_factory(page)
pageerror_callback = self._add_page_error_factory(page)
page.on("console", console_callback)
page.on("pageerror", pageerror_callback)

self.current_page = page
self.pages.append(page)

return self.current_page

def forget(self) -> None:
"""Forget everything you knew about being a playwright."""
self.browser.close()

def __init__(self, browser: Browser | BrowserContext) -> None:
self.browser = browser
self._current_page = None
self.pages = []
12 changes: 8 additions & 4 deletions screenpy_playwright/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .enter import Enter
from .open import Open
from .refresh_the_page import RefreshThePage
from .save_console_log import SaveConsoleLog
from .save_screenshot import SaveScreenshot
from .scroll import Scroll
from .select import Select
Expand All @@ -13,6 +14,7 @@
Enters = Enter
GoTo = GoesTo = Visit = Visits = Opens = Open
Refresh = Refreshes = RefreshesThePage = RefreshThePage
SavesConsoleLog = SaveConsoleLog
SavesScreenshot = SavesAScreenshot = SaveAScreenshot = SaveScreenshot
Scrolls = Scroll
Selects = Select
Expand All @@ -23,16 +25,18 @@
"Clicks",
"Enter",
"Enters",
"GoTo",
"GoesTo",
"GoTo",
"Open",
"Opens",
"RefreshThePage",
"RefreshesThePage",
"SaveScreenshot",
"RefreshThePage",
"SaveAScreenshot",
"SavesScreenshot",
"SaveConsoleLog",
"SavesAScreenshot",
"SavesConsoleLog",
"SaveScreenshot",
"SavesScreenshot",
"Scroll",
"Scrolls",
"Select",
Expand Down
5 changes: 1 addition & 4 deletions screenpy_playwright/actions/open.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def describe(self) -> str:
def perform_as(self, the_actor: Actor) -> None:
"""Direct the actor to Open a webpage."""
browse_the_web = the_actor.ability_to(BrowseTheWebSynchronously)
page = browse_the_web.browser.new_page()
page = browse_the_web.new_page()
try:
page.goto(self.url, **self.kwargs)
except PlaywrightError as e:
Expand All @@ -66,9 +66,6 @@ def perform_as(self, the_actor: Actor) -> None:
)
raise DeliveryError(msg) from e

browse_the_web.current_page = page
browse_the_web.pages.append(page)

def __init__(self, location: str | PageObject, **kwargs: Unpack[OpenTypes]) -> None:
url = getattr(location, "url", location)
url = f'{os.getenv("BASE_URL", "")}{url}'
Expand Down
107 changes: 107 additions & 0 deletions screenpy_playwright/actions/save_console_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Save a screenshot of the current page."""

from __future__ import annotations

from itertools import chain
from pathlib import Path
from typing import TYPE_CHECKING, Any

from screenpy import AttachTheFile, DeliveryError, beat

from ..abilities import BrowseTheWebSynchronously

if TYPE_CHECKING:
from playwright.sync_api import ConsoleMessage
from screenpy import Actor
from typing_extensions import Self


class SaveConsoleLog:
"""Save the Actor's browser's console log.

Use the :meth:`~SaveConsoleLog.and_attach_it` method to indicate that this
file should be attached to all reports through the Narrator's adapters. This
method also accepts any keyword arguments those adapters might require.

Abilities Required:
:class:`~screenpy_playwright.abilities.BrowseTheWebSynchronously`

Examples::

the_actor.attempts_to(SaveConsoleLog("consolelog.txt"))

the_actor.attempts_to(SaveConsoleLog.as_(filepath))

# attach file to the Narrator's reports (behavior depends on adapter).
the_actor.attempts_to(SaveConsoleLog.as_(filepath).and_attach_it())

# using screenpy_adapter_allure plugin!
from allure_commons.types import AttachmentTypes
the_actor.attempts_to(
SaveConsoleLog.as_(filepath).and_attach_it_with(
attachment_type=AttachmentTypes.TXT,
),
)
"""

attach_kwargs: dict | None
path: Path
filename: str

def describe(self) -> str:
"""Describe the Action in present tense."""
return f"Save browser console log as {self.filename}"

@classmethod
def as_(cls, path: str | Path) -> Self:
"""Supply the name and/or filepath for the console log.

If only a name is supplied, the screenshot will appear in the current
working directory.

Args:
path: The filepath for the screenshot, including its name.
"""
return cls(path=path)

def __init__(self, path: str | Path) -> None:
self.path = Path(path)
self.filename = self.path.name
self.attach_kwargs = None

def and_attach_it(self, **kwargs: Any) -> Self: # noqa: ANN401
"""Indicate the screenshot should be attached to any reports.

This method accepts any additional keywords needed by any adapters
attached for :external+screenpy:ref:`Narration`.

Aliases:
- ``and_attach_it_with``

Args:
kwargs: keyword arguments for the adapters used by the narrator.
"""
self.attach_kwargs = kwargs
return self

and_attach_it_with = and_attach_it

@beat("{} saves their browser's console log as {filename}")
def perform_as(self, the_actor: Actor) -> None:
"""Direct the actor to save their browser's console log."""
browse_the_web = the_actor.ability_to(BrowseTheWebSynchronously)
all_logs: chain[ConsoleMessage] = chain.from_iterable(
browse_the_web.console_logs.values() # type: ignore[arg-type]
)

try:
self.path.write_text("\n".join(str(log) for log in all_logs))
except OSError as e:
msg = (
f"{the_actor} encountered an issue while attempting to "
f"save their console log: {e.__class__.__name__}"
)
raise DeliveryError(msg) from e

if self.attach_kwargs is not None:
the_actor.attempts_to(AttachTheFile(str(self.path), **self.attach_kwargs))
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ def Tester() -> AnActor:
BrowseTheWeb_Mocked.pages = []
BrowseTheWeb_Mocked._current_page = None
BrowseTheWeb_Mocked.browser = mock.Mock(spec=Browser)
BrowseTheWeb_Mocked.console_logs = {}

return AnActor.named("Tester").who_can(BrowseTheWeb_Mocked)
9 changes: 9 additions & 0 deletions tests/test_abilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ def test_can_have_separate_instance_attribute(self) -> None:
assert mock_playwright.firefox.launch.call_count == 1
assert mock_playwright.webkit.launch.call_count == 1

def test_new_page(self) -> None:
playwright, browser = get_mocked_playwright_and_browser()
btws = BrowseTheWebSynchronously.using(playwright, browser)

page = btws.new_page()

assert btws.current_page is page
assert page in btws.pages

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

Expand Down
Loading