import path from "path" import { expect, test } from "@playwright/test" import type { Page } from "@playwright/test" const SCREENSHOT_DIR = path.join(__dirname, "screenshots") // Helper to capture console errors function collectConsoleErrors(page: Page): string[] { const errors: string[] = [] page.on("console", (msg) => { if (msg.type() === "error") { errors.push(`[console.error] ${msg.text()}`) } }) page.on("pageerror", (err) => { errors.push(`[pageerror] ${err.message}`) }) return errors } test.describe("CannaManage E2E Funktionscheck — Phases 1-3", () => { test.setTimeout(30_000) test("01 - Login page loads correctly", async ({ page }) => { const errors = collectConsoleErrors(page) const response = await page.goto("/login", { waitUntil: "domcontentloaded", }) expect(response?.status()).toBe(200) // Wait for hydration await page.waitForTimeout(3000) // Verify login form elements are visible const emailField = page.locator('input[id="email"]') const passwordField = page.locator('input[id="password"]') const submitButton = page.locator('button[type="submit"]') await expect(emailField).toBeVisible({ timeout: 10000 }) await expect(passwordField).toBeVisible({ timeout: 5000 }) await expect(submitButton).toBeVisible({ timeout: 5000 }) // Verify branding await expect(page.locator("text=CannaManage")).toBeVisible({ timeout: 5000, }) // Take screenshot await page.screenshot({ path: path.join(SCREENSHOT_DIR, "01-login-page.png"), fullPage: true, }) // Report console errors (filter expected auth errors without backend) const unexpectedErrors = errors.filter( (e) => !e.includes("next-auth") && !e.includes("ECONNREFUSED") && !e.includes("fetch") && !e.includes("Failed to fetch") && !e.includes("NetworkError") && !e.includes("ERR_CONNECTION_REFUSED") && !e.includes("[auth]") ) expect( unexpectedErrors, `Unexpected console errors on login page: ${unexpectedErrors.join(", ")}` ).toHaveLength(0) }) test("02 - Auth redirect for protected routes", async ({ page }) => { collectConsoleErrors(page) await page.goto("/dashboard", { waitUntil: "domcontentloaded" }) // Wait for redirect to happen await page.waitForTimeout(3000) await page.waitForURL(/\/login/, { timeout: 10000 }) // Should redirect to /login with callbackUrl expect(page.url()).toContain("/login") // Take screenshot await page.screenshot({ path: path.join(SCREENSHOT_DIR, "02-auth-redirect.png"), fullPage: true, }) }) test("03 - Login with invalid credentials shows error", async ({ page }) => { collectConsoleErrors(page) await page.goto("/login", { waitUntil: "domcontentloaded" }) await page.waitForTimeout(2000) // Fill in invalid credentials await page.fill('input[id="email"]', "test@invalid.com") await page.fill('input[id="password"]', "wrongpass") // Click submit await page.click('button[type="submit"]') // Wait for the response — backend isn't running so we expect network error feedback await page.waitForTimeout(5000) // Take screenshot regardless of what happened await page.screenshot({ path: path.join(SCREENSHOT_DIR, "03-login-error.png"), fullPage: true, }) // Check for any error indication on page const errorVisible = await page .locator('[class*="destructive"], [class*="error"], [class*="amber"]') .first() .isVisible() .catch(() => false) console.log(`Login error feedback visible: ${errorVisible}`) // We expect SOME error feedback (either "invalid credentials" or "network error") // Not hard-failing if missing — just documenting }) test("04 - 404 page renders for unknown routes", async ({ page }) => { collectConsoleErrors(page) const response = await page.goto("/this-does-not-exist", { waitUntil: "domcontentloaded", }) await page.waitForTimeout(3000) // Take screenshot of whatever page we land on await page.screenshot({ path: path.join(SCREENSHOT_DIR, "04-not-found.png"), fullPage: true, }) // Document the actual URL we ended up at const url = page.url() console.log(`404 test: ended up at ${url}`) console.log(`404 test: response status ${response?.status()}`) // The page should either show a 404 content or redirect to login (middleware) const isExpectedBehavior = url.includes("/login") || url.includes("not-found") || url.includes("this-does-not-exist") expect(isExpectedBehavior).toBeTruthy() }) test("05 - No critical JavaScript errors on accessible pages", async ({ page, }) => { const errors = collectConsoleErrors(page) await page.goto("/login", { waitUntil: "domcontentloaded" }) await page.waitForTimeout(3000) // Filter out expected errors (next-auth session check without backend) const criticalErrors = errors.filter( (e) => !e.includes("next-auth") && !e.includes("NEXT_REDIRECT") && !e.includes("fetch") && !e.includes("Failed to fetch") && !e.includes("NetworkError") && !e.includes("ECONNREFUSED") && !e.includes("ERR_CONNECTION_REFUSED") && !e.includes("[auth]") && !e.includes("session") && !e.includes("hydrat") ) console.log(`Total console errors: ${errors.length}`) console.log(`Critical (non-network) errors: ${criticalErrors.length}`) for (const err of errors) { console.log(` ${err}`) } // Only fail on truly critical errors expect( criticalErrors, `Critical JS errors: ${criticalErrors.join("\n")}` ).toHaveLength(0) }) test("06 - Login page visual structure check", async ({ page }) => { await page.goto("/login", { waitUntil: "domcontentloaded" }) await page.waitForTimeout(3000) // Check page structure elements const heading = page.locator("h1") const form = page.locator("form") const emailInput = page.locator('input[type="email"]') const passwordInput = page.locator('input[type="password"]') await expect(heading).toContainText("CannaManage", { timeout: 5000 }) await expect(form).toBeVisible({ timeout: 5000 }) await expect(emailInput).toBeVisible({ timeout: 5000 }) await expect(passwordInput).toBeVisible({ timeout: 5000 }) // Check placeholder text await expect(emailInput).toHaveAttribute("placeholder", "name@verein.de") // Viewport check — ensure nothing overflows const bodyWidth = await page.evaluate(() => document.body.scrollWidth) const viewportWidth = await page.evaluate(() => window.innerWidth) expect(bodyWidth).toBeLessThanOrEqual(viewportWidth + 1) console.log("Login page visual structure: OK") }) })