/** * HMS 小程序 E2E 交互操作测试 — 模拟真实用户操作 * - 注入认证 → 首页展示 * - 健康数据页:查看体征、录入体征 * - 咨询列表:查看会话、发送消息 * - 预约页:查看预约列表 * - 医生端:查看工作台 */ 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 apiReq(method, path, token = null, body = null) { return new Promise((resolve, reject) => { const url = new URL(API_PREFIX + path, 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 getPageData(mp, description) { try { const page = await mp.currentPage(); const data = await page.data(); return { page, data }; } catch (e) { log('FAIL', description, e.message); return null; } } async function main() { console.log('\n🚀 HMS 小程序 E2E 交互操作测试\n' + '='.repeat(60)); // Step 0: 获取认证 console.log('\n📡 准备认证'); const loginResp = await apiReq('POST', '/auth/login', null, { username: 'admin', password: 'Admin@2026' }); const token = loginResp.data.access_token; const user = loginResp.data.user; log('PASS', '后端登录', `user=${user.username}`); // 获取患者 + 咨询数据 const patientsResp = await apiReq('GET', '/health/patients?page=1&page_size=5', token); const patients = patientsResp.data?.data || []; const patient = patients[0] || null; const consultResp = await apiReq('GET', '/health/consultation-sessions?page=1&page_size=5', token); const consultations = consultResp.data?.data || consultResp.data?.items || []; log('PASS', '测试数据准备', `患者=${patients.length}, 咨询=${consultations.length}`); // Step 1: 启动 console.log('\n📱 启动小程序'); const mp = await new Launcher().launch({ cliPath: CLI_PATH, projectPath: PROJECT_PATH }); await sleep(5000); log('PASS', 'DevTools 启动'); // ============================================ // Step 2: 访客首页 — 查看欢迎内容和文章 // ============================================ console.log('\n🏠 场景1: 访客浏览首页'); { const result = await getPageData(mp, '访客首页'); if (result) { const { page, data } = result; log(page.path === 'pages/index/index' ? 'PASS' : 'FAIL', '访客首页加载', `path=${page.path}`); // 查看页面数据结构 const keys = Object.keys(data); log('INFO', '首页组件 data', `${keys.length} 个字段: ${keys.slice(0, 8).join(', ')}`); // 尝试查找可交互元素 try { const buttons = await page.$$('.login-btn, .btn-login, button'); log('INFO', '访客首页按钮', `找到 ${buttons ? buttons.length : 0} 个`); } catch (e) { /* ignore selector errors */ } } } // ============================================ // Step 3: 注入认证 → 查看登录后首页 // ============================================ console.log('\n🔑 场景2: 用户登录 → 首页仪表盘'); { // 注入认证 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', '认证注入'); // reLaunch 触发 useDidShow restore await mp.reLaunch('/pages/index/index'); await sleep(3000); // 验证 storage 写入 const storedToken = await mp.callWxMethod('getStorageSync', 'access_token'); log(String(storedToken).length > 10 ? 'PASS' : 'FAIL', 'Token 持久化', `len=${String(storedToken).length}`); // 查看首页数据 const result = await getPageData(mp, '登录后首页'); if (result) { const { page, data } = result; log('PASS', '首页加载', `path=${page.path}`); // 输出关键数据 const dataKeys = Object.keys(data); log('INFO', '首页数据', `${dataKeys.length} 个字段: ${dataKeys.slice(0, 12).join(', ')}`); } } // ============================================ // Step 4: 健康数据页 — 查看体征记录 // ============================================ console.log('\n💚 场景3: 查看健康数据'); { await mp.switchTab('/pages/health/index'); await sleep(3000); const result = await getPageData(mp, '健康数据页'); if (result) { const { page, data } = result; log('PASS', '健康数据页导航', `path=${page.path}`); // 检查体征数据 const vitalSigns = data.vitalSigns || data.latestVital || data.records || data.healthData; if (vitalSigns) { log('PASS', '体征数据展示', typeof vitalSigns === 'object' ? JSON.stringify(vitalSigns).substring(0, 100) : String(vitalSigns).substring(0, 80)); } else { log('INFO', '体征数据', `未在 page data 中直接找到,keys=${Object.keys(data).join(', ')}`); } // 检查是否有录入按钮 try { const addBtn = await page.$$('.add-btn, .record-btn, [class*="add"], [class*="record"]'); log('INFO', '录入按钮', `${addBtn ? addBtn.length : 0} 个匹配`); } catch (e) { /* ignore */ } } } // ============================================ // Step 5: 咨询列表 — 查看会话 // ============================================ console.log('\n💬 场景4: 查看咨询列表'); { await mp.reLaunch('/pages/consultation/index'); await sleep(3000); const result = await getPageData(mp, '咨询列表'); if (result) { const { page, data } = result; log('PASS', '咨询列表导航', `path=${page.path}`); const sessions = data.sessions || data.consultations || data.list || data.items; if (Array.isArray(sessions) && sessions.length > 0) { log('PASS', '咨询会话列表', `${sessions.length} 个会话,第一个: ${JSON.stringify(sessions[0]).substring(0, 80)}`); } else { log('INFO', '咨询数据', `data keys: ${Object.keys(data).join(', ')}`); } // 尝试点击第一个咨询会话 if (consultations.length > 0) { const consultId = consultations[0].id; await mp.reLaunch(`/pages/consultation/detail/index?id=${consultId}`); await sleep(3000); const detailResult = await getPageData(mp, '咨询详情'); if (detailResult) { const { page: detailPage, data: detailData } = detailResult; log('PASS', '咨询详情导航', `path=${detailPage.path}`); log('INFO', '咨询详情数据', `keys: ${Object.keys(detailData).slice(0, 10).join(', ')}`); // 检查消息列表 const messages = detailData.messages || detailData.messageList || detailData.msgList; if (Array.isArray(messages) && messages.length > 0) { log('PASS', '消息列表', `${messages.length} 条消息`); } // 检查消息输入框 try { const inputEl = await page.$('textarea, input'); log(inputEl ? 'PASS' : 'INFO', '消息输入框', inputEl ? '找到' : '未找到'); } catch (e) { /* ignore */ } } } } } // ============================================ // Step 6: 预约页 — 查看预约列表 // ============================================ console.log('\n📅 场景5: 查看预约'); { await mp.reLaunch('/pages/appointment/index'); await sleep(3000); const result = await getPageData(mp, '预约页'); if (result) { const { page, data } = result; log('PASS', '预约页导航', `path=${page.path}`); const appointments = data.appointments || data.list || data.records; if (Array.isArray(appointments) && appointments.length > 0) { log('PASS', '预约列表', `${appointments.length} 条预约`); } else { log('INFO', '预约数据', `data keys: ${Object.keys(data).slice(0, 10).join(', ')}`); } } } // ============================================ // Step 7: 个人中心 — 查看个人信息 // ============================================ console.log('\n👤 场景6: 个人中心'); { await mp.reLaunch('/pages/profile/index'); await sleep(3000); const result = await getPageData(mp, '个人中心'); if (result) { const { page, data } = result; log('PASS', '个人中心导航', `path=${page.path}`); log('INFO', '个人中心数据', `keys: ${Object.keys(data).slice(0, 10).join(', ')}`); } } // ============================================ // Step 8: 积分商城 — 查看商品 // ============================================ console.log('\n🎁 场景7: 积分商城'); { await mp.reLaunch('/pages/mall/index'); await sleep(3000); const result = await getPageData(mp, '积分商城'); if (result) { const { page, data } = result; log('PASS', '积分商城导航', `path=${page.path}`); const products = data.products || data.goods || data.list; if (Array.isArray(products) && products.length > 0) { log('PASS', '商品列表', `${products.length} 件商品`); } else { log('INFO', '积分商城数据', `keys: ${Object.keys(data).slice(0, 10).join(', ')}`); } } } // ============================================ // Step 9: 医生端工作台 // ============================================ console.log('\n👨‍⚕️ 场景8: 医生端工作台'); { await mp.reLaunch('/pages/doctor/index'); await sleep(3000); const result = await getPageData(mp, '医生端工作台'); if (result) { const { page, data } = result; log('PASS', '医生端工作台', `path=${page.path}`); log('INFO', '工作台数据', `keys: ${Object.keys(data).slice(0, 10).join(', ')}`); // 检查统计数据 const stats = data.stats || data.statistics || data.dashboard; if (stats) { log('PASS', '医生统计', JSON.stringify(stats).substring(0, 100)); } } } // ============================================ // Step 10: 医生端咨询管理 // ============================================ console.log('\n💬 场景9: 医生端咨询管理'); { await mp.reLaunch('/pages/doctor/consultation/index'); await sleep(3000); const result = await getPageData(mp, '医生端咨询'); if (result) { const { page, data } = result; log('PASS', '医生端咨询', `path=${page.path}`); const sessions = data.sessions || data.list || data.consultations; if (Array.isArray(sessions) && sessions.length > 0) { log('PASS', '医生端咨询列表', `${sessions.length} 个会话`); } } } // ============================================ // Step 11: 医生端患者管理 // ============================================ console.log('\n👥 场景10: 医生端患者管理'); { await mp.reLaunch('/pages/doctor/patients/index'); await sleep(3000); const result = await getPageData(mp, '医生端患者'); if (result) { const { page, data } = result; log('PASS', '医生端患者管理', `path=${page.path}`); const patientList = data.patients || data.list; if (Array.isArray(patientList) && patientList.length > 0) { log('PASS', '患者列表', `${patientList.length} 位患者`); } } } // ============================================ // Step 12: 消息中心 // ============================================ console.log('\n📬 场景11: 消息中心'); { await mp.reLaunch('/pages/messages/index'); await sleep(3000); const result = await getPageData(mp, '消息中心'); if (result) { const { page, data } = result; log('PASS', '消息中心', `path=${page.path}`); const messages = data.messages || data.list || data.notifications; if (Array.isArray(messages) && messages.length > 0) { log('PASS', '消息列表', `${messages.length} 条消息`); } else { log('INFO', '消息数据', `keys: ${Object.keys(data).slice(0, 10).join(', ')}`); } } } // ============================================ // Step 13: 返回首页 — 验证持久性 // ============================================ console.log('\n🔄 场景12: 页面切换后验证认证持久性'); { await mp.reLaunch('/pages/index/index'); await sleep(3000); // 验证认证仍然存在 const storedToken2 = await mp.callWxMethod('getStorageSync', 'access_token'); log(String(storedToken2).length > 10 ? 'PASS' : 'FAIL', '认证持久性', '多次页面切换后 token 仍有效'); const result = await getPageData(mp, '首页回访'); if (result) { log('PASS', '首页回访', `path=${result.page.path}`); } } // 清理 console.log('\n🧹 清理'); try { await mp.close(); log('PASS', '关闭连接'); } catch (e) { log('INFO', '关闭', String(e).substring(0, 50)); } // 汇总 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); });