#!/usr/bin/env node /** * 精准诊断 — 定位 IoTStatusBar 和 ChatInput 在 DOM 中的确切位置 */ import WebSocket from 'ws'; const CDP_PORT = parseInt(process.env.CDP_PORT || '9225'); const BASE = 'http://localhost:5199'; const API = 'http://localhost:8080/api/v1'; 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 evaluate(cdp, expression, label) { try { const r = await cdp('Runtime.evaluate', { returnByValue: true, expression }); const val = r?.result?.value ?? r?.value; console.log(`\n=== ${label} ===`); console.log(typeof val === 'string' ? val : JSON.stringify(val, null, 2)); return val; } catch (e) { console.error(`[${label}] ERROR:`, e.message); return null; } } 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'); // Connect to existing page or create new one let pages = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json`)).json(); console.log('Pages:', pages.map(p => ({ id: p.id?.slice(0, 20), url: p.url?.slice(0, 60) }))); // Prefer existing localhost page, or navigate a page to localhost let ourPage = pages.find(p => p.url === BASE || p.url === `${BASE}/`); let needNavigate = false; if (!ourPage) { // Create new target const ver = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json/version`)).json(); const bw = new WebSocket(ver.webSocketDebuggerUrl); await new Promise((resolve, reject) => { bw.once('open', resolve); bw.once('error', reject); setTimeout(() => reject(new Error('browser WS timeout')), 5000); }); const bcdp = makeCDPHelper(bw); await bcdp('Target.createTarget', { url: 'about:blank', width: 1440, height: 900 }); bw.close(); await sleep(1000); pages = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json`)).json(); ourPage = pages.find(p => p.url === 'about:blank'); needNavigate = true; } if (!ourPage) throw new Error('No page found'); console.log('Using page:', { id: ourPage.id?.slice(0, 20), url: ourPage.url }); // Connect to page 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'); // Navigate, inject auth, reload await cdp('Page.navigate', { url: BASE }); await sleep(3000); 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); // === DIAGNOSIS 1: Main element's full DOM structure === await evaluate(cdp, ` (() => { const main = document.querySelector('main'); if (!main) return 'NO_MAIN'; function describe(el, maxDepth) { if (!el || maxDepth <= 0) return null; const r = el.getBoundingClientRect(); const cs = getComputedStyle(el); const txt = (el.textContent || '').replace(/\\s+/g, ' ').slice(0, 80); return { tag: el.tagName, cls: (el.className?.slice?.(0, 200) || ''), id: el.id || '', rect: { t: Math.round(r.top), b: Math.round(r.bottom), l: Math.round(r.left), r: Math.round(r.right), w: Math.round(r.width), h: Math.round(r.height) }, cs: { display: cs.display, overflow: cs.overflow, height: cs.height, flexShrink: cs.flexShrink, flexGrow: cs.flexGrow }, clientH: el.clientHeight, scrollH: el.scrollHeight, txt, kids: maxDepth > 1 ? Array.from(el.children).map(c => describe(c, maxDepth - 1)) : undefined, }; } return describe(main, 5); })() `, 'MainDOMTree'); // === DIAGNOSIS 2: Find ChatContainer (.chat-background) and its children === await evaluate(cdp, ` (() => { const cb = document.querySelector('.chat-background'); if (!cb) return 'NO_CHAT_BACKGROUND'; const r = cb.getBoundingClientRect(); const cs = getComputedStyle(cb); const kids = Array.from(cb.children).map((c, i) => { const cr = c.getBoundingClientRect(); const ccs = getComputedStyle(c); return { idx: i, tag: c.tagName, cls: (c.className?.slice?.(0, 200) || ''), rect: { t: Math.round(cr.top), b: Math.round(cr.bottom), h: Math.round(cr.height) }, cs: { display: ccs.display, flexShrink: ccs.flexShrink, flexGrow: ccs.flexGrow, height: ccs.height }, clientH: c.clientHeight, txt: (c.textContent || '').replace(/\\s+/g, ' ').slice(0, 100), }; }); return { chatBgRect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }, chatBgCS: { height: cs.height, overflow: cs.overflow, display: cs.display }, chatBgClientH: cb.clientHeight, children: kids, }; })() `, 'ChatContainerChildren'); // === DIAGNOSIS 3: Check if IoTStatusBar is rendered at all === await evaluate(cdp, ` (() => { // Search for all elements that might be the IoTStatusBar const results = []; // Strategy 1: Find div with "IoT" text that is a direct child of .chat-background const cb = document.querySelector('.chat-background'); if (cb) { const iotInChatBg = Array.from(cb.querySelectorAll('*')).filter(el => el.textContent?.includes('IoT') && el.children.length === 0 ); results.push({ strategy: 'IoT_leaf_in_chatBg', count: iotInChatBg.length, elements: iotInChatBg.slice(0, 3).map(el => { const r = el.getBoundingClientRect(); return { tag: el.tagName, txt: (el.textContent || '').slice(0, 80), rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }, }; }), }); } // Strategy 2: Find .border-t elements inside .chat-background if (cb) { const borderTInCb = Array.from(cb.querySelectorAll('.border-t')); results.push({ strategy: 'borderT_in_chatBg', count: borderTInCb.length, elements: borderTInCb.map(el => { const r = el.getBoundingClientRect(); let parentCls = ''; let p = el.parentElement; for (let i = 0; i < 3 && p; i++) { parentCls += (p.className?.slice?.(0, 100) || '') + ' | '; p = p.parentElement; } return { rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }, txt: (el.textContent || '').replace(/\\s+/g, ' ').slice(0, 100), parentChain: parentCls, }; }), }); } // Strategy 3: Find all flex-shrink-0 inside .chat-background if (cb) { const shrinkInCb = Array.from(cb.querySelectorAll('.flex-shrink-0')); results.push({ strategy: 'flexShrink0_in_chatBg', count: shrinkInCb.length, elements: shrinkInCb.map(el => { const r = el.getBoundingClientRect(); return { rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }, cls: (el.className?.slice?.(0, 200) || ''), txt: (el.textContent || '').replace(/\\s+/g, ' ').slice(0, 100), display: getComputedStyle(el).display, }; }), }); } return results; })() `, 'IoTStatusBarSearch'); // === DIAGNOSIS 4: Get complete rect for every child of the App wrapper === await evaluate(cdp, ` (() => { const main = document.querySelector('main'); if (!main?.firstElementChild) return 'NO_MAIN_CHILD'; const appWrapper = main.firstElementChild; return { appWrapperCls: appWrapper.className?.slice?.(0, 200), appWrapperRect: (() => { const r = appWrapper.getBoundingClientRect(); return { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }; })(), children: Array.from(appWrapper.children).map((c, i) => { const r = c.getBoundingClientRect(); const cs = getComputedStyle(c); return { idx: i, cls: (c.className?.slice?.(0, 200) || ''), rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }, cs: { display: cs.display, flexGrow: cs.flexGrow, flexShrink: cs.flexShrink, height: cs.height, overflow: cs.overflow }, clientH: c.clientHeight, childCount: c.children.length, firstChildCls: c.firstElementChild ? (c.firstElementChild.className?.slice?.(0, 150) || '') : '', }; }), }; })() `, 'AppWrapperChildren'); pw.close(); console.log('\n✅ Done.'); } main().catch(err => { console.error('Fatal:', err); process.exit(1); });