feat(logging-p0): add screenshot capture and browser console diagnostics for ERP errors

Enhance ERP automation error diagnostics by capturing PNG screenshots
on every error and forwarding browser console warnings/errors to the
structured logger. Includes automatic cleanup of old screenshots
aligned with the configured log retention period.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-04-04 14:00:48 +08:00
parent c8783a2cef
commit fce8dbc37f
8 changed files with 539 additions and 2 deletions

View File

@@ -2,6 +2,7 @@ import { chromium } from 'playwright'
import type { ErpConfig, ErpSession } from '../../types/erp.types'
import { createLogger } from '../logger'
import { capturePageContext } from './erp-error-context'
import { attachPageDiagnostics, attachContextDiagnostics } from './page-diagnostics'
const log = createLogger('ErpAuthService')
@@ -55,6 +56,8 @@ export class ErpAuthService {
})
const page = await context.newPage()
attachPageDiagnostics(page)
attachContextDiagnostics(context)
// Navigate to login page (use actual login URL from Python code)
const loginUrl = `${this.config.url}/yonbip/resources/uap/rbac/login/main/index.html`

View File

@@ -6,12 +6,61 @@
*/
import type { Page } from 'playwright'
import fs from 'fs'
import path from 'path'
import { getLogDir } from '../logger/shared'
export interface ErpErrorContext {
pageUrl?: string
frameHierarchy?: Array<{ name: string; url: string }>
targetSelector?: string
step?: string
screenshotPath?: string
}
/**
* Sanitize a step name for use as a filename component.
* Replaces non-alphanumeric characters with underscores and truncates.
*/
function sanitizeForFilename(step: string | undefined): string {
if (!step) return 'unknown'
return step.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 40)
}
/**
* Capture a screenshot of the page for error diagnostics.
* Stored as PNG under <logDir>/screenshots/.
* Defensive: never throws.
*/
async function captureScreenshot(page: Page, step?: string): Promise<string | undefined> {
try {
if (page.isClosed()) return undefined
const screenshotDir = path.join(getLogDir(), 'screenshots')
fs.mkdirSync(screenshotDir, { recursive: true })
const now = new Date()
const timestamp = [
now.getFullYear(),
String(now.getMonth() + 1).padStart(2, '0'),
String(now.getDate()).padStart(2, '0'),
'_',
String(now.getHours()).padStart(2, '0'),
String(now.getMinutes()).padStart(2, '0'),
String(now.getSeconds()).padStart(2, '0')
].join('')
const filename = `err_${timestamp}_${sanitizeForFilename(step)}.png`
const filePath = path.join(screenshotDir, filename)
const buffer = await page.screenshot({ type: 'png', timeout: 5000 })
fs.writeFileSync(filePath, buffer)
return filePath
} catch {
// screenshot failure must not propagate
return undefined
}
}
/**
@@ -49,5 +98,7 @@ export async function capturePageContext(
ctx.step = step
}
ctx.screenshotPath = await captureScreenshot(page, step)
return ctx
}

View File

@@ -0,0 +1,50 @@
/**
* Page Diagnostics
*
* Attaches browser console and error listeners to Playwright pages
* so that ERP-side JS errors are visible in the application log.
*
* Only warning and error level console messages are captured —
* ERP (YonBIP) outputs large volumes of info-level messages that
* would drown the log.
*/
import type { Page, BrowserContext } from 'playwright'
import { createLogger } from '../logger'
const log = createLogger('BrowserDiagnostics')
/**
* Attach console and error listeners to a single page.
*/
export function attachPageDiagnostics(page: Page): void {
page.on('console', (msg) => {
const type = msg.type()
if (type !== 'warning' && type !== 'error') return
const location = msg.location()
log.error(`[Browser ${type}] ${msg.text()}`, {
pageUrl: page.url(),
consoleType: type,
location: location ? `${location.url}:${location.lineNumber}` : undefined
})
})
page.on('pageerror', (error) => {
log.error(`[Browser pageerror] ${error.message}`, {
pageUrl: page.url(),
error: error.message,
stack: error.stack
})
})
}
/**
* Attach diagnostics to all current and future pages in a browser context.
* Covers popups and pages opened by ERP automation.
*/
export function attachContextDiagnostics(context: BrowserContext): void {
context.on('page', (page) => {
attachPageDiagnostics(page)
})
}