Skip to content

Commit

Permalink
add SaveConsoleLog Action.
Browse files Browse the repository at this point in the history
  • Loading branch information
perrygoy committed Jul 22, 2024
1 parent 7c92df1 commit 12e7633
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 86 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.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
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.

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
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
86 changes: 79 additions & 7 deletions tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Click,
Enter,
RefreshThePage,
SaveConsoleLog,
SaveScreenshot,
Scroll,
Select,
Expand Down Expand Up @@ -224,6 +225,79 @@ def test_raises_deliveryerror(self, Tester: Actor) -> None:
SaveScreenshot("./screenshot.png").perform_as(Tester)


class TestSaveConsoleLog:

class_path = "screenpy_playwright.actions.save_console_log"

def test_can_be_instantiated(self) -> None:
ss1 = SaveConsoleLog("./consolelog.txt")
ss2 = SaveConsoleLog.as_("./consolelog.txt")
ss3 = SaveConsoleLog.as_("./consolelog.txt").and_attach_it()
ss4 = SaveConsoleLog.as_("./consolelog.png").and_attach_it(saw="chain")

assert isinstance(ss1, SaveConsoleLog)
assert isinstance(ss2, SaveConsoleLog)
assert isinstance(ss3, SaveConsoleLog)
assert isinstance(ss4, SaveConsoleLog)

def test_implements_protocol(self) -> None:
ss = SaveConsoleLog("./consolelog.txt")

assert isinstance(ss, Describable)
assert isinstance(ss, Performable)

def test_filepath_vs_filename(self) -> None:
test_name = "smecker.txt"
test_path = f"boondock/saints/{test_name}"

ss = SaveConsoleLog.as_(test_path)

assert str(ss.path) == test_path
assert ss.filename == test_name

@mock.patch(f"{class_path}.AttachTheFile", autospec=True)
def test_perform_sends_kwargs_to_attach(
self, mocked_attachthefile: mock.Mock, Tester: Actor
) -> None:
test_path = "souiiie.txt"
test_kwargs = {"color": "Red", "weather": "Tornado"}
test_logs = {mock.Mock(): ["souie", "souiiie", "sooouiiie"]}
btws = Tester.ability_to(BrowseTheWebSynchronously)
btws.console_logs = test_logs # type: ignore[assignment]

with mock.patch(f"{self.class_path}.Path", autospec=True) as mocked_path:
mocked_path.return_value.__str__.return_value = test_path
SaveConsoleLog(test_path).and_attach_it(**test_kwargs).perform_as(Tester)

mocked_attachthefile.assert_called_once_with(test_path, **test_kwargs)
mocked_path(test_path).write_text.assert_called_once_with(
"souie\nsouiiie\nsooouiiie"
)

def test_describe(self) -> None:
assert SaveConsoleLog("pth").describe() == "Save browser console log as pth"

def test_subclass(self) -> None:
"""test code for mypy to scan without issue"""

class SubSaveConsoleLog(SaveConsoleLog):
pass

sss1 = SubSaveConsoleLog("./consolelog.txt")
sss2 = SubSaveConsoleLog.as_("./consolelog.txt")
sss3 = SubSaveConsoleLog.as_("./consolelog.txt").and_attach_it()

assert isinstance(sss1, SubSaveConsoleLog)
assert isinstance(sss2, SubSaveConsoleLog)
assert isinstance(sss3, SubSaveConsoleLog)

def test_raises_deliveryerror(self, Tester: Actor) -> None:
with mock.patch(f"{self.class_path}.Path", autospec=True) as mocked_path:
mocked_path.return_value.write_text.side_effect = OSError("I have no pen.")
with pytest.raises(DeliveryError):
SaveConsoleLog("./consolelog.txt").perform_as(Tester)


class TestScroll:
def test_can_be_instantiated(self) -> None:
s1 = Scroll(100, 200)
Expand Down Expand Up @@ -373,20 +447,18 @@ class PageObject:
def test_perform_visit(self, Tester: Actor) -> None:
url = "https://example.org/itsdotcom"
mock_ability = Tester.ability_to(BrowseTheWebSynchronously)
mock_browser = mock_ability.browser

Visit(url, wait_until="commit").perform_as(Tester)

mock_new_page_func = cast(mock.Mock, mock_browser.new_page)
mock_new_page_func.assert_called_once()
mock_page = mock_new_page_func.return_value
mock_new_page = cast(mock.Mock, mock_ability.new_page)
mock_new_page.assert_called_once()
mock_page = mock_new_page.return_value
mock_page.goto.assert_called_once_with(url, wait_until="commit")
assert mock_ability.current_page == mock_page
assert mock_page in mock_ability.pages

def test_raises_deliveryerror(self, Tester: Actor) -> None:
browse_the_web = cast(mock.Mock, Tester.ability_to(BrowseTheWebSynchronously))
browse_the_web.browser.new_page.return_value = browse_the_web.current_page
mock_new_page = cast(mock.Mock, browse_the_web.new_page)
mock_new_page.return_value = browse_the_web.current_page
browse_the_web.current_page.goto.side_effect = PlaywrightError("I have no map.")

with pytest.raises(DeliveryError):
Expand Down
12 changes: 8 additions & 4 deletions tests/test_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ def test_screenpy_playwright() -> None:
"RefreshesThePage",
"RefreshThePage",
"SaveAScreenshot",
"SaveConsoleLog",
"SavesAScreenshot",
"SavesConsoleLog",
"SaveScreenshot",
"SavesScreenshot",
"Scroll",
Expand Down Expand Up @@ -61,16 +63,18 @@ def test_actions() -> None:
"Clicks",
"Enter",
"Enters",
"GoTo",
"GoesTo",
"GoTo",
"Open",
"Opens",
"RefreshThePage",
"RefreshesThePage",
"SaveScreenshot",
"RefreshThePage",
"SaveAScreenshot",
"SavesScreenshot",
"SaveConsoleLog",
"SavesAScreenshot",
"SavesConsoleLog",
"SaveScreenshot",
"SavesScreenshot",
"Scroll",
"Scrolls",
"Select",
Expand Down

0 comments on commit 12e7633

Please sign in to comment.