[Fix] Add missing test fixtures and address review feedback
- Add constants.ts with all required exports (key aliases, team IDs) - Add fixtures/users.ts with all role definitions and storage paths - Add fixtures/seed.sql for deterministic test database seeding - Remove Firefox project from playwright config (only Chromium installed) - Remove unused variable in teams.spec.ts - Rename CircleCI job to e2e_ui_testing
This commit is contained in:
parent
d09d98a70a
commit
a8f4f464ce
@ -3074,7 +3074,7 @@ jobs:
|
||||
CI=true npm run test -- --run \
|
||||
--pool forks --poolOptions.forks.maxForks=8
|
||||
|
||||
ui_e2e_tests:
|
||||
e2e_ui_testing:
|
||||
docker:
|
||||
- image: cimg/python:3.12-browsers
|
||||
auth:
|
||||
|
||||
@ -1,6 +1,22 @@
|
||||
// Storage state paths for each role
|
||||
export const ADMIN_STORAGE_PATH = "admin.storageState.json";
|
||||
export const ADMIN_VIEWER_STORAGE_PATH = "adminViewer.storageState.json";
|
||||
export const INTERNAL_USER_STORAGE_PATH = "internalUser.storageState.json";
|
||||
export const INTERNAL_VIEWER_STORAGE_PATH = "internalViewer.storageState.json";
|
||||
export const TEAM_ADMIN_STORAGE_PATH = "teamAdmin.storageState.json";
|
||||
|
||||
export const E2E_UPDATE_LIMITS_KEY_ID_PREFIX = "102c";
|
||||
export const E2E_DELETE_KEY_ID_PREFIX = "94a5";
|
||||
export const E2E_DELETE_KEY_NAME = "e2eDeleteKey";
|
||||
export const E2E_REGENERATE_KEY_ID_PREFIX = "593a";
|
||||
// Key aliases for seeded test keys (match seed.sql)
|
||||
export const E2E_UPDATE_LIMITS_KEY_ALIAS = "e2eUpdateLimitsKey";
|
||||
export const E2E_DELETE_KEY_ALIAS = "e2eDeleteKey";
|
||||
export const E2E_REGENERATE_KEY_ALIAS = "e2eRegenerateKey";
|
||||
export const E2E_INTERNAL_USER_KEY_ALIAS = "e2eInternalUserKey";
|
||||
export const E2E_VIEWER_KEY_ALIAS = "e2eViewerKey";
|
||||
|
||||
// Team identifiers (match seed.sql)
|
||||
export const E2E_TEAM_CRUD_ID = "e2e-team-crud";
|
||||
export const E2E_TEAM_CRUD_ALIAS = "E2E Team CRUD";
|
||||
export const E2E_TEAM_DELETE_ID = "e2e-team-delete";
|
||||
export const E2E_TEAM_DELETE_ALIAS = "E2E Team Delete";
|
||||
export const E2E_TEAM_ORG_ID = "e2e-team-org";
|
||||
export const E2E_TEAM_NO_ADMIN_ID = "e2e-team-no-admin";
|
||||
export const E2E_TEAM_NO_ADMIN_ALIAS = "E2E Team No Admin";
|
||||
|
||||
84
ui/litellm-dashboard/e2e_tests/fixtures/seed.sql
Normal file
84
ui/litellm-dashboard/e2e_tests/fixtures/seed.sql
Normal file
@ -0,0 +1,84 @@
|
||||
-- E2E Test Seed Data
|
||||
-- Idempotent: deletes all e2e-* rows then re-inserts deterministic data.
|
||||
|
||||
-- 1. Clean up in dependency order
|
||||
DELETE FROM "LiteLLM_TeamMembership" WHERE "user_id" LIKE 'e2e-%';
|
||||
DELETE FROM "LiteLLM_VerificationToken" WHERE token LIKE 'e2e-%';
|
||||
DELETE FROM "LiteLLM_TeamTable" WHERE "team_id" LIKE 'e2e-%';
|
||||
DELETE FROM "LiteLLM_OrganizationTable" WHERE "organization_id" LIKE 'e2e-%';
|
||||
DELETE FROM "LiteLLM_UserTable" WHERE "user_id" LIKE 'e2e-%';
|
||||
DELETE FROM "LiteLLM_BudgetTable" WHERE "budget_id" LIKE 'e2e-%';
|
||||
|
||||
-- 2. Budget (created_by and updated_by are NOT NULL)
|
||||
INSERT INTO "LiteLLM_BudgetTable" ("budget_id", "max_budget", "created_by", "updated_by")
|
||||
VALUES ('e2e-budget-org', 1000, 'e2e-proxy-admin', 'e2e-proxy-admin');
|
||||
|
||||
-- 3. Organization (created_by and updated_by are NOT NULL)
|
||||
INSERT INTO "LiteLLM_OrganizationTable" (
|
||||
"organization_id", "organization_alias", "budget_id",
|
||||
"metadata", "models", "spend", "model_spend",
|
||||
"created_by", "updated_by"
|
||||
) VALUES (
|
||||
'e2e-org-main', 'E2E Organization', 'e2e-budget-org',
|
||||
'{}'::jsonb, ARRAY[]::text[], 0.0, '{}'::jsonb,
|
||||
'e2e-proxy-admin', 'e2e-proxy-admin'
|
||||
);
|
||||
|
||||
-- 4. Users (password hash is scrypt of "test")
|
||||
INSERT INTO "LiteLLM_UserTable" ("user_id", "user_email", "user_role", "teams", "password")
|
||||
VALUES
|
||||
('e2e-proxy-admin', 'admin@test.local', 'proxy_admin', '{"e2e-team-crud"}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr'),
|
||||
('e2e-admin-viewer', 'adminviewer@test.local', 'proxy_admin_viewer', '{}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr'),
|
||||
('e2e-internal-user', 'internal@test.local', 'internal_user', '{"e2e-team-crud","e2e-team-org"}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr'),
|
||||
('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-removable-member', 'removable@test.local', 'internal_user', '{"e2e-team-crud"}', 'scrypt:MU5CcTAi6rVK1HfY1rVPEWq6r4sxg837eq9dG4n5Q6BhDJ44442+seC6LAhLEAYr');
|
||||
|
||||
-- 5. Teams (members_with_roles is required JSON)
|
||||
INSERT INTO "LiteLLM_TeamTable" (
|
||||
"team_id", "team_alias", "organization_id", "admins", "members",
|
||||
"members_with_roles", "metadata", "models", "spend", "model_spend", "model_max_budget", "blocked"
|
||||
) VALUES
|
||||
('e2e-team-crud', 'E2E Team CRUD', NULL,
|
||||
'{"e2e-team-admin"}',
|
||||
'{"e2e-team-admin","e2e-internal-user","e2e-internal-viewer","e2e-removable-member"}',
|
||||
'[{"role":"admin","user_id":"e2e-team-admin"},{"role":"user","user_id":"e2e-internal-user"},{"role":"user","user_id":"e2e-internal-viewer"},{"role":"user","user_id":"e2e-removable-member"}]'::jsonb,
|
||||
'{}'::jsonb, '{"fake-openai-gpt-4","fake-anthropic-claude"}', 0.0, '{}'::jsonb, '{}'::jsonb, false),
|
||||
|
||||
('e2e-team-delete', 'E2E Team Delete', NULL,
|
||||
'{"e2e-team-admin"}', '{"e2e-team-admin"}',
|
||||
'[{"role":"admin","user_id":"e2e-team-admin"}]'::jsonb,
|
||||
'{}'::jsonb, '{"fake-openai-gpt-4"}', 0.0, '{}'::jsonb, '{}'::jsonb, false),
|
||||
|
||||
('e2e-team-org', 'E2E Team In Org', 'e2e-org-main',
|
||||
'{}', '{"e2e-internal-user"}',
|
||||
'[{"role":"user","user_id":"e2e-internal-user"}]'::jsonb,
|
||||
'{}'::jsonb, '{"fake-openai-gpt-4"}', 0.0, '{}'::jsonb, '{}'::jsonb, false),
|
||||
|
||||
('e2e-team-no-admin', 'E2E Team No Admin', NULL,
|
||||
'{}', '{"e2e-invitable-user"}',
|
||||
'[{"role":"user","user_id":"e2e-invitable-user"}]'::jsonb,
|
||||
'{}'::jsonb, '{"fake-openai-gpt-4"}', 0.0, '{}'::jsonb, '{}'::jsonb, false);
|
||||
|
||||
-- 6. Team Memberships (only user_id, team_id, spend — no created_at/updated_at)
|
||||
INSERT INTO "LiteLLM_TeamMembership" ("user_id", "team_id", "spend")
|
||||
VALUES
|
||||
('e2e-team-admin', 'e2e-team-crud', 0.0),
|
||||
('e2e-internal-user', 'e2e-team-crud', 0.0),
|
||||
('e2e-internal-viewer', 'e2e-team-crud', 0.0),
|
||||
('e2e-removable-member', 'e2e-team-crud', 0.0),
|
||||
('e2e-team-admin', 'e2e-team-delete', 0.0),
|
||||
('e2e-internal-user', 'e2e-team-org', 0.0),
|
||||
('e2e-invitable-user', 'e2e-team-no-admin', 0.0);
|
||||
|
||||
-- 7. Verification Tokens (API Keys)
|
||||
INSERT INTO "LiteLLM_VerificationToken" (
|
||||
"token", "key_name", "key_alias", "user_id", "team_id",
|
||||
"models", "spend", "max_budget", "expires", "metadata"
|
||||
) VALUES
|
||||
('e2e-key-update-limits', 'sk-e2e-update', 'e2eUpdateLimitsKey', 'e2e-proxy-admin', 'e2e-team-crud', '{"fake-openai-gpt-4"}', 0.0, NULL, NULL, '{}'::jsonb),
|
||||
('e2e-key-delete', 'sk-e2e-delete', 'e2eDeleteKey', 'e2e-proxy-admin', 'e2e-team-crud', '{"fake-openai-gpt-4"}', 0.0, NULL, NULL, '{}'::jsonb),
|
||||
('e2e-key-regenerate', 'sk-e2e-regen', 'e2eRegenerateKey', 'e2e-proxy-admin', 'e2e-team-crud', '{"fake-openai-gpt-4"}', 0.0, NULL, NULL, '{}'::jsonb),
|
||||
('e2e-key-internal-user', 'sk-e2e-internal', 'e2eInternalUserKey', 'e2e-internal-user', 'e2e-team-crud', '{"fake-openai-gpt-4"}', 0.0, NULL, NULL, '{}'::jsonb),
|
||||
('e2e-key-viewer', 'sk-e2e-viewer', 'e2eViewerKey', 'e2e-internal-viewer', NULL, '{"fake-openai-gpt-4"}', 0.0, NULL, NULL, '{}'::jsonb);
|
||||
@ -1,10 +1,38 @@
|
||||
import { Role } from "./roles";
|
||||
export enum Role {
|
||||
ProxyAdmin = "proxy_admin",
|
||||
ProxyAdminViewer = "proxy_admin_viewer",
|
||||
InternalUser = "internal_user",
|
||||
InternalUserViewer = "internal_user_viewer",
|
||||
TeamAdmin = "team_admin",
|
||||
}
|
||||
|
||||
const isCI = !!process.env.CI;
|
||||
|
||||
export const users = {
|
||||
export const users: Record<Role, { email: string; password: string }> = {
|
||||
[Role.ProxyAdmin]: {
|
||||
email: "admin",
|
||||
password: isCI ? "gm" : "sk-1234",
|
||||
password: process.env.LITELLM_MASTER_KEY || "sk-1234",
|
||||
},
|
||||
[Role.ProxyAdminViewer]: {
|
||||
email: "adminviewer@test.local",
|
||||
password: "test",
|
||||
},
|
||||
[Role.InternalUser]: {
|
||||
email: "internal@test.local",
|
||||
password: "test",
|
||||
},
|
||||
[Role.InternalUserViewer]: {
|
||||
email: "viewer@test.local",
|
||||
password: "test",
|
||||
},
|
||||
[Role.TeamAdmin]: {
|
||||
email: "teamadmin@test.local",
|
||||
password: "test",
|
||||
},
|
||||
};
|
||||
|
||||
export const STORAGE_PATHS: Record<Role, string> = {
|
||||
[Role.ProxyAdmin]: "admin.storageState.json",
|
||||
[Role.ProxyAdminViewer]: "adminViewer.storageState.json",
|
||||
[Role.InternalUser]: "internalUser.storageState.json",
|
||||
[Role.InternalUserViewer]: "internalViewer.storageState.json",
|
||||
[Role.TeamAdmin]: "teamAdmin.storageState.json",
|
||||
};
|
||||
|
||||
@ -36,11 +36,6 @@ export default defineConfig({
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Timeout settings */
|
||||
|
||||
@ -15,8 +15,6 @@ import { navigateToPage, dismissFeedbackPopup } from "../../helpers/navigation";
|
||||
* clickable span (OldTeams Typography.Text).
|
||||
*/
|
||||
async function clickTeamId(page: import("@playwright/test").Page, teamId: string) {
|
||||
const idPrefix = teamId.slice(0, 7);
|
||||
// The team ID is either a Button or a clickable span containing the first 7 chars
|
||||
const cell = page.locator("td").filter({ hasText: teamId }).first();
|
||||
await expect(cell).toBeVisible({ timeout: 10_000 });
|
||||
await cell.click();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user