/** * HMS 小程序 E2E 自动化测试脚本 (ESM) */ import { Launcher } from '@weapp-vite/miniprogram-automator'; import http from 'http'; const CLI_PATH = 'D:/微信web开发者工具/cli.bat'; const PROJECT_PATH = 'G:/hms/apps/miniprogram/dist'; const API_BASE = 'http://localhost:3000'; const API_PREFIX = '/api/v1'; const results = { pass: 0, fail: 0, items: [] }; function log(status, name, detail = '') { const icon = status === 'PASS' ? '✅' : status === 'FAIL' ? '❌' : 'ℹ️'; console.log(`${icon} ${status}: ${name}${detail ? ' — ' + detail : ''}`); results.items.push({ status, name, detail }); if (status === 'PASS') results.pass++; if (status === 'FAIL') results.fail++; } function apiRequest(method, path, token = null, body = null) { return new Promise((resolve, reject) => { const fullPath = API_PREFIX + path; const url = new URL(fullPath, API_BASE); const headers = { 'Content-Type': 'application/json' }; if (token) headers['Authorization'] = `Bearer ${token}`; const opts = { method, hostname: url.hostname, port: url.port, path: url.pathname + url.search, headers }; const req = http.request(opts, res => { let data = ''; res.on('data', c => data += c); res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve({ raw: data, status: res.statusCode }); } }); }); req.on('error', reject); if (body) req.write(JSON.stringify(body)); req.end(); }); } const sleep = ms => new Promise(r => setTimeout(r, ms)); async function main() { console.log('\n🚀 HMS 小程序 E2E 测试\n' + '='.repeat(60)); // Step 0: 登录获取 token console.log('\n📡 Step 0: 获取后端认证 Token'); let token, user; try { const resp = await apiRequest('POST', '/auth/login', null, { username: 'admin', password: 'Admin@2026' }); if (!resp.success) throw new Error(JSON.stringify(resp)); token = resp.data.access_token; user = resp.data.user; log('PASS', 'Admin 登录', `user=${user.username}`); } catch (e) { log('FAIL', 'Admin 登录', e.message); process.exit(1); } // 获取患者数据 let patient = null; try { const resp = await apiRequest('GET', '/health/patients?page=1&page_size=5', token); const patients = resp.data?.data || []; patient = patients[0] || null; log(patient ? 'PASS' : 'INFO', '获取患者数据', patient ? `name=${patient.name}` : '无患者'); } catch (e) { log('INFO', '获取患者', e.message); } // Step 1: 启动 console.log('\n📱 Step 1: 启动微信开发者工具'); let mp; try { mp = await new Launcher().launch({ cliPath: CLI_PATH, projectPath: PROJECT_PATH }); log('PASS', 'Launcher.launch()', 'DevTools 启动成功'); } catch (e) { log('FAIL', 'Launcher.launch()', e.message); process.exit(1); } await sleep(5000); // Step 2: 访客首页 console.log('\n🏠 Step 2: 验证访客模式首页'); try { let page = await mp.currentPage(); log(page.path === 'pages/index/index' ? 'PASS' : 'INFO', '首页路径', page.path); } catch (e) { log('FAIL', '访客首页', e.message); } // Step 3: 注入认证 console.log('\n🔑 Step 3: 注入认证状态'); try { // Use callWxMethod to inject storage (avoids long string issues with evaluate) await mp.callWxMethod('setStorageSync', 'access_token', token); await mp.callWxMethod('setStorageSync', 'refresh_token', token); await mp.callWxMethod('setStorageSync', 'user_data', JSON.stringify(user)); await mp.callWxMethod('setStorageSync', 'user_roles', JSON.stringify(['admin'])); await mp.callWxMethod('setStorageSync', 'tenant_id', user.tenant_id || ''); if (patient) { await mp.callWxMethod('setStorageSync', 'current_patient', JSON.stringify(patient)); await mp.callWxMethod('setStorageSync', 'current_patient_id', patient.id); } log('PASS', 'Storage 注入', '已写入'); await sleep(500); await mp.reLaunch('/pages/index/index'); await sleep(3000); const newPage = await mp.currentPage(); log('PASS', 'reLaunch 首页', `path=${newPage.path}`); } catch (e) { log('FAIL', '认证注入', e.message); } // Step 4: 验证登录后首页 console.log('\n🏠 Step 4: 验证登录后首页'); try { // Verify storage was correctly written by reading it back const storedUser = await mp.callWxMethod('getStorageSync', 'user_data'); const storedRoles = await mp.callWxMethod('getStorageSync', 'user_roles'); const storedToken = await mp.callWxMethod('getStorageSync', 'access_token'); const hasStoredUser = !!storedToken && String(storedToken).length > 10; log(hasStoredUser ? 'PASS' : 'FAIL', 'Storage 验证', hasStoredUser ? `token_len=${String(storedToken).length}, user=${String(storedUser).substring(0, 30)}` : 'token missing'); log(storedRoles ? 'PASS' : 'FAIL', '角色数据', `roles=${String(storedRoles)}`); } catch (e) { log('FAIL', '登录后首页', e.message); } // Step 5: 健康数据页 console.log('\n💚 Step 5: 健康数据页'); try { await mp.switchTab('/pages/health/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '健康数据页导航', `path=${page.path}`); } catch (e) { log('FAIL', '健康数据页', e.message); } // Step 6: 消息中心 console.log('\n💬 Step 6: 消息中心'); try { await mp.switchTab('/pages/messages/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '消息中心导航', `path=${page.path}`); } catch (e) { log('FAIL', '消息中心', e.message); } // Step 7: 咨询列表 console.log('\n💬 Step 7: 咨询列表'); try { await mp.reLaunch('/pages/consultation/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '咨询列表导航', `path=${page.path}`); } catch (e) { log('FAIL', '咨询列表', e.message); } // Step 8: 咨询详情(如果有数据) console.log('\n💬 Step 8: 咨询详情页'); try { await mp.reLaunch('/pages/consultation/detail/index?id=test'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '咨询详情导航', `path=${page.path}`); } catch (e) { log('FAIL', '咨询详情', e.message); } // Step 9: 预约页 console.log('\n📅 Step 9: 预约页'); try { await mp.reLaunch('/pages/appointment/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '预约页导航', `path=${page.path}`); } catch (e) { log('FAIL', '预约页', e.message); } // Step 10: 个人中心 console.log('\n👤 Step 10: 个人中心'); try { await mp.reLaunch('/pages/profile/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '个人中心导航', `path=${page.path}`); } catch (e) { log('FAIL', '个人中心', e.message); } // Step 11: 积分商城 console.log('\n🎁 Step 11: 积分商城'); try { await mp.reLaunch('/pages/mall/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '积分商城导航', `path=${page.path}`); } catch (e) { log('FAIL', '积分商城', e.message); } // Step 12: 医生端分包首页 console.log('\n👨‍⚕️ Step 12: 医生端分包'); try { await mp.reLaunch('/pages/doctor/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '医生端首页导航', `path=${page.path}`); } catch (e) { log('FAIL', '医生端首页', e.message); } // Step 13: 医生端咨询管理 console.log('\n💬 Step 13: 医生端咨询管理'); try { await mp.reLaunch('/pages/doctor/consultation/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '医生端咨询列表', `path=${page.path}`); } catch (e) { log('FAIL', '医生端咨询列表', e.message); } // Step 14: 医生端患者管理 console.log('\n👥 Step 14: 医生端患者管理'); try { await mp.reLaunch('/pages/doctor/patients/index'); await sleep(2000); const page = await mp.currentPage(); log('PASS', '医生端患者管理', `path=${page.path}`); } catch (e) { log('FAIL', '医生端患者管理', e.message); } // 清理 console.log('\n🧹 清理...'); try { await mp.close(); log('PASS', '关闭连接'); } catch (e) { log('INFO', '关闭连接', String(e).substring(0, 60)); } // 汇总 console.log('\n' + '='.repeat(60)); console.log(`📊 通过: ${results.pass} | 失败: ${results.fail} | 通过率: ${((results.pass / (results.pass + results.fail)) * 100).toFixed(1)}%`); const failures = results.items.filter(i => i.status === 'FAIL'); if (failures.length) { console.log('\n❌ 失败项:'); failures.forEach(f => console.log(` - ${f.name}: ${f.detail}`)); } process.exit(results.fail > 0 ? 1 : 0); } main().catch(e => { console.error('Fatal:', e); process.exit(1); });