[Test] UI - E2E: Add Playwright tests with local PostgreSQL#25126
[Test] UI - E2E: Add Playwright tests with local PostgreSQL#25126yuneng-berri merged 7 commits intomainfrom
Conversation
Add a self-contained Playwright E2E test suite that runs against a local PostgreSQL database instead of Neon. Tests cover role-based access for all 5 user roles (proxy admin, admin viewer, internal user, internal viewer, team admin) and authentication flows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds a self-contained Playwright E2E test suite ( Key changes:
Issues found:
Confidence Score: 4/5Safe to merge after addressing the missing mock server failure guard; remaining issues are P2 style and completeness concerns. One P1 finding: the mock LLM server health-check loop in run_e2e.sh has no exit guard, producing silent hard-to-diagnose failures when the mock server fails to start. The fix is a handful of lines and follows the exact pattern already used for the proxy check in the same file. Two P2 findings (hardcoded URL in globalSetup.ts, missing GHA workflow) do not affect test correctness but represent incomplete work relative to the PR's stated scope. All test logic, the seed SQL scrypt hash format, and the proxy integration are verified correct. tests/ui_e2e_tests/run_e2e.sh (mock server health-check loop, lines 109–113) and tests/ui_e2e_tests/globalSetup.ts (hardcoded URL, line 9)
|
| Filename | Overview |
|---|---|
| tests/ui_e2e_tests/run_e2e.sh | Orchestration script for E2E tests; missing failure guard after mock LLM server health-check loop, letting the run continue silently when the mock never starts |
| tests/ui_e2e_tests/globalSetup.ts | Pre-test admin login and storage-state capture; hardcodes absolute URL instead of an env var or shared constant |
| tests/ui_e2e_tests/playwright.config.ts | Playwright configuration is clean; correctly defines baseURL, single worker, CI/local retry and reporter branching |
| tests/ui_e2e_tests/fixtures/seed.sql | Database seed with correctly formatted scrypt-hashed test users and idempotent ON CONFLICT DO NOTHING inserts |
| tests/ui_e2e_tests/fixtures/mock_llm_server/server.py | Minimal FastAPI OpenAI-compatible mock server; clean and correct |
| tests/ui_e2e_tests/constants.ts | Test user credentials and page/role enums; clean |
| tests/ui_e2e_tests/tests/roles/proxy-admin.spec.ts | Reuses global admin storage state; includes an API call test against /team/list using the master key |
| tests/ui_e2e_tests/tests/roles/internal-viewer.spec.ts | Verifies absence of write-action buttons for the internal viewer role; correct role-isolation pattern |
| tests/ui_e2e_tests/tests/security/login-logout.spec.ts | Validates login with valid credentials and unauthenticated redirect to login page |
| ui/litellm-dashboard/package.json | Added e2e:psql script entry point pointing to run_e2e.sh |
Sequence Diagram
sequenceDiagram
participant S as run_e2e.sh
participant DB as PostgreSQL
participant ML as Mock LLM :8090
participant PX as LiteLLM Proxy :4000
participant PW as Playwright / globalSetup
S->>DB: prisma db push (schema)
S->>ML: start background process
Note over S,ML: health poll 15 s max<br/>⚠️ no exit guard if never healthy
S->>PX: start background process
Note over S,PX: health poll 180 s max<br/>✅ exits with error if never healthy
S->>DB: psql seed.sql (users, teams)
S->>S: npm install
S->>PW: npx playwright test
PW->>PX: goto http://localhost:4000/ui/login (hardcoded)
PX-->>PW: login page
PW->>PX: fill credentials + click Login
PX-->>PW: redirect to /ui dashboard
PW->>PW: saveStorageState → admin.storageState.json
loop Each spec (10 tests)
PW->>PX: navigate / assert UI
PX->>ML: LLM calls (if any)
ML-->>PX: canned responses
PX-->>PW: HTML / API response
end
Reviews (5): Last reviewed commit: "Merge remote main into litellm_ui_e2e_ps..." | Re-trigger Greptile
| await expect(page.getByRole("menuitem", { name: "Virtual Keys" })).toBeVisible(); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Team admin test does not verify team-admin-specific behavior
The only assertion here is toBeVisible() for the "Virtual Keys" menu item, which is visible to every authenticated user regardless of role. This test would pass even if the team admin's scoping to their own teams was completely broken, since it only validates that a login succeeded.
A more meaningful test would verify something specific to the team admin role — e.g. that the user sees keys or teams scoped to their administered teams, but not teams/keys outside their scope. For example, checking that e2e-team-crud is visible but system-level admin controls (like "Admin Settings") are not.
| test("Can list teams via API", async ({ page }) => { | ||
| const response = await page.request.get("/team/list", { |
There was a problem hiding this comment.
Fallback master key
"sk-1234" duplicated inline
constants.ts already reads process.env.LITELLM_MASTER_KEY || "sk-1234" for the proxy admin password. Duplicating the same expression (with the same fallback) inside proxy-admin.spec.ts creates two places to keep in sync. More importantly, the run_e2e.sh script always sets LITELLM_MASTER_KEY to a freshly generated random value (sk-e2e-<hex32>), so the "sk-1234" fallback will only ever fire outside the test harness.
Prefer importing users[Role.ProxyAdmin].password from constants.ts instead of re-reading the env var inline:
| test("Can list teams via API", async ({ page }) => { | |
| const response = await page.request.get("/team/list", { | |
| headers: { | |
| Authorization: `Bearer ${users[Role.ProxyAdmin].password}`, | |
| }, |
(Add the required import for users and Role at the top of the file.)
- team-admin: assert Admin Settings is not visible (role-specific check) - proxy-admin: use users[Role.ProxyAdmin].password from constants instead of duplicating the env var fallback inline
| # --- Playwright --- | ||
| echo "=== Installing Playwright dependencies ===" | ||
| cd "$SCRIPT_DIR" | ||
| npm install --silent | ||
|
|
||
| echo "=== Running Playwright tests ===" | ||
| npx playwright test "$@" |
There was a problem hiding this comment.
Missing
playwright install step before running tests
npm install --silent only installs the Node packages — it does not download Playwright's browser binaries. Without an explicit npx playwright install call, any fresh CI agent or new developer machine will fail immediately with:
Error: browserType.launch: Executable doesn't exist at /path/to/chromium
The fix is to add the install step between npm install and npx playwright test:
| # --- Playwright --- | |
| echo "=== Installing Playwright dependencies ===" | |
| cd "$SCRIPT_DIR" | |
| npm install --silent | |
| echo "=== Running Playwright tests ===" | |
| npx playwright test "$@" | |
| echo "=== Installing Playwright dependencies ===" | |
| cd "$SCRIPT_DIR" | |
| npm install --silent | |
| npx playwright install chromium --with-deps | |
| echo "=== Running Playwright tests ===" | |
| npx playwright test "$@" | |
| EXIT_CODE=$? |
The --with-deps flag also installs the OS-level shared libraries Chromium requires, which is commonly the missing piece in Linux CI environments.
Summary
run_e2e.sh)Changes
tests/ui_e2e_tests/directory with 10 tests across 6 spec filesrun_e2e.shauto-detects CI vs local: spins up Docker postgres locally, uses GHA service container in CIverify_password()formatgetByRole("menuitem", ...)to avoid strict-mode violations and brittlenesstest-proxy-e2e-azure-batches.ymle2e:psqlscript toui/litellm-dashboard/package.jsonTesting
Type
✅ Test
🚄 Infrastructure