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()