import { expect, loginAs, test } from "./fixtures"; test.describe("management workflows, driven end to end from the browser", () => { test.beforeEach(async ({ page }) => { await loginAs(page, "hub-admin"); }); test("tool catalog pagination is real: Next/Previous move through disjoint, server-issued pages", async ({ page, }) => { await page.goto("/tools"); const firstRowId = () => page.locator("table tbody tr").first().locator("small").first().textContent(); const firstPageFirstId = await firstRowId(); await expect(page.getByRole("button", { name: "Previous page" })).toBeDisabled(); await page.getByRole("button", { name: "Next page" }).click(); await expect(page.locator("table tbody tr").first().locator("small").first()).not.toHaveText(firstPageFirstId ?? ""); const secondPageFirstId = await firstRowId(); await page.getByRole("button", { name: "Previous page" }).click(); await expect(page.locator("table tbody tr").first().locator("small").first()).toHaveText(firstPageFirstId ?? ""); await page.getByRole("button", { name: "Next page" }).click(); await expect(page.locator("table tbody tr").first().locator("small").first()).toHaveText(secondPageFirstId ?? ""); }); test("project detail renders real registered environments and connector bindings, not a fixed 'prod'", async ({ page, }) => { await page.goto("/projects/ludarium?tab=Environments"); await expect(page.getByLabel("Project detail sections").getByRole("link", { name: "Environments" })).toHaveAttribute("aria-current", "page"); await expect(page.getByLabel("Project environments", { exact: true }).getByRole("heading", { name: "prod", exact: true })).toBeVisible(); // ludarium never registered a staging environment (docs/known-state, and // tests/test_wp230_ui_completion.py's equivalent backend assertion). await expect(page.getByLabel("Project environments", { exact: true }).getByRole("heading", { name: "staging", exact: true })).toHaveCount(0); await page.getByLabel("Project detail sections").getByRole("link", { name: "Connectors" }).click(); await expect(page.getByLabel("Connector bindings", { exact: true }).getByRole("heading", { name: "knowledge", exact: true })).toBeVisible(); }); test("an unregistered project is a real 404, not a fabricated default", async ({ page }) => { await page.goto("/projects/does-not-exist"); await expect(page.getByRole("alert")).toBeVisible(); await expect(page.getByText(/is not registered/)).toBeVisible(); }); test("policy simulation is wired to the real engine: a known-allow case explains its decision", async ({ page, }) => { await page.goto("/policies"); await page.getByLabel("Project").selectOption({ label: "Ludarium" }); await page.getByLabel("Environment").selectOption("prod"); await page.getByLabel("Tool").selectOption({ label: "Knowledge Document Search" }); await page.getByRole("button", { name: "Explain decision" }).click(); const decisionCard = page.getByRole("status").filter({ hasText: "Matched:" }); await expect(decisionCard).toBeVisible(); await expect(decisionCard.getByText(/Trace [0-9a-f-]{8,}/)).toBeVisible(); await expect(decisionCard.getByText("Matched: none")).toHaveCount(0); }); test("catalog preview surfaces a real validation result, driven by a real project selector", async ({ page }) => { await page.goto("/tools"); await page.getByRole("button", { name: "Validate and preview" }).click(); const outcome = page.getByText(/Valid preview|Validation blocked/); await expect(outcome).toBeVisible({ timeout: 15_000 }); const applyButton = page.getByRole("button", { name: "Apply approved catalog" }); if (await outcome.filter({ hasText: "Valid preview" }).count()) { await expect(applyButton).toBeEnabled(); await applyButton.click(); await expect(page.getByRole("dialog")).toContainText("All registered project routes may be updated"); await page.getByRole("button", { name: "Confirm apply" }).click(); await expect(page.getByText(/Catalog reconciliation completed and verified/)).toBeVisible({ timeout: 15_000 }); } else { await expect(applyButton).toBeDisabled(); } }); test("connector state is communicated as visible text, not color alone", async ({ page }) => { await page.goto("/connectors"); await expect(page.locator("table tbody tr").first()).toBeVisible(); const statusCells = page.locator("table tbody tr td .status"); const count = await statusCells.count(); expect(count).toBeGreaterThan(0); for (let index = 0; index < count; index += 1) { const text = (await statusCells.nth(index).innerText()).trim(); expect(["Healthy", "Degraded", "Unavailable", "Unknown"]).toContain(text); } }); test("table density toggle changes row padding without changing row count or data", async ({ page }) => { await page.goto("/tools"); await expect(page.locator("table tbody tr").first()).toBeVisible(); const rowCountBefore = await page.locator("table tbody tr").count(); await page.getByRole("button", { name: "Compact" }).click(); await expect(page.locator(".table-wrap")).toHaveClass(/density-compact/); expect(await page.locator("table tbody tr").count()).toBe(rowCountBefore); await page.getByRole("button", { name: "Comfortable" }).click(); await expect(page.locator(".table-wrap")).toHaveClass(/density-comfortable/); }); test("density toggle exposes which option is selected, not just the applied state", async ({ page }) => { // Regression test for a real bug found during WP-235's visual polish // pass: the Stitch retokenization made .button's default look a solid // accent pill, which is also what the "active" class resolved to -- // Comfortable and Compact rendered identically, so neither the sighted // UI nor assistive tech could tell which density was selected. await page.goto("/tools"); const comfortable = page.getByRole("button", { name: "Comfortable" }); const compact = page.getByRole("button", { name: "Compact" }); await expect(comfortable).toHaveAttribute("aria-pressed", "true"); await expect(compact).toHaveAttribute("aria-pressed", "false"); await expect(compact).toHaveClass(/button-secondary/); await compact.click(); await expect(comfortable).toHaveAttribute("aria-pressed", "false"); await expect(compact).toHaveAttribute("aria-pressed", "true"); await expect(comfortable).toHaveClass(/button-secondary/); }); test("policy simulation renders a step-by-step decision trace, not just the top-line result", async ({ page }) => { await page.goto("/policies"); await page.getByLabel("Project").selectOption({ label: "Ludarium" }); await page.getByLabel("Environment").selectOption("prod"); await page.getByLabel("Tool").selectOption({ label: "Knowledge Document Search" }); await page.getByRole("button", { name: "Explain decision" }).click(); await expect(page.getByText("Decision logic trace")).toBeVisible(); const steps = page.locator(".trace-step"); await expect(steps.first()).toBeVisible(); expect(await steps.count()).toBeGreaterThan(0); // Every rendered step must carry a real phase name from the engine's // trace, not an empty placeholder row. const firstStepPhase = await steps.first().locator("strong").innerText(); expect(firstStepPhase.trim().length).toBeGreaterThan(0); }); test("generated client configuration never renders secret material into the DOM", async ({ page }) => { await page.goto("/clients"); await page.getByLabel("Name").fill("e2e-config-check"); await page.getByRole("button", { name: "Create pending client" }).click(); await expect(page.getByText(/Client created: e2e-config-check/)).toBeVisible(); await page.reload(); const targetRow = page.getByRole("row", { name: /e2e-config-check/ }); await expect(targetRow).toBeVisible(); await targetRow.getByRole("button", { name: "Config" }).click(); await expect(page.getByText(/Generated configuration for/)).toBeVisible(); const bodyText = await page.locator("body").innerText(); expect(bodyText).not.toMatch(/"secret"|client_secret|bearer [a-z0-9]{20,}/i); expect(bodyText).toContain("Secret material is write-only"); }); test("client onboarding does not claim that the Hub provisions an Authentik identity", async ({ page }) => { await page.goto("/clients"); await expect(page.getByText(/External identity provisioning is a separate administrator task/)).toBeVisible(); await expect(page.getByText(/Authentik hand-off reference/)).toHaveCount(0); }); test("Operations page renders the same real drift/backup/denial data as the dashboard, not a fabricated summary", async ({ page }) => { await page.goto("/"); const dashboardDrift = await page.locator(".metric-card", { hasText: "Reconcile drift" }).locator("strong").innerText(); await page.goto("/operations"); await expect(page.getByRole("heading", { name: "Operations" })).toBeVisible(); const operationsDrift = await page.locator(".metric-card", { hasText: "Reconcile drift" }).locator("strong").innerText(); expect(operationsDrift).toBe(dashboardDrift); }); test("tool readiness validates the full registered path without invoking the tool", async ({ page }) => { await page.goto("/tools"); await page.getByRole("button", { name: "Validate and preview" }).click(); await expect(page.getByText("Valid preview")).toBeVisible({ timeout: 15_000 }); await page.getByRole("button", { name: "Apply approved catalog" }).click(); await page.getByRole("button", { name: "Confirm apply" }).click(); await expect(page.getByText(/Catalog reconciliation completed and verified/)).toBeVisible({ timeout: 15_000 }); await page.getByLabel("Filter records").fill("Fleet Ops Operations Summary"); const row = page.getByRole("row", { name: /Fleet Ops Operations Summary/ }); await row.getByRole("button", { name: "Readiness" }).click(); await page.getByRole("dialog").getByLabel("Project").selectOption({ label: "Fleet Ops" }); await page.getByRole("dialog").getByLabel("Environment").selectOption("prod"); await page.getByRole("button", { name: "Run readiness check" }).click(); await expect(page.getByRole("dialog").getByText("Ready", { exact: true })).toBeVisible(); await expect(page.getByRole("dialog").getByText(/no MCP tool invocation was performed/i)).toBeVisible(); await expect(page.getByRole("dialog").getByText(/healthy/i).first()).toBeVisible(); }); test("closing a Modal returns keyboard focus to the element that opened it, not document.body", async ({ page }) => { // Regression test for a real bug found during WP-235 Task 12's manual // accessibility pass: Modal moved focus into the dialog on open but // never restored it on close, silently dropping keyboard users back to // document.body. Uses the tools "Readiness" button's Modal as an isolated, // side-effect-free trigger. await page.goto("/tools"); const trigger = page.locator("table tbody tr").first().getByRole("button", { name: "Readiness" }); await trigger.click(); await expect(page.getByRole("dialog")).toBeVisible(); await page.keyboard.press("Escape"); await expect(page.getByRole("dialog")).toHaveCount(0); await expect(trigger).toBeFocused(); }); test("an open Modal locks background scroll and traps keyboard focus", async ({ page }) => { await page.goto("/tools"); await page.locator("table tbody tr").first().getByRole("button", { name: "Readiness" }).click(); const dialog = page.getByRole("dialog"); await expect(dialog).toBeVisible(); await expect.poll(() => page.evaluate(() => document.body.style.overflow)).toBe("hidden"); await page.keyboard.press("Control+K"); await expect(page.getByRole("dialog")).toHaveCount(1); await expect(page.getByRole("dialog", { name: "Quick jump" })).toHaveCount(0); const close = dialog.getByRole("button", { name: "Close dialog" }); const readiness = dialog.getByRole("button", { name: "Run readiness check" }); await expect(readiness).toBeEnabled(); await close.focus(); await page.keyboard.press("Shift+Tab"); await expect(readiness).toBeFocused(); await page.keyboard.press("Tab"); await expect(close).toBeFocused(); await close.click(); await expect.poll(() => page.evaluate(() => document.body.style.overflow)).toBe(""); }); test("Operations page shows an honest placeholder for approvals, not a fabricated queue", async ({ page }) => { await page.goto("/operations"); await expect(page.getByRole("heading", { name: "Approvals" })).toBeVisible(); await expect(page.getByText(/Approval workflow is not yet part of the authorized v1 management surface/)).toBeVisible(); }); });