- 新增 ADR 架构决策记录 (Design DNA 集成与深化) - 新增 CMS 系统设计文档 - 新增实施计划文档 (Phase1-3) - 新增 Bain 品牌升级设计规格 - 新增 E2E 分层测试套件 (P1-P4) - 新增视觉回归测试配置 - 新增光效分析、视觉验证等辅助脚本 - 更新验收测试报告
158 lines
5.7 KiB
JavaScript
158 lines
5.7 KiB
JavaScript
import { chromium } from '@playwright/test';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const screenshotsDir = '/tmp/screenshots/final-visual-test';
|
|
fs.mkdirSync(screenshotsDir, { recursive: true });
|
|
|
|
async function runTests() {
|
|
console.log('========================================');
|
|
console.log(' Novalon Homepage Visual Test');
|
|
console.log('========================================');
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const context = await browser.newContext({
|
|
viewport: { width: 1440, height: 1080 },
|
|
});
|
|
const page = await context.newPage();
|
|
|
|
console.log('\n1. Loading homepage...');
|
|
await page.goto('http://localhost:3000', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForTimeout(2000);
|
|
console.log(' ✓ Page loaded');
|
|
|
|
console.log('\n2. Capturing full page screenshot...');
|
|
await page.screenshot({
|
|
path: path.join(screenshotsDir, '01-home-full.png'),
|
|
fullPage: true,
|
|
});
|
|
console.log(' ✓ Full page captured');
|
|
|
|
const sections = [
|
|
{ name: '02-hero', scrollY: 0, desc: 'Hero Section' },
|
|
{ name: '03-stats', scrollY: 900, desc: 'Stats Section (可衡量成果)' },
|
|
{ name: '04-services', scrollY: 1800, desc: 'Services Matrix' },
|
|
{ name: '05-industries', scrollY: 2800, desc: 'Industries Section' },
|
|
{ name: '06-cases', scrollY: 3600, desc: 'Case Studies' },
|
|
{ name: '07-approach', scrollY: 4500, desc: 'Approach Section' },
|
|
{ name: '08-insights', scrollY: 5400, desc: 'Insights Section' },
|
|
{ name: '09-cta', scrollY: 6300, desc: 'CTA Section' },
|
|
];
|
|
|
|
console.log('\n3. Capturing section screenshots...');
|
|
for (const section of sections) {
|
|
await page.evaluate((scrollY) => {
|
|
window.scrollTo(0, scrollY);
|
|
}, section.scrollY);
|
|
await page.waitForTimeout(800);
|
|
|
|
await page.screenshot({
|
|
path: path.join(screenshotsDir, `${section.name}.png`),
|
|
});
|
|
console.log(` ✓ ${section.desc}`);
|
|
}
|
|
|
|
console.log('\n4. Checking text readability...');
|
|
const textIssues = await page.evaluate(() => {
|
|
const issues = [];
|
|
const allElements = document.querySelectorAll('*');
|
|
|
|
allElements.forEach((el) => {
|
|
const style = window.getComputedStyle(el);
|
|
const opacity = parseFloat(style.opacity);
|
|
const mixBlendMode = style.mixBlendMode;
|
|
|
|
if (mixBlendMode && mixBlendMode !== 'normal') {
|
|
const rect = el.getBoundingClientRect();
|
|
if (rect.width > 50 && rect.height > 20) {
|
|
issues.push({
|
|
type: 'mix-blend-mode',
|
|
tag: el.tagName,
|
|
className: el.className.substring(0, 80),
|
|
blendMode: mixBlendMode,
|
|
text: el.textContent?.trim().substring(0, 40) || '',
|
|
});
|
|
}
|
|
}
|
|
|
|
if (opacity < 0.5 && opacity > 0) {
|
|
const rect = el.getBoundingClientRect();
|
|
if (rect.width > 100 && rect.height > 30 && el.textContent?.trim().length > 10) {
|
|
issues.push({
|
|
type: 'low-opacity-text',
|
|
tag: el.tagName,
|
|
className: el.className.substring(0, 80),
|
|
opacity: opacity.toFixed(2),
|
|
text: el.textContent.trim().substring(0, 40),
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return issues.slice(0, 20);
|
|
});
|
|
|
|
if (textIssues.length > 0) {
|
|
console.log(` ⚠ Found ${textIssues.length} potential issues:`);
|
|
textIssues.forEach((issue, i) => {
|
|
console.log(` ${i + 1}. [${issue.type}] ${issue.tag}: "${issue.text}"`);
|
|
if (issue.blendMode) console.log(` blendMode: ${issue.blendMode}`);
|
|
if (issue.opacity) console.log(` opacity: ${issue.opacity}`);
|
|
});
|
|
} else {
|
|
console.log(' ✓ No major text readability issues');
|
|
}
|
|
|
|
console.log('\n5. Checking glow/blur elements...');
|
|
const glowElements = await page.evaluate(() => {
|
|
const elements = [];
|
|
const allElements = document.querySelectorAll('*');
|
|
|
|
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.03) {
|
|
const rect = el.getBoundingClientRect();
|
|
if (rect.width > 150 && rect.height > 150) {
|
|
elements.push({
|
|
tag: el.tagName,
|
|
className: el.className.substring(0, 100),
|
|
opacity: opacity.toFixed(3),
|
|
hasBlur,
|
|
hasRadialGradient,
|
|
size: `${Math.round(rect.width)}x${Math.round(rect.height)}`,
|
|
position: style.position,
|
|
zIndex: style.zIndex || 'auto',
|
|
pointerEvents: style.pointerEvents,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return elements.sort((a, b) => parseFloat(b.opacity) - parseFloat(a.opacity)).slice(0, 12);
|
|
});
|
|
|
|
console.log(` Found ${glowElements.length} significant glow/blur elements:`);
|
|
glowElements.forEach((el, i) => {
|
|
const type = el.hasBlur && el.hasRadialGradient ? 'blur+gradient' : el.hasBlur ? 'blur' : 'gradient';
|
|
console.log(` ${i + 1}. ${el.tag} (${el.size}, ${type}, opacity: ${el.opacity})`);
|
|
console.log(` position: ${el.position}, zIndex: ${el.zIndex}, pointerEvents: ${el.pointerEvents}`);
|
|
console.log(` class: ${el.className.substring(0, 60)}`);
|
|
});
|
|
|
|
await browser.close();
|
|
|
|
console.log('\n========================================');
|
|
console.log(' Test Complete');
|
|
console.log('========================================');
|
|
console.log(` Screenshots saved to: ${screenshotsDir}`);
|
|
console.log('\n To view screenshots:');
|
|
console.log(` open ${screenshotsDir}`);
|
|
console.log('========================================');
|
|
}
|
|
|
|
runTests().catch(console.error);
|