Files
mcn-short-video/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-addr.mjs
T
maogeigei f77735b206 工作台:目录改名 mcn-workshop + 任务进度可见 + 会话归属配置化
1. 目录 mcn-work-shop -> mcn-workshop(空间分组名取会话 cwd 的目录名,只改字符串无效,必须真改名)
2. 全仓替换 mcn-work-shop -> mcn-workshop:37 文件 121 处;历史日志按沿革句规矩保留当时目录名
3. 任务进度可见:/api/run/status 产出 已运行时长/工具调用/最近动作/停滞判定(读会话日志尾部),前端新增右下角常驻面板,四处任务入口接入
4. 会话归属目录配置化:新增 config.sessionCwd(留空=工作台自身目录),现指向 D://AI技能//mcn-workshop,使任务显示为命名分组而非「未分组任务」
5. 修前端轮询静默缺陷:连续 3 次查询失败即提示服务断开(原逻辑静默空转到 15 分钟超时,用户零感知)
2026-10-08 13:09:06 +08:00

91 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* cdp-verify-addr.mjs — 验证视频详情页:工具栏标题去重(静态"视频详情") + 第一行改为"视频地址"(点击打开视频) + 2/3行四字段等宽(09-01) */
const CDP = 'http://127.0.0.1:9223';
async function getWs(targetId) {
const tabs = await (await fetch(CDP + '/json')).json();
const t = tabs.find((x) => x.id === targetId);
if (!t) throw new Error('target not found: ' + targetId);
return t.webSocketDebuggerUrl;
}
function cdp(ws, method, params = {}) {
return new Promise((resolve, reject) => {
const id = ++cdp._id;
const w = new WebSocket(ws);
const timer = setTimeout(() => { try { w.close(); } catch (e) {} reject(new Error('timeout: ' + method)); }, 30000);
w.onopen = () => w.send(JSON.stringify({ id, method, params }));
w.onmessage = (ev) => {
const m = JSON.parse(ev.data);
if (m.id !== id) return;
clearTimeout(timer);
try { w.close(); } catch (e) {}
m.error ? reject(new Error(method + ': ' + JSON.stringify(m.error))) : resolve(m.result);
};
w.onerror = () => { clearTimeout(timer); reject(new Error('ws error: ' + method)); };
});
}
cdp._id = 0;
async function evalJs(ws, expr) {
const r = await cdp(ws, 'Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true });
if (r.exceptionDetails) throw new Error('eval err: ' + JSON.stringify(r.exceptionDetails).slice(0, 300));
return r.result && r.result.value;
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const waitFor = async (ws, expr, timeout = 15000, label = '') => {
const t0 = Date.now();
while (Date.now() - t0 < timeout) {
if (await evalJs(ws, expr)) return true;
await sleep(300);
}
throw new Error('waitFor timeout: ' + label);
};
async function main() {
const tabs = await (await fetch(CDP + '/json')).json();
const tab = tabs.find((x) => x.url.includes('localhost:8900')) || tabs.find((x) => x.type === 'page');
if (!tab) throw new Error('no 8900 tab found');
const ws = await getWs(tab.id);
await cdp(ws, 'Page.enable');
await cdp(ws, 'Runtime.enable');
let pass = 0, fail = 0;
const check = (name, cond) => { cond ? pass++ : fail++; console.log((cond ? 'PASS' : 'FAIL') + ' | ' + name); };
await cdp(ws, 'Page.navigate', { url: 'http://localhost:8900/?v=addr#/video/680' });
await waitFor(ws, `!!document.querySelector('.video-info')`, 15000, '视频详情渲染');
const r = await evalJs(ws, `(() => {
const card = document.querySelector('.video-info');
const rows = card ? [...card.querySelectorAll('.info-row')] : [];
const rowLabels = rows.map((row) => [...row.querySelectorAll('.info-label')].map((l) => l.textContent.trim()));
const firstItem = rows[0] ? rows[0].querySelector('.info-item') : null;
const firstLink = firstItem ? firstItem.querySelector('a.link') : null;
const widths = rows.slice(1).map((row) => [...row.querySelectorAll('.info-item')].map((it) => Math.round(it.getBoundingClientRect().width)));
const pageTitle = document.querySelector('.page-toolbar .page-title');
const hasRenderErr = document.body.textContent.includes('渲染失败');
return {
rowCount: rows.length,
rowLabels,
firstLabel: firstItem ? firstItem.querySelector('.info-label').textContent.trim() : '',
firstHref: firstLink ? firstLink.getAttribute('data-ext') : null,
firstText: firstItem ? firstItem.querySelector('.info-value').textContent.trim().slice(0, 40) : '',
pageTitle: pageTitle ? pageTitle.textContent.trim() : '',
widths,
hasRenderErr
};
})()`);
check('工具栏标题为静态「视频详情」(非视频标题)', r.pageTitle === '视频详情');
check('信息卡共 4 行', r.rowCount === 4);
check('第1行 label=视频地址(' + r.firstLabel + ')', r.firstLabel === '视频地址');
check('第1行值为可点击链接且指向视频 URL', !!r.firstHref && r.firstHref.startsWith('http'));
check('第2行=账号/视频ID/时长/发布时间', JSON.stringify(r.rowLabels[1]) === JSON.stringify(['账号', '视频ID', '时长', '发布时间']));
check('第3行=点赞/评论/分享/收藏', JSON.stringify(r.rowLabels[2]) === JSON.stringify(['点赞', '评论', '分享', '收藏']));
check('第4行=标签', JSON.stringify(r.rowLabels[3]) === JSON.stringify(['标签']));
check('第2行四字段等宽(' + r.widths[0] + ')', new Set(r.widths[0]).size === 1);
check('第3行四字段等宽(' + r.widths[1] + ')', new Set(r.widths[1]).size === 1);
check('无渲染失败', !r.hasRenderErr);
console.log(`\n结果: ${pass} PASS / ${fail} FAIL`);
process.exit(fail ? 1 : 0);
}
main().catch((e) => { console.error('ERROR:', e.message); process.exit(1); });