aef4c61dcc
- e2e/dev-system/nav-smoke.spec.ts: new Playwright spec in the dev-system suite; iterates every href from navSections + adminNavEntries, asserts HTTP status ≠ 404 and no "page not found" text for an authenticated admin - e2e/navigation.spec.ts: add "all nav routes resolve" smoke block covering 16 routes not previously tested in the isolated test suite All 35 routes pass against live dev server. Catches dead nav links before users encounter them. Co-Authored-By: claude-flow <ruv@ruv.net>
136 lines
4.6 KiB
TypeScript
136 lines
4.6 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
test.describe("Navigation", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto("/auth/signin");
|
|
await page.fill('input[type="email"]', "admin@capakraken.dev");
|
|
await page.fill('input[type="password"]', "admin123");
|
|
await page.click('button[type="submit"]');
|
|
await expect(page).toHaveURL(/\/(dashboard|resources)/);
|
|
});
|
|
|
|
test("main nav links navigate correctly", async ({ page }) => {
|
|
const navRoutes = [
|
|
{ label: "Dashboard", url: "/dashboard" },
|
|
{ label: "Timeline", url: "/timeline" },
|
|
{ label: "Allocations", url: "/allocations" },
|
|
{ label: "Resources", url: "/resources" },
|
|
{ label: "Projects", url: "/projects" },
|
|
];
|
|
|
|
for (const route of navRoutes) {
|
|
const navLink = page.locator(`nav a >> text="${route.label}"`).first();
|
|
await navLink.click();
|
|
await page.waitForLoadState("networkidle");
|
|
await expect(page).toHaveURL(new RegExp(route.url));
|
|
}
|
|
});
|
|
|
|
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");
|
|
|
|
// Find the collapse button (contains "Collapse" text)
|
|
const collapseBtn = page.locator("nav button", { hasText: "Collapse" });
|
|
if ((await collapseBtn.count()) > 0) {
|
|
await collapseBtn.click();
|
|
await page.waitForTimeout(300);
|
|
// After collapsing, the sidebar should be narrow (72px)
|
|
const nav = page.locator("nav").first();
|
|
const box = await nav.boundingBox();
|
|
if (box) {
|
|
expect(box.width).toBeLessThan(100);
|
|
}
|
|
|
|
// Expand again — the button should still be visible as an icon
|
|
const expandBtn = page.locator("nav button").filter({ has: page.locator("svg") }).last();
|
|
await expandBtn.click();
|
|
await page.waitForTimeout(300);
|
|
const boxExpanded = await nav.boundingBox();
|
|
if (boxExpanded) {
|
|
expect(boxExpanded.width).toBeGreaterThan(200);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("preferences modal opens via sidebar button", async ({ page }) => {
|
|
await page.goto("/dashboard");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
const prefsBtn = page.locator("nav button", { hasText: "Preferences" });
|
|
if ((await prefsBtn.count()) > 0) {
|
|
await prefsBtn.click();
|
|
await expect(page.locator("text=Preferences")).toBeVisible({ timeout: 5000 });
|
|
// Should show theme/mode controls
|
|
await expect(
|
|
page.locator("text=Light").or(page.locator("text=Dark").or(page.locator("text=System"))),
|
|
).toBeVisible();
|
|
// Close the modal
|
|
await page.keyboard.press("Escape");
|
|
}
|
|
});
|
|
|
|
test("mobile hamburger menu opens sidebar on small viewport", async ({ page }) => {
|
|
// Set a mobile viewport size
|
|
await page.setViewportSize({ width: 375, height: 812 });
|
|
await page.goto("/dashboard");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// The hamburger button should be visible on mobile
|
|
const hamburgerBtn = page.locator("button").filter({ has: page.locator("svg") }).first();
|
|
await expect(hamburgerBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
await hamburgerBtn.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Mobile sidebar overlay should appear with nav links
|
|
await expect(page.locator("text=Dashboard")).toBeVisible();
|
|
await expect(page.locator("text=Timeline")).toBeVisible();
|
|
|
|
// Close button should be visible in mobile sidebar
|
|
const closeBtn = page
|
|
.locator("nav button")
|
|
.filter({ has: page.locator("svg") })
|
|
.first();
|
|
if ((await closeBtn.count()) > 0) {
|
|
await closeBtn.click();
|
|
}
|
|
});
|
|
});
|