import { expect, test } from "@playwright/test"; // open-notebook (notebook.franksdank.cloud), deployed 2026-10-19, is a guarded // vhost whose outpost (notebook-proxy) fronts an app with its OWN password // login - the same shape as hermes (17-) and OpenWebUI (18-): the guard chain // (Anubis PoW -> authentik outpost) must hand off to the app, and the app's own // login page is the expected landing. Until now this host appeared in neither // the 13-/14- GUARDED lists nor any dedicated spec, so a broken upstream would // have stayed invisible - the exact 17-hermes "uncovered vhost" failure class. // 14- now covers the unauthenticated gate; this spec owns authenticated // reachability with a POSITIVE app marker, not just "not a login wall". const VHOST = "https://notebook.franksdank.cloud/"; const CHALLENGE = "Making sure you're not a bot"; test("notebook vhost reaches the open-notebook app through the guard chain", async ({ page }) => { test.setTimeout(180_000); await page.goto(VHOST); // The Anubis proof-of-work clears in a real browser and the outpost hands off // to the app; the user must never be parked on an authentik flow. 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("notebook.franksdank.cloud"); expect( new URL(page.url()).pathname.startsWith("/if/"), "must not be parked on an authentik flow", ).toBe(false); expect( await page.locator('input[name="uidField"]').count(), "must not show the authentik login form", ).toBe(0); // Positive identity: the page really is open-notebook's own login, not a // Caddy error or the IdP shell. The app answering its own login is the pass. await expect(page, "destination title must be Open Notebook").toHaveTitle(/open notebook/i, { timeout: 30_000, }); await expect(page.locator("body"), "open-notebook login marker").toContainText( /enter your password/i, { timeout: 30_000 }, ); const body = await page.locator("body").innerText(); expect(body, "must not leak the IdP flow shell").not.toContain("Powered by authentik"); expect(body, "must not be a Caddy upstream error").not.toContain("Bad Gateway"); const resp = await page.request.get(VHOST); expect(resp.status(), "open-notebook must answer, not error").toBeLessThan(500); });