mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-30 04:22:02 +00:00
Merge pull request #18533 from BerriAI/litellm_ui_e2e_2
[Refactor] UI - E2E Tests: Parity With Legacy Tests
This commit is contained in:
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user