SPB Git

spb/akos

Public
3.9 KB · 81 lines typescript
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
Files · main
Menu

Theme