优化分批执行点击菜单

This commit is contained in:
dengqichen 2025-03-07 13:05:31 +08:00
parent c8bd981cc4
commit ff891e82b0
7 changed files with 185 additions and 81 deletions

View File

@ -13,3 +13,13 @@ 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
# 浏览器配置
BROWSER_HEADLESS=false
BROWSER_SLOW_MO=50
BROWSER_TIMEOUT=30000
# 视窗配置
VIEWPORT_WIDTH=1920
VIEWPORT_HEIGHT=1080

View File

@ -1,6 +1,6 @@
const { chromium } = require('@playwright/test');
const LongiMainPage = require('../../tests/pages/LongiMainPage');
const LongiLoginPage = require('../../tests/pages/LongiLoginPage');
const LongiMainPage = require('../pages/LongiMainPage');
const LongiLoginPage = require('../pages/LongiLoginPage');
const menuDataService = require('../services/MenuDataService');
/**
@ -9,28 +9,67 @@ const menuDataService = require('../services/MenuDataService');
*/
class TestController {
constructor() {
this.initializeConfig();
}
/**
* 初始化配置
* @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: false,
headless: process.env.BROWSER_HEADLESS === 'true',
args: ['--start-maximized'],
slowMo: 50
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)
};
}
/**
* 创建浏览器实例
* @returns {Promise<{browser: import('@playwright/test').Browser, page: import('@playwright/test').Page}>}
* @private
*/
async createBrowser() {
try {
const browser = await chromium.launch(this.browserConfig);
const page = await browser.newPage();
await page.setViewportSize(this.viewportConfig);
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) {
const loginPage = new LongiLoginPage(page);
await loginPage.navigateToLoginPage();
return await loginPage.clickLoginButton();
try {
const loginPage = new LongiLoginPage(page);
await loginPage.navigateToLoginPage();
return await loginPage.clickLoginButton();
} catch (error) {
console.error('登录失败:', error);
return false;
}
}
/**
@ -39,73 +78,98 @@ class TestController {
*/
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);
let browser, page;
try {
({ browser, page } = await this.createBrowser());
const mainPage = new LongiMainPage(page);
if (!await this.performLogin(page)) {
throw new Error('登录失败,无法收集菜单数据');
}
const menuItems = await mainPage.checkAndLoadMenuItems();
return menuDataService.saveMenuData(menuItems);
} catch (error) {
console.error('收集菜单数据失败:', error);
throw error;
} finally {
await browser.close();
if (browser) await browser.close();
}
}
/**
* 执行一批菜单的测试
* @param {Array} menuBatch - 要测试的菜单数组
* @private
*/
async runBatchTest(menuBatch) {
if (!menuBatch?.length) return;
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);
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 menuBatch) {
for (const menuItem of completedBatch) {
if (!newProgress.includes(menuItem.id)) {
newProgress.push(menuItem.id);
}
}
menuDataService.saveProgress(newProgress);
} finally {
await browser.close();
} catch (error) {
console.error('更新测试进度失败:', error);
throw error;
}
}
/**
* 获取下一批要测试的菜单
* @returns {Array|null} - 下一批要测试的菜单如果没有则返回null
* @private
*/
getNextBatch() {
const menuData = menuDataService.getMenuData();
const progress = menuDataService.getProgress();
try {
const menuData = menuDataService.getMenuData();
const progress = menuDataService.getProgress();
if (!menuData) return null;
if (!menuData) return null;
const remainingMenus = menuData.filter(menu => !progress.includes(menu.id));
if (remainingMenus.length === 0) return null;
const remainingMenus = menuData.filter(menu => !progress.includes(menu.id));
if (remainingMenus.length === 0) return null;
return remainingMenus.slice(0, this.batchSize);
return remainingMenus.slice(0, this.batchSize);
} catch (error) {
console.error('获取下一批测试菜单失败:', error);
throw error;
}
}
/**
@ -113,18 +177,71 @@ class TestController {
* @returns {Object} - 进度信息
*/
getTestProgress() {
const menuData = menuDataService.getMenuData();
const progress = menuDataService.getProgress();
try {
const menuData = menuDataService.getMenuData();
const progress = menuDataService.getProgress();
if (!menuData) {
return { total: 0, completed: 0, remaining: 0 };
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;
}
}
return {
total: menuData.length,
completed: progress.length,
remaining: menuData.length - progress.length
};
/**
* 执行所有菜单的测试
* @returns {Promise<Object>} - 测试结果
*/
async runAllTests() {
try {
// 清理之前的进度
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

@ -3,55 +3,32 @@ require('../../config/env');
const { test } = require('@playwright/test');
const TestController = require('../../src/controllers/TestController');
const menuDataService = require('../../src/services/MenuDataService');
test.describe('菜单可访问性测试', () => {
let controller;
test.beforeAll(async () => {
controller = new TestController();
// 打印环境变量,用于调试
console.log('BASE_URL:', process.env.BASE_URL);
});
test('收集菜单数据', async () => {
test('应该能成功收集所有菜单数据', async () => {
const menuData = await controller.collectMenuData();
console.log(`成功收集 ${menuData.length} 个菜单项`);
test.expect(menuData.length).toBeGreaterThan(0);
console.log(`✓ 成功收集 ${menuData.length} 个菜单项`);
});
test('批量测试菜单', async () => {
// 清理之前的进度
menuDataService.saveProgress([]);
test('应该能访问所有菜单页面', async () => {
// 开始批量测试
const result = await controller.runAllTests();
let retryCount = 0;
const maxRetries = parseInt(process.env.TEST_RETRY_COUNT || '3', 10);
// 验证测试结果
const progress = controller.getTestProgress();
test.expect(progress.completed).toBe(progress.total);
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(`最终进度: ${finalProgress.completed}/${finalProgress.total}`);
break;
}
// 显示当前进度
const currentProgress = controller.getTestProgress();
console.log(`\n当前进度: ${currentProgress.completed}/${currentProgress.total}`);
// 执行当前批次
await controller.runBatchTest(batch);
// 批次间暂停
const batchInterval = parseInt(process.env.TEST_BATCH_INTERVAL || '2000', 10);
await new Promise(resolve => setTimeout(resolve, batchInterval));
}
// 输出测试统计
console.log('\n测试完成');
console.log(`✓ 总计测试: ${progress.total} 个菜单`);
console.log(`✓ 成功完成: ${progress.completed} 个菜单`);
console.log(`✓ 成功率: ${((progress.completed / progress.total) * 100).toFixed(2)}%`);
});
});