import { expect, test } from "@playwright/test" import type { Page } from "@playwright/test" /** * System Integration Test — runs against the REAL Docker stack or mock backend. * * Environment: * - BASE_URL: set by docker-compose.test.yml (http://frontend:3000) * - Falls back to http://localhost:3000 for local dev with mock backend * * Test data (seeded via scripts/seed/init.sql): * - Admin: admin@test.de / test123 * - 5 members, 3 batches, 3 distributions */ const BASE = process.env.BASE_URL || "http://localhost:3000" test.describe("System Integration Test", () => { test.describe.configure({ mode: "serial" }) let page: Page test.beforeAll(async ({ browser }) => { page = await browser.newPage() }) test.afterAll(async () => { await page.close() }) test("login page loads correctly", async () => { await page.goto(`${BASE}/login`) await page.waitForLoadState("networkidle") // Login page should have email and password fields const emailInput = page.locator('input[name="email"], input[type="email"]') const passwordInput = page.locator( 'input[name="password"], input[type="password"]' ) await expect(emailInput).toBeVisible() await expect(passwordInput).toBeVisible() // Should have a submit button const submitButton = page.locator('button[type="submit"]') await expect(submitButton).toBeVisible() }) test("admin can log in with seeded credentials", async () => { await page.goto(`${BASE}/login`) await page.waitForLoadState("networkidle") // Fill login form await page.fill('input[name="email"], input[type="email"]', "admin@test.de") await page.fill('input[name="password"], input[type="password"]', "test123") await page.click('button[type="submit"]') // Wait for navigation away from login page — redirect to dashboard await page.waitForURL((url) => !url.pathname.includes("/login"), { timeout: 15000, }) // Verify we're on an authenticated page (not still on login) const url = page.url() expect(url).not.toContain("/login") }) test("dashboard displays content after login", async () => { // Navigate to dashboard explicitly await page.goto(`${BASE}/dashboard`) await page.waitForLoadState("networkidle") // Dashboard should have recognizable content const heading = page.locator("h1, h2, h3").first() await expect(heading).toBeVisible({ timeout: 10000 }) // Should not be on login page (redirected back) const url = page.url() expect(url).not.toContain("/login") }) test("members page shows member data", async () => { await page.goto(`${BASE}/members`) await page.waitForLoadState("networkidle") // Should have a table or list of members const content = page.locator( 'table, [role="table"], [data-testid="members-list"]' ) await expect(content).toBeVisible({ timeout: 10000 }) }) test("distributions page is accessible", async () => { await page.goto(`${BASE}/distributions`) await page.waitForLoadState("networkidle") // Should have distribution content const content = page.locator( 'table, [role="table"], [data-testid="distributions-list"]' ) await expect(content).toBeVisible({ timeout: 10000 }) }) test("stock page is accessible", async () => { await page.goto(`${BASE}/stock`) await page.waitForLoadState("networkidle") // Should have stock/batch content const content = page.locator( 'table, [role="table"], [data-testid="stock-list"]' ) await expect(content).toBeVisible({ timeout: 10000 }) }) test("reports page is accessible", async () => { await page.goto(`${BASE}/reports`) await page.waitForLoadState("networkidle") // Reports page should show "Berichte" heading or report-related content const reportContent = page.locator( 'h1:has-text("Berichte"), h2:has-text("Berichte"), :text("Monatsbericht")' ) await expect(reportContent.first()).toBeVisible({ timeout: 10000 }) }) test("navigation sidebar works", async () => { await page.goto(`${BASE}/dashboard`) await page.waitForLoadState("networkidle") // Check that main navigation links exist (shadcn sidebar uses data-sidebar attrs) const navLinks = page.locator('[data-sidebar] a, nav a, aside a, [role="navigation"] a') const count = await navLinks.count() expect(count).toBeGreaterThan(0) }) test("no console errors on critical pages", async () => { const errors: string[] = [] page.on("console", (msg) => { if (msg.type() === "error") { errors.push(msg.text()) } }) // Visit each critical page const criticalPages = ["/dashboard", "/members", "/distributions", "/stock"] for (const path of criticalPages) { await page.goto(`${BASE}${path}`) await page.waitForLoadState("networkidle") } // Filter out known non-critical errors (e.g., favicon, source maps, API 500s from mock backend) const criticalErrors = errors.filter( (e) => !e.includes("favicon") && !e.includes(".map") && !e.includes("hydration") && !e.includes("Failed to load resource") && !e.includes("MISSING_MESSAGE") ) expect(criticalErrors).toHaveLength(0) }) })