litellm/tests/e2e/ui/helpers/navigation.ts
ryan-crabbe-berri 0fcaadf11c
test(e2e): move Admin UI Playwright suite to tests/e2e/ui (#34196)
Relocates ui/litellm-dashboard/e2e_tests to tests/e2e/ui so all end to end
suites live under tests/e2e. The suite stays in TypeScript and becomes a
self-contained npm package with its own package.json, lockfile and tsconfig
instead of leaning on the dashboard's toolchain; the dashboard drops its
@playwright/test dependency, e2e scripts and knip/vitest/tsconfig carve-outs.

CI paths follow the move: both CircleCI jobs (main e2e and the
SERVER_ROOT_PATH migration smoke) and the test_server_root_path workflow now
install and run Playwright from tests/e2e/ui, with the node cache keyed on
both lockfiles. classify_changes.sh treats tests/e2e/ui as client so spec
edits keep skipping backend jobs. The suite's mock LLM fixture is excluded
from the e2e basedpyright zero-error gate in pyrightconfig.json since it
belongs to the TS suite, not the typed Python harness.
2026-07-22 19:43:10 +00:00

51 lines
2.1 KiB
TypeScript

import { Page } from "../fixtures/pages";
import { Page as PlaywrightPage, expect } from "@playwright/test";
/**
* Navigates to a specific page using the page query parameter.
* Waits for the sidebar to be visible before returning.
*/
export async function navigateToPage(page: PlaywrightPage, pageEnum: Page): Promise<void> {
// A fresh deep-link can race the auth bootstrap: the app briefly treats the
// session as anonymous, bounces through /ui/login, and lands back on the
// default page with the ?page= param dropped. Re-issue the navigation until
// the requested page sticks (auth is warm by the second load) so callers never
// assert against the default page.
for (let attempt = 0; attempt < 3; attempt++) {
await page.goto(`/ui?page=${pageEnum}`);
await page.waitForLoadState("networkidle");
const url = new URL(page.url());
const onLegacyRoot = url.pathname.replace(/\/+$/, "").endsWith("/ui");
if (!onLegacyRoot || url.searchParams.get("page") === pageEnum) {
break;
}
}
// Dismiss the "Quick feedback" popup if it appears
await dismissFeedbackPopup(page);
}
/**
* Dismiss the "Quick feedback" popup that may appear on any page.
*/
export async function dismissFeedbackPopup(page: PlaywrightPage): Promise<void> {
const dismissButton = page.getByText("Don't ask me again");
if (await dismissButton.isVisible({ timeout: 1_500 }).catch(() => false)) {
await dismissButton.click();
// Wait for the popup to disappear
await expect(dismissButton)
.not.toBeVisible({ timeout: 2_000 })
.catch(() => {});
}
}
/**
* Click on a team ID in the table. Team IDs are rendered differently depending
* on the component version — try button first (Tremor Button), fall back to
* clickable span (Teams Typography.Text).
*/
export async function clickTeamId(page: PlaywrightPage, teamId: string): Promise<void> {
const cell = page.locator("td").filter({ hasText: teamId }).first();
await expect(cell).toBeVisible({ timeout: 10_000 });
await cell.click();
await expect(page.getByText("Back to Teams")).toBeVisible({ timeout: 10_000 });
}