Files
novalon-website/phase4-visual-test.mjs
zhangxiang 602ed6a671 test(hooks): finalize mutation test improvements and release acceptance
- Raise use-swipe-gesture mutation score to 66.13% (target 65%+)
- Maintain use-reduced-motion mutation score at 76.32% (target 50%+)
- Fix use-reduced-motion.ts ESLint set-state-in-effect warning
- Add UJ-10 deep searcher journey (category browse → article read → content discovery)
- Add 40 new test files (analytics, detail, sections, ui, lib components)
- Update test-strategy-plan.md to v2.0 (sync test count to 1591)
- Sync README.md with final release metrics

Quality gates: TS 0 errors, ESLint 0 errors, 121 suites / 1591 tests passed
2026-08-02 19:39:27 +08:00

136 lines
4.5 KiB
JavaScript

// @ts-nocheck
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);
})();