/** * T45 主场景示例走查:**不注入任何场景**(默认 `success`),验证维护者要求的两条示例直接可见: * · MP-001 城南测试监测点 —— K=2 正常重叠(聚合符号 K=2,可逐台展开); * · MP-002 太常测试监测点 —— K=11 降级(本屏 10 台 / 其余见清单)。 * * 全程真实鼠标点地图标记(与用户路径一致);坐标读取用只读 `getPointMarkerTargets()`, * 并在投影点附近做小网格扫描点击(贴地形标记会随地形细化抖动几像素,真人点不中也会再点)。 */ import { cdpLaunch } from './cdp-driver.mjs' const BASE = process.env.COCKPIT_BASE || 'http://localhost:8000' const CHROME = process.env.CHROME_PATH || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe' // 主场景:**不带** scenario 查询参数。 const ENTRY_URL = process.env.COCKPIT_URL || `${BASE}/#/cockpit` const results = [] function check(id, pass, evidence) { results.push({ id, pass }) console.log(`${pass ? 'PASS' : 'FAIL'} ${id} :: ${evidence === undefined ? '' : String(evidence)}`) } const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const browser = await cdpLaunch({ executablePath: CHROME, width: 1680, height: 950 }) console.log(`Chrome: ${await browser.version()}`) const page = await browser.newPage() const errors = [] page.on('pageerror', (e) => errors.push(String(e && e.message ? e.message : e))) page.on('console', (m) => { if (m.type() === 'error') errors.push(`console.error: ${m.text()}`) }) await page.goto(ENTRY_URL, { timeout: 60000 }) await page.waitForSelector('.cockpit-shell', { timeout: 20000 }) if (await page.$('[data-cockpit-intro]')) { await page.click('[data-cockpit-intro]') await page.waitForSelector('[data-cockpit-intro]', { hidden: true, timeout: 10000 }).catch(() => {}) } await page.waitForFunction( () => ['ready', 'failed', 'disabled'].includes(document.querySelector('.cockpit-shell__map .map-canvas')?.dataset.mapEngine), { timeout: 40000 }, ).catch(() => {}) await page.evaluate(() => { const dev = window.__cockpitMap if (!dev || !dev.mapSummary) return let steps = 0 const step = () => { const state = dev.mapSummary.getState() if (!state || ['complete', 'empty', 'failed', 'expired'].includes(state.phase) || steps >= 20) return dev.mapSummary.advance(); steps += 1; window.setTimeout(step, 220) } step() }) await page.waitForFunction(() => { const dev = window.__cockpitMap const s = dev && dev.mapSummary ? dev.mapSummary.getState() : null return !s || ['complete', 'empty', 'failed', 'expired'].includes(s.phase) }, { timeout: 20000 }).catch(() => {}) await page.waitForFunction(() => { const dev = window.__cockpitMap const s = dev && dev.getCameraState ? dev.getCameraState() : null return !s || s.settled === true }, { timeout: 15000 }).catch(() => {}) await sleep(1500) const locate = (code) => page.evaluate((pointCode) => { const dev = window.__cockpitMap const all = dev && dev.getPointMarkerTargets ? dev.getPointMarkerTargets() : [] return all.find((item) => item.visible && item.code === pointCode) || null }, code) const readOverlap = () => page.evaluate(() => { const host = document.querySelector('.cockpit-shell__map .map-canvas') const dev = window.__cockpitMap const devices = dev && dev.getAssociatedDevices ? dev.getAssociatedDevices() : { entities: [], onMap: 0 } return { overlap: dev && dev.getOverlapState ? dev.getOverlapState() : null, detailLevel: (document.querySelector('[data-point-detail]') || { dataset: {} }).dataset.detailLevel || '', pointCode: (document.querySelector('[data-point-detail]') || { dataset: {} }).dataset.pointCode || '', rows: [...document.querySelectorAll('[data-device-layer-row]')].map((n) => n.dataset.deviceLayerCode), aggregateEntities: (devices.entities || []).filter((e) => e.overlapAggregate).length, mapOverlapState: host && host.dataset.mapOverlapState, mapOverlapCount: host && host.dataset.mapOverlapCount, } }) const OFFSETS = [[0, 0], [0, -6], [0, 6], [-6, 0], [6, 0], [0, -12], [0, 12], [-10, -6], [10, 6]] /** 真实鼠标点某个点位标记,直到点位详情打开(或重试用尽)。 */ async function openPoint(code) { for (let round = 0; round < 5; round += 1) { const marker = await locate(code) if (marker) { for (const [dx, dy] of OFFSETS) { await page.mouse.move(1200, 300) await sleep(70) await page.mouse.move(marker.x + dx, marker.y + dy) await sleep(140) await page.mouse.click(marker.x + dx, marker.y + dy) await sleep(800) const now = await readOverlap() if (now.detailLevel === 'point' && now.pointCode === code) return now } } await sleep(400) } return readOverlap() } // 遮罩让真实鼠标能命中画布(既有锚定浮层会盖住画布;仅测试态)。 async function passthroughDetail() { await page.evaluate(() => { const detail = document.querySelector('.cockpit-shell__detail') if (detail) { detail.style.pointerEvents = 'none' detail.querySelectorAll('*').forEach((child) => { child.style.pointerEvents = 'none' }) } }) } /** 真实点击顶栏「归位」,等相机收敛回广视角(相机飞近会把另一个标记挤出画布)。 */ async function returnHome() { await page.click('.cockpit-shell__home').catch(() => {}) await page.waitForFunction(() => { const dev = window.__cockpitMap const s = dev && dev.getCameraState ? dev.getCameraState() : null return !s || s.settled === true }, { timeout: 20000 }).catch(() => {}) await sleep(1200) } // ── 示例 1:MP-001 = K=2 正常重叠 ── const first = await openPoint('MP-001') await passthroughDetail() check('M1-main-scene-MP001-opens', first.detailLevel === 'point' && first.pointCode === 'MP-001', `真实点 MP-001 后 detailLevel=${first.detailLevel} pointCode=${first.pointCode}`) check('M2-main-scene-MP001-K2', first.overlap && first.overlap.count === 2 && first.overlap.state === 'collapsed' && first.aggregateEntities === 1 && first.rows.length === 2 && first.mapOverlapCount === '2', `MP-001:K=${first.overlap && first.overlap.count} state=${first.overlap && first.overlap.state} 聚合实体=${first.aggregateEntities} 清单=${first.rows.length} 行 data-count=${first.mapOverlapCount}`) // ── 示例 2:MP-002 = K=11 降级 ── // 先归位(真实点顶栏按钮):点开 MP-001 后相机会飞近,MP-002 会被挤出画布。 await returnHome() const markerDiag = await page.evaluate(() => { const dev = window.__cockpitMap const host = document.querySelector('.cockpit-shell__map .map-canvas') const rect = host ? host.getBoundingClientRect() : null const all = dev && dev.getPointMarkerTargets ? dev.getPointMarkerTargets() : [] const cam = dev && dev.getCameraState ? dev.getCameraState() : null return { host: rect ? { left: Math.round(rect.left), top: Math.round(rect.top), right: Math.round(rect.right), bottom: Math.round(rect.bottom) } : null, camera: cam ? { settled: cam.settled, hpr: cam.hpr } : null, targets: all.map((t) => ({ code: t.code, x: Math.round(t.x), y: Math.round(t.y), visible: t.visible })), } }) console.log(`归位后标记坐标(证据):${JSON.stringify(markerDiag)}`) const second = await openPoint('MP-002') await passthroughDetail() check('M3-main-scene-MP002-opens', second.detailLevel === 'point' && second.pointCode === 'MP-002', `真实点 MP-002 后 detailLevel=${second.detailLevel} pointCode=${second.pointCode}`) check('M4-main-scene-MP002-K11-degraded', second.overlap && second.overlap.count === 11 && second.overlap.state === 'degraded' && second.aggregateEntities === 1 && second.rows.length === 11 && second.mapOverlapCount === '11' && second.mapOverlapState === 'degraded', `MP-002:K=${second.overlap && second.overlap.count} state=${second.overlap && second.overlap.state} 聚合实体=${second.aggregateEntities} 清单=${second.rows.length} 行 data-state=${second.mapOverlapState} data-count=${second.mapOverlapCount}`) check('M5-no-page-errors', errors.length === 0, errors.length ? errors.slice(0, 3).join(' | ') : '无 pageerror / console.error') await page.close() await browser.close() const failed = results.filter((r) => !r.pass) console.log('') console.log(`T45 主场景示例走查:${results.length - failed.length}/${results.length} 项通过。`) if (failed.length) { for (const item of failed) console.log(` · ${item.id}`); process.exit(1) } console.log('T45 主场景示例走查全部通过。')