diff --git a/ui/litellm-dashboard/e2e_tests/helpers/navigation.ts b/ui/litellm-dashboard/e2e_tests/helpers/navigation.ts index 3eb0dc9b24..556e964842 100644 --- a/ui/litellm-dashboard/e2e_tests/helpers/navigation.ts +++ b/ui/litellm-dashboard/e2e_tests/helpers/navigation.ts @@ -23,3 +23,15 @@ export async function dismissFeedbackPopup(page: PlaywrightPage): Promise await expect(dismissButton).not.toBeVisible({ timeout: 2_000 }).catch(() => {}); } } + +/** + * Click on a team ID in the table. Team IDs are rendered differently depending + * on the component version — try button first (Tremor Button), fall back to + * clickable span (OldTeams Typography.Text). + */ +export async function clickTeamId(page: PlaywrightPage, teamId: string): Promise { + 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 }); +} diff --git a/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUser.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUser.spec.ts new file mode 100644 index 0000000000..c706ae0aef --- /dev/null +++ b/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUser.spec.ts @@ -0,0 +1,55 @@ +import { test, expect } from "@playwright/test"; +import { + E2E_INTERNAL_USER_KEY_ALIAS, + E2E_TEAM_CRUD_ALIAS, + E2E_TEAM_CRUD_ID, + INTERNAL_USER_STORAGE_PATH, +} from "../../constants"; +import { Page } from "../../fixtures/pages"; +import { navigateToPage, clickTeamId } from "../../helpers/navigation"; + +test.describe("Internal User", () => { + test.use({ storageState: INTERNAL_USER_STORAGE_PATH }); + + test("Create Key modal shows the team dropdown populated with the user's teams", async ({ page }) => { + await navigateToPage(page, Page.ApiKeys); + + await page.getByRole("button", { name: /Create New Key/i }).click(); + await expect(page.getByText("Key Ownership")).toBeVisible({ timeout: 10_000 }); + + // Open the team dropdown — seeded internal user is a member of + // e2e-team-crud and e2e-team-org, so we expect at least the CRUD alias. + const teamSelect = page.locator(".ant-select", { hasText: "Search or select a team" }); + await teamSelect.click(); + await page.keyboard.type(E2E_TEAM_CRUD_ALIAS); + await expect( + page.locator(".ant-select-dropdown:visible").getByText(E2E_TEAM_CRUD_ALIAS).first(), + ).toBeVisible({ timeout: 5_000 }); + }); + + test("Team info page omits the Settings tab for non-admin members", async ({ page }) => { + await navigateToPage(page, Page.Teams); + + await clickTeamId(page, E2E_TEAM_CRUD_ID); + + // Overview / My User / Virtual Keys are always visible; Settings is gated + // on canEditTeam and must NOT render for a regular team member. + await expect(page.getByRole("tab", { name: "Overview" })).toBeVisible({ timeout: 5_000 }); + await expect(page.getByRole("tab", { name: "Settings" })).not.toBeVisible(); + await expect(page.getByRole("tab", { name: "Members" })).not.toBeVisible(); + }); + + test("Virtual Keys page does not surface litellm-dashboard team keys", async ({ page }) => { + await navigateToPage(page, Page.ApiKeys); + + // Anchor on the user's own seeded key so the absence check below cannot + // pass vacuously against an empty table. + await expect( + page.locator("table tbody").getByText(E2E_INTERNAL_USER_KEY_ALIAS).first(), + ).toBeVisible({ timeout: 10_000 }); + + // The litellm-dashboard team is the proxy's internal bookkeeping team — + // its keys must never leak into an internal user's Virtual Keys table. + await expect(page.locator("table tbody").getByText("litellm-dashboard")).toHaveCount(0); + }); +}); diff --git a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts index 6f6e837339..4774b50dbc 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts @@ -7,19 +7,7 @@ import { E2E_TEAM_ORG_ID, } from "../../constants"; import { Page } from "../../fixtures/pages"; -import { navigateToPage, dismissFeedbackPopup } from "../../helpers/navigation"; - -/** - * Click on a team ID in the table. Team IDs are rendered differently depending - * on the component version — try button first (Tremor Button), fall back to - * clickable span (OldTeams Typography.Text). - */ -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 }); -} +import { navigateToPage, dismissFeedbackPopup, clickTeamId } from "../../helpers/navigation"; test.describe("Proxy Admin - Teams", () => { test.use({ storageState: ADMIN_STORAGE_PATH });