Files
workspace/code/fms/.fps-probe/measure-wheel-js.mjs
T
2026-08-20 17:30:56 +08:00

121 lines
4.9 KiB
JavaScript

import { chromium } from 'playwright-core'
// JS 派发 wheel 事件驱动滚动(与真实滚轮走同一 onTableWheel → scrollTop += deltaY 路径,
// 不受 CDP 输入合并影响;transform 滚动模式下 DOM scrollTop 恒为 0 也可用)
const BASE = process.env.FMS_BASE || 'http://localhost:5082'
const DURATION_MS = Number(process.env.DURATION_MS || 3000)
const WHEEL_DELTA = Number(process.env.WHEEL_DELTA || 100)
const WHEEL_INTERVAL_MS = Number(process.env.WHEEL_INTERVAL_MS || 8)
const chromePath =
process.env.CHROME_PATH || 'C:/Program Files/Google/Chrome/Application/chrome.exe'
const seedStorage = `
sessionStorage.setItem('fms-login', JSON.stringify({
loginInfo: { token: 'probe-token', user: { id: 1, account: 'admin', name: '探针用户' } },
}));
localStorage.setItem('fms-user', JSON.stringify({ userInfo: { orgId: 'PROBE', account: 'admin' } }));
localStorage.setItem('app', JSON.stringify({ darkMode: false }));
`
const browser = await chromium.launch({
executablePath: chromePath,
headless: process.env.HEADED !== '1',
args: [
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu-sandbox',
...(process.env.HEADED === '1' ? ['--disable-background-timer-throttling'] : []),
],
})
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } })
await context.addInitScript(seedStorage)
const page = await context.newPage()
page.on('pageerror', (err) => console.log('[pageerror]', String(err).slice(0, 300)))
console.log(`打开 ${BASE}/dashboard ...`)
await page.goto(`${BASE}/dashboard`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await page.waitForSelector('.fms-table', { timeout: 20000 })
await page.waitForFunction(() => document.querySelectorAll('.stk-tbody-main tr').length > 0)
await page.waitForTimeout(1200)
async function measure(durationMs, drive) {
return page.evaluate(
async ({ durationMs, drive, delta, intervalMs }) => {
const root = document.querySelector('.fms-table')
// wheel 监听器绑定在 .stk-table 滚动容器上;transform 模式下无纵向溢出,直接用容器派发
const sc =
[...root.querySelectorAll('*')].filter(
(el) => el.scrollHeight > el.clientHeight + 2 && getComputedStyle(el).overflowY !== 'hidden',
)[0] || root.querySelector('.stk-table')
const maxScroll = sc ? sc.scrollHeight - sc.clientHeight : 0
if (sc) sc.scrollTop = 0
let wheelEvents = 0
let scrollEvents = 0
let frames = 0
let minGap = Infinity
let maxGap = 0
let prev = performance.now()
const start = performance.now()
const scrollListener = () => scrollEvents++
if (sc) sc.addEventListener('scroll', scrollListener)
await new Promise((resolve) => {
const tick = (now) => {
// 每帧同步派发滚轮事件(不受 setInterval 节流影响),等价于高频真实滚轮
if (drive === 'wheel' && sc) {
sc.dispatchEvent(
new WheelEvent('wheel', { deltaY: delta, deltaMode: 0, cancelable: true, bubbles: true }),
)
wheelEvents++
}
const gap = now - prev
prev = now
frames++
if (gap < minGap) minGap = gap
if (gap > maxGap) maxGap = gap
if (now - start < durationMs) requestAnimationFrame(tick)
else resolve()
}
requestAnimationFrame(tick)
})
if (sc) sc.removeEventListener('scroll', scrollListener)
const elapsed = (performance.now() - start) / 1000
const tbodyTransform = root.querySelector('.stk-tbody-main')?.style.transform || ''
return {
scene: drive,
fps: Math.round((frames / elapsed) * 10) / 10,
avgFrameMs: Math.round(((elapsed * 1000) / frames) * 10) / 10,
minGapMs: Math.round(minGap),
maxGapMs: Math.round(maxGap),
wheelEvents,
scrollEvents,
scrollTop: sc?.scrollTop ?? 0,
renderedRows: root.querySelectorAll('.stk-tbody-main tr').length,
customThumb: root.querySelectorAll('.stk-sb-thumb.vertical').length,
tbodyTransform,
}
},
{ durationMs, drive, delta: WHEEL_DELTA, intervalMs: WHEEL_INTERVAL_MS },
)
}
for (let round = 1; round <= 2; round++) {
console.log(`\n===== 第 ${round}/2 轮 =====`)
const idle = await measure(1200, 'idle')
console.log(
`[静止] FPS=${idle.fps} avgFrame=${idle.avgFrameMs}ms rows=${idle.renderedRows} thumb=${idle.customThumb}`,
)
const scroll = await measure(DURATION_MS, 'wheel')
console.log(
`[滚轮] FPS=${scroll.fps} avgFrame=${scroll.avgFrameMs}ms minGap=${scroll.minGapMs} maxGap=${scroll.maxGapMs} ` +
`wheelEvt=${scroll.wheelEvents} scrollEvt=${scroll.scrollEvents} scrollTop=${scroll.scrollTop} ` +
`rows=${scroll.renderedRows} thumb=${scroll.customThumb} transform=${scroll.tbodyTransform.slice(0, 40)}`,
)
await page.waitForTimeout(400)
}
await browser.close()