import { test, expect } from "@playwright/test"; import { gotoDashboardRoute } from "./helpers/dashboardAuth"; function getTimeRangeSelector(page: import("@playwright/test").Page) { return page.getByRole("tablist", { name: /select time range/i }).first(); } async function waitForAnalyticsShell(page: import("@playwright/test").Page) { const mainTabList = page.locator('[role="tablist"]').first(); await expect(mainTabList).toBeVisible({ timeout: 15000 }); await expect( page .locator("button") .filter({ hasText: /overview/i, }) .first() ).toBeVisible({ timeout: 15000 }); } test.describe("Analytics Tabs UI", () => { test.beforeEach(async ({ page }) => { await page.route("**/api/usage/analytics", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ stats: { totalRequests: 1234, totalTokens: 567890, estimatedCost: 12.34, }, charts: {}, }), }); }); await page.route("**/api/usage/utilization**", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ timeRange: "24h", bucketSizeMinutes: 10, providers: ["codex", "claude", "gemini"], data: [ { timestamp: new Date(Date.now() - 3600000).toISOString(), provider: "codex", remainingPct: 75.5, isExhausted: false, windowKey: "5h", }, { timestamp: new Date().toISOString(), provider: "codex", remainingPct: 72.0, isExhausted: false, windowKey: "5h", }, ], }), }); }); await page.route("**/api/usage/combo-health**", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ timeRange: "24h", combo: { id: "test-combo", name: "Test Combo", models: ["openai/gpt-4", "anthropic/claude-3"], }, quotaHealth: { overall: { remainingAvg: 68.5, trend: "stable", }, providers: [ { provider: "openai", remaining: 75.0, trend: "stable", }, ], }, usageSkew: { modelDistribution: [ { model: "openai/gpt-4", count: 45, share: 0.6 }, { model: "anthropic/claude-3", count: 30, share: 0.4 }, ], gini: 0.2, }, performance: { successRate: 0.98, avgLatency: 1200, totalCalls: 75, }, }), }); }); await page.route("**/api/combos", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ combos: [ { id: "test-combo", name: "Test Combo", models: ["openai/gpt-4", "anthropic/claude-3"], strategy: "priority", isActive: true, }, ], }), }); }); }); test("displays all 5 analytics tabs", async ({ page }) => { await gotoDashboardRoute(page, "/dashboard/analytics"); await waitForAnalyticsShell(page); const mainTabList = page.locator('[role="tablist"]').first(); await expect(mainTabList).toBeVisible(); const tabButtons = page.locator( 'button[class*="segmented"], [role="tablist"] > button, [role="tablist"] div > button' ); const count = await tabButtons.count(); expect(count).toBeGreaterThanOrEqual(4); const tabLabels = ["overview", "evals", "search", "utilization", "combo health"]; for (const label of tabLabels) { const tabButton = page .locator("button") .filter({ hasText: new RegExp(label, "i"), }) .first(); await expect(tabButton).toBeVisible(); } }); test("Provider Utilization tab shows TimeRangeSelector and chart", async ({ page }) => { await gotoDashboardRoute(page, "/dashboard/analytics"); await waitForAnalyticsShell(page); const utilizationTab = page .locator("button") .filter({ hasText: /utilization/i, }) .first(); // Retry click until the tab switches, mitigating Next.js hydration race conditions await expect(async () => { await utilizationTab.click(); const timeRangeSelector = page .locator('[role="tablist"][aria-label]') .filter({ hasText: /1h/ }) .first(); await expect(timeRangeSelector).toBeVisible({ timeout: 1000 }); }).toPass({ timeout: 15000 }); const timeRangeSelector = page .locator('[role="tablist"][aria-label]') .filter({ hasText: /1h/ }) .first(); const timeRangeButtons = timeRangeSelector.locator('button[role="tab"]'); await expect(timeRangeButtons.first()).toBeVisible(); const chart = page .locator('svg.recharts-surface, .recharts-wrapper, div[class*="recharts"]') .first(); await expect(chart).toBeVisible(); }); test("Combo Health tab displays health cards and metrics", async ({ page }) => { await gotoDashboardRoute(page, "/dashboard/analytics"); await waitForAnalyticsShell(page); const comboHealthTab = page .locator("button") .filter({ hasText: /combo.*health/i, }) .first(); await expect(async () => { await comboHealthTab.click(); const timeRangeSelector = getTimeRangeSelector(page); await expect(timeRangeSelector).toBeVisible({ timeout: 1000 }); }).toPass({ timeout: 15000 }); const mainContent = page.locator('main, [class*="dashboard"], div[class*="container"]').first(); await expect(mainContent).toBeVisible(); const timeRangeSelector = getTimeRangeSelector(page); await expect(timeRangeSelector).toBeVisible(); const metricElements = page .locator('[class*="rounded-lg"], [class*="border"], [class*="bg-black/"]') .first(); await expect(metricElements).toBeVisible(); }); test("time range change triggers network request", async ({ page }) => { await gotoDashboardRoute(page, "/dashboard/analytics"); await waitForAnalyticsShell(page); const utilizationTab = page .locator("button") .filter({ hasText: /utilization/i, }) .first(); await expect(async () => { await utilizationTab.click(); const timeRangeSelector = getTimeRangeSelector(page); await expect(timeRangeSelector).toBeVisible({ timeout: 1000 }); }).toPass({ timeout: 15000 }); let networkRequestMade = false; page.on("request", (request) => { if (request.url().includes("/api/usage/utilization")) { const url = new URL(request.url()); if (url.searchParams.get("range") === "7d") { networkRequestMade = true; } } }); const timeRangeSelector = getTimeRangeSelector(page); const sevenDayButton = timeRangeSelector .locator('button[role="tab"]') .filter({ hasText: "7d" }) .first(); if (await sevenDayButton.isVisible()) { await sevenDayButton.click(); } else { const buttons = timeRangeSelector.locator('button[role="tab"]'); const count = await buttons.count(); for (let i = 0; i < count; i++) { const button = buttons.nth(i); const text = await button.textContent(); if (text?.includes("7")) { await button.click(); break; } } } await page.waitForTimeout(1000); await expect .poll(() => networkRequestMade, { message: "Expected time range change to trigger network request", }) .toBe(true); }); test("tab switching persists state correctly", async ({ page }) => { await gotoDashboardRoute(page, "/dashboard/analytics"); await waitForAnalyticsShell(page); const overviewTab = page .locator("button") .filter({ hasText: /overview/i, }) .first(); await expect(async () => { await overviewTab.click(); const overviewStats = page.locator("text=Total API Requests").first(); // Overview uses UsageAnalytics, we wait for a generic evidence of overview // Or simply just wait 300ms if click doesn't throw }) .toPass({ timeout: 15000 }) .catch(() => {}); const utilizationTab = page .locator("button") .filter({ hasText: /utilization/i, }) .first(); await expect(async () => { await utilizationTab.click(); const chart = page .locator('svg.recharts-surface, .recharts-wrapper, div[class*="recharts"]') .first(); await expect(chart).toBeVisible({ timeout: 1000 }); }).toPass({ timeout: 15000 }); const chart = page .locator('svg.recharts-surface, .recharts-wrapper, div[class*="recharts"]') .first(); await expect(chart).toBeVisible(); const comboHealthTab = page .locator("button") .filter({ hasText: /combo.*health/i, }) .first(); await expect(async () => { await comboHealthTab.click(); const timeRangeSelector = getTimeRangeSelector(page); await expect(timeRangeSelector).toBeVisible({ timeout: 1000 }); }).toPass({ timeout: 15000 }); const timeRangeSelector = getTimeRangeSelector(page); await expect(timeRangeSelector).toBeVisible(); await expect(async () => { await utilizationTab.click(); await expect(chart).toBeVisible({ timeout: 1000 }); }).toPass({ timeout: 15000 }); await expect(chart).toBeVisible(); }); });