Files
serverAppConfigs/tests/e2e/specs/10-homepage.spec.ts
T
Frank 3c5d77d1fa e2e: count fused Model Information names in the user homepage spec
The Protoss model cards render as .mi-name inside one full-width panel;
the panel-name census now counts both render styles, keeping services.json
the single source of truth.
2026-10-08 02:51:09 -07:00

55 lines
2.6 KiB
TypeScript

import { expect, test } from "@playwright/test";
// The user-facing homepage as a standard (non-admin) authenticated user.
// Expected panels derive from the registry the homepage itself renders
// (/services.json), so a registry edit updates the test instead of breaking it.
test("user homepage renders only user-category services", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/frank — services/, { timeout: 60_000 });
const registry: { name: string; category: string }[] = await (await page.request.get("/services.json")).json();
const userNames = registry.filter((s) => s.category === "user").map((s) => s.name);
// Panels hydrate client-side; poll the count, then compare exact sets.
// The three Protoss model cards are fused into one Model Information
// component: each model renders a `.mi-name` inside a full-width panel,
// not a standalone `.panel .name`. Count both render styles so the
// registry stays the single source of truth for the expected names.
const nameSel = "#grid .panel .name, #grid .mi-name";
await expect
.poll(() => page.locator(nameSel).count(), { timeout: 60_000 })
.toBe(userNames.length);
const names = await page.locator(nameSel).allTextContents();
expect([...names].sort()).toEqual([...userNames].sort());
// RBAC tripwire: an admin-category panel in the user grid means the gate leaks.
expect(names).not.toContain("Grafana");
});
test("user homepage exposes exactly the registry's open links", async ({ page }) => {
const registry: { name: string; category: string; showLink: boolean }[] = await (
await page.request.get("/services.json")
).json();
const openCount = registry.filter((s) => s.category === "user" && s.showLink).length;
await page.goto("/");
await expect(page.locator("#grid a.open")).toHaveCount(openCount, { timeout: 60_000 });
const hrefs = await page.locator("#grid a.open").evaluateAll((els) =>
els.map((el) => (el as HTMLAnchorElement).getAttribute("href")),
);
for (const href of hrefs) {
expect(href, "links use current vhost names").not.toMatch(/chat\.|jellyfin\.|comfyui\./);
}
});
test("standard user is denied the admin panel (RBAC)", async ({ page }) => {
const resp = await page.request.get("/admin/");
expect(resp.status()).toBe(403);
expect(await resp.text()).toContain("Forbidden: homepage admins only");
// The footer link a standard user can click also dead-ends at the gate.
await page.goto("/");
await page.locator('footer a[href="/admin/"]').click();
await expect(page.locator("body")).toContainText("Forbidden: homepage admins only");
});