const fs = require('fs'); const path = require('path'); const LongiMainPage = require('../pages/LongiMainPage'); const LongiLoginPage = require('../pages/LongiLoginPage'); 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}); } } /** * 创建浏览器实例 * @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} 菜单数据数组 */ async fetchAndSaveMenuData() { console.log('开始获取菜单数据...'); let browser, page; 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('登录失败,无法获取菜单数据'); } // 获取菜单数据 const mainPage = new LongiMainPage(page); const menuItems = await mainPage.getMenuItems(); // 保存数据 await this.saveMenuData(menuItems); return menuItems; } catch (error) { console.error(`获取并保存菜单数据时出错: ${error}`); return null; } finally { if (browser) await browser.close(); } } /** * 保存菜单数据 * @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); } } /** * 执行所有菜单的测试 * @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; } } } // 导出单例实例 module.exports = new LongiTestService();