mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-25 01:02:15 +00:00
test(ui): cover narrowed dashboard form journeys
This commit is contained in:
parent
b1f9da79a8
commit
c34adb4ab2
5 changed files with 220 additions and 0 deletions
55
tests/e2e/ui/tests/prompts/addPrompt.spec.ts
Normal file
55
tests/e2e/ui/tests/prompts/addPrompt.spec.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
|
||||
import { ADMIN_STORAGE_PATH } from "../../constants";
|
||||
import { Page as DashboardPage } from "../../fixtures/pages";
|
||||
import { navigateToPage } from "../../helpers/navigation";
|
||||
import { readBack } from "../../helpers/roundTrip";
|
||||
import { masterKey, uniqueSuffix } from "../../helpers/traffic";
|
||||
|
||||
test.use({ storageState: ADMIN_STORAGE_PATH });
|
||||
|
||||
test.describe("Prompt upload form", () => {
|
||||
test("uploads a prompt file and reads the created prompt back", async ({
|
||||
page,
|
||||
}) => {
|
||||
const promptId = `e2e-prompt-${uniqueSuffix()}`;
|
||||
await navigateToPage(page, DashboardPage.Prompts);
|
||||
await page.getByRole("button", { name: "Upload .prompt File" }).click();
|
||||
|
||||
try {
|
||||
await expect(
|
||||
page.getByRole("dialog", { name: "Add New Prompt" }),
|
||||
).toBeVisible();
|
||||
await page.getByLabel("Prompt ID").fill(promptId);
|
||||
await page.locator('input[type="file"]').setInputFiles({
|
||||
name: "e2e.prompt",
|
||||
mimeType: "text/plain",
|
||||
buffer: Buffer.from(
|
||||
'model: fake-openai-gpt-4\ntemplate: "Hello {{name}}"\n',
|
||||
),
|
||||
});
|
||||
await expect(page.getByText("Selected: e2e.prompt")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Create Prompt" }).click();
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const response = await page.request.get(
|
||||
`/prompts/${encodeURIComponent(promptId)}/info?environment=development`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${masterKey()}` },
|
||||
},
|
||||
);
|
||||
return response.ok();
|
||||
})
|
||||
.toBe(true);
|
||||
await expect(page.getByText(promptId, { exact: true })).toBeVisible();
|
||||
} finally {
|
||||
await page.request.delete(
|
||||
`/prompts/${encodeURIComponent(promptId)}?environment=development`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${masterKey()}` },
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
76
tests/e2e/ui/tests/tagManagement/tagManagement.spec.ts
Normal file
76
tests/e2e/ui/tests/tagManagement/tagManagement.spec.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
|
||||
import { ADMIN_STORAGE_PATH } from "../../constants";
|
||||
import { Page as DashboardPage } from "../../fixtures/pages";
|
||||
import { navigateToPage } from "../../helpers/navigation";
|
||||
import { captureRequestBody, readBack } from "../../helpers/roundTrip";
|
||||
import { masterKey, uniqueSuffix } from "../../helpers/traffic";
|
||||
|
||||
test.use({ storageState: ADMIN_STORAGE_PATH });
|
||||
|
||||
test.describe("Tag management", () => {
|
||||
test("creates, edits, reopens, and reads back a tag", async ({ page }) => {
|
||||
const tagName = `e2e-tag-${uniqueSuffix()}`;
|
||||
const description = "synthetic tag description";
|
||||
const updatedDescription = `${description} updated`;
|
||||
|
||||
await navigateToPage(page, DashboardPage.TagManagement);
|
||||
await page.getByRole("button", { name: "+ Create New Tag" }).click();
|
||||
|
||||
try {
|
||||
await expect(
|
||||
page.getByRole("dialog", { name: "Create New Tag" }),
|
||||
).toBeVisible();
|
||||
await page.getByLabel("Tag Name").fill(tagName);
|
||||
await page.getByLabel("Description").fill(description);
|
||||
await page.getByRole("button", { name: "Create Tag" }).click();
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const response = await readBack<
|
||||
Record<string, Record<string, unknown>>
|
||||
>(page, "/tag/list");
|
||||
return Object.values(response).some((tag) => tag.name === tagName);
|
||||
})
|
||||
.toBe(true);
|
||||
await expect(page.getByText(tagName, { exact: true })).toBeVisible();
|
||||
|
||||
await page.getByText(tagName, { exact: true }).click();
|
||||
await expect(page.getByText("Tag Name:")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Edit Tag" }).click();
|
||||
await page.getByLabel("Description").fill(updatedDescription);
|
||||
const updateBody = await captureRequestBody(
|
||||
page,
|
||||
{ method: "POST", urlIncludes: "/tag/update" },
|
||||
() => page.getByRole("button", { name: "Save Changes" }).click(),
|
||||
);
|
||||
expect(updateBody).toMatchObject({
|
||||
name: tagName,
|
||||
description: updatedDescription,
|
||||
});
|
||||
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const infoResponse = await page.request.post("/tag/info", {
|
||||
headers: { Authorization: `Bearer ${masterKey()}` },
|
||||
data: { names: [tagName] },
|
||||
});
|
||||
expect(infoResponse.ok()).toBe(true);
|
||||
const info = (await infoResponse.json()) as Record<
|
||||
string,
|
||||
{ description?: string }
|
||||
>;
|
||||
return info[tagName]?.description;
|
||||
})
|
||||
.toBe(updatedDescription);
|
||||
} finally {
|
||||
await page.request.post("/tag/delete", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${masterKey()}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: { name: tagName },
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { fireEvent, renderWithProviders as render, screen, waitFor } from "../../../tests/test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
|
|
@ -406,4 +407,48 @@ describe("PaginatedSearchSelect", () => {
|
|||
expect(input).toHaveValue("aliasalpha");
|
||||
await waitFor(() => expect(onSearchChange).toHaveBeenLastCalledWith("aliasalpha"));
|
||||
});
|
||||
|
||||
it("keeps the latest query results when an earlier response resolves last", async () => {
|
||||
const pending = new Map<string, (options: SearchSelectOption[]) => void>();
|
||||
|
||||
function QueryBackedSelect() {
|
||||
const [query, setQuery] = useState("");
|
||||
const result = useQuery({
|
||||
queryKey: ["paginated-select-race", query],
|
||||
queryFn: () =>
|
||||
new Promise<SearchSelectOption[]>((resolve) => {
|
||||
pending.set(query, resolve);
|
||||
}),
|
||||
enabled: query.length > 0,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<button type="button" onClick={() => setQuery("A")}>
|
||||
Search A
|
||||
</button>
|
||||
<button type="button" onClick={() => setQuery("B")}>
|
||||
Search B
|
||||
</button>
|
||||
<PaginatedSearchSelect options={result.data ?? []} onValueChange={vi.fn()} onSearchChange={setQuery} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const user = userEvent.setup();
|
||||
render(<QueryBackedSelect />);
|
||||
await user.click(screen.getByRole("button", { name: "Search A" }));
|
||||
await user.click(screen.getByRole("button", { name: "Search B" }));
|
||||
await waitFor(() => {
|
||||
expect(pending.has("A")).toBe(true);
|
||||
expect(pending.has("B")).toBe(true);
|
||||
});
|
||||
|
||||
pending.get("B")?.([{ label: "B result", value: "b" }]);
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
expect(await screen.findByText("B result")).toBeInTheDocument();
|
||||
|
||||
pending.get("A")?.([{ label: "A result", value: "a" }]);
|
||||
await waitFor(() => expect(screen.queryByText("A result")).not.toBeInTheDocument());
|
||||
expect(screen.getByText("B result")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -111,4 +111,37 @@ describe("SearchSelect", () => {
|
|||
expect(screen.queryByText("Growth")).not.toBeInTheDocument();
|
||||
expect(onValueChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("supports keyboard select, clear, escape, blur, and reopen", async () => {
|
||||
const onValueChange = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
function Controlled() {
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
return (
|
||||
<SearchSelect
|
||||
options={OPTIONS}
|
||||
value={value}
|
||||
onValueChange={(next) => {
|
||||
setValue(next);
|
||||
onValueChange(next);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render(<Controlled />);
|
||||
const input = screen.getByRole("combobox");
|
||||
await user.tab();
|
||||
await user.keyboard("{Enter}");
|
||||
await user.keyboard("{ArrowDown}{Enter}");
|
||||
expect(onValueChange).toHaveBeenLastCalledWith("team-1");
|
||||
const clear = screen.getByRole("button", { name: "Clear" });
|
||||
clear.focus();
|
||||
await user.keyboard("{Enter}");
|
||||
expect(onValueChange).toHaveBeenLastCalledWith(null);
|
||||
await user.keyboard("{Escape}");
|
||||
await user.tab();
|
||||
await user.tab({ shift: true });
|
||||
expect(input).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -344,4 +344,15 @@ describe("RequestLogsFilters", () => {
|
|||
|
||||
expect(set).toHaveBeenCalledWith(LOG_FILTER_IDS.CACHE_STATUS, undefined);
|
||||
});
|
||||
|
||||
it("clears the raw Error Code combobox through the undefined filter contract", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { set } = renderFilters({ [LOG_FILTER_IDS.ERROR_CODE]: "429" });
|
||||
const input = await screen.findByPlaceholderText("Select or type an error code");
|
||||
|
||||
await user.click(input);
|
||||
await user.click(screen.getByRole("button", { name: "Clear", hidden: true }));
|
||||
|
||||
expect(set).toHaveBeenCalledWith(LOG_FILTER_IDS.ERROR_CODE, undefined);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue