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.
This commit is contained in:
2026-09-01 10:33:21 +08:00
parent b80db75c25
commit f744078c28
8 changed files with 49 additions and 20 deletions
+8
View File
@@ -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',
},
+19
View File
@@ -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);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

+22 -20
View File
@@ -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>