fix(visual): make founder quote visible in visual regression snapshots
- Set reducedMotion:'reduce' on all visual regression projects. - ScrollReveal/StaggerReveal now render visible by default. - Scroll to bottom then top in waitForPageStable before screenshot. - Add L2 element snapshot for founder-quote-section. - Redesign quote as light card (bg-bg-primary/95) with ink text. - Verified: type-check ok; lint 0 errors; visual:update 23/23.
@@ -51,6 +51,10 @@ export default defineConfig({
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
viewport: { width: 1280, height: 800 },
|
||||
// 视觉回归确定性:reducedMotion 让 ScrollReveal/StaggerReveal 走 initial={} 分支
|
||||
// 默认可见(不残留 opacity:0),避免全页截图时 below-the-fold 模块因 whileInView
|
||||
// 未触发而空白。同时使 Hero 粒子场走固定种子的静态降级帧,快照零漂移。
|
||||
reducedMotion: 'reduce',
|
||||
},
|
||||
testMatch: 'visual-regression.spec.ts',
|
||||
},
|
||||
@@ -59,6 +63,7 @@ export default defineConfig({
|
||||
use: {
|
||||
...devices['iPad Pro 11'],
|
||||
viewport: { width: 834, height: 1194 },
|
||||
reducedMotion: 'reduce',
|
||||
},
|
||||
testMatch: 'visual-regression.spec.ts',
|
||||
},
|
||||
@@ -69,6 +74,7 @@ export default defineConfig({
|
||||
viewport: { width: 390, height: 844 },
|
||||
isMobile: true,
|
||||
hasTouch: true,
|
||||
reducedMotion: 'reduce',
|
||||
},
|
||||
testMatch: 'visual-regression.spec.ts',
|
||||
},
|
||||
@@ -78,6 +84,7 @@ export default defineConfig({
|
||||
...devices['Desktop Firefox'],
|
||||
viewport: { width: 1280, height: 800 },
|
||||
storageState: './storageState.firefox.json',
|
||||
reducedMotion: 'reduce',
|
||||
},
|
||||
testMatch: 'visual-regression.spec.ts',
|
||||
},
|
||||
@@ -87,6 +94,7 @@ export default defineConfig({
|
||||
...devices['Desktop Safari'],
|
||||
viewport: { width: 1280, height: 800 },
|
||||
storageState: './storageState.firefox.json',
|
||||
reducedMotion: 'reduce',
|
||||
},
|
||||
testMatch: 'visual-regression.spec.ts',
|
||||
},
|
||||
|
||||
@@ -21,6 +21,16 @@ const VISUAL_TEST_PAGES = [
|
||||
async function waitForPageStable(page: Page) {
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 触发全页 ScrollReveal / StaggerReveal:先滚到底再回顶,让所有 below-the-fold
|
||||
// 模块的 whileInView 动画进入最终态,避免全页截图时内容停在 opacity:0 初始态。
|
||||
// 视觉回归项目已统一 reducedMotion:'reduce',此滚动作为双保险,覆盖任何媒体查询
|
||||
// 在 viewport resize 期间短暂失真的边界情况。
|
||||
await page.evaluate(() => window.scrollTo({ top: document.body.scrollHeight, behavior: 'instant' }));
|
||||
await page.waitForTimeout(800);
|
||||
await page.evaluate(() => window.scrollTo({ top: 0, behavior: 'instant' }));
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.evaluate(() => {
|
||||
document.querySelectorAll('img').forEach(img => {
|
||||
if (!img.complete) {
|
||||
@@ -103,6 +113,15 @@ test.describe('L2: 组件视觉状态测试', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('创始团队观点 - 桌面端视觉', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
||||
await waitForPageStable(page);
|
||||
|
||||
const founder = page.locator('[data-testid="founder-quote-section"]');
|
||||
await founder.scrollIntoViewIfNeeded();
|
||||
await expect(founder).toHaveScreenshot('founder-quote-section.png');
|
||||
});
|
||||
|
||||
test('表单输入框 - 默认/聚焦状态', async ({ page }) => {
|
||||
await page.goto('/contact', { waitUntil: 'domcontentloaded' });
|
||||
await waitForPageStable(page);
|
||||
|
||||
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
@@ -637,27 +637,29 @@ function FounderQuoteSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||||
/>
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<ScrollReveal className="max-w-4xl mx-auto text-center">
|
||||
<div className="flex items-center justify-center gap-3 mb-8">
|
||||
<div className="w-10 h-px bg-white/40" />
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-white/80">
|
||||
{eyebrow}
|
||||
</span>
|
||||
<div className="w-10 h-px bg-white/40" />
|
||||
</div>
|
||||
<Quote className="w-10 h-10 mx-auto mb-8 text-white/40" aria-hidden="true" />
|
||||
<blockquote className="text-2xl sm:text-3xl md:text-4xl lg:text-[2.75rem] font-bold text-white leading-[1.2] tracking-tight mb-10">
|
||||
{quoteText}
|
||||
</blockquote>
|
||||
{(quoteName || quoteRole) && (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
{quoteName && (
|
||||
<div className="text-base font-semibold text-white">{quoteName}</div>
|
||||
)}
|
||||
{quoteRole && (
|
||||
<div className="text-sm text-white/70">{quoteRole}</div>
|
||||
)}
|
||||
<div className="bg-bg-primary/95 rounded-2xl p-8 sm:p-10 md:p-12 max-w-3xl mx-auto shadow-xl">
|
||||
<div className="flex items-center justify-center gap-3 mb-8">
|
||||
<div className="w-10 h-px bg-brand/40" />
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
{eyebrow}
|
||||
</span>
|
||||
<div className="w-10 h-px bg-brand/40" />
|
||||
</div>
|
||||
)}
|
||||
<Quote className="w-10 h-10 mx-auto mb-8 text-brand/40" aria-hidden="true" />
|
||||
<blockquote className="text-2xl sm:text-3xl md:text-4xl font-semibold text-ink leading-snug tracking-tight mb-10 border-l-0 pl-0 m-0">
|
||||
{quoteText}
|
||||
</blockquote>
|
||||
{(quoteName || quoteRole) && (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
{quoteName && (
|
||||
<div className="text-base font-semibold text-ink">{quoteName}</div>
|
||||
)}
|
||||
{quoteRole && (
|
||||
<div className="text-sm text-text-secondary">{quoteRole}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</section>
|
||||
|
||||