diff --git a/e2e/src/tests/performance/core-web-vitals.spec.ts b/e2e/src/tests/performance/core-web-vitals.spec.ts new file mode 100644 index 0000000..796d652 --- /dev/null +++ b/e2e/src/tests/performance/core-web-vitals.spec.ts @@ -0,0 +1,288 @@ +import { test, expect } from '@playwright/test'; +import { HomePage } from '../../pages/HomePage'; +import { ContactPage } from '../../pages/ContactPage'; +import { PERFORMANCE_THRESHOLDS } from '../../data/test-data'; + +test.describe('Core Web Vitals性能测试', () => { + test.describe('首页性能测试', () => { + let homePage: HomePage; + + test.beforeEach(async ({ page }) => { + homePage = new HomePage(page); + }); + + test('应该满足页面加载时间阈值', async () => { + await homePage.goto(); + const performance = await homePage.measurePageLoadPerformance(); + + expect(performance.loadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.loadTime); + }); + + test('应该满足DOM内容加载时间阈值', async () => { + await homePage.goto(); + const performance = await homePage.measurePageLoadPerformance(); + + expect(performance.domContentLoaded).toBeLessThan(PERFORMANCE_THRESHOLDS.firstContentfulPaint); + }); + + test('应该满足首次内容绘制阈值', async () => { + await homePage.goto(); + const performance = await homePage.measurePageLoadPerformance(); + + expect(performance.firstContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.firstContentfulPaint); + }); + + test('应该满足最大内容绘制阈值', async () => { + await homePage.goto(); + const vitals = await homePage.getCoreWebVitals(); + + expect(vitals.largestContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.largestContentfulPaint); + }); + + test('应该满足可交互时间阈值', async () => { + await homePage.goto(); + const vitals = await homePage.getCoreWebVitals(); + + expect(vitals.largestContentfulPaint).toBeLessThan(PERFORMANCE_THRESHOLDS.timeToInteractive); + }); + + test('应该满足首次输入延迟阈值', async () => { + await homePage.goto(); + const vitals = await homePage.getCoreWebVitals(); + + expect(vitals.firstInputDelay).toBeLessThan(PERFORMANCE_THRESHOLDS.firstInputDelay); + }); + + test('应该满足累积布局偏移阈值', async () => { + await homePage.goto(); + const vitals = await homePage.getCoreWebVitals(); + + expect(vitals.cumulativeLayoutShift).toBeLessThan(PERFORMANCE_THRESHOLDS.cumulativeLayoutShift); + }); + }); + + test.describe('联系页面性能测试', () => { + let contactPage: ContactPage; + + test.beforeEach(async ({ page }) => { + contactPage = new ContactPage(page); + }); + + test('应该满足页面加载时间阈值', async () => { + await contactPage.goto(); + const performance = await contactPage.measurePerformance(); + + expect(performance.loadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.loadTime); + }); + + test('应该满足表单提交性能阈值', async () => { + await contactPage.goto(); + const performance = await contactPage.measureFormSubmissionPerformance(); + + expect(performance.fillTime).toBeLessThan(1000); + expect(performance.submitTime).toBeLessThan(2000); + expect(performance.totalTime).toBeLessThan(3000); + }); + }); + + test.describe('网络时序测试', () => { + let homePage: HomePage; + + test.beforeEach(async ({ page }) => { + homePage = new HomePage(page); + }); + + test('应该满足DNS查询时间阈值', async () => { + await homePage.goto(); + const timing = await homePage.getNetworkTiming(); + + expect(timing.dns).toBeLessThan(500); + }); + + test('应该满足TCP连接时间阈值', async () => { + await homePage.goto(); + const timing = await homePage.getNetworkTiming(); + + expect(timing.tcp).toBeLessThan(500); + }); + + test('应该满足SSL握手时间阈值', async () => { + await homePage.goto(); + const timing = await homePage.getNetworkTiming(); + + expect(timing.ssl).toBeLessThan(500); + }); + + test('应该满足请求时间阈值', async () => { + await homePage.goto(); + const timing = await homePage.getNetworkTiming(); + + expect(timing.request).toBeLessThan(1000); + }); + + test('应该满足响应时间阈值', async () => { + await homePage.goto(); + const timing = await homePage.getNetworkTiming(); + + expect(timing.response).toBeLessThan(1000); + }); + }); + + test.describe('资源加载测试', () => { + let homePage: HomePage; + + test.beforeEach(async ({ page }) => { + homePage = new HomePage(page); + }); + + test('应该加载所有关键资源', async () => { + await homePage.goto(); + const resources = await homePage.getResourceTiming(); + + const images = resources.filter(r => r.initiatorType === 'img'); + const scripts = resources.filter(r => r.initiatorType === 'script'); + const stylesheets = resources.filter(r => r.initiatorType === 'link'); + + expect(images.length).toBeGreaterThan(0); + expect(scripts.length).toBeGreaterThan(0); + expect(stylesheets.length).toBeGreaterThan(0); + }); + + test('应该满足资源加载时间阈值', async () => { + await homePage.goto(); + const resources = await homePage.getResourceTiming(); + + resources.forEach(resource => { + const loadTime = resource.responseEnd - resource.fetchStart; + expect(loadTime).toBeLessThan(5000); + }); + }); + + test('应该没有加载失败的资源', async () => { + await homePage.goto(); + const resources = await homePage.getResourceTiming(); + + const failedResources = resources.filter(r => r.transferSize === 0); + expect(failedResources.length).toBe(0); + }); + }); + + test.describe('滚动性能测试', () => { + let homePage: HomePage; + + test.beforeEach(async ({ page }) => { + homePage = new HomePage(page); + }); + + test('应该能够平滑滚动到各个区域', async () => { + await homePage.goto(); + + const scrollTimes: number[] = []; + const sections = ['services', 'products', 'cases', 'news', 'contact']; + + for (const section of sections) { + const startTime = Date.now(); + await homePage.scrollToSection(section); + const scrollTime = Date.now() - startTime; + scrollTimes.push(scrollTime); + } + + const avgScrollTime = scrollTimes.reduce((a, b) => a + b, 0) / scrollTimes.length; + expect(avgScrollTime).toBeLessThan(500); + }); + + test('应该能够快速滚动到页面底部', async () => { + await homePage.goto(); + + const startTime = Date.now(); + await homePage.scrollToBottom(); + const scrollTime = Date.now() - startTime; + + expect(scrollTime).toBeLessThan(1000); + }); + + test('应该能够快速滚动到页面顶部', async () => { + await homePage.goto(); + await homePage.scrollToBottom(); + + const startTime = Date.now(); + await homePage.scrollToTop(); + const scrollTime = Date.now() - startTime; + + expect(scrollTime).toBeLessThan(1000); + }); + }); + + test.describe('交互性能测试', () => { + let homePage: HomePage; + + test.beforeEach(async ({ page }) => { + homePage = new HomePage(page); + }); + + test('应该能够快速点击导航链接', async () => { + await homePage.goto(); + + const startTime = Date.now(); + await homePage.clickNavigationItem('服务'); + await homePage.waitForTimeout(500); + const clickTime = Date.now() - startTime; + + expect(clickTime).toBeLessThan(500); + }); + + test('应该能够快速点击联系按钮', async () => { + await homePage.goto(); + + const startTime = Date.now(); + await homePage.clickContactButton(); + await homePage.waitForLoadState('networkidle'); + const clickTime = Date.now() - startTime; + + expect(clickTime).toBeLessThan(1000); + }); + }); + + test.describe('性能预算测试', () => { + let homePage: HomePage; + + test.beforeEach(async ({ page }) => { + homePage = new HomePage(page); + }); + + test('应该满足总页面大小预算', async () => { + await homePage.goto(); + const resources = await homePage.getResourceTiming(); + + const totalSize = resources.reduce((sum, r) => sum + r.transferSize, 0); + expect(totalSize).toBeLessThan(1600000); + }); + + test('应该满足图片大小预算', async () => { + await homePage.goto(); + const resources = await homePage.getResourceTiming(); + + const images = resources.filter(r => r.initiatorType === 'img'); + const imageSize = images.reduce((sum, r) => sum + r.transferSize, 0); + expect(imageSize).toBeLessThan(500000); + }); + + test('应该满足脚本大小预算', async () => { + await homePage.goto(); + const resources = await homePage.getResourceTiming(); + + const scripts = resources.filter(r => r.initiatorType === 'script'); + const scriptSize = scripts.reduce((sum, r) => sum + r.transferSize, 0); + expect(scriptSize).toBeLessThan(300000); + }); + + test('应该满足样式表大小预算', async () => { + await homePage.goto(); + const resources = await homePage.getResourceTiming(); + + const stylesheets = resources.filter(r => r.initiatorType === 'link'); + const stylesheetSize = stylesheets.reduce((sum, r) => sum + r.transferSize, 0); + expect(stylesheetSize).toBeLessThan(100000); + }); + }); +});