test(e2e): cover Internal Viewer nav, key, and team-info gating (#29075)

* test(e2e): cover Internal Viewer nav, key, and team-info gating

Three previously-uncovered manual-QA paths for the Internal Viewer role:

- Nav only renders the read-only sections; admin-only items
  (Internal Users, Organizations, Models + Endpoints) stay hidden.
- Virtual Keys page hides Create New Key, and the key detail view
  hides Regenerate / Reset Spend / Delete actions.
- Team info page hides Members and Settings tabs for the viewer.

* test(e2e): scope viewer nav to sidebar, strengthen tab assertions

Address review feedback on the Internal Viewer e2e spec:
- Scope the nav test to the sidebar complementary landmark and match
  items by link role + accessible name. The prior CSS nav, aside
  selector grabbed the top bar (the sidebar is a complementary
  landmark, not a <nav> tag), so the assertions never hit the real
  nav links.
- Land via navigateToPage so the networkidle wait settles the
  role-gated nav before asserting.
- Assert the Virtual Keys tab is visible (was only commented).
- Use toHaveCount(0) for hidden team tabs to match the nav block;
  tabs are conditionally rendered, not CSS-hidden.
- Drop redundant dismissFeedbackPopup calls (navigateToPage already
  dismisses internally).
This commit is contained in:
ryan-crabbe-berri 2026-05-29 14:36:06 -07:00 committed by GitHub
parent a55817cbc6
commit 10bda4456a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -0,0 +1,90 @@
import { test, expect } from "@playwright/test";
import {
E2E_TEAM_CRUD_ID,
E2E_VIEWER_KEY_ALIAS,
INTERNAL_VIEWER_STORAGE_PATH,
} from "../../constants";
import { Page } from "../../fixtures/pages";
import { navigateToPage } from "../../helpers/navigation";
async function clickTeamId(page: import("@playwright/test").Page, teamId: string) {
const cell = page.locator("td").filter({ hasText: teamId }).first();
await expect(cell).toBeVisible({ timeout: 10_000 });
await cell.click();
await expect(page.getByText("Back to Teams")).toBeVisible({ timeout: 10_000 });
}
test.describe("Internal Viewer", () => {
test.use({ storageState: INTERNAL_VIEWER_STORAGE_PATH });
test("Nav shows only the allowed options for the Internal Viewer role", async ({ page }) => {
// Use navigateToPage so the networkidle wait lets the async role-gated nav
// settle before we assert — a bare page.goto races the permission fetch.
await navigateToPage(page, Page.ApiKeys);
// Scope to the sidebar and match items by their link role + accessible
// name. The sidebar is a `complementary` landmark (the `navigation` role
// is the top bar), and each item renders as a link inside it — far tighter
// than a CSS `nav, aside` selector or a getByText on stray text nodes.
const nav = page.getByRole("complementary");
// Items that must be visible per the manual-QA checklist
const expectedVisible = [
"Virtual Keys",
"MCP Servers",
"Guardrails",
"Usage",
"Logs",
"Teams",
"API Reference",
"AI Hub",
];
for (const label of expectedVisible) {
await expect(
nav.getByRole("link", { name: label, exact: true }).first(),
`expected nav item "${label}" to render for Internal Viewer`,
).toBeVisible({ timeout: 5_000 });
}
// Items that must NOT be visible (admin-only surface)
const expectedHidden = ["Internal Users", "Organizations", "Models + Endpoints"];
for (const label of expectedHidden) {
await expect(
nav.getByRole("link", { name: label, exact: true }),
`nav item "${label}" must not render for Internal Viewer`,
).toHaveCount(0);
}
});
test("Virtual Keys page hides Create / Regenerate / Reset / Delete controls", async ({ page }) => {
await navigateToPage(page, Page.ApiKeys);
// Create button is gated on rolesWithWriteAccess (Internal Viewer is not in it)
await expect(page.getByRole("button", { name: /Create New Key/i })).toHaveCount(0);
// Open the viewer's own key info page
const keyRow = page.locator("tr", { hasText: E2E_VIEWER_KEY_ALIAS });
await expect(keyRow).toBeVisible({ timeout: 10_000 });
await keyRow.locator("button").first().click();
await expect(page.getByText("Back to Keys")).toBeVisible({ timeout: 10_000 });
// None of the destructive / mutating actions should render
await expect(page.getByRole("button", { name: "Regenerate Key" })).toHaveCount(0);
await expect(page.getByRole("button", { name: /Reset Spend/i })).toHaveCount(0);
await expect(page.getByRole("button", { name: "Delete Key" })).toHaveCount(0);
});
test("Team info page omits Members and Settings tabs for an Internal Viewer", async ({ page }) => {
await navigateToPage(page, Page.Teams);
await clickTeamId(page, E2E_TEAM_CRUD_ID);
// Overview / Virtual Keys are always visible; Settings + Members are not.
// Tabs are conditionally rendered (getTeamInfoVisibleTabs filters the list),
// so assert absence from the DOM with toHaveCount(0) to match the nav block.
await expect(page.getByRole("tab", { name: "Overview" })).toBeVisible({ timeout: 5_000 });
await expect(page.getByRole("tab", { name: "Virtual Keys" })).toBeVisible({ timeout: 5_000 });
await expect(page.getByRole("tab", { name: "Settings" })).toHaveCount(0);
await expect(page.getByRole("tab", { name: "Members" })).toHaveCount(0);
});
});