docs(test): 添加设计文档、测试规范与 E2E 测试套件

- 新增 ADR 架构决策记录 (Design DNA 集成与深化)
- 新增 CMS 系统设计文档
- 新增实施计划文档 (Phase1-3)
- 新增 Bain 品牌升级设计规格
- 新增 E2E 分层测试套件 (P1-P4)
- 新增视觉回归测试配置
- 新增光效分析、视觉验证等辅助脚本
- 更新验收测试报告
This commit is contained in:
张翔
2026-07-07 06:54:25 +08:00
parent 8def296301
commit 636bc4ecde
31 changed files with 9262 additions and 41 deletions
+134
View File
@@ -0,0 +1,134 @@
import { chromium } from '@playwright/test';
import { mkdirSync, writeFileSync } from 'fs';
import { join } from 'path';
const screenshotDir = '/tmp/screenshots/phase4-visual-test';
mkdirSync(screenshotDir, { recursive: true });
const pages = [
{ name: 'product-detail', url: 'http://localhost:3000/products/erp', waitMs: 2000 },
{ name: 'solution-detail', url: 'http://localhost:3000/solutions/manufacturing', waitMs: 2000 },
{ name: 'service-detail', url: 'http://localhost:3000/services/software', waitMs: 2000 },
];
const sections = [
{ name: 'hero', scrollY: 0 },
{ name: 'mid-section', scrollY: 800 },
{ name: 'cta-section', scrollY: 2500 },
];
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
});
const results = [];
for (const page of pages) {
console.log(`\n=== Testing: ${page.name} ===`);
const pageObj = await context.newPage();
try {
await pageObj.goto(page.url, { waitUntil: 'domcontentloaded', timeout: 30000 });
await pageObj.waitForTimeout(page.waitMs + 2000);
// 检查页面是否有错误
const pageErrors = [];
pageObj.on('pageerror', (err) => {
pageErrors.push(err.message);
});
for (const section of sections) {
await pageObj.evaluate((y) => window.scrollTo(0, y), section.scrollY);
await pageObj.waitForTimeout(800);
const screenshotPath = join(screenshotDir, `${page.name}-${section.name}.png`);
await pageObj.screenshot({ path: screenshotPath, fullPage: false });
console.log(`${section.name} screenshot saved`);
}
// 检查文字可见性
const textVisibility = await pageObj.evaluate(() => {
const textElements = document.querySelectorAll('h1, h2, h3, p, span');
let total = 0;
let visible = 0;
let lowOpacity = 0;
for (const el of textElements) {
const style = window.getComputedStyle(el);
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0 && rect.top < window.innerHeight) {
total++;
const opacity = parseFloat(style.opacity);
if (opacity >= 0.9) visible++;
if (opacity < 0.5) lowOpacity++;
}
}
return { total, visible, lowOpacity };
});
console.log(` Text visibility: ${textVisibility.visible}/${textVisibility.total} visible, ${textVisibility.lowOpacity} low opacity`);
// 检查光晕元素
const glowElements = await pageObj.evaluate(() => {
const elements = document.querySelectorAll('*');
const glowEls = [];
for (const el of elements) {
const style = window.getComputedStyle(el);
const hasBlur = style.filter.includes('blur');
const hasLowOpacity = parseFloat(style.opacity) < 0.1;
const isRounded = style.borderRadius.includes('%') || parseInt(style.borderRadius) > 50;
if (hasBlur && hasLowOpacity && isRounded && el.offsetWidth > 100) {
glowEls.push({
tag: el.tagName,
width: el.offsetWidth,
opacity: style.opacity,
pointerEvents: style.pointerEvents,
});
}
}
return glowEls.slice(0, 10);
});
console.log(` Glow elements found: ${glowElements.length}`);
glowElements.forEach((el, i) => {
console.log(` [${i}] ${el.tag} ${el.width}px opacity:${el.opacity} pointer-events:${el.pointerEvents}`);
});
results.push({
page: page.name,
status: pageErrors.length === 0 ? 'pass' : 'fail',
errors: pageErrors,
textVisibility,
glowCount: glowElements.length,
});
} catch (err) {
console.log(` ✗ Error: ${err.message}`);
results.push({ page: page.name, status: 'error', error: err.message });
}
await pageObj.close();
}
await browser.close();
console.log('\n\n=== Phase 4 Visual Test Summary ===');
results.forEach((r) => {
console.log(`${r.status === 'pass' ? '✓' : '✗'} ${r.page}: ${r.status}`);
if (r.errors?.length) console.log(` Errors: ${r.errors.join(', ')}`);
if (r.error) console.log(` Error: ${r.error}`);
});
const allPassed = results.every((r) => r.status === 'pass');
console.log(`\nOverall: ${allPassed ? 'ALL PASSED ✓' : 'SOME FAILED ✗'}`);
writeFileSync(
join(screenshotDir, 'results.json'),
JSON.stringify(results, null, 2)
);
process.exit(allPassed ? 0 : 1);
})();