优化分批执行点击菜单

This commit is contained in:
dengqichen 2025-03-06 17:49:42 +08:00
parent 3571f5fb0f
commit eb8d523d91
6 changed files with 602 additions and 2 deletions

View File

@ -0,0 +1,159 @@
const { chromium } = require('@playwright/test');
const LongiMainPage = require('../tests/pages/LongiMainPage');
const LongiLoginPage = require('../tests/pages/LongiLoginPage');
const menuDataService = require('../services/MenuDataService');
class TestController {
/**
* @param {Object} options - 配置选项
* @param {number} [options.batchSize=5] - 每批次测试的菜单数量
* @param {number} [options.retryCount=3] - 失败重试次数
* @param {number} [options.batchInterval=2000] - 批次间隔时间(ms)
*/
constructor(options = {}) {
this.batchSize = options.batchSize || 5;
this.retryCount = options.retryCount || 3;
this.batchInterval = options.batchInterval || 2000;
// 浏览器配置
this.browserConfig = {
headless: false, // 使用有头模式
args: ['--start-maximized'], // 最大化窗口
slowMo: 50 // 放慢操作速度,便于观察
};
}
/**
* 执行登录操作
* @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;

View File

@ -9,7 +9,10 @@
"report": "playwright show-report",
"codegen": "playwright codegen",
"debug": "playwright test --debug",
"test:longi:check-normal:dev": "cross-env NODE_ENV=dev playwright test tests/longi-ibp/check-page-normal.test.js --headed --project=chromium"
"test:longi:check-normal:dev": "cross-env NODE_ENV=dev node scripts/run-tests.js",
"test:longi:check-clean": "cross-env NODE_ENV=dev node scripts/run-tests.js --clean",
"test:longi:check-collect": "cross-env NODE_ENV=dev node scripts/run-tests.js --collect-only",
"test:longi:check-clean-continue": "cross-env NODE_ENV=dev node scripts/run-tests.js --clean --continue"
},
"keywords": [
"playwright",

View File

@ -10,6 +10,12 @@
// const path = require('path');
// const fs = require('fs');
//
// // 确保在其他任何代码之前加载环境变量
// require('../config/env');
//
// const TestController = require('../controllers/TestController');
// const menuDataService = require('../services/MenuDataService');
//
// // 设置命令行选项
// program
// .version('1.0.0')
@ -89,3 +95,103 @@
// console.error(chalk.red('测试执行失败:'), error.message);
// process.exit(1);
// }
// 确保在其他任何代码之前加载环境变量
require('../config/env');
const TestController = require('../controllers/TestController');
const menuDataService = require('../services/MenuDataService');
/**
* 格式化进度信息
* @param {Object} progress - 进度对象
* @returns {string} - 格式化的进度字符串
*/
function formatProgress(progress) {
const percentage = ((progress.completed / progress.total) * 100).toFixed(2);
return `进度: ${progress.completed}/${progress.total} (${percentage}%)`;
}
/**
* 主执行函数
*/
async function main() {
console.log('环境变量:', {
NODE_ENV: process.env.NODE_ENV,
BASE_URL: process.env.BASE_URL,
MENU_DATA_FILE_PATH: process.env.MENU_DATA_FILE_PATH
});
const controller = new TestController({
batchSize: 5,
retryCount: 3,
batchInterval: 2000
});
// 处理命令行参数
const args = process.argv.slice(2);
if (args.includes('--clean')) {
menuDataService.clearAll();
console.log('已清理所有数据');
if (!args.includes('--continue')) {
return;
}
}
// 强制重新收集菜单数据
console.log('开始收集菜单数据...');
const menuData = await controller.collectMenuData();
console.log(`成功收集 ${menuData.length} 个菜单项`);
// 清理之前的进度
menuDataService.saveProgress([]);
// 显示初始进度
const initialProgress = controller.getTestProgress();
console.log('\n初始' + formatProgress(initialProgress));
if (args.includes('--collect-only')) {
console.log('仅收集菜单数据,退出执行');
return;
}
// 执行测试
console.log('\n开始执行测试...\n');
let retryCount = 0;
const maxRetries = 3;
while (true) {
const batch = controller.getNextBatch();
if (!batch || batch.length === 0) {
const finalProgress = controller.getTestProgress();
// 检查是否所有菜单都测试完成
if (finalProgress.completed < finalProgress.total && retryCount < maxRetries) {
console.log(`\n还有未完成的测试,尝试重试 (${retryCount + 1}/${maxRetries})...`);
retryCount++;
continue;
}
console.log('\n所有测试完成');
console.log('最终' + formatProgress(finalProgress));
break;
}
// 显示当前进度
const currentProgress = controller.getTestProgress();
console.log('\n当前' + formatProgress(currentProgress));
// 执行当前批次
await controller.runBatchTest(batch);
// 批次间暂停
console.log(`\n等待 ${controller.batchInterval/1000} 秒后继续下一批次...\n`);
await new Promise(resolve => setTimeout(resolve, controller.batchInterval));
}
}
// 执行主函数
main().catch(error => {
console.error('执行过程中出现错误:', error);
process.exit(1);
});

View File

@ -0,0 +1,77 @@
const fs = require('fs');
const path = require('path');
class MenuDataService {
constructor() {
this.dataDir = path.join(process.cwd(), 'test-data');
this.menuDataPath = path.join(this.dataDir, 'menu-data.json');
this.progressPath = path.join(this.dataDir, 'test-progress.json');
// 确保数据目录存在
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();

254
test-data/menu-data.json Normal file
View File

@ -0,0 +1,254 @@
[
{
"id": 1,
"text": "主数据",
"path": "主数据",
"hasThirdMenu": true
},
{
"id": 2,
"text": "业务数据",
"path": "业务数据",
"hasThirdMenu": true
},
{
"id": 3,
"text": "电池基础数据",
"path": "电池基础数据",
"hasThirdMenu": true
},
{
"id": 4,
"text": "主需求计划",
"path": "主需求计划",
"hasThirdMenu": false
},
{
"id": 5,
"text": "供需粗匹配",
"path": "供需粗匹配",
"hasThirdMenu": false
},
{
"id": 6,
"text": "产线规划",
"path": "产线规划",
"hasThirdMenu": true
},
{
"id": 7,
"text": "电池投放计划",
"path": "电池投放计划",
"hasThirdMenu": true
},
{
"id": 8,
"text": "主生产计划",
"path": "主生产计划",
"hasThirdMenu": true
},
{
"id": 9,
"text": "基地生产计划",
"path": "基地生产计划",
"hasThirdMenu": true
},
{
"id": 10,
"text": "ATP生成和发布",
"path": "ATP生成和发布",
"hasThirdMenu": false
},
{
"id": 11,
"text": "ATP分配调整",
"path": "ATP分配调整",
"hasThirdMenu": false
},
{
"id": 12,
"text": "ATP查询",
"path": "ATP查询",
"hasThirdMenu": false
},
{
"id": 13,
"text": "产量预测",
"path": "产量预测",
"hasThirdMenu": true
},
{
"id": 14,
"text": "入库预测",
"path": "入库预测",
"hasThirdMenu": false
},
{
"id": 15,
"text": "硅片投入需求",
"path": "硅片投入需求",
"hasThirdMenu": false
},
{
"id": 16,
"text": "硅片到货需求",
"path": "硅片到货需求",
"hasThirdMenu": false
},
{
"id": 17,
"text": "硅片需求预测",
"path": "硅片需求预测",
"hasThirdMenu": false
},
{
"id": 18,
"text": "硅片缺口审视",
"path": "硅片缺口审视",
"hasThirdMenu": false
},
{
"id": 19,
"text": "电池需求数据",
"path": "电池需求数据",
"hasThirdMenu": false
},
{
"id": 20,
"text": "基地产能利用率",
"path": "基地产能利用率",
"hasThirdMenu": false
},
{
"id": 21,
"text": "电池生产计划",
"path": "电池生产计划",
"hasThirdMenu": false
},
{
"id": 22,
"text": "入库节奏基础参数",
"path": "入库节奏基础参数",
"hasThirdMenu": false
},
{
"id": 23,
"text": "入库节奏",
"path": "入库节奏",
"hasThirdMenu": false
},
{
"id": 24,
"text": "电池基地生产计划",
"path": "电池基地生产计划",
"hasThirdMenu": false
},
{
"id": 25,
"text": "电池入库比例",
"path": "电池入库比例",
"hasThirdMenu": false
},
{
"id": 26,
"text": "电池入库计划",
"path": "电池入库计划",
"hasThirdMenu": false
},
{
"id": 27,
"text": "电池要货计划",
"path": "电池要货计划",
"hasThirdMenu": false
},
{
"id": 28,
"text": "电池发货计划",
"path": "电池发货计划",
"hasThirdMenu": false
},
{
"id": 29,
"text": "jiahx测试",
"path": "jiahx测试",
"hasThirdMenu": false
},
{
"id": 30,
"text": "业务参数",
"path": "业务参数",
"hasThirdMenu": false
},
{
"id": 31,
"text": "计划对象",
"path": "计划对象",
"hasThirdMenu": false
},
{
"id": 32,
"text": "排产优先级",
"path": "排产优先级",
"hasThirdMenu": false
},
{
"id": 33,
"text": "逃生通道",
"path": "逃生通道",
"hasThirdMenu": false
},
{
"id": 34,
"text": "主需求计划",
"path": "主需求计划",
"hasThirdMenu": true
},
{
"id": 35,
"text": "电池投放",
"path": "电池投放",
"hasThirdMenu": true
},
{
"id": 36,
"text": "供需粗匹配",
"path": "供需粗匹配",
"hasThirdMenu": true
},
{
"id": 37,
"text": "ATP",
"path": "ATP",
"hasThirdMenu": true
},
{
"id": 38,
"text": "电池要发货",
"path": "电池要发货",
"hasThirdMenu": true
},
{
"id": 39,
"text": "主计划/基地计划",
"path": "主计划/基地计划",
"hasThirdMenu": true
},
{
"id": 40,
"text": "基地计划",
"path": "基地计划",
"hasThirdMenu": true
},
{
"id": 41,
"text": "产品系列(内部手工维护)",
"path": "产品系列(内部手工维护)",
"hasThirdMenu": true
},
{
"id": 42,
"text": "管理视图",
"path": "管理视图",
"hasThirdMenu": false
}
]

View File

@ -0,0 +1 @@
[]