/** * 小程序 E2E 测试一键启动脚本 * * 用法: * node e2e/run-tests.js # 全量测试,IDE 保持运行 * node e2e/run-tests.js --smoke # 仅冒烟测试 * node e2e/run-tests.js --journeys # 仅用户旅程测试 * node e2e/run-tests.js --close # 测试结束后关闭 IDE * node e2e/run-tests.js --watch # Watch 模式(自动重测) * * 环境变量: * WECHAT_DEVTOOLS_CLI 微信开发者工具 CLI 路径(默认自动检测) * UNIAPP_OUTPUT 小程序编译输出目录 * WECHAT_DEVTOOLS_PORT 自动化服务端口(默认 63956) */ const { spawn, execSync } = require('child_process'); const path = require('path'); const fs = require('fs'); // ── 配置 ────────────────────────────────────────── const CONFIG = { cliPath: process.env.WECHAT_DEVTOOLS_CLI || 'D:\\微信web开发者工具\\cli.bat', projectPath: process.env.UNIAPP_OUTPUT || path.resolve(__dirname, '../unpackage/dist/dev/mp-weixin'), port: parseInt(process.env.WECHAT_DEVTOOLS_PORT || '63956', 10), jestConfig: path.resolve(__dirname, 'jest.config.js'), testTimeout: 180000, }; // ── 解析参数 ─────────────────────────────────────── const args = process.argv.slice(2); const opts = { smoke: args.includes('--smoke'), journeys: args.includes('--journeys'), profile: args.includes('--profile'), booking: args.includes('--booking'), close: args.includes('--close'), watch: args.includes('--watch'), verbose: args.includes('--verbose') || args.includes('-v'), }; // ── 日志工具 ─────────────────────────────────────── const colors = { reset: '\x1b[0m', green: '\x1b[32m', yellow: '\x1b[33m', red: '\x1b[31m', cyan: '\x1b[36m', bold: '\x1b[1m' }; const log = { info: (msg) => console.log(`${colors.cyan}[INFO]${colors.reset} ${msg}`), ok: (msg) => console.log(`${colors.green}[OK]${colors.reset} ${msg}`), warn: (msg) => console.log(`${colors.yellow}[WARN]${colors.reset} ${msg}`), err: (msg) => console.log(`${colors.red}[ERR]${colors.reset} ${msg}`), step: (msg) => console.log(`\n${colors.bold}${'='.repeat(60)}${colors.reset}\n${colors.bold} ${msg}${colors.reset}\n${'='.repeat(60)}`), }; // ── 检查 IDE 是否已在运行 ────────────────────────── function isIDERunning() { try { const result = execSync(`powershell -Command "Get-Process -Name '微信开发者工具' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], }); return result.trim().length > 0; } catch { return false; } } // ── 启动 IDE 自动化 ───────────────────────────────── async function launchIDE() { log.step('Step 1/4: 启动微信开发者工具'); if (!fs.existsSync(CONFIG.cliPath)) { throw new Error(`CLI 未找到: ${CONFIG.cliPath}\n请设置环境变量 WECHAT_DEVTOOLS_CLI`); } if (!fs.existsSync(CONFIG.projectPath)) { throw new Error(`项目路径不存在: ${CONFIG.projectPath}\n请先执行 uni-app 编译: npm run dev:mp-weixin`); } log.info(`项目路径: ${CONFIG.projectPath}`); log.info(`服务端口: ${CONFIG.port}`); if (isIDERunning()) { log.ok('微信开发者工具已在运行'); } else { log.info('正在启动微信开发者工具...'); } return new Promise((resolve, reject) => { const child = spawn(CONFIG.cliPath, [ 'auto', '--project', CONFIG.projectPath, '--auto-port', String(CONFIG.port), ], { stdio: opts.verbose ? 'inherit' : ['ignore', 'pipe', 'pipe'], shell: true, }); let output = ''; child.stdout.on('data', (data) => { output += data.toString(); if (opts.verbose) process.stdout.write(data); }); child.stderr.on('data', (data) => { output += data.toString(); if (opts.verbose) process.stderr.write(data); }); child.on('close', (code) => { if (code === 0) { log.ok('IDE 启动成功'); resolve(); } else { // CLI close 命令会返回 1,但 IDE 可能已在运行 if (isIDERunning()) { log.ok('IDE 已在运行(复用现有实例)'); resolve(); } else { reject(new Error(`IDE 启动失败 (code: ${code})`)); } } }); child.on('error', (err) => { reject(new Error(`无法启动 IDE: ${err.message}`)); }); // 超时保护 setTimeout(() => { if (isIDERunning()) { log.ok('IDE 启动确认'); resolve(); } else { reject(new Error('IDE 启动超时')); } }, 30000); }); } // ── 等待 IDE 就绪 ────────────────────────────────── function waitForIDE(delayMs = 5000) { log.info(`等待 IDE 就绪 (${delayMs / 1000}s)...`); return new Promise(resolve => setTimeout(resolve, delayMs)); } // ── 运行 Jest 测试 ────────────────────────────────── function runTests() { log.step('Step 2/4: 运行小程序 E2E 测试'); const jestArgs = [ 'jest', '--config', CONFIG.jestConfig, '--verbose', '--testTimeout', String(CONFIG.testTimeout), '--forceExit', ]; // 测试范围 if (opts.smoke) { jestArgs.push('--testPathPattern', 'smoke/'); } else if (opts.profile) { jestArgs.push('--testPathPattern', 'profile-journey'); } else if (opts.booking) { jestArgs.push('--testPathPattern', 'booking-journey'); } else if (opts.journeys) { jestArgs.push('--testPathPattern', 'journeys/'); } // Watch 模式 if (opts.watch) { jestArgs.push('--watch'); jestArgs.push('--detectOpenHandles'); } log.info(`执行: npx ${jestArgs.join(' ')}`); return new Promise((resolve, reject) => { const child = spawn('npx', jestArgs, { cwd: path.resolve(__dirname, '..'), stdio: 'inherit', shell: true, env: { ...process.env, WECHAT_DEVTOOLS_PORT: String(CONFIG.port), UNIAPP_OUTPUT: CONFIG.projectPath, FORCE_COLOR: '1', }, }); child.on('close', (code) => { if (code === 0) { resolve(true); } else { resolve(false); } }); child.on('error', (err) => { reject(err); }); }); } // ── 生成/查看测试报告 ────────────────────────────── function openReport() { log.step('Step 3/4: 测试报告'); const reportPath = path.resolve(__dirname, '../test-results/miniapp-report.html'); if (fs.existsSync(reportPath)) { log.ok(`HTML 报告: file:///${reportPath.replace(/\\/g, '/')}`); // 尝试用默认浏览器打开 try { execSync(`start "" "${reportPath}"`, { stdio: 'ignore' }); log.info('报告已在浏览器中打开'); } catch { // 无头环境跳过 } } else { log.warn('未找到 HTML 测试报告(可能 jest-html-reporter 未安装)'); } } // ── 关闭 IDE ────────────────────────────────────── async function closeIDE() { if (!opts.close) return; log.step('Step 4/4: 关闭 IDE'); return new Promise((resolve) => { const child = spawn(CONFIG.cliPath, ['close'], { stdio: opts.verbose ? 'inherit' : 'ignore', shell: true, }); child.on('close', () => { log.ok('IDE 已关闭'); resolve(); }); setTimeout(() => resolve(), 10000); }); } // ── 主流程 ───────────────────────────────────────── async function main() { const startTime = Date.now(); console.log(` ${colors.bold}${colors.cyan} ╔══════════════════════════════════════╗ ║ 小程序 E2E 自动化测试启动器 ║ ╚══════════════════════════════════════╝${colors.reset} 测试范围: ${opts.smoke ? '冒烟测试' : opts.journeys ? '用户旅程' : '全量'} 关闭IDE : ${opts.close ? '是' : '否(保持运行)'} Watch : ${opts.watch ? '是' : '否'} `); try { if (!opts.watch) { await launchIDE(); await waitForIDE(); } const passed = await runTests(); if (!opts.watch) { if (passed) { openReport(); } await closeIDE(); } const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); if (passed) { console.log(`\n${colors.green}${colors.bold} 全部测试通过! 耗时: ${elapsed}s${colors.reset}\n`); process.exit(0); } else { console.log(`\n${colors.red}${colors.bold} 测试未全部通过 耗时: ${elapsed}s${colors.reset}\n`); process.exit(1); } } catch (error) { log.err(error.message); console.error(error); process.exit(1); } } main();