- 31 new managed skills (anubis/arr/authentik/hermes/freetoken/podman/llm-swap runbooks etc.) now tracked. - Removed superseded skills: omp_harness_config, peon_ping_integration (replaced by user-authored peon-ping-* managed skills), rocmfp4-strix-benchmark-orchestration. - New memory-root skills: niri_compositor_ops, omp_git_workflow, omp_model_config, quadlet-media-migration, homelab-service-triage, hermes-dankclaw-ops, frank-default-large-ops, caddy-auth-dns-playbook.
3.1 KiB
name, description
| name | description |
|---|---|
| authentik-flow-ui-automation | 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
- 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_userstage and the next login hangs on a "Continue as " interstitial that never completes. Each login = newbrowser.newContext(). - Real Chrome UA. Anubis denies obvious headless UAs; set
userAgentto a real Chrome UA inuse:(config-level) orlaunchOptions. Headless chromium + REAL_UA solves the PoW fine. - Shadow-DOM locators. authentik stages are Lit web components; locate inputs with
page.locator('input[name=username]')etc. (Playwright pierces shadow roots) — neverpage.$.
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:
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-probefor non-browser API-posture checks of guarded routes.