mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-05 14:23:44 +00:00
test(e2e): cover Internal User key modal, team info, key page (#29074)
* test(e2e): cover Internal User key modal, team info, key page Three previously-uncovered manual-QA paths for the Internal User role: - Create Key modal — confirm the team dropdown is populated with the user's teams (verifying the role-scoped UI flow exists). - Team info page — confirm the Settings/Members tabs are hidden for a regular team member; only the read-only tabs render. - Virtual Keys page — confirm the proxy's internal litellm-dashboard team keys never leak into an internal user's table. * test(e2e): share clickTeamId helper, strengthen key-filter assertion Address review feedback on the Internal User e2e spec: - Extract clickTeamId into helpers/navigation.ts; import in both internalUser and teams specs instead of duplicating it. - Anchor the litellm-dashboard absence check on the user's own seeded key so it cannot pass vacuously against an empty table. - Drop redundant dismissFeedbackPopup calls (navigateToPage already dismisses internally).
This commit is contained in:
@@ -23,3 +23,15 @@ export async function dismissFeedbackPopup(page: PlaywrightPage): Promise<void>
|
||||
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<void> {
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user