| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- /**
- * T32 真机走查:界面形态(开发诊断 vs 生产干净)。
- *
- * 维护者 2026-09-18 要求:正式环境不要那么多明显文本压在中央 3D 场景上,状态信息收成
- * 底部半透明小字;**颜色语义(点位状态图例、状态灯)必须保留**。
- *
- * 断言的两种形态(同一个 dev server,靠注入配置切换,所以不依赖构建模式):
- * · `map.ui.debug: false` → 干净形态:标题/图层占位/实例提示/顶栏配置标签/当前模式读数都不在;
- * 图例与状态灯在;引擎状态在**底部且半透明、字号更小**;
- * · `map.ui.debug: true` → 调试形态:以上都在(且与票 04/09/21/29 走查依赖的选择器一致)。
- *
- * 运行(需先起 `yarn dev`):node .scratch/cockpit-shell-panel-layout/verify/t32-ui-mode-walkthrough.mjs
- */
- import { readFileSync } from 'node:fs'
- import { join } from 'node:path'
- import { tmpdir } from 'node:os'
- 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 PUPPETEER = 'file:///C:/Users/ASUS/.dsh/profiles/desktop/node_modules/puppeteer-core/lib/puppeteer/puppeteer-core.js'
- const OUT_PROD = join(tmpdir(), 'cockpit-t32-production.png')
- const OUT_DEBUG = join(tmpdir(), 'cockpit-t32-debug.png')
- const config = JSON.parse(readFileSync(new URL('../../../public/cockpit/config.json', import.meta.url), 'utf8'))
- const results = []
- function check(id, desc, pass, evidence) {
- results.push({ id, desc, pass })
- console.log(`${pass ? 'PASS' : 'FAIL'} ${id} ${desc}${evidence ? ` — ${evidence}` : ''}`)
- }
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
- const puppeteer = (await import(PUPPETEER)).default
- const browser = await puppeteer.launch({
- executablePath: CHROME,
- headless: true,
- userDataDir: join(tmpdir(), 'cockpit-t32-profile'),
- defaultViewport: { width: 1680, height: 950 },
- args: ['--no-sandbox', '--disable-dev-shm-usage', '--no-first-run', '--no-default-browser-check'],
- })
- console.log(`Chrome: ${await browser.version()}`)
- /** 打开一页,注入 `map.ui.debug` 指定的形态,等引擎就绪后把「界面清单」读回来。 */
- async function openWith(uiDebug, screenshotPath) {
- const page = await browser.newPage()
- const errors = []
- page.on('pageerror', (e) => errors.push(String(e && e.message ? e.message : e)))
- await page.evaluateOnNewDocument(
- (cfg, debug) => {
- window.__COCKPIT_CONFIG__ = {
- ...cfg,
- map: { ...cfg.map, intro: { enabled: false }, ui: { debug } },
- }
- },
- config,
- uiDebug,
- )
- await page.goto(ENTRY_URL, { waitUntil: 'networkidle2', timeout: 60000 })
- await page.waitForSelector('.cockpit-shell', { timeout: 20000 })
- await page.waitForFunction(
- () => document.querySelector('.cockpit-shell__map .map-canvas')?.dataset.mapEngine === 'ready',
- { timeout: 60000 },
- )
- await sleep(2500)
- if (screenshotPath) await page.screenshot({ path: screenshotPath })
- const inventory = await page.evaluate(() => {
- const pill = document.querySelector('[data-map-engine-status]')
- const pillStyle = pill ? getComputedStyle(pill) : null
- const statusLine = document.querySelector('.map-holo__status')
- const hostRect = document.querySelector('.cockpit-shell__map .map-canvas')?.getBoundingClientRect()
- const pillRect = pill ? pill.getBoundingClientRect() : null
- return {
- uiMode: pill ? pill.dataset.uiMode : '',
- // 票 33 起图例常驻一张图最上方,所以 `.map-holo__top` 在干净形态下也在;
- // 干净形态要收起的是它里面的**调试抬头**(标题 + 图层占位)。
- holoTop: document.querySelectorAll('.map-holo__top').length,
- headline: document.querySelectorAll('.map-holo__headline').length,
- placeholderTitle: document.querySelectorAll('.map-canvas__title').length,
- devHint: document.querySelectorAll('.map-holo__hint').length,
- configTag: document.querySelectorAll('.cockpit-shell__config').length,
- modeReadout: document.querySelectorAll('.cockpit-shell__meta').length,
- // 用户需要看的东西:颜色语义与图层控件
- legend: document.querySelectorAll('[data-map-legend] .status-tag').length,
- statusLine: document.querySelectorAll('.map-holo__status').length,
- statusDot: document.querySelectorAll('.map-holo__status i').length,
- homeButton: document.querySelectorAll('.cockpit-shell__home').length,
- viewButtons: document.querySelectorAll('.cockpit-shell__view-btn').length,
- scopeButtons: document.querySelectorAll('[data-scope]').length,
- // 引擎状态那一行的位置与观感
- pillBottomOffset: pillRect && hostRect ? Math.round(hostRect.bottom - pillRect.bottom) : null,
- pillTopOffset: pillRect && hostRect ? Math.round(pillRect.top - hostRect.top) : null,
- pillFontSize: pillStyle ? parseFloat(pillStyle.fontSize) : null,
- pillOpacity: pillStyle ? Number(pillStyle.color.match(/[\d.]+\)$/) ? pillStyle.color.match(/[\d.]+\s*\)/)[0].replace(')', '') : 1) : null,
- pillHasBackground: pillStyle ? pillStyle.backgroundColor !== 'rgba(0, 0, 0, 0)' : null,
- quietClass: statusLine ? statusLine.classList.contains('is-quiet') : null,
- statusFontSize: statusLine ? parseFloat(getComputedStyle(statusLine).fontSize) : null,
- pillText: pill ? pill.textContent.trim() : '',
- }
- })
- return { page, inventory, errors }
- }
- try {
- // ---------------- 生产干净形态 ----------------
- const prod = await openWith(false, OUT_PROD)
- const p = prod.inventory
- check(
- 'A1-prod-chrome-stripped',
- '干净形态:中央图上的调试抬头(标题/图层占位)、实例提示与顶栏诊断读数全部收起',
- p.headline === 0 && p.placeholderTitle === 0 && p.devHint === 0 && p.configTag === 0 && p.modeReadout === 0,
- JSON.stringify({ headline: p.headline, title: p.placeholderTitle, hint: p.devHint, configTag: p.configTag, mode: p.modeReadout }),
- )
- check(
- 'A2-prod-keeps-color-semantics',
- '干净形态**保留颜色语义**:点位状态图例、状态灯、图层控件与归位按钮都还在',
- p.legend > 0 && p.statusDot > 0 && p.statusLine > 0 && p.homeButton > 0 && p.viewButtons >= 2 && p.scopeButtons > 0,
- JSON.stringify({ legend: p.legend, dots: p.statusDot, home: p.homeButton, viewBtns: p.viewButtons, scopes: p.scopeButtons }),
- )
- check(
- 'A3-prod-pill-is-quiet-bottom',
- '引擎状态收紧成**右下角状态日志**:贴底、无底色、字号更小、颜色带透明度',
- p.uiMode === 'production' &&
- p.pillBottomOffset !== null &&
- p.pillBottomOffset <= 16 &&
- p.pillHasBackground === false &&
- p.pillFontSize <= 10 &&
- p.pillOpacity !== null &&
- p.pillOpacity < 1,
- JSON.stringify({ uiMode: p.uiMode, bottom: p.pillBottomOffset, bg: p.pillHasBackground, font: p.pillFontSize, alpha: p.pillOpacity }),
- )
- check(
- 'A4-prod-status-line-quiet',
- '底部摘要行同步收敛(is-quiet:字号更小、字色更淡)',
- p.quietClass === true && p.statusFontSize <= 10,
- JSON.stringify({ quietClass: p.quietClass, font: p.statusFontSize }),
- )
- check('A5-prod-no-errors', '干净形态无页面级报错', prod.errors.length === 0, JSON.stringify(prod.errors.slice(0, 3)))
- await prod.page.close()
- // ---------------- 调试形态(回归:既有走查依赖的选择器必须还在) ----------------
- const debug = await openWith(true, OUT_DEBUG)
- const d = debug.inventory
- check(
- 'A6-debug-keeps-everything',
- '调试形态保持原样:调试抬头/实例提示/配置标签/模式读数都在(票 04/09/21/29 走查依赖的选择器不变)',
- d.headline === 1 &&
- d.placeholderTitle === 1 &&
- d.devHint === 1 &&
- d.configTag === 1 &&
- d.modeReadout === 1 &&
- d.uiMode === 'debug',
- JSON.stringify({ headline: d.headline, title: d.placeholderTitle, hint: d.devHint, configTag: d.configTag, mode: d.modeReadout, uiMode: d.uiMode }),
- )
- check(
- 'A7-debug-pill-keeps-reading',
- '调试形态仍是可读的药丸(有底色、字号 11),位置让开顶部的图例与标题',
- d.pillHasBackground === true && d.pillFontSize === 11 && d.pillTopOffset !== null && d.pillTopOffset >= 80,
- JSON.stringify({ top: d.pillTopOffset, bg: d.pillHasBackground, font: d.pillFontSize }),
- )
- check(
- 'A8-both-forms-read-state',
- '两种形态都能读到引擎状态(形态只改呈现,不改可观测性)',
- p.pillText.includes('地形') && d.pillText.includes('地形'),
- JSON.stringify({ prod: p.pillText.slice(0, 40), debug: d.pillText.slice(0, 40) }),
- )
- check('A9-debug-no-errors', '调试形态无页面级报错', debug.errors.length === 0, JSON.stringify(debug.errors.slice(0, 3)))
- await debug.page.close()
- } finally {
- await browser.close()
- }
- const failed = results.filter((r) => !r.pass)
- console.log('')
- console.log(`T32 走查:${results.length - failed.length}/${results.length} 项通过。截图:干净 ${OUT_PROD} / 调试 ${OUT_DEBUG}`)
- if (failed.length) {
- console.log('未通过:' + failed.map((f) => f.id).join(', '))
- process.exit(1)
- }
- console.log('T32 走查全部通过。')
|