docs(test): 添加设计文档、测试规范与 E2E 测试套件
- 新增 ADR 架构决策记录 (Design DNA 集成与深化) - 新增 CMS 系统设计文档 - 新增实施计划文档 (Phase1-3) - 新增 Bain 品牌升级设计规格 - 新增 E2E 分层测试套件 (P1-P4) - 新增视觉回归测试配置 - 新增光效分析、视觉验证等辅助脚本 - 更新验收测试报告
This commit is contained in:
@@ -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);
|
||||
})();
|
||||
Reference in New Issue
Block a user