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); });