mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
The suite now targets LITELLM_PROXY_URL (default http://localhost:4000), the same live gateway the python e2e suites use, instead of provisioning its own postgres, mock LLM server and SQL-seeded proxy. globalSetup seeds every e2e-* user, team, org, key and model deployment through the management API with the master key, so any gateway with a master key and store_model_in_db works; seed.sql and the mock LLM server are gone. The two deployments (e2e-ui-openai, e2e-ui-anthropic) route to real providers per the harness's no-mocks rule, and the key creation spec now asserts a real completion instead of the mock's canned body. Seeding is idempotent against a persistent gateway: teardown deletes per-entity (batch deletes are all-or-nothing and one missing id used to strand every other row), the modelHub spec resets the public model groups list it fills, and the MCP spec purges its dead-URL servers whose failing health checks kept networkidle navigation from settling on reruns. Both Playwright configs pin workers: 1 since several specs mutate global gateway state; the suite documents that it needs the gateway to itself while it runs. Two specs are also caught up with UI refactors that had moved their buttons into dropdown menus (key deletion under "More key actions", credential editing under the row's actions menu). run_e2e.sh keeps a provisioning mode for CI and machines with no gateway, and skips provisioning entirely when LITELLM_PROXY_URL is set. The CircleCI jobs drop their seed and mock-server steps; the mock server's removal also lets tests/e2e/ui leave the basedpyright exclude list.
65 lines
2.9 KiB
TypeScript
65 lines
2.9 KiB
TypeScript
import { chromium, expect, request } from "@playwright/test";
|
|
import { users, Role, STORAGE_PATHS } from "./fixtures/users";
|
|
import { seedGateway } from "./fixtures/apiSeed";
|
|
import { PROXY_BASE_URL } from "./constants";
|
|
import * as fs from "fs";
|
|
|
|
async function globalSetup() {
|
|
const browser = await chromium.launch();
|
|
const rootPath = process.env.SERVER_ROOT_PATH ?? "";
|
|
const masterKey = process.env.LITELLM_MASTER_KEY || "sk-1234";
|
|
|
|
await seedGateway(`${PROXY_BASE_URL}${rootPath}`, masterKey);
|
|
|
|
// The Projects sidebar item is hidden unless the enterprise-gated
|
|
// enable_projects_ui setting is on, and the seeded DB starts with it off.
|
|
// The proxy runs with LITELLM_LICENSE in CI, so enable it the same way
|
|
// the admin UI toggle does; the projects migration smoke needs the link.
|
|
const api = await request.newContext();
|
|
const settingsRes = await api.patch(`${PROXY_BASE_URL}${rootPath}/update/ui_settings`, {
|
|
headers: { Authorization: `Bearer ${masterKey}` },
|
|
data: { enable_projects_ui: true },
|
|
});
|
|
if (!settingsRes.ok()) {
|
|
throw new Error(`Enabling enable_projects_ui failed (${settingsRes.status()}): ${await settingsRes.text()}`);
|
|
}
|
|
await api.dispose();
|
|
|
|
for (const role of Object.values(Role)) {
|
|
const { email, password } = users[role];
|
|
const storagePath = STORAGE_PATHS[role];
|
|
const page = await browser.newPage();
|
|
try {
|
|
await page.goto(`${PROXY_BASE_URL}${rootPath}/ui/login`);
|
|
await page.getByPlaceholder("Enter your username").fill(email);
|
|
await page.getByPlaceholder("Enter your password").fill(password);
|
|
await page.getByRole("button", { name: "Login", exact: true }).click();
|
|
await page.waitForURL((url) => url.pathname.startsWith(`${rootPath}/ui`) && !url.pathname.includes("/login"), {
|
|
timeout: 30_000,
|
|
});
|
|
await expect(page.locator("a", { hasText: "Virtual Keys" })).toBeVisible({ timeout: 30_000 });
|
|
// Dismiss feedback popup if present
|
|
const dismiss = page.getByText("Don't ask me again");
|
|
if (await dismiss.isVisible({ timeout: 1_500 }).catch(() => false)) {
|
|
await dismiss.click();
|
|
}
|
|
// The login flow stores a post-login return URL in the litellm_return_url
|
|
// cookie. If the snapshot captures it before the app consumes it, every
|
|
// test inheriting this storageState gets yanked to that stale URL the
|
|
// first time it mounts a page (the e2e suite's main flake source).
|
|
await page.context().clearCookies({ name: "litellm_return_url" });
|
|
await page.context().storageState({ path: storagePath });
|
|
} catch (e) {
|
|
fs.mkdirSync("test-results", { recursive: true });
|
|
await page.screenshot({ path: `test-results/global-setup-${role}-failure.png`, fullPage: true });
|
|
console.error(`Global setup failed for role ${role}. Screenshot saved. URL: ${page.url()}`);
|
|
throw e;
|
|
} finally {
|
|
await page.close();
|
|
}
|
|
}
|
|
|
|
await browser.close();
|
|
}
|
|
|
|
export default globalSetup;
|