mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
The playwright suite never executed on stage. It died in globalSetup before a single test ran, and the reported error was a red herring. /app/e2e/ui is a read-only filesystem in the packaged e2e image (the image runner already redirects playwright's own artifacts to TMPDIR for this reason), but the suite wrote three things relative to cwd: the per-role storageState files, the failure-screenshot directory, and the html report. Reproduced in the pod: storageState raises EROFS, mkdir test-results raises ENOENT. Worse, the catch block that exists to capture a screenshot threw its own ENOENT while handling a failure, so the real login error was replaced by a filesystem error. That is why the run looked like a missing directory rather than whatever actually went wrong. Route every artifact through ARTIFACT_DIR (E2E_UI_ARTIFACT_DIR, default "." to keep run_e2e.sh behavior unchanged), make the diagnostic screenshot best-effort so it can never mask the underlying failure, and point playwright's reporter and outputDir at the same place so a bare `npx playwright test` works there too. fixtures/users.ts had its own copy of the five storageState filenames; it now re-exports the ones from constants so the paths have a single definition. Verified in the read-only pod: both writes fail before, both succeed after. 85 tests enumerate and tsc --noEmit is clean. Refs LIT-4821
78 lines
3.5 KiB
TypeScript
78 lines
3.5 KiB
TypeScript
import { chromium, expect, request } from "@playwright/test";
|
|
import { users, Role, STORAGE_PATHS } from "./fixtures/users";
|
|
import { ARTIFACT_DIR, UI_BASE_URL } from "./constants";
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
|
|
async function globalSetup() {
|
|
const browser = await chromium.launch();
|
|
const rootPath = process.env.SERVER_ROOT_PATH ?? "";
|
|
|
|
// 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 masterKey = process.env.LITELLM_MASTER_KEY || "sk-1234";
|
|
const api = await request.newContext();
|
|
const settingsRes = await api.patch(`${UI_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(`${UI_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) {
|
|
// Best-effort diagnostics only: this handler must never replace the real
|
|
// failure with its own. Writing the screenshot used to throw ENOENT/EROFS
|
|
// on the read-only cwd in the e2e image, which masked every underlying
|
|
// login error and made the run look like a filesystem bug.
|
|
try {
|
|
const failureDir = path.join(ARTIFACT_DIR, "test-results");
|
|
fs.mkdirSync(failureDir, { recursive: true });
|
|
await page.screenshot({
|
|
path: path.join(failureDir, `global-setup-${role}-failure.png`),
|
|
fullPage: true,
|
|
});
|
|
console.error(`Global setup failed for role ${role}. Screenshot saved. URL: ${page.url()}`);
|
|
} catch (diagnosticError) {
|
|
console.error(
|
|
`Global setup failed for role ${role} at URL: ${page.url()}. ` +
|
|
`Could not save a screenshot: ${diagnosticError}`,
|
|
);
|
|
}
|
|
throw e;
|
|
} finally {
|
|
await page.close();
|
|
}
|
|
}
|
|
|
|
await browser.close();
|
|
}
|
|
|
|
export default globalSetup;
|