From abf96601e83c3c62094ab3582574f6fa41335446 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 11 May 2026 13:35:12 -0600 Subject: [PATCH] dev/screenshot.py: tolerate post-onboarding /api/onboarding 404 After HA finishes its first-run wizard the /api/onboarding endpoint returns 404 plain-text instead of a JSON step list. The previous screenshot run blew up trying to json-parse "404: Not Found". Both call sites (_onboard and _complete_onboarding) now check the status code first and treat anything non-200 as "already complete -- skip and go to the login path". --- dev/screenshot.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/dev/screenshot.py b/dev/screenshot.py index 43ba96d..41f4698 100644 --- a/dev/screenshot.py +++ b/dev/screenshot.py @@ -32,7 +32,17 @@ async def _complete_onboarding( ) -> None: """POST every remaining onboarding step in turn so HA stops greeting us.""" r = await client.get("/api/onboarding") - pending = [s["step"] for s in r.json() if not s.get("done")] + if r.status_code != 200: + # Endpoint disappears once onboarding is fully complete — nothing + # to do, the user is already past the welcome wizard. + print(" onboarding endpoint 404 — already complete") + return + try: + steps = r.json() + except Exception: + print(" onboarding endpoint returned non-JSON — assuming complete") + return + pending = [s["step"] for s in steps if not s.get("done")] print(f" pending onboarding: {pending}") if "core_config" in pending: @@ -73,7 +83,15 @@ async def _onboard(ha_url: str) -> str: """ async with httpx.AsyncClient(base_url=ha_url, timeout=30.0) as client: r = await client.get("/api/onboarding") - steps = r.json() + # Once onboarding is fully complete the endpoint 404s with a + # plain-text body instead of a JSON step list — skip straight to + # the subsequent-run login path in that case. + steps: list[dict] = [] + if r.status_code == 200: + try: + steps = r.json() + except Exception: + steps = [] user_step = next((s for s in steps if s["step"] == "user"), None) if user_step and not user_step.get("done"): @@ -199,7 +217,8 @@ async def _take_screenshots(ha_url: str, token: str, outdir: Path) -> list[Path] viewport={"width": 1440, "height": 900}, device_scale_factor=2, ) - # Inject auth so we skip the login screen. + # Inject auth so we skip the login screen + force HA's dark theme + # so screenshots match the docs site's default theme. await context.add_init_script( f"""window.localStorage.setItem('hassTokens', JSON.stringify({{ access_token: '{token}', @@ -211,6 +230,15 @@ async def _take_screenshots(ha_url: str, token: str, outdir: Path) -> list[Path] refresh_token: 'placeholder', }})); window.localStorage.setItem('selectedLanguage', '"en"'); + // Force dark theme — HA reads selectedTheme from localStorage + // before the user-settings panel loads. The empty 'theme' object + // tells HA "use the default dark theme, not a custom one". + window.localStorage.setItem('selectedTheme', JSON.stringify({{ + theme: 'default', + dark: true, + primaryColor: null, + accentColor: null, + }})); """ ) page = await context.new_page()