#!/usr/bin/env node const { spawn } = require('child_process') const path = require('path') const fs = require('fs') // 颜色输出函数 const colors = { reset: '\x1b[0m', bright: '\x1b[1m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m' } function colorLog(color, message) { console.log(`${colors[color]}${message}${colors.reset}`) } // 解析命令行参数 const args = process.argv.slice(2) const options = { unit: args.includes('--unit'), e2e: args.includes('--e2e'), coverage: args.includes('--coverage'), watch: args.includes('--watch'), verbose: args.includes('--verbose'), component: args.includes('--component'), api: args.includes('--api'), store: args.includes('--store'), ci: args.includes('--ci') } // 如果没有指定测试类型,默认运行所有测试 if (!options.unit && !options.e2e && !options.component && !options.api && !options.store) { options.unit = true options.e2e = true } // 构建测试命令 function buildTestCommand(type) { let command = 'npx' let args = [] if (type === 'unit') { args.push('vitest', 'run') if (options.coverage) { args.push('--coverage') } if (options.watch) { args = args.filter(arg => arg !== 'run') args.push('--watch') } if (options.verbose) { args.push('--verbose') } // 添加特定的测试文件模式 if (options.component) { args.push('--dir', 'tests/unit/components') } else if (options.api) { args.push('--dir', 'tests/unit/services') } else if (options.store) { args.push('--dir', 'tests/unit/stores') } } else if (type === 'e2e') { args.push('playwright', 'test') if (options.ci) { args.push('--reporter=line') } else { args.push('--reporter=list') args.push('--reporter=html') } if (options.verbose) { args.push('--verbose') } } return { command, args } } // 运行测试命令 function runTestCommand(type) { return new Promise((resolve, reject) => { const { command, args } = buildTestCommand(type) colorLog('cyan', `运行 ${type} 测试...`) colorLog('blue', `${command} ${args.join(' ')}`) const testProcess = spawn(command, args, { stdio: 'inherit', shell: true, cwd: path.resolve(__dirname, '..') }) testProcess.on('close', (code) => { if (code === 0) { colorLog('green', `${type} 测试通过!`) resolve(code) } else { colorLog('red', `${type} 测试失败!`) reject(new Error(`${type} 测试失败,退出码: ${code}`)) } }) testProcess.on('error', (error) => { colorLog('red', `运行 ${type} 测试时出错: ${error.message}`) reject(error) }) }) } // 运行代码质量检查 function runCodeQualityChecks() { return new Promise((resolve, reject) => { colorLog('cyan', '运行代码质量检查...') // 运行ESLint const eslintProcess = spawn('npx', ['eslint', '--ext', '.js,.vue,.ts', 'src/', 'tests/'], { stdio: 'inherit', shell: true, cwd: path.resolve(__dirname, '..') }) eslintProcess.on('close', (code) => { if (code === 0) { colorLog('green', 'ESLint 检查通过!') // 运行Prettier检查 const prettierProcess = spawn('npx', ['prettier', '--check', 'src/**/*.{js,vue,ts,css,scss,json,md}'], { stdio: 'inherit', shell: true, cwd: path.resolve(__dirname, '..') }) prettierProcess.on('close', (prettierCode) => { if (prettierCode === 0) { colorLog('green', 'Prettier 检查通过!') resolve() } else { colorLog('red', 'Prettier 检查失败!') reject(new Error(`Prettier 检查失败,退出码: ${prettierCode}`)) } }) prettierProcess.on('error', (error) => { colorLog('red', `运行 Prettier 检查时出错: ${error.message}`) reject(error) }) } else { colorLog('red', 'ESLint 检查失败!') reject(new Error(`ESLint 检查失败,退出码: ${code}`)) } }) eslintProcess.on('error', (error) => { colorLog('red', `运行 ESLint 检查时出错: ${error.message}`) reject(error) }) }) } // 生成测试报告 function generateTestReports() { return new Promise((resolve) => { colorLog('cyan', '生成测试报告...') // 确保报告目录存在 const reportsDir = path.resolve(__dirname, '../reports') if (!fs.existsSync(reportsDir)) { fs.mkdirSync(reportsDir, { recursive: true }) } // 创建测试报告摘要 const summaryPath = path.join(reportsDir, 'test-summary.json') const summary = { timestamp: new Date().toISOString(), tests: { unit: options.unit, e2e: options.e2e, component: options.component, api: options.api, store: options.store }, coverage: options.coverage, watch: options.watch, verbose: options.verbose } fs.writeFileSync(summaryPath, JSON.stringify(summary, null, 2)) colorLog('green', '测试报告已生成!') resolve() }) } // 主函数 async function main() { try { colorLog('bright', '开始运行自动化测试和代码质量检查...') // 运行代码质量检查 await runCodeQualityChecks() // 运行单元测试 if (options.unit) { await runTestCommand('unit') } // 运行E2E测试 if (options.e2e) { await runTestCommand('e2e') } // 生成测试报告 await generateTestReports() colorLog('bright', colorLog('green', '所有测试和代码质量检查通过!')) process.exit(0) } catch (error) { colorLog('red', `测试或代码质量检查失败: ${error.message}`) process.exit(1) } } // 显示帮助信息 function showHelp() { colorLog('bright', '自动化测试和代码质量检查工具') console.log('') console.log('用法: node scripts/test.js [选项]') console.log('') console.log('选项:') console.log(' --unit 运行单元测试') console.log(' --e2e 运行端到端测试') console.log(' --component 运行组件测试') console.log(' --api 运行API服务测试') console.log(' --store 运行状态管理测试') console.log(' --coverage 生成测试覆盖率报告') console.log(' --watch 监视模式运行测试') console.log(' --verbose 详细输出') console.log(' --ci CI模式运行测试') console.log('') console.log('示例:') console.log(' node scripts/test.js --unit --coverage') console.log(' node scripts/test.js --e2e --verbose') console.log(' node scripts/test.js --component --watch') } // 检查是否需要显示帮助信息 if (args.includes('--help') || args.includes('-h')) { showHelp() process.exit(0) } // 运行主函数 main()