/** * 心知益家 - 自动化测试运行脚本 * * 测试策略: * 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 = `
总测试数
通过 ✅
失败 ❌
通过率
测试策略:构造请求数据,调用后端接口,验证响应,不进行UI点击
| 测试ID | 描述 | 优先级 | 状态 | 请求数据 |
|---|---|---|---|---|
| ${r.id} | ${r.description} | ${r.priority} | ${r.status.toUpperCase()} | ${JSON.stringify(r.request || {})} |
${JSON.stringify(r.request, null, 2)}
${JSON.stringify(r.response, null, 2)}
测试策略:浏览器自动化测试,管理员和成长规划师登录,测试过程截图
测试账号:管理员 ${this.config?.adminAccount?.phone || 'N/A'} / 成长规划师 ${this.config?.teacherAccount?.phone || 'N/A'}
| 测试ID | 描述 | 优先级 | 状态 | 截图 |
|---|---|---|---|---|
| ${r.id} | ${r.description} | ${r.priority} | ${r.status.toUpperCase()} | ${r.screenshots?.map(s => `${path.basename(s)}`).join('') || '-'} |