Files
novalon-website/scripts/audit/l3-b2-verify.mjs
T
zhangxiang 10160fb7f2 refactor(detail): 合并 L3 客户案例/资质认证区块为共享组件
- 新建 CaseStudiesSection / CertificationsSection 共享组件(src/components/detail),
  合并 solution/product 逐字重复的本地实现与 service 的异类实现(卡片 motion.a
  链 /cases/<slug> 依赖尚不存在数据 → 404,违反零编造)
- bg-white → bg-bg-primary token 化(浅色零视觉差异,深色模式正确反色,对齐暗黑 Phase 2)
- 数据闸门保留:caseStudies/certifications 空时 return null,由 L3EmptyFallback 接管
- grid/flex 类直接挂 StaggerReveal,修复合并前的单列布局 bug
- 动效 0.8s → 0.7s(detail-cta-section + service CTASection),对齐 ≤700ms 约束
- 附 scripts/audit/l3-b2-verify.mjs 运行时冒烟(product 页浅深双版,0 报错,hydration 通过)

验证:type-check 0 错 / lint 0 错(127 既有 warnings) / 单测 129 套件 1628 通过
2026-09-02 12:51:03 +08:00

71 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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));