// 09-04 R64.3 验证:AI 脚本弹窗「删除」按钮去文字留图标 const CDP = 9223; const list = await fetch(`http://localhost:${CDP}/json`).then((r) => r.json()); const page = list.find((t) => t.type === 'page' && (t.url || '').includes('8900')) || 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, 300) }; return r.result.value; }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); await send('Page.enable'); await send('Runtime.enable'); await send('Network.enable'); await send('Network.setCacheDisabled', { cacheDisabled: true }); await send('Emulation.setDeviceMetricsOverride', { width: 860, height: 1000, deviceScaleFactor: 1, mobile: false }); const waitFor = async (expr, ms = 15000) => { 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 fs = await import('node:fs'); await send('Page.navigate', { url: `http://127.0.0.1:8900/?t=${Date.now()}#/rewrites` }); if (!(await waitFor(`location.hash === '#/rewrites' && !!document.querySelector('#view .rw-tabs') && [...document.scripts].some((s) => (s.src || '').includes('data-pages.js?v=20260904o'))`, 15000))) fail('rewrites route'); await sleep(600); // 打开弹窗:优先 ref tab 行 data-detail,其次切 custom tab 行 data-cview let opened = await ev(`(() => { const b = document.querySelector('[data-detail], [data-cview], [data-rewrite]'); if (!b) return false; b.click(); return true; })()`); if (!opened) { // 尝试切到 custom tab await ev(`[...document.querySelectorAll('#view .rw-tabs [data-tab]')].find((t) => t.textContent.includes('自定义选题'))?.click()`); await sleep(800); opened = await ev(`(() => { const b = document.querySelector('[data-cview]'); if (!b) return false; b.click(); return true; })()`); } if (!opened) { console.log('SKIP: no script row to open'); process.exit(2); } if (!(await waitFor(`!!document.querySelector('.rw-modal')`, 8000))) fail('modal open'); await sleep(700); const r = await ev(`(() => { const m = document.querySelector('.rw-modal'); if (!m) return { err: 'no .rw-modal' }; const d = m.querySelector('[data-del-script]'); if (!d) return { err: 'no [data-del-script]' }; return { text: (d.textContent || '').trim(), hasSvg: !!d.querySelector('svg'), title: d.getAttribute('title'), footHtml: (m.querySelector('.rw-sec-foot')?.textContent || '').trim().slice(0, 40) }; })()`); console.log(JSON.stringify(r, null, 2)); const rect = await ev(`(() => { const el = document.querySelector('[data-del-script]'); const r = el.getBoundingClientRect(); return { x: r.x, y: r.y, w: r.width, h: r.height }; })()`); if (rect && !rect.__err) { const shot = await send('Page.captureScreenshot', { format: 'png', clip: { x: Math.max(0, rect.x - 140), y: Math.max(0, rect.y - 30), width: 300, height: rect.h + 60, scale: 1 } }); fs.writeFileSync(`tmp/r64_del_btn.png`, Buffer.from(shot.data, 'base64')); } const ok = r && !r.__err && r.text === '' && r.hasSvg; console.log(ok ? '\n=== PASS ===' : '\n=== CHECK ==='); ws.close(); process.exit(ok ? 0 : 1);