优化分批执行点击菜单

This commit is contained in:
dengqichen 2025-03-07 14:52:52 +08:00
parent 03e91a0ff9
commit 6221a18dd3
3 changed files with 176 additions and 196 deletions

View File

@ -1,62 +1,10 @@
const {chromium} = require('@playwright/test');
const LongiMainPage = require('../pages/LongiMainPage');
const LongiLoginPage = require('../pages/LongiLoginPage');
const menuDataService = require('../services/LongiTestService'); const menuDataService = require('../services/LongiTestService');
/** /**
* 测试控制器 * 测试控制器
* 负责协调页面操作和数据管理 * 负责协调测试流程
*/ */
class LongiTestController { class LongiTestController {
constructor() {
this.initializeConfig();
}
/**
* 初始化配置
* @private
*/
initializeConfig() {
// 从环境变量获取测试相关配置
this.batchSize = parseInt(process.env.TEST_BATCH_SIZE);
this.retryCount = parseInt(process.env.TEST_RETRY_COUNT);
this.batchInterval = parseInt(process.env.TEST_BATCH_INTERVAL);
this.maxRetryDelay = parseInt(process.env.TEST_MAX_RETRY_DELAY);
}
/**
* 创建浏览器实例
* @returns {Promise<{browser: import('@playwright/test').Browser, page: import('@playwright/test').Page}>}
* @private
*/
async createBrowser() {
try {
const browser = await chromium.launch();
const page = await browser.newPage();
return {browser, page};
} catch (error) {
console.error('创建浏览器实例失败:', error);
throw new Error('无法创建浏览器实例');
}
}
/**
* 执行登录操作
* @param {import('@playwright/test').Page} page - Playwright页面对象
* @returns {Promise<boolean>} 登录是否成功
* @private
*/
async performLogin(page) {
try {
const loginPage = new LongiLoginPage(page);
await loginPage.navigateToLoginPage();
return await loginPage.clickLoginButton();
} catch (error) {
console.error('登录失败:', error);
return false;
}
}
/** /**
* 获取并保存菜单数据 * 获取并保存菜单数据
* @returns {Promise<Array>} 菜单数据数组 * @returns {Promise<Array>} 菜单数据数组
@ -65,102 +13,12 @@ class LongiTestController {
return await menuDataService.fetchAndSaveMenuData(); return await menuDataService.fetchAndSaveMenuData();
} }
/**
* 执行一批菜单的测试
* @param {Array} menuBatch - 要测试的菜单数组
* @private
*/
async runBatchTest(menuBatch) {
if (!menuBatch?.length) return;
console.log(`开始执行批次测试,包含 ${menuBatch.length} 个菜单项:`);
console.log(menuBatch.map(m => m.text).join(', '));
let browser, page;
try {
({browser, page} = await this.createBrowser());
const mainPage = new LongiMainPage(page);
if (!await this.performLogin(page)) {
throw new Error('登录失败,无法执行菜单测试');
}
await mainPage.handleAllMenuClicks(menuBatch);
await this.updateTestProgress(menuBatch);
} catch (error) {
console.error('批次测试执行失败:', error);
throw error;
} finally {
if (browser) await browser.close();
}
}
/**
* 更新测试进度
* @param {Array} completedBatch - 完成测试的菜单批次
* @private
*/
async updateTestProgress(completedBatch) {
try {
const progress = menuDataService.getProgress();
const newProgress = [...progress];
for (const menuItem of completedBatch) {
if (!newProgress.includes(menuItem.id)) {
newProgress.push(menuItem.id);
}
}
menuDataService.saveProgress(newProgress);
} catch (error) {
console.error('更新测试进度失败:', error);
throw error;
}
}
/**
* 获取下一批要测试的菜单
* @returns {Array|null} - 下一批要测试的菜单如果没有则返回null
* @private
*/
getNextBatch() {
try {
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);
} catch (error) {
console.error('获取下一批测试菜单失败:', error);
throw error;
}
}
/** /**
* 获取测试进度信息 * 获取测试进度信息
* @returns {Object} - 进度信息 * @returns {Object} - 进度信息
*/ */
getTestProgress() { getTestProgress() {
try { return menuDataService.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
};
} catch (error) {
console.error('获取测试进度失败:', error);
throw error;
}
} }
/** /**
@ -168,47 +26,7 @@ class LongiTestController {
* @returns {Promise<Object>} - 测试结果 * @returns {Promise<Object>} - 测试结果
*/ */
async runAllTests() { async runAllTests() {
try { return await menuDataService.runAllTests();
// 清理之前的进度
menuDataService.saveProgress([]);
let retryCount = 0;
let lastError = null;
while (true) {
try {
const batch = this.getNextBatch();
if (!batch?.length) {
const progress = this.getTestProgress();
if (progress.completed < progress.total && retryCount < this.retryCount) {
console.log(`\n还有未完成的测试,尝试重试 (${retryCount + 1}/${this.retryCount})...`);
retryCount++;
// 使用指数退避策略
const delay = Math.min(this.batchInterval * Math.pow(2, retryCount), this.maxRetryDelay);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return this.getTestProgress();
}
// 执行当前批次
await this.runBatchTest(batch);
lastError = null;
// 批次间暂停
await new Promise(resolve => setTimeout(resolve, this.batchInterval));
} catch (error) {
lastError = error;
console.error('批次执行失败:', error);
// 继续下一个批次
}
}
} catch (error) {
console.error('执行所有测试失败:', error);
throw error;
}
} }
} }

View File

@ -6,21 +6,121 @@ const {chromium} = require('@playwright/test');
/** /**
* 菜单数据服务 * 菜单数据服务
* 负责菜单数据的存储和检索 * 负责菜单数据的存储和检索以及浏览器操作
*/ */
class LongiTestService { class LongiTestService {
constructor() { constructor() {
// 从环境变量获取路径配置 this.initializeConfig();
this.initializePaths();
}
/**
* 初始化配置
* @private
*/
initializeConfig() {
this.batchSize = parseInt(process.env.TEST_BATCH_SIZE);
this.retryCount = parseInt(process.env.TEST_RETRY_COUNT);
this.batchInterval = parseInt(process.env.TEST_BATCH_INTERVAL);
this.maxRetryDelay = parseInt(process.env.TEST_MAX_RETRY_DELAY);
}
/**
* 初始化路径
* @private
*/
initializePaths() {
this.dataDir = path.join(process.cwd(), process.env.TEST_DATA_DIR); this.dataDir = path.join(process.cwd(), process.env.TEST_DATA_DIR);
this.menuDataPath = process.env.MENU_DATA_FILE_PATH; this.menuDataPath = process.env.MENU_DATA_FILE_PATH;
this.progressPath = process.env.TEST_PROGRESS_FILE_PATH; this.progressPath = process.env.TEST_PROGRESS_FILE_PATH;
// 确保数据目录存在
if (!fs.existsSync(this.dataDir)) { if (!fs.existsSync(this.dataDir)) {
fs.mkdirSync(this.dataDir, { recursive: true }); fs.mkdirSync(this.dataDir, {recursive: true});
} }
} }
/**
* 创建浏览器实例
* @returns {Promise<{browser: Browser, page: Page}>}
* @private
*/
async createBrowser() {
const browser = await chromium.launch();
const page = await browser.newPage();
return {browser, page};
}
/**
* 执行登录操作
* @param {Page} page - Playwright页面对象
* @returns {Promise<void>}
* @private
*/
async performLogin(page) {
const loginPage = new LongiLoginPage(page);
await loginPage.navigateToLoginPage();
const loginSuccess = await loginPage.clickLoginButton();
if (!loginSuccess) {
throw new Error('登录失败');
}
}
/**
* 执行一批菜单的测试
* @param {Array} menuBatch - 要测试的菜单数组
*/
async runBatchTest(menuBatch) {
if (!menuBatch?.length) return;
console.log(`开始执行批次测试,包含 ${menuBatch.length} 个菜单项:`);
console.log(menuBatch.map(m => m.text).join(', '));
let browser, page;
try {
({browser, page} = await this.createBrowser());
await this.performLogin(page);
const mainPage = new LongiMainPage(page);
await mainPage.handleAllMenuClicks(menuBatch);
await this.updateTestProgress(menuBatch);
} finally {
if (browser) await browser.close();
}
}
/**
* 更新测试进度
* @param {Array} completedBatch - 完成测试的菜单批次
* @private
*/
async updateTestProgress(completedBatch) {
const progress = this.getProgress();
const newProgress = [...progress];
for (const menuItem of completedBatch) {
if (!newProgress.includes(menuItem.id)) {
newProgress.push(menuItem.id);
}
}
this.saveProgress(newProgress);
}
/**
* 获取下一批要测试的菜单
* @returns {Array|null} - 下一批要测试的菜单如果没有则返回null
*/
getNextBatch() {
const menuData = this.getMenuData();
const progress = this.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 {Promise<Array>} 菜单数据数组 * @returns {Promise<Array>} 菜单数据数组
@ -32,12 +132,12 @@ class LongiTestService {
try { try {
browser = await chromium.launch(); browser = await chromium.launch();
page = await browser.newPage(); page = await browser.newPage();
// 登录 // 登录
const loginPage = new LongiLoginPage(page); const loginPage = new LongiLoginPage(page);
await loginPage.navigateToLoginPage(); await loginPage.navigateToLoginPage();
const loginSuccess = await loginPage.clickLoginButton(); const loginSuccess = await loginPage.clickLoginButton();
if (!loginSuccess) { if (!loginSuccess) {
throw new Error('登录失败,无法获取菜单数据'); throw new Error('登录失败,无法获取菜单数据');
} }
@ -45,10 +145,10 @@ class LongiTestService {
// 获取菜单数据 // 获取菜单数据
const mainPage = new LongiMainPage(page); const mainPage = new LongiMainPage(page);
const menuItems = await mainPage.getMenuItems(); const menuItems = await mainPage.getMenuItems();
// 保存数据 // 保存数据
await this.saveMenuData(menuItems); await this.saveMenuData(menuItems);
return menuItems; return menuItems;
} catch (error) { } catch (error) {
console.error(`获取并保存菜单数据时出错: ${error}`); console.error(`获取并保存菜单数据时出错: ${error}`);
@ -116,6 +216,68 @@ class LongiTestService {
fs.unlinkSync(this.progressPath); fs.unlinkSync(this.progressPath);
} }
} }
/**
* 执行所有菜单的测试
* @returns {Promise<Object>} - 测试结果
*/
async runAllTests() {
// 清理之前的进度
this.saveProgress([]);
let retryCount = 0;
while (true) {
try {
const batch = this.getNextBatch();
if (!batch?.length) {
const progress = this.getTestProgress();
if (progress.completed < progress.total && retryCount < this.retryCount) {
console.log(`\n还有未完成的测试,尝试重试 (${retryCount + 1}/${this.retryCount})...`);
retryCount++;
const delay = Math.min(this.batchInterval * Math.pow(2, retryCount), this.maxRetryDelay);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return progress;
}
await this.runBatchTest(batch);
await new Promise(resolve => setTimeout(resolve, this.batchInterval));
} catch (error) {
console.error('测试执行失败:', error);
if (error.message.includes('登录失败')) {
throw error; // 登录失败直接终止
}
// 其他错误继续下一批次
}
}
}
/**
* 获取测试进度信息
* @returns {Object} - 进度信息包含总数已完成数和剩余数
*/
getTestProgress() {
try {
const menuData = this.getMenuData();
const progress = this.getProgress();
if (!menuData) {
return {total: 0, completed: 0, remaining: 0};
}
return {
total: menuData.length,
completed: progress.length,
remaining: menuData.length - progress.length
};
} catch (error) {
console.error('获取测试进度失败:', error);
throw error;
}
}
} }
// 导出单例实例 // 导出单例实例

View File

@ -18,8 +18,8 @@ test.describe('测试所有隆基需求计划是否可用', () => {
}); });
test('访问并点击所有页面', async () => { test('访问并点击所有页面', async () => {
// 开始批量测试 // 执行所有测试
const result = await controller.runAllTests(); await controller.runAllTests();
// 验证测试结果 // 验证测试结果
const progress = controller.getTestProgress(); const progress = controller.getTestProgress();