Files
hms/tools/weapp-mcp/e2e-interactive-test.mjs
iven 66329852b8
Some checks failed
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
fix(miniprogram): useDidShow 恢复认证状态 + E2E 全系统测试报告
- app.tsx: 将 restoreAuth/restoreUI 从 useEffect 改为 useDidShow,
  修复 reLaunch 后 Zustand store 未恢复导致访客模式的问题
- docs/qa/e2e-full-system-report.md: 三端 E2E 测试报告更新,
  原 BUG-1(Admin 随访管理 403)确认为误报,综合通过率 100% (64/64)
- tools/weapp-mcp/e2e-test.mjs: 小程序 E2E 基础导航测试脚本
- tools/weapp-mcp/e2e-interactive-test.mjs: 小程序 E2E 交互操作测试脚本

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 18:25:43 +08:00

392 lines
15 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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); });