import { test, expect, Page } from '@playwright/test'; test.describe.configure({ mode: 'parallel' }); 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' }, { 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: '团队介绍' }, ]; async function waitForPageStable(page: Page) { await page.waitForLoadState('networkidle'); await page.waitForTimeout(2000); 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); const primaryBtn = page.locator('button, a[role="button"]').first(); if (await primaryBtn.count() > 0 && await primaryBtn.isVisible()) { await expect(primaryBtn).toHaveScreenshot('button-default.png'); await primaryBtn.hover(); await page.waitForTimeout(300); await expect(primaryBtn).toHaveScreenshot('button-hover.png'); await primaryBtn.focus(); await page.waitForTimeout(200); await expect(primaryBtn).toHaveScreenshot('button-focus.png'); } }); test('导航菜单 - 桌面端视觉', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); const header = page.locator('header').first(); if (await header.isVisible()) { await expect(header).toHaveScreenshot('header-navigation.png'); } }); test('页脚区域 - 视觉一致性', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); const footer = page.locator('footer').first(); if (await footer.isVisible()) { await expect(footer).toHaveScreenshot('footer-section.png'); } }); test('卡片组件 - 视觉样式', async ({ page }) => { await page.goto('/products', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); const card = page.locator('[class*="card"]').first(); if (await card.count() > 0 && await card.isVisible()) { await expect(card).toHaveScreenshot('product-card.png'); } }); test('表单输入框 - 默认/聚焦状态', async ({ page }) => { await page.goto('/contact', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); const input = page.locator('input[type="text"], input[type="email"]').first(); if (await input.count() > 0 && await input.isVisible()) { await expect(input).toHaveScreenshot('input-default.png'); await input.click(); await page.waitForTimeout(200); await expect(input).toHaveScreenshot('input-focused.png'); } }); }); test.describe('L2: 主题切换视觉测试', () => { test.setTimeout(45000); test('浅色主题 - 首页视觉', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); const htmlEl = page.locator('html'); const currentTheme = await htmlEl.getAttribute('data-theme') || 'light'; if (currentTheme !== 'light') { await htmlEl.evaluate(el => el.setAttribute('data-theme', 'light')); await page.waitForTimeout(300); } await expect(page.locator('main').first()).toHaveScreenshot('theme-light-main.png'); }); test('深色主题 - 首页视觉', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); const htmlEl = page.locator('html'); await htmlEl.evaluate(el => el.setAttribute('data-theme', 'dark')); await page.waitForTimeout(500); await expect(page.locator('main').first()).toHaveScreenshot('theme-dark-main.png'); }); }); test.describe('L3: 排版与色彩验证', () => { test.setTimeout(30000); test('首页 H1 标题字体样式', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); const h1 = page.locator('h1').first(); if (await h1.count() > 0 && await h1.isVisible()) { const styles = await h1.evaluate(el => { const computed = window.getComputedStyle(el); return { fontSize: computed.fontSize, fontWeight: computed.fontWeight, fontFamily: computed.fontFamily, color: computed.color, lineHeight: computed.lineHeight, }; }); const size = parseFloat(styles.fontSize); // Support clamp()/variable-based font sizes that may resolve differently if (!isNaN(size)) { expect(size).toBeGreaterThanOrEqual(16); } expect(styles.color || styles.fontFamily).toBeTruthy(); } }); test('品牌主色调验证', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); await waitForPageStable(page); const primaryColor = await page.evaluate(() => { const style = getComputedStyle(document.documentElement); return { brand: style.getPropertyValue('--color-brand')?.trim(), ink: style.getPropertyValue('--color-ink')?.trim(), bg: style.getPropertyValue('--color-bg-primary')?.trim(), text: style.getPropertyValue('--color-text-primary')?.trim(), }; }); expect(primaryColor.brand || primaryColor.ink || primaryColor.bg).toBeTruthy(); }); });