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 // the app probes /api/v1/auth/me on load; a 401 before login is expected and not an error21 page.on("console", (m) => { if (m.type() === "error" && !/status of 401/.test(m.text())) errors.push(m.text()); });22 page.on("pageerror", (e) => errors.push(String(e)));23 await page.goto("/login");24 await page.getByLabel("Email").fill(EMAIL);25 await page.getByLabel("Password").fill(PASSWORD);26 await page.getByRole("button", { name: "Sign in" }).click();27 await expect(page).toHaveURL(/\/$/);28 });29 test.afterEach(() => { expect(errors, errors.join("\n")).toEqual([]); errors.length = 0; });3031 test("overview → apis → api detail", async ({ page }) => {32 await noJunk(page);33 await expect(page.getByRole("heading", { name: "Overview" })).toBeVisible();34 await expect(page.locator(".tile").first()).toBeVisible();35 await page.getByRole("button", { name: "Open navigation" }).click();36 await expect(page.locator("#drawer.open")).toBeVisible();37 await page.locator("#drawer").getByRole("link", { name: "API Atlas" }).click();38 await expect(page.getByRole("heading", { name: "API Atlas" })).toBeVisible();39 await noJunk(page);40 const first = page.locator("table.tbl tbody tr a").first();41 await expect(first).toBeVisible();42 await first.click();43 await noJunk(page);44 await expect(page.getByRole("tab", { name: "Endpoints" })).toBeVisible();45 await page.getByRole("tab", { name: "Evidence" }).click();46 await noJunk(page);47 });4849 test("executions → detail", async ({ page }) => {50 await page.goto("/executions");51 await noJunk(page);52 const first = page.locator("table.tbl tbody tr a").first();53 await expect(first).toBeVisible();54 await first.click();55 await noJunk(page);56 await expect(page.getByRole("heading", { name: /Execution/ })).toBeVisible();57 await expect(page.getByText("Policy decision")).toBeVisible();58 });5960 test("lineage, debt, policy, operations, settings", async ({ page }) => {61 for (const [path, heading] of [["/lineage", "Research Lineage"], ["/debt", "Knowledge Debt"], ["/policy", "Policy Center"], ["/operations", "Operations"], ["/settings", "Settings"]] as const) {62 await page.goto(path);63 await expect(page.getByRole("heading", { name: heading, level: 1 })).toBeVisible();64 await noJunk(page);65 }66 await page.getByRole("tab", { name: "Audit" }).click().catch(() => {});67 await page.goto("/operations");68 await page.getByRole("tab", { name: "Audit" }).click();69 await page.getByRole("button", { name: "Verify chain" }).click();70 await expect(page.getByText(/Chain intact/)).toBeVisible({ timeout: 20_000 });71 });7273 test("dark mode toggle persists", async ({ page }) => {74 await page.getByRole("button", { name: "Toggle dark mode" }).click();75 const theme = await page.evaluate(() => document.documentElement.getAttribute("data-theme"));76 expect(["dark", "light"]).toContain(theme);77 await page.reload();78 expect(await page.evaluate(() => document.documentElement.getAttribute("data-theme"))).toBe(theme);79 });80});81