From c049f6a0734aa2864907aee8cf082d99cec60980 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Tue, 30 Dec 2025 12:19:25 -0800 Subject: [PATCH] Adding e2e tests for sidebar --- .gitignore | 3 +- .../e2e_tests/auth/login.setup.ts | 20 ----------- .../e2e_tests/fixtures/roles.ts | 6 ++++ .../e2e_tests/fixtures/users.ts | 10 ++++++ ui/litellm-dashboard/e2e_tests/globalSetup.ts | 18 ++++++++++ .../e2e_tests/playwright.config.ts | 1 + .../e2e_tests/tests/login/login.spec.ts | 13 +++++++ .../tests/navigation/sidebar.spec.ts | 34 +++++++++++++++++++ 8 files changed, 84 insertions(+), 21 deletions(-) delete mode 100644 ui/litellm-dashboard/e2e_tests/auth/login.setup.ts create mode 100644 ui/litellm-dashboard/e2e_tests/fixtures/roles.ts create mode 100644 ui/litellm-dashboard/e2e_tests/fixtures/users.ts create mode 100644 ui/litellm-dashboard/e2e_tests/globalSetup.ts create mode 100644 ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts create mode 100644 ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts diff --git a/.gitignore b/.gitignore index b6d4fd4419..9a4f666bf4 100644 --- a/.gitignore +++ b/.gitignore @@ -102,4 +102,5 @@ litellm/proxy/_experimental/out/guardrails/index.html scripts/test_vertex_ai_search.py LAZY_LOADING_IMPROVEMENTS.md **/test-results -**/playwright-report \ No newline at end of file +**/playwright-report +**/*.storageState.json \ No newline at end of file diff --git a/ui/litellm-dashboard/e2e_tests/auth/login.setup.ts b/ui/litellm-dashboard/e2e_tests/auth/login.setup.ts deleted file mode 100644 index 712191f684..0000000000 --- a/ui/litellm-dashboard/e2e_tests/auth/login.setup.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { test, expect } from "@playwright/test"; - -test("login and save auth state", async ({ page }) => { - await page.goto("http://localhost:4000/ui"); - - await page.getByPlaceholder("Enter your username").fill("admin"); - await page.getByPlaceholder("Enter your password").fill("gm"); - - const loginButton = page.getByRole("button", { name: "Login" }); - await expect(loginButton).toBeEnabled(); - await loginButton.click(); - - // Assert successful login (important) - await expect(page.getByText("AI Gateway")).toBeVisible(); - - // 🔐 Save auth state - await page.context().storageState({ - path: "storageState.json", - }); -}); diff --git a/ui/litellm-dashboard/e2e_tests/fixtures/roles.ts b/ui/litellm-dashboard/e2e_tests/fixtures/roles.ts new file mode 100644 index 0000000000..913230ad44 --- /dev/null +++ b/ui/litellm-dashboard/e2e_tests/fixtures/roles.ts @@ -0,0 +1,6 @@ +export enum Role { + ProxyAdmin = "proxy_admin", + ProxyAdminViewer = "proxy_admin_viewer", + InternalUser = "internal_user", + InternalUserViewer = "internal_user_viewer", +} diff --git a/ui/litellm-dashboard/e2e_tests/fixtures/users.ts b/ui/litellm-dashboard/e2e_tests/fixtures/users.ts new file mode 100644 index 0000000000..d1f1eab00e --- /dev/null +++ b/ui/litellm-dashboard/e2e_tests/fixtures/users.ts @@ -0,0 +1,10 @@ +import { Role } from "./roles"; + +const isCI = !!process.env.CI; + +export const users = { + [Role.ProxyAdmin]: { + email: "admin", + password: isCI ? "gm" : "sk-1234", + }, +}; diff --git a/ui/litellm-dashboard/e2e_tests/globalSetup.ts b/ui/litellm-dashboard/e2e_tests/globalSetup.ts new file mode 100644 index 0000000000..a725c58f35 --- /dev/null +++ b/ui/litellm-dashboard/e2e_tests/globalSetup.ts @@ -0,0 +1,18 @@ +import { chromium } from "@playwright/test"; +import { users } from "./fixtures/users"; +import { Role } from "./fixtures/roles"; + +async function globalSetup() { + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.goto("http://localhost:4000/ui/login"); + await page.getByPlaceholder("Enter your username").fill(users[Role.ProxyAdmin].email); + await page.getByPlaceholder("Enter your password").fill(users[Role.ProxyAdmin].password); + const loginButton = page.getByRole("button", { name: "Login" }); + await loginButton.click(); + await page.waitForSelector("text=AI Gateway"); + await page.context().storageState({ path: "admin.storageState.json" }); + await browser.close(); +} + +export default globalSetup; diff --git a/ui/litellm-dashboard/e2e_tests/playwright.config.ts b/ui/litellm-dashboard/e2e_tests/playwright.config.ts index 80d45a892c..9df2281016 100644 --- a/ui/litellm-dashboard/e2e_tests/playwright.config.ts +++ b/ui/litellm-dashboard/e2e_tests/playwright.config.ts @@ -44,4 +44,5 @@ export default defineConfig({ expect: { timeout: 10 * 1000, }, + globalSetup: require.resolve("./globalSetup"), }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts new file mode 100644 index 0000000000..5ac977ff0c --- /dev/null +++ b/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts @@ -0,0 +1,13 @@ +import { expect, test } from "@playwright/test"; +import { users } from "../../fixtures/users"; +import { Role } from "../../fixtures/roles"; + +test("user can log in", async ({ page }) => { + await page.goto("http://localhost:4000/ui/login"); + await page.getByPlaceholder("Enter your username").fill(users[Role.ProxyAdmin].email); + await page.getByPlaceholder("Enter your password").fill(users[Role.ProxyAdmin].password); + const loginButton = page.getByRole("button", { name: "Login" }); + await expect(loginButton).toBeEnabled(); + await loginButton.click(); + await expect(page.getByText("AI Gateway")).toBeVisible(); +}); diff --git a/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts new file mode 100644 index 0000000000..dafb03a7cb --- /dev/null +++ b/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts @@ -0,0 +1,34 @@ +import test, { expect } from "@playwright/test"; +import { Role } from "../../fixtures/roles"; + +const sidebarButtons = { + [Role.ProxyAdmin]: [ + "Virtual Keys", + "Playground", + "Models", + "Usage", + "Teams", + "Internal User", + "Settings", + "Experimental", + "API Reference", + "AI Hub", + ], +}; + +const roles = [{ role: Role.ProxyAdmin, storage: "admin.storageState.json" }]; + +for (const { role, storage } of roles) { + test.describe(`${role} sidebar`, () => { + test.use({ storageState: storage }); + + test("can see and navigate all sidebar buttons", async ({ page }) => { + await page.goto("http://localhost:4000/ui"); + for (const button of sidebarButtons[role as keyof typeof sidebarButtons]) { + const tab = page.getByRole("menuitem", { name: button }); + await expect(tab).toBeVisible(); + await tab.click(); + } + }); + }); +}