103 lines
3.3 KiB
JavaScript
103 lines
3.3 KiB
JavaScript
/**
|
||
* 通过 CDP 截图,用于无人值守环境下的界面功能验证。
|
||
*
|
||
* 用法:
|
||
* node scripts/shot.mjs <port> <outfile>
|
||
*/
|
||
|
||
import { writeFile } from 'node:fs/promises'
|
||
|
||
const port = Number(process.argv[2] ?? 9335)
|
||
const outFile = process.argv[3] ?? 'shot.png'
|
||
|
||
async function main() {
|
||
const listRes = await fetch(`http://127.0.0.1:${port}/json/list`)
|
||
const targets = await listRes.json()
|
||
const page = targets.find((t) => t.type === 'page')
|
||
if (!page) {
|
||
console.error('未找到 page 目标。当前目标:', targets.map((t) => `${t.type}:${t.url}`).join(', '))
|
||
process.exit(1)
|
||
}
|
||
console.log('目标页面:', page.url)
|
||
|
||
const ws = new WebSocket(page.webSocketDebuggerUrl)
|
||
let id = 0
|
||
const pending = new Map()
|
||
|
||
const send = (method, params = {}) =>
|
||
new Promise((resolve, reject) => {
|
||
const msgId = ++id
|
||
const timer = setTimeout(() => {
|
||
pending.delete(msgId)
|
||
reject(new Error(`CDP 调用超时: ${method}`))
|
||
}, 8000)
|
||
pending.set(msgId, (value) => {
|
||
clearTimeout(timer)
|
||
resolve(value)
|
||
})
|
||
ws.send(JSON.stringify({ id: msgId, method, params }))
|
||
})
|
||
|
||
await new Promise((resolve, reject) => {
|
||
const timer = setTimeout(() => reject(new Error('WebSocket 连接超时(8s)')), 8000)
|
||
ws.addEventListener('open', () => {
|
||
clearTimeout(timer)
|
||
resolve()
|
||
})
|
||
ws.addEventListener('error', (e) => {
|
||
clearTimeout(timer)
|
||
reject(new Error(`WebSocket 错误: ${e?.message ?? 'unknown'}`))
|
||
})
|
||
})
|
||
console.log('CDP 已连接')
|
||
|
||
ws.addEventListener('message', (ev) => {
|
||
const msg = JSON.parse(ev.data)
|
||
if (msg.id && pending.has(msg.id)) {
|
||
pending.get(msg.id)(msg.result)
|
||
pending.delete(msg.id)
|
||
}
|
||
})
|
||
|
||
await send('Page.enable')
|
||
// 等首屏与字体稳定
|
||
await new Promise((r) => setTimeout(r, 2500))
|
||
|
||
const metrics = await send('Runtime.evaluate', {
|
||
expression: `(() => {
|
||
const c = document.createElement('canvas')
|
||
const gl = c.getContext('webgl2') || c.getContext('webgl')
|
||
const dbg = gl && gl.getExtension('WEBGL_debug_renderer_info')
|
||
return JSON.stringify({
|
||
title: document.title,
|
||
theme: document.documentElement.dataset.theme,
|
||
hasApi: typeof window.ycode === 'object',
|
||
gpuRenderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : (gl ? 'webgl(no-debug-ext)' : 'NO WEBGL'),
|
||
bodyText: (document.body.innerText || '').slice(0, 400),
|
||
shell: !!document.querySelector('.app-shell'),
|
||
columns: document.querySelector('.app-shell')
|
||
? getComputedStyle(document.querySelector('.app-shell')).gridTemplateColumns : null,
|
||
composer: !!document.querySelector('.composer__input'),
|
||
sessionItems: document.querySelectorAll('.sidebar__item-title').length,
|
||
panels: document.querySelectorAll('.panel').length
|
||
})
|
||
})()`,
|
||
returnByValue: true
|
||
})
|
||
console.log('DOM 状态:', metrics?.result?.value)
|
||
|
||
const shot = await send('Page.captureScreenshot', { format: 'png' })
|
||
if (!shot?.data) {
|
||
console.error('截图失败')
|
||
process.exit(1)
|
||
}
|
||
await writeFile(outFile, Buffer.from(shot.data, 'base64'))
|
||
console.log('已保存:', outFile)
|
||
ws.close()
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error('异常:', err.message)
|
||
process.exit(1)
|
||
})
|