import { tmpdir } from "node:os"; import { randomUUID } from "node:crypto"; import path from "node:path"; import type { Page } from "@playwright/test"; import type { UsageReportEntry } from "@getpaseo/protocol/messages"; import { expect, test } from "../support/fixtures"; import { gotoAppShell, openSettings } from "../support/helpers/app"; import { addConnectedHostAndReload } from "../support/helpers/hosts"; import { startIsolatedHostDaemon } from "../support/helpers/isolated-host-daemon"; import { openSettingsSection } from "../support/helpers/settings"; import { getServerId } from "../support/helpers/server-id"; import { seedSidebarFooterPreferences } from "../support/helpers/sidebar-nav-settings"; import { installUsageReportsFixture, type UsageListRequest, type UsageReportsFixture, } from "../support/helpers/usage-reports"; import { installLoginUsage, expectLoginUsageFreshness, installCodexWindowUsage, expectCodexReportedWindows, refreshLoginUsage, hoverUsageWindow, } from "../support/helpers/usage-login"; import { claudeAndCodexReports, expectPinnedUsage, openCompactSidebar, openUsageFromIcon, openUsageFromItem, pinRow, openUsageOptions, closeUsageOptions, expectUsageOptionsFitContent, showUsageAs, refreshAllUsage, togglePin, usageItem, } from "../support/helpers/usage-sidebar-item"; const emptyHome = path.join(tmpdir(), `paseo-usage-empty-${randomUUID()}`); test.use({ e2eDaemonEnvironment: { HOME: emptyHome, USERPROFILE: emptyHome, CODEX_HOME: emptyHome, CLAUDE_CONFIG_DIR: emptyHome, XDG_DATA_HOME: emptyHome, PI_CODING_AGENT_DIR: emptyHome, COPILOT_TOKEN: "", GITHUB_TOKEN: "", GITHUB_PAT: "", CURSOR_ACCESS_TOKEN: "", CURSOR_TOKEN: "", GROK_API_KEY: "", GROK_TOKEN: "", KIMI_TOKEN: "", KIMI_API_KEY: "", KIMI_CODE_HOME: emptyHome, MINIMAX_API_KEY: "", ZAI_API_KEY: "", GLM_API_KEY: "", }, }); // These tests read the sidebar summary and pin windows, which both need the Usage item on. test.beforeEach(async ({ page }) => { await seedSidebarFooterPreferences(page, [{ key: "usage", visible: true }]); }); function forcedRefreshes(usage: UsageReportsFixture): UsageListRequest[] { return usage.listRequests().filter((request) => request.forceRefresh); } // Two hours reads "2h ago" for an hour, so the assertion cannot race the clock. function twoHoursAgo(): string { return new Date(Date.now() - 2 * 60 * 60_000).toISOString(); } /** Set PASEO_QA_SCREENSHOT_DIR to keep a QA screenshot. */ async function qaScreenshot(page: Page, name: string) { const directory = process.env.PASEO_QA_SCREENSHOT_DIR; if (!directory) return; await page.waitForTimeout(600); await page.addStyleTag({ content: ".__expo_fast_refresh { display: none !important; }" }); await page.screenshot({ path: path.join(directory, `${name}.png`) }); } function hostFilter(page: Page) { return page.locator('[data-testid="usage-host-filter-trigger"]:visible'); } function weeklyReport(sourceId: string, usedPct: number): UsageReportEntry { return { id: `${sourceId}:a`, account: {}, fetchedAt: twoHoursAgo(), sourceId, sourceLabel: `${sourceId} plan`, report: { status: "available", windows: [{ id: "weekly", label: "Weekly", usedPct }] }, }; } function installReversedUsageRefresh(page: Page) { const initial = [weeklyReport("opencode", 31), weeklyReport("openai", 12)]; const refreshed = [weeklyReport("openai", 22), weeklyReport("opencode", 41)]; return installUsageReportsFixture(page, { lists: [(request) => (request.forceRefresh ? refreshed : initial)], }); } async function openUsageSummaryAtWidth(page: Page, width: number) { await page.setViewportSize({ width, height: 900 }); await gotoAppShell(page); if (width === 390) await openCompactSidebar(page); await expectPinnedUsage(page, ["31% Weekly", "12% Weekly"]); await openUsageFromItem(page); } async function expectUsageProviderOrder(page: Page, summary: string[]) { const group = page.getByTestId(`usage-host-${getServerId()}`); await expect(group.getByText(/^(opencode|openai) plan$/)).toHaveText([ "opencode plan", "openai plan", ]); await expectPinnedUsage(page, summary); } async function refreshUsageAndWaitForReports(page: Page, usage: UsageReportsFixture) { const requestCount = usage.listRequests().length; await refreshAllUsage(page); await usage.waitForListRequests(requestCount + 1); const group = page.getByTestId(`usage-host-${getServerId()}`); await expect(group.getByText("41% used", { exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "Refresh all", exact: true })).toBeEnabled(); } test.describe("usage modal", () => { test("opens from the sidebar on the host's reports, with no host filter for one host", async ({ page, }) => { test.setTimeout(120_000); const serverId = getServerId(); const usage = await installUsageReportsFixture(page, { lists: [ [ { id: "alpha:a", account: {}, fetchedAt: twoHoursAgo(), sourceId: "alpha", sourceLabel: "Alpha plan", report: { status: "available", windows: [{ id: "weekly", label: "Weekly", usedPct: 31 }], }, }, { id: "beta:b", account: {}, fetchedAt: "2026-01-01T00:00:00.000Z", sourceId: "beta", sourceLabel: "Beta plan", report: { status: "unavailable", problem: { kind: "no_quota", detail: "No active coding plan" }, }, }, ], ], }); await gotoAppShell(page); await openUsageFromItem(page); await usage.waitForListRequests(1); const group = page.getByTestId(`usage-host-${serverId}`); await expect(group.getByText("Alpha plan", { exact: true })).toBeVisible({ timeout: 10_000 }); await expect(hostFilter(page)).toHaveCount(0); await qaScreenshot(page, "usage-modal-one-host"); await expect(group.getByText("31% used")).toBeVisible(); await expect(group.getByText("Beta plan", { exact: true })).toBeVisible(); await expect(group.getByText("Unavailable", { exact: true })).toBeVisible(); await group.getByTestId("usage-refresh").first().hover(); await expect(page.getByTestId("usage-freshness-tooltip")).toHaveText("Updated 2h ago"); await expect(group.getByTestId("usage-freshness")).toHaveCount(0); }); for (const width of [1440, 390]) { test(`keeps sidebar and modal provider order across a reversed refresh at width ${width}`, async ({ page, }) => { const usage = await installReversedUsageRefresh(page); await openUsageSummaryAtWidth(page, width); await expectUsageProviderOrder(page, ["31% Weekly", "12% Weekly"]); await qaScreenshot(page, `usage-order-${width}-before-refresh`); await refreshUsageAndWaitForReports(page, usage); await expectUsageProviderOrder(page, ["41% Weekly", "22% Weekly"]); await qaScreenshot(page, `usage-order-${width}-after-refresh`); }); } test("refreshes one report from its card", async ({ page }) => { test.setTimeout(120_000); const serverId = getServerId(); const beta: UsageReportEntry = { id: "beta:b", account: {}, fetchedAt: twoHoursAgo(), sourceId: "beta", sourceLabel: "Beta plan", report: { status: "available", windows: [{ id: "weekly", label: "Weekly", usedPct: 12 }], }, }; const alpha = (usedPct: number, fetchedAt: string): UsageReportEntry => ({ id: "alpha:a", account: {}, fetchedAt, sourceId: "alpha", sourceLabel: "Alpha plan", report: { status: "available", windows: [{ id: "weekly", label: "Weekly", usedPct }], }, }); // The sidebar summary and the modal each load reports; only a card's Refresh forces one. const usage = await installUsageReportsFixture(page, { lists: [ (request) => request.forceRefresh ? [alpha(58, new Date().toISOString())] : [alpha(31, twoHoursAgo()), beta], ], }); await gotoAppShell(page); await openUsageFromItem(page); const group = page.getByTestId(`usage-host-${serverId}`); await expect(group.getByText("31% used")).toBeVisible({ timeout: 10_000 }); const alphaRefresh = group.getByTestId("usage-refresh").first(); await alphaRefresh.click(); await expect .poll(() => forcedRefreshes(usage)) .toEqual([{ forceRefresh: true, reportIds: ["alpha:a"] }]); await expect(group.getByText("58% used")).toBeVisible(); await expect(group.getByText("12% used")).toBeVisible(); await group.getByTestId("usage-refresh").nth(1).hover(); await expect(page.getByTestId("usage-freshness-tooltip")).toHaveText("Updated 2h ago"); await alphaRefresh.hover(); await expect(page.getByTestId("usage-freshness-tooltip")).toHaveText("Updated just now"); }); test("shows the host once it connects after a cold load on a phone", async ({ page }) => { test.setTimeout(120_000); const serverId = getServerId(); const usage = await installUsageReportsFixture(page, { lists: [ [ { id: "alpha:a", account: {}, fetchedAt: twoHoursAgo(), sourceId: "alpha", sourceLabel: "Alpha plan", report: { status: "available", windows: [] }, }, ], ], }); await page.setViewportSize({ width: 390, height: 844 }); await gotoAppShell(page); await openCompactSidebar(page); await openUsageFromIcon(page); await usage.waitForListRequests(1); const group = page.getByTestId(`usage-host-${serverId}`); await expect(group.getByText("Alpha plan", { exact: true })).toBeVisible({ timeout: 10_000 }); await expect(group.getByTestId("usage-freshness")).toHaveText("Updated 2h ago"); }); test("tells the user to update a host without usage support", async ({ page }) => { test.setTimeout(120_000); const serverId = getServerId(); const usage = await installUsageReportsFixture(page, { usageSupported: false }); await gotoAppShell(page); // Without reports the footer has no Usage item; its Usage icon opens Usage. await openUsageFromIcon(page); await expect( page.getByTestId(`usage-host-${serverId}`).getByText(/^Update .+ to see usage$/), ).toBeVisible({ timeout: 10_000 }); await qaScreenshot(page, "phase7-usage-update-host"); await expect(page.getByTestId("usage-options-menu")).toHaveCount(0); await expect(page.getByTestId("usage-refresh-all")).toHaveCount(0); expect(usage.listRequests()).toHaveLength(0); }); test("a host picked in the Usage modal is the sidebar's host too, after a reload", async ({ page, }) => { test.setTimeout(420_000); const primaryServerId = getServerId(); const secondary = await startIsolatedHostDaemon( `srv_usage_${randomUUID().replaceAll("-", "").slice(0, 12)}`, ); try { await installUsageReportsFixture(page, { lists: [[weeklyReport("alpha", 31)]] }); await installUsageReportsFixture(page, { port: secondary.port, lists: [[weeklyReport("beta", 12)]], }); await gotoAppShell(page); await addConnectedHostAndReload(page, { serverId: secondary.serverId, label: "Secondary box", port: secondary.port, }); // Nothing picked and no workspace open: the first host. await expectPinnedUsage(page, ["31% Weekly"]); await openUsageFromItem(page); await expect(page.getByTestId(`usage-host-${primaryServerId}`)).toBeVisible(); await hostFilter(page).click(); await page.getByTestId(`usage-host-filter-item-${secondary.serverId}`).click(); await expect( page.getByTestId(`usage-host-${secondary.serverId}`).getByText("12% used"), ).toBeVisible({ timeout: 30_000 }); await expect(hostFilter(page)).toContainText("Secondary box"); await qaScreenshot(page, "usage-modal-picked-host"); await expectPinnedUsage(page, ["12% Weekly"]); // The e2e seed resets the host list on every load, so reopening re-adds the second host. await addConnectedHostAndReload(page, { serverId: secondary.serverId, label: "Secondary box", port: secondary.port, }); await expectPinnedUsage(page, ["12% Weekly"]); await openUsageFromItem(page); await expect( page.getByTestId(`usage-host-${secondary.serverId}`).getByText("12% used"), ).toBeVisible({ timeout: 30_000 }); } finally { await secondary.close().catch(() => undefined); } }); }); const loginWindows: UsageReportEntry["report"] = { status: "available", windows: [ { id: "five_hour", label: "Session", shortLabel: "5h", summary: true, usedPct: 31 }, { id: "weekly", label: "Weekly", shortLabel: "wk", summary: true, usedPct: 54 }, ], }; test("expired login refreshes to windows with visible pin toggles", async ({ page }) => { const fixture = await installLoginUsage({ status: "unavailable", problem: { kind: "expired", expiresAt: new Date(Date.now() - 3_600_000).toISOString(), refreshedBy: "claude", }, }); try { await gotoAppShell(page); await openUsageFromIcon(page); await expect( page.getByText("Claude: Login expired 1h ago. Run claude to refresh it.", { exact: true }), ).toBeVisible(); await expect(page.getByText("Unavailable", { exact: true })).toBeVisible(); await qaScreenshot(page, "usage-expired-login"); await fixture.setReport(loginWindows); await refreshLoginUsage(page); await expect( page.getByText("Claude: Login expired 1h ago. Run claude to refresh it.", { exact: true }), ).toHaveCount(0); await expectPinnedUsage(page, ["31% 5h", "54% wk"]); const row = page.getByRole("checkbox", { name: /^Pin Claude Weekly, / }); await expect(row).toBeChecked(); await expect(row.getByTestId("usage-pin-glyph-pinned")).toHaveCSS("opacity", "1"); await hoverUsageWindow(page, "Weekly"); await expect(page.getByText("Unpin", { exact: true })).toBeVisible(); await expect(row.getByTestId("usage-pin-glyph-pinned")).toHaveCSS("opacity", "1"); await expect(row.getByTestId("usage-pin-glyph-pinned").locator("svg")).not.toHaveAttribute( "fill", "none", ); await qaScreenshot(page, "usage-pin-hover"); await togglePin(page.getByTestId("usage-report-login-journey:account"), "Claude", "Weekly"); await expect(row.getByTestId("usage-pin-glyph-unpinned")).toHaveCSS("opacity", "1"); await expect(row.getByTestId("usage-pin-glyph-unpinned").locator("svg")).toHaveAttribute( "fill", "none", ); await hoverUsageWindow(page, "Weekly"); await expect(page.getByText("Pin", { exact: true })).toBeVisible(); await expectPinnedUsage(page, ["31% 5h"]); await qaScreenshot(page, "usage-pin-unselected"); await togglePin(page.getByTestId("usage-report-login-journey:account"), "Claude", "Weekly"); await expect(row.getByTestId("usage-pin-glyph-pinned")).toHaveCSS("opacity", "1"); await expectPinnedUsage(page, ["31% 5h", "54% wk"]); } finally { await fixture.cleanup(); } }); test("compact usage rows always show the pin glyph", async ({ page }) => { const fixture = await installLoginUsage(loginWindows); try { await page.setViewportSize({ width: 390, height: 844 }); await gotoAppShell(page); await openCompactSidebar(page); await openUsageFromIcon(page); const card = page.getByTestId("usage-report-login-journey:account"); await expect(card.getByTestId("usage-pin-glyph-pinned")).toHaveCount(2); await expect(card.getByTestId("usage-pin-glyph-pinned").nth(0)).toHaveCSS("opacity", "1"); await expect(card.getByTestId("usage-pin-glyph-pinned").nth(1)).toHaveCSS("opacity", "1"); await qaScreenshot(page, "usage-pin-compact"); } finally { await fixture.cleanup(); } }); for (const shape of ["seven-day-only", "Spark"] as const) { test(`Codex ${shape} windows use provider durations on the card and sidebar`, async ({ page, }) => { const reset = Math.floor(Date.now() / 1000) + 5 * 86400; const source = await installCodexWindowUsage({ rate_limit: { primary_window: { used_percent: 11, limit_window_seconds: 604800, reset_at: reset }, secondary_window: null, }, additional_rate_limits: shape === "Spark" ? [ { limit_name: "GPT-5.3-Codex-Spark", metered_feature: "codex_bengalfox", rate_limit: { primary_window: { used_percent: 0, limit_window_seconds: 18000, reset_at: Math.floor(Date.now() / 1000) + 3600, }, secondary_window: { used_percent: 0, limit_window_seconds: 604800, reset_at: reset, }, }, }, ] : [], }); try { await page.setViewportSize({ width: 1440, height: 900 }); await gotoAppShell(page); await expect(usageItem(page)).toBeVisible(); await openUsageFromItem(page); await qaScreenshot(page, `codex-${shape}-card-and-summary`); await expectCodexReportedWindows(page, shape); await qaScreenshot(page, `codex-${shape}-pins`); } finally { await source.cleanup(); } }); } for (const theme of ["light", "dark"] as const) { for (const [size, viewport] of Object.entries({ desktop: { width: 1440, height: 900 }, compact: { width: 390, height: 844 }, })) { test(`Usage Settings cog ${size} ${theme}`, async ({ page }, testInfo) => { test.setTimeout(120_000); await page.addInitScript((value) => { const key = "@paseo:app-settings"; const current = JSON.parse(localStorage.getItem(key) ?? "{}"); localStorage.setItem(key, JSON.stringify({ ...current, theme: value })); }, theme); const usage = await installUsageReportsFixture(page, { lists: [() => claudeAndCodexReports()], }); await page.setViewportSize(viewport); await gotoAppShell(page); if (size === "compact") await openCompactSidebar(page); await openUsageFromIcon(page); const screen = page.getByTestId(`usage-host-${getServerId()}`); await expect(screen.getByText("Claude", { exact: true })).toBeVisible({ timeout: 30_000 }); const settings = page.getByTestId("usage-options-menu"); await expect(settings).toHaveAccessibleName("Settings"); await expect(page.getByTestId("usage-display-used")).toHaveCount(0); await captureSettingsState(page, testInfo, `${size}-${theme}-cog`); await openUsageOptions(page); await expectUsageOptionsFitContent(page); await expect(page.getByText("Pinned windows show in the sidebar footer")).toBeVisible(); await expect(page.getByTestId("usage-display-used")).toHaveAttribute("aria-selected", "true"); await captureSettingsState(page, testInfo, `${size}-${theme}-settings`); await showUsageAs(page, "remaining"); await expect(screen.getByText("69% left")).toBeVisible(); await showUsageAs(page, "used"); await expect(pinRow(screen, "Claude", "Session")).toHaveAccessibleName( /^Pin Claude Session, 31% used · resets /, ); await openUsageOptions(page); await closeUsageOptions(page); await refreshAllUsage(page); await expect.poll(() => forcedRefreshes(usage)).toEqual([{ forceRefresh: true }]); }); } } async function captureSettingsState( page: Page, testInfo: import("@playwright/test").TestInfo, name: string, ) { await qaScreenshot(page, name); await testInfo.attach(name, { body: await page.screenshot(), contentType: "image/png" }); } test("an account with all logins failing shows each harness and remedy, then only usage when one succeeds", async ({ page, }) => { const fixture = await installLoginUsage([ { harness: "Codex", report: { status: "error", error: "Usage API returned 500. Try again." } }, { harness: "OpenCode", report: { status: "unavailable", problem: { kind: "rejected", status: 401, refreshedBy: "opencode" }, }, }, { harness: "Pi", report: { status: "unavailable", problem: { kind: "no_quota", detail: "No active plan" } }, }, { harness: "OMP", report: { status: "unavailable", problem: { kind: "rejected", status: 403, refreshedBy: "omp" }, }, }, ]); try { await gotoAppShell(page); await openUsageFromIcon(page); await expectLoginErrors(page, [ "Codex: Usage API returned 500. Try again.", "OpenCode: Login rejected (HTTP 401). Run opencode to refresh it.", "Pi: No active plan", "OMP: Login rejected (HTTP 403). Run omp to refresh it.", ]); await fixture.setReport([ { harness: "Codex", report: { status: "error", error: "Usage API returned 500. Try again." }, }, { harness: "OpenCode", report: { status: "available", windows: [{ id: "weekly", label: "Weekly", usedPct: 42 }] }, }, ]); await refreshLoginUsage(page); await expectLoginUsageOnly(page, "42% used"); } finally { await fixture.cleanup(); } }); async function expectLoginErrors(page: Page, lines: string[]) { const card = page.getByTestId("usage-report-login-journey:account"); await expect(card.getByTestId("usage-login-error")).toHaveText(lines.join("\n")); } async function expectLoginUsageOnly(page: Page, percentage: string) { const card = page.getByTestId("usage-report-login-journey:account"); await expect(card.getByText(new RegExp(`^${percentage}(?:$| ·)`))).toBeVisible(); await expect(card.getByTestId("usage-login-error")).toHaveCount(0); } for (const compact of [false, true]) { test(`failed polls retain usage and successful freshness (${compact ? "footer" : "tooltip"})`, async ({ page, }) => { const fixture = await installLoginUsage({ status: "error", error: "Initial failure" }); try { await page.clock.install(); await page.setViewportSize( compact ? { width: 390, height: 844 } : { width: 1440, height: 900 }, ); await gotoAppShell(page); if (compact) await openCompactSidebar(page); await openUsageFromIcon(page); await expectLoginErrors(page, ["Claude: Initial failure"]); await test.step("developer errors do not interrupt freshness inspection", async () => { await page.evaluate(() => console.error("E2E: developer toast while Usage is open")); await expectLoginUsageFreshness(page, "No successful update", compact); }); await fixture.setReport({ status: "available", windows: [{ id: "weekly", label: "Weekly", usedPct: 42, resetsAt: "2000-01-01T00:00:00Z" }], }); // Move only the browser clock; real daemon fetches continue using the host clock. await page.clock.setFixedTime(new Date(Date.now() + 3_900_000)); await refreshLoginUsage(page); await expectLoginUsageOnly(page, "42% used"); await advanceUsageClock(page, 60_000); await expectLoginUsageFreshness(page, "Updated 1h ago", compact); await fixture.setReport({ status: "error", error: "Rate limited" }); await refreshLoginUsage(page); await expectRetainedLoginUsage(page, "Claude: Rate limited"); await expectLoginUsageFreshness(page, "Last updated 1h ago", compact); await refreshLoginUsage(page); await expectRetainedLoginUsage(page, "Claude: Rate limited"); await expectLoginUsageFreshness(page, "Last updated 1h ago", compact); await fixture.setReport({ status: "unavailable", problem: { kind: "no_quota", detail: "No active quota" }, }); await refreshLoginUsage(page); await expectRetainedLoginUsage(page, "Claude: No active quota"); await expectLoginUsageFreshness(page, "Last updated 1h ago", compact); await fixture.setReport({ status: "available", windows: [{ id: "weekly", label: "Weekly", usedPct: 43 }], }); await page.clock.setFixedTime(new Date(Date.now() + 150_000)); await refreshLoginUsage(page); await expectLoginUsageOnly(page, "43% used"); await advanceUsageClock(page, 60_000); await expect(page.getByText("42% used", { exact: true })).toHaveCount(0); await expectLoginUsageFreshness(page, "Updated 2m ago", compact); } finally { await fixture.cleanup(); } }); } async function expectRetainedLoginUsage(page: Page, message: string) { const card = page.getByTestId("usage-report-login-journey:account"); await expect(card.getByText(/^42% used(?:$| ·)/)).toBeVisible(); await expect(card.getByTestId("usage-login-error")).toHaveText(message); await expect(card.getByText("Reset time passed", { exact: false })).toBeVisible(); } async function switchToJapanese(page: Page) { await page.getByRole("button", { name: "Close", exact: true }).click(); await openSettings(page); await openSettingsSection(page, "general"); await page.getByRole("button", { name: "System", exact: true }).click(); await page.getByRole("menuitem", { name: "日本語 - Japanese", exact: true }).click(); await page.getByRole("button", { name: "戻る", exact: true }).click(); } async function openJapaneseUsage(page: Page) { await page.getByRole("button", { name: "使用状況", exact: true }).click(); await expect(page.getByTestId("usage-modal").first()).toBeVisible(); } test("usage percentages state their meaning and follow the selected language", async ({ page }) => { test.setTimeout(120_000); const login = await installLoginUsage({ status: "available", windows: [{ id: "session", label: "Session", usedPct: 70, summary: true }], }); try { await gotoAppShell(page); await openUsageFromIcon(page); await expect(page.getByText("70% used", { exact: true })).toBeVisible(); await switchToJapanese(page); await expect(usageItem(page)).toHaveAccessibleName(/Claude セッション 70% 使用済み/); await openJapaneseUsage(page); await expect(page.getByText("70% 使用済み", { exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "すべて更新", exact: true })).toBeVisible(); await page.getByRole("button", { name: "設定", exact: true }).last().click(); await expect(page.getByText("割合", { exact: true })).toBeVisible(); await expect(page.getByText("使用済み", { exact: true })).toBeVisible(); await expect(page.getByText("残り", { exact: true })).toBeVisible(); } finally { await login.cleanup(); } }); test("open usage clocks advance without refreshing or reopening", async ({ page }) => { test.setTimeout(120_000); const now = new Date(); const reset = new Date(now.getTime() + 30.5 * 60_000).toISOString(); const login = await installLoginUsage({ status: "available", windows: [ { id: "session", label: "Session", usedPct: 42, resetsAt: reset }, { id: "weekly", label: "Weekly", usedPct: 80, runsOutAt: reset, shortfallPct: 10 }, ], balances: [{ id: "credits", label: "Credits", unit: "usd", remaining: 12, resetsAt: reset }], }); try { await openCompactUsageWithClock(page, now); const card = page.getByTestId("usage-report-login-journey:account"); await expect(card.getByTestId("usage-freshness")).toHaveText("Updated just now"); await expect(card.getByText("resets 30m", { exact: false }).first()).toBeVisible(); // The shared minute clock is wall-aligned, and the daemon timestamps use real time. // After two browser minutes the latest displayed tick is one or two minutes old. await advanceUsageClock(page, 120_000); await expect(card.getByTestId("usage-freshness")).toHaveText(/^Updated [12]m ago$/); await expect(card.getByText(/resets (28|29)m/).first()).toBeVisible(); await expect(card.getByText(/runs out (28|29)m/)).toBeVisible(); await expect(card.getByTestId("provider-usage-balance-credits-reset")).toHaveText( /^resets (28|29)m$/, ); } finally { await login.cleanup(); } }); async function openCompactUsageWithClock(page: Page, now: Date) { await page.setViewportSize({ width: 390, height: 844 }); await page.clock.install({ time: now }); await gotoAppShell(page); await openCompactSidebar(page); await openUsageFromIcon(page); } async function advanceUsageClock(page: Page, milliseconds: number) { await page.clock.fastForward(milliseconds); }