mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-30 01:52:18 +00:00
test(e2e): cover Team Admin view + member + key flows
Adds a new spec exercising the previously-uncovered team-admin manual-QA items: viewing all team keys (including other members'), adding a member, removing a member, and creating a team key with All Team Models. Also seeds a dedicated invitee user so the add-member test can run in parallel with the proxy-admin invite test without colliding on the team roster.
This commit is contained in:
parent
ea015332d8
commit
1fca23c44f
2 changed files with 118 additions and 0 deletions
|
|
@ -33,6 +33,7 @@ VALUES
|
|||
('e2e-internal-viewer', 'viewer@test.local', 'internal_user_viewer', '{"e2e-team-crud"}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr'),
|
||||
('e2e-team-admin', 'teamadmin@test.local', 'internal_user', '{"e2e-team-crud","e2e-team-delete"}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr'),
|
||||
('e2e-invitable-user', 'invitable@test.local', 'internal_user', '{}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr'),
|
||||
('e2e-invitable-by-team-admin', 'invitable-team@test.local', 'internal_user', '{}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr'),
|
||||
('e2e-removable-member', 'removable@test.local', 'internal_user', '{"e2e-team-crud"}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr');
|
||||
|
||||
-- 5. Teams (members_with_roles is required JSON)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import {
|
||||
E2E_INTERNAL_USER_KEY_ALIAS,
|
||||
E2E_TEAM_CRUD_ALIAS,
|
||||
E2E_TEAM_CRUD_ID,
|
||||
TEAM_ADMIN_STORAGE_PATH,
|
||||
} from "../../constants";
|
||||
import { Page } from "../../fixtures/pages";
|
||||
import { navigateToPage, dismissFeedbackPopup } 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("Team Admin", () => {
|
||||
test.use({ storageState: TEAM_ADMIN_STORAGE_PATH });
|
||||
|
||||
test("Team admin can see all team keys including internal user keys", async ({ page }) => {
|
||||
// Step from the manual-QA checklist: navigate into the team info page,
|
||||
// open the Virtual Keys tab, and confirm a key belonging to another
|
||||
// team member (the seeded internal user) is visible.
|
||||
await navigateToPage(page, Page.Teams);
|
||||
await dismissFeedbackPopup(page);
|
||||
|
||||
await clickTeamId(page, E2E_TEAM_CRUD_ID);
|
||||
|
||||
await page.getByRole("tab", { name: "Virtual Keys" }).click();
|
||||
await expect(page.getByText(E2E_INTERNAL_USER_KEY_ALIAS).first())
|
||||
.toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// And from the global Virtual Keys page, the same key should be visible.
|
||||
await navigateToPage(page, Page.ApiKeys);
|
||||
await expect(page.getByText(E2E_INTERNAL_USER_KEY_ALIAS).first())
|
||||
.toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("Team admin can add a member to their team", async ({ page }) => {
|
||||
await navigateToPage(page, Page.Teams);
|
||||
await dismissFeedbackPopup(page);
|
||||
|
||||
await clickTeamId(page, E2E_TEAM_CRUD_ID);
|
||||
|
||||
await page.getByRole("tab", { name: "Members" }).click();
|
||||
await page.getByRole("button", { name: /Add Member/i }).click();
|
||||
|
||||
const modal = page.locator(".ant-modal:visible");
|
||||
await expect(modal).toBeVisible({ timeout: 5_000 });
|
||||
|
||||
// Use a dedicated invitee user so this doesn't race with the proxy-admin
|
||||
// "Invite a user" test that adds invitable@test.local to the same team.
|
||||
await modal.locator(".ant-select").first().click();
|
||||
await page.keyboard.type("invitable-team@test.local");
|
||||
|
||||
const emailOption = page.getByRole("option", { name: "invitable-team@test.local" }).first();
|
||||
await expect(emailOption).toBeAttached({ timeout: 10_000 });
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await modal.getByRole("button", { name: /Add Member/i }).click();
|
||||
|
||||
await expect(page.getByText(/member.*added|success/i).first())
|
||||
.toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("Team admin can remove a member from their team", async ({ page }) => {
|
||||
await navigateToPage(page, Page.Teams);
|
||||
await dismissFeedbackPopup(page);
|
||||
|
||||
await clickTeamId(page, E2E_TEAM_CRUD_ID);
|
||||
|
||||
await page.getByRole("tab", { name: "Members" }).click();
|
||||
|
||||
// Find the row for the seeded removable member and click its delete icon.
|
||||
const row = page.locator("tr", { hasText: "removable@test.local" }).first();
|
||||
await expect(row).toBeVisible({ timeout: 10_000 });
|
||||
// The trash icon sits in the Actions column at the end of the row.
|
||||
await row.locator("svg, img").last().click();
|
||||
|
||||
const modal = page.locator(".ant-modal:visible");
|
||||
await expect(modal).toBeVisible({ timeout: 5_000 });
|
||||
await modal.getByRole("button", { name: /^Delete$/ }).click();
|
||||
|
||||
await expect(page.getByText(/removed|deleted|success/i).first())
|
||||
.toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("Team admin can create a team key with All Team Models", async ({ page }) => {
|
||||
await navigateToPage(page, Page.ApiKeys);
|
||||
await dismissFeedbackPopup(page);
|
||||
|
||||
await page.getByRole("button", { name: /Create New Key/i }).click();
|
||||
await expect(page.getByText("Key Ownership")).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
const keyName = `e2e-team-admin-key-${Date.now()}`;
|
||||
await page.getByTestId("base-input").fill(keyName);
|
||||
|
||||
// Team selector — same locator pattern as the proxy-admin keys test.
|
||||
const teamSelect = page.locator(".ant-select", { hasText: "Search or select a team" });
|
||||
await teamSelect.click();
|
||||
await page.keyboard.type(E2E_TEAM_CRUD_ALIAS);
|
||||
await page.locator(".ant-select-dropdown:visible").getByText(E2E_TEAM_CRUD_ALIAS).first().click();
|
||||
|
||||
// Models — pick "All Team Models"
|
||||
await page.locator(".ant-select-selection-overflow").click();
|
||||
await page.locator(".ant-select-dropdown:visible").getByText("All Team Models").click();
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page.getByRole("button", { name: "Create Key", exact: true }).click();
|
||||
|
||||
await expect(page.getByText("Save your Key")).toBeVisible({ timeout: 10_000 });
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await expect(page.getByText(keyName)).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue