diff --git a/runner/run_recipe_ci.py b/runner/run_recipe_ci.py index f0b1e0c..8fe76dc 100644 --- a/runner/run_recipe_ci.py +++ b/runner/run_recipe_ci.py @@ -50,6 +50,7 @@ from harness import ( # noqa: E402 lifecycle, naming, results as results_mod, + screenshot as screenshot_mod, warm, warmsnap, ) @@ -914,6 +915,7 @@ def main() -> int: results: dict[str, str] = {} lifecycle.janitor() dep_teardown_error: str | None = None + screenshot_rel: str | None = None # Phase 3 U1 (R4): set once the app screenshot is captured try: # ---- (Q3.2a) install-time OIDC: provision the warm-dep realm BEFORE the single deploy so # install_steps.sh can read $CCCI_DEPS_FILE and wire the OIDC env into that one deploy. On @@ -963,6 +965,19 @@ def main() -> int: print(f"!! deploy/readiness failed: {e}", flush=True) deploy_ok = False + # ---- Phase 3 U1 (R4): capture a real app screenshot while the app is up, at the cleanest + # "freshly installed + healthy" moment (before any tier mutates state and before teardown). + # Placed OUTSIDE the deploy try/except so a screenshot issue can NEVER flip deploy_ok. + # Secret-safe by default (landing page, never a credentials page; recipes opt into a + # post-login view via a SCREENSHOT meta hook). Best-effort — capture() swallows all errors and + # returns None, so this never blocks or fails the run (R7). None → results.json `screenshot` + # stays null → the card shows the "no screenshot" placeholder (cosmetics never change verdict). + if deploy_ok: + shot = screenshot_mod.capture( + domain, screenshot_mod.screenshot_path(run_artifact_dir), recipe_meta=meta + ) + screenshot_rel = os.path.basename(shot) if shot else None + # ---- INSTALL tier (always; additive generic + overlay, no op) ---- if "install" in stages: results["install"] = ( @@ -1213,6 +1228,7 @@ def main() -> int: sso_unverified=sso_unverified, clean_teardown=clean_teardown, no_secret_leak=True, # narrowed below by an actual scan of the serialised artifact + screenshot=screenshot_rel, # Phase 3 U1 (R4): relative PNG name iff capture succeeded finished_ts=time.time(), ) # Real (if narrow) leak check: no known infra-secret value may appear in the artifact (R7).