Files
plainpages/e2e-tests/devstack-login.spec.ts
T
lilleman a005acb93d
CI / full-gate (push) Successful in 2m38s
Cut non-essential prose from docs and comments, and require the same of every future change
README loses the competitor comparison, the personas and the repeated philosophy; the
five near-identical E2E command blocks become a table plus one command, and the file
map a clause per entry. AGENTS.md keeps every decision but drops the narrative around
them. todo.md's completed items collapse to their task line — git holds the rest.

Comments lose restatement, README duplication and history ("used to", "originally",
dated notes). AGENTS.md gains a Prose discipline section making this a standing pass on
every change rather than a one-off cleanup.

src/compose.test.ts now expects 6 documented E2E run commands, not 10, since the README
states the command once instead of per suite.
2026-08-05 23:41:12 +02:00

45 lines
2.5 KiB
TypeScript

import { expect, test } from "./console-guard.ts";
// The from-scratch dev experience the banner advertises: `docker compose up`, open the printed
// login URL, sign in as the seeded admin, land on the dashboard. A host-scoped Kratos CSRF cookie
// cannot cross `localhost`↔`127.0.0.1`, so a cross-host login POST loses it and Kratos redirects to
// its error sink; APP_URL canonicalises every off-host visitor onto one cookie host instead.
//
// The runner is on the host network against the plain `docker compose up` topology, so it sees
// http://localhost:3000 and http://127.0.0.1:4433 exactly as a host browser does. The proxied
// full-flow suite cannot catch this — it fronts web + Kratos on one origin.
const ADMIN_EMAIL = "admin@plainpages.local"; // seeded by bootstrap
const ADMIN_PASSWORD = "admin";
async function signIn(page: import("@playwright/test").Page): Promise<void> {
await page.fill('input[name="identifier"]', ADMIN_EMAIL);
await page.fill('input[name="password"]', ADMIN_PASSWORD);
await page.locator('.auth-form button[type="submit"]').click();
}
test("seeded admin logs in from the advertised URL (http://localhost:3000) and reaches the dashboard", async ({ page }) => {
test.setTimeout(90_000);
// Open the app at the URL the first-run banner prints, then follow the landing's "Sign in" action.
await page.goto("/");
await page.locator("#main-content").getByRole("link", { name: "Sign in" }).click();
await signIn(page);
// Signed in on the app — NOT dumped on the Kratos /error "Page not found" page.
await expect(page).not.toHaveURL(/\/error(\?|$)/);
await expect(page.locator("h1"), 'must not land on the "Page not found" 404 view').not.toHaveText("Page not found");
await expect(page.locator(".profile-mail")).toHaveText(ADMIN_EMAIL);
});
test("entering on the wrong host (http://127.0.0.1:3000) is canonicalised to APP_URL and login still works", async ({ page }) => {
test.setTimeout(90_000);
// The exact trigger from the bug report: a user types 127.0.0.1 instead of the advertised localhost.
// The canonical-host redirect sends them to localhost before the flow starts, so the CSRF cookie
// and the cross-origin Kratos POST share one host and login succeeds.
await page.goto("http://127.0.0.1:3000/login");
await expect(page).toHaveURL(/^http:\/\/localhost:3000\//); // 308'd onto the canonical host
await signIn(page);
await expect(page).not.toHaveURL(/\/error(\?|$)/);
await expect(page.locator(".profile-mail")).toHaveText(ADMIN_EMAIL);
});