优化分批执行点击菜单
This commit is contained in:
parent
ff891e82b0
commit
0dfad65b57
29
.env.dev
29
.env.dev
@ -1,25 +1,24 @@
|
||||
# 基础URL
|
||||
# 基础配置
|
||||
BASE_URL=https://ibp-dev.longi.com
|
||||
|
||||
# 测试配置
|
||||
TEST_DATA_DIR=test-data
|
||||
TEST_BATCH_SIZE=5
|
||||
TEST_RETRY_COUNT=3
|
||||
TEST_BATCH_INTERVAL=1000
|
||||
TEST_MAX_RETRY_DELAY=5000
|
||||
|
||||
# 超时配置
|
||||
MENU_TIME_OUT=30000
|
||||
|
||||
# 数据目录配置
|
||||
TEST_DATA_DIR=test-data
|
||||
MENU_DATA_FILE_PATH=test-data/menu-data.json
|
||||
TEST_PROGRESS_FILE_PATH=test-data/test-progress.json
|
||||
|
||||
# 测试批次配置
|
||||
TEST_BATCH_SIZE=5
|
||||
TEST_RETRY_COUNT=3
|
||||
TEST_BATCH_INTERVAL=2000
|
||||
TEST_MAX_RETRY_DELAY=5000
|
||||
EXPECT_TIMEOUT=30000
|
||||
|
||||
# 浏览器配置
|
||||
BROWSER_HEADLESS=false
|
||||
BROWSER_SLOW_MO=50
|
||||
BROWSER_TIMEOUT=30000
|
||||
|
||||
# 视窗配置
|
||||
VIEWPORT_WIDTH=1920
|
||||
VIEWPORT_HEIGHT=1080
|
||||
VIEWPORT_HEIGHT=1080
|
||||
|
||||
# 数据目录配置
|
||||
MENU_DATA_FILE_PATH=test-data/menu-data.json
|
||||
TEST_PROGRESS_FILE_PATH=test-data/test-progress.json
|
||||
206
README.md
206
README.md
@ -1,137 +1,155 @@
|
||||
# Playwright 自动化测试项目
|
||||
# Playwright E2E Testing Framework
|
||||
|
||||
基于 Playwright 的自动化测试框架,专门用于测试 Web 应用的菜单可访问性。
|
||||
本项目是基于Playwright的端到端测试框架,用于自动化测试Web应用的功能和性能。
|
||||
|
||||
## 项目结构
|
||||
## 项目架构
|
||||
|
||||
```
|
||||
playwright-project/
|
||||
├── tests/ # 测试相关文件
|
||||
│ ├── e2e/ # 端到端测试用例
|
||||
│ │ └── menu.spec.js # 菜单测试用例
|
||||
│ ├── pages/ # Page Object Models
|
||||
│ │ ├── BasePage.js # 基础页面类
|
||||
│ │ ├── LoginPage.js # 登录页面类
|
||||
│ │ └── MainPage.js # 主页面类
|
||||
│ └── utils/ # 测试工具
|
||||
│ └── FileUtils.js # 文件操作工具
|
||||
├── src/ # 源代码
|
||||
│ ├── services/ # 业务服务
|
||||
│ │ └── MenuDataService.js # 菜单数据服务
|
||||
│ └── controllers/ # 业务控制器
|
||||
│ └── TestController.js # 测试控制器
|
||||
├── config/ # 配置文件
|
||||
│ └── env.js # 环境变量配置
|
||||
├── playwright.config.js # Playwright 配置
|
||||
└── package.json # 项目配置
|
||||
playwright/
|
||||
├── src/
|
||||
│ ├── controllers/ # 控制器层,负责测试流程控制
|
||||
│ ├── pages/ # 页面对象层,封装页面操作
|
||||
│ └── services/ # 服务层,处理数据和业务逻辑
|
||||
├── tests/
|
||||
│ └── e2e/ # 端到端测试用例
|
||||
├── .env.dev # 开发环境配置
|
||||
└── playwright.config.js # Playwright配置文件
|
||||
```
|
||||
|
||||
## 功能特性
|
||||
## 配置管理
|
||||
|
||||
- 🔄 批量测试菜单可访问性
|
||||
- 💾 自动保存和恢复测试进度
|
||||
- 🎯 支持分批次执行测试
|
||||
- 🔍 详细的测试报告
|
||||
- 🛠 可配置的测试参数
|
||||
项目采用分层的配置管理方式,确保配置的统一性和可维护性:
|
||||
|
||||
## 环境要求
|
||||
### 1. 环境变量配置 (.env.dev)
|
||||
|
||||
- Node.js 14+
|
||||
- npm 6+
|
||||
- Playwright 依赖项
|
||||
环境变量配置文件包含所有可配置项,按功能分类:
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 安装 Playwright 浏览器
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
在 `.env.dev` 文件中配置以下环境变量:
|
||||
|
||||
```bash
|
||||
# 基础URL
|
||||
BASE_URL=https://your-app-url.com
|
||||
```ini
|
||||
# 基础配置
|
||||
BASE_URL=https://ibp-dev.longi.com
|
||||
|
||||
# 测试配置
|
||||
TEST_BATCH_SIZE=5 # 每批次测试的菜单数量
|
||||
TEST_RETRY_COUNT=3 # 失败重试次数
|
||||
TEST_BATCH_INTERVAL=2000 # 批次间隔时间(ms)
|
||||
MENU_TIME_OUT=30000 # 菜单加载超时时间
|
||||
TEST_DATA_DIR=test-data
|
||||
TEST_BATCH_SIZE=5
|
||||
TEST_RETRY_COUNT=3
|
||||
TEST_BATCH_INTERVAL=1000
|
||||
TEST_MAX_RETRY_DELAY=5000
|
||||
|
||||
# 超时配置
|
||||
MENU_TIME_OUT=30000
|
||||
EXPECT_TIMEOUT=30000
|
||||
|
||||
# 浏览器配置
|
||||
BROWSER_HEADLESS=false
|
||||
BROWSER_SLOW_MO=50
|
||||
BROWSER_TIMEOUT=30000
|
||||
VIEWPORT_WIDTH=1920
|
||||
VIEWPORT_HEIGHT=1080
|
||||
|
||||
# 数据目录配置
|
||||
TEST_DATA_DIR=test-data
|
||||
MENU_DATA_FILE_PATH=test-data/menu-data.json
|
||||
TEST_PROGRESS_FILE_PATH=test-data/test-progress.json
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
### 2. Playwright配置 (playwright.config.js)
|
||||
|
||||
### 运行测试
|
||||
Playwright配置文件统一管理所有浏览器相关的配置:
|
||||
|
||||
```bash
|
||||
# 运行菜单测试
|
||||
npm run test:menu
|
||||
- 浏览器设置(headless模式、视窗大小等)
|
||||
- 测试超时设置
|
||||
- 并发和重试策略
|
||||
- 测试报告配置
|
||||
|
||||
# 使用 UI 模式运行测试
|
||||
npm run test:menu:ui
|
||||
### 3. 测试控制器配置 (TestController)
|
||||
|
||||
# 调试模式运行测试
|
||||
npm run test:menu:debug
|
||||
测试控制器只管理测试流程相关的配置:
|
||||
|
||||
# 清理数据并重新测试
|
||||
npm run test:menu:clean
|
||||
```
|
||||
- 批次大小
|
||||
- 重试次数
|
||||
- 批次间隔
|
||||
- 最大重试延迟
|
||||
|
||||
### 查看报告
|
||||
## 主要功能
|
||||
|
||||
```bash
|
||||
npm run report
|
||||
```
|
||||
1. **菜单遍历测试**
|
||||
- 自动收集菜单数据
|
||||
- 批量执行菜单点击测试
|
||||
- 支持断点续测
|
||||
- 提供测试进度跟踪
|
||||
|
||||
## 测试流程
|
||||
2. **智能重试机制**
|
||||
- 支持失败重试
|
||||
- 使用指数退避策略
|
||||
- 可配置最大重试次数和延迟
|
||||
|
||||
1. **数据收集**
|
||||
- 登录系统
|
||||
- 收集菜单数据
|
||||
- 保存到文件
|
||||
3. **灵活的配置系统**
|
||||
- 所有配置项可通过环境变量覆盖
|
||||
- 提供合理的默认值
|
||||
- 配置项分类清晰
|
||||
|
||||
2. **批量测试**
|
||||
- 分批加载菜单
|
||||
- 逐个点击测试
|
||||
- 记录测试进度
|
||||
## 使用说明
|
||||
|
||||
1. **安装依赖**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **配置环境变量**
|
||||
- 复制 `.env.dev` 到 `.env`
|
||||
- 根据需要修改配置项
|
||||
|
||||
3. **运行测试**
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
4. **查看报告**
|
||||
```bash
|
||||
npm run show-report
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **配置管理**
|
||||
- 所有配置统一在 `.env` 文件中管理
|
||||
- 不同环境使用不同的 `.env` 文件
|
||||
- 避免在代码中硬编码配置值
|
||||
|
||||
2. **测试用例编写**
|
||||
- 使用页面对象模式
|
||||
- 保持测试用例独立性
|
||||
- 合理使用断言和超时设置
|
||||
|
||||
3. **错误处理**
|
||||
- 自动重试失败项
|
||||
- 记录错误信息
|
||||
- 生成测试报告
|
||||
- 实现合理的重试机制
|
||||
- 记录详细的错误日志
|
||||
- 提供清晰的错误信息
|
||||
|
||||
## 开发指南
|
||||
## 注意事项
|
||||
|
||||
### 添加新的测试用例
|
||||
1. 确保环境变量配置正确
|
||||
2. 注意浏览器配置只在 `playwright.config.js` 中管理
|
||||
3. 测试用例应该是独立和可重复的
|
||||
4. 定期检查和清理测试数据
|
||||
|
||||
1. 在 `tests/e2e` 目录下创建测试文件
|
||||
2. 使用 Page Object Model 模式
|
||||
3. 遵循现有的代码结构
|
||||
## 常见问题
|
||||
|
||||
### 修改页面对象
|
||||
1. **测试运行失败**
|
||||
- 检查网络连接
|
||||
- 验证环境变量配置
|
||||
- 查看错误日志
|
||||
|
||||
1. 在 `tests/pages` 目录下修改或添加页面类
|
||||
2. 继承 `BasePage` 类
|
||||
3. 实现必要的页面方法
|
||||
2. **配置不生效**
|
||||
- 确认环境变量文件位置正确
|
||||
- 检查配置项拼写
|
||||
- 重启测试进程
|
||||
|
||||
## 贡献指南
|
||||
|
||||
1. Fork 项目
|
||||
2. 创建特性分支
|
||||
3. 提交更改
|
||||
4. 推送到分支
|
||||
5. 创建 Pull Request
|
||||
3. 提交变更
|
||||
4. 发起 Pull Request
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
@ -1,160 +0,0 @@
|
||||
const { chromium } = require('@playwright/test');
|
||||
const LongiMainPage = require('../tests/pages/LongiMainPage');
|
||||
const LongiLoginPage = require('../tests/pages/LongiLoginPage');
|
||||
const menuDataService = require('../services/MenuDataService');
|
||||
|
||||
class TestController {
|
||||
constructor() {
|
||||
// 从环境变量获取配置
|
||||
this.batchSize = parseInt(process.env.TEST_BATCH_SIZE || '5', 10);
|
||||
this.retryCount = parseInt(process.env.TEST_RETRY_COUNT || '3', 10);
|
||||
this.batchInterval = parseInt(process.env.TEST_BATCH_INTERVAL || '2000', 10);
|
||||
|
||||
// 浏览器配置
|
||||
this.browserConfig = {
|
||||
headless: false, // 使用有头模式
|
||||
args: ['--start-maximized'], // 最大化窗口
|
||||
slowMo: 50 // 放慢操作速度,便于观察
|
||||
};
|
||||
|
||||
console.log('测试配置:', {
|
||||
batchSize: this.batchSize,
|
||||
retryCount: this.retryCount,
|
||||
batchInterval: this.batchInterval
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行登录操作
|
||||
* @param {import('@playwright/test').Page} page - Playwright页面对象
|
||||
* @returns {Promise<boolean>} 登录是否成功
|
||||
*/
|
||||
async performLogin(page) {
|
||||
const loginPage = new LongiLoginPage(page);
|
||||
|
||||
// 导航到登录页面
|
||||
await loginPage.navigateToLoginPage();
|
||||
|
||||
// 点击登录按钮
|
||||
const loginSuccess = await loginPage.clickLoginButton();
|
||||
|
||||
if (!loginSuccess) {
|
||||
console.error('登录失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('登录成功');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集菜单数据
|
||||
* @returns {Promise<Array>} - 处理后的菜单数据
|
||||
*/
|
||||
async collectMenuData() {
|
||||
console.log('开始收集菜单数据...');
|
||||
const browser = await chromium.launch(this.browserConfig);
|
||||
const page = await browser.newPage();
|
||||
|
||||
// 设置视窗大小
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
|
||||
const mainPage = new LongiMainPage(page);
|
||||
|
||||
try {
|
||||
// 使用 LongiLoginPage 处理登录
|
||||
const loginSuccess = await this.performLogin(page);
|
||||
if (!loginSuccess) {
|
||||
throw new Error('登录失败,无法收集菜单数据');
|
||||
}
|
||||
|
||||
console.log('登录成功,正在获取菜单项...');
|
||||
const menuItems = await mainPage.checkAndLoadMenuItems();
|
||||
console.log(`成功获取 ${menuItems.length} 个菜单项`);
|
||||
return menuDataService.saveMenuData(menuItems);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一批菜单的测试
|
||||
* @param {Array} menuBatch - 要测试的菜单数组
|
||||
*/
|
||||
async runBatchTest(menuBatch) {
|
||||
console.log(`开始执行批次测试,包含 ${menuBatch.length} 个菜单项:`);
|
||||
console.log(menuBatch.map(m => m.text).join(', '));
|
||||
|
||||
const browser = await chromium.launch(this.browserConfig);
|
||||
const page = await browser.newPage();
|
||||
|
||||
// 设置视窗大小
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
|
||||
const mainPage = new LongiMainPage(page);
|
||||
|
||||
try {
|
||||
// 使用 LongiLoginPage 处理登录
|
||||
const loginSuccess = await this.performLogin(page);
|
||||
if (!loginSuccess) {
|
||||
throw new Error('登录失败,无法执行菜单测试');
|
||||
}
|
||||
|
||||
console.log('登录成功,开始测试菜单项...');
|
||||
|
||||
// 使用 handleAllMenuClicks 方法处理所有菜单
|
||||
await mainPage.handleAllMenuClicks(menuBatch);
|
||||
|
||||
// 更新进度
|
||||
const progress = menuDataService.getProgress();
|
||||
const newProgress = [...progress];
|
||||
for (const menuItem of menuBatch) {
|
||||
if (!newProgress.includes(menuItem.id)) {
|
||||
newProgress.push(menuItem.id);
|
||||
}
|
||||
}
|
||||
menuDataService.saveProgress(newProgress);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下一批要测试的菜单
|
||||
* @returns {Array|null} - 下一批要测试的菜单,如果没有则返回null
|
||||
*/
|
||||
getNextBatch() {
|
||||
const menuData = menuDataService.getMenuData();
|
||||
const progress = menuDataService.getProgress();
|
||||
|
||||
if (!menuData) return null;
|
||||
|
||||
// 过滤出未测试的菜单
|
||||
const remainingMenus = menuData.filter(menu => !progress.includes(menu.id));
|
||||
if (remainingMenus.length === 0) return null;
|
||||
|
||||
// 返回下一批要测试的菜单
|
||||
return remainingMenus.slice(0, this.batchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测试进度信息
|
||||
* @returns {Object} - 进度信息
|
||||
*/
|
||||
getTestProgress() {
|
||||
const menuData = menuDataService.getMenuData();
|
||||
const progress = menuDataService.getProgress();
|
||||
|
||||
if (!menuData) {
|
||||
return { total: 0, completed: 0, remaining: 0 };
|
||||
}
|
||||
|
||||
return {
|
||||
total: menuData.length,
|
||||
completed: progress.length,
|
||||
remaining: menuData.length - progress.length
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TestController;
|
||||
@ -1,82 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 菜单数据服务
|
||||
* 负责菜单数据的存储和检索
|
||||
*/
|
||||
class MenuDataService {
|
||||
constructor() {
|
||||
// 从环境变量获取路径配置
|
||||
this.dataDir = process.env.TEST_DATA_DIR;
|
||||
this.menuDataPath = process.env.MENU_DATA_FILE_PATH;
|
||||
this.progressPath = process.env.TEST_PROGRESS_FILE_PATH;
|
||||
|
||||
// 确保数据目录存在
|
||||
if (!fs.existsSync(this.dataDir)) {
|
||||
fs.mkdirSync(this.dataDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存菜单数据
|
||||
* @param {Array} menuItems - 从页面获取的原始菜单项
|
||||
* @returns {Array} - 处理后的菜单数据
|
||||
*/
|
||||
saveMenuData(menuItems) {
|
||||
const menuData = menuItems.map((menu, index) => ({
|
||||
id: index + 1,
|
||||
text: menu.text,
|
||||
path: menu.path || menu.text,
|
||||
hasThirdMenu: menu.hasThirdMenu,
|
||||
parentMenu: menu.parentMenu
|
||||
}));
|
||||
fs.writeFileSync(this.menuDataPath, JSON.stringify(menuData, null, 2));
|
||||
return menuData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取菜单数据
|
||||
* @returns {Array|null} - 菜单数据数组,如果文件不存在则返回null
|
||||
*/
|
||||
getMenuData() {
|
||||
if (!fs.existsSync(this.menuDataPath)) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(this.menuDataPath, 'utf8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存测试进度
|
||||
* @param {Array} completedMenus - 已完成测试的菜单ID数组
|
||||
*/
|
||||
saveProgress(completedMenus) {
|
||||
fs.writeFileSync(this.progressPath, JSON.stringify(completedMenus, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取测试进度
|
||||
* @returns {Array} - 已完成测试的菜单ID数组
|
||||
*/
|
||||
getProgress() {
|
||||
if (!fs.existsSync(this.progressPath)) {
|
||||
return [];
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(this.progressPath, 'utf8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有数据文件
|
||||
*/
|
||||
clearAll() {
|
||||
if (fs.existsSync(this.menuDataPath)) {
|
||||
fs.unlinkSync(this.menuDataPath);
|
||||
}
|
||||
if (fs.existsSync(this.progressPath)) {
|
||||
fs.unlinkSync(this.progressPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
module.exports = new MenuDataService();
|
||||
@ -10,10 +10,7 @@ module.exports = defineConfig({
|
||||
timeout: 60 * 60 * 1000, // 1小时
|
||||
/* 每个测试的预期状态 */
|
||||
expect: {
|
||||
/**
|
||||
* 断言超时时间
|
||||
*/
|
||||
timeout: 30000
|
||||
timeout: parseInt(process.env.EXPECT_TIMEOUT || '30000', 10)
|
||||
},
|
||||
/* 测试运行并发数 */
|
||||
fullyParallel: false,
|
||||
@ -34,8 +31,17 @@ module.exports = defineConfig({
|
||||
screenshot: 'only-on-failure',
|
||||
/* 录制视频 */
|
||||
video: 'retain-on-failure',
|
||||
headless: false,
|
||||
viewport: { width: 1920, height: 1080 },
|
||||
/* 浏览器配置 */
|
||||
headless: process.env.BROWSER_HEADLESS === 'true',
|
||||
viewport: {
|
||||
width: parseInt(process.env.VIEWPORT_WIDTH || '1920', 10),
|
||||
height: parseInt(process.env.VIEWPORT_HEIGHT || '1080', 10)
|
||||
},
|
||||
/* 浏览器启动选项 */
|
||||
launchOptions: {
|
||||
slowMo: parseInt(process.env.BROWSER_SLOW_MO || '50', 10),
|
||||
timeout: parseInt(process.env.BROWSER_TIMEOUT || '30000', 10)
|
||||
}
|
||||
},
|
||||
|
||||
/* 配置不同的浏览器项目 */
|
||||
|
||||
@ -17,25 +17,11 @@ class TestController {
|
||||
* @private
|
||||
*/
|
||||
initializeConfig() {
|
||||
// 从环境变量获取配置
|
||||
// 从环境变量获取测试相关配置
|
||||
this.batchSize = parseInt(process.env.TEST_BATCH_SIZE || '5', 10);
|
||||
this.retryCount = parseInt(process.env.TEST_RETRY_COUNT || '3', 10);
|
||||
this.batchInterval = parseInt(process.env.TEST_BATCH_INTERVAL || '2000', 10);
|
||||
this.maxRetryDelay = parseInt(process.env.TEST_MAX_RETRY_DELAY || '5000', 10);
|
||||
|
||||
// 浏览器配置
|
||||
this.browserConfig = {
|
||||
headless: process.env.BROWSER_HEADLESS === 'true',
|
||||
args: ['--start-maximized'],
|
||||
slowMo: parseInt(process.env.BROWSER_SLOW_MO || '50', 10),
|
||||
timeout: parseInt(process.env.BROWSER_TIMEOUT || '30000', 10)
|
||||
};
|
||||
|
||||
// 视窗配置
|
||||
this.viewportConfig = {
|
||||
width: parseInt(process.env.VIEWPORT_WIDTH || '1920', 10),
|
||||
height: parseInt(process.env.VIEWPORT_HEIGHT || '1080', 10)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,9 +31,8 @@ class TestController {
|
||||
*/
|
||||
async createBrowser() {
|
||||
try {
|
||||
const browser = await chromium.launch(this.browserConfig);
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
await page.setViewportSize(this.viewportConfig);
|
||||
return { browser, page };
|
||||
} catch (error) {
|
||||
console.error('创建浏览器实例失败:', error);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user