/** * Logo 对比诊断:生产环境 vs 本地 dev * * 用法:node scripts/audit/logo-compare.mjs * 输出:dogfood-ui-audit/logo-compare/{prod,local}-header.png + logo-meta.json */ import { chromium } from 'playwright'; import fs from 'node:fs'; import path from 'node:path'; const OUT = path.join(process.cwd(), 'dogfood-ui-audit', 'logo-compare'); fs.mkdirSync(OUT, { recursive: true }); const TARGETS = [ { tag: 'prod', url: 'https://www.novalon.cn/' }, { tag: 'local', url: 'http://127.0.0.1:3000/' }, ]; const browser = await chromium.launch(); const meta = {}; for (const t of TARGETS) { const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2, }); const page = await ctx.newPage(); const errors = []; page.on('requestfailed', r => errors.push('FAILED ' + r.url())); page.on('console', m => { if (m.type() === 'error') errors.push('CONSOLE ' + m.text()); }); try { await page.goto(t.url, { waitUntil: 'networkidle', timeout: 60000 }); await page.waitForTimeout(1500); // header logo 元素探测 const info = await page.evaluate(() => { const imgs = [...document.querySelectorAll('img')] .filter(i => /logo/i.test(i.getAttribute('src') || '')); return imgs.map(i => { const r = i.getBoundingClientRect(); const cs = getComputedStyle(i); return { src: i.getAttribute('src'), currentSrc: i.currentSrc, renderedW: Math.round(r.width), renderedH: Math.round(r.height), naturalW: i.naturalWidth, naturalH: i.naturalHeight, complete: i.complete, fontFamily: cs.fontFamily, }; }); }); // 截 header 区域(放大到 logo 可见) const header = page.locator('header').first(); await header.screenshot({ path: path.join(OUT, `${t.tag}-header.png`) }).catch(async () => { await page.screenshot({ path: path.join(OUT, `${t.tag}-header.png`), clip: { x: 0, y: 0, width: 1440, height: 80 } }); }); meta[t.tag] = { url: t.url, logos: info, errors }; console.log(`[${t.tag}] OK logos=${info.length} errors=${errors.length}`); for (const l of info) { console.log(` src=${l.src} currentSrc=${l.currentSrc}`); console.log(` rendered=${l.renderedW}x${l.renderedH} natural=${l.naturalW}x${l.naturalH} complete=${l.complete}`); } } catch (e) { meta[t.tag] = { url: t.url, error: e.message.slice(0, 200), errors }; console.log(`[${t.tag}] FAILED ${e.message.slice(0, 120)}`); } await ctx.close(); } fs.writeFileSync(path.join(OUT, 'logo-meta.json'), JSON.stringify(meta, null, 2)); await browser.close(); console.log('\n✓ 输出目录:' + OUT);