import { test, expect, Page } from '@playwright/test'; const ANIMATION_THRESHOLDS = { fps: 30, frameTime: 33.33, animationDuration: 500, }; interface AnimationMetrics { fps: number; frameTime: number; droppedFrames: number; totalFrames: number; } async function measureAnimationPerformance(page: Page, animationTrigger: () => Promise, duration: number = 1000): Promise { const frames: number[] = []; await page.evaluate(() => { window.performanceMetrics = { frames: [], startTime: performance.now(), }; }); const startTime = Date.now(); const frameCollector = setInterval(async () => { const timestamp = await page.evaluate(() => { const metrics = (window as any).performanceMetrics; if (metrics) { metrics.frames.push(performance.now() - metrics.startTime); } return performance.now(); }); frames.push(timestamp); }, 16); await animationTrigger(); await page.waitForTimeout(duration); clearInterval(frameCollector); const metrics = await page.evaluate(() => { const metrics = (window as any).performanceMetrics; return metrics ? metrics.frames : []; }); const totalFrames = metrics.length; const droppedFrames = metrics.filter((frameTime: number, index: number) => { if (index === 0) return false; return frameTime - metrics[index - 1] > 33.33; }).length; const fps = totalFrames / (duration / 1000); const frameTime = duration / totalFrames; return { fps, frameTime, droppedFrames, totalFrames, }; } async function measureThemeSwitchAnimation(page: Page): Promise { return await measureAnimationPerformance(page, async () => { await page.click('.theme-switch-btn'); }, 500); } async function measurePageTransitionAnimation(page: Page, targetUrl: string): Promise { return await measureAnimationPerformance(page, async () => { await page.click(`[data-href="${targetUrl}"]`); }, 500); } async function measureComponentAnimation(page: Page, selector: string): Promise { return await measureAnimationPerformance(page, async () => { await page.click(selector); }, 500); } test.describe('主题切换动画性能测试', () => { test('浅色主题切换到深色主题FPS应大于30', async ({ page }) => { await page.goto('/pages/user/index'); await page.waitForSelector('.theme-switch-btn'); const metrics = await measureThemeSwitchAnimation(page); console.log('主题切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('深色主题切换到浅色主题FPS应大于30', async ({ page }) => { await page.goto('/pages/user/index'); await page.waitForSelector('.theme-switch-btn'); await page.click('.theme-switch-btn'); await page.waitForTimeout(500); const metrics = await measureThemeSwitchAnimation(page); console.log('主题切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('主题切换掉帧数应小于5', async ({ page }) => { await page.goto('/pages/user/index'); await page.waitForSelector('.theme-switch-btn'); const metrics = await measureThemeSwitchAnimation(page); console.log('主题切换掉帧数:', metrics.droppedFrames); expect(metrics.droppedFrames).toBeLessThan(5); }); test('主题切换帧时间应小于33.33ms', async ({ page }) => { await page.goto('/pages/user/index'); await page.waitForSelector('.theme-switch-btn'); const metrics = await measureThemeSwitchAnimation(page); console.log('主题切换帧时间:', metrics.frameTime); expect(metrics.frameTime).toBeLessThan(ANIMATION_THRESHOLDS.frameTime); }); }); test.describe('页面切换动画性能测试', () => { test('首页切换到日历页FPS应大于30', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-href="/pages/calendar/index"]'); const metrics = await measurePageTransitionAnimation(page, '/pages/calendar/index'); console.log('页面切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('首页切换到黄历页FPS应大于30', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-href="/pages/almanac/index"]'); const metrics = await measurePageTransitionAnimation(page, '/pages/almanac/index'); console.log('页面切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('首页切换到用户页FPS应大于30', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-href="/pages/user/index"]'); const metrics = await measurePageTransitionAnimation(page, '/pages/user/index'); console.log('页面切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('首页切换到搜索页FPS应大于30', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-href="/pages/search/index"]'); const metrics = await measurePageTransitionAnimation(page, '/pages/search/index'); console.log('页面切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('日历页切换到黄历页FPS应大于30', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('[data-href="/pages/almanac/index"]'); const metrics = await measurePageTransitionAnimation(page, '/pages/almanac/index'); console.log('页面切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('黄历页切换到日历页FPS应大于30', async ({ page }) => { await page.goto('/pages/almanac/index'); await page.waitForSelector('[data-href="/pages/calendar/index"]'); const metrics = await measurePageTransitionAnimation(page, '/pages/calendar/index'); console.log('页面切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('页面切换掉帧数应小于5', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-href="/pages/calendar/index"]'); const metrics = await measurePageTransitionAnimation(page, '/pages/calendar/index'); console.log('页面切换掉帧数:', metrics.droppedFrames); expect(metrics.droppedFrames).toBeLessThan(5); }); test('页面切换帧时间应小于33.33ms', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-href="/pages/calendar/index"]'); const metrics = await measurePageTransitionAnimation(page, '/pages/calendar/index'); console.log('页面切换帧时间:', metrics.frameTime); expect(metrics.frameTime).toBeLessThan(ANIMATION_THRESHOLDS.frameTime); }); }); test.describe('组件动画性能测试', () => { test('日历月切换动画FPS应大于30', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-next-month'); const metrics = await measureComponentAnimation(page, '.calendar-next-month'); console.log('日历月切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('日历年切换动画FPS应大于30', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-year-selector'); await page.click('.calendar-year-selector'); await page.waitForSelector('.calendar-year-option[data-year="2027"]'); const metrics = await measureComponentAnimation(page, '.calendar-year-option[data-year="2027"]'); console.log('日历年切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('黄历月切换动画FPS应大于30', async ({ page }) => { await page.goto('/pages/almanac/index'); await page.waitForSelector('.almanac-next-month'); const metrics = await measureComponentAnimation(page, '.almanac-next-month'); console.log('黄历月切换动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('搜索建议展开动画FPS应大于30', async ({ page }) => { await page.goto('/pages/search/index'); await page.waitForSelector('.search-input'); await page.fill('.search-input', '春'); await page.waitForSelector('.search-suggestions'); const metrics = await measureComponentAnimation(page, '.search-suggestions'); console.log('搜索建议展开动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('搜索历史展开动画FPS应大于30', async ({ page }) => { await page.goto('/pages/search/index'); await page.waitForSelector('.search-history-btn'); const metrics = await measureComponentAnimation(page, '.search-history-btn'); console.log('搜索历史展开动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('搜索热门展开动画FPS应大于30', async ({ page }) => { await page.goto('/pages/search/index'); await page.waitForSelector('.search-hot-btn'); const metrics = await measureComponentAnimation(page, '.search-hot-btn'); console.log('搜索热门展开动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('用户设置展开动画FPS应大于30', async ({ page }) => { await page.goto('/pages/user/index'); await page.waitForSelector('.user-settings-btn'); const metrics = await measureComponentAnimation(page, '.user-settings-btn'); console.log('用户设置展开动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('组件动画掉帧数应小于5', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-next-month'); const metrics = await measureComponentAnimation(page, '.calendar-next-month'); console.log('组件动画掉帧数:', metrics.droppedFrames); expect(metrics.droppedFrames).toBeLessThan(5); }); test('组件动画帧时间应小于33.33ms', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-next-month'); const metrics = await measureComponentAnimation(page, '.calendar-next-month'); console.log('组件动画帧时间:', metrics.frameTime); expect(metrics.frameTime).toBeLessThan(ANIMATION_THRESHOLDS.frameTime); }); }); test.describe('滚动动画性能测试', () => { test('日历列表滚动FPS应大于30', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-container'); const metrics = await measureAnimationPerformance(page, async () => { await page.evaluate(() => { const container = document.querySelector('.calendar-container'); if (container) { container.scrollTop = 500; } }); }, 500); console.log('日历列表滚动动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('黄历列表滚动FPS应大于30', async ({ page }) => { await page.goto('/pages/almanac/index'); await page.waitForSelector('.almanac-container'); const metrics = await measureAnimationPerformance(page, async () => { await page.evaluate(() => { const container = document.querySelector('.almanac-container'); if (container) { container.scrollTop = 500; } }); }, 500); console.log('黄历列表滚动动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('搜索结果滚动FPS应大于30', async ({ page }) => { await page.goto('/pages/search/index'); await page.waitForSelector('.search-input'); await page.fill('.search-input', '春节'); await page.click('.search-btn'); await page.waitForSelector('.search-results'); const metrics = await measureAnimationPerformance(page, async () => { await page.evaluate(() => { const container = document.querySelector('.search-results'); if (container) { container.scrollTop = 500; } }); }, 500); console.log('搜索结果滚动动画指标:', metrics); expect(metrics.fps).toBeGreaterThan(ANIMATION_THRESHOLDS.fps); }); test('滚动掉帧数应小于5', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-container'); const metrics = await measureAnimationPerformance(page, async () => { await page.evaluate(() => { const container = document.querySelector('.calendar-container'); if (container) { container.scrollTop = 500; } }); }, 500); console.log('滚动掉帧数:', metrics.droppedFrames); expect(metrics.droppedFrames).toBeLessThan(5); }); test('滚动帧时间应小于33.33ms', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-container'); const metrics = await measureAnimationPerformance(page, async () => { await page.evaluate(() => { const container = document.querySelector('.calendar-container'); if (container) { container.scrollTop = 500; } }); }, 500); console.log('滚动帧时间:', metrics.frameTime); expect(metrics.frameTime).toBeLessThan(ANIMATION_THRESHOLDS.frameTime); }); });