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
+221
View File
@@ -0,0 +1,221 @@
import { chromium } from '@playwright/test';
import fs from 'fs';
import path from 'path';
const screenshotsDir = '/tmp/screenshots/full-visual-test';
fs.mkdirSync(screenshotsDir, { recursive: true });
const pages = [
{ name: 'home', url: 'http://localhost:3000', sections: ['hero', 'stats', 'services', 'industries', 'cases', 'approach', 'insights', 'cta'] },
{ name: 'products', url: 'http://localhost:3000/products', sections: ['hero', 'product-grid'] },
{ name: 'solutions', url: 'http://localhost:3000/solutions', sections: ['hero', 'solutions-grid'] },
{ name: 'services', url: 'http://localhost:3000/services', sections: ['hero', 'services-grid'] },
{ name: 'about', url: 'http://localhost:3000/about', sections: ['hero', 'about-content'] },
];
async function capturePage(page, pageInfo) {
console.log(`\n=== Testing ${pageInfo.name} page ===`);
await page.goto(pageInfo.url, { waitUntil: 'networkidle' });
await page.waitForTimeout(1500);
await page.screenshot({
path: path.join(screenshotsDir, `${pageInfo.name}-full.png`),
fullPage: true,
});
console.log(` ✓ Full page screenshot captured`);
const viewportHeight = 1080;
let currentScroll = 0;
let sectionIndex = 0;
while (sectionIndex < 5) {
await page.evaluate((scroll) => {
window.scrollTo(0, scroll);
}, currentScroll);
await page.waitForTimeout(800);
await page.screenshot({
path: path.join(screenshotsDir, `${pageInfo.name}-section-${sectionIndex + 1}.png`),
});
currentScroll += viewportHeight * 0.8;
sectionIndex++;
}
console.log(` ✓ Section screenshots captured`);
const cards = await page.$$('a[href], .card, [class*="group"]');
if (cards.length > 0) {
const firstCard = cards[0];
await firstCard.scrollIntoViewIfNeeded();
await page.waitForTimeout(500);
const box = await firstCard.boundingBox();
if (box) {
await page.screenshot({
path: path.join(screenshotsDir, `${pageInfo.name}-card-before.png`),
clip: {
x: Math.max(0, box.x - 20),
y: Math.max(0, box.y - 20),
width: Math.min(box.width + 40, 800),
height: Math.min(box.height + 40, 600),
},
});
await firstCard.hover();
await page.waitForTimeout(600);
await page.screenshot({
path: path.join(screenshotsDir, `${pageInfo.name}-card-hover.png`),
clip: {
x: Math.max(0, box.x - 20),
y: Math.max(0, box.y - 20),
width: Math.min(box.width + 40, 800),
height: Math.min(box.height + 40, 600),
},
});
console.log(` ✓ Card hover states captured`);
}
}
await page.evaluate(() => {
window.scrollTo(0, 0);
});
await page.waitForTimeout(500);
}
async function checkTextReadability(page, pageName) {
console.log(`\n Checking text readability...`);
const issues = await page.evaluate(() => {
const problems = [];
const textElements = document.querySelectorAll('h1, h2, h3, h4, p, span, a');
textElements.forEach((el, index) => {
const style = window.getComputedStyle(el);
const opacity = parseFloat(style.opacity);
const color = style.color;
const bgColor = style.backgroundColor;
const fontSize = parseFloat(style.fontSize);
const zIndex = parseInt(style.zIndex) || 0;
const rect = el.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
if (opacity < 0.4 && opacity > 0 && el.textContent.trim().length > 5) {
problems.push({
type: 'low-opacity',
text: el.textContent.trim().substring(0, 50),
opacity: opacity.toFixed(2),
tag: el.tagName,
});
}
if (style.mixBlendMode && style.mixBlendMode !== 'normal') {
problems.push({
type: 'mix-blend-mode',
text: el.textContent.trim().substring(0, 50),
blendMode: style.mixBlendMode,
tag: el.tagName,
});
}
});
return problems.slice(0, 10);
});
if (issues.length > 0) {
console.log(` ⚠ Found ${issues.length} potential readability issues:`);
issues.forEach((issue, i) => {
console.log(` ${i + 1}. [${issue.type}] ${issue.tag}: "${issue.text}"`);
});
} else {
console.log(` ✓ No major readability issues found`);
}
return issues;
}
async function checkGlowElements(page, pageName) {
console.log(`\n Checking glow/blur elements...`);
const glowInfo = await page.evaluate(() => {
const allElements = document.querySelectorAll('*');
const glowElements = [];
allElements.forEach((el) => {
const style = window.getComputedStyle(el);
const hasBlur = style.filter.includes('blur');
const hasRadialGradient = style.backgroundImage.includes('radial-gradient');
const opacity = parseFloat(style.opacity);
if ((hasBlur || hasRadialGradient) && opacity > 0.05) {
const rect = el.getBoundingClientRect();
if (rect.width > 100 && rect.height > 100) {
glowElements.push({
tag: el.tagName,
className: el.className.substring(0, 100),
opacity: opacity.toFixed(3),
hasBlur,
hasRadialGradient,
width: rect.width,
height: rect.height,
zIndex: style.zIndex || 'auto',
position: style.position,
});
}
}
});
return glowElements.slice(0, 15);
});
console.log(` Found ${glowInfo.length} glow/blur elements (>100px, opacity>0.05)`);
glowInfo.forEach((el, i) => {
console.log(` ${i + 1}. ${el.tag} (${Math.round(el.width)}x${Math.round(el.height)}, opacity: ${el.opacity})`);
console.log(` position: ${el.position}, zIndex: ${el.zIndex}`);
});
return glowInfo;
}
async function runTests() {
console.log('========================================');
console.log(' Novalon Website Visual Test Suite');
console.log('========================================');
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1440, height: 1080 },
});
const page = await context.newPage();
let totalIssues = 0;
for (const pageInfo of pages.slice(0, 3)) {
try {
await capturePage(page, pageInfo);
const readabilityIssues = await checkTextReadability(page, pageInfo.name);
const glowElements = await checkGlowElements(page, pageInfo.name);
totalIssues += readabilityIssues.length;
} catch (e) {
console.log(` ✗ Error testing ${pageInfo.name}: ${e.message}`);
}
}
await browser.close();
console.log('\n========================================');
console.log(' Test Summary');
console.log('========================================');
console.log(` Screenshots saved to: ${screenshotsDir}`);
console.log(` Total readability issues found: ${totalIssues}`);
console.log('\n Check the screenshots for visual verification.');
console.log('========================================');
}
runTests().catch(console.error);