Files
gym-manage/gym-manage-uniapp/e2e/run-tests.js
T

290 lines
9.2 KiB
JavaScript

/**
* 小程序 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();