✨ feat: add console error monitoring for Playwright tests; enhance page fixture with error assertions
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { Page } from "@playwright/test"
|
||||
import { test as base, expect as pwExpect } from "@playwright/test"
|
||||
import { attachConsoleMonitor } from "../fixtures/console-monitor"
|
||||
|
||||
export { expect, type Page, API_BASE, clickSpaLink } from "../e2e/fixtures"
|
||||
import { expect } from "../e2e/fixtures"
|
||||
@@ -11,6 +12,8 @@ export const test = base.extend<MobileFixtures>({
|
||||
* Override page fixture: BrowserSync domcontentloaded workaround.
|
||||
*/
|
||||
page: async ({ page }, use) => {
|
||||
const monitor = attachConsoleMonitor(page)
|
||||
|
||||
const origGoto = page.goto.bind(page)
|
||||
const origReload = page.reload.bind(page)
|
||||
|
||||
@@ -19,6 +22,7 @@ export const test = base.extend<MobileFixtures>({
|
||||
page.reload = ((opts?: any) => origReload({ waitUntil: "domcontentloaded", ...opts })) as typeof page.reload
|
||||
|
||||
await use(page)
|
||||
monitor.assertNoErrors()
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Page, Locator } from "@playwright/test"
|
||||
import { test as base, expect as pwExpect } from "@playwright/test"
|
||||
import { attachConsoleMonitor } from "../fixtures/console-monitor"
|
||||
|
||||
export { expect, type Page, API_BASE, clickSpaLink } from "../e2e/fixtures"
|
||||
import { expect } from "../e2e/fixtures"
|
||||
@@ -11,6 +12,8 @@ export const test = base.extend<VisualFixtures>({
|
||||
* Override page fixture: BrowserSync domcontentloaded workaround.
|
||||
*/
|
||||
page: async ({ page }, use) => {
|
||||
const monitor = attachConsoleMonitor(page)
|
||||
|
||||
const origGoto = page.goto.bind(page)
|
||||
const origReload = page.reload.bind(page)
|
||||
|
||||
@@ -19,6 +22,7 @@ export const test = base.extend<VisualFixtures>({
|
||||
page.reload = ((opts?: any) => origReload({ waitUntil: "domcontentloaded", ...opts })) as typeof page.reload
|
||||
|
||||
await use(page)
|
||||
monitor.assertNoErrors()
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { test as base, expect, type Page } from "@playwright/test"
|
||||
import { ensureTestUser, type TestUserCredentials } from "../api/helpers/test-user"
|
||||
import { attachConsoleMonitor } from "../fixtures/console-monitor"
|
||||
|
||||
const API_BASE = "/api"
|
||||
|
||||
@@ -29,6 +30,8 @@ export const test = base.extend<E2eFixtures, E2eWorkerFixtures>({
|
||||
* navigation methods to "domcontentloaded".
|
||||
*/
|
||||
page: async ({ page }, use) => {
|
||||
const monitor = attachConsoleMonitor(page)
|
||||
|
||||
const origGoto = page.goto.bind(page)
|
||||
const origReload = page.reload.bind(page)
|
||||
const origGoBack = page.goBack.bind(page)
|
||||
@@ -42,6 +45,7 @@ export const test = base.extend<E2eFixtures, E2eWorkerFixtures>({
|
||||
origGoForward({ waitUntil: "domcontentloaded", ...opts })) as typeof page.goForward
|
||||
|
||||
await use(page)
|
||||
monitor.assertNoErrors()
|
||||
},
|
||||
|
||||
// Worker-scoped: create/reuse test user once per worker
|
||||
|
||||
117
tests/fixtures/console-monitor.ts
vendored
Normal file
117
tests/fixtures/console-monitor.ts
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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!`
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user