feat: feedback_api — screenshot_page with Playwright (graceful fallback)
This commit is contained in:
parent
e03a7171b1
commit
ff40caf350
2 changed files with 51 additions and 0 deletions
|
|
@ -13,6 +13,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import yaml
|
import yaml
|
||||||
|
from playwright.sync_api import sync_playwright
|
||||||
|
|
||||||
_ROOT = Path(__file__).parent.parent
|
_ROOT = Path(__file__).parent.parent
|
||||||
_EMAIL_RE = re.compile(r"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}")
|
_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()
|
resp.raise_for_status()
|
||||||
return resp.json().get("browser_download_url", "")
|
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
|
||||||
|
|
|
||||||
|
|
@ -242,3 +242,32 @@ def test_upload_attachment_returns_url(mock_post, monkeypatch):
|
||||||
}
|
}
|
||||||
url = upload_attachment(42, b"\x89PNG", "screenshot.png")
|
url = upload_attachment(42, b"\x89PNG", "screenshot.png")
|
||||||
assert url == "https://example.com/assets/abc"
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue