chore: establish YCode replication baseline and TODO
This commit is contained in:
commit
6dc59f89ba
1118 files changed
+196002
No files matched your search
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 通过 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)
|
||||
})
|
||||
Reference in new issue
Block a user