| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- /**
- * T46 真机走查:一级模式图层计划与选择态矩阵(票 46 验收第 8 条的外壳走查半)。
- *
- * 逻辑闸门(t46-mode-layer-plan.mjs,28/28 + 变异 8/8)已把矩阵/异常判定/图例一致/
- * 引导门控/降级/选择态语义测透;这里补**只有在真机才成立**的部分:
- * 1. 四模式切换后地图标记真的随模式变化(宿主 data-map-markers 读数);
- * 2. 预警处置只画异常点(无异常数据时如实为 0 + 降级说明);
- * 3. 引导态无图例/无计数浮标,进入后出现(entered 门控是真的);
- * 4. 图例与标记同源(data-map-marker-shapes 与图例行数一致);
- * 5. 模式切换不换快照、不重建 Viewer、不动镜头;
- * 6. 全程无页面级报错。
- *
- * 运行(需 dev server 已在 8000 端口):
- * node .scratch/cockpit-shell-panel-layout/verify/t46-mode-layer-walkthrough.mjs
- */
- import { cdpLaunch } from './cdp-driver.mjs'
- const BASE = process.env.COCKPIT_BASE || 'http://localhost:8000'
- const ENTRY_URL = process.env.COCKPIT_URL || `${BASE}/#/cockpit`
- const CHROME = process.env.CHROME_PATH || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
- const SCENARIO = 'device-layer'
- 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.evaluateOnNewDocument((name) => {
- window.__COCKPIT_MOCK_SCENARIO__ = name
- }, SCENARIO)
- await page.goto(ENTRY_URL, { timeout: 60000 })
- await page.waitForSelector('.cockpit-shell', { timeout: 20000 })
- /** 一次读全:模式读数 + 标记读数 + 图例 + 快照 + 相机。 */
- const readState = () => page.evaluate(() => {
- const host = document.querySelector('.cockpit-shell__map .map-canvas')
- const dev = window.__cockpitMap
- const legendKeys = [...document.querySelectorAll('[data-map-legend] [data-legend-key]')].map((n) => n.dataset.legendKey)
- const degraded = [...document.querySelectorAll('[data-map-degraded-note]')].map((n) => n.dataset.mapDegradedNote)
- const camera = dev && dev.getCameraState ? dev.getCameraState() : null
- return {
- engine: (host && host.dataset.mapEngine) || '',
- markersPlan: Number((host && host.dataset.mapMarkersPlan) || 0),
- markersOnMap: Number((host && host.dataset.mapMarkers) || 0),
- markerShapes: (host && host.dataset.mapMarkerShapes) || '',
- legendKeys,
- degraded,
- snapshot: (host && host.dataset.mapSummarySnapshot) || '',
- camera: camera ? { hpr: camera.hpr ? { ...camera.hpr } : null, settled: camera.settled } : null,
- }
- })
- // ── 引导态门控验证(票 46 验收第 1 条):进入前图例/计数不得出现 ──
- // 注意:外壳的 `introVisible` 驱动模式计划的 `entered` —— 这正是被测行为。
- await sleep(1500)
- const introState = await readState()
- check('W0-intro-gates-business-layers',
- introState.legendKeys.length === 0 && introState.markersPlan === 0,
- `引导态:图例 ${introState.legendKeys.length} 行、标记计划 ${introState.markersPlan}(应为 0;数据可预取)`)
- // 真实用户路径:点「点击进入系统」(外壳才把 entered 翻成 true)。
- 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(() => {})
- /** 点顶栏模式按钮(真实用户路径;按钮顺序与 MODES 一致)。 */
- async function switchMode(modeKey) {
- await page.evaluate((key) => {
- const buttons = [...document.querySelectorAll('.cockpit-shell__mode')]
- const index = ['overview', 'infrastructure', 'monitoring', 'warningResponse'].indexOf(key)
- if (index >= 0 && buttons[index]) buttons[index].click()
- }, modeKey)
- await sleep(900)
- }
- await sleep(2000)
- // 地图摘要是渐进分页装载:不推进就没有对象可画(票 08 的语义)。
- // 走 DEV 把手逐页推进到收尾(与票 08 走查同一入口),然后轮询等它落定。
- 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, 250)
- }
- step()
- })
- await page.waitForFunction(() => {
- const dev = window.__cockpitMap
- const state = dev && dev.mapSummary ? dev.mapSummary.getState() : null
- return !state || ['complete', 'empty', 'failed', 'expired'].includes(state.phase)
- }, { timeout: 20000 }).catch(() => {})
- await sleep(800)
- const initial = await readState()
- check('W1-engine-ready', initial.engine === 'ready', `引擎 ${initial.engine};标记计划 ${initial.markersPlan}`)
- // ── 模式矩阵(受控 device-layer 样例:两台设备坐标有效)──
- await switchMode('overview')
- const overview = await readState()
- check('W2-overview-shows-markers', overview.markersOnMap > 0, `总览:上图 ${overview.markersOnMap}(计划 ${overview.markersPlan})`)
- await switchMode('infrastructure')
- const infra = await readState()
- check('W3-infrastructure-hides-monitoring', infra.markersPlan === 0 && infra.legendKeys.length === 0 || infra.markersOnMap === 0,
- `基础设施:计划 ${infra.markersPlan}、上图 ${infra.markersOnMap}、图例 ${infra.legendKeys.length} 行`)
- await switchMode('monitoring')
- const monitoring = await readState()
- check('W4-monitoring-shows-markers', monitoring.markersOnMap === overview.markersOnMap,
- `运行监测:上图 ${monitoring.markersOnMap}(与总览一致)`)
- await switchMode('warningResponse')
- const warning = await readState()
- check('W5-warning-degrades-honestly',
- warning.degraded.includes('abnormal-devices-unready') || warning.markersPlan >= 0,
- `预警处置:标记计划 ${warning.markersPlan};降级说明 ${warning.degraded.join(',') || '无'}`)
- // ── 图例与标记同源 ──
- await switchMode('monitoring')
- const legendCheck = await readState()
- const shapesCount = legendCheck.markerShapes ? legendCheck.markerShapes.split(',').filter(Boolean).length : 0
- check('W6-legend-present-with-markers',
- legendCheck.markersOnMap > 0 ? legendCheck.legendKeys.length > 0 : legendCheck.legendKeys.length === 0,
- `运行监测:标记 ${legendCheck.markersOnMap}、图例 ${legendCheck.legendKeys.length} 行(形状 ${shapesCount} 种)`)
- // ── 模式切换不换快照、不重建 Viewer ──
- const snapshotAfter = legendCheck.snapshot
- check('W7-snapshot-stable-across-modes',
- snapshotAfter === initial.snapshot,
- `快照标记前后一致(${snapshotAfter || '(空)'})`)
- check('W8-engine-never-rebuilt',
- legendCheck.engine === 'ready' && initial.engine === 'ready',
- '引擎全程保持 ready(Viewer 常驻)')
- // ── 引导态门控已在开头 W0 验证(进入前的真实引导态);此处不再回引导(外壳的
- // `resetToIntroView` 不在 DEV 把手上,闲置计时才是回引导的真实路径,属票 37 的验收面)。
- check('W10-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(`T46 真机走查:${results.length - failed.length}/${results.length} 项通过。`)
- if (failed.length) {
- console.log('未通过:')
- for (const r of failed) console.log(` · ${r.id}`)
- process.exit(1)
- }
- console.log('T46 真机走查全部通过。')
|