118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
/**
|
||
* 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!`
|
||
)
|
||
}
|
||
},
|
||
}
|
||
}
|