/** * Playwright MCP 测试运行器 * * 使用方法: * npx tsx tests/runner.ts # 运行所有测试 * npx tsx tests/runner.ts --login # 只运行登录测试 * npx tsx tests/runner.ts --headed # 有头模式运行(可见浏览器) * npx tsx tests/runner.ts --report # 生成 HTML 报告 */ import { testSuite, type TestResult, type TestModule } from './index'; interface RunOptions { filter?: string; headed?: boolean; report?: boolean; } class TestRunner { private results: TestResult[] = []; private startTime: number = 0; async run(options: RunOptions = {}): Promise { this.startTime = Date.now(); this.results = []; console.log('🚀 Playwright MCP 测试启动\n'); console.log(`📅 ${new Date().toLocaleString()}`); console.log(`🔧 模式: ${options.headed ? '有头' : '无头'}`); if (options.filter) { console.log(`🔍 过滤: ${options.filter}`); } console.log(''); // 筛选测试模块 let modules = Object.entries(testSuite); if (options.filter) { modules = modules.filter(([name]) => name.toLowerCase().includes(options.filter!.toLowerCase()) ); } if (modules.length === 0) { console.log('❌ 没有找到匹配的测试模块'); process.exit(1); } // 顺序执行测试 for (const [name, module] of modules) { await this.runModule(name, module as TestModule); } // 输出报告 this.printSummary(); if (options.report) { this.generateReport(); } // 设置退出码 const hasFailed = this.results.some(r => !r.passed); process.exit(hasFailed ? 1 : 0); } private async runModule(name: string, module: TestModule): Promise { console.log(`\n📦 ${module.name}`); console.log(` ${module.description}`); console.log('─'.repeat(50)); // 这里通过 MCP 工具执行实际的测试 // 由于 MCP 工具需要由 Claude 调用,这里我们输出测试指令 console.log(`\n 测试用例 (${module.tests.length}个):`); for (const test of module.tests) { console.log(` ${test.passed ? '✅' : '❌'} ${test.name}`); if (test.error) { console.log(` 错误: ${test.error}`); } if (test.duration) { console.log(` 耗时: ${test.duration}ms`); } this.results.push(test); } } private printSummary(): void { const duration = Date.now() - this.startTime; const total = this.results.length; const passed = this.results.filter(r => r.passed).length; const failed = total - passed; console.log('\n' + '='.repeat(50)); console.log('📊 测试报告摘要'); console.log('='.repeat(50)); console.log(` 总计: ${total} 个测试`); console.log(` ✅ 通过: ${passed} 个`); console.log(` ❌ 失败: ${failed} 个`); console.log(` ⏱️ 耗时: ${(duration / 1000).toFixed(2)} 秒`); console.log('='.repeat(50)); if (failed > 0) { console.log('\n❌ 失败的测试:'); this.results .filter(r => !r.passed) .forEach(r => console.log(` - ${r.name}: ${r.error}`)); } } private generateReport(): void { const report = { timestamp: new Date().toISOString(), summary: { total: this.results.length, passed: this.results.filter(r => r.passed).length, failed: this.results.filter(r => !r.passed).length, duration: Date.now() - this.startTime, }, results: this.results, }; const fs = require('fs'); const path = require('path'); const reportPath = path.join(__dirname, 'test-report.json'); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); console.log(`\n📄 报告已保存: ${reportPath}`); } } // 解析命令行参数 function parseArgs(): RunOptions { const args = process.argv.slice(2); const options: RunOptions = {}; if (args.includes('--headed')) { options.headed = true; } if (args.includes('--report')) { options.report = true; } // 查找过滤参数 const filterArg = args.find(a => a.startsWith('--')); if (filterArg && !['--headed', '--report'].includes(filterArg)) { options.filter = filterArg.replace('--', ''); } return options; } // 主函数 async function main() { const options = parseArgs(); const runner = new TestRunner(); await runner.run(options); } main().catch(console.error);