Files
serverAppConfigs/tests/e2e/specs/12-crawl.spec.ts
T
Frank d2587e1d29 e2e: crawl reset survives app navigation; dragoon docs carry the cut ledger
Two faces of the same verification round.

12-crawl returned to the homepage between links with a bare page.goto, and Plex
Web (signed out, so it renders its own sign-in iframe for its Plex-account flow)
keeps redirecting the top frame: Chromium cancelled our goto with
net::ERR_ABORTED, outside the try, so the whole crawl died — once as a 420 s
timeout in the deploy gate, once at 5.4 s on the same link. backHome() tolerates
that one error class and re-issues; any other error still propagates, and the
per-app verdict (guard settled, hostname, no authentik uidField, status < 500)
is unchanged. Plex was healthy throughout (container up 2 days, 32400 answering
HTTPS 401 / plain-HTTP empty reply), so this was app behaviour, not a fault —
recorded as gotcha 6 on the E2E page.

The 200,704 boot printed 5.5 GiB reserved and 74.1 GiB in all (not the ~5.4/~74
my arithmetic predicted), so every prose figure is re-keyed to the ledger, and
the runbook gains the second production-boot section: marker revision, pool
reservation, shape line, prompt-cache entry size, and the refusal contract at the
new ceiling (400 in 0.86 s naming 200704 and the 96,405-token overage, before any
prefill). Cost stated (two maximum-length conversations never coexist; the 180k
compaction target is unreachable inside a 155,648 window) and purchase stated
(1.7 GiB of page cache, 9.1 GiB against the 2026-10-06 shape).
2026-10-17 22:45:34 -07:00

74 lines
3.1 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
// Clicks every open link on the user homepage and asserts the destination is
// the real app, reached through the guard chain without an authentik prompt.
// The link set is read live from the rendered homepage (it mirrors
// shared/homepage/services.json open entries) so a registry edit cannot leave
// this test asserting a stale world.
const CHALLENGE = "Making sure you're not a bot";
async function settleThroughGuard(page: Page, targetHost: string): Promise<void> {
// Each guarded vhost runs its own Anubis proof-of-work on first visit;
// wait for the challenge to clear before judging the destination.
await expect
.poll(async () => !(await page.locator("body").innerText()).includes(CHALLENGE), { timeout: 90_000 })
.toBe(true);
await expect
.poll(() => new URL(page.url()).hostname, { timeout: 60_000 })
.toBe(targetHost);
expect(await page.locator('input[name="uidField"]').count()).toBe(0);
}
async function backHome(page: Page): Promise<void> {
// The homepage is the loop's only anchor, and some apps keep issuing their
// OWN navigation after load — Plex Web's "would like to sign in to your
// Plex account" iframe redirects the top frame, which cancels our goto with
// net::ERR_ABORTED. That abort is the app's behaviour, not a guard-chain
// failure (the app itself was already judged above), so tolerate it and
// re-issue rather than letting one host abort the whole crawl.
for (let attempt = 0; attempt < 3; attempt++) {
try {
await page.goto("/", { waitUntil: "domcontentloaded" });
return;
} catch (err) {
if (!String(err).includes("ERR_ABORTED")) throw err;
await page.waitForTimeout(1_000);
}
}
await page.goto("/");
}
test("every user open link reaches its app", async ({ page }) => {
test.setTimeout(420_000);
// Expected link count comes from the live registry, mirroring the
// showLink=true user entries — a registry edit cannot stale this test.
const registry: { name: string; category: string; showLink: boolean }[] =
await (await page.request.get("/services.json")).json();
const expectedUserLinks = registry.filter((s) => s.category === "user" && s.showLink).length;
await page.goto("/");
await expect(page.locator("#grid a.open")).toHaveCount(expectedUserLinks);
const hrefs = await page
.locator("#grid a.open")
.evaluateAll((els) => els.map((el) => ({ href: (el as HTMLAnchorElement).href, label: el.textContent ?? "" })));
const failures: string[] = [];
for (const link of hrefs) {
const target = new URL(link.href);
try {
await page.goto(link.href);
await settleThroughGuard(page, target.hostname);
const resp = await page.request.get(page.url());
if (resp.status() >= 500) {
failures.push(`${link.label}: ${page.url()} answered ${resp.status()}`);
}
} catch (err) {
failures.push(`${link.label}: ${err instanceof Error ? err.message : String(err)}`);
}
await backHome(page);
await expect(page.locator("#grid a.open")).toHaveCount(expectedUserLinks);
}
expect(failures, failures.join("\n")).toEqual([]);
});