Files
mcn-short-video/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-footer.mjs
T

130 lines
6.1 KiB
JavaScript
Raw Normal View History

/* cdp-verify-footer.mjs — 验证账号列表渲染修复(09-01)
* 背景:上一轮误加裸赋值 accReset(严格模式 ReferenceError)+ list-footer 未包账号列表
* 验证:①账号列表页渲染成功(无 console 异常)②批量操作条与分页同一行(.list-footer 内)
* ③三列表均含 .list-footer ④批量模式切换正常
* 运行:node cdp-verify-footer.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 main() {
// 找 8900 标签页
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');
await cdp(ws, 'Log.enable');
const errors = [];
const onErr = (ev) => { const m = JSON.parse(ev.data); if (m.method === 'Runtime.exceptionThrown') errors.push(JSON.stringify(m.params.exceptionDetails).slice(0, 200)); };
const ws2 = new WebSocket(ws);
await new Promise((r) => { ws2.onopen = r; });
ws2.onmessage = onErr;
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=verify#/accounts' });
await sleep(2500);
const acc = await evalJs(ws, `(() => {
const root = document.querySelector('#view, main, .view, #app');
const page = root ? root.innerHTML : document.body.innerHTML;
return {
hasError: /页面渲染失败|not defined|ReferenceError/.test(page),
hasTable: !!document.querySelector('.table'),
hasRows: document.querySelectorAll('.table tbody tr').length,
footer: !!document.querySelector('.list-footer'),
footerBoth: !!document.querySelector('.list-footer .list-batchbar') && !!document.querySelector('.list-footer .pager'),
batchBtn: !!document.querySelector('[data-batch-open]'),
title: document.querySelector('.page-title')?.textContent || ''
};
})()`);
check('账号列表渲染无错误文案', !acc.hasError);
check('账号列表表格存在', acc.hasTable);
check('列表有数据行', acc.hasRows > 0);
check('.list-footer 存在(批量+分页同行容器)', acc.footer);
check('.list-footer 内含批量条和分页器', acc.footerBoth);
check('批量操作图标按钮存在', acc.batchBtn);
console.log(' 页面标题:', acc.title, '| 行数:', acc.hasRows);
// 点批量操作图标 → 验证批量模式 + 同行不破坏
await evalJs(ws, `document.querySelector('[data-batch-open]').click()`);
await sleep(600);
const bm = await evalJs(ws, `(() => ({
cbs: document.querySelectorAll('.batch-cb').length,
delBtn: !!document.querySelector('[data-batch-del]'),
cancelBtn: !!document.querySelector('[data-batch-cancel]'),
footerStill: !!document.querySelector('.list-footer .list-batchbar') && !!document.querySelector('.list-footer .pager'),
rowHasCb: !!document.querySelector('.table tbody .cb-cell input.batch-cb')
}))()`);
check('批量模式每行有勾选框', bm.cbs > 0 && bm.rowHasCb);
check('删除(trash)图标按钮出现', bm.delBtn);
check('取消(x)图标按钮出现', bm.cancelBtn);
check('批量模式下批量条+分页仍同行', bm.footerStill);
// 取消恢复
await evalJs(ws, `document.querySelector('[data-batch-cancel]').click()`);
await sleep(500);
const back = await evalJs(ws, `({ cbs: document.querySelectorAll('.batch-cb').length, footer: !!document.querySelector('.list-footer') })`);
check('取消后恢复常态(无勾选框)', back.cbs === 0);
check('取消后 footer 仍在', back.footer);
// 顺手验证另外两个列表的 footer
for (const [name, hash, sel] of [
['账号详情视频列表', '#/account/3', '.list-footer'],
['AI写脚本列表', '#/rewrites', '.list-footer']
]) {
await cdp(ws, 'Page.navigate', { url: 'http://localhost:8900/?v=verify' + hash });
await sleep(2000);
const r = await evalJs(ws, `(() => {
const f = document.querySelector('.list-footer');
return { f: !!f, both: f ? !!f.querySelector('.list-batchbar') && !!f.querySelector('.pager') : false, err: /页面渲染失败|not defined/.test(document.body.innerHTML) };
})()`);
check(name + ' .list-footer 存在', r.f);
check(name + ' 批量条+分页同行', r.both);
check(name + ' 无渲染错误', !r.err);
}
console.log('--- console 异常:', errors.length ? errors.join('\n') : '无');
if (errors.length) fail++;
check('无 console 异常', errors.length === 0);
console.log(`\n结果: ${pass} PASS / ${fail} FAIL`);
ws2.close();
process.exit(fail ? 1 : 0);
}
main().catch((e) => { console.error('ERROR:', e.message); process.exit(1); });