import { test, expect, Page } from '@playwright/test'; const PERFORMANCE_THRESHOLDS = { pageLoadTime: 2000, firstContentfulPaint: 1000, largestContentfulPaint: 1500, timeToInteractive: 2000, cumulativeLayoutShift: 0.1, firstInputDelay: 100, }; interface PerformanceMetrics { pageLoadTime: number; firstContentfulPaint: number; largestContentfulPaint: number; timeToInteractive: number; cumulativeLayoutShift: number; firstInputDelay: number; } async function measurePagePerformance(page: Page, url: string): Promise { const startTime = Date.now(); await page.goto(url, { waitUntil: 'networkidle' }); const pageLoadTime = Date.now() - startTime; const metrics = await page.evaluate(() => { const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; const paint = performance.getEntriesByType('paint'); const fcp = paint.find(entry => entry.name === 'first-contentful-paint')?.startTime || 0; return { pageLoadTime: 0, firstContentfulPaint: fcp, largestContentfulPaint: 0, timeToInteractive: navigation.domInteractive - navigation.startTime, cumulativeLayoutShift: 0, firstInputDelay: 0, }; }); metrics.pageLoadTime = pageLoadTime; const lcp = await page.evaluate(() => { return new Promise((resolve) => { if (!('PerformanceObserver' in window)) { resolve(0); return; } const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; resolve(lastEntry.startTime); }); observer.observe({ entryTypes: ['largest-contentful-paint'] }); setTimeout(() => { observer.disconnect(); resolve(0); }, 5000); }); }); metrics.largestContentfulPaint = lcp; const cls = await page.evaluate(() => { return new Promise((resolve) => { if (!('PerformanceObserver' in window)) { resolve(0); return; } let clsValue = 0; const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsValue += (entry as any).value; } } }); observer.observe({ entryTypes: ['layout-shift'] }); setTimeout(() => { observer.disconnect(); resolve(clsValue); }, 5000); }); }); metrics.cumulativeLayoutShift = cls; const fid = await page.evaluate(() => { return new Promise((resolve) => { if (!('PerformanceObserver' in window)) { resolve(0); return; } const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { resolve((entry as any).processingStart - entry.startTime); } }); observer.observe({ entryTypes: ['first-input'] }); setTimeout(() => { observer.disconnect(); resolve(0); }, 5000); }); }); metrics.firstInputDelay = fid; return metrics; } test.describe('日历页面加载性能测试', () => { test('日历首页加载时间应小于2秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/calendar/index'); console.log('日历首页性能指标:', metrics); expect(metrics.pageLoadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('日历首页首次内容绘制应小于1秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/calendar/index'); console.log('日历首页FCP:', metrics.firstContentfulPaint); expect(metrics.firstContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.firstContentfulPaint); }); test('日历首页最大内容绘制应小于1.5秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/calendar/index'); console.log('日历首页LCP:', metrics.largestContentfulPaint); expect(metrics.largestContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.largestContentfulPaint); }); test('日历首页可交互时间应小于2秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/calendar/index'); console.log('日历首页TTI:', metrics.timeToInteractive); expect(metrics.timeToInteractive).toBeLessThan(PERFORMANCE_THRESHOLDS.timeToInteractive); }); test('日历首页累积布局偏移应小于0.1', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/calendar/index'); console.log('日历首页CLS:', metrics.cumulativeLayoutShift); expect(metrics.cumulativeLayoutShift).toBeLessThan(PERFORMANCE_THRESHOLDS.cumulativeLayoutShift); }); test('日历详情页加载时间应小于2秒', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.click('.calendar-day[data-date="2026-02-11"]'); const startTime = Date.now(); await page.waitForURL('**/calendar/detail**'); const loadTime = Date.now() - startTime; console.log('日历详情页加载时间:', loadTime); expect(loadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('日历月切换加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-container'); const startTime = Date.now(); await page.click('.calendar-next-month'); await page.waitForSelector('.calendar-container'); const loadTime = Date.now() - startTime; console.log('日历月切换加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); test('日历年切换加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-container'); const startTime = Date.now(); await page.click('.calendar-year-selector'); await page.click('.calendar-year-option[data-year="2027"]'); await page.waitForSelector('.calendar-container'); const loadTime = Date.now() - startTime; console.log('日历年切换加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); test('日历今日跳转加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-container'); const startTime = Date.now(); await page.click('.calendar-today-btn'); await page.waitForSelector('.calendar-container'); const loadTime = Date.now() - startTime; console.log('日历今日跳转加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); test('日历搜索结果加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/calendar/index'); await page.waitForSelector('.calendar-container'); await page.fill('.calendar-search-input', '2026-02-14'); const startTime = Date.now(); await page.click('.calendar-search-btn'); await page.waitForSelector('.calendar-day[data-date="2026-02-14"]'); const loadTime = Date.now() - startTime; console.log('日历搜索结果加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); }); test.describe('黄历页面加载性能测试', () => { test('黄历首页加载时间应小于2秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/almanac/index'); console.log('黄历首页性能指标:', metrics); expect(metrics.pageLoadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('黄历首页首次内容绘制应小于1秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/almanac/index'); console.log('黄历首页FCP:', metrics.firstContentfulPaint); expect(metrics.firstContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.firstContentfulPaint); }); test('黄历首页最大内容绘制应小于1.5秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/almanac/index'); console.log('黄历首页LCP:', metrics.largestContentfulPaint); expect(metrics.largestContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.largestContentfulPaint); }); test('黄历详情页加载时间应小于2秒', async ({ page }) => { await page.goto('/pages/almanac/index'); await page.click('.almanac-day[data-date="2026-02-11"]'); const startTime = Date.now(); await page.waitForURL('**/almanac/detail**'); const loadTime = Date.now() - startTime; console.log('黄历详情页加载时间:', loadTime); expect(loadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('黄历月切换加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/almanac/index'); await page.waitForSelector('.almanac-container'); const startTime = Date.now(); await page.click('.almanac-next-month'); await page.waitForSelector('.almanac-container'); const loadTime = Date.now() - startTime; console.log('黄历月切换加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); test('黄历宜忌筛选加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/almanac/index'); await page.waitForSelector('.almanac-container'); const startTime = Date.now(); await page.click('.almanac-filter-btn'); await page.click('.filter-checkbox[data-value="嫁娶"]'); await page.click('.filter-confirm-btn'); await page.waitForSelector('.almanac-container'); const loadTime = Date.now() - startTime; console.log('黄历宜忌筛选加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); test('黄历搜索结果加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/almanac/index'); await page.waitForSelector('.almanac-container'); await page.fill('.almanac-search-input', '春节'); const startTime = Date.now(); await page.click('.almanac-search-btn'); await page.waitForSelector('.almanac-search-result'); const loadTime = Date.now() - startTime; console.log('黄历搜索结果加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); }); test.describe('用户页面加载性能测试', () => { test('用户首页加载时间应小于2秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/user/index'); console.log('用户首页性能指标:', metrics); expect(metrics.pageLoadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('用户首页首次内容绘制应小于1秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/user/index'); console.log('用户首页FCP:', metrics.firstContentfulPaint); expect(metrics.firstContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.firstContentfulPaint); }); test('用户首页最大内容绘制应小于1.5秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/user/index'); console.log('用户首页LCP:', metrics.largestContentfulPaint); expect(metrics.largestContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.largestContentfulPaint); }); test('用户设置页加载时间应小于2秒', async ({ page }) => { await page.goto('/pages/user/index'); await page.click('.user-settings-btn'); const startTime = Date.now(); await page.waitForURL('**/user/settings**'); const loadTime = Date.now() - startTime; console.log('用户设置页加载时间:', loadTime); expect(loadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('用户收藏页加载时间应小于2秒', async ({ page }) => { await page.goto('/pages/user/index'); await page.click('.user-favorites-btn'); const startTime = Date.now(); await page.waitForURL('**/user/favorites**'); const loadTime = Date.now() - startTime; console.log('用户收藏页加载时间:', loadTime); expect(loadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('用户历史页加载时间应小于2秒', async ({ page }) => { await page.goto('/pages/user/index'); await page.click('.user-history-btn'); const startTime = Date.now(); await page.waitForURL('**/user/history**'); const loadTime = Date.now() - startTime; console.log('用户历史页加载时间:', loadTime); expect(loadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('用户主题切换加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/user/index'); await page.waitForSelector('.user-container'); const startTime = Date.now(); await page.click('.theme-switch-btn'); await page.waitForSelector('.user-container'); const loadTime = Date.now() - startTime; console.log('用户主题切换加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); }); test.describe('搜索页面加载性能测试', () => { test('搜索首页加载时间应小于2秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/search/index'); console.log('搜索首页性能指标:', metrics); expect(metrics.pageLoadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.pageLoadTime); }); test('搜索首页首次内容绘制应小于1秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/search/index'); console.log('搜索首页FCP:', metrics.firstContentfulPaint); expect(metrics.firstContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.firstContentfulPaint); }); test('搜索首页最大内容绘制应小于1.5秒', async ({ page }) => { const metrics = await measurePagePerformance(page, '/pages/search/index'); console.log('搜索首页LCP:', metrics.largestContentfulPaint); expect(metrics.largestContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.largestContentfulPaint); }); test('搜索结果加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/search/index'); await page.waitForSelector('.search-input'); await page.fill('.search-input', '春节'); const startTime = Date.now(); await page.click('.search-btn'); await page.waitForSelector('.search-results'); const loadTime = Date.now() - startTime; console.log('搜索结果加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); test('搜索建议加载时间应小于500毫秒', async ({ page }) => { await page.goto('/pages/search/index'); await page.waitForSelector('.search-input'); await page.fill('.search-input', '春'); const startTime = Date.now(); await page.waitForSelector('.search-suggestions'); const loadTime = Date.now() - startTime; console.log('搜索建议加载时间:', loadTime); expect(loadTime).toBeLessThan(500); }); test('搜索历史加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/search/index'); await page.waitForSelector('.search-input'); const startTime = Date.now(); await page.click('.search-history-btn'); await page.waitForSelector('.search-history-list'); const loadTime = Date.now() - startTime; console.log('搜索历史加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); test('搜索热门加载时间应小于1秒', async ({ page }) => { await page.goto('/pages/search/index'); await page.waitForSelector('.search-input'); const startTime = Date.now(); await page.click('.search-hot-btn'); await page.waitForSelector('.search-hot-list'); const loadTime = Date.now() - startTime; console.log('搜索热门加载时间:', loadTime); expect(loadTime).toBeLessThan(1000); }); test('搜索分类切换加载时间应小于500毫秒', 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 startTime = Date.now(); await page.click('.search-category[data-category="almanac"]'); await page.waitForSelector('.search-results'); const loadTime = Date.now() - startTime; console.log('搜索分类切换加载时间:', loadTime); expect(loadTime).toBeLessThan(500); }); });