diff --git a/apps/web/e2e/dev-system/nav-smoke.spec.ts b/apps/web/e2e/dev-system/nav-smoke.spec.ts new file mode 100644 index 0000000..817faae --- /dev/null +++ b/apps/web/e2e/dev-system/nav-smoke.spec.ts @@ -0,0 +1,103 @@ +/** + * Navigation route smoke test — runs against the live dev server (port 3100). + * + * Verifies that every sidebar destination in AppShell (navSections + adminNavEntries) + * returns a real page (not 404) for an authenticated ADMIN user. + * + * Config: playwright.dev.config.ts + * Auth: e2e/dev-system/.auth/admin.json (created by global-setup.ts) + * + * Run: + * pnpm --filter @capakraken/web exec playwright test \ + * --config playwright.dev.config.ts \ + * e2e/dev-system/nav-smoke.spec.ts + */ +import * as path from "path"; +import { expect, test } from "@playwright/test"; + +// ── Standalone href list ──────────────────────────────────────────────────── +// Mirrors AppShell.tsx navSections + adminNavEntries. +// DO NOT import AppShell here — that pulls in the full Next.js component tree. +// Keep in sync with apps/web/src/components/layout/AppShell.tsx. + +const REGULAR_NAV_HREFS = [ + // Planning + "/dashboard", + "/timeline", + "/allocations", + "/staffing", + "/notifications", + // Estimating + "/estimates", + "/admin/rate-cards", + "/admin/effort-rules", + "/admin/experience-multipliers", + // Resources + "/resources", + "/projects", + "/roles", + // Analytics + "/analytics/skills", + "/reports/chargeability", + "/reports/builder", + "/analytics/computation-graph", + "/analytics/insights", + // Time Off + "/vacations/my", + "/vacations", + // Account + "/account/security", +] as const; + +const ADMIN_NAV_HREFS = [ + "/admin/blueprints", + "/admin/clients", + "/admin/countries", + "/admin/org-units", + "/admin/utilization-categories", + "/admin/management-levels", + "/admin/imports", + "/admin/calculation-rules", + "/admin/vacations", + "/admin/users", + "/admin/system-roles", + "/admin/settings", + "/admin/notifications", + "/admin/webhooks", + "/admin/activity-log", +] as const; + +const ALL_HREFS = [...REGULAR_NAV_HREFS, ...ADMIN_NAV_HREFS] as const; + +// ── Auth state ────────────────────────────────────────────────────────────── +// Reuse the saved admin session from global-setup — avoids hitting the rate limiter. +test.use({ + storageState: path.join(__dirname, ".auth/admin.json"), +}); + +// ── Tests ─────────────────────────────────────────────────────────────────── +test.describe("nav smoke — all sidebar routes resolve for admin (no 404)", () => { + for (const href of ALL_HREFS) { + test(href, async ({ page }) => { + // waitUntil: "commit" — only wait for HTTP response headers, not full render. + // Fast enough for a 404 check; avoids timeouts on data-heavy pages. + // Generous timeout: analytics/insights may need JIT compile on first visit (~15 s). + const response = await page.goto(href, { + waitUntil: "commit", + timeout: 60_000, + }); + + // HTTP response must not be 404 + expect( + response?.status(), + `${href} returned HTTP ${response?.status()} — expected any status except 404`, + ).not.toBe(404); + + // Belt-and-suspenders: also check the rendered page doesn't contain Next.js 404 text + await expect( + page.locator("text=This page could not be found"), + `${href} rendered a Next.js 404 page`, + ).not.toBeVisible({ timeout: 15_000 }); + }); + } +}); diff --git a/apps/web/e2e/navigation.spec.ts b/apps/web/e2e/navigation.spec.ts index abee8a4..d379263 100644 --- a/apps/web/e2e/navigation.spec.ts +++ b/apps/web/e2e/navigation.spec.ts @@ -26,6 +26,42 @@ test.describe("Navigation", () => { } }); + test("all nav routes resolve — no 404 (smoke)", async ({ page }) => { + // Complements the click-based test above with a direct-navigation check + // covering every sidebar destination. Uses admin@capakraken.dev (ADMIN role). + const routes = [ + // Already covered by click test but included for completeness + "/dashboard", + "/timeline", + "/allocations", + "/resources", + "/projects", + // Additional routes not covered by the click test + "/staffing", + "/notifications", + "/estimates", + "/roles", + "/analytics/skills", + "/reports/chargeability", + "/reports/builder", + "/analytics/insights", + "/vacations/my", + "/vacations", + "/account/security", + ]; + + for (const route of routes) { + const response = await page.goto(route, { + waitUntil: "commit", + timeout: 60_000, + }); + expect( + response?.status(), + `${route} returned HTTP ${response?.status()} — expected any status except 404`, + ).not.toBe(404); + } + }); + test("sidebar collapse and expand works", async ({ page }) => { await page.goto("/dashboard"); await page.waitForLoadState("networkidle");