Merge pull request #18533 from BerriAI/litellm_ui_e2e_2

[Refactor] UI - E2E Tests: Parity With Legacy Tests
This commit is contained in:
yuneng-jiang
2025-12-30 17:31:42 -08:00
committed by GitHub
4 changed files with 154 additions and 1 deletions
@@ -20,7 +20,7 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:3000",
baseURL: "http://localhost:4000",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
@@ -0,0 +1,11 @@
import { test, expect } from "@playwright/test";
test.describe("Authentication Checks", () => {
test("should redirect unauthenticated user from a protected page", async ({ page }) => {
const protectedPageUrl = "http://localhost:4000/ui?page=llm-playground";
const expectedRedirectUrl = "http://localhost:4000/ui/login/";
await page.goto(protectedPageUrl, { waitUntil: "domcontentloaded" });
await expect(page).toHaveURL(expectedRedirectUrl);
await expect(page.getByRole("heading", { name: "Login" })).toBeVisible();
});
});
@@ -0,0 +1,90 @@
import { test, expect, Page } from "@playwright/test";
test.describe("Internal Users Search", () => {
test.use({ storageState: "admin.storageState.json" });
async function goToInternalUsers(page: Page) {
await page.goto("http://localhost:4000/ui");
const tab = page.getByRole("menuitem", { name: "Internal User" });
await expect(tab).toBeVisible();
await tab.click();
await expect(page.locator("tbody tr").first()).toBeVisible();
await expect(page.locator(".ant-skeleton")).toHaveCount(0);
}
test("can search users by email", async ({ page }) => {
await goToInternalUsers(page);
const rows = page.locator("tbody tr");
const searchInput = page.getByPlaceholder("Search by email...");
await expect(searchInput).toBeVisible();
// Ensure initial data is loaded
const initialCount = await rows.count();
expect(initialCount).toBeGreaterThan(0);
// 🔹 Apply filter + wait for backend response
await Promise.all([
page.waitForResponse(
(res) =>
res.url().includes("/user/list") &&
res.url().includes("user_email=test%40") && // encoded "test@"
res.status() === 200,
),
searchInput.fill("test@"),
]);
await page.waitForTimeout(5000);
const filteredCount = await rows.count();
await expect(filteredCount).toBeLessThan(initialCount);
// 🔹 Clear filter + wait for unfiltered request
await Promise.all([
page.waitForResponse(
(res) => res.url().includes("/user/list") && !res.url().includes("user_email=") && res.status() === 200,
),
searchInput.clear(),
]);
const resetCount = await rows.count();
await expect(resetCount).toBe(initialCount);
});
test("can filter users by user ID and SSO ID", async ({ page }) => {
await goToInternalUsers(page);
const rows = page.locator("tbody tr");
// Ensure initial data is loaded
const initialCount = await rows.count();
expect(initialCount).toBeGreaterThan(0);
const filtersButton = page.getByRole("button", {
name: "Filters",
exact: true,
});
await filtersButton.click();
const userIdInput = page.getByPlaceholder("Filter by User ID");
const ssoIdInput = page.getByPlaceholder("Filter by SSO ID");
await Promise.all([
page.waitForResponse(
(res) => res.url().includes("/user/list") && res.url().includes("user_ids=user") && res.status() === 200,
),
userIdInput.fill("user"),
]);
await Promise.all([
page.waitForResponse(
(res) =>
res.url().includes("/user/list") &&
res.url().includes("user_ids=user") &&
res.url().includes("sso_user_ids=sso") &&
res.status() === 200,
),
ssoIdInput.fill("sso"),
]);
const combinedFilteredCount = await rows.count();
await expect(combinedFilteredCount).toBeLessThan(initialCount);
});
});
@@ -0,0 +1,52 @@
import { test, expect, Page } from "@playwright/test";
test.describe("Internal Users Page", () => {
test.use({ storageState: "admin.storageState.json" });
async function goToInternalUsers(page: Page) {
await page.goto("http://localhost:4000/ui");
const internalUserTab = page.getByRole("menuitem", { name: "Internal User" });
await expect(internalUserTab).toBeVisible();
await internalUserTab.click();
const firstRow = page.locator("tbody tr").first();
await expect(firstRow).toBeVisible();
await expect(page.locator(".ant-skeleton")).toHaveCount(0);
}
test("renders internal users table correctly", async ({ page }) => {
await goToInternalUsers(page);
const rows = page.locator("tbody tr");
const rowCount = await rows.count();
expect(rowCount).toBeGreaterThan(0);
const userIdHeader = page.getByRole("columnheader", { name: "User ID" });
await expect(userIdHeader).toBeVisible();
const virtualKeysHeader = page.getByRole("columnheader", { name: "Virtual Keys" });
await expect(virtualKeysHeader).toBeVisible();
});
test("pagination controls work correctly", async ({ page }) => {
await goToInternalUsers(page);
const paginationInfo = page.locator(".text-sm.text-gray-700");
const prevButton = page.getByRole("button", { name: "Previous" });
const nextButton = page.getByRole("button", { name: "Next" });
const infoText = (await paginationInfo.textContent()) || "";
// On first page, Previous should be disabled
if (infoText.includes("1 -")) {
await expect(prevButton).toBeDisabled();
}
// Check if there are more pages
const hasMorePages = infoText.includes("of") && !infoText.endsWith("25 of 25");
if (hasMorePages) {
await expect(nextButton).toBeEnabled();
}
});
});