| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- /** T45 真机走查:重叠设备聚合与展开(票45 / 规格17)。 */
- 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-overlap'
- const results = []
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
- function check(id, pass, evidence) {
- results.push({ id, pass })
- console.log(`${pass ? 'PASS' : 'FAIL'} ${id} :: ${evidence || ''}`)
- }
- 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 })
- 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 sleep(1200)
- // 推进地图摘要到 complete。
- 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 state = dev && dev.mapSummary ? dev.mapSummary.getState() : null
- return !state || ['complete', 'empty', 'failed', 'expired'].includes(state.phase)
- }, { timeout: 20000 }).catch(() => {})
- await page.evaluate(() => window.__cockpitMap && window.__cockpitMap.selectMonitoringPoint && window.__cockpitMap.selectMonitoringPoint('MP-001'))
- await page.waitForSelector('[data-device-layer-list] [data-device-layer-row]', { timeout: 20000 }).catch(() => {})
- await sleep(2500)
- const read = () => page.evaluate(() => {
- const host = document.querySelector('.cockpit-shell__map .map-canvas')
- const dev = window.__cockpitMap
- const devices = dev && dev.getAssociatedDevices ? dev.getAssociatedDevices() : { entities: [], overlap: null, onMap: 0 }
- return {
- overlap: dev && dev.getOverlapState ? dev.getOverlapState() : null,
- targets: dev && dev.getOverlapTargets ? dev.getOverlapTargets() : [],
- entities: devices.entities || [],
- onMap: devices.onMap,
- mapPicked: host && host.dataset.mapPicked,
- devicePicked: host && host.dataset.mapDevicePicked,
- panelSelection: host && host.dataset.mapPanelSelection,
- deviceHovered: host && host.dataset.mapDeviceHovered,
- correspondence: dev && dev.getDeviceCorrespondence ? dev.getDeviceCorrespondence() : null,
- detailLevel: (document.querySelector('[data-point-detail]') || { dataset: {} }).dataset.detailLevel || '',
- rows: [...document.querySelectorAll('[data-device-layer-row]')].length,
- }
- })
- const collapsed = await read()
- const aggregate = collapsed.entities.filter((e) => e.overlapAggregate)
- const hiddenDevices = collapsed.entities.filter((e) => e.deviceCode && e.visible === false)
- check('W1-collapsed-has-aggregate',
- collapsed.overlap && collapsed.overlap.state === 'collapsed' && collapsed.overlap.count === 2 && aggregate.length === 1,
- `state=${collapsed.overlap && collapsed.overlap.state} K=${collapsed.overlap && collapsed.overlap.count} 聚合实体=${aggregate.length}`)
- check('W2-aggregate-has-no-device-identity',
- aggregate.length === 1 && !aggregate[0].deviceCode && aggregate[0].overlapCount === 2,
- `聚合实体 deviceCode=${aggregate[0] && aggregate[0].deviceCode || '(空)'} count=${aggregate[0] && aggregate[0].overlapCount}`)
- check('W3-collapsed-real-devices-hidden',
- hiddenDevices.length === 2 && collapsed.onMap === 0,
- `隐藏逐台设备=${hiddenDevices.length} onMap=${collapsed.onMap}`)
- check('W4-list-keeps-all-devices', collapsed.rows === 2, `清单行数=${collapsed.rows}`)
- const aggregateTarget = collapsed.targets.find((target) => target.aggregate && target.visible)
- check('W5-aggregate-projects-on-screen',
- Boolean(aggregateTarget && Number.isFinite(aggregateTarget.x) && Number.isFinite(aggregateTarget.y)),
- aggregateTarget ? `窗口坐标 ${aggregateTarget.x.toFixed(1)},${aggregateTarget.y.toFixed(1)}` : '聚合实体未投影到视窗')
- if (aggregateTarget) {
- await page.evaluate(() => {
- const detail = document.querySelector('.cockpit-shell__detail')
- if (detail) {
- detail.style.pointerEvents = 'none'
- detail.querySelectorAll('*').forEach((child) => { child.style.pointerEvents = 'none' })
- }
- })
- // 真实鼠标 hover:瞬态预览,离开即回折叠(不写选中态)。
- await page.mouse.move(aggregateTarget.x, aggregateTarget.y)
- await sleep(350)
- const preview = await read()
- check('W6-real-hover-preview',
- preview.overlap && preview.overlap.preview === true && preview.overlap.expanded === false,
- `真实pointermove后 preview=${preview.overlap && preview.overlap.preview} expanded=${preview.overlap && preview.overlap.expanded}`)
- await page.mouse.move(1200, 500)
- await sleep(100)
- await page.mouse.move(1500, 750)
- await sleep(350)
- const left = await read()
- check('W7-real-mouseleave-collapses',
- left.overlap && left.overlap.state === 'collapsed' && left.overlap.preview === false,
- `离开后 state=${left.overlap && left.overlap.state} preview=${left.overlap && left.overlap.preview}`)
- // 真实鼠标 click 聚合符号:粘性展开。
- const targetAgain = left.targets.find((target) => target.aggregate && target.visible) || aggregateTarget
- await page.mouse.move(1200, 500)
- await sleep(100)
- await page.mouse.click(targetAgain.x, targetAgain.y)
- await sleep(500)
- const expanded = await read()
- const visibleDevices = expanded.entities.filter((e) => e.deviceCode && e.visible)
- const expandedAggregate = expanded.entities.filter((e) => e.overlapAggregate && e.visible)
- check('W8-real-click-sticky-expands',
- expanded.overlap && expanded.overlap.state === 'expanded' && expanded.overlap.expanded === true,
- `真实click后 state=${expanded.overlap && expanded.overlap.state} expanded=${expanded.overlap && expanded.overlap.expanded}`)
- check('W9-expanded-shows-device-identities',
- visibleDevices.length === 2 && visibleDevices.every((e) => e.deviceCode) && expandedAggregate.length === 1,
- `可见设备=${visibleDevices.map((e) => e.deviceCode).join('/')} 聚合可见=${expandedAggregate.length}`)
- check('W10-expanded-screen-offsets-preserve-space',
- visibleDevices.every((e) => e.pixelOffset && (e.pixelOffset.x !== 0 || e.pixelOffset.y !== 0)),
- `pixelOffset=${visibleDevices.map((e) => `${e.deviceCode}:${e.pixelOffset && e.pixelOffset.x},${e.pixelOffset && e.pixelOffset.y}`).join(' ')}`)
- // 真实鼠标 click 展开子设备:普通拾取返回真实设备身份,设备优先拾取生效。
- const deviceTarget = expanded.targets.find((target) => target.deviceCode === 'EQ-001' && target.visible)
- if (deviceTarget) {
- await page.mouse.move(1200, 500)
- await sleep(150)
- await page.mouse.click(deviceTarget.x, deviceTarget.y)
- }
- await sleep(600)
- const picked = await read()
- check('W11-real-pick-expanded-device',
- Boolean(deviceTarget) && picked.devicePicked === 'EQ-001' && picked.panelSelection === 'equipment',
- `设备目标=${Boolean(deviceTarget)} picked=${picked.devicePicked} panel=${picked.panelSelection}`)
- // 面板返回点位结束展开(走 store 层级回退入口,与票 43 同一用户路径)。
- await page.evaluate(() => window.__cockpitMap && window.__cockpitMap.backToPointLevel && window.__cockpitMap.backToPointLevel())
- await sleep(450)
- const backed = await read()
- check('W12-back-to-point-collapses',
- backed.overlap && backed.overlap.state === 'collapsed' && !backed.overlap.expanded,
- `返回点位后 state=${backed.overlap && backed.overlap.state}`)
- // 重新真实点击聚合后点空白,展开也应收起。
- const aggregateAfterBack = backed.targets.find((target) => target.aggregate && target.visible)
- if (aggregateAfterBack) {
- await page.mouse.click(aggregateAfterBack.x, aggregateAfterBack.y)
- await sleep(300)
- // 纯只读探针:确认空白点落在 canvas 上(不被面板/浮层覆盖),不触发任何拾取。
- const blankProbe = await page.evaluate(() => {
- const el = document.elementFromPoint(700, 800)
- const host = document.querySelector('.cockpit-shell__map .map-canvas')
- window.__t45BlankPointer = { down: 0, up: 0 }
- if (host) {
- host.addEventListener('pointerdown', () => { window.__t45BlankPointer.down += 1 })
- host.addEventListener('pointerup', () => { window.__t45BlankPointer.up += 1 })
- }
- return { element: el ? `${el.tagName}.${String(el.className).replace(/ /g, '.')}` : '' }
- })
- await page.mouse.move(700, 800)
- await sleep(120)
- await page.mouse.click(700, 800)
- await sleep(400)
- const blankReachedCanvas = await page.evaluate(() => window.__t45BlankPointer.down === 1 && window.__t45BlankPointer.up === 1)
- const blank = await read()
- check('W13-blank-click-collapses',
- Boolean(aggregateAfterBack) && blankReachedCanvas === true && blank.overlap && blank.overlap.state === 'collapsed' && !blank.overlap.expanded,
- `空白click后 state=${blank.overlap && blank.overlap.state}(canvas命中=${blankReachedCanvas})`)
- }
- // 预警处置模式:分项切换按票46语义会清空选择;重新选择MP-001后再验证状态过滤。
- await page.evaluate(() => {
- const button = [...document.querySelectorAll('.cockpit-shell__mode')][3]
- if (button) button.click()
- })
- await sleep(900)
- await page.evaluate(() => window.__cockpitMap && window.__cockpitMap.selectMonitoringPoint && window.__cockpitMap.selectMonitoringPoint('MP-001'))
- await page.waitForSelector('[data-device-layer-list] [data-device-layer-row]', { timeout: 15000 }).catch(() => {})
- await sleep(900)
- const warning = await read()
- const warningDevices = warning.entities.filter((e) => e.deviceCode && e.visible).map((e) => e.deviceCode)
- const warningAggregates = warning.entities.filter((e) => e.overlapAggregate && e.visible)
- check('W15-warning-mode-filters-device-status',
- !warningDevices.includes('EQ-002') && (warningDevices.includes('EQ-001') || warningAggregates.length === 1),
- `预警模式可见设备=${warningDevices.join('/') || '无'} 聚合=${warningAggregates.length} K=${warning.overlap && warning.overlap.count}`)
- }
- const finalFields = await read()
- const legacyFields = [finalFields.mapPicked, finalFields.devicePicked, finalFields.panelSelection, finalFields.deviceHovered]
- check('W14-existing-selection-fields-preserved',
- legacyFields.every((value) => value === undefined || typeof value === 'string'),
- `mapPicked=${finalFields.mapPicked || '(未初始化)'} devicePicked=${finalFields.devicePicked || '(未初始化)'} panel=${finalFields.panelSelection || '(未初始化)'} hovered=${finalFields.deviceHovered || '(未初始化)'}`)
- check('W16-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 真机走查全部通过。')
|