--- name: authentik-flow-ui-automation description: Drive the authentik two-stage login through real browser UI for E2E tests on Anubis+outpost-guarded routes; hardened against the Lit stage re-render race and Anubis PoW budgets --- Drive authentik's two-stage login (identification → password) through a real browser UI for E2E tests on routes guarded by the Anubis + Authentik chain (Caddy → Anubis :8923 → outpost :9000). Proven in `tests/e2e/lib/login.ts` (`akLogin`) against authentik 2026.x. ## Hard rules 1. **Fresh context per login.** Never reuse a context or page across logins, and never retry a failed login inside the same context — a half-finished flow poisons authentik's `pending_user` stage and the next login hangs on a "Continue as " interstitial that never completes. Each login = new `browser.newContext()`. 2. **Real Chrome UA.** Anubis denies obvious headless UAs; set `userAgent` to a real Chrome UA in `use:` (config-level) or `launchOptions`. Headless chromium + REAL_UA solves the PoW fine. 3. **Shadow-DOM locators.** authentik stages are Lit web components; locate inputs with `page.locator('input[name=username]')` etc. (Playwright pierces shadow roots) — never `page.$`. ## The Lit re-render race (the big one) The flow executor re-renders a stage right after mount. A programmatic `fill()` can succeed and then be WIPED by the re-render; the submit then POSTs an empty form ("This field is required") and `waitForURL` times out while the page still shows the password stage with an empty field. Hardened sequence per stage: ```ts for (let attempt = 1; attempt <= 3; attempt++) { await page.fill('input[name=password]', pass); // VERIFY the value survived the re-render before submitting if ((await page.inputValue('input[name=password]')) !== pass) continue; await page.keyboard.press('Enter'); // native form path; avoids button re-render races try { await page.waitForURL(u => !u.pathname.includes('/if/flow/'), { timeout: 30_000 }); return; // past this stage } catch { /* maybe wiped; retry */ } } throw new Error(`akLogin: stage never advanced after 3 attempts`); ``` - Submit with **Enter**, not a button click — the button can be re-created between snapshot and click. - Verify-after-fill is the load-bearing step; without it you silently submit empty forms. - The identification stage has the same race; apply the same fill-verify-Enter there. ## Anubis PoW budgets The first guarded-vhost hit in a fresh context runs an Anubis proof-of-work; on a cold/loaded box it can outlive default assertion budgets. Give title/URL asserts after `goto` 60 s budgets. With `COOKIE_DOMAIN` set fleet-wide (Anubis ≥ v1.27), every later guarded vhost in the same context skips the challenge (~0.5 s per vhost in the crawl specs). ## See also - `serverAppConfigs-docs/Architecture/E2E Suite.md` — project layout, credentials model (`tests/e2e/.env.local`, git-ignored), run commands, gotcha list. - `serverAppConfigs-docs/Infrastructure/Anubis.md` — chain behavior, v1.27 gotchas (X-Real-Ip mandatory, DENY = HTTP 200 body). - `arr-auth-posture-probe` for non-browser API-posture checks of guarded routes.