08ea5fbe98
添加用户管理视图、API和状态管理文件
E2E测试工具使用指南
概述
本E2E测试工具是一个基于Playwright的可复用端到端测试框架,提供了模块化的测试用例编写能力、统一的测试环境配置、常用测试操作的封装与复用,以及清晰的测试报告与日志输出能力。
目录结构
e2e/
├── core/ # 核心模块
│ ├── test-config.ts # 测试配置管理
│ ├── test-data.ts # 测试数据生成器
│ ├── test-logger.ts # 测试日志记录器
│ └── test-reporter.ts # 测试报告生成器
├── pages/ # 页面对象模型
│ ├── base-page.ts # 基础页面类
│ ├── login-page.ts # 登录页面
│ ├── dashboard-page.ts # 仪表盘页面
│ ├── user-management-page.ts # 用户管理页面
│ ├── role-management-page.ts # 角色管理页面
│ └── menu-management-page.ts # 菜单管理页面
├── helpers/ # 测试辅助工具
│ ├── screenshot-helper.ts # 截图辅助工具
│ ├── form-helper.ts # 表单辅助工具
│ └── table-helper.ts # 表格辅助工具
├── fixtures/ # 测试夹具
├── utils/ # 工具函数
│ └── common-utils.ts # 通用工具函数
├── constants/ # 常量定义
│ └── index.ts # 常量集合
├── examples/ # 示例测试
│ └── complete-example.spec.ts
├── test-fixtures.ts # Playwright测试夹具
└── mock-manager.ts # Mock服务管理器
快速开始
1. 安装依赖
npm install --save-dev @playwright/test
2. 配置测试环境
在项目根目录创建 .env.e2e 文件:
# E2E测试环境配置
E2E_ENV=local
E2E_BASE_URL=http://localhost:5173
E2E_MOCK_ENABLED=true
E2E_MOCK_MODE=full
3. 编写测试用例
使用提供的测试夹具编写测试用例:
import { test, expect } from './test-fixtures';
test.describe('登录功能测试', () => {
test('成功登录', async ({ pageObjects, testData, testLogger }) => {
testLogger.startTest('成功登录');
try {
await pageObjects.loginPage.navigate();
await pageObjects.loginPage.login(testData.admin.username, testData.admin.password);
await pageObjects.dashboardPage.waitForLoad();
const pageTitle = await pageObjects.dashboardPage.getPageTitle();
expect(pageTitle).toContain('仪表盘');
testLogger.endTest('成功登录', 'passed');
} catch (error) {
testLogger.endTest('成功登录', 'failed', error as Error);
throw error;
}
});
});
4. 运行测试
# 运行所有测试
npm run test:e2e
# 运行特定测试文件
npx playwright test e2e/examples/complete-example.spec.ts
# 运行特定测试用例
npx playwright test -g "成功登录"
# 调试模式运行
npx playwright test --debug
核心功能
1. 测试配置管理
test-config.ts 提供了统一的测试环境配置管理:
import { testConfig } from './core/test-config';
// 获取当前环境配置
const env = testConfig.getEnvironment();
console.log(env.name); // 环境名称
console.log(env.baseURL); // 基础URL
console.log(env.mockEnabled); // Mock是否启用
console.log(env.timeout); // 超时配置
// 切换环境
testConfig.setEnvironment('dev');
// 获取特定环境配置
const devConfig = testConfig.getEnvironment('dev');
2. 测试数据生成
test-data.ts 提供了测试数据生成器:
import { testDataGenerator } from './core/test-data';
// 生成用户数据
const userData = testDataGenerator.generateUserData({
username: 'testuser',
email: 'test@example.com',
status: 'active'
});
// 生成角色数据
const roleData = testDataGenerator.generateRoleData({
roleName: '测试角色',
roleCode: 'test_role',
status: 1
});
// 生成菜单数据
const menuData = testDataGenerator.generateMenuData({
menuName: '测试菜单',
menuType: 1,
path: '/test',
status: 0
});
// 生成权限数据
const permissionData = testDataGenerator.generatePermissionData({
permissionName: '测试权限',
permissionCode: 'test:permission',
permissionType: 'button'
});
3. 测试日志记录
test-logger.ts 提供了结构化的测试日志记录:
import { testLogger } from './core/test-logger';
// 开始测试
testLogger.startTest('测试名称');
// 开始测试步骤
testLogger.startStep('步骤名称');
// 记录不同级别的日志
testLogger.debug('调试信息');
testLogger.info('普通信息');
testLogger.warn('警告信息');
testLogger.error('错误信息', error);
// 结束测试步骤
testLogger.endStep('步骤名称', 'passed');
// 结束测试
testLogger.endTest('测试名称', 'passed');
4. 测试报告生成
test-reporter.ts 提供了测试报告生成功能:
import { testReporter } from './core/test-reporter';
// 开始测试报告
testReporter.startReport();
// 记录测试结果
testReporter.recordTestResult({
testName: '测试名称',
status: 'passed',
duration: 1000,
steps: [],
logs: [],
screenshots: [],
errors: []
});
// 生成所有报告
await testReporter.generateAllReports('./test-results/reports');
// 生成JSON报告
await testReporter.generateJSONReport('./test-results/reports/e2e-report.json');
// 生成HTML报告
await testReporter.generateHTMLReport('./test-results/reports/e2e-report.html');
5. 页面对象模型
所有页面类都继承自 BasePage,提供统一的页面操作接口:
import { BasePage } from './pages/base-page';
import { LoginPage } from './pages/login-page';
import { DashboardPage } from './pages/dashboard-page';
// 使用页面对象
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('admin', 'password');
const dashboardPage = new DashboardPage(page);
await dashboardPage.waitForLoad();
const title = await dashboardPage.getPageTitle();
6. 测试辅助工具
截图辅助工具
import { ScreenshotHelper } from './helpers/screenshot-helper';
const screenshotHelper = new ScreenshotHelper(page);
// 截取当前页面
await screenshotHelper.takeScreenshot('page-screenshot');
// 截取整个页面
await screenshotHelper.takeFullPageScreenshot('full-page');
// 截取特定元素
await screenshotHelper.takeElementScreenshot('element-screenshot', '.selector');
// 在测试失败时自动截图
await screenshotHelper.takeScreenshotOnFailure('test-failure');
表单辅助工具
import { FormHelper } from './helpers/form-helper';
const formHelper = new FormHelper(page);
// 填写表单字段
await formHelper.fillField('input[name="username"]', 'testuser');
await formHelper.fillField('input[type="password"]', 'password', 'password');
await formHelper.fillField('select[name="role"]', 'admin', 'select');
await formHelper.fillField('input[type="checkbox"]', true, 'checkbox');
// 填写整个表单
await formHelper.fillForm({
username: 'testuser',
password: 'password',
email: 'test@example.com',
role: 'admin'
});
// 提交表单
await formHelper.submitForm();
// 重置表单
await formHelper.resetForm();
// 验证表单
const isValid = await formHelper.validateForm();
表格辅助工具
import { TableHelper } from './helpers/table-helper';
const tableHelper = new TableHelper(page);
// 获取表格行数
const rowCount = await tableHelper.getRowCount('.ant-table');
// 获取表格列数
const columnCount = await tableHelper.getColumnCount('.ant-table');
// 获取单元格文本
const cellText = await tableHelper.getCellText('.ant-table', 0, 0);
// 获取整行数据
const rowData = await tableHelper.getRowData('.ant-table', 0);
// 获取整列数据
const columnData = await tableHelper.getColumnData('.ant-table', 0);
// 点击表格行
await tableHelper.clickRow('.ant-table', 0);
// 点击表格单元格
await tableHelper.clickCell('.ant-table', 0, 0);
// 等待表格加载
await tableHelper.waitForTableLoad('.ant-table');
// 验证表格数据
const isValid = await tableHelper.validateTableData('.ant-table', expectedData);
7. Mock服务集成
import { MockManager } from './mock-manager';
const mockConfig = {
enabled: true,
mode: 'full',
mockPaths: [],
delay: 0,
logCalls: true,
validateResponses: true,
dataSource: 'memory'
};
const mockManager = new MockManager(mockConfig);
// 拦截API请求
await mockManager.interceptAPIRequest(page);
// 添加Mock响应
mockManager.addMockResponse({
url: '/api/login',
method: 'POST',
response: {
code: 200,
data: {
token: 'mock-token',
userInfo: {
id: 1,
username: 'admin'
}
}
}
});
// 清除Mock响应
mockManager.clearMockResponses();
测试夹具
本工具提供了以下测试夹具:
pageObjects
提供所有页面对象的实例:
test('使用页面对象', async ({ pageObjects }) => {
await pageObjects.loginPage.navigate();
await pageObjects.loginPage.login('admin', 'password');
await pageObjects.dashboardPage.waitForLoad();
});
helpers
提供所有辅助工具的实例:
test('使用辅助工具', async ({ helpers }) => {
await helpers.screenshot.takeScreenshot('test');
await helpers.form.fillField('input[name="username"]', 'test');
const rowCount = await helpers.table.getRowCount('.ant-table');
});
testData
提供预定义的测试数据:
test('使用测试数据', async ({ testData }) => {
console.log(testData.user); // 普通用户数据
console.log(testData.admin); // 管理员数据
console.log(testData.role); // 角色数据
console.log(testData.menu); // 菜单数据
console.log(testData.permission); // 权限数据
});
mockManager
提供Mock服务管理器:
test('使用Mock服务', async ({ mockManager }) => {
mockManager.addMockResponse({
url: '/api/test',
method: 'GET',
response: { code: 200, data: 'mock data' }
});
});
testConfig
提供测试配置:
test('使用测试配置', async ({ testConfig }) => {
console.log(testConfig.name); // 环境名称
console.log(testConfig.baseURL); // 基础URL
console.log(testConfig.mockEnabled); // Mock是否启用
});
testLogger
提供测试日志记录器:
test('使用测试日志', async ({ testLogger }) => {
testLogger.info('测试信息');
testLogger.error('测试错误', error);
});
testReporter
提供测试报告生成器:
test('使用测试报告', async ({ testReporter }) => {
testReporter.recordTestResult({
testName: '测试名称',
status: 'passed',
duration: 1000
});
});
最佳实践
1. 测试用例组织
- 使用
test.describe组织相关的测试用例 - 使用
test.beforeEach和test.afterEach设置测试前置和后置条件 - 为每个测试用例提供清晰的描述
test.describe('用户管理功能', () => {
test.beforeEach(async ({ pageObjects, testData }) => {
await pageObjects.loginPage.navigate();
await pageObjects.loginPage.login(testData.admin.username, testData.admin.password);
});
test.afterEach(async ({ helpers }) => {
await helpers.screenshot.takeScreenshot('after-test');
});
test('创建用户', async ({ pageObjects }) => {
// 测试逻辑
});
});
2. 页面对象使用
- 始终使用页面对象而不是直接操作页面元素
- 将页面选择器封装在页面对象中
- 在页面对象中实现业务逻辑方法
// 好的做法
await pageObjects.loginPage.login('admin', 'password');
// 不好的做法
await page.fill('input[placeholder="请输入用户名"]', 'admin');
await page.fill('input[placeholder="请输入密码"]', 'password');
await page.click('button[type="submit"]');
3. 测试数据管理
- 使用测试数据生成器创建测试数据
- 避免硬编码测试数据
- 使用测试夹具提供的预定义数据
// 好的做法
const userData = testDataGenerator.generateUserData({
username: 'testuser',
status: 'active'
});
// 不好的做法
const userData = {
username: 'testuser',
password: 'password',
email: 'test@example.com',
// ... 硬编码的数据
};
4. 错误处理
- 在测试用例中使用 try-catch 捕获错误
- 使用测试日志记录错误信息
- 在测试失败时截图
test('测试用例', async ({ testLogger, helpers }) => {
testLogger.startTest('测试用例');
try {
// 测试逻辑
testLogger.endTest('测试用例', 'passed');
} catch (error) {
testLogger.endTest('测试用例', 'failed', error as Error);
await helpers.screenshot.takeScreenshot('test-failure');
throw error;
}
});
5. 等待策略
- 使用页面对象提供的等待方法
- 避免使用固定的等待时间
- 使用 Playwright 的自动等待机制
// 好的做法
await pageObjects.dashboardPage.waitForLoad();
await page.waitForSelector('.element', { state: 'visible' });
// 不好的做法
await page.waitForTimeout(5000);
6. 断言使用
- 使用 Playwright 的 expect 断言
- 提供有意义的断言消息
- 验证关键的业务逻辑
// 好的做法
expect(pageTitle).toContain('仪表盘');
expect(successMessage).toBeTruthy();
expect(rowCount).toBeGreaterThan(0);
// 不好的做法
expect(pageTitle).toBeTruthy();
故障排查
测试失败时的调试
- 查看测试日志:
test-results/logs/ - 查看截图:
test-results/screenshots/ - 查看测试报告:
test-results/reports/ - 使用调试模式运行:
npx playwright test --debug
常见问题
-
元素未找到
- 检查选择器是否正确
- 确保元素已加载
- 使用适当的等待策略
-
测试超时
- 增加超时配置
- 检查网络请求是否正常
- 优化测试等待策略
-
Mock服务不工作
- 确认Mock服务已启用
- 检查Mock配置是否正确
- 验证Mock响应格式
扩展开发
添加新的页面对象
- 在
pages/目录下创建新的页面类 - 继承
BasePage类 - 实现页面特定的方法和选择器
import { BasePage } from './base-page';
export class NewPage extends BasePage {
private readonly selectors = {
// 页面选择器
};
constructor(page: Page) {
super(page);
}
// 页面方法
}
添加新的辅助工具
- 在
helpers/目录下创建新的辅助工具类 - 实现辅助工具方法
- 在测试夹具中注册新的辅助工具
export class NewHelper {
private page: Page;
constructor(page: Page) {
this.page = page;
}
// 辅助工具方法
}
添加新的测试数据生成器
- 在
test-data.ts中添加新的数据生成方法 - 定义数据接口
- 实现数据生成逻辑
export interface NewData {
// 数据接口
}
generateNewData(overrides: Partial<NewData> = {}): NewData {
// 数据生成逻辑
}
性能优化
并行执行
Playwright 默认支持并行执行测试,可以通过配置文件调整:
export default defineConfig({
workers: 4, // 并发工作进程数
fullyParallel: true, // 完全并行执行
});
测试隔离
确保每个测试用例都是独立的,避免测试之间的依赖:
test.beforeEach(async ({ page }) => {
// 清理测试数据
// 重置测试状态
});
重试机制
配置测试失败时的重试次数:
export default defineConfig({
retries: 2, // 失败时重试次数
});
持续集成
CI/CD集成
在CI/CD流程中集成E2E测试:
# .github/workflows/e2e-tests.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- run: npm ci
- run: npm run test:e2e
- uses: actions/upload-artifact@v2
if: failure()
with:
name: test-results
path: test-results/
总结
本E2E测试工具提供了完整的端到端测试解决方案,包括:
- ✅ 模块化的测试用例编写
- ✅ 统一的测试环境配置
- ✅ 常用测试操作的封装与复用
- ✅ 清晰的测试报告与日志输出
- ✅ 页面对象模型(POM)
- ✅ 测试辅助工具
- ✅ Mock服务集成
- ✅ 测试数据生成
通过使用本工具,可以高效地编写、执行和维护E2E测试,确保应用的质量和稳定性。