/** * Console/Network-Error-Monitor für Playwright-Tests. * * Überwacht Browser-Pages auf: * - `pageerror` – Uncaught Exceptions * - `console.error` – Alle console.error()-Aufrufe * - `requestfailed` – Fehlgeschlagene Netzwerk-Requests (nicht absichtlich abgebrochene) * * Verwendung in Fixtures: * ```ts * const monitor = attachConsoleMonitor(page) * // … setup … * await use(page) * monitor.assertNoErrors() * ``` * * Fehler werden NICHT ignoriert, sondern in der App gefixt. * IGNORED_PATTERNS nur für Infrastructure-Rauschen, das nicht in der App liegt. */ import type { Page } from "@playwright/test" // ── Whitelist (nur Infrastructure-Rauschen, keine App-Fehler) ──────── const IGNORED_PATTERNS: RegExp[] = [ /browser-sync/i, /cc\.webmakers\.de/i, /sentry/i, /Failed to load resource: net::ERR_/i, /Failed to load resource: the server responded with a status of/i, /TypeError: Failed to fetch/i, ] // ── Types ──────────────────────────────────────────────────────────── interface ConsoleError { type: "pageerror" | "console.error" | "request-failed" message: string } export interface ConsoleMonitor { getErrors(): ConsoleError[] assertNoErrors(): void } // ── Implementation ─────────────────────────────────────────────────── function isIgnored(text: string): boolean { return IGNORED_PATTERNS.some((pattern) => pattern.test(text)) } export function attachConsoleMonitor(page: Page): ConsoleMonitor { const errors: ConsoleError[] = [] // Inject script to capture detailed unhandled rejection info page.addInitScript(() => { window.addEventListener("unhandledrejection", (event) => { const reason = event.reason if (reason && typeof reason === "object" && !(reason instanceof Error)) { try { const detail = JSON.stringify( reason, (_key, val) => { if (val instanceof Response) return `[Response ${val.status} ${val.url}]` if (val instanceof Request) return `[Request ${val.method} ${val.url}]` return val }, 2 ) console.error(`[unhandled-rejection-detail] ${detail}`) } catch { console.error(`[unhandled-rejection-detail] ${String(reason)}`) } } }) }) page.on("pageerror", (error) => { const msg = error.stack || error.message || String(error) if (!isIgnored(msg)) { errors.push({ type: "pageerror", message: msg }) } }) page.on("console", (msg) => { if (msg.type() === "error") { const text = msg.text() if (!isIgnored(text)) { errors.push({ type: "console.error", message: text }) } } }) page.on("requestfailed", (request) => { const failure = request.failure()?.errorText || "unknown" if (failure === "net::ERR_ABORTED" || failure === "net::ERR_FAILED") return const url = request.url() if (isIgnored(url)) return errors.push({ type: "request-failed", message: `${request.method()} ${url} – ${failure}`, }) }) return { getErrors: () => [...errors], assertNoErrors() { if (errors.length > 0) { const summary = errors.map((e) => ` [${e.type}] ${e.message}`).join("\n") throw new Error( `Browser-Fehler während des Tests erkannt (${errors.length}):\n${summary}\n\n` + `→ Fehler in der App fixen, NICHT in IGNORED_PATTERNS aufnehmen!` ) } }, } }