Files
hms/tools/weapp-mcp/e2e-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

257 lines
8.9 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 自动化测试脚本 (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); });