Files
mcn-short-video/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-batch-e2e.mjs
T

91 lines
4.4 KiB
JavaScript
Raw Normal View History

/* 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); });