From 6221a18dd3973943974facb7c3c0ff1d1b7ee83a Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 7 Mar 2025 14:52:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=88=86=E6=89=B9=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=82=B9=E5=87=BB=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/LongiTestController.js | 188 +------------------------ src/services/LongiTestService.js | 180 +++++++++++++++++++++-- tests/e2e/menu.spec.js | 4 +- 3 files changed, 176 insertions(+), 196 deletions(-) diff --git a/src/controllers/LongiTestController.js b/src/controllers/LongiTestController.js index 28d9ade..d6c3f5f 100644 --- a/src/controllers/LongiTestController.js +++ b/src/controllers/LongiTestController.js @@ -1,62 +1,10 @@ -const {chromium} = require('@playwright/test'); -const LongiMainPage = require('../pages/LongiMainPage'); -const LongiLoginPage = require('../pages/LongiLoginPage'); const menuDataService = require('../services/LongiTestService'); /** * 测试控制器 - * 负责协调页面操作和数据管理 + * 负责协调测试流程 */ 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} 登录是否成功 - * @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} 菜单数据数组 @@ -65,102 +13,12 @@ class LongiTestController { 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} - 进度信息 */ getTestProgress() { - try { - 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; - } + return menuDataService.getTestProgress(); } /** @@ -168,47 +26,7 @@ class LongiTestController { * @returns {Promise} - 测试结果 */ 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; - } + return await menuDataService.runAllTests(); } } diff --git a/src/services/LongiTestService.js b/src/services/LongiTestService.js index 07d371c..cafbb98 100644 --- a/src/services/LongiTestService.js +++ b/src/services/LongiTestService.js @@ -6,21 +6,121 @@ const {chromium} = require('@playwright/test'); /** * 菜单数据服务 - * 负责菜单数据的存储和检索 + * 负责菜单数据的存储和检索,以及浏览器操作 */ class LongiTestService { 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.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 }); + 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} + * @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} 菜单数据数组 @@ -32,12 +132,12 @@ class LongiTestService { try { browser = await chromium.launch(); page = await browser.newPage(); - + // 登录 const loginPage = new LongiLoginPage(page); await loginPage.navigateToLoginPage(); const loginSuccess = await loginPage.clickLoginButton(); - + if (!loginSuccess) { throw new Error('登录失败,无法获取菜单数据'); } @@ -45,10 +145,10 @@ class LongiTestService { // 获取菜单数据 const mainPage = new LongiMainPage(page); const menuItems = await mainPage.getMenuItems(); - + // 保存数据 await this.saveMenuData(menuItems); - + return menuItems; } catch (error) { console.error(`获取并保存菜单数据时出错: ${error}`); @@ -116,6 +216,68 @@ class LongiTestService { fs.unlinkSync(this.progressPath); } } + + /** + * 执行所有菜单的测试 + * @returns {Promise} - 测试结果 + */ + 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; + } + } } // 导出单例实例 diff --git a/tests/e2e/menu.spec.js b/tests/e2e/menu.spec.js index cd83f04..45c859d 100644 --- a/tests/e2e/menu.spec.js +++ b/tests/e2e/menu.spec.js @@ -18,8 +18,8 @@ test.describe('测试所有隆基需求计划是否可用', () => { }); test('访问并点击所有页面', async () => { - // 开始批量测试 - const result = await controller.runAllTests(); + // 执行所有测试 + await controller.runAllTests(); // 验证测试结果 const progress = controller.getTestProgress();