feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
# 性能测试文档
|
||||
|
||||
## 概述
|
||||
|
||||
性能测试用于评估系统在不同负载条件下的性能表现,包括页面加载速度、API响应时间、并发处理能力和资源使用情况。
|
||||
|
||||
## 测试框架
|
||||
|
||||
性能测试基于 Playwright 框架,提供以下功能:
|
||||
- 页面加载性能测试
|
||||
- API响应性能测试
|
||||
- 并发和负载测试
|
||||
- 内存使用监控
|
||||
- 网络请求统计
|
||||
|
||||
### 核心文件
|
||||
|
||||
- `performance.spec.ts`: 页面加载性能测试和性能指标收集工具
|
||||
- `api-performance.spec.ts`: API响应性能测试
|
||||
- `concurrency-performance.spec.ts`: 并发和负载测试
|
||||
|
||||
## 运行性能测试
|
||||
|
||||
### 前置条件
|
||||
|
||||
1. 确保后端服务已启动
|
||||
2. 确保前端开发服务器已启动
|
||||
3. 确保数据库已初始化测试数据
|
||||
4. 确保系统资源充足(CPU、内存、网络)
|
||||
|
||||
### 运行所有性能测试
|
||||
|
||||
```bash
|
||||
npx playwright test e2e/performance
|
||||
```
|
||||
|
||||
### 运行特定性能测试套件
|
||||
|
||||
```bash
|
||||
# 运行页面加载性能测试
|
||||
npx playwright test e2e/performance/performance.spec.ts
|
||||
|
||||
# 运行API响应性能测试
|
||||
npx playwright test e2e/performance/api-performance.spec.ts
|
||||
|
||||
# 运行并发和负载测试
|
||||
npx playwright test e2e/performance/concurrency-performance.spec.ts
|
||||
```
|
||||
|
||||
### 运行性能测试(UI模式)
|
||||
|
||||
```bash
|
||||
npx playwright test e2e/performance --ui
|
||||
```
|
||||
|
||||
### 调试性能测试
|
||||
|
||||
```bash
|
||||
npx playwright test e2e/performance --debug
|
||||
```
|
||||
|
||||
## 性能指标
|
||||
|
||||
### 性能指标类
|
||||
|
||||
`PerformanceMetrics` 类提供以下统计方法:
|
||||
|
||||
- `getAverage(name)`: 获取平均值
|
||||
- `getP95(name)`: 获取95百分位值
|
||||
- `getP99(name)`: 获取99百分位值
|
||||
- `getMax(name)`: 获取最大值
|
||||
- `getMin(name)`: 获取最小值
|
||||
|
||||
### 性能测试辅助类
|
||||
|
||||
`PerformanceTestHelper` 类提供以下辅助方法:
|
||||
|
||||
- `measurePageLoad(page, url)`: 测量页面加载时间
|
||||
- `measureApiCall(page, apiCall)`: 测量API调用时间
|
||||
- `measureElementInteraction(page, selector, action)`: 测量元素交互时间
|
||||
- `measurePageNavigation(page, fromUrl, toUrl)`: 测量页面导航时间
|
||||
- `measureMemoryUsage(page)`: 测量内存使用情况
|
||||
- `measureNetworkRequests(page)`: 测量网络请求数量
|
||||
|
||||
## 性能测试用例
|
||||
|
||||
### PT-001: 页面加载性能
|
||||
|
||||
| 用例ID | 用例名称 | 性能目标 |
|
||||
|---------|---------|---------|
|
||||
| PT-001 | 登录页面加载性能 | < 3000ms |
|
||||
| PT-002 | 仪表盘页面加载性能 | < 2000ms |
|
||||
| PT-003 | 用户管理页面加载性能 | < 2000ms |
|
||||
| PT-004 | 黄历页面加载性能 | < 2000ms |
|
||||
| PT-005 | 运势页面加载性能 | < 2000ms |
|
||||
|
||||
### PT-006: API响应性能
|
||||
|
||||
| 用例ID | 用例名称 | 性能目标 |
|
||||
|---------|---------|---------|
|
||||
| PT-006 | 用户登录API性能 | < 2000ms |
|
||||
| PT-007 | 获取用户列表API性能 | < 1500ms |
|
||||
| PT-008 | 创建用户API性能 | < 2000ms |
|
||||
| PT-009 | 黄历查询API性能 | < 1500ms |
|
||||
| PT-010 | 运势查询API性能 | < 2000ms |
|
||||
| PT-011 | 紫微斗数生成API性能 | < 3000ms |
|
||||
|
||||
### PT-012: 并发和负载测试
|
||||
|
||||
| 用例ID | 用例名称 | 性能目标 |
|
||||
|---------|---------|---------|
|
||||
| PT-012 | 并发登录测试 | 平均响应时间 < 5000ms |
|
||||
| PT-013 | 并发黄历查询测试 | 平均响应时间 < 3000ms |
|
||||
| PT-014 | 页面切换性能测试 | 页面切换 < 1000ms |
|
||||
| PT-015 | 表单提交性能测试 | 表单提交 < 1000ms |
|
||||
| PT-016 | 内存使用监控 | 内存增长 < 50MB |
|
||||
| PT-017 | 网络请求统计 | 平均请求次数 < 20 |
|
||||
|
||||
## 性能基准
|
||||
|
||||
### 页面加载性能基准
|
||||
|
||||
- **优秀**: < 1000ms
|
||||
- **良好**: 1000ms - 2000ms
|
||||
- **可接受**: 2000ms - 3000ms
|
||||
- **需要优化**: > 3000ms
|
||||
|
||||
### API响应性能基准
|
||||
|
||||
- **优秀**: < 500ms
|
||||
- **良好**: 500ms - 1000ms
|
||||
- **可接受**: 1000ms - 2000ms
|
||||
- **需要优化**: > 2000ms
|
||||
|
||||
### 并发性能基准
|
||||
|
||||
- **优秀**: 平均响应时间 < 1000ms
|
||||
- **良好**: 平均响应时间 1000ms - 3000ms
|
||||
- **可接受**: 平均响应时间 3000ms - 5000ms
|
||||
- **需要优化**: 平均响应时间 > 5000ms
|
||||
|
||||
## 性能报告
|
||||
|
||||
性能测试执行后会生成以下报告:
|
||||
|
||||
1. **控制台输出**: 实时显示性能指标
|
||||
2. **HTML报告**: `playwright-report/index.html`
|
||||
3. **JSON报告**: `test-results/results.json`
|
||||
|
||||
### 性能报告示例
|
||||
|
||||
```
|
||||
=== 性能测试报告 ===
|
||||
|
||||
登录页面加载时间:
|
||||
平均值: 1250.50ms
|
||||
P95: 1450.00ms
|
||||
P99: 1520.00ms
|
||||
最大值: 1600.00ms
|
||||
最小值: 1100.00ms
|
||||
样本数: 10
|
||||
|
||||
仪表盘页面加载时间:
|
||||
平均值: 890.30ms
|
||||
P95: 980.00ms
|
||||
P99: 1020.00ms
|
||||
最大值: 1100.00ms
|
||||
最小值: 750.00ms
|
||||
样本数: 10
|
||||
|
||||
====================
|
||||
```
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 页面加载优化
|
||||
|
||||
1. **代码分割**: 使用动态导入减少初始加载时间
|
||||
2. **资源压缩**: 启用Gzip/Brotli压缩
|
||||
3. **CDN加速**: 使用CDN分发静态资源
|
||||
4. **缓存策略**: 实现合理的缓存策略
|
||||
5. **图片优化**: 使用WebP格式和懒加载
|
||||
|
||||
### API响应优化
|
||||
|
||||
1. **数据库优化**: 添加索引、优化查询
|
||||
2. **缓存机制**: 使用Redis缓存热点数据
|
||||
3. **异步处理**: 使用消息队列处理耗时操作
|
||||
4. **连接池**: 优化数据库连接池配置
|
||||
5. **分页查询**: 避免返回大量数据
|
||||
|
||||
### 并发处理优化
|
||||
|
||||
1. **负载均衡**: 使用负载均衡器分发请求
|
||||
2. **限流措施**: 实现API限流保护
|
||||
3. **连接复用**: 使用HTTP/2和连接复用
|
||||
4. **资源隔离**: 隔离不同服务的资源
|
||||
5. **自动扩容**: 实现自动扩容机制
|
||||
|
||||
## 性能监控
|
||||
|
||||
### 持续监控
|
||||
|
||||
建议在生产环境中实施以下监控:
|
||||
|
||||
1. **APM工具**: 使用New Relic、Datadog等APM工具
|
||||
2. **日志分析**: 使用ELK Stack分析日志
|
||||
3. **指标收集**: 使用Prometheus收集指标
|
||||
4. **告警机制**: 设置性能告警阈值
|
||||
5. **定期报告**: 生成定期性能报告
|
||||
|
||||
### 性能指标
|
||||
|
||||
建议监控以下关键指标:
|
||||
|
||||
1. **响应时间**: P50、P95、P99响应时间
|
||||
2. **吞吐量**: 每秒请求数(RPS)
|
||||
3. **错误率**: HTTP错误率
|
||||
4. **资源使用**: CPU、内存、磁盘使用率
|
||||
5. **网络流量**: 入站和出站流量
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 常见性能问题
|
||||
|
||||
1. **页面加载缓慢**
|
||||
- 检查网络带宽
|
||||
- 检查服务器资源使用
|
||||
- 检查资源加载顺序
|
||||
- 使用浏览器开发者工具分析
|
||||
|
||||
2. **API响应缓慢**
|
||||
- 检查数据库查询性能
|
||||
- 检查缓存命中率
|
||||
- 检查网络延迟
|
||||
- 检查并发连接数
|
||||
|
||||
3. **内存泄漏**
|
||||
- 检查内存使用趋势
|
||||
- 检查对象引用
|
||||
- 使用内存分析工具
|
||||
- 优化数据结构
|
||||
|
||||
4. **并发性能差**
|
||||
- 检查线程池配置
|
||||
- 检查锁竞争
|
||||
- 检查资源争用
|
||||
- 优化并发算法
|
||||
|
||||
## 性能测试最佳实践
|
||||
|
||||
1. **测试环境**: 使用与生产环境相似的测试环境
|
||||
2. **测试数据**: 使用真实大小的测试数据
|
||||
3. **多次运行**: 每个测试运行多次取平均值
|
||||
4. **基线对比**: 与历史基线对比性能变化
|
||||
5. **持续监控**: 建立持续性能监控机制
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题,请联系测试团队或查看项目文档。
|
||||
@@ -0,0 +1,110 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/login-page';
|
||||
import { DashboardPage } from '../pages/dashboard-page';
|
||||
import { UserManagementPage } from '../pages/user-management-page';
|
||||
import { testConfig } from '../core/test-config';
|
||||
import { PerformanceMetrics, PerformanceTestHelper } from '../helpers/performance-helper';
|
||||
|
||||
test.describe.configure({
|
||||
mode: 'serial',
|
||||
timeout: 120000
|
||||
});
|
||||
|
||||
test.describe('性能测试 - API响应性能', () => {
|
||||
const metrics = new PerformanceMetrics();
|
||||
const helper = new PerformanceTestHelper();
|
||||
|
||||
test.afterAll(() => {
|
||||
metrics.printReport();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
});
|
||||
|
||||
test('PT-006: 用户登录API性能', async ({ page }) => {
|
||||
await helper.clearCacheAndCookies(page);
|
||||
|
||||
const responseTime = await helper.measureApiCall(page, async () => {
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.login('admin', 'admin123');
|
||||
});
|
||||
|
||||
metrics.recordMetric('用户登录API响应时间', responseTime);
|
||||
expect(responseTime).toBeLessThan(2000);
|
||||
});
|
||||
|
||||
test('PT-007: 获取用户列表API性能', async ({ page }) => {
|
||||
const responseTime = await helper.measureApiCall(page, async () => {
|
||||
await page.goto(`${testConfig.getBaseURL()}/users`);
|
||||
await page.waitForSelector('[data-testid="user-table"]');
|
||||
});
|
||||
|
||||
metrics.recordMetric('获取用户列表API响应时间', responseTime);
|
||||
expect(responseTime).toBeLessThan(1500);
|
||||
});
|
||||
|
||||
test('PT-008: 创建用户API性能', async ({ page }) => {
|
||||
const userManagementPage = new UserManagementPage(page);
|
||||
const testUsername = `perfuser_${Date.now()}`;
|
||||
|
||||
const responseTime = await helper.measureApiCall(page, async () => {
|
||||
await page.goto(`${testConfig.getBaseURL()}/users`);
|
||||
await userManagementPage.clickAddUser();
|
||||
await userManagementPage.fillUserForm({
|
||||
username: testUsername,
|
||||
email: `perfuser_${Date.now()}@example.com`,
|
||||
password: 'Test@123456',
|
||||
confirmPassword: 'Test@123456',
|
||||
role: 'USER',
|
||||
status: 'ACTIVE'
|
||||
});
|
||||
await userManagementPage.submitUserForm();
|
||||
await page.waitForSelector('.ant-message-success');
|
||||
});
|
||||
|
||||
metrics.recordMetric('创建用户API响应时间', responseTime);
|
||||
expect(responseTime).toBeLessThan(2000);
|
||||
});
|
||||
|
||||
test('PT-009: 黄历查询API性能', async ({ page }) => {
|
||||
const responseTime = await helper.measureApiCall(page, async () => {
|
||||
await page.goto(`${testConfig.getBaseURL()}/almanac`);
|
||||
await page.fill('[data-testid="date-picker"]', '2024-01-01');
|
||||
await page.click('[data-testid="query-button"]');
|
||||
await page.waitForSelector('[data-testid="almanac-result"]');
|
||||
});
|
||||
|
||||
metrics.recordMetric('黄历查询API响应时间', responseTime);
|
||||
expect(responseTime).toBeLessThan(1500);
|
||||
});
|
||||
|
||||
test('PT-010: 运势查询API性能', async ({ page }) => {
|
||||
const responseTime = await helper.measureApiCall(page, async () => {
|
||||
await page.goto(`${testConfig.getBaseURL()}/fortune`);
|
||||
await page.fill('[data-testid="fortune-date"]', '2024-01-15');
|
||||
await page.click('[data-testid="query-fortune-button"]');
|
||||
await page.waitForSelector('[data-testid="daily-fortune"]');
|
||||
});
|
||||
|
||||
metrics.recordMetric('运势查询API响应时间', responseTime);
|
||||
expect(responseTime).toBeLessThan(2000);
|
||||
});
|
||||
|
||||
test('PT-011: 紫微斗数生成API性能', async ({ page }) => {
|
||||
const responseTime = await helper.measureApiCall(page, async () => {
|
||||
await page.goto(`${testConfig.getBaseURL()}/ziwei`);
|
||||
await page.fill('[data-testid="birth-date"]', '1990-05-15');
|
||||
await page.fill('[data-testid="birth-time"]', '08:30');
|
||||
await page.click('[data-testid="gender-male"]');
|
||||
await page.click('[data-testid="generate-chart-button"]');
|
||||
await page.waitForSelector('[data-testid="ziwei-chart"]');
|
||||
});
|
||||
|
||||
metrics.recordMetric('紫微斗数生成API响应时间', responseTime);
|
||||
expect(responseTime).toBeLessThan(3000);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,203 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/login-page';
|
||||
import { testConfig } from '../core/test-config';
|
||||
import { PerformanceMetrics, PerformanceTestHelper } from '../helpers/performance-helper';
|
||||
|
||||
test.describe.configure({
|
||||
mode: 'serial',
|
||||
timeout: 180000
|
||||
});
|
||||
|
||||
test.describe('性能测试 - 并发和负载测试', () => {
|
||||
const metrics = new PerformanceMetrics();
|
||||
const helper = new PerformanceTestHelper();
|
||||
|
||||
test.afterAll(() => {
|
||||
metrics.printReport();
|
||||
});
|
||||
|
||||
test('PT-012: 并发登录测试', async ({ browser }) => {
|
||||
const concurrency = 5;
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
for (let i = 0; i < concurrency; i++) {
|
||||
promises.push(
|
||||
(async () => {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.login(`user${i}`, `password${i}`);
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
metrics.recordMetric(`并发登录用户${i}响应时间`, duration);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const averageTime = metrics.getAverage('并发登录用户0响应时间');
|
||||
expect(averageTime).toBeLessThan(5000);
|
||||
});
|
||||
|
||||
test('PT-013: 并发黄历查询测试', async ({ browser }) => {
|
||||
const concurrency = 10;
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
for (let i = 0; i < concurrency; i++) {
|
||||
promises.push(
|
||||
(async () => {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await page.goto(`${testConfig.getBaseURL()}/almanac`);
|
||||
await page.fill('[data-testid="date-picker"]', `2024-01-${(i % 30) + 1}`);
|
||||
await page.click('[data-testid="query-button"]');
|
||||
await page.waitForSelector('[data-testid="almanac-result"]');
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
metrics.recordMetric(`并发黄历查询${i}响应时间`, duration);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const averageTime = metrics.getAverage('并发黄历查询0响应时间');
|
||||
expect(averageTime).toBeLessThan(3000);
|
||||
});
|
||||
|
||||
test('PT-014: 页面切换性能测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
const pages = [
|
||||
'/dashboard',
|
||||
'/users',
|
||||
'/almanac',
|
||||
'/fortune',
|
||||
'/ziwei'
|
||||
];
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (const pagePath of pages) {
|
||||
const startTime = Date.now();
|
||||
await page.goto(`${testConfig.getBaseURL()}${pagePath}`, { waitUntil: 'networkidle' });
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
metrics.recordMetric(`页面切换-${pagePath}`, duration);
|
||||
}
|
||||
}
|
||||
|
||||
const averageDashboardTime = metrics.getAverage('页面切换-/dashboard');
|
||||
const averageUsersTime = metrics.getAverage('页面切换-/users');
|
||||
const averageAlmanacTime = metrics.getAverage('页面切换-/almanac');
|
||||
|
||||
expect(averageDashboardTime).toBeLessThan(1000);
|
||||
expect(averageUsersTime).toBeLessThan(1000);
|
||||
expect(averageAlmanacTime).toBeLessThan(1000);
|
||||
});
|
||||
|
||||
test('PT-015: 表单提交性能测试', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
await page.goto(`${testConfig.getBaseURL()}/almanac`);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const startTime = Date.now();
|
||||
await page.fill('[data-testid="date-picker"]', `2024-01-${(i % 30) + 1}`);
|
||||
await page.click('[data-testid="query-button"]');
|
||||
await page.waitForSelector('[data-testid="almanac-result"]');
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
metrics.recordMetric(`表单提交-${i}`, duration);
|
||||
}
|
||||
|
||||
const averageTime = metrics.getAverage('表单提交-0');
|
||||
expect(averageTime).toBeLessThan(1000);
|
||||
});
|
||||
|
||||
test('PT-016: 内存使用监控', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
const memoryUsages: number[] = [];
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const metrics = await helper.measureMemoryUsage(page);
|
||||
memoryUsages.push(metrics.used);
|
||||
|
||||
await page.goto(`${testConfig.getBaseURL()}/almanac`);
|
||||
await page.fill('[data-testid="date-picker"]', `2024-01-${(i % 30) + 1}`);
|
||||
await page.click('[data-testid="query-button"]');
|
||||
await page.waitForSelector('[data-testid="almanac-result"]');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
const maxMemory = Math.max(...memoryUsages);
|
||||
const minMemory = Math.min(...memoryUsages);
|
||||
const memoryGrowth = maxMemory - minMemory;
|
||||
|
||||
console.log(`\n内存使用统计:`);
|
||||
console.log(` 最小值: ${(minMemory / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(` 最大值: ${(maxMemory / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(` 增长: ${(memoryGrowth / 1024 / 1024).toFixed(2)} MB`);
|
||||
|
||||
expect(memoryGrowth).toBeLessThan(50 * 1024 * 1024);
|
||||
});
|
||||
|
||||
test('PT-017: 网络请求统计', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
const requestCounts: number[] = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let requestCount = 0;
|
||||
|
||||
page.on('request', () => {
|
||||
requestCount++;
|
||||
});
|
||||
|
||||
await page.goto(`${testConfig.getBaseURL()}/almanac`);
|
||||
await page.fill('[data-testid="date-picker"]', `2024-01-${(i % 30) + 1}`);
|
||||
await page.click('[data-testid="query-button"]');
|
||||
await page.waitForSelector('[data-testid="almanac-result"]');
|
||||
|
||||
requestCounts.push(requestCount);
|
||||
metrics.recordMetric(`页面请求次数-${i}`, requestCount);
|
||||
}
|
||||
|
||||
const averageRequests = metrics.getAverage('页面请求次数-0');
|
||||
console.log(`\n网络请求统计:`);
|
||||
console.log(` 平均请求次数: ${averageRequests.toFixed(0)}`);
|
||||
|
||||
expect(averageRequests).toBeLessThan(20);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/login-page';
|
||||
import { testConfig } from '../core/test-config';
|
||||
|
||||
export class PerformanceMetrics {
|
||||
private metrics: Map<string, number[]> = new Map();
|
||||
|
||||
recordMetric(name: string, value: number) {
|
||||
if (!this.metrics.has(name)) {
|
||||
this.metrics.set(name, []);
|
||||
}
|
||||
this.metrics.get(name)!.push(value);
|
||||
}
|
||||
|
||||
getAverage(name: string): number {
|
||||
const values = this.metrics.get(name) || [];
|
||||
if (values.length === 0) return 0;
|
||||
const sum = values.reduce((a, b) => a + b, 0);
|
||||
return sum / values.length;
|
||||
}
|
||||
|
||||
getP95(name: string): number {
|
||||
const values = this.metrics.get(name) || [];
|
||||
if (values.length === 0) return 0;
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const index = Math.floor(sorted.length * 0.95);
|
||||
return sorted[index];
|
||||
}
|
||||
|
||||
getP99(name: string): number {
|
||||
const values = this.metrics.get(name) || [];
|
||||
if (values.length === 0) return 0;
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const index = Math.floor(sorted.length * 0.99);
|
||||
return sorted[index];
|
||||
}
|
||||
|
||||
getMax(name: string): number {
|
||||
const values = this.metrics.get(name) || [];
|
||||
if (values.length === 0) return 0;
|
||||
return Math.max(...values);
|
||||
}
|
||||
|
||||
getMin(name: string): number {
|
||||
const values = this.metrics.get(name) || [];
|
||||
if (values.length === 0) return 0;
|
||||
return Math.min(...values);
|
||||
}
|
||||
|
||||
printReport() {
|
||||
console.log('\n=== 性能测试报告 ===');
|
||||
for (const [name, values] of this.metrics.entries()) {
|
||||
console.log(`\n${name}:`);
|
||||
console.log(` 平均值: ${this.getAverage(name).toFixed(2)}ms`);
|
||||
console.log(` P95: ${this.getP95(name).toFixed(2)}ms`);
|
||||
console.log(` P99: ${this.getP99(name).toFixed(2)}ms`);
|
||||
console.log(` 最大值: ${this.getMax(name).toFixed(2)}ms`);
|
||||
console.log(` 最小值: ${this.getMin(name).toFixed(2)}ms`);
|
||||
console.log(` 样本数: ${values.length}`);
|
||||
}
|
||||
console.log('\n====================\n');
|
||||
}
|
||||
}
|
||||
|
||||
export class PerformanceTestHelper {
|
||||
static async measurePageLoad(page: Page, url: string): Promise<number> {
|
||||
const startTime = Date.now();
|
||||
await page.goto(url, { waitUntil: 'networkidle' });
|
||||
const endTime = Date.now();
|
||||
return endTime - startTime;
|
||||
}
|
||||
|
||||
static async measureApiCall(page: Page, apiCall: () => Promise<void>): Promise<number> {
|
||||
const startTime = Date.now();
|
||||
await apiCall();
|
||||
const endTime = Date.now();
|
||||
return endTime - startTime;
|
||||
}
|
||||
|
||||
static async measureElementInteraction(
|
||||
page: Page,
|
||||
selector: string,
|
||||
action: () => Promise<void>
|
||||
): Promise<number> {
|
||||
await page.waitForSelector(selector, { state: 'visible' });
|
||||
const startTime = Date.now();
|
||||
await action();
|
||||
const endTime = Date.now();
|
||||
return endTime - startTime;
|
||||
}
|
||||
|
||||
static async measurePageNavigation(
|
||||
page: Page,
|
||||
fromUrl: string,
|
||||
toUrl: string
|
||||
): Promise<number> {
|
||||
await page.goto(fromUrl, { waitUntil: 'networkidle' });
|
||||
const startTime = Date.now();
|
||||
await page.goto(toUrl, { waitUntil: 'networkidle' });
|
||||
const endTime = Date.now();
|
||||
return endTime - startTime;
|
||||
}
|
||||
|
||||
static async measureMemoryUsage(page: Page): Promise<{ used: number; total: number }> {
|
||||
const metrics = await page.evaluate(() => {
|
||||
if (performance && (performance as any).memory) {
|
||||
return {
|
||||
used: (performance as any).memory.usedJSHeapSize,
|
||||
total: (performance as any).memory.totalJSHeapSize
|
||||
};
|
||||
}
|
||||
return { used: 0, total: 0 };
|
||||
});
|
||||
return metrics;
|
||||
}
|
||||
|
||||
static async measureNetworkRequests(page: Page): Promise<number> {
|
||||
let requestCount = 0;
|
||||
|
||||
page.on('request', () => {
|
||||
requestCount++;
|
||||
});
|
||||
|
||||
return requestCount;
|
||||
}
|
||||
|
||||
static async clearCacheAndCookies(page: Page) {
|
||||
await page.context().clearCookies();
|
||||
await page.context().clearPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
test.describe.configure({
|
||||
mode: 'serial',
|
||||
timeout: 120000
|
||||
});
|
||||
|
||||
test.describe('性能测试 - 页面加载性能', () => {
|
||||
const metrics = new PerformanceMetrics();
|
||||
const helper = new PerformanceTestHelper();
|
||||
|
||||
test.afterAll(() => {
|
||||
metrics.printReport();
|
||||
});
|
||||
|
||||
test('PT-001: 登录页面加载性能', async ({ page }) => {
|
||||
const loadTime = await helper.measurePageLoad(page, testConfig.getBaseURL());
|
||||
metrics.recordMetric('登录页面加载时间', loadTime);
|
||||
|
||||
expect(loadTime).toBeLessThan(3000);
|
||||
});
|
||||
|
||||
test('PT-002: 仪表盘页面加载性能', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
const loadTime = await helper.measurePageLoad(page, `${testConfig.getBaseURL()}/dashboard`);
|
||||
metrics.recordMetric('仪表盘页面加载时间', loadTime);
|
||||
|
||||
expect(loadTime).toBeLessThan(2000);
|
||||
});
|
||||
|
||||
test('PT-003: 用户管理页面加载性能', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
const loadTime = await helper.measurePageLoad(page, `${testConfig.getBaseURL()}/users`);
|
||||
metrics.recordMetric('用户管理页面加载时间', loadTime);
|
||||
|
||||
expect(loadTime).toBeLessThan(2000);
|
||||
});
|
||||
|
||||
test('PT-004: 黄历页面加载性能', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
const loadTime = await helper.measurePageLoad(page, `${testConfig.getBaseURL()}/almanac`);
|
||||
metrics.recordMetric('黄历页面加载时间', loadTime);
|
||||
|
||||
expect(loadTime).toBeLessThan(2000);
|
||||
});
|
||||
|
||||
test('PT-005: 运势页面加载性能', async ({ page }) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await page.goto(testConfig.getBaseURL());
|
||||
await loginPage.login('admin', 'admin123');
|
||||
|
||||
const loadTime = await helper.measurePageLoad(page, `${testConfig.getBaseURL()}/fortune`);
|
||||
metrics.recordMetric('运势页面加载时间', loadTime);
|
||||
|
||||
expect(loadTime).toBeLessThan(2000);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user