This commit is contained in:
oneao committed 2026-10-16 17:30:17 +08:00
1 parent e7c84a82a2
commit 025d3232c4
65 files changed
+9838 -9111

No files matched your search

@@ -0,0 +1,173 @@
import { chromium } from '@playwright/test'
/**
* 探针 v7 —— 排除「测量环境本身」造成的假象(只读诊断)。
*
* 必须回答的问题:v3~v6 的 ~12fps 是不是 headless Chromium 走软件光栅化
* (SwiftShader / 无 GPU 合成)导致的?如果是,那这个数字不能当成用户的真实体感。
*
* 做法:同一页面、同一套动作,对比
* · headless(默认)
* · headless + GPU 相关开关
* · headed(真实窗口,有 GPU)
* 并输出 compositor / GPU 信息。
*/
const BASE = process.env.PROBE_URL || 'http://127.0.0.1:5090'
const MENU_CODE = 'menu.table_test'
async function routePerms(context) {
await context.route('**/api/data/loaddata', async (route) => {
let viewName = ''
try {
viewName = JSON.parse(route.request().postData() || '').view_name || ''
} catch {
/* ignore */
}
const response = await route.fetch()
if (viewName !== 's_power' && viewName !== 's_user_power') return route.fulfill({ response })
let json
try {
json = await response.json()
} catch {
return route.fulfill({ response })
}
const rows = Array.isArray(json?.data) ? json.data : []
if (viewName === 's_power') {
if (!rows.some((r) => r.b_id === MENU_CODE)) rows.push({ b_id: MENU_CODE, b_canuse: 1 })
} else if (!rows.some((r) => r.b_power_id === MENU_CODE)) {
rows.push({ b_user_id: 'demo_zhangwei', b_power_id: MENU_CODE, b_canuse: 1 })
}
json.data = rows
await route.fulfill({
response,
body: JSON.stringify(json),
headers: { ...response.headers(), 'content-type': 'application/json' },
})
})
}
async function fps(page, { axis = 'y', steps = 120, px = 60 } = {}) {
return page.evaluate(
async ({ axis, steps, px }) => {
const cont = document.querySelector('.stk-table')
cont.scrollTop = 0
cont.scrollLeft = 0
await new Promise((r) => requestAnimationFrame(r))
const frames = []
let last = performance.now()
let stop = false
const tick = () => {
const now = performance.now()
frames.push(now - last)
last = now
if (!stop) requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
for (let i = 0; i < steps; i += 1) {
await new Promise((r) => requestAnimationFrame(r))
if (axis === 'y') {
const max = cont.scrollHeight - cont.clientHeight
cont.scrollTop = cont.scrollTop >= max - 1 ? 0 : Math.min(max, cont.scrollTop + px)
} else {
const max = cont.scrollWidth - cont.clientWidth
cont.scrollLeft = cont.scrollLeft >= max - 1 ? 0 : Math.min(max, cont.scrollLeft + px)
}
}
stop = true
const sorted = [...frames].sort((a, b) => a - b)
const avg = frames.reduce((a, b) => a + b, 0) / frames.length
const q = (arr, p) => arr[Math.min(arr.length - 1, Math.floor(arr.length * p))]
return { fps: +(1000 / avg).toFixed(1), avg: +avg.toFixed(1), p95: +q(sorted, 0.95).toFixed(1) }
},
{ axis, steps, px },
)
}
async function probeRun(label, launchOpts) {
let browser
try {
browser = await chromium.launch(launchOpts)
} catch (e) {
console.log(`${label}: 启动失败 ${String(e).slice(0, 120)}`)
return null
}
const context = await browser.newContext({ viewport: { width: 1600, height: 900 } })
await routePerms(context)
const page = await context.newPage()
// GPU / 合成器信息
let gpu = {}
try {
const client = await context.newCDPSession(page)
const info = await client.send('SystemInfo.getInfo')
gpu = {
devices: (info.gpu?.devices || []).map((d) => ({
vendor: d.vendorId,
device: d.deviceId,
devString: (d.deviceString || '').slice(0, 60),
})),
auxAttributes: info.gpu?.auxAttributes
? {
softwareRendering: info.gpu.auxAttributes.softwareRendering,
glRenderer: info.gpu.auxAttributes.glRenderer,
glVendor: info.gpu.auxAttributes.glVendor,
gpuCompositing: info.gpu.auxAttributes.gpuCompositing,
}
: null,
modelName: info.modelName,
}
await client.detach()
} catch (e) {
gpu = { error: String(e).slice(0, 100) }
}
await page.goto(`${BASE}/table-test`, { waitUntil: 'domcontentloaded' })
await page.waitForSelector('.stk-table', { timeout: 90000 })
await page.waitForTimeout(3000)
const y1 = await fps(page, { axis: 'y' })
const x1 = await fps(page, { axis: 'x', steps: 80, px: 80 })
const y2 = await fps(page, { axis: 'y' })
const webgl = await page.evaluate(() => {
try {
const c = document.createElement('canvas')
const gl = c.getContext('webgl') || c.getContext('experimental-webgl')
if (!gl) return { webgl: false }
const dbg = gl.getExtension('WEBGL_debug_renderer_info')
return {
webgl: true,
renderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER),
vendor: dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR),
}
} catch (e) {
return { error: String(e).slice(0, 80) }
}
})
console.log(`\n===== ${label} =====`)
console.log('GPU:', JSON.stringify(gpu))
console.log('WebGL:', JSON.stringify(webgl))
console.log(`纵向: ${JSON.stringify(y1)} / ${JSON.stringify(y2)}`)
console.log(`横向: ${JSON.stringify(x1)}`)
await browser.close()
return { label, y1, x1, y2 }
}
const results = []
results.push(await probeRun('A. headless(默认,探针前几版用的就是这个)', { headless: true }))
results.push(
await probeRun('B. headless + 强制 GPU', {
headless: true,
args: ['--enable-gpu', '--use-gl=angle', '--use-angle=d3d11', '--enable-unsafe-swiftshader'],
}),
)
results.push(await probeRun('C. headed(真实窗口)', { headless: false }))
console.log('\n================ 汇总 ================')
for (const r of results) {
if (!r) continue
console.log(`${r.label}\n 纵 ${r.y1.fps}/${r.y2.fps} fps 横 ${r.x1.fps} fps`)
}