139 lines
5.6 KiB
TypeScript
139 lines
5.6 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
import { gotoDashboardRoute } from "./helpers/dashboardAuth";
|
|
|
|
test.describe("Playground Compare Tab", () => {
|
|
function buildSseResponse(content: string, model: string): string {
|
|
return [
|
|
`data: ${JSON.stringify({ id: "cmp-1", object: "chat.completion.chunk", model, choices: [{ delta: { role: "assistant", content }, index: 0, finish_reason: null }] })}`,
|
|
`data: ${JSON.stringify({ id: "cmp-1", object: "chat.completion.chunk", model, choices: [{ delta: {}, index: 0, finish_reason: "stop" }], usage: { prompt_tokens: 5, completion_tokens: 3 } })}`,
|
|
"data: [DONE]",
|
|
"",
|
|
].join("\n");
|
|
}
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
// Mock presets
|
|
await page.route("**/api/playground/presets", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ presets: [] }),
|
|
});
|
|
});
|
|
|
|
// Mock chat completions with SSE response
|
|
let callCount = 0;
|
|
await page.route("**/api/v1/chat/completions", async (route) => {
|
|
callCount += 1;
|
|
const model = callCount % 2 === 0 ? "claude-3-haiku" : "openai/gpt-4o-mini";
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "text/event-stream",
|
|
body: buildSseResponse(`Response from ${model}`, model),
|
|
});
|
|
});
|
|
});
|
|
|
|
test("navigates to compare tab via URL param", async ({ page }) => {
|
|
await gotoDashboardRoute(page, "/dashboard/playground?tab=compare");
|
|
|
|
// Compare tab should be visible and active
|
|
const compareTab = page.getByRole("tab", { name: /compare/i });
|
|
await expect(compareTab).toBeVisible({ timeout: 15000 });
|
|
await expect(compareTab).toHaveAttribute("aria-selected", "true");
|
|
});
|
|
|
|
test("can add two columns in Compare tab", async ({ page }) => {
|
|
await gotoDashboardRoute(page, "/dashboard/playground");
|
|
|
|
// Navigate to Compare tab
|
|
const compareTab = page.getByRole("tab", { name: /compare/i });
|
|
await expect(compareTab).toBeVisible({ timeout: 15000 });
|
|
await compareTab.click();
|
|
|
|
// Get the Add model button
|
|
const addButton = page.getByRole("button", { name: /add model/i });
|
|
await expect(addButton).toBeVisible({ timeout: 10000 });
|
|
|
|
// Type a model name in the input and add it
|
|
const modelInput = page.locator('input[placeholder*="Model"], input[aria-label*="model" i]').first();
|
|
await expect(modelInput).toBeVisible({ timeout: 10000 });
|
|
|
|
// Add first column
|
|
await modelInput.fill("openai/gpt-4o-mini");
|
|
await addButton.click();
|
|
|
|
// Wait a moment for the column to appear
|
|
await page.waitForTimeout(300);
|
|
|
|
// Add second column
|
|
await modelInput.fill("claude-3-haiku");
|
|
await addButton.click();
|
|
|
|
await page.waitForTimeout(300);
|
|
|
|
// Both columns should be visible (each column shows a model name or remove button)
|
|
const removeButtons = page.getByRole("button", { name: /remove column/i });
|
|
// There should be at least 2 remove buttons (one per column)
|
|
const count = await removeButtons.count();
|
|
expect(count).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
test("Run all button is visible when there are columns", async ({ page }) => {
|
|
await gotoDashboardRoute(page, "/dashboard/playground");
|
|
|
|
const compareTab = page.getByRole("tab", { name: /compare/i });
|
|
await expect(compareTab).toBeVisible({ timeout: 15000 });
|
|
await compareTab.click();
|
|
|
|
// Add a column
|
|
const addButton = page.getByRole("button", { name: /add model/i });
|
|
await expect(addButton).toBeVisible({ timeout: 10000 });
|
|
|
|
const modelInput = page.locator('input[placeholder*="Model"], input[aria-label*="model" i]').first();
|
|
await expect(modelInput).toBeVisible({ timeout: 10000 });
|
|
await modelInput.fill("openai/gpt-4o");
|
|
await addButton.click();
|
|
|
|
await page.waitForTimeout(300);
|
|
|
|
// Run all button should be visible
|
|
const runAllButton = page.getByRole("button", { name: /run all/i });
|
|
await expect(runAllButton).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test("Cancel all button is visible and clickable", async ({ page }) => {
|
|
await gotoDashboardRoute(page, "/dashboard/playground");
|
|
|
|
const compareTab = page.getByRole("tab", { name: /compare/i });
|
|
await expect(compareTab).toBeVisible({ timeout: 15000 });
|
|
await compareTab.click();
|
|
|
|
// Wait for CompareTab to finish loading (it's a dynamic import with ssr: false).
|
|
// The "Add model column" button is always rendered once the component mounts and
|
|
// provides a reliable hydration signal before we check the Run/Cancel toolbar.
|
|
await expect(page.getByRole("button", { name: /add model/i })).toBeVisible({
|
|
timeout: 10000,
|
|
});
|
|
|
|
// The toolbar shows "Run all" when idle and "Cancel all" when streaming —
|
|
// they are mutually exclusive. Verify the toolbar control is always present
|
|
// by checking that exactly one of the two buttons is visible.
|
|
// (CompareTab.tsx renders <button aria-label="Run all columns"> or
|
|
// <button aria-label="Cancel all streams"> based on isAnyStreaming state.)
|
|
const runAllButton = page.getByRole("button", { name: /run all/i });
|
|
const cancelButton = page.getByRole("button", { name: /cancel all|abort/i });
|
|
// Use expect().toBeVisible() instead of isVisible() — the latter does not wait
|
|
// in Playwright 1.50+ (timeout option is deprecated/ignored on locator.isVisible).
|
|
const hasRunAll = await expect(runAllButton)
|
|
.toBeVisible({ timeout: 5000 })
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
const hasCancel = await expect(cancelButton)
|
|
.toBeVisible({ timeout: 1000 })
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
expect(hasRunAll || hasCancel).toBe(true);
|
|
});
|
|
});
|