BangSo/apps/web/e2e/model-picker-search.spec.ts

77 lines
3.4 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { captureScreenshot, completeOnboarding, signup } from "./helpers";
test("model dropdown search and provider group headers", async ({ page }, testInfo) => {
const stamp = Date.now();
const userName = `Model picker ${stamp}`;
await signup(page, `model-picker-${stamp}@rakazo.test`, "password12", userName);
await completeOnboarding(page);
await page.getByRole("button", { name: new RegExp(userName) }).click();
await page.getByRole("button", { name: "Models", exact: true }).click();
await expect(page.getByRole("button", { name: "Close model settings" })).toBeVisible();
// OpenRouter has many models so group headers and search are obvious.
const providerSearch = page.getByPlaceholder("Search providers");
await providerSearch.fill("openrouter");
await page.getByRole("button", { name: /OpenRouter/ }).click();
const modelCombobox = page.getByRole("combobox", { name: "Model", exact: true });
await modelCombobox.click();
const modelSearch = page.getByRole("combobox", { name: "Search models" });
const modelOptions = page.getByRole("listbox", { name: "Model options" });
await expect(modelSearch).toBeVisible();
await expect(modelSearch).toHaveAttribute("placeholder", "Search");
await expect(modelOptions).toBeVisible();
// Provider section header inside the model listbox (not the provider button).
await expect(modelOptions.getByText("OpenRouter", { exact: true })).toBeVisible();
await captureScreenshot(page, testInfo, "model-picker-dropdown-groups");
await modelSearch.fill("claude");
const optionTexts = await modelOptions.getByRole("option").allTextContents();
expect(optionTexts.length).toBeGreaterThan(0);
expect(optionTexts.every((text) => /claude/i.test(text))).toBe(true);
await expect(page.getByText("No matching models")).toBeHidden();
await captureScreenshot(page, testInfo, "model-picker-dropdown-filtered");
const firstActive = await modelSearch.getAttribute("aria-activedescendant");
expect(firstActive).toBeTruthy();
await modelSearch.press("ArrowDown");
await expect(modelSearch).toBeFocused();
const afterDown = await modelSearch.getAttribute("aria-activedescendant");
expect(afterDown).toBeTruthy();
expect(afterDown).not.toBe(firstActive);
await modelSearch.press("Home");
await expect(modelSearch).toBeFocused();
await expect(modelSearch).toHaveAttribute("aria-activedescendant", firstActive!);
await modelSearch.press("End");
await expect(modelSearch).toBeFocused();
const afterEnd = await modelSearch.getAttribute("aria-activedescendant");
expect(afterEnd).toBeTruthy();
expect(afterEnd).not.toBe(firstActive);
await modelSearch.press("ArrowUp");
await expect(modelSearch).toBeFocused();
const afterUp = await modelSearch.getAttribute("aria-activedescendant");
expect(afterUp).toBeTruthy();
expect(afterUp).not.toBe(afterEnd);
const highlighted = page.locator(`[id="${afterUp}"]`);
const selectedLabel = ((await highlighted.locator("span").first().textContent()) ?? "").trim();
expect(selectedLabel.length).toBeGreaterThan(0);
await modelSearch.press("Enter");
await expect(modelSearch).toBeHidden();
await expect(modelCombobox).toHaveText(selectedLabel);
await modelCombobox.click();
await expect(modelSearch).toBeVisible();
await modelSearch.fill("no-model-matches-this");
await expect(page.getByText("No matching models")).toBeVisible();
await expect(modelOptions.getByRole("option")).toHaveCount(0);
});