'use strict'; /* 验收脚本:真实场景(南宁青秀区出发 → 柳州为目的地)→ 选柳州 → 生成方案 → 检查路线图 */ const { spawn } = require('child_process'); const fs = require('fs'); const path = require('path'); const OUT = path.join(__dirname, '..', 'preview'); const BASE = process.env.BASE || 'http://127.0.0.1:8791'; const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe'; function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } async function getWsUrl() { const res = await fetch('http://127.0.0.1:9333/json/version'); return (await res.json()).webSocketDebuggerUrl; } 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 browser = await CDP.connect(await getWsUrl()); 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: 1400, height: 1700, deviceScaleFactor: 1, mobile: false }); await S('Page.navigate', { url: BASE }); await sleep(2500); 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, 500)); return r.result?.result?.value; }; // 1) 直接注入出发地坐标(河池市金城江区),绕过联想 await evalJs(`(() => { state.origin = { name:'河池市金城江区', label:'广西壮族自治区 · 河池市 · 金城江区', lat:24.69562, lon:108.08091, admin1:'广西壮族自治区', country:'中国' }; document.querySelector('#originInput').value = '河池市金城江区'; const r = document.querySelector('#radius'); if (r) { r.value = '250'; r.dispatchEvent(new Event('change',{bubbles:true})); } return true; })()`); await evalJs(`document.querySelector('#analyzeBtn')?.click()`); // 分析要跑 20~60s(高德检索 + 天气) for (let i = 0; i < 60; i += 1) { await sleep(2000); const n = await evalJs(`document.querySelectorAll('.dest-card').length`); if (n > 0) { console.log('candidates =', n, 'after', (i + 1) * 2, 's'); break; } } // 2) 找到「柳州市」并勾选它(真实场景:目的地是柳州) const picked = await evalJs(`(() => { const cards = [...document.querySelectorAll('.dest-card')]; const hit = cards.find(c => (c.innerText||'').includes('柳州市')); if (!hit) return 'NO-LIUZHOU'; const btn = hit.querySelector('.pick-btn'); btn.click(); return 'clicked'; })()`); console.log('pick 柳州市 =', picked); await sleep(1500); console.log('selected =', await evalJs(`document.querySelector('#selectedCount')?.textContent`)); // 3) 生成方案 await evalJs(`document.querySelector('#planBtn')?.click()`); for (let i = 0; i < 60; i += 1) { await sleep(2000); const t = await evalJs(`(document.querySelector('#planOutput')?.innerText||'').length`); if (t > 200) { console.log('plan chars =', t, 'after', (i + 1) * 2, 's'); break; } } await sleep(4000); // 4) 检查路线图状态 const route = await evalJs(`(() => ({ note: document.querySelector('#routeNote')?.textContent || '', legend: document.querySelector('#routeMap .map-legend')?.textContent || '', hasImg: !!document.querySelector('#routeMap img'), circles: document.querySelectorAll('#routeMap .map-overlay circle').length, polyline: !!document.querySelector('#routeMap .map-overlay polyline'), names: [...document.querySelectorAll('#routeMap .map-overlay .map-name')].map(t=>t.textContent), hints: [...document.querySelectorAll('#routeMap .map-hint')].map(t=>t.textContent.slice(0,90)), routePoints: (state.routePoints||[]).map(p=>p.name+'@'+p.lat+','+p.lon), missing: state.routeMissing || [], }))()`); console.log('ROUTE =', JSON.stringify(route, null, 1)); await evalJs(`gotoStep(3)`); await sleep(1200); await evalJs(`document.querySelector('#routeMap')?.scrollIntoView({block:'center'})`); await sleep(1200); const shot = await S('Page.captureScreenshot', { format: 'png' }); fs.writeFileSync(path.join(OUT, 'route-plan.png'), Buffer.from(shot.result.data, 'base64')); console.log('screenshot -> preview/route-plan.png'); // 单独截路线图区域(clip 按元素文档坐标截,不依赖滚动位置) const box = await evalJs(`(() => { const el = document.querySelector('#routeMap'); if (!el) return null; const r = el.getBoundingClientRect(); return { x: r.left + window.scrollX, y: r.top + window.scrollY, width: Math.round(r.width), height: Math.round(r.height) }; })()`); console.log('clip box =', JSON.stringify(box)); if (box && box.height > 20) { const shot2 = await S('Page.captureScreenshot', { format: 'png', captureBeyondViewport: true, clip: { x: Math.max(0, box.x - 8), y: Math.max(0, box.y - 8), width: box.width + 16, height: box.height + 16, scale: 1 }, }); fs.writeFileSync(path.join(OUT, 'route-map.png'), Buffer.from(shot2.result.data, 'base64')); console.log('screenshot -> preview/route-map.png'); } browser.ws.close(); process.exit(0); })().catch((e) => { console.error('FAIL', e.message); process.exit(1); });