#!/usr/bin/env node /** * 最终诊断 — 检查 JS 控制台错误 + 终端渲染确认 */ import { writeFileSync } from 'fs'; import WebSocket from 'ws'; const CDP_PORT = 9225; const BASE = 'http://localhost:5199'; const API = 'http://localhost:8080/api/v1'; const OUT = '/home/aska/Code/Cyrene/debug/logs/chromium'; function makeCDPHelper(ws) { let msgId = 1; const pending = new Map(); ws.on('message', (raw) => { try { const msg = JSON.parse(raw.toString()); if (msg.id && pending.has(msg.id)) { pending.get(msg.id)(msg.result || msg); pending.delete(msg.id); } } catch {} }); return (method, params = {}) => new Promise((resolve, reject) => { const id = msgId++; const timer = setTimeout(() => { pending.delete(id); reject(new Error('CDP timeout: ' + method)); }, 15000); ws.send(JSON.stringify({ id, method, params })); pending.set(id, (r) => { clearTimeout(timer); resolve(r); }); }); } async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } async function main() { // Login const loginRes = await fetch(`${API}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'admin', password: 'admin123' }), }); const { token, user_id: userId } = await loginRes.json(); if (!token) throw new Error('Login failed'); // Use existing page const pages = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json`)).json(); const ourPage = pages.find(p => p.url === BASE || p.url === `${BASE}/`); if (!ourPage) throw new Error('No page found'); // Connect CDP const pw = new WebSocket(ourPage.webSocketDebuggerUrl); await new Promise((resolve, reject) => { pw.once('open', resolve); pw.once('error', reject); setTimeout(() => reject(new Error('page WS timeout')), 5000); }); const cdp = makeCDPHelper(pw); await cdp('Page.enable'); await cdp('Runtime.enable'); await cdp('Log.enable'); // Collect console messages const consoleMessages = []; pw.on('message', (raw) => { try { const msg = JSON.parse(raw.toString()); if (msg.method === 'Runtime.consoleAPICalled') { consoleMessages.push({ type: msg.params.type, text: msg.params.args?.map(a => a.value ?? a.description ?? '').join(' ').slice(0, 300), }); } if (msg.method === 'Log.entryAdded') { consoleMessages.push({ type: 'log', text: msg.params.entry?.text?.slice(0, 300) || '', }); } } catch {} }); // Inject auth, reload await cdp('Runtime.evaluate', { expression: `localStorage.setItem('token', ${JSON.stringify(token)}); localStorage.setItem('user_id', '${userId}');` }); await cdp('Page.navigate', { url: BASE }); await sleep(5000); // Get JS errors await cdp('Runtime.evaluate', { expression: `window.__errors = []; window.onerror = function(m,s,l,c,e) { window.__errors.push({msg:m,source:s,line:l,col:c,error:String(e)}); }; 'listening';` }); // Screenshot console.log('[1] Taking screenshot...'); const ss = await cdp('Page.captureScreenshot', { format: 'png' }); if (ss?.data) { writeFileSync(`${OUT}/screenshot_layout.png`, Buffer.from(ss.data, 'base64')); console.log(' ✅ Saved', ss.data.length, 'bytes base64'); } // Get console errors const jsErrors = await cdp('Runtime.evaluate', { returnByValue: true, expression: `JSON.stringify(window.__errors || [])` }); console.log('\n[2] JS Errors:', jsErrors?.result?.value || 'none'); // Console messages console.log('\n[3] Console messages collected:', consoleMessages.length); for (const m of consoleMessages) { console.log(` [${m.type}] ${m.text}`); } // Check computed styles for rendering issues console.log('\n[4] Final rendering check...'); const finalCheck = await cdp('Runtime.evaluate', { returnByValue: true, expression: ` JSON.stringify({ chatInput: (() => { const ta = document.querySelector('textarea'); if (!ta) return 'NO_TEXTAREA'; const r = ta.getBoundingClientRect(); // Walk up to find the ChatInput wrapper let parent = ta.parentElement; for (let i = 0; i < 10 && parent; i++) { if (parent.className?.includes?.('border-t')) break; parent = parent.parentElement; } const wrapper = parent; return { textareaRect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height), w: Math.round(r.width) }, textareaVisible: r.top < innerHeight && r.bottom > 0 && r.width > 0, wrapperRect: wrapper ? (() => { const wr = wrapper.getBoundingClientRect(); return { t: Math.round(wr.top), b: Math.round(wr.bottom), h: Math.round(wr.height) }; })() : null, wrapperBg: wrapper ? getComputedStyle(wrapper).backgroundColor : 'N/A', wrapperDisplay: wrapper ? getComputedStyle(wrapper).display : 'N/A', }; })(), iotStatusBar: (() => { const cb = document.querySelector('.chat-background'); if (!cb) return 'NO_CHAT_BG'; const kids = Array.from(cb.children); const iotKid = kids[kids.length - 1]; // Last child of chat-background if (!iotKid) return 'NO_IOT_KID'; const r = iotKid.getBoundingClientRect(); return { rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }, visible: r.top < innerHeight && r.bottom > 0 && r.height > 0, cls: iotKid.className?.slice?.(0, 200) || '', cs: { display: getComputedStyle(iotKid).display, bg: getComputedStyle(iotKid).backgroundColor }, txt: (iotKid.textContent || '').slice(0, 100), }; })(), overlays: (() => { const all = Array.from(document.querySelectorAll('*')); return all.filter(el => { const cs = getComputedStyle(el); return (cs.position === 'fixed' || cs.position === 'absolute') && parseInt(cs.zIndex) > 0 && el.clientHeight > 100; }).map(el => { const r = el.getBoundingClientRect(); return { tag: el.tagName, cls: (el.className?.slice?.(0, 150) || ''), rect: { t: Math.round(r.top), b: Math.round(r.bottom), l: Math.round(r.left), r: Math.round(r.right), h: Math.round(r.height) }, zIndex: getComputedStyle(el).zIndex, position: getComputedStyle(el).position, }; }); })(), }) ` }); console.log(finalCheck?.result?.value || 'ERROR'); pw.close(); console.log('\n✅ Done.'); } main().catch(err => { console.error('Fatal:', err); process.exit(1); });