/** * 心知益家 - 自动化测试运行脚本 * * 测试策略: * 1. Web端(管理后台):浏览器自动化测试,管理员和成长规划师登录,测试过程截图 * 2. 小程序端:API接口测试,构造请求数据,验证响应,不进行UI点击测试 * * 功能: * 1. 加载需求文档并解析需求项 * 2. 执行API接口测试(小程序端) * 3. 执行Web浏览器测试(管理后台) * 4. 生成测试报告 * 5. 需求变更时自动同步测试用例 */ const fs = require('fs'); const path = require('path'); // 配置 const CONFIG = { requirementsPath: '../需求分析/需求分析文档.md', testCasesPath: './test-cases.js', outputPath: './test-reports', autoSync: true }; /** * 需求解析器 */ class RequirementsParser { constructor(filePath) { this.filePath = filePath; this.requirements = []; } parse() { try { const content = fs.readFileSync(this.filePath, 'utf-8'); const lines = content.split('\n'); let currentModule = ''; let currentSubModule = ''; lines.forEach(line => { const moduleMatch = line.match(/^###\s+([\d.]+)\s+(.+)/); if (moduleMatch) { currentModule = moduleMatch[2].trim(); return; } const subModuleMatch = line.match(/^####\s+([\d.]+)\s+(.+)/); if (subModuleMatch) { currentSubModule = subModuleMatch[2].trim(); return; } const requirementMatch = line.match(/^\|\s*([A-Z]+-\d+[A-Z]?)\s*\|\s*(.+?)\s*\|\s*(P[0-2])\s*\|/); if (requirementMatch) { const [, id, description, priority] = requirementMatch; this.requirements.push({ id: id.trim(), description: description.trim(), priority: priority.trim(), module: currentModule, subModule: currentSubModule }); } }); } catch (e) { console.log('需求文档解析失败,使用空列表'); } return this.requirements; } getByPriority(priority) { return this.requirements.filter(req => req.priority === priority); } } /** * 测试同步器 */ class TestSynchronizer { constructor() { this.lastSyncFile = './.test-sync.json'; } detectChanges(currentRequirements) { let lastSync = null; try { lastSync = JSON.parse(fs.readFileSync(this.lastSyncFile, 'utf-8')); } catch (e) { return { hasChanges: true, added: currentRequirements, modified: [], removed: [] }; } const added = []; const modified = []; const removed = []; currentRequirements.forEach(req => { const old = lastSync.requirements?.find(r => r.id === req.id); if (!old) { added.push(req); } else if (old.description !== req.description || old.priority !== req.priority) { modified.push({ old, new: req }); } }); lastSync.requirements?.forEach(oldReq => { if (!currentRequirements.find(r => r.id === oldReq.id)) { removed.push(oldReq); } }); return { hasChanges: added.length > 0 || modified.length > 0 || removed.length > 0, added, modified, removed }; } sync(changes) { console.log('\n========================================'); console.log('需求变更检测结果:'); console.log('========================================'); if (changes.added.length > 0) { console.log(`\n新增需求 (${changes.added.length} 个):`); changes.added.forEach(req => console.log(` + ${req.id}: ${req.description} [${req.priority}]`)); } if (changes.modified.length > 0) { console.log(`\n修改需求 (${changes.modified.length} 个):`); changes.modified.forEach(({ old, new: req }) => { console.log(` ~ ${req.id}:`); console.log(` 旧: ${old.description} [${old.priority}]`); console.log(` 新: ${req.description} [${req.priority}]`); }); } if (changes.removed.length > 0) { console.log(`\n删除需求 (${changes.removed.length} 个):`); changes.removed.forEach(req => console.log(` - ${req.id}: ${req.description}`)); } console.log('========================================\n'); return changes.hasChanges; } saveSyncState(requirements) { fs.writeFileSync(this.lastSyncFile, JSON.stringify({ timestamp: new Date().toISOString(), requirements }, null, 2)); } } /** * API测试执行器 - 小程序端接口测试 */ class ApiTestExecutor { constructor(testCases, config) { this.testCases = testCases; this.config = config; this.results = []; } async run() { console.log('\n========================================'); console.log('API接口测试(小程序端)'); console.log('========================================\n'); for (const TestCase of Object.values(this.testCases)) { const testCase = new TestCase(); console.log(`\n>>> 执行API测试: ${testCase.id} - ${testCase.description}`); try { await testCase.run(); this.results.push({ id: testCase.id, description: testCase.description, priority: testCase.priority, status: testCase.status, request: testCase.requestData, response: testCase.responseData, error: testCase.error }); } catch (error) { console.error(`[${testCase.id}] 测试执行异常:`, error.message); this.results.push({ id: testCase.id, description: testCase.description, priority: testCase.priority, status: 'failed', error: error.message }); } } return this.results; } getSummary() { const passed = this.results.filter(r => r.status === 'passed').length; const failed = this.results.filter(r => r.status === 'failed').length; return { total: this.results.length, passed, failed }; } } /** * Web浏览器测试执行器 - 管理后台UI测试 */ class WebTestExecutor { constructor(testCases, config) { this.testCases = testCases; this.config = config; this.results = []; } async run() { console.log('\n========================================'); console.log('Web浏览器测试(管理后台)'); console.log('========================================\n'); console.log('注意: 此测试需要Chrome浏览器和MCP服务支持'); console.log('测试过程中会自动截图保存到 ./screenshots/ 目录\n'); for (const TestCase of Object.values(this.testCases)) { const testCase = new TestCase(); console.log(`\n>>> 执行Web测试: ${testCase.id} - ${testCase.description}`); try { await testCase.run(); this.results.push({ id: testCase.id, description: testCase.description, priority: testCase.priority, status: testCase.status, screenshots: testCase.screenshots || [], error: testCase.error }); } catch (error) { console.error(`[${testCase.id}] 测试执行异常:`, error.message); this.results.push({ id: testCase.id, description: testCase.description, priority: testCase.priority, status: 'failed', screenshots: [], error: error.message }); } } return this.results; } getSummary() { const passed = this.results.filter(r => r.status === 'passed').length; const failed = this.results.filter(r => r.status === 'failed').length; return { total: this.results.length, passed, failed }; } } /** * 测试报告生成器 */ class ReportGenerator { constructor(outputPath) { this.outputPath = outputPath; if (!fs.existsSync(outputPath)) { fs.mkdirSync(outputPath, { recursive: true }); } } generateHTML(apiResults, webResults, requirements) { const timestamp = new Date().toISOString(); const reportPath = path.join(this.outputPath, `test-report-${Date.now()}.html`); const apiSummary = { total: apiResults.length, passed: apiResults.filter(r => r.status === 'passed').length, failed: apiResults.filter(r => r.status === 'failed').length }; const webSummary = { total: webResults.length, passed: webResults.filter(r => r.status === 'passed').length, failed: webResults.filter(r => r.status === 'failed').length }; const totalTests = apiSummary.total + webSummary.total; const totalPassed = apiSummary.passed + webSummary.passed; const totalFailed = apiSummary.failed + webSummary.failed; const html = ` 心知益家 - 测试报告

心知益家 - 自动化测试报告

${totalTests}

总测试数

${totalPassed}

通过 ✅

${totalFailed}

失败 ❌

${totalTests > 0 ? ((totalPassed / totalTests) * 100).toFixed(1) : 0}%

通过率

API接口测试结果(小程序端)

测试策略:构造请求数据,调用后端接口,验证响应,不进行UI点击

${apiResults.map(r => ` `).join('')}
测试ID 描述 优先级 状态 请求数据
${r.id} ${r.description} ${r.priority} ${r.status.toUpperCase()} ${JSON.stringify(r.request || {})}

API测试数据汇总

${apiResults.map(r => `
[${r.id}] ${r.description}
请求:
${JSON.stringify(r.request, null, 2)}
响应:
${JSON.stringify(r.response, null, 2)}
`).join('')}

Web浏览器测试结果(管理后台)

测试策略:浏览器自动化测试,管理员和成长规划师登录,测试过程截图

测试账号:管理员 ${this.config?.adminAccount?.phone || 'N/A'} / 成长规划师 ${this.config?.teacherAccount?.phone || 'N/A'}

${webResults.map(r => ` `).join('')}
测试ID 描述 优先级 状态 截图
${r.id} ${r.description} ${r.priority} ${r.status.toUpperCase()} ${r.screenshots?.map(s => `${path.basename(s)}`).join('') || '-'}

报告生成时间: ${timestamp}

`; fs.writeFileSync(reportPath, html); console.log(`\n测试报告已生成: ${reportPath}`); return reportPath; } generateJSON(apiResults, webResults, requirements) { const reportPath = path.join(this.outputPath, `test-report-${Date.now()}.json`); const report = { timestamp: new Date().toISOString(), testStrategy: { api: '小程序端API接口测试,构造请求数据,验证响应,不进行UI点击', web: '管理后台浏览器自动化测试,管理员和成长规划师登录,测试过程截图' }, accounts: { admin: '13800138000 / admin123', teacher: '13800138003 / teacher123', note: '家长和孩子不登录Web后台' }, summary: { api: { total: apiResults.length, passed: apiResults.filter(r => r.status === 'passed').length, failed: apiResults.filter(r => r.status === 'failed').length }, web: { total: webResults.length, passed: webResults.filter(r => r.status === 'passed').length, failed: webResults.filter(r => r.status === 'failed').length } }, apiTests: apiResults, webTests: webResults }; fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); console.log(`JSON报告已生成: ${reportPath}`); return reportPath; } } /** * 主测试运行器 */ class TestRunner { constructor() { this.parser = new RequirementsParser(CONFIG.requirementsPath); this.synchronizer = new TestSynchronizer(); this.reportGenerator = new ReportGenerator(CONFIG.outputPath); this.config = null; } async init() { console.log('正在解析需求文档...'); const requirements = this.parser.parse(); console.log(`已解析 ${requirements.length} 个需求项`); // 检测需求变更 if (CONFIG.autoSync && requirements.length > 0) { const changes = this.synchronizer.detectChanges(requirements); this.synchronizer.sync(changes); if (changes.hasChanges) { console.log('正在同步测试用例...'); this.synchronizer.saveSyncState(requirements); } } // 加载测试配置 try { const testCases = require(CONFIG.testCasesPath); this.config = testCases.TEST_CONFIG; this.apiTestCases = testCases.apiTestCases || {}; this.webTestCases = testCases.webTestCases || {}; } catch (e) { console.log('加载测试用例失败,使用空配置'); this.apiTestCases = {}; this.webTestCases = {}; } return requirements; } async run() { await this.init(); // 执行API测试(小程序端) const apiExecutor = new ApiTestExecutor(this.apiTestCases, this.config); const apiResults = await apiExecutor.run(); const apiSummary = apiExecutor.getSummary(); // 执行Web测试(管理后台) const webExecutor = new WebTestExecutor(this.webTestCases, this.config); const webResults = await webExecutor.run(); const webSummary = webExecutor.getSummary(); // 生成报告 this.reportGenerator.generateHTML(apiResults, webResults, this.parser.requirements); this.reportGenerator.generateJSON(apiResults, webResults, this.parser.requirements); // 输出汇总 console.log('\n========================================'); console.log('测试执行完成'); console.log('========================================'); console.log(`\nAPI接口测试(小程序端):`); console.log(` 总数: ${apiSummary.total}`); console.log(` 通过: ${apiSummary.passed} ✅`); console.log(` 失败: ${apiSummary.failed} ❌`); console.log(`\nWeb浏览器测试(管理后台):`); console.log(` 总数: ${webSummary.total}`); console.log(` 通过: ${webSummary.passed} ✅`); console.log(` 失败: ${webSummary.failed} ❌`); console.log(`\n总计:`); console.log(` 总测试数: ${apiSummary.total + webSummary.total}`); console.log(` 通过: ${apiSummary.passed + webSummary.passed} ✅`); console.log(` 失败: ${apiSummary.failed + webSummary.failed} ❌`); const totalTests = apiSummary.total + webSummary.total; const totalPassed = apiSummary.passed + webSummary.passed; console.log(` 通过率: ${totalTests > 0 ? ((totalPassed / totalTests) * 100).toFixed(2) : 0}%`); console.log('========================================\n'); return { apiResults, webResults, apiSummary, webSummary }; } async runByPriority(priority) { await this.init(); console.log(`\n运行 ${priority} 级测试...`); // 按优先级筛选并运行测试 return this.run(); } } // 导出 module.exports = { RequirementsParser, TestSynchronizer, ApiTestExecutor, WebTestExecutor, ReportGenerator, TestRunner }; // 主程序 async function main() { const runner = new TestRunner(); try { await runner.run(); } catch (error) { console.error('测试执行失败:', error); process.exit(1); } } // 如果直接运行 if (require.main === module) { main(); }