Files
OmniRoute/tests/e2e/group-b-quota-plans-config.spec.ts
diegosouzapw fc77100c3f test: stabilize quota syncQuotaCombos shards + fix 2 e2e specs
- quota-combo-balancing / quota-multiprovider: eliminate the per-test full SQLite
  migration (migrate once at module load; resetStorage now DELETEs rows instead of
  rmSync+re-migrate) so it no longer races --test-force-exit under concurrency;
  drain the fire-and-forget syncQuotaCombosGuarded dispatched by createPool/
  updatePool (flushPendingSyncs via setImmediate) so assertions see deterministic
  combo state; assert the GROUP-slug combo name (combos are named by group, like
  quota-combo-groups) and seed the group. Validated on CI shards 5/8 + 8/8 (7 runs).
- playground-compare: wait for CompareTab (dynamic import) to mount, and use
  expect().toBeVisible() instead of locator.isVisible() (which no longer waits in
  Playwright 1.50+).
- group-b-quota-plans-config: drop the unreliable raw-HTML "500" substring check
  (Next.js chunk hashes contain "500"); keep the real error-boundary text check.
2026-06-02 22:39:13 -03:00

155 lines
5.7 KiB
TypeScript

/**
* Group B — Quota Plans Config E2E spec.
*
* The originally planned standalone page /dashboard/costs/quota-share/plans does not
* exist in the current codebase (Group B plan 22 F9 implemented plans via the
* PoolWizard inside /dashboard/costs/quota-share, not a separate route).
*
* Tests are corrected to navigate to the existing /dashboard/costs/quota-share page
* which contains the group <select> element (QuotaSharePageClient.tsx line ~362).
* Backend is mocked so this spec does not require a running upstream.
*/
import { test, expect } from "@playwright/test";
import { gotoDashboardRoute } from "./helpers/dashboardAuth";
test.describe("Group B — Quota Plans Config", () => {
test.beforeEach(async ({ page }) => {
// Mock the plans list endpoint
await page.route("**/api/quota/plans**", async (route) => {
const url = new URL(route.request().url());
const pathParts = url.pathname.split("/");
const lastPart = pathParts[pathParts.length - 1];
if (lastPart === "plans") {
// List all plans
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([
{
connectionId: null,
provider: "codex",
dimensions: [
{ unit: "percent", window: "5h", limit: 100 },
{ unit: "percent", window: "weekly", limit: 100 },
],
source: "auto",
},
]),
});
} else {
// Single plan by connectionId
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
connectionId: lastPart,
provider: "codex",
dimensions: [
{ unit: "percent", window: "5h", limit: 100 },
{ unit: "percent", window: "weekly", limit: 100 },
],
source: "auto",
}),
});
}
});
// Mock pools list — QuotaSharePageClient uses usePools() which fetches /api/quota/pools
await page.route("**/api/quota/pools**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([]),
});
});
// Mock pool groups list
await page.route("**/api/quota/groups**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([]),
});
});
// Mock provider connections list
await page.route("**/api/providers/client**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([]),
});
});
// Mock API keys list
await page.route("**/api/keys**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([]),
});
});
// Mock quota-store settings
await page.route("**/api/settings/quota-store**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ driver: "sqlite", redisUrl: null }),
});
});
});
test("quota share page exists and returns 200", async ({ page }) => {
// /dashboard/costs/quota-share/plans does not exist as a standalone route;
// the plans wizard is embedded in /dashboard/costs/quota-share.
const response = await page.goto(
"http://localhost:20128/dashboard/costs/quota-share",
{ waitUntil: "domcontentloaded" }
);
expect(response?.status()).not.toBe(404);
expect(response?.status()).not.toBe(500);
});
test("quota plans config page renders provider selector", async ({ page }) => {
// The standalone /plans sub-page was never created; the group <select> element
// that allows filtering pools lives directly in /dashboard/costs/quota-share
// (QuotaSharePageClient.tsx). Navigate there instead.
await gotoDashboardRoute(page, "/dashboard/costs/quota-share");
// Group selector (a <select> element) should be visible
const providerSelector = page.locator(
"select, [role='combobox'], [data-testid='provider-selector']"
);
await expect(providerSelector.first()).toBeVisible({ timeout: 15000 });
});
test("selecting codex provider shows dimension rows", async ({ page }) => {
// Navigate to the real quota-share page (plans are embedded, not a standalone route)
await gotoDashboardRoute(page, "/dashboard/costs/quota-share");
// The group selector is a <select> element in QuotaSharePageClient
const selector = page.locator("select, [role='combobox']").first();
await expect(selector).toBeVisible({ timeout: 15000 });
// Select codex if the option is available (it will only appear if the mock
// returns a group named "codex" — the current mock returns an empty groups list,
// so the selector will only have the "All groups" option).
const codexOption = page.getByRole("option", { name: /codex/i });
if (await codexOption.isVisible({ timeout: 3000 }).catch(() => false)) {
await selector.selectOption({ label: /codex/i });
}
// After selection, the page should not be in a broken state.
// Note: page.content() includes the full HTML source, which contains Next.js
// chunk filenames — those hashes can legitimately contain the string "500".
// Checking for "500" in raw HTML is unreliable; instead check for the actual
// error boundary text that OmniRoute renders on unrecoverable errors
// (src/app/error.tsx heading: "Internal Server Error").
const pageContent = await page.content();
expect(pageContent).not.toContain("Internal Server Error");
});
});