tests/e2e/model-chain.spec.ts
Rama main · réplica de lectura del repositorio original.
import { expect, test } from "@playwright/test";
import { enableScopedE2EAuth, scopedPath } from "./helpers/auth";
import { mockSettingsPageData, model } from "./helpers/theme-settings-fixtures";
import { expectFullRouteWidth } from "./helpers/route-width";
test("chain edits survive switching tabs, save atomically, and fit desktop and mobile", async ({
page,
}, testInfo) => {
await enableScopedE2EAuth(page);
await mockSettingsPageData(page);
await page.route(/\/api\/settings(?:\?.*)?$/, (route) =>
route.fulfill({ json: { default_model: model.id, theme: "dark" } })
);
const alternatives = ["One", "Two", "Three", "Four", "Five"].map((name) => ({
...model,
id: `openai/${name.toLowerCase()}`,
name,
}));
const catalog = [
model,
...alternatives,
{
...model,
id: "openai/disabled",
name: "Disabled model",
is_enabled: false,
},
];
let saved = { primary: model.id, fallbacks: ["openai/one", "openai/two"] };
let writes = 0;
let failSave = true;
let failLoad = true;
await page.route("**/api/models", async (route) => {
if (route.request().method() === "PATCH") {
const body = route.request().postDataJSON();
catalog.find((item) => item.id === body.model_id)!.is_enabled =
body.is_enabled;
}
await route.fulfill({
json: { models: catalog.filter((item) => item.is_enabled), catalog },
});
});
await page.route("**/api/settings/model-chain", async (route) => {
if (route.request().method() === "GET" && failLoad)
return route.fulfill({
status: 500,
json: { error: "Unable to load model chain" },
});
if (route.request().method() === "PATCH") {
writes++;
if (failSave)
return route.fulfill({
status: 500,
json: {
error: "Unable to save model chain. No changes were applied.",
},
});
saved = route.request().postDataJSON();
}
await route.fulfill({ json: saved });
});
await page.setViewportSize({ width: 2560, height: 1440 });
await page.goto(scopedPath("/models/configuration"));
const section = page.getByRole("region", { name: "Default and fallbacks" });
await expect(section.getByRole("alert")).toContainText(
"Unable to load model chain"
);
failLoad = false;
await section.getByRole("button", { name: "Retry", exact: true }).click();
const primary = section.getByRole("button", { name: "Primary", exact: true });
await expect(primary).toContainText(model.name);
await expect(
section.getByRole("button", { name: "Save chain" })
).toBeDisabled();
expect((await section.boundingBox())!.width).toBeLessThanOrEqual(760);
await expectFullRouteWidth(page, page.getByTestId("models-configuration"));
await expect(page.getByTestId("models-routing-preview")).toContainText(
"Fallback 2"
);
const first = section.getByRole("button", {
name: "Fallback 1",
exact: true,
});
const move = section.getByRole("button", { name: "Move fallback 1 down" });
expect(
(await move.boundingBox())!.x -
((await first.boundingBox())!.x + (await first.boundingBox())!.width)
).toBeLessThan(50);
await page.screenshot({
path: testInfo.outputPath("model-chain-desktop.png"),
});
await primary.click();
await expect(
page.getByRole("option", { name: /Disabled model/ })
).toHaveCount(0);
await page.getByPlaceholder("Search enabled models...").fill("Five");
await page.keyboard.press("ArrowDown");
await page.keyboard.press("Enter");
await expect(primary).toContainText("Five");
expect(writes).toBe(0);
await section.getByRole("button", { name: "Discard", exact: true }).click();
await expect(primary).toContainText(model.name);
await primary.click();
await page
.getByRole("dialog", { name: "Primary options", exact: true })
.getByRole("option", { name: "One", exact: true })
.click();
await expect(primary).toContainText("One");
await expect(first).toContainText("Two");
await section.getByRole("button", { name: "Discard", exact: true }).click();
await expect(first).toContainText("One");
await primary.click();
await page.keyboard.press("Escape");
await expect(primary).toBeFocused();
expect(
await primary.evaluate((element) => getComputedStyle(element).boxShadow)
).not.toBe("none");
await page.getByTestId("models-tab-catalog").click();
await expect(page).toHaveURL(/\/models\/catalog$/);
await page.getByTestId("models-set-default-openai/five").click();
await expect(page.getByTestId("models-set-default-openai/five")).toHaveCount(
0
);
await expect(page.getByTestId("models-chain-role-openai/five")).toHaveText(
"Primary"
);
await page.getByTestId("models-add-fallback-openai/three").click();
await expect(page.getByTestId("models-chain-role-openai/three")).toHaveText(
"Fallback 3"
);
await expect(page.getByTestId("models-draft-indicator")).toBeVisible();
await expect(page.getByTestId("models-chain-draft-bar")).toContainText(
"Routing chain changed"
);
expect(writes).toBe(0);
await page.getByTestId("models-tab-configuration").click();
await expect(page).toHaveURL(/\/models\/configuration$/);
await expect(primary).toContainText("Five");
await section
.getByRole("button", { name: "Add a fallback", exact: true })
.click();
await page.getByRole("option", { name: "Four", exact: true }).click();
await expect(section.getByText("4/4", { exact: true })).toBeVisible();
await expect(
section.getByRole("button", { name: "Add a fallback", exact: true })
).toBeDisabled();
await section.getByRole("button", { name: "Move fallback 1 down" }).click();
await expect(first).toContainText("Two");
await section.getByRole("button", { name: "Move fallback 2 up" }).click();
await expect(first).toContainText("One");
await first.click();
await page
.getByRole("dialog", { name: "Fallback 1 options", exact: true })
.getByRole("option", { name: model.name, exact: true })
.click();
await expect(first).toContainText(model.name);
await first.click();
await page
.getByRole("dialog", { name: "Fallback 1 options", exact: true })
.getByRole("option", { name: "One", exact: true })
.click();
await expect(first).toContainText("One");
await section.getByRole("button", { name: "Save chain" }).click();
await expect(section.getByRole("status")).toContainText("Unable to save");
expect(saved.primary).toBe(model.id);
await expect(primary).toContainText("Five");
failSave = false;
await section.getByRole("button", { name: "Save chain" }).click();
await expect(section.getByRole("status")).toHaveText("Chain saved.");
expect(saved).toEqual({
primary: "openai/five",
fallbacks: ["openai/one", "openai/two", "openai/three", "openai/four"],
});
await page.reload();
await expect(primary).toContainText("Five");
await page.getByTestId("models-tab-catalog").click();
await page.getByTestId("models-toggle-openai/five").click();
await page.getByTestId("models-toggle-openai/one").click();
await page.getByTestId("models-tab-configuration").click();
await expect(
section.getByText("Disabled in catalog", { exact: true })
).toHaveCount(2);
await expect(page.getByTestId("models-routing-preview")).toContainText(
"Disabled in catalog"
);
await first.click();
await expect(
page.getByRole("option", { name: "One", exact: true })
).toHaveCount(0);
await page.keyboard.press("Escape");
await expect(page.getByPlaceholder("Search enabled models...")).toHaveCount(
0
);
await page.setViewportSize({ width: 390, height: 844 });
await expectFullRouteWidth(page, page.getByTestId("models-configuration"));
expect(
await page.evaluate(
() => document.documentElement.scrollWidth <= window.innerWidth
)
).toBe(true);
await page.screenshot({
path: testInfo.outputPath("model-chain-mobile.png"),
});
await page.getByTestId("models-tab-catalog").click();
await expectFullRouteWidth(page, page.getByTestId("models-content"));
expect(
await page.evaluate(
() => document.documentElement.scrollWidth <= window.innerWidth
)
).toBe(true);
await page.getByTestId("models-toggle-openai/five").click();
await page.getByTestId("models-tab-configuration").click();
for (let index = 0; index < 4; index++)
await section
.getByRole("button", { name: "Remove fallback 1", exact: true })
.click();
await section.getByRole("button", { name: "Save chain" }).click();
await expect(section.getByRole("status")).toHaveText("Chain saved.");
expect(saved.fallbacks).toEqual([]);
});