// 验证「账号设定」卡片版/文字版切换(账号详情页 persona tab) // 用例1:旧梦留声机(1629) 有卡片 → 默认卡片版 + 可切换文字版 // 用例2:王微斯(1627) 无卡片 → 直接文字版,无切换控件 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 open'); 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'); const shot = async (name) => { try { const r = await send('Page.captureScreenshot', { format: 'png' }); const fs = await import('node:fs'); fs.writeFileSync('shot-' + name + '.png', Buffer.from(r.data, 'base64')); console.log('截图:', name); } catch (e) { console.log('截图失败(跳过):', e.message); } }; const goto = async (url) => { await send('Page.navigate', { url }); await sleep(2500); await send('Page.reload', { ignoreCache: true }); // hash 路由 navigate 不重载文档,强制刷新加载最新 JS await sleep(3000); }; // ---- 用例1:旧梦留声机(有卡片)---- console.log('=== 用例1 旧梦留声机 1629 ==='); await goto('http://localhost:8899/#/account/1629'); // 点击「账号设定」tab const tabClick = await ev(`(() => { const b = document.querySelector('[data-tab="persona"]'); if (!b) return { err: 'no persona tab' }; b.click(); return 'clicked persona tab'; })()`); console.log('切tab:', JSON.stringify(tabClick)); await sleep(1800); // 等 loadPersona 两个 fetch 完成 const s1 = await ev(`(() => { const sw = document.querySelector('.persona-vs-icons'); const card = document.querySelector('.card-view'); const md = document.querySelector('.persona-text'); const img = card && card.querySelector('img'); const btns = sw ? [...sw.querySelectorAll('.vi-btn')] : []; const active = btns.find((x) => x.classList.contains('vi-active')); const cardIcon = btns[0] && btns[0].querySelector('svg path'); return { hasIcons: !!sw, btnTitles: btns.map((x) => x.title), activeTitle: active ? active.title : null, cardIconIsImage: cardIcon ? cardIcon.getAttribute('d').includes('m21 15-3.086') : null, cardHidden: card ? card.classList.contains('vs-hidden') : null, cardImgSrc: img ? img.getAttribute('src').slice(0, 60) : null, mdHidden: md ? md.classList.contains('vs-hidden') : null, mdLen: md ? md.textContent.length : 0, }; })()`); console.log('默认卡片版状态:', JSON.stringify(s1)); // 切到文字版 await ev(`(() => { const b = document.querySelector('[data-vs="text"]'); if (b) b.click(); return 1; })()`); await sleep(400); const s2 = await ev(`(() => { const sw = document.querySelector('.persona-vs-icons'); const card = document.querySelector('.card-view'); const md = document.querySelector('.persona-text'); const active = sw && [...sw.querySelectorAll('.vi-btn')].find((x) => x.classList.contains('vi-active')); return { activeTitle: active ? active.title : null, activeCount: sw ? [...sw.querySelectorAll('.vi-btn.vi-active')].length : null, cardHidden: card ? card.classList.contains('vs-hidden') : null, mdHidden: md ? md.classList.contains('vs-hidden') : null, }; })()`); console.log('切文字版后:', JSON.stringify(s2)); // 切回卡片版 await ev(`(() => { const b = document.querySelector('[data-vs="card"]'); if (b) b.click(); return 1; })()`); await sleep(400); const s3 = await ev(`(() => { const card = document.querySelector('.card-view'); const md = document.querySelector('.persona-text'); const active = document.querySelector('.vi-btn.vi-active'); return { activeTitle: active ? active.title : null, activeCount: document.querySelectorAll('.vi-btn.vi-active').length, cardHidden: card ? card.classList.contains('vs-hidden') : null, mdHidden: md ? md.classList.contains('vs-hidden') : null, }; })()`); console.log('切回卡片版后:', JSON.stringify(s3)); // ---- 用例2:王微斯(无卡片)---- console.log('=== 用例2 王微斯 1627(无卡片)==='); await goto('http://localhost:8899/#/account/1627'); await ev(`(() => { const b = document.querySelector('[data-tab="persona"]'); if (b) b.click(); return 1; })()`); await sleep(1800); const s4 = await ev(`(() => { const sw = document.querySelector('.persona-vs-icons'); const md = document.querySelector('.persona-text'); return { hasIcons: !!sw, mdExists: !!md, mdHidden: md ? md.classList.contains('vs-hidden') : null, mdLen: md ? md.textContent.length : 0, }; })()`); console.log('无卡片账号状态:', JSON.stringify(s4)); // 回到用例1 页面截图卡片版效果(最后统一截图,失败不中断) await goto('http://localhost:8899/#/account/1629'); await ev(`(() => { const b = document.querySelector('[data-tab="persona"]'); if (b) b.click(); return 1; })()`); await sleep(1800); // 多视口宽度自检:验证图片是否随内容区自适应(不应超出 viewport 引发横向滚动) for (const vw of [1280, 1024, 900, 1920, 2560]) { await send('Emulation.setDeviceMetricsOverride', { width: vw, height: 800, deviceScaleFactor: 1, mobile: false }); await sleep(500); const info = await ev(`(() => { const img = document.querySelector('.card-view img'); const body = document.querySelector('.detail-body'); const wrap = document.querySelector('.persona-wrap'); const page = document.querySelector('.page-detail'); return { viewport: ${vw}, docScrollW: document.documentElement.scrollWidth, bodyW: body ? Math.round(body.getBoundingClientRect().width) : null, wrapW: wrap ? Math.round(wrap.getBoundingClientRect().width) : null, imgW: img ? Math.round(img.getBoundingClientRect().width) : null, pageW: page ? Math.round(page.getBoundingClientRect().width) : null, bodyOverflow: body ? getComputedStyle(body).overflowX : null, }; })()`); console.log('视口', vw, ':', JSON.stringify(info)); } await send('Emulation.clearDeviceMetricsOverride'); await sleep(300); await shot('persona-card-final'); console.log('DONE'); ws.close(); process.exit(0);