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 分钟超时,用户零感知)
92 lines
4.4 KiB
JavaScript
92 lines
4.4 KiB
JavaScript
/* cdp-verify-batch-e2e.mjs — 批量删除端到端验证(UI 点击删除 → confirm → API → 列表刷新 → 库清理)
|
|
* 用测试视频(标题 __batch_e2e__)走完整链路,删除后验证库中无残留
|
|
* 方法:CDP 9223 → 8900 标签页 → #/account/3 视频列表 → 批量模式 → 勾选测试视频 → hook confirm 返回 true → 点删除
|
|
* 运行:node cdp-verify-batch-e2e.mjs
|
|
*/
|
|
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));
|
|
async function waitFor(ws, expr, timeout = 40000, label = '') {
|
|
const t0 = Date.now();
|
|
while (Date.now() - t0 < timeout) {
|
|
try { if (await evalJs(ws, expr)) return true; } catch (e) {}
|
|
await sleep(800);
|
|
}
|
|
throw new Error('waitFor timeout: ' + label + ' -> ' + expr);
|
|
}
|
|
|
|
async function main() {
|
|
const tabs = await (await fetch(CDP + '/json')).json();
|
|
const wb = tabs.find((t) => t.url.includes('localhost:8900') || t.url.includes('127.0.0.1:8900'));
|
|
if (!wb) throw new Error('未找到 8900 工作台标签页');
|
|
const ws = await getWs(wb.id);
|
|
console.log('▶ 复用标签页:', wb.url);
|
|
|
|
// 进账号详情视频列表(测试视频挂在俊希 id=3 下)
|
|
await evalJs(ws, `location.hash = '#/account/3'`);
|
|
await cdp(ws, 'Page.reload', { ignoreCache: true });
|
|
await waitFor(ws, `!!document.querySelector('tbody tr .td-id a')`, 40000, '视频列表渲染');
|
|
await sleep(400);
|
|
|
|
// 进批量模式
|
|
await evalJs(ws, `document.querySelector('[data-batch-open]').click()`);
|
|
await waitFor(ws, `!!document.querySelector('.batch-cb')`, 20000, '批量模式');
|
|
await sleep(300);
|
|
|
|
// 定位测试视频行(标题 __batch_e2e__)并勾选
|
|
const found = await evalJs(ws, `(() => {
|
|
const rows = [...document.querySelectorAll('tbody tr')];
|
|
const tr = rows.find((r) => r.textContent.includes('__batch_e2e__'));
|
|
if (!tr) return false;
|
|
tr.querySelector('.batch-cb').click();
|
|
return true;
|
|
})()`);
|
|
if (!found) throw new Error('未找到测试视频行 __batch_e2e__');
|
|
await sleep(200);
|
|
const cnt = await evalJs(ws, `document.querySelector('.batch-count-n').textContent`);
|
|
console.log('勾选测试视频后计数:', cnt);
|
|
if (cnt !== '1') throw new Error('计数异常: ' + cnt);
|
|
|
|
// hook confirm 返回 true,点击删除所选
|
|
await evalJs(ws, `window.confirm = () => true; true`);
|
|
await evalJs(ws, `document.querySelector('[data-batch-del]').click()`);
|
|
// 等待 toast 出现(删除完成提示)或列表刷新后批量模式退出
|
|
await sleep(1500);
|
|
const after = await evalJs(ws, `(() => ({
|
|
hasCbs: document.querySelectorAll('.batch-cb').length > 0, // 批量模式应退出(false)
|
|
hasOpen: !!document.querySelector('[data-batch-open]'), // 恢复常态图标
|
|
hasTestRow: [...document.querySelectorAll('tbody tr')].some((r) => r.textContent.includes('__batch_e2e__')), // 测试行应消失
|
|
}))()`);
|
|
console.log('删除后状态:', JSON.stringify(after));
|
|
console.log((!after.hasCbs && after.hasOpen && !after.hasTestRow) ? '✅ 端到端删除 PASS(批量模式退出+测试行消失)' : '❌ FAIL');
|
|
process.exit(after.hasCbs || !after.hasOpen || after.hasTestRow ? 1 : 0);
|
|
}
|
|
main().catch((e) => { console.error('脚本异常:', e.message); process.exit(1); });
|