import { chromium } from 'playwright-core' const BASE = process.env.FMS_BASE || 'http://localhost:5082' const SCROLL_MS = Number(process.env.SCROLL_MS || 3000) const ROUNDS = Number(process.env.ROUNDS || 2) const WHEEL_INTERVAL = Number(process.env.WHEEL_INTERVAL || 10) // ms 每次滚轮间隔(模拟快速滚轮) 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: true, args: [ '--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu-sandbox', '--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) { // 把鼠标移到表格上,确保滚轮事件落在表格滚动容器上 const rect = await page.evaluate(() => { const root = document.querySelector('.fms-table') const r = root?.getBoundingClientRect() return r ? { x: r.left + r.width / 2, y: r.top + Math.min(200, r.height / 2) } : null }) if (rect) await page.mouse.move(rect.x, rect.y) // 用真实滚轮事件驱动(transform 模式下 DOM scrollTop 恒为 0,程序化 scrollTop 无效) const wheelDriver = (async () => { const end = Date.now() + durationMs while (Date.now() < end) { await page.mouse.wheel(0, 100) await page.waitForTimeout(WHEEL_INTERVAL) } })() const result = await page.evaluate( async ({ durationMs }) => { let frames = 0 let minGap = Infinity let maxGap = 0 let prev = performance.now() const start = performance.now() const root0 = document.querySelector('.fms-table') const sc0 = root0 ? [...root0.querySelectorAll('*')].filter((el) => el.scrollHeight > el.clientHeight + 2)[0] : null const scrollTop0 = sc0?.scrollTop ?? 0 const vs0 = window.__vs0 await new Promise((resolve) => { const tick = (now) => { 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) }) const elapsed = (performance.now() - start) / 1000 const root = document.querySelector('.fms-table') const scroller = root ? [...root.querySelectorAll('*')].filter( (el) => el.scrollHeight > el.clientHeight + 2, )[0] : null return { fps: Math.round((frames / elapsed) * 10) / 10, avgFrameMs: Math.round(((elapsed * 1000) / frames) * 10) / 10, minGapMs: Math.round(minGap), maxGapMs: Math.round(maxGap), renderedRows: root?.querySelectorAll('.stk-tbody-main tr').length ?? 0, customThumb: root?.querySelectorAll('.stk-sb-thumb.vertical').length ?? 0, scrollerOverflow: scroller ? getComputedStyle(scroller).overflowY : null, scrolledBy: (scroller?.scrollTop ?? 0) - scrollTop0, } }, { durationMs }, ) await wheelDriver return result } for (let round = 1; round <= ROUNDS; round++) { console.log(`\n===== 第 ${round}/${ROUNDS} 轮 =====`) const idle = await measure(1200) console.log(`[静止] FPS=${idle.fps} avgFrame=${idle.avgFrameMs}ms rows=${idle.renderedRows} thumb=${idle.customThumb}`) const scroll = await measure(SCROLL_MS) console.log( `[滚轮滚动] FPS=${scroll.fps} avgFrame=${scroll.avgFrameMs}ms minGap=${scroll.minGapMs} maxGap=${scroll.maxGapMs} ` + `rows=${scroll.renderedRows} thumb=${scroll.customThumb} overflow=${scroll.scrollerOverflow}`, ) await page.evaluate(() => { const root = document.querySelector('.fms-table') const els = root ? [...root.querySelectorAll('*')] : [] const sc = els.filter((el) => el.scrollHeight > el.clientHeight + 2)[0] if (sc) sc.scrollTop = 0 }) await page.waitForTimeout(400) } await browser.close()