From 40863e3eca5e4af8c6713226ad01fe21c4e74eb4 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 7 Mar 2025 16:53:34 +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 --- package.json | 4 +- playwright.config.js | 25 +++-- src/controllers/LongiTestController.js | 8 -- src/hooks/setup.js | 11 ++ src/hooks/teardown.js | 11 ++ src/hooks/testLifecycle.js | 119 ++++++++++++++++++++++ src/pages/BasePage.js | 38 ++++++- src/pages/LongiMainPage.js | 6 +- src/services/LongiTestService.js | 135 +++++++++++++++++++------ src/services/PerformanceService.js | 108 ++++++++++++++++++++ src/utils/FileUtils.js | 73 +++++++++++-- test-data/test-progress.json | 8 +- tests/e2e/menu.spec.js | 25 +++-- 13 files changed, 493 insertions(+), 78 deletions(-) create mode 100644 src/hooks/setup.js create mode 100644 src/hooks/teardown.js create mode 100644 src/hooks/testLifecycle.js create mode 100644 src/services/PerformanceService.js diff --git a/package.json b/package.json index 0cfd5aa..31a1591 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,7 @@ "test:menu": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js", "test:menu:ui": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --ui", "test:menu:debug": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --debug", - "test:menu:clean": "cross-env NODE_ENV=dev rimraf test-data/* && npm run test:menu", - "test:menu:collect": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --grep \"收集菜单数据\"", - "test:menu:batch": "cross-env NODE_ENV=dev playwright test tests/e2e/menu.spec.js --grep \"批量测试菜单\"" + "test:menu:clean": "cross-env NODE_ENV=dev rimraf test-data/* && npm run test:menu" }, "keywords": [ "playwright", diff --git a/playwright.config.js b/playwright.config.js index 21ebccb..868c75b 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,19 +1,18 @@ // @ts-check const {defineConfig, devices} = require('@playwright/test'); +const testLifecycle = require('./src/hooks/testLifecycle'); /** * @see https://playwright.dev/docs/test-configuration - */ -/** * @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { testDir: './tests', /* 测试超时时间 */ - timeout: parseInt(process.env.EXPECT_TIMEOUT), + timeout: parseInt(process.env.EXPECT_TIMEOUT || '30000'), /* 每个测试的预期状态 */ expect: { - timeout: parseInt(process.env.EXPECT_TIMEOUT) + timeout: parseInt(process.env.EXPECT_TIMEOUT || '30000') }, /* 测试运行并发数 */ fullyParallel: false, @@ -30,19 +29,21 @@ const config = { /* 收集测试追踪信息 */ trace: 'on-first-retry', /* 自动截图 */ - screenshot: 'only-on-failure', + screenshot: { + mode: 'only-on-failure' + }, /* 录制视频 */ video: 'retain-on-failure', /* 浏览器配置 */ headless: process.env.BROWSER_HEADLESS === 'true', viewport: { - width: parseInt(process.env.VIEWPORT_WIDTH), - height: parseInt(process.env.VIEWPORT_HEIGHT) + width: parseInt(process.env.VIEWPORT_WIDTH || '1920'), + height: parseInt(process.env.VIEWPORT_HEIGHT || '1080') }, /* 浏览器启动选项 */ launchOptions: { - slowMo: parseInt(process.env.BROWSER_SLOW_MO), - timeout: parseInt(process.env.BROWSER_TIMEOUT) + slowMo: parseInt(process.env.BROWSER_SLOW_MO || '50'), + timeout: parseInt(process.env.BROWSER_TIMEOUT || '30000') } }, @@ -60,6 +61,10 @@ const config = { // port: 3000, // reuseExistingServer: !process.env.CI, // }, + + /* 全局设置和清理 */ + globalSetup: './src/hooks/setup.js', + globalTeardown: './src/hooks/teardown.js' }; -module.exports = config; \ No newline at end of file +module.exports = defineConfig(config); \ No newline at end of file diff --git a/src/controllers/LongiTestController.js b/src/controllers/LongiTestController.js index d6c3f5f..1aa67e5 100644 --- a/src/controllers/LongiTestController.js +++ b/src/controllers/LongiTestController.js @@ -13,14 +13,6 @@ class LongiTestController { return await menuDataService.fetchAndSaveMenuData(); } - /** - * 获取测试进度信息 - * @returns {Object} - 进度信息 - */ - getTestProgress() { - return menuDataService.getTestProgress(); - } - /** * 执行所有菜单的测试 * @returns {Promise} - 测试结果 diff --git a/src/hooks/setup.js b/src/hooks/setup.js new file mode 100644 index 0000000..961dfa3 --- /dev/null +++ b/src/hooks/setup.js @@ -0,0 +1,11 @@ +const testLifecycle = require('./testLifecycle'); + +/** + * 全局设置 + * 在所有测试开始前执行 + */ +async function globalSetup() { + await testLifecycle.beforeAll(); +} + +module.exports = globalSetup; \ No newline at end of file diff --git a/src/hooks/teardown.js b/src/hooks/teardown.js new file mode 100644 index 0000000..881b895 --- /dev/null +++ b/src/hooks/teardown.js @@ -0,0 +1,11 @@ +const testLifecycle = require('./testLifecycle'); + +/** + * 全局清理 + * 在所有测试结束后执行 + */ +async function globalTeardown() { + await testLifecycle.afterAll(); +} + +module.exports = globalTeardown; \ No newline at end of file diff --git a/src/hooks/testLifecycle.js b/src/hooks/testLifecycle.js new file mode 100644 index 0000000..30395dc --- /dev/null +++ b/src/hooks/testLifecycle.js @@ -0,0 +1,119 @@ +const performanceService = require('../services/PerformanceService'); +const FileUtils = require('../utils/FileUtils'); + +/** + * 测试生命周期管理 + * 负责管理测试的全局生命周期,包括: + * 1. 测试开始前的初始化工作 + * 2. 测试结束后的清理和报告生成 + * 3. 其他全局测试设置 + */ +class TestLifecycle { + constructor() { + this.reportPath = process.env.PERFORMANCE_REPORT_PATH || './test-results/performance-report.json'; + } + + /** + * 测试开始前的初始化 + * 在所有测试开始前执行,用于准备测试环境 + */ + async beforeAll() { + // 确保测试结果目录存在 + await FileUtils.ensureDirectoryExists('./test-results'); + // 清空之前的性能数据 + await performanceService.clearPerformanceData(); + console.log('✨ 测试环境初始化完成'); + } + + /** + * 测试结束后的处理 + * 在所有测试结束后执行,用于生成报告和清理工作 + */ + async afterAll() { + const performanceData = performanceService.getPerformanceData(); + const report = this.generateReport(performanceData); + await this.saveReport(report); + console.log(`📊 测试报告已生成: ${this.reportPath}`); + } + + /** + * 生成测试报告 + * @param {Array} performanceData 性能数据数组 + * @returns {Object} 测试报告对象 + */ + generateReport(performanceData) { + // 按页面名称分组的统计 + const pageStats = {}; + let totalDuration = 0; + let totalSuccess = 0; + let totalFailure = 0; + + // 统计每个页面的性能数据 + performanceData.forEach(record => { + const { pageName, duration, success } = record; + + if (!pageStats[pageName]) { + pageStats[pageName] = { + totalTests: 0, + successCount: 0, + failureCount: 0, + totalDuration: 0, + averageDuration: 0, + minDuration: Infinity, + maxDuration: 0, + errors: [] + }; + } + + const stats = pageStats[pageName]; + stats.totalTests++; + success ? stats.successCount++ : stats.failureCount++; + stats.totalDuration += duration; + stats.minDuration = Math.min(stats.minDuration, duration); + stats.maxDuration = Math.max(stats.maxDuration, duration); + stats.averageDuration = stats.totalDuration / stats.totalTests; + + if (!success && record.errorMessage) { + stats.errors.push({ + timestamp: record.timestamp, + error: record.errorMessage + }); + } + + // 更新总体统计 + totalDuration += duration; + success ? totalSuccess++ : totalFailure++; + }); + + // 生成报告 + return { + summary: { + totalTests: performanceData.length, + successCount: totalSuccess, + failureCount: totalFailure, + successRate: (totalSuccess / performanceData.length * 100).toFixed(2) + '%', + averageDuration: totalDuration / performanceData.length, + totalDuration: totalDuration + }, + pageStats, + timestamp: new Date().toISOString(), + rawData: performanceData + }; + } + + /** + * 保存测试报告 + * @param {Object} report 测试报告对象 + */ + async saveReport(report) { + try { + await FileUtils.writeJsonFile(this.reportPath, report); + } catch (error) { + console.error('保存测试报告失败:', error); + } + } +} + +// 创建单例实例 +const testLifecycle = new TestLifecycle(); +module.exports = testLifecycle; \ No newline at end of file diff --git a/src/pages/BasePage.js b/src/pages/BasePage.js index 8f771aa..0e7ec33 100644 --- a/src/pages/BasePage.js +++ b/src/pages/BasePage.js @@ -3,6 +3,8 @@ * 提供所有页面共用的方法和属性 */ +const performanceService = require('../services/PerformanceService'); + class BasePage { /** * 创建页面对象 @@ -192,20 +194,28 @@ class BasePage { async waitForIBPPageLoadWithRetry(pageName) { console.log(`等待页面 ${pageName} 数据加载...`); + const startTime = Date.now(); let retryCount = 0; const {maxRetries, retryInterval, stabilityDelay} = this.config.pageLoad; + let errorMessage = null; try { while (retryCount < maxRetries) { // 检查错误状态 const hasError = await this.checkPageError(pageName); - if (hasError) return false; + if (hasError) { + errorMessage = await this.getErrorMessage(); + break; + } // 检查加载状态 const isLoading = await this.elementExistsBySelector(this.selectors.loadingMask); if (!isLoading) { await this.waitForTimeout(stabilityDelay); console.log(`✅ 页面 ${pageName} 加载完成`); + + // 记录成功的性能数据 + await performanceService.recordSuccess(pageName, startTime, Date.now()); return true; } @@ -213,9 +223,17 @@ class BasePage { await this.waitForTimeout(retryInterval); } - console.error(`页面加载超时: ${pageName}, 重试次数: ${maxRetries}`); + if (retryCount >= maxRetries) { + errorMessage = `页面加载超时: ${pageName}, 重试次数: ${maxRetries}`; + } + + // 记录失败的性能数据 + await performanceService.recordFailure(pageName, startTime, Date.now(), errorMessage); + console.error(errorMessage); return false; } catch (error) { + // 记录异常的性能数据 + await performanceService.recordError(pageName, startTime, Date.now(), error); console.error(`页面加载出错: ${pageName}, 错误信息: ${error.message}`); return false; } @@ -242,6 +260,21 @@ class BasePage { return false; } + /** + * 获取错误信息 + * @returns {Promise} 错误信息 + * @private + */ + async getErrorMessage() { + const errorSelectors = [this.selectors.errorBox, this.selectors.errorMessage]; + for (const selector of errorSelectors) { + const elements = await this.page.locator(selector).all(); + if (elements.length > 0) { + return await this.getTextByElement(elements[0]); + } + } + return null; + } /** * 获取当前URL @@ -251,7 +284,6 @@ class BasePage { return this.page.url(); } - /** * 等待指定时间 * @param {number} ms 等待时间(毫秒) diff --git a/src/pages/LongiMainPage.js b/src/pages/LongiMainPage.js index 8426e9e..58b8855 100644 --- a/src/pages/LongiMainPage.js +++ b/src/pages/LongiMainPage.js @@ -119,7 +119,7 @@ class LongiMainPage extends BasePage { * @returns {Promise} 菜单项数组 */ async findMenuItems() { - console.log('开始查找菜单项...'); + // console.log('开始查找菜单项...'); // 等待菜单加载完成 await this.page.waitForSelector(this.selectors.sideNav, { @@ -128,7 +128,7 @@ class LongiMainPage extends BasePage { // 获取所有菜单项 const items = await this.page.locator(this.selectors.menuItems).all(); - console.log(`找到 ${items.length} 个菜单项元素`); + // console.log(`找到 ${items.length} 个菜单项元素`); const menuItems = []; // 处理每个菜单项 @@ -165,7 +165,7 @@ class LongiMainPage extends BasePage { // 综合判断是否有三级菜单 const hasThirdMenu = isSubMenuTitle || hasSubMenuIndicator || hasThirdLevelIndicator; - console.log(`菜单项 "${text.trim()}" ${hasThirdMenu ? '有' : '没有'}三级菜单 (通过DOM结构判断)`); + // console.log(`菜单项 "${text.trim()}" ${hasThirdMenu ? '有' : '没有'}三级菜单 (通过DOM结构判断)`); // 生成唯一标识符,结合索引和文本 const uniqueId = `menu_${i}_${text.trim().replace(/\s+/g, '_')}`; diff --git a/src/services/LongiTestService.js b/src/services/LongiTestService.js index cafbb98..8970e80 100644 --- a/src/services/LongiTestService.js +++ b/src/services/LongiTestService.js @@ -83,7 +83,9 @@ class LongiTestService { const mainPage = new LongiMainPage(page); await mainPage.handleAllMenuClicks(menuBatch); - await this.updateTestProgress(menuBatch); + } catch (error) { + console.error('批次测试执行失败:', error); + throw error; } finally { if (browser) await browser.close(); } @@ -95,13 +97,29 @@ class LongiTestService { * @private */ async updateTestProgress(completedBatch) { + if (!completedBatch?.length) return; + const progress = this.getProgress(); const newProgress = [...progress]; + + // 验证并更新进度 for (const menuItem of completedBatch) { + if (!menuItem?.id) { + console.warn('警告: 菜单项缺少ID', menuItem); + continue; + } if (!newProgress.includes(menuItem.id)) { newProgress.push(menuItem.id); } } + + // 验证新进度的有效性 + const menuData = this.getMenuData(); + if (newProgress.length > menuData.length) { + console.error('错误: 进度数超过总菜单数'); + return; + } + this.saveProgress(newProgress); } @@ -222,39 +240,89 @@ class LongiTestService { * @returns {Promise} - 测试结果 */ async runAllTests() { - // 清理之前的进度 - this.saveProgress([]); - let retryCount = 0; + try { + // 获取菜单数据并验证 + const menuData = this.getMenuData(); + if (!menuData?.length) { + throw new Error('没有可用的菜单数据,请先执行 fetchAndSaveMenuData'); + } - while (true) { - try { - const batch = this.getNextBatch(); - if (!batch?.length) { - const progress = this.getTestProgress(); + // 清理之前的进度 + this.saveProgress([]); + + let retryCount = 0; + let lastProgress = null; - 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; + while (true) { + try { + const batch = this.getNextBatch(); + if (!batch?.length) { + const progress = this.getTestProgress(); + + // 验证进度数据的有效性 + if (!this.isValidProgress(progress)) { + throw new Error('进度数据无效'); + } + + 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; } - return progress; - } + await this.runBatchTest(batch); + await this.updateTestProgress(batch); - await this.runBatchTest(batch); - await new Promise(resolve => setTimeout(resolve, this.batchInterval)); - } catch (error) { - console.error('测试执行失败:', error); - if (error.message.includes('登录失败')) { - throw error; // 登录失败直接终止 + // 验证进度是否正确更新 + const currentProgress = this.getTestProgress(); + if (lastProgress && currentProgress.completed <= lastProgress.completed) { + console.warn('警告: 进度可能未正确更新'); + } + lastProgress = currentProgress; + + await new Promise(resolve => setTimeout(resolve, this.batchInterval)); + } catch (error) { + console.error('测试执行失败:', error); + if (error.message.includes('登录失败')) { + throw error; // 登录失败直接终止 + } + // 其他错误继续下一批次 } - // 其他错误继续下一批次 } + } catch (error) { + console.error('运行所有测试失败:', error); + throw error; } } + /** + * 验证进度数据的有效性 + * @param {Object} progress - 进度数据 + * @returns {boolean} - 是否有效 + * @private + */ + isValidProgress(progress) { + if (!progress || typeof progress !== 'object') return false; + + const menuData = this.getMenuData(); + if (!menuData?.length) return false; + + return ( + typeof progress.total === 'number' && + typeof progress.completed === 'number' && + typeof progress.remaining === 'number' && + progress.total === menuData.length && + progress.completed >= 0 && + progress.completed <= progress.total && + progress.remaining === (progress.total - progress.completed) + ); + } + /** * 获取测试进度信息 * @returns {Object} - 进度信息,包含总数、已完成数和剩余数 @@ -264,15 +332,22 @@ class LongiTestService { const menuData = this.getMenuData(); const progress = this.getProgress(); - if (!menuData) { + if (!menuData?.length) { + console.warn('警告: 没有可用的菜单数据'); return {total: 0, completed: 0, remaining: 0}; } - return { - total: menuData.length, - completed: progress.length, - remaining: menuData.length - progress.length - }; + const completed = progress.length; + const total = menuData.length; + const remaining = total - completed; + + // 验证数据一致性 + if (completed > total) { + console.error('错误: 完成数超过总数'); + return {total, completed: total, remaining: 0}; + } + + return {total, completed, remaining}; } catch (error) { console.error('获取测试进度失败:', error); throw error; diff --git a/src/services/PerformanceService.js b/src/services/PerformanceService.js new file mode 100644 index 0000000..b87f590 --- /dev/null +++ b/src/services/PerformanceService.js @@ -0,0 +1,108 @@ +const FileUtils = require('../utils/FileUtils'); + +/** + * 性能数据服务 + * 负责记录和管理页面性能数据 + */ +class PerformanceService { + constructor() { + this.performanceData = []; + this.performanceDataPath = process.env.PERFORMANCE_DATA_FILE_PATH || './test-results/performance-data.json'; + } + + /** + * 记录成功的页面加载性能数据 + * @param {string} pageName 页面名称 + * @param {number} startTime 开始时间 + * @param {number} endTime 结束时间 + */ + async recordSuccess(pageName, startTime, endTime) { + await this._recordPerformance({ + pageName, + startTime, + endTime, + duration: endTime - startTime, + success: true, + errorMessage: null + }); + } + + /** + * 记录失败的页面加载性能数据 + * @param {string} pageName 页面名称 + * @param {number} startTime 开始时间 + * @param {number} endTime 结束时间 + * @param {string} errorMessage 错误信息 + */ + async recordFailure(pageName, startTime, endTime, errorMessage) { + await this._recordPerformance({ + pageName, + startTime, + endTime, + duration: endTime - startTime, + success: false, + errorMessage + }); + } + + /** + * 记录异常的页面加载性能数据 + * @param {string} pageName 页面名称 + * @param {number} startTime 开始时间 + * @param {number} endTime 结束时间 + * @param {Error} error 错误对象 + */ + async recordError(pageName, startTime, endTime, error) { + await this._recordPerformance({ + pageName, + startTime, + endTime, + duration: endTime - startTime, + success: false, + errorMessage: error.message + }); + } + + /** + * 内部方法:记录性能数据 + * @private + */ + async _recordPerformance(data) { + this.performanceData.push({ + ...data, + timestamp: new Date().toISOString() + }); + await this.savePerformanceData(); + } + + /** + * 保存性能数据到文件 + */ + async savePerformanceData() { + try { + await FileUtils.writeJsonFile(this.performanceDataPath, this.performanceData); + } catch (error) { + console.error('保存性能数据失败:', error); + } + } + + /** + * 清空性能数据 + */ + async clearPerformanceData() { + this.performanceData = []; + await this.savePerformanceData(); + } + + /** + * 获取性能数据 + * @returns {Array} 性能数据数组 + */ + getPerformanceData() { + return this.performanceData; + } +} + +// 创建单例实例 +const performanceService = new PerformanceService(); +module.exports = performanceService; \ No newline at end of file diff --git a/src/utils/FileUtils.js b/src/utils/FileUtils.js index fde2c13..83f423f 100644 --- a/src/utils/FileUtils.js +++ b/src/utils/FileUtils.js @@ -2,7 +2,7 @@ * 文件操作工具类 * 提供文件读写、目录创建等常用操作 */ -const fs = require('fs'); +const fs = require('fs').promises; const path = require('path'); class FileUtils { @@ -25,17 +25,72 @@ class FileUtils { /** * 确保目录存在,如果不存在则创建 * @param {string} dirPath 目录路径 - * @returns {boolean} 操作是否成功 */ - static ensureDirectoryExists(dirPath) { + static async ensureDirectoryExists(dirPath) { try { - if (!fs.existsSync(dirPath)) { - fs.mkdirSync(dirPath, {recursive: true}); - console.log(`目录已创建: ${dirPath}`); - } - return true; + await fs.access(dirPath); + } catch { + await fs.mkdir(dirPath, { recursive: true }); + } + } + + /** + * 写入JSON文件 + * @param {string} filePath 文件路径 + * @param {Object} data 要写入的数据 + */ + static async writeJsonFile(filePath, data) { + try { + // 确保目录存在 + await this.ensureDirectoryExists(path.dirname(filePath)); + // 写入文件 + await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); } catch (error) { - console.error(`创建目录失败: ${dirPath}`, error); + console.error(`写入JSON文件失败 [${filePath}]:`, error); + throw error; + } + } + + /** + * 读取JSON文件 + * @param {string} filePath 文件路径 + * @returns {Promise} 读取的数据 + */ + static async readJsonFile(filePath) { + try { + const content = await fs.readFile(filePath, 'utf8'); + return JSON.parse(content); + } catch (error) { + console.error(`读取JSON文件失败 [${filePath}]:`, error); + throw error; + } + } + + /** + * 删除文件 + * @param {string} filePath 文件路径 + */ + static async deleteFile(filePath) { + try { + await fs.unlink(filePath); + } catch (error) { + if (error.code !== 'ENOENT') { // 如果错误不是"文件不存在" + console.error(`删除文件失败 [${filePath}]:`, error); + throw error; + } + } + } + + /** + * 检查文件是否存在 + * @param {string} filePath 文件路径 + * @returns {Promise} 文件是否存在 + */ + static async fileExists(filePath) { + try { + await fs.access(filePath); + return true; + } catch { return false; } } diff --git a/test-data/test-progress.json b/test-data/test-progress.json index 0637a08..7eede7d 100644 --- a/test-data/test-progress.json +++ b/test-data/test-progress.json @@ -1 +1,7 @@ -[] \ No newline at end of file +[ + 1, + 2, + 3, + 4, + 5 +] \ No newline at end of file diff --git a/tests/e2e/menu.spec.js b/tests/e2e/menu.spec.js index 45c859d..10f57d5 100644 --- a/tests/e2e/menu.spec.js +++ b/tests/e2e/menu.spec.js @@ -1,34 +1,37 @@ // 加载环境变量 require('../../config/env'); -const { test } = require('@playwright/test'); +const {test} = require('@playwright/test'); const TestController = require('../../src/controllers/LongiTestController'); -test.describe('测试所有隆基需求计划是否可用', () => { +test.describe('IBP系统菜单可访问性测试', () => { let controller; test.beforeAll(async () => { controller = new TestController(); }); - test('获取最新菜单数据', async () => { + test('应能成功获取系统所有可测试的菜单项', async () => { const menuData = await controller.fetchAndSaveMenuData(); + test.expect(menuData).toBeTruthy(); test.expect(menuData.length).toBeGreaterThan(0); console.log(`✓ 成功收集 ${menuData.length} 个菜单项`); }); - test('访问并点击所有页面', async () => { - // 执行所有测试 - await controller.runAllTests(); + test('应能成功访问所有菜单页面', async () => { + // 执行所有测试并获取进度 + const progress = await controller.runAllTests(); // 验证测试结果 - const progress = controller.getTestProgress(); + test.expect(progress).toBeTruthy(); + test.expect(progress.total).toBeGreaterThan(0); test.expect(progress.completed).toBe(progress.total); - + test.expect(progress.remaining).toBe(0); + // 输出测试统计 console.log('\n测试完成!'); - console.log(`✓ 总计测试: ${progress.total} 个菜单`); - console.log(`✓ 成功完成: ${progress.completed} 个菜单`); - console.log(`✓ 成功率: ${((progress.completed / progress.total) * 100).toFixed(2)}%`); + console.log(`总菜单数: ${progress.total}`); + console.log(`完成数量: ${progress.completed}`); + console.log(`剩余数量: ${progress.remaining}`); }); }); \ No newline at end of file