import { expect, test, type Page } from "@playwright/test"; async function signIn(page: 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)/); } /** * Navigate to the projects list and return the href of the first project row. * Returns null if the list is empty (seed data may not be present). */ async function getFirstProjectHref(page: Page): Promise { await page.goto("/projects"); await page.waitForLoadState("networkidle"); // Try to find a link inside the projects table that points to /projects/ const projectLink = page .locator("table a[href*='/projects/']") .or(page.locator("a[href*='/projects/']").filter({ hasNot: page.locator("nav") })) .first(); if ((await projectLink.count()) === 0) { return null; } return projectLink.getAttribute("href"); } test.describe("Project Detail Page", () => { test.beforeEach(async ({ page }) => { await signIn(page); }); test("project detail page loads from list link", async ({ page }) => { const href = await getFirstProjectHref(page); if (!href) { test.skip(true, "No seeded projects available to navigate to"); return; } await page.goto(href); await page.waitForLoadState("networkidle"); // Project detail renders an h1 with the project name await expect(page.locator("h1").first()).toBeVisible({ timeout: 10000 }); }); test("project detail shows assignments and demands sections", async ({ page }) => { const href = await getFirstProjectHref(page); if (!href) { test.skip(true, "No seeded projects available to navigate to"); return; } await page.goto(href); await page.waitForLoadState("networkidle"); // The detail page should contain stat labels for assignments and open demands await expect( page.locator("text=Assignments").or(page.locator("text=Open Demands")).first(), ).toBeVisible({ timeout: 10000 }); }); test("project detail shows budget status card", async ({ page }) => { const href = await getFirstProjectHref(page); if (!href) { test.skip(true, "No seeded projects available to navigate to"); return; } await page.goto(href); await page.waitForLoadState("networkidle"); // BudgetStatusCard renders budget-related content await expect( page.locator("text=Budget").or(page.locator("text=budget")).first(), ).toBeVisible({ timeout: 10000 }); }); test("unknown project id shows not-found state", async ({ page }) => { await page.goto("/projects/does-not-exist-abc123"); await page.waitForLoadState("networkidle"); // Server-side notFound() triggers the Next.js 404 page await expect( page.locator("text=404").or(page.locator("text=Not Found")).or(page.locator("text=not found")).first(), ).toBeVisible({ timeout: 10000 }); }); test("no console errors when viewing first project", async ({ page }) => { const consoleErrors: string[] = []; page.on("console", (msg) => { if (msg.type() === "error") { consoleErrors.push(msg.text()); } }); const href = await getFirstProjectHref(page); if (!href) { test.skip(true, "No seeded projects available to navigate to"); return; } await page.goto(href); await page.waitForLoadState("networkidle"); const appErrors = consoleErrors.filter( (e) => !e.includes("extension") && !e.includes("favicon"), ); expect(appErrors, `Unexpected console errors: ${appErrors.join("\n")}`).toHaveLength(0); }); });