import { test, expect, Page } from './fixtures'; test.describe.configure({ mode: 'parallel' }); // dev 指示器 的剥离已上移到 e2e/fixtures.ts 的 context 夹具,全站所有 project 共用。 // 基线口径固定为「用户已表达 Cookie 偏好」。同意条由 2s 定时器后才渲染,慢引擎(firefox / webkit) // 会在截图前把它画进首屏底部,快引擎(chromium)则赶不上 —— 这是与引擎速度相关的竞态, // 且这两个 project 为规避 newContext 兼容噪声而使用空 storageState(见 5a4d310),拿不到预置偏好。 // 时间戳取常量而非 Date.now(),避免基线随采集日漂移。 test.beforeEach(async ({ page }) => { await page.addInitScript(() => { localStorage.setItem( 'novalon-cookie-preferences', JSON.stringify({ necessary: true, analytics: false, marketing: false, functionality: true, timestamp: 1784284000000, }), ); }); }); const VISUAL_TEST_PAGES = [ { path: '/', name: 'home', label: '首页' }, { path: '/about', name: 'about', label: '关于我们' }, { path: '/contact', name: 'contact', label: '联系我们' }, { path: '/products', name: 'products', label: '产品中心' }, { path: '/products/erp', name: 'product-erp', label: '产品详情-ERP' }, // 与 /products/erp 是不同路由:此前只在 Lighthouse URL 表里,视觉表缺它 ⇒ 同型覆盖缺口(验收 §8 限制⑦) { path: '/products/erp-upgrade', name: 'product-erp-upgrade', label: '产品 ERP 升级页' }, { path: '/about/brand', name: 'about-brand', label: '品牌资产页' }, { path: '/solutions', name: 'solutions', label: '解决方案列表' }, { path: '/solutions/manufacturing', name: 'solution-manufacturing', label: '解决方案详情-制造' }, { path: '/services', name: 'services', label: '服务列表' }, { path: '/services/software', name: 'service-software', label: '服务详情-软件开发' }, { path: '/news', name: 'news', label: '新闻列表' }, { path: '/news/company-founded', name: 'news-detail', label: '新闻详情' }, { path: '/team', name: 'team', label: '团队介绍' }, { path: '/methodology', name: 'methodology', label: '方法论' }, ]; 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) { img.loading = 'eager'; } }); }); await page.waitForTimeout(1000); await page.evaluate(() => { document.fonts?.ready; }); await page.waitForTimeout(500); } test.describe('L1: 全页面视觉回归测试', () => { test.setTimeout(60000); for (const { path, name, label } of VISUAL_TEST_PAGES) { test(`${label} (${path}) - 全页截图对比`, async ({ page }) => { await page.goto(path, { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); await expect(page).toHaveScreenshot(`${name}-fullpage.png`, { fullPage: true, animations: 'disabled', caret: 'hide', }); }); } }); test.describe('L2: 组件视觉状态测试', () => { test.setTimeout(45000); test('按钮组件 - 默认/悬停/聚焦状态', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); // N-15(c) 诊断结论:**装配缺陷,不是产品缺陷**。 // 旧选择器 `button, a[role="button"]` 的 .first() 取的是 DOM 序第一个