more logging

This commit is contained in:
Tommy D. Rossi
2026-02-03 22:01:44 +01:00
parent adeaabd5aa
commit 26ddeb2a7b
2 changed files with 47 additions and 37 deletions
+27 -17
View File
@@ -1069,12 +1069,19 @@ async function getLabelBoxesForRefs({
logger?: { info?: (...args: unknown[]) => void; error?: (...args: unknown[]) => void } logger?: { info?: (...args: unknown[]) => void; error?: (...args: unknown[]) => void }
cdp?: ICDPSession cdp?: ICDPSession
}): Promise<AriaLabel[]> { }): Promise<AriaLabel[]> {
const log = logger?.info ?? logger?.error ?? console.error
const session = cdp || await getCDPSessionForPage({ page, wsUrl }) const session = cdp || await getCDPSessionForPage({ page, wsUrl })
const sema = new Sema(maxConcurrency) const sema = new Sema(maxConcurrency)
const labelRefs = refs.filter((ref) => { const labelRefs = refs.filter((ref) => {
return Boolean(ref.backendNodeId) && INTERACTIVE_ROLES.has(ref.role) return Boolean(ref.backendNodeId) && INTERACTIVE_ROLES.has(ref.role)
}) })
log(`[getLabelBoxesForRefs] processing ${labelRefs.length} interactive refs (concurrency: ${maxConcurrency})`)
const startTime = Date.now()
let completed = 0
let timedOut = 0
let failed = 0
try { try {
const labels = await Promise.all( const labels = await Promise.all(
labelRefs.map(async (ref) => { labelRefs.map(async (ref) => {
@@ -1089,8 +1096,12 @@ async function getLabelBoxesForRefs({
setTimeout(() => { resolve(null) }, BOX_MODEL_TIMEOUT_MS) setTimeout(() => { resolve(null) }, BOX_MODEL_TIMEOUT_MS)
}), }),
]) ])
completed++
if (completed % 50 === 0 || completed === labelRefs.length) {
log(`[getLabelBoxesForRefs] progress: ${completed}/${labelRefs.length} (${timedOut} timeouts, ${failed} errors) - ${Date.now() - startTime}ms`)
}
if (!response) { if (!response) {
logger?.error?.('[playwriter] getBoxModel timed out for', ref.ref) timedOut++
return null return null
} }
const box = buildBoxFromQuad(response.model.border) const box = buildBoxFromQuad(response.model.border)
@@ -1099,13 +1110,15 @@ async function getLabelBoxesForRefs({
} }
return { ref: ref.ref, role: ref.role, box } return { ref: ref.ref, role: ref.role, box }
} catch (error) { } catch (error) {
logger?.error?.('[playwriter] getBoxModel failed for', ref.ref, error) completed++
failed++
return null return null
} finally { } finally {
sema.release() sema.release()
} }
}) })
) )
log(`[getLabelBoxesForRefs] done: ${completed} completed, ${timedOut} timeouts, ${failed} errors - ${Date.now() - startTime}ms`)
return labels.filter(isTruthy) return labels.filter(isTruthy)
} finally { } finally {
if (!cdp) { if (!cdp) {
@@ -1145,24 +1158,24 @@ export async function showAriaRefLabels({ page, locator, interactiveOnly = true,
labelCount: number labelCount: number
}> { }> {
const startTime = Date.now() const startTime = Date.now()
const log = logger?.info ?? logger?.error ?? console.error
log(`[showAriaRefLabels] starting...`)
await ensureA11yClient(page) await ensureA11yClient(page)
log(`[showAriaRefLabels] ensureA11yClient: ${Date.now() - startTime}ms`)
const log = logger?.info ?? logger?.error const cdpStart = Date.now()
if (log) {
log(`ensureA11yClient: ${Date.now() - startTime}ms`)
}
const snapshotStart = Date.now()
const cdp = await getCDPSessionForPage({ page, wsUrl }) const cdp = await getCDPSessionForPage({ page, wsUrl })
log(`[showAriaRefLabels] getCDPSessionForPage: ${Date.now() - cdpStart}ms`)
try { try {
const snapshotStart = Date.now()
const { snapshot, refs } = await getAriaSnapshot({ page, locator, interactiveOnly, wsUrl, cdp }) const { snapshot, refs } = await getAriaSnapshot({ page, locator, interactiveOnly, wsUrl, cdp })
const shortRefMap = new Map(refs.map((entry) => { const shortRefMap = new Map(refs.map((entry) => {
return [entry.ref, entry.shortRef] return [entry.ref, entry.shortRef]
})) }))
if (log) { const interactiveRefs = refs.filter((ref) => Boolean(ref.backendNodeId) && INTERACTIVE_ROLES.has(ref.role))
log(`getAriaSnapshot: ${Date.now() - snapshotStart}ms`) log(`[showAriaRefLabels] getAriaSnapshot: ${Date.now() - snapshotStart}ms (${refs.length} refs, ${interactiveRefs.length} interactive)`)
}
const rootHandle = locator ? await locator.elementHandle() : null const rootHandle = locator ? await locator.elementHandle() : null
@@ -1174,9 +1187,7 @@ export async function showAriaRefLabels({ page, locator, interactiveOnly = true,
ref: shortRefMap.get(label.ref) ?? label.ref, ref: shortRefMap.get(label.ref) ?? label.ref,
} }
}) })
if (log) { log(`[showAriaRefLabels] getLabelBoxesForRefs: ${Date.now() - labelsStart}ms (${labels.length} boxes)`)
log(`getLabelBoxesForRefs: ${Date.now() - labelsStart}ms (${labels.length} boxes)`)
}
const renderStart = Date.now() const renderStart = Date.now()
const labelCount = await page.evaluate(({ entries, root, interactiveOnly: intOnly }) => { const labelCount = await page.evaluate(({ entries, root, interactiveOnly: intOnly }) => {
@@ -1196,9 +1207,8 @@ export async function showAriaRefLabels({ page, locator, interactiveOnly = true,
throw new Error('a11y client not loaded') throw new Error('a11y client not loaded')
}, { entries: shortLabels, root: rootHandle, interactiveOnly }) }, { entries: shortLabels, root: rootHandle, interactiveOnly })
if (log) { log(`[showAriaRefLabels] renderA11yLabels: ${Date.now() - renderStart}ms (${labelCount} labels)`)
log(`renderA11yLabels: ${Date.now() - renderStart}ms (${labelCount} labels)`) log(`[showAriaRefLabels] total: ${Date.now() - startTime}ms`)
}
return { snapshot, labelCount } return { snapshot, labelCount }
} finally { } finally {
+20 -20
View File
@@ -1116,61 +1116,61 @@ export async function startPlayWriterCDPRelayServer({
// ============================================================================ // ============================================================================
// CLI Execute Endpoints - For stateful code execution via CLI // CLI Execute Endpoints - For stateful code execution via CLI
// ============================================================================ // ============================================================================
// Session counter for suggesting next session number // Session counter for suggesting next session number
let nextSessionNumber = 1 let nextSessionNumber = 1
// Lazy-load ExecutorManager to avoid circular imports and only when needed // Lazy-load ExecutorManager to avoid circular imports and only when needed
let executorManager: import('./executor.js').ExecutorManager | null = null let executorManager: import('./executor.js').ExecutorManager | null = null
const getExecutorManager = async () => { const getExecutorManager = async () => {
if (!executorManager) { if (!executorManager) {
const { ExecutorManager } = await import('./executor.js') const { ExecutorManager } = await import('./executor.js')
// Pass config instead of URL so executor can generate unique client IDs for each connection // Pass config instead of URL so executor can generate unique client IDs for each connection
executorManager = new ExecutorManager({ executorManager = new ExecutorManager({
cdpConfig: { host: '127.0.0.1', port }, cdpConfig: { host: '127.0.0.1', port },
logger: logger || { log: console.log, error: console.error }, logger: logger || { log: console.error, error: console.error },
}) })
} }
return executorManager return executorManager
} }
app.post('/cli/execute', async (c) => { app.post('/cli/execute', async (c) => {
try { try {
const body = await c.req.json() as { sessionId: string; code: string; timeout?: number; cwd?: string } const body = await c.req.json() as { sessionId: string; code: string; timeout?: number; cwd?: string }
const { sessionId, code, timeout = 10000, cwd } = body const { sessionId, code, timeout = 10000, cwd } = body
if (!sessionId || !code) { if (!sessionId || !code) {
return c.json({ error: 'sessionId and code are required' }, 400) return c.json({ error: 'sessionId and code are required' }, 400)
} }
const manager = await getExecutorManager() const manager = await getExecutorManager()
const executor = manager.getExecutor(sessionId, cwd) const executor = manager.getExecutor(sessionId, cwd)
const result = await executor.execute(code, timeout) const result = await executor.execute(code, timeout)
// Increment session counter after each execution to avoid conflicts // Increment session counter after each execution to avoid conflicts
nextSessionNumber++ nextSessionNumber++
return c.json(result) return c.json(result)
} catch (error: any) { } catch (error: any) {
logger?.error('Execute endpoint error:', error) logger?.error('Execute endpoint error:', error)
return c.json({ text: `Server error: ${error.message}`, images: [], isError: true }, 500) return c.json({ text: `Server error: ${error.message}`, images: [], isError: true }, 500)
} }
}) })
app.post('/cli/reset', async (c) => { app.post('/cli/reset', async (c) => {
try { try {
const body = await c.req.json() as { sessionId: string; cwd?: string } const body = await c.req.json() as { sessionId: string; cwd?: string }
const { sessionId, cwd } = body const { sessionId, cwd } = body
if (!sessionId) { if (!sessionId) {
return c.json({ error: 'sessionId is required' }, 400) return c.json({ error: 'sessionId is required' }, 400)
} }
const manager = await getExecutorManager() const manager = await getExecutorManager()
const executor = manager.getExecutor(sessionId, cwd) const executor = manager.getExecutor(sessionId, cwd)
const { page, context } = await executor.reset() const { page, context } = await executor.reset()
return c.json({ return c.json({
success: true, success: true,
pageUrl: page.url(), pageUrl: page.url(),
@@ -1181,32 +1181,32 @@ export async function startPlayWriterCDPRelayServer({
return c.json({ error: error.message }, 500) return c.json({ error: error.message }, 500)
} }
}) })
app.get('/cli/sessions', async (c) => { app.get('/cli/sessions', async (c) => {
const manager = await getExecutorManager() const manager = await getExecutorManager()
return c.json({ sessions: manager.listSessions() }) return c.json({ sessions: manager.listSessions() })
}) })
app.get('/cli/session/suggest', (c) => { app.get('/cli/session/suggest', (c) => {
return c.json({ next: nextSessionNumber }) return c.json({ next: nextSessionNumber })
}) })
app.post('/cli/session/delete', async (c) => { app.post('/cli/session/delete', async (c) => {
try { try {
const body = await c.req.json() as { sessionId: string } const body = await c.req.json() as { sessionId: string }
const { sessionId } = body const { sessionId } = body
if (!sessionId) { if (!sessionId) {
return c.json({ error: 'sessionId is required' }, 400) return c.json({ error: 'sessionId is required' }, 400)
} }
const manager = await getExecutorManager() const manager = await getExecutorManager()
const deleted = manager.deleteExecutor(sessionId) const deleted = manager.deleteExecutor(sessionId)
if (!deleted) { if (!deleted) {
return c.json({ error: `Session ${sessionId} not found` }, 404) return c.json({ error: `Session ${sessionId} not found` }, 404)
} }
return c.json({ success: true }) return c.json({ success: true })
} catch (error: any) { } catch (error: any) {
logger?.error('Delete session endpoint error:', error) logger?.error('Delete session endpoint error:', error)