'use strict'; /* 真实浏览器诊断:跑完整三步,dump 路线图 DOM 与住宿区 DOM */ const fs = require('fs'); const path = require('path'); const OUT = path.join(__dirname, '..', 'preview'); const BASE = process.env.BASE || 'http://127.0.0.1:8788'; const AMAP = process.env.AMAP_KEY || ''; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); class CDP { constructor(ws) { this.ws = ws; this.id = 0; this.waits = new Map(); } static async connect(url) { const ws = new WebSocket(url); await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej; }); const c = new CDP(ws); ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && c.waits.has(m.id)) { c.waits.get(m.id)(m); c.waits.delete(m.id); } }; return c; } send(method, params = {}, sessionId) { const id = ++this.id; return new Promise((res) => { this.waits.set(id, res); this.ws.send(JSON.stringify({ id, method, params, sessionId })); }); } } (async () => { const { webSocketDebuggerUrl } = await (await fetch('http://127.0.0.1:9333/json/version')).json(); const browser = await CDP.connect(webSocketDebuggerUrl); const { result: { targetId } } = await browser.send('Target.createTarget', { url: 'about:blank' }); const { result: { sessionId } } = await browser.send('Target.attachToTarget', { targetId, flatten: true }); const S = (m, p) => browser.send(m, p, sessionId); await S('Page.enable'); await S('Runtime.enable'); await S('Emulation.setDeviceMetricsOverride', { width: 1360, height: 1800, deviceScaleFactor: 1, mobile: false }); const evalJs = async (expr) => { const r = await S('Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true }); if (r.result?.exceptionDetails) throw new Error(JSON.stringify(r.result.exceptionDetails).slice(0, 400)); return r.result?.result?.value; }; // 预置设置(含高德 Key),这样能跑通真实底图与周边 POI await S('Page.navigate', { url: BASE }); await sleep(2500); if (AMAP) { await evalJs(`(() => { const raw = JSON.parse(localStorage.getItem('tripsense.ai') || '{}'); raw.amapKey = ${JSON.stringify(AMAP)}; localStorage.setItem('tripsense.ai', JSON.stringify(raw)); return true; })()`); await S('Page.reload'); await sleep(2500); console.log('amap key injected:', AMAP ? AMAP.slice(0, 6) + '…' : '(none)'); } // 出发地 await evalJs(`(() => { const el = document.querySelector('#originInput'); el.value = '柳州'; el.dispatchEvent(new Event('input', { bubbles: true })); return true; })()`); for (let i = 0; i < 20; i++) { await sleep(1000); const ok = await evalJs(`(() => { const it = document.querySelector('#suggestBox .suggest-item'); if (it && !document.querySelector('#suggestBox').classList.contains('hidden')) { it.click(); return true; } return false; })()`); if (ok) break; } console.log('origin =', await evalJs(`document.querySelector('#originInput')?.value`)); // 分析 await evalJs(`document.querySelector('#analyzeBtn')?.click()`); for (let i = 0; i < 40; i++) { await sleep(1500); const n = await evalJs(`document.querySelectorAll('.dest-card').length`); if (n) { console.log('candidates =', n); break; } if (i === 39) console.log('candidates = 0 (超时)'); } console.log('alerts =', await evalJs(`(document.querySelector('#alerts')?.innerText || '').slice(0, 300)`)); // 选 3 个 await evalJs(`(() => { const b = [...document.querySelectorAll('.dest-card .pick-btn')].slice(0,3); b.forEach(x => x.click()); return b.length; })()`); await sleep(2000); // 进入第三步(否则面板 display:none,getBoundingClientRect 全是 0) await evalJs(`document.querySelector('#toStep3')?.click()`); await sleep(1200); console.log('step3 active =', await evalJs(`document.querySelector('#panel3')?.classList.contains('active')`)); // 生成方案 await evalJs(`document.querySelector('#planBtn')?.click()`); for (let i = 0; i < 30; i++) { await sleep(1500); const t = await evalJs(`document.querySelector('#outputMeta')?.textContent || ''`); if (t.includes('生成完成') || t.includes('中断')) { console.log('outputMeta =', t); break; } if (i === 29) console.log('outputMeta =', t); } await sleep(4000); // ===== 关键诊断:路线图 ===== console.log('\n=== 路线图诊断 ==='); console.log('planExtra hidden =', await evalJs(`document.querySelector('#planExtra')?.classList.contains('hidden')`)); console.log('routeNote =', JSON.stringify(await evalJs(`document.querySelector('#routeNote')?.textContent`))); console.log('routeMap children =', await evalJs(`document.querySelector('#routeMap')?.children.length`)); console.log('routeMap html (600) =', JSON.stringify(await evalJs(`(document.querySelector('#routeMap')?.innerHTML||'').slice(0,600)`))); console.log('routeMap box =', JSON.stringify(await evalJs(`(() => { const e = document.querySelector('#routeMap'); if(!e) return null; const r = e.getBoundingClientRect(); return {w: Math.round(r.width), h: Math.round(r.height)}; })()`))); console.log('svg count =', await evalJs(`document.querySelectorAll('#routeMap svg').length`)); console.log('img count =', await evalJs(`document.querySelectorAll('#routeMap img').length`)); // ===== 住宿诊断 ===== console.log('\n=== 住宿诊断 ==='); console.log('stayNote =', JSON.stringify(await evalJs(`document.querySelector('#stayNote')?.textContent`))); console.log('hotelBox (900) =', JSON.stringify(await evalJs(`(document.querySelector('#hotelBox')?.innerText||'').slice(0,900)`))); // 截图:先整页,再分别截路线图与住宿卡 await evalJs(`document.querySelector('#planExtra')?.scrollIntoView({block:'start'})`); await sleep(1200); const shot = await S('Page.captureScreenshot', { format: 'png' }); if (shot.result?.data) fs.writeFileSync(path.join(OUT, 'preview-diag.png'), Buffer.from(shot.result.data, 'base64')); const clipOf = async (sel, file) => { const box = await evalJs(`(() => { const e = document.querySelector(${JSON.stringify(sel)}); if(!e) return null; const r = e.getBoundingClientRect(); return {x:r.x+window.scrollX,y:r.y+window.scrollY,width:r.width,height:r.height}; })()`); if (!box || box.width < 2) { console.log('no box for', sel); return; } const s = await S('Page.captureScreenshot', { format: 'png', clip: { ...box, scale: 1 } }); if (s.result?.data) fs.writeFileSync(path.join(OUT, file), Buffer.from(s.result.data, 'base64')); }; await clipOf('#routeMap', 'preview-diag-route.png'); await clipOf('#hotelBox', 'preview-diag-hotel.png'); console.log('\nscreenshots saved to preview/'); browser.ws.close(); process.exit(0); })().catch((e) => { console.error('FAIL', e.message); process.exit(1); });