1/** Mobile (iPhone 14, 390×844) end-to-end walk of the AKOS dashboard against a live deployment.2 * Env: AKOS_BASE_URL, AKOS_E2E_EMAIL, AKOS_E2E_PASSWORD. Asserts real data renders (no "undefined"/"NaN"), no console errors, drawer works. */3import { test, expect, type Page } from "@playwright/test";45const EMAIL = process.env.AKOS_E2E_EMAIL ?? "";6const PASSWORD = process.env.AKOS_E2E_PASSWORD ?? "";78async function noJunk(page: Page) {9 await page.waitForLoadState("networkidle");10 await expect(page.locator(".loading")).toHaveCount(0, { timeout: 20_000 });11 const text = await page.locator("main").innerText();12 expect(text).not.toMatch(/\bundefined\b/);13 expect(text).not.toMatch(/\bNaN\b/);14 expect(text).not.toMatch(/\[object Object\]/);15}1617test.describe("AKOS mobile dashboard", () => {18 const errors: string[] = [];19 test.beforeEach(async ({ page }) => {20 page.on("console", (m) => { if (m.type() === "error") errors.push(m.text()); });21 page.on("pageerror", (e) => errors.push(String(e)));22 await page.goto("/login");23 await page.getByLabel("Email").fill(EMAIL);24 await page.getByLabel("Password").fill(PASSWORD);25 await page.getByRole("button", { name: "Sign in" }).click();26 await expect(page).toHaveURL(/\/$/);27 });28 test.afterEach(() => { expect(errors, errors.join("\n")).toEqual([]); errors.length = 0; });2930 test("overview → apis → api detail", async ({ page }) => {31 await noJunk(page);32 await expect(page.getByRole("heading", { name: "Overview" })).toBeVisible();33 await expect(page.locator(".tile").first()).toBeVisible();34 await page.getByRole("button", { name: "Open navigation" }).click();35 await expect(page.locator("#drawer.open")).toBeVisible();36 await page.locator("#drawer").getByRole("link", { name: "API Atlas" }).click();37 await expect(page.getByRole("heading", { name: "API Atlas" })).toBeVisible();38 await noJunk(page);39 const first = page.locator("table.tbl tbody tr a").first();40 await expect(first).toBeVisible();41 await first.click();42 await noJunk(page);43 await expect(page.getByRole("tab", { name: "Endpoints" })).toBeVisible();44 await page.getByRole("tab", { name: "Evidence" }).click();45 await noJunk(page);46 });4748 test("executions → detail", async ({ page }) => {49 await page.goto("/executions");50 await noJunk(page);51 const first = page.locator("table.tbl tbody tr a").first();52 await expect(first).toBeVisible();53 await first.click();54 await noJunk(page);55 await expect(page.getByRole("heading", { name: /Execution/ })).toBeVisible();56 await expect(page.getByText("Policy decision")).toBeVisible();57 });5859 test("lineage, debt, policy, operations, settings", async ({ page }) => {60 for (const [path, heading] of [["/lineage", "Research Lineage"], ["/debt", "Knowledge Debt"], ["/policy", "Policy Center"], ["/operations", "Operations"], ["/settings", "Settings"]] as const) {61 await page.goto(path);62 await expect(page.getByRole("heading", { name: heading, level: 1 })).toBeVisible();63 await noJunk(page);64 }65 await page.getByRole("tab", { name: "Audit" }).click().catch(() => {});66 await page.goto("/operations");67 await page.getByRole("tab", { name: "Audit" }).click();68 await page.getByRole("button", { name: "Verify chain" }).click();69 await expect(page.getByText(/Chain intact/)).toBeVisible({ timeout: 20_000 });70 });7172 test("dark mode toggle persists", async ({ page }) => {73 await page.getByRole("button", { name: "Toggle dark mode" }).click();74 const theme = await page.evaluate(() => document.documentElement.getAttribute("data-theme"));75 expect(["dark", "light"]).toContain(theme);76 await page.reload();77 expect(await page.evaluate(() => document.documentElement.getAttribute("data-theme"))).toBe(theme);78 });79});80