290 lines
9.2 KiB
JavaScript
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();
|