From 44adabca9e512c31b8b30cb9d7bc85b56534275b Mon Sep 17 00:00:00 2001 From: Paul Nothaft <53005142+the-luap@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:14:00 +0200 Subject: [PATCH] test(e2e): read the admin JWT from the cookie, not the login body (#1071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three specs acquire an admin token with `const body = await res.json(); return body.token`. The admin login has not returned a token in its body for some time — establishAdminSession() sets the JWT as the httpOnly `admin_token` cookie and responds with `res.json({ user })` — so the token was undefined and every one of them failed at the first assertion, before exercising anything they were written to cover. Server-side the cookie and an Authorization: Bearer header are interchangeable (see middleware/gallery.js, which reads the cookie first and accepts an admin-typed Bearer second), so the fix is to read the value back out of the context cookie jar and keep threading it as a Bearer. Every downstream call in these specs stays exactly as it was. Measured against a real stack, running only these three files: before 0 passed, 6 failed — all six at the token assertion after 3 passed, 3 failed The three that still fail no longer fail on auth: they get deep into the flow and then miss UI that has since changed (a settings label, a locator that no longer resolves). That is a separate and much larger staleness problem across this directory — a full run is 12 passed against roughly two dozen failures of that kind — and it is not addressed here. Worth knowing: no CI workflow runs tests/e2e at all, which is why this rotted silently while `npm run test:e2e` stayed documented in CLAUDE.md. Wiring it up is the obvious follow-up, but it has to wait until the suite is actually green, or it would just pin main red. Claude-Session: https://claude.ai/code/session_01Ra4hcsYiKuQLbbRsg6EjAc Co-authored-by: Paul Nothaft --- tests/e2e/auth-smoke.spec.ts | 6 +++--- tests/e2e/customer-portal-flow.spec.ts | 11 ++++++++--- tests/e2e/optional-email-event-creation.spec.ts | 11 ++++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/tests/e2e/auth-smoke.spec.ts b/tests/e2e/auth-smoke.spec.ts index d876caff..b62bdd44 100644 --- a/tests/e2e/auth-smoke.spec.ts +++ b/tests/e2e/auth-smoke.spec.ts @@ -18,9 +18,9 @@ async function createEventWithPhotos(page: Page, adminToken?: string, attempt = }, }); expect(loginResponse.ok()).toBeTruthy(); - const loginData = await loginResponse.json(); - token = loginData.token; - expect(token).toBeTruthy(); + const cookies = await page.context().cookies(); + token = cookies.find((c) => c.name === 'admin_token')?.value; + expect(token, 'admin_token cookie missing from the login response').toBeTruthy(); } const eventName = `Playwright Smoke ${Date.now()}`; diff --git a/tests/e2e/customer-portal-flow.spec.ts b/tests/e2e/customer-portal-flow.spec.ts index b50a3c96..f6f90381 100644 --- a/tests/e2e/customer-portal-flow.spec.ts +++ b/tests/e2e/customer-portal-flow.spec.ts @@ -40,9 +40,14 @@ async function adminLogin(page: Page): Promise { failOnStatusCode: false, }); expect(res.ok()).toBeTruthy(); - const json = await res.json(); - expect(json.token).toBeTruthy(); - return json.token; + // The admin JWT is delivered as the httpOnly `admin_token` cookie, not in + // the response body. Server-side the cookie and an Authorization: Bearer + // header are interchangeable, so read it back out of the context jar and + // keep threading it as a Bearer — every downstream call stays as it was. + const cookies = await page.context().cookies(); + const token = cookies.find((c) => c.name === 'admin_token')?.value; + expect(token, 'admin_token cookie missing from the login response').toBeTruthy(); + return token as string; } async function setCustomerPortalEnabled(page: Page, adminToken: string, enabled: boolean) { diff --git a/tests/e2e/optional-email-event-creation.spec.ts b/tests/e2e/optional-email-event-creation.spec.ts index 641a1bf2..56e018df 100644 --- a/tests/e2e/optional-email-event-creation.spec.ts +++ b/tests/e2e/optional-email-event-creation.spec.ts @@ -8,9 +8,14 @@ async function getAdminToken(page: Page): Promise { data: { username: ADMIN_EMAIL, password: ADMIN_PASSWORD }, }); expect(res.ok()).toBeTruthy(); - const body = await res.json(); - expect(body.token).toBeTruthy(); - return body.token; + // The admin JWT is delivered as the httpOnly `admin_token` cookie, not in + // the response body. Server-side the cookie and an Authorization: Bearer + // header are interchangeable, so read it back out of the context jar and + // keep threading it as a Bearer — every downstream call stays as it was. + const cookies = await page.context().cookies(); + const token = cookies.find((c) => c.name === 'admin_token')?.value; + expect(token, 'admin_token cookie missing from the login response').toBeTruthy(); + return token as string; } async function updateEventSettings(