#!/usr/bin/env node /** * 布局诊断 v3 — 正确使用 CDP Target.createTarget + Page target */ import { writeFileSync, mkdirSync } from 'fs'; import WebSocket from 'ws'; const BASE = 'http://localhost:5199'; const API = 'http://localhost:8080/api/v1'; const CDP_PORT = parseInt(process.env.CDP_PORT || '9225'); const OUT = '/home/aska/Code/Cyrene/debug/logs/chromium'; mkdirSync(OUT, { recursive: true }); 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) => { const id = msgId++; ws.send(JSON.stringify({ id, method, params })); pending.set(id, resolve); }); } async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } async function main() { // Step 1: Login console.log('[1] Logging in...'); const loginRes = await fetch(`${API}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'admin', password: 'admin123' }), }); const loginData = await loginRes.json(); const token = loginData?.token; const userId = loginData?.user_id || 'admin'; console.log(' userId:', userId, 'token:', token?.slice(0, 20) + '...'); if (!token) throw new Error('Login failed'); // Step 2: Connect to browser CDP, create target console.log('[2] Getting browser WS URL...'); const ver = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json/version`)).json(); const browserWsUrl = ver.webSocketDebuggerUrl; console.log(' Browser WS:', browserWsUrl.slice(0, 80)); // Connect to browser const bw = new WebSocket(browserWsUrl); 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); // Create a new page target console.log('[3] Creating page target...'); const targetResult = await bcdp('Target.createTarget', { url: 'about:blank', width: 1440, height: 900 }); const targetId = targetResult?.targetId; console.log(' TargetId:', targetId); // Get page list to find our target's WS URL const pages = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json`)).json(); const ourPage = pages.find(p => p.id === targetId || p.url === 'about:blank'); const pageWsUrl = ourPage?.webSocketDebuggerUrl; console.log(' Page WS:', pageWsUrl?.slice(0, 80)); if (!pageWsUrl) throw new Error('Could not find page WS URL'); // Close browser connection, connect to page bw.close(); // Step 4: Connect to page CDP console.log('[4] Connecting to page target...'); const pw = new WebSocket(pageWsUrl); 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('DOM.enable'); // Step 5: Navigate to set origin, inject auth, reload console.log('[5] Setting auth...'); await cdp('Page.navigate', { url: BASE }); await sleep(3000); // Inject localStorage const injectResult = await cdp('Runtime.evaluate', { expression: `localStorage.setItem('token', ${JSON.stringify(token)}); localStorage.setItem('user_id', '${userId}'); 'injected';` }); console.log(' Inject result:', JSON.stringify(injectResult).slice(0, 200)); // Reload to pick up auth console.log('[6] Reloading with auth...'); await cdp('Page.navigate', { url: BASE }); await sleep(4000); // Check if on chat page const checkResult = await cdp('Runtime.evaluate', { returnByValue: true, expression: `JSON.stringify({ title: document.title, hasTextarea: !!document.querySelector('textarea'), bodyText: document.body?.innerText?.slice(0, 200) })` }); console.log(' Page state:', checkResult?.result?.value || 'N/A'); // Step 7: Screenshot console.log('[7] Taking screenshot...'); const ss = await cdp('Page.captureScreenshot', { format: 'png', clip: { x: 0, y: 0, width: 1440, height: 900, scale: 1 } }); if (ss?.data) { writeFileSync(`${OUT}/screenshot_layout.png`, Buffer.from(ss.data, 'base64')); console.log(' ✅ Saved', ss.data.length, 'chars base64'); } else { console.log(' ⚠️ No screenshot data'); } // Step 8: Extract layout diagnostics console.log('[8] Extracting layout info...'); const diagResult = await cdp('Runtime.evaluate', { returnByValue: true, expression: ` (() => { function info(el) { if (!el) return null; const s = getComputedStyle(el); const r = el.getBoundingClientRect(); return { tag: el.tagName, cls: (el.className && typeof el.className === 'string') ? el.className.slice(0,300) : '', rect: { t: Math.round(r.top), l: Math.round(r.left), b: Math.round(r.bottom), r: Math.round(r.right), w: Math.round(r.width), h: Math.round(r.height) }, display: s.display, visibility: s.visibility, opacity: s.opacity, overflow: s.overflow, overflowY: s.overflowY, position: s.position, zIndex: s.zIndex, flexShrink: s.flexShrink, flexGrow: s.flexGrow, clientH: el.clientHeight, scrollH: el.scrollHeight, offsetParent: el.offsetParent ? el.offsetParent.tagName : null, }; } const R = {}; R.viewport = { w: innerWidth, h: innerHeight }; R.root = info(document.getElementById('root')); R.hScreen = info(document.querySelector('.h-screen')); R.main = info(document.querySelector('main')); const m = document.querySelector('main'); if (m && m.firstElementChild) { R.appWrapper = info(m.firstElementChild); R.appWrapperChildren = Array.from(m.firstElementChild.children).map((c,i) => ({ idx: i, cls: (c.className && typeof c.className === 'string') ? c.className.slice(0,200) : '', ...info(c), })); } R.chatBackground = info(document.querySelector('.chat-background')); const cb = document.querySelector('.chat-background'); if (cb) { R.chatBgChildren = Array.from(cb.children).map((c,i) => ({ idx: i, cls: (c.className && typeof c.className === 'string') ? c.className.slice(0,200) : '', ...info(c), })); } R.textarea = info(document.querySelector('textarea')); R.shrink0 = Array.from(document.querySelectorAll('.flex-shrink-0')).map(el => ({ text: (el.textContent||'').slice(0,60), ...info(el), })); R.borderT = Array.from(document.querySelectorAll('.border-t')).map(el => ({ text: (el.textContent||'').slice(0,100), ...info(el), })); return JSON.stringify(R); })() ` }); const diagValue = diagResult?.result?.value || diagResult?.value || '{}'; const result = typeof diagValue === 'string' ? JSON.parse(diagValue) : diagValue; console.log(JSON.stringify(result, null, 2)); writeFileSync(`${OUT}/diagnostics.json`, JSON.stringify(result, null, 2)); pw.close(); console.log('\n✅ Done'); } main().catch(err => { console.error('Fatal:', err); process.exit(1); });