Files
mcn-short-video/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-personacard.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

148 lines
6.9 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.
// 验证「账号设定」卡片版/文字版切换(账号详情页 persona tab)
// 用例1:旧梦留声机(1629) 有卡片 → 默认卡片版 + 可切换文字版
// 用例2:王微斯(1627) 无卡片 → 直接文字版,无切换控件
const list = await fetch('http://localhost:9223/json').then((r) => r.json());
let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899'));
if (!page) { console.log('ERR: no workbuddy page open'); process.exit(1); }
const ws = new WebSocket(page.webSocketDebuggerUrl);
let id = 0; const pending = new Map();
const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); });
ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } };
await new Promise((r) => (ws.onopen = r));
const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 300) }; return r.result.value; };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
await send('Page.enable'); await send('Runtime.enable');
const shot = async (name) => {
try {
const r = await send('Page.captureScreenshot', { format: 'png' });
const fs = await import('node:fs');
fs.writeFileSync('shot-' + name + '.png', Buffer.from(r.data, 'base64'));
console.log('截图:', name);
} catch (e) { console.log('截图失败(跳过):', e.message); }
};
const goto = async (url) => {
await send('Page.navigate', { url });
await sleep(2500);
await send('Page.reload', { ignoreCache: true }); // hash 路由 navigate 不重载文档,强制刷新加载最新 JS
await sleep(3000);
};
// ---- 用例1:旧梦留声机(有卡片)----
console.log('=== 用例1 旧梦留声机 1629 ===');
await goto('http://localhost:8899/#/account/1629');
// 点击「账号设定」tab
const tabClick = await ev(`(() => {
const b = document.querySelector('[data-tab="persona"]');
if (!b) return { err: 'no persona tab' };
b.click();
return 'clicked persona tab';
})()`);
console.log('切tab:', JSON.stringify(tabClick));
await sleep(1800); // 等 loadPersona 两个 fetch 完成
const s1 = await ev(`(() => {
const sw = document.querySelector('.persona-vs-icons');
const card = document.querySelector('.card-view');
const md = document.querySelector('.persona-text');
const img = card && card.querySelector('img');
const btns = sw ? [...sw.querySelectorAll('.vi-btn')] : [];
const active = btns.find((x) => x.classList.contains('vi-active'));
const cardIcon = btns[0] && btns[0].querySelector('svg path');
return {
hasIcons: !!sw,
btnTitles: btns.map((x) => x.title),
activeTitle: active ? active.title : null,
cardIconIsImage: cardIcon ? cardIcon.getAttribute('d').includes('m21 15-3.086') : null,
cardHidden: card ? card.classList.contains('vs-hidden') : null,
cardImgSrc: img ? img.getAttribute('src').slice(0, 60) : null,
mdHidden: md ? md.classList.contains('vs-hidden') : null,
mdLen: md ? md.textContent.length : 0,
};
})()`);
console.log('默认卡片版状态:', JSON.stringify(s1));
// 切到文字版
await ev(`(() => { const b = document.querySelector('[data-vs="text"]'); if (b) b.click(); return 1; })()`);
await sleep(400);
const s2 = await ev(`(() => {
const sw = document.querySelector('.persona-vs-icons');
const card = document.querySelector('.card-view');
const md = document.querySelector('.persona-text');
const active = sw && [...sw.querySelectorAll('.vi-btn')].find((x) => x.classList.contains('vi-active'));
return {
activeTitle: active ? active.title : null,
activeCount: sw ? [...sw.querySelectorAll('.vi-btn.vi-active')].length : null,
cardHidden: card ? card.classList.contains('vs-hidden') : null,
mdHidden: md ? md.classList.contains('vs-hidden') : null,
};
})()`);
console.log('切文字版后:', JSON.stringify(s2));
// 切回卡片版
await ev(`(() => { const b = document.querySelector('[data-vs="card"]'); if (b) b.click(); return 1; })()`);
await sleep(400);
const s3 = await ev(`(() => {
const card = document.querySelector('.card-view');
const md = document.querySelector('.persona-text');
const active = document.querySelector('.vi-btn.vi-active');
return {
activeTitle: active ? active.title : null,
activeCount: document.querySelectorAll('.vi-btn.vi-active').length,
cardHidden: card ? card.classList.contains('vs-hidden') : null,
mdHidden: md ? md.classList.contains('vs-hidden') : null,
};
})()`);
console.log('切回卡片版后:', JSON.stringify(s3));
// ---- 用例2:王微斯(无卡片)----
console.log('=== 用例2 王微斯 1627(无卡片)===');
await goto('http://localhost:8899/#/account/1627');
await ev(`(() => { const b = document.querySelector('[data-tab="persona"]'); if (b) b.click(); return 1; })()`);
await sleep(1800);
const s4 = await ev(`(() => {
const sw = document.querySelector('.persona-vs-icons');
const md = document.querySelector('.persona-text');
return {
hasIcons: !!sw,
mdExists: !!md,
mdHidden: md ? md.classList.contains('vs-hidden') : null,
mdLen: md ? md.textContent.length : 0,
};
})()`);
console.log('无卡片账号状态:', JSON.stringify(s4));
// 回到用例1 页面截图卡片版效果(最后统一截图,失败不中断)
await goto('http://localhost:8899/#/account/1629');
await ev(`(() => { const b = document.querySelector('[data-tab="persona"]'); if (b) b.click(); return 1; })()`);
await sleep(1800);
// 多视口宽度自检:验证图片是否随内容区自适应(不应超出 viewport 引发横向滚动)
for (const vw of [1280, 1024, 900, 1920, 2560]) {
await send('Emulation.setDeviceMetricsOverride', { width: vw, height: 800, deviceScaleFactor: 1, mobile: false });
await sleep(500);
const info = await ev(`(() => {
const img = document.querySelector('.card-view img');
const body = document.querySelector('.detail-body');
const wrap = document.querySelector('.persona-wrap');
const page = document.querySelector('.page-detail');
return {
viewport: ${vw},
docScrollW: document.documentElement.scrollWidth,
bodyW: body ? Math.round(body.getBoundingClientRect().width) : null,
wrapW: wrap ? Math.round(wrap.getBoundingClientRect().width) : null,
imgW: img ? Math.round(img.getBoundingClientRect().width) : null,
pageW: page ? Math.round(page.getBoundingClientRect().width) : null,
bodyOverflow: body ? getComputedStyle(body).overflowX : null,
};
})()`);
console.log('视口', vw, ':', JSON.stringify(info));
}
await send('Emulation.clearDeviceMetricsOverride');
await sleep(300);
await shot('persona-card-final');
console.log('DONE');
ws.close();
process.exit(0);