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

87 lines
5.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.
// R28 冒烟:视频详情页——去「视频分析」tab;未解析=空态「解析」按钮;已解析=内容右上「刷新」按钮
const list = await fetch('http://localhost:9225/json').then((r) => r.json());
const page = list.find((t) => t.type === 'page');
if (!page) { console.log('ERR: no page'); 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, 200) }; return r.result.value; };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
await send('Page.enable'); await send('Runtime.enable');
const waitFor = async (expr, ms = 12000) => { const t0 = Date.now(); while (Date.now() - t0 < ms) { if (await ev(expr)) return true; await sleep(300); } return false; };
const fail = (m) => { console.log('FAIL@' + m); process.exit(1); };
const goto = async (hash) => { await ev(`location.hash = ${JSON.stringify(hash)}`); await sleep(700); };
await send('Page.navigate', { url: 'http://localhost:8900/#/' });
if (!(await waitFor(`!!document.querySelector('#view')`))) fail('app boot');
await ev(`document.querySelectorAll('.modal-mask').forEach((m) => m.remove())`); // 清 root 设置弹窗
const out = {};
/* ============ 1) 未解析视频 #/video/58 ============ */
await goto('#/video/58');
if (!(await waitFor(`!!document.querySelector('#view .tabs')`))) fail('tabs render');
out.unparsed = await ev(`(() => {
const tabs = [...document.querySelectorAll('#view .tabs .tab')].map((t) => ({ txt: t.textContent.trim(), active: t.classList.contains('tab-active') }));
const es = document.querySelector('#view .empty-state');
const pb = document.querySelector('#view [data-parse]');
return {
tabs,
hasAnalysisTab: tabs.some((t) => t.txt === '视频分析'),
emptyState: !!es,
parseBtn: pb ? { mode: pb.dataset.parse, txt: pb.textContent.trim() } : null,
};
})()`);
// 切「视频拆解」tab → 也应空态+解析按钮
await ev(`[...document.querySelectorAll('#view .tabs .tab')].find((t) => t.textContent.trim() === '视频拆解').click()`);
if (!(await waitFor(`document.querySelector('#view [data-parse]')`))) fail('deconstruct parse btn');
out.unparsedDecon = await ev(`(() => {
const pb = document.querySelector('#view [data-parse]');
return { mode: pb.dataset.parse, empty: !!document.querySelector('#view .empty-state'), activeTab: document.querySelector('#view .tabs .tab-active').textContent.trim() };
})()`);
// 点「解析」→ confirmRun 弹窗 → 取消(不触发真实任务)
await ev(`document.querySelector('#view [data-parse]').click()`);
if (!(await waitFor(`[...document.querySelectorAll('.modal-mask .modal')].some((m) => m.textContent.includes('解析'))`))) fail('parse modal');
out.parseModal = await ev(`(() => {
const m = [...document.querySelectorAll('.modal-mask .modal')].find((x) => x.textContent.includes('视频拆解') || x.querySelector('h3'));
const r = { title: m.querySelector('h3') ? m.querySelector('h3').textContent.trim() : '', hasOk: !!m.querySelector('[data-ok]') };
m.querySelector('[data-cancel]').click();
return r;
})()`);
await sleep(300);
/* ============ 2) 已解析视频 #/video/117 ============ */
await goto('#/video/117');
if (!(await waitFor(`!!document.querySelector('#view .md-body')`))) fail('parsed md-body');
out.parsed = await ev(`(() => {
const tools = document.querySelector('#view .vd-tools');
const pb = tools ? tools.querySelector('[data-parse]') : null;
return {
hasTools: !!tools,
refreshBtn: pb ? { mode: pb.dataset.parse, txt: pb.textContent.trim(), hasIcon: !!pb.querySelector('svg') } : null,
mdLen: document.querySelector('#view .md-body').textContent.length,
};
})()`);
// 切「视频拆解」tab:有内容→右上刷新;无内容→空态解析按钮(两种均可,但必须出现 data-parse)
await ev(`[...document.querySelectorAll('#view .tabs .tab')].find((t) => t.textContent.trim() === '视频拆解').click()`);
if (!(await waitFor(`document.querySelector('#view [data-parse]')`))) fail('parsed decon btn');
out.parsedDecon = await ev(`(() => {
const pb = document.querySelector('#view [data-parse]');
return { mode: pb.dataset.parse, hasTools: !!document.querySelector('#view .vd-tools'), hasMd: !!document.querySelector('#view .md-body') };
})()`);
console.log(JSON.stringify(out, null, 1));
const ok = out.unparsed && !out.unparsed.__err
&& out.unparsed.tabs.length === 2 && out.unparsed.tabs[0].txt === '视频脚本' && out.unparsed.tabs[1].txt === '视频拆解'
&& out.unparsed.hasAnalysisTab === false
&& out.unparsed.emptyState === true && out.unparsed.parseBtn && out.unparsed.parseBtn.mode === 'parse' && out.unparsed.parseBtn.txt.includes('解析')
&& out.unparsedDecon && out.unparsedDecon.mode === 'parse' && out.unparsedDecon.empty === true && out.unparsedDecon.activeTab === '视频拆解'
&& out.parseModal && out.parseModal.hasOk && !out.parseModal.title.includes('刷新')
&& out.parsed && !out.parsed.__err && out.parsed.hasTools === true
&& out.parsed.refreshBtn && out.parsed.refreshBtn.mode === 'refresh' && out.parsed.refreshBtn.txt.includes('刷新') && out.parsed.refreshBtn.hasIcon
&& out.parsedDecon && (out.parsedDecon.mode === 'refresh' || (out.parsedDecon.mode === 'parse' && out.parsedDecon.hasMd === false));
console.log(ok ? '\n=== PASS ===' : '\n=== CHECK ===');
ws.close();
process.exit(ok ? 0 : 1);