300 lines
13 KiB
JavaScript
300 lines
13 KiB
JavaScript
const automator = require('miniprogram-automator');
|
|
const { execSync } = require('child_process');
|
|
|
|
const TIMEOUT = 10000;
|
|
function wt(p, ms, label) { return Promise.race([p, new Promise((_, r) => setTimeout(() => r(new Error(label + ' timeout')), ms))]); }
|
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
|
|
async function trySelect(page, sel) {
|
|
try {
|
|
const el = await wt(page.$(sel), 3000, sel);
|
|
if (!el) return null;
|
|
const text = (await wt(el.textContent(), 3000, 'text')).trim().replace(/\s+/g, ' ');
|
|
return text;
|
|
} catch { return null; }
|
|
}
|
|
|
|
async function trySelectAll(page, sel) {
|
|
try {
|
|
const els = await wt(page.$$(sel), 3000, sel);
|
|
const items = [];
|
|
for (let i = 0; i < els.length; i++) {
|
|
try { items.push((await els[i].textContent()).trim().replace(/\s+/g, ' ')); } catch {}
|
|
}
|
|
return items;
|
|
} catch { return []; }
|
|
}
|
|
|
|
async function main() {
|
|
console.log('=== HMS 深度验证 V3 ===\n');
|
|
|
|
let mp;
|
|
for (let i = 1; i <= 8; i++) {
|
|
try {
|
|
mp = await wt(automator.connect({ wsEndpoint: 'ws://127.0.0.1:9420' }), TIMEOUT, 'connect');
|
|
console.log('[OK] 连接成功\n');
|
|
break;
|
|
} catch { if (i === 8) { console.log('连接失败,请确认已运行: cli.bat auto --project ... --auto-port 9420'); return; } console.log('等待...(' + i + ')'); await sleep(3); }
|
|
}
|
|
|
|
const issues = [];
|
|
|
|
// === 首页 ===
|
|
console.log('━━━ 首页 ━━━');
|
|
try {
|
|
await wt(mp.switchTab('/pages/index/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const greeting = await trySelect(page, '.greeting-hello');
|
|
const gName = await trySelect(page, '.greeting-name');
|
|
const gDate = await trySelect(page, '.greeting-date');
|
|
const healthItems = await trySelectAll(page, '.health-item');
|
|
const services = await trySelectAll(page, '.service-item');
|
|
const emptyText = await trySelect(page, '.empty-text');
|
|
|
|
console.log(' 问候语:', greeting || '✗');
|
|
console.log(' 用户名:', gName || '✗');
|
|
console.log(' 日期:', gDate || '✗');
|
|
console.log(' 健康卡片:', healthItems.length > 0 ? healthItems.length + '个' : '✗');
|
|
healthItems.forEach(h => console.log(' -', h));
|
|
console.log(' 快捷服务:', services.length > 0 ? services.length + '个' : '✗');
|
|
services.forEach(s => console.log(' -', s));
|
|
console.log(' 空状态:', emptyText || '(无)');
|
|
|
|
if (!greeting) issues.push('首页缺少问候语');
|
|
if (healthItems.length === 0) issues.push('首页缺少健康数据卡片(应有4个)');
|
|
if (services.length === 0) issues.push('首页缺少快捷服务(应有4个)');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('首页:' + e.message); }
|
|
|
|
// === 健康中心 ===
|
|
console.log('\n━━━ 健康中心 ━━━');
|
|
try {
|
|
await wt(mp.switchTab('/pages/health/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const header = await trySelect(page, '.health-header-title');
|
|
const inputBtn = await trySelect(page, '.health-header-btn-text');
|
|
const cards = await trySelectAll(page, '.health-card');
|
|
const actions = await trySelectAll(page, '.action-card');
|
|
|
|
console.log(' 标题:', header || '✗');
|
|
console.log(' 录入按钮:', inputBtn || '✗');
|
|
console.log(' 健康卡片:', cards.length > 0 ? cards.length + '个' : '✗');
|
|
cards.forEach(c => console.log(' -', c));
|
|
console.log(' 趋势入口:', actions.length > 0 ? actions.length + '个' : '✗');
|
|
actions.forEach(a => console.log(' -', a));
|
|
|
|
if (!header) issues.push('健康中心缺少标题');
|
|
if (cards.length === 0) issues.push('健康中心缺少数据卡片');
|
|
if (actions.length === 0) issues.push('健康中心缺少趋势入口');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('健康中心:' + e.message); }
|
|
|
|
// === 健康录入 ===
|
|
console.log('\n━━━ 健康录入 ━━━');
|
|
try {
|
|
await wt(mp.reLaunch('/pages/health/input/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const picker = await trySelect(page, '.input-picker');
|
|
const fields = await trySelectAll(page, '.input-field');
|
|
const submit = await trySelect(page, '.input-submit');
|
|
const labels = await trySelectAll(page, '.input-label');
|
|
|
|
console.log(' 指标选择器:', picker || '✗');
|
|
console.log(' 表单标签:', labels.length + '个');
|
|
labels.forEach(l => console.log(' -', l));
|
|
console.log(' 输入框:', fields.length + '个');
|
|
console.log(' 提交按钮:', submit || '✗');
|
|
|
|
if (!picker) issues.push('健康录入缺少指标选择器');
|
|
if (fields.length === 0) issues.push('健康录入缺少输入框');
|
|
if (!submit) issues.push('健康录入缺少提交按钮');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('健康录入:' + e.message); }
|
|
|
|
// === 健康趋势 ===
|
|
console.log('\n━━━ 健康趋势 ━━━');
|
|
try {
|
|
await wt(mp.reLaunch('/pages/health/trend/index?indicator=heart_rate'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const title = await trySelect(page, '.trend-title');
|
|
const tabs = await trySelectAll(page, '.trend-tab-text');
|
|
|
|
console.log(' 标题:', title || '✗');
|
|
console.log(' 时间Tab:', tabs.length > 0 ? tabs.join(', ') : '✗');
|
|
|
|
if (!title) issues.push('健康趋势缺少标题');
|
|
if (tabs.length === 0) issues.push('健康趋势缺少时间范围选项(7天/30天/90天)');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('健康趋势:' + e.message); }
|
|
|
|
// === 预约列表 ===
|
|
console.log('\n━━━ 预约列表 ━━━');
|
|
try {
|
|
await wt(mp.switchTab('/pages/appointment/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const pageTitle = await trySelect(page, '.page-title');
|
|
const fab = await trySelect(page, '.fab-text');
|
|
const empty = await trySelect(page, '.empty-text');
|
|
const apptCards = await trySelectAll(page, '.appointment-card');
|
|
|
|
console.log(' 标题:', pageTitle || '✗');
|
|
console.log(' 悬浮按钮:', fab || '✗');
|
|
console.log(' 空状态:', empty || '(有数据)');
|
|
console.log(' 预约卡片:', apptCards.length + '个');
|
|
|
|
if (!pageTitle) issues.push('预约列表缺少标题');
|
|
if (!fab) issues.push('预约列表缺少新建按钮');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('预约列表:' + e.message); }
|
|
|
|
// === 创建预约 ===
|
|
console.log('\n━━━ 创建预约 ━━━');
|
|
try {
|
|
await wt(mp.reLaunch('/pages/appointment/create/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const stepTitle = await trySelect(page, '.step-title');
|
|
const depts = await trySelectAll(page, '.dept-card');
|
|
const nextBtn = await trySelect(page, '.btn-next');
|
|
|
|
console.log(' 步骤标题:', stepTitle || '✗');
|
|
console.log(' 科室卡片:', depts.length > 0 ? depts.length + '个' : '✗');
|
|
depts.forEach(d => console.log(' -', d));
|
|
console.log(' 下一步按钮:', nextBtn || '✗');
|
|
|
|
if (depts.length === 0) issues.push('创建预约缺少科室选择(应有6个)');
|
|
if (!nextBtn) issues.push('创建预约缺少下一步按钮');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('创建预约:' + e.message); }
|
|
|
|
// === 资讯文章 ===
|
|
console.log('\n━━━ 资讯文章 ━━━');
|
|
try {
|
|
await wt(mp.switchTab('/pages/article/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const articles = await trySelectAll(page, '.article-card');
|
|
const empty = await trySelect(page, '.empty-text');
|
|
|
|
console.log(' 文章卡片:', articles.length + '个');
|
|
if (articles.length > 0) articles.forEach((a, i) => console.log(' [' + i + ']', a.substring(0, 50)));
|
|
console.log(' 空状态:', empty || '(有数据)');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('资讯文章:' + e.message); }
|
|
|
|
// === 个人中心 ===
|
|
console.log('\n━━━ 个人中心 ━━━');
|
|
try {
|
|
await wt(mp.switchTab('/pages/profile/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const avatar = await trySelect(page, '.profile-avatar-text');
|
|
const pName = await trySelect(page, '.profile-name');
|
|
const menus = await trySelectAll(page, '.menu-item');
|
|
const logout = await trySelect(page, '.logout-text');
|
|
|
|
console.log(' 头像字符:', avatar || '✗');
|
|
console.log(' 用户名:', pName || '✗');
|
|
console.log(' 菜单项:', menus.length > 0 ? menus.length + '个' : '✗');
|
|
menus.forEach(m => console.log(' -', m));
|
|
console.log(' 退出按钮:', logout || '✗');
|
|
|
|
if (menus.length === 0) issues.push('个人中心缺少菜单项(应有5个)');
|
|
if (!logout) issues.push('个人中心缺少退出登录');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('个人中心:' + e.message); }
|
|
|
|
// === 就诊人管理 ===
|
|
console.log('\n━━━ 就诊人管理 ━━━');
|
|
try {
|
|
await wt(mp.reLaunch('/pages/profile/family/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const addBtn = await trySelect(page, '.family-add-text');
|
|
const patients = await trySelectAll(page, '.family-item');
|
|
const empty = await trySelect(page, '.empty-text');
|
|
|
|
console.log(' 添加按钮:', addBtn || '✗');
|
|
console.log(' 就诊人:', patients.length + '个');
|
|
console.log(' 空状态:', empty || '(有数据)');
|
|
|
|
if (!addBtn) issues.push('就诊人管理缺少添加按钮');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('就诊人管理:' + e.message); }
|
|
|
|
// === 我的报告 ===
|
|
console.log('\n━━━ 我的报告 ━━━');
|
|
try {
|
|
await wt(mp.reLaunch('/pages/profile/reports/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const reports = await trySelectAll(page, '.report-card');
|
|
const empty = await trySelect(page, '.empty-text');
|
|
|
|
console.log(' 报告卡片:', reports.length + '个');
|
|
console.log(' 空状态:', empty || '(有数据)');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('我的报告:' + e.message); }
|
|
|
|
// === 登录页 ===
|
|
console.log('\n━━━ 登录页 ━━━');
|
|
try {
|
|
await wt(mp.reLaunch('/pages/login/index'), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
|
|
const logo = await trySelect(page, '.login-logo-text');
|
|
const lTitle = await trySelect(page, '.login-title');
|
|
const lSub = await trySelect(page, '.login-subtitle');
|
|
const lBtn = await trySelect(page, '.login-btn');
|
|
const agreement = await trySelect(page, '.agreement-text');
|
|
const checkbox = await trySelect(page, '.checkbox');
|
|
const links = await trySelectAll(page, '.agreement-link');
|
|
|
|
console.log(' Logo:', logo || '✗');
|
|
console.log(' 标题:', lTitle || '✗');
|
|
console.log(' 副标题:', lSub || '✗');
|
|
console.log(' 登录按钮:', lBtn ? '✓' : '✗');
|
|
console.log(' 协议文案:', agreement ? agreement.substring(0, 40) : '✗');
|
|
console.log(' 勾选框:', checkbox ? '✓' : '✗');
|
|
console.log(' 协议链接:', links.length + '个');
|
|
|
|
if (!lBtn) issues.push('登录页缺少登录按钮');
|
|
if (!checkbox) issues.push('登录页缺少协议勾选框');
|
|
if (links.length < 2) issues.push('登录页缺少用户协议/隐私政策链接(应有2个)');
|
|
} catch (e) { console.log(' FAIL:', e.message); issues.push('登录页:' + e.message); }
|
|
|
|
// === 法律页面 ===
|
|
console.log('\n━━━ 法律页面 ━━━');
|
|
for (const [name, path] of [['用户协议', '/pages/legal/user-agreement'], ['隐私政策', '/pages/legal/privacy-policy']]) {
|
|
try {
|
|
await wt(mp.reLaunch(path), TIMEOUT, 'nav');
|
|
await sleep(2);
|
|
const page = await wt(mp.currentPage(), TIMEOUT, 'page');
|
|
const views = await trySelectAll(page, 'view');
|
|
const hasContent = views.some(v => v.length > 10);
|
|
console.log(' ' + name + ':', hasContent ? '✓ 有内容' : '✗ 内容为空');
|
|
if (!hasContent) issues.push(name + '内容为空');
|
|
} catch (e) { console.log(' ' + name + ' FAIL:', e.message); }
|
|
}
|
|
|
|
// ═══ 汇总 ═══
|
|
console.log('\n═══════════════════════════════════════');
|
|
if (issues.length === 0) {
|
|
console.log(' 全部页面内容验证通过');
|
|
} else {
|
|
console.log(' 发现 ' + issues.length + ' 个问题:');
|
|
issues.forEach((issue, i) => console.log(' ' + (i + 1) + '. ' + issue));
|
|
}
|
|
console.log('═══════════════════════════════════════');
|
|
|
|
mp.disconnect();
|
|
}
|
|
|
|
main().catch(err => { console.error('Fatal:', err.message); process.exit(1); });
|