diff --git a/apps/admin/e2e/mobile-workspace.spec.ts b/apps/admin/e2e/mobile-workspace.spec.ts index 8a816b5..9ed22cd 100644 --- a/apps/admin/e2e/mobile-workspace.spec.ts +++ b/apps/admin/e2e/mobile-workspace.spec.ts @@ -68,20 +68,20 @@ test("mobile dashboard uses the compact header, period picker, and KPI grid", as await expect(page.getByRole("menuitem", { name: "Previous cycles" })).toBeVisible(); await page.keyboard.press("Escape"); - const commitment = page.locator("p").filter({ hasText: /^Subscription commitment$/ }).first().locator(".."); + const people = page.locator("p").filter({ hasText: /^People$/ }).first().locator(".."); const usage = page.locator("p").filter({ hasText: /^Estimated usage$/ }).first().locator(".."); const spendPerDay = page.locator("p").filter({ hasText: /^Est\. spend\/day$/ }).first().locator(".."); - const [commitmentBox, usageBox, spendPerDayBox] = await Promise.all([ - commitment.boundingBox(), + const [peopleBox, usageBox, spendPerDayBox] = await Promise.all([ + people.boundingBox(), usage.boundingBox(), spendPerDay.boundingBox(), ]); - expect(commitmentBox).not.toBeNull(); + expect(peopleBox).not.toBeNull(); expect(usageBox).not.toBeNull(); expect(spendPerDayBox).not.toBeNull(); - expect(Math.abs(commitmentBox!.y - usageBox!.y)).toBeLessThan(4); - expect(usageBox!.x).toBeGreaterThan(commitmentBox!.x); - expect(spendPerDayBox!.y).toBeGreaterThan(commitmentBox!.y); + expect(Math.abs(peopleBox!.y - usageBox!.y)).toBeLessThan(4); + expect(usageBox!.x).toBeGreaterThan(peopleBox!.x); + expect(spendPerDayBox!.y).toBeGreaterThan(peopleBox!.y); const syncStatus = page.getByText(/^Last synced/); const syncButton = page.getByRole("button", { name: /Sync now|Syncing/ }); diff --git a/apps/admin/e2e/workspace.spec.ts b/apps/admin/e2e/workspace.spec.ts index e0c170c..9e049ec 100644 --- a/apps/admin/e2e/workspace.spec.ts +++ b/apps/admin/e2e/workspace.spec.ts @@ -31,12 +31,18 @@ test("workspace startup has no bootstrap gate or duplicate page-data request", a const appRequests: string[] = []; const failedAppRequests: string[] = []; page.on("request", (request) => { - const path = new URL(request.url()).pathname; - if (path.startsWith("/api/app/")) appRequests.push(path); + const url = new URL(request.url()); + if (url.pathname.startsWith("/api/app/")) { + appRequests.push(`${url.pathname}${url.search}`); + } }); page.on("requestfailed", (request) => { - const path = new URL(request.url()).pathname; - if (path.startsWith("/api/app/")) failedAppRequests.push(path); + const failure = request.failure()?.errorText ?? ""; + if (failure.includes("ERR_ABORTED") || failure.includes("NS_BINDING_ABORTED")) return; + const url = new URL(request.url()); + if (url.pathname.startsWith("/api/app/")) { + failedAppRequests.push(`${url.pathname}${url.search}`); + } }); await page.goto("/dashboard"); @@ -45,7 +51,12 @@ test("workspace startup has no bootstrap gate or duplicate page-data request", a expect(appRequests).not.toContain("/api/app/bootstrap"); // The client workspace context and destination page model each load at most once. expect(appRequests.filter((path) => path === "/api/app/workspace-context").length).toBeLessThanOrEqual(1); - expect(appRequests.filter((path) => path === "/api/app/dashboard").length).toBeLessThanOrEqual(1); + const dashboardRequests = appRequests.filter((path) => path.startsWith("/api/app/dashboard")); + expect(dashboardRequests.filter((path) => path.includes("slice=shell")).length).toBeLessThanOrEqual(2); + expect(dashboardRequests.filter((path) => path.includes("slice=metrics")).length).toBeLessThanOrEqual(2); + expect( + dashboardRequests.filter((path) => !path.includes("slice=shell") && !path.includes("slice=metrics")).length, + ).toBeLessThanOrEqual(1); expect(failedAppRequests).toEqual([]); }); @@ -169,7 +180,9 @@ test("dashboard exposes seeded calculation output and all period controls", asyn await expect(page.getByRole("heading", { name: "Spend, traffic, coverage." })).toBeVisible(); await expect(page.getByRole("heading", { name: "Requests." })).toBeVisible(); await expect(page.getByText("Subscription commitment")).toBeVisible(); - await expect(page.getByText("purchased seats · current cycle")).toBeVisible(); + await expect( + page.getByText(/All tracked plans on track|Purchased seats · run-out from allowance pace|Earliest run-out/i), + ).toBeVisible(); await expect(page.getByText("$40.00").first()).toBeVisible(); await expect(page.getByText("Estimated usage").first()).toBeVisible(); await expect(page.getByText("$27.00").first()).toBeVisible(); @@ -180,17 +193,15 @@ test("dashboard exposes seeded calculation output and all period controls", asyn // Free/detected tools (OpenCode, ChatGPT Free) appear in Current cycles. await expect(page.getByRole("link", { name: /OpenCode/i }).first()).toBeVisible(); await expect(page.getByRole("link", { name: /ChatGPT.*Free/i }).first()).toBeVisible(); - await expect(page.getByText("1.5M")).toHaveCount(0); + await expect(page.getByRole("heading", { name: "Top models." })).toBeVisible(); await expect(page.getByRole("heading", { name: "Current cycles." })).toBeVisible(); await page.getByRole("link", { name: "Previous cycles" }).click(); await expect(page).toHaveURL(/view=previous_cycles/); await expect(page.getByRole("heading", { name: "Previous cycles." })).toBeVisible(); - await expect(page.getByText("purchased seats · previous cycle")).toBeVisible(); await expect(page.getByText("verified + estimated · previous cycles")).toBeVisible(); await page.getByRole("link", { name: "Current cycles" }).click(); await expect(page).toHaveURL(/view=current_cycles/); await expect(page.getByRole("heading", { name: "Current cycles." })).toBeVisible(); - await expect(page.getByText("purchased seats · current cycle")).toBeVisible(); await page.goto("/dashboard?view=last_30_days"); await page.getByRole("button", { name: "Adjust rolling period" }).click(); await page.getByText("Last 14 days").click();