feat(shot): mattermost-lts SCREENSHOT hook → /login (default lands the desktop-or-browser interstitial; watch-list wants the real sign-in form) + public screenshot.settle() for hooks; unit test via real loader; 206 unit tests pass, lint PASS
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@ -58,6 +58,12 @@ def _settle(page, idle_timeout_ms: int) -> None:
|
||||
page.wait_for_timeout(RENDER_GRACE_MS)
|
||||
|
||||
|
||||
def settle(page, idle_timeout_ms: int = SETTLE_TIMEOUT_MS) -> None:
|
||||
"""Public settle for recipe SCREENSHOT hooks: after the hook navigates to its safe view, call
|
||||
this so the snap happens post-paint. Same bounded best-effort contract as the default path."""
|
||||
_settle(page, idle_timeout_ms)
|
||||
|
||||
|
||||
def _snap_with_blank_retry(page, out_path: str) -> None:
|
||||
"""Screenshot the page; if the PNG is blank/spinner-sized, retry ONCE after a longer settle.
|
||||
The retry overwrites the tiny frame with a strictly-later one (same page, more paint time)."""
|
||||
|
||||
@ -18,3 +18,22 @@ HEALTH_OK = (200, 302)
|
||||
DEPLOY_TIMEOUT = 900
|
||||
HTTP_TIMEOUT = 600
|
||||
EXTRA_ENV = {"TIMEOUT": "600"}
|
||||
|
||||
|
||||
def SCREENSHOT(page, ctx):
|
||||
"""Land the real sign-in form for the CI card (phase-shot). The default landing capture gets
|
||||
mattermost's "view in desktop app or browser?" interstitial at `/` — a real page but not
|
||||
representative of the app. `/login` renders the standard login form directly. Credential-free
|
||||
(empty fields, R7 secret-safety: never a page showing generated secrets); the harness snaps
|
||||
the PNG after this returns."""
|
||||
from harness import browser as harness_browser
|
||||
from harness import screenshot as screenshot_mod
|
||||
|
||||
harness_browser.goto_with_retry(
|
||||
page,
|
||||
f"{ctx.base_url}/login",
|
||||
accept_statuses=(200,),
|
||||
deadline_seconds=screenshot_mod.NAV_DEADLINE_S,
|
||||
wait_until="domcontentloaded",
|
||||
)
|
||||
screenshot_mod.settle(page)
|
||||
|
||||
@ -116,6 +116,33 @@ def test_wait_budget_within_step_cap():
|
||||
assert total_ms <= 60_000, f"screenshot wait budget {total_ms}ms exceeds the ~60s step cap"
|
||||
|
||||
|
||||
def test_mattermost_screenshot_hook_lands_login():
|
||||
"""phase-shot: mattermost-lts ships the first real SCREENSHOT hook — `/` serves the
|
||||
desktop-or-browser interstitial, so the hook must navigate to /login (the representative,
|
||||
credential-free sign-in form) and settle; the harness then snaps the PNG."""
|
||||
|
||||
class _Resp:
|
||||
status = 200
|
||||
|
||||
class _NavPage(_FakePage):
|
||||
def __init__(self):
|
||||
super().__init__([])
|
||||
self.urls = []
|
||||
|
||||
def goto(self, url, wait_until=None, timeout=None):
|
||||
self.urls.append(url)
|
||||
return _Resp()
|
||||
|
||||
tests_dir = os.path.join(os.path.dirname(__file__), "..")
|
||||
meta = meta_mod.load("mattermost-lts", tests_dir=tests_dir)
|
||||
hook = S._load_screenshot_hook(meta)
|
||||
assert callable(hook), "mattermost-lts SCREENSHOT hook missing from the real load path"
|
||||
page = _NavPage()
|
||||
hook(page, meta_mod.hook_ctx("mm.example.org", meta))
|
||||
assert page.urls == ["https://mm.example.org/login"]
|
||||
assert page.idle_waits, "hook must settle before the harness snaps"
|
||||
|
||||
|
||||
def test_screenshot_reachable_through_real_load_path(tmp_path):
|
||||
"""R2 proof (rcust P1): a recipe SCREENSHOT hook declared in recipe_meta.py arrives at
|
||||
screenshot._load_screenshot_hook through the REAL orchestrator load path (meta.load — the
|
||||
|
||||
Reference in New Issue
Block a user