// 验证:①展开源脚本按钮移到 AI脚本 标题右侧(间隔 10px)②弹窗最大宽度 1400→1600 const list = await fetch('http://localhost:9223/json').then((r) => r.json()); let page = list.find((t) => t.type === 'page' && t.url.includes('localhost:8899')); if (!page) { console.log('ERR: no workbuddy 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'); if (!page.url.includes('#/rewrites')) { await send('Page.navigate', { url: 'http://localhost:8899/#/rewrites' }); await sleep(2000); } await send('Page.reload', { ignoreCache: true }); await sleep(3000); // 打开第一条改写的查看弹窗 await ev(`(() => { const b = document.querySelector('[data-detail]'); if (!b) return 'no-detail-btn'; b.click(); return 'opened'; })()`); await sleep(2500); // ① 按钮位置:应在 AI脚本 .rw-sec-head 内,与「AI脚本」文字间隔 10px const pos = await ev(`(() => { const head = [...document.querySelectorAll('.rw-sec-head')].find((h) => h.textContent.trim().startsWith('AI脚本')); if (!head) return { err: 'no AI脚本 head' }; const btn = head.querySelector('[data-src-toggle]'); if (!btn) return { err: 'no toggle in AI head' }; const textNode = [...head.childNodes].find((n) => n.nodeType === 3 && n.textContent.includes('AI脚本')); const range = document.createRange(); range.selectNodeContents(textNode); const textRect = range.getBoundingClientRect(); const btnRect = btn.getBoundingClientRect(); // 文字右缘 → 按钮左缘 的水平距离(同一行) const gap = btnRect.left - textRect.right; // 顶部标题行不应再有 toggle const titleRowHasToggle = !!document.querySelector('.modal-title-row [data-src-toggle]'); return { inAiHead: true, gap: Math.round(gap * 100) / 100, btnTitle: btn.title, verticalAligned: Math.abs(btnRect.top - textRect.top) < 4, titleRowHasToggle, }; })()`); console.log('① 按钮位置:', JSON.stringify(pos)); // ② 点击按钮 → 源脚本列展开 await ev(`(() => { const b = document.querySelector('[data-src-toggle]'); b.click(); return 'toggled'; })()`); await sleep(600); const expanded = await ev(`(() => { const srcSec = document.querySelector('[data-src-sec]'); const toggle = document.querySelector('[data-src-toggle]'); return { srcVisible: srcSec && getComputedStyle(srcSec).display !== 'none', toggleTitle: toggle ? toggle.title : null }; })()`); console.log('② 点击后展开源脚本:', JSON.stringify(expanded)); await ev(`(() => { const b = document.querySelector('[data-src-toggle]'); b.click(); return 'toggled-back'; })()`); await sleep(500); // ③ 弹窗宽度:多视口验证 1600 封顶 const widthAt = async (vw) => { await send('Emulation.setDeviceMetricsOverride', { width: vw, height: 900, deviceScaleFactor: 1, mobile: false }); await sleep(500); return ev(`(() => { const m = document.querySelector('.rw-modal'); if (!m) return null; const r = m.getBoundingClientRect(); return { vw: window.innerWidth, w: Math.round(r.width), left: Math.round(r.left), right: Math.round(window.innerWidth - r.right) }; })()`); }; const w1200 = await widthAt(1200); const w1500 = await widthAt(1500); const w1700 = await widthAt(1700); const w2000 = await widthAt(2000); console.log('③ 弹窗宽度:', JSON.stringify({ w1200, w1500, w1700, w2000 })); await send('Emulation.clearDeviceMetricsOverride'); ws.close(); console.log('DONE');