Files
command-center/agent/managed-skills/authentik-flow-ui-automation/SKILL.md
T
Frank 9bfa0be85d chore(skills): promote runbook skills to managed-skills; drop superseded SKILL.md
- 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.
2026-09-09 16:20:28 -07:00

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

  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:

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.