// B-2 运行时冒烟:确认合并后的共享 L3 组件(CaseStudiesSection / // CertificationsSection)接入详情页后不破坏页面、hydration 成功、无控制台报错。 // // 关键约束(2026-09-02):当前所有 product/service/solution 的 caseStudies / // certifications 均为空数组(零编造前提,刻意留空),dataProofs 有填充。 // 因此 product 页 L3 整段不渲染(L3EmptyFallback 因 dataProofs 非空而休眠, // CaseStudiesSection/CertificationsSection 因 caseStudies/certs 空而 return null)。 // 本脚本不验证「填充态」渲染(无真实数据,禁止编造),只验证: // 1) 页面正常加载、hero 渲染(非空白) // 2) hydration 完成(framer-motion 元素 opacity 达到 1) // 3) 无未捕获控制台错误(合并 import 未破坏模块图) // 4) L3 区块按数据闸门正确缺席(无「客户案例」h2 = CaseStudiesSection 休眠) // 浅色 + 深色双版截图留证。 import { chromium } from 'playwright'; import { writeFileSync, mkdirSync } from 'node:fs'; const BASE = 'http://localhost:3000'; const OUT = 'dogfood-b2-verify'; mkdirSync(OUT, { recursive: true }); const browser = await chromium.launch({ args: ['--no-proxy-server'] }); async function check(path, theme) { const ctx = await browser.newContext(); const page = await ctx.newPage(); if (theme === 'dark') { await page.addInitScript(() => { try { localStorage.setItem('novalon-theme', 'dark'); } catch {} }); } const errors = []; page.on('console', (m) => { if (m.type() === 'error') errors.push(m.text()); }); page.on('pageerror', (e) => errors.push('PAGEERROR: ' + e.message)); await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 30000 }); // hydration 探测:轮询 framer-motion 元素 opacity === '1' let hydrated = false; try { await page.waitForFunction(() => { const els = Array.from(document.querySelectorAll('div[style*="opacity"]')); return els.some((el) => getComputedStyle(el).opacity === '1'); }, { timeout: 10000 }); hydrated = true; } catch { hydrated = false; } const heroText = await page.locator('h1').first().innerText().catch(() => '(no h1)'); const hasCaseStudiesH2 = await page.locator("h2:has-text('客户案例')").count(); const l3Signals = await page.locator("#l3-signals-heading").count(); const file = `${OUT}/erp-${theme}.png`; await page.screenshot({ path: file, fullPage: true }); await ctx.close(); return { theme, path, hydrated, heroText: heroText.slice(0, 40), hasCaseStudiesH2, l3Signals, errors, file, }; } const results = []; results.push(await check('/products/erp', 'light')); results.push(await check('/products/erp', 'dark')); await browser.close(); const report = { generatedAt: new Date().toISOString(), results }; writeFileSync(`${OUT}/report.json`, JSON.stringify(report, null, 2)); console.log(JSON.stringify(report, null, 2));