feat: feedback_api — screenshot_page with Playwright (graceful fallback)

This commit is contained in:
pyr0ball 2026-03-03 12:14:33 -08:00
parent b77bb754af
commit 260be9e821
2 changed files with 51 additions and 0 deletions

View file

@ -13,6 +13,7 @@ from pathlib import Path
import requests
import yaml
from playwright.sync_api import sync_playwright
_ROOT = Path(__file__).parent.parent
_EMAIL_RE = re.compile(r"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}")
@ -192,3 +193,24 @@ def upload_attachment(
)
resp.raise_for_status()
return resp.json().get("browser_download_url", "")
def screenshot_page(port: int | None = None) -> bytes | None:
"""
Capture a screenshot of the running Peregrine UI using Playwright.
Returns PNG bytes, or None if Playwright is not installed or if capture fails.
"""
if port is None:
port = int(os.environ.get("STREAMLIT_PORT", os.environ.get("STREAMLIT_SERVER_PORT", "8502")))
try:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={"width": 1280, "height": 800})
page.goto(f"http://localhost:{port}", timeout=10_000)
page.wait_for_load_state("networkidle", timeout=10_000)
png = page.screenshot(full_page=False)
browser.close()
return png
except Exception:
return None

View file

@ -242,3 +242,32 @@ def test_upload_attachment_returns_url(mock_post, monkeypatch):
}
url = upload_attachment(42, b"\x89PNG", "screenshot.png")
assert url == "https://example.com/assets/abc"
# ── screenshot_page ───────────────────────────────────────────────────────────
def test_screenshot_page_returns_none_on_failure(monkeypatch):
"""screenshot_page returns None gracefully when capture fails."""
from scripts.feedback_api import screenshot_page
# Patch sync_playwright to raise an exception (simulates any failure)
import scripts.feedback_api as fapi
def bad_playwright():
raise RuntimeError("browser unavailable")
monkeypatch.setattr(fapi, "sync_playwright", bad_playwright)
result = screenshot_page(port=9999)
assert result is None
@patch("scripts.feedback_api.sync_playwright")
def test_screenshot_page_returns_bytes(mock_pw):
"""screenshot_page returns PNG bytes when playwright is available."""
from scripts.feedback_api import screenshot_page
fake_png = b"\x89PNG\r\n\x1a\n"
mock_context = MagicMock()
mock_pw.return_value.__enter__ = lambda s: mock_context
mock_pw.return_value.__exit__ = MagicMock(return_value=False)
mock_browser = mock_context.chromium.launch.return_value
mock_page = mock_browser.new_page.return_value
mock_page.screenshot.return_value = fake_png
result = screenshot_page(port=8502)
assert result == fake_png