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 分钟超时,用户零感知)
148 lines
6.9 KiB
JavaScript
148 lines
6.9 KiB
JavaScript
// 验证「账号设定」卡片版/文字版切换(账号详情页 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);
|