From b1372d428f8428162a39aab967e6f32bbb216b28 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 24 Sep 2026 20:06:57 -0700 Subject: [PATCH] e2e: harden akLogin against the Lit stage re-render race fill-verify-Enter with a single inputValue read per stage (identification and password); the toHaveValue assertion hard-failed every attempt when the re-render wipe landed inside its 5s window (00-auth.setup failure 2026-09-24) --- tests/e2e/lib/login.ts | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/e2e/lib/login.ts b/tests/e2e/lib/login.ts index c2ef0d3..6135762 100644 --- a/tests/e2e/lib/login.ts +++ b/tests/e2e/lib/login.ts @@ -13,20 +13,30 @@ export async function akLogin(page: Page, baseUrl: string, user: string, pass: s await page.goto(baseUrl + "/"); const uid = page.locator('input[name="uidField"]'); await uid.waitFor({ state: "visible", timeout: 90_000 }); - await uid.fill(user); - await page.locator('button[type="submit"]').first().click(); + + // The flow executor (Lit) re-renders a stage right after mount and can wipe + // a programmatic fill; a submit then POSTs an empty form ("This field is + // required") and the stage never advances. Both stages share the race: + // fill, VERIFY the value survived with a single read (a toHaveValue + // assertion hard-fails the attempt when the wipe lands inside its window), + // submit via Enter (native form path; the button can be re-created between + // snapshot and click), and retry if the stage reset under us. + for (let attempt = 0; attempt < 3; attempt++) { + await uid.fill(user); + if ((await uid.inputValue()) !== user) continue; + await uid.press("Enter"); + try { + await page.locator('input[name="password"]').waitFor({ state: "visible", timeout: 15_000 }); + break; + } catch { + if (attempt === 2) throw new Error("identification stage never advanced after 3 attempts"); + } + } const pwd = page.locator('input[name="password"]'); - await pwd.waitFor({ state: "visible", timeout: 30_000 }); - - // authentik's flow executor (Lit) can re-render the stage right after it - // mounts, wiping a programmatic fill; clicking the submit button then - // POSTs an empty form ("This field is required"). Fill, VERIFY the value - // survived, and submit via Enter (native form path), retrying if the - // stage reset under us. for (let attempt = 0; attempt < 3; attempt++) { await pwd.fill(pass); - await expect(pwd).toHaveValue(pass, { timeout: 5_000 }); + if ((await pwd.inputValue()) !== pass) continue; await pwd.press("Enter"); try { await page.waitForURL(/:\/\/www\.franksdank\.cloud\//, { timeout: 20_000 });