# SPA Auth/OTP Form Debugging (headless patchright/Playwright, 2026-09) General techniques learned while automating a React SPA auth form (email → OTP code → onboarding) behind Cloudflare + Statsig. Applies to any signup/login flow (Notion, Clerk-style, etc.). ## 1. Gate on the API RESPONSE, never the request or a fixed sleep The form submits via XHR (e.g. `sendTemporaryPassword`). Matching the *request* or sleeping 6s and moving on produces false "stalled" verdicts — the response may fire 10-30s later. ```python st = {"stp": None} page.on("response", lambda r: st.__setitem__("stp", r.status) if "sendTemporaryPassword" in r.url else None) btn.click() for _ in range(30): time.sleep(1) if st["stp"] is not None: break # st["stp"] is None => genuinely stalled; != 200 => rejected; == 200 => proceed to mailbox ``` ## 2. "Loading..." button = click does nothing SPA submit buttons can render as `Loading...\nContinue` while client-side gates (Statsig init, config fetch) finish. Clicking then is a no-op. - Probe before click: `btn.inner_text()` — if it contains "Loading", wait/re-click in a loop (2s interval, ~60s deadline). - Deep probe via `page.evaluate` on the raw `