const automator = require('miniprogram-automator'); const { execSync } = require('child_process'); const TIMEOUT = 12000; function withTimeout(promise, ms, label) { return Promise.race([promise, new Promise((_, r) => setTimeout(() => r(new Error(label + ' timeout')), ms))]); } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } async function checkElements(page, selectors) { const results = {}; for (const [name, selector] of Object.entries(selectors)) { try { const el = await page.$(selector); if (el) { const text = (await el.textContent()).trim().replace(/\s+/g, ' '); results[name] = { found: true, text }; } else { results[name] = { found: false, text: '' }; } } catch (e) { results[name] = { found: false, text: '', error: e.message }; } } return results; } async function checkMultiElements(page, selector) { try { const els = await page.$$(selector); const items = []; for (let i = 0; i < els.length; i++) { try { const text = (await els[i].textContent()).trim().replace(/\s+/g, ' '); items.push(text); } catch {} } return items; } catch { return []; } } async function main() { console.log('=== HMS 深度验证 (元素级) ===\n'); // Restart automation console.log('启动自动化...'); try { execSync('"D:/微信web开发者工具/cli.bat" auto --project "g:/hms/apps/miniprogram" --auto-port 9420', { stdio: 'pipe', timeout: 15000 }); } catch {} // Retry connection let mp; for (let attempt = 1; attempt <= 5; attempt++) { await sleep(5000); try { mp = await withTimeout(automator.connect({ wsEndpoint: 'ws://127.0.0.1:9420' }), TIMEOUT, 'connect'); console.log('[OK] 已连接 (第' + attempt + '次尝试)\n'); break; } catch (e) { console.log('连接失败 (第' + attempt + '次)...'); if (attempt === 5) throw e; } } const issues = []; // ═══ 1. 首页 ═══ console.log('━━━ 1. 首页 ━━━'); try { await mp.switchTab('/pages/index/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); // 等待页面渲染 try { await page.waitFor('.index-page', { timeout: 5000 }); } catch {} const els = await checkElements(page, { greeting: '.greeting-hello', gName: '.greeting-name', gDate: '.greeting-date', healthTitle: '.section-title', healthItem: '.health-item', serviceItem: '.service-item', }); console.log(' 问候语:', els.greeting.found ? '✓ ' + els.greeting.text : '✗ 缺失'); console.log(' 用户名:', els.gName.found ? '✓ ' + els.gName.text : '✗ 缺失'); console.log(' 日期:', els.gDate.found ? '✓ ' + els.gDate.text : '✗ 缺失'); const healthItems = await checkMultiElements(page, '.health-item'); console.log(' 健康卡片:', healthItems.length > 0 ? '✓ ' + healthItems.length + '个' : '✗ 缺失'); if (healthItems.length > 0) { healthItems.forEach((h, i) => console.log(' [' + i + ']', h)); } const services = await checkMultiElements(page, '.service-item'); console.log(' 快捷服务:', services.length > 0 ? '✓ ' + services.length + '个' : '✗ 缺失'); if (services.length > 0) { services.forEach((s, i) => console.log(' [' + i + ']', s)); } if (!els.greeting.found) issues.push('首页: 缺少问候语'); if (healthItems.length === 0) issues.push('首页: 缺少健康数据卡片'); if (services.length === 0) issues.push('首页: 缺少快捷服务'); } catch (e) { console.log(' [FAIL]', e.message); issues.push('首页: ' + e.message); } // ═══ 2. 健康中心 ═══ console.log('\n━━━ 2. 健康中心 ━━━'); try { await mp.switchTab('/pages/health/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); try { await page.waitFor('.health-page', { timeout: 5000 }); } catch {} const els = await checkElements(page, { header: '.health-header-title', inputBtn: '.health-header-btn-text', card: '.health-card', action: '.action-card', }); console.log(' 标题:', els.header.found ? '✓ ' + els.header.text : '✗'); const cards = await checkMultiElements(page, '.health-card'); console.log(' 健康卡片:', cards.length > 0 ? '✓ ' + cards.length + '个' : '✗'); const actions = await checkMultiElements(page, '.action-card'); console.log(' 趋势入口:', actions.length > 0 ? '✓ ' + actions.length + '个' : '✗'); if (actions.length > 0) actions.forEach((a, i) => console.log(' [' + i + ']', a)); if (cards.length === 0) issues.push('健康中心: 缺少健康数据卡片'); if (actions.length === 0) issues.push('健康中心: 缺少趋势快捷入口'); } catch (e) { console.log(' [FAIL]', e.message); issues.push('健康中心: ' + e.message); } // ═══ 3. 健康数据录入 ═══ console.log('\n━━━ 3. 健康数据录入 ━━━'); try { await mp.reLaunch('/pages/health/input/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); const els = await checkElements(page, { label: '.input-label', picker: '.input-picker', field: '.input-field', submit: '.input-submit', }); console.log(' 表单标签:', els.label.found ? '✓' : '✗'); console.log(' 指标选择器:', els.picker.found ? '✓ ' + els.picker.text : '✗'); console.log(' 数值输入:', els.field.found ? '✓' : '✗'); console.log(' 提交按钮:', els.submit.found ? '✓ ' + els.submit.text : '✗'); if (!els.picker.found) issues.push('健康录入: 缺少指标选择器'); if (!els.field.found) issues.push('健康录入: 缺少数值输入框'); if (!els.submit.found) issues.push('健康录入: 缺少提交按钮'); } catch (e) { console.log(' [FAIL]', e.message); issues.push('健康录入: ' + e.message); } // ═══ 4. 创建预约 ═══ console.log('\n━━━ 4. 创建预约 ━━━'); try { await mp.reLaunch('/pages/appointment/create/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); const stepEl = await checkElements(page, { stepTitle: '.step-title', deptCard: '.dept-card', nextBtn: '.btn-next', }); console.log(' 步骤标题:', stepEl.stepTitle.found ? '✓ ' + stepEl.stepTitle.text : '✗'); const deptCards = await checkMultiElements(page, '.dept-card'); console.log(' 科室卡片:', deptCards.length > 0 ? '✓ ' + deptCards.length + '个' : '✗'); if (deptCards.length > 0) { deptCards.forEach((d, i) => console.log(' [' + i + ']', d)); } console.log(' "下一步"按钮:', stepEl.nextBtn.found ? '✓' : '✗'); if (deptCards.length === 0) issues.push('创建预约: 缺少科室选择卡片'); if (!stepEl.nextBtn.found) issues.push('创建预约: 缺少下一步按钮'); } catch (e) { console.log(' [FAIL]', e.message); issues.push('创建预约: ' + e.message); } // ═══ 5. 预约列表 ═══ console.log('\n━━━ 5. 预约列表 ━━━'); try { await mp.switchTab('/pages/appointment/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); const els = await checkElements(page, { title: '.page-title', fab: '.fab-btn', empty: '.empty-text', list: '.appointment-list', }); console.log(' 标题:', els.title.found ? '✓ ' + els.title.text : '✗'); console.log(' 新建预约按钮:', els.fab.found ? '✓' : '✗'); console.log(' 空状态:', els.empty.found ? '✓ ' + els.empty.text : ' (有数据)'); const cards = await checkMultiElements(page, '.appointment-card'); console.log(' 预约卡片:', cards.length + '个'); if (!els.fab.found) issues.push('预约列表: 缺少新建预约按钮'); } catch (e) { console.log(' [FAIL]', e.message); issues.push('预约列表: ' + e.message); } // ═══ 6. 资讯文章 ═══ console.log('\n━━━ 6. 资讯文章 ━━━'); try { await mp.switchTab('/pages/article/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); const articles = await checkMultiElements(page, '.article-card'); const empty = await checkElements(page, { e: '.empty-text' }); console.log(' 文章卡片:', articles.length + '个'); console.log(' 空状态:', empty.e.found ? '✓ ' + empty.e.text : ''); if (articles.length > 0) { articles.forEach((a, i) => console.log(' [' + i + ']', a.substring(0, 50))); } } catch (e) { console.log(' [FAIL]', e.message); issues.push('资讯文章: ' + e.message); } // ═══ 7. 个人中心 ═══ console.log('\n━━━ 7. 个人中心 ━━━'); try { await mp.switchTab('/pages/profile/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); const els = await checkElements(page, { avatar: '.profile-avatar-text', name: '.profile-name', menu: '.menu-item', logout: '.logout-text', }); console.log(' 头像:', els.avatar.found ? '✓ ' + els.avatar.text : '✗'); console.log(' 用户名:', els.name.found ? '✓ ' + els.name.text : '✗'); const menuItems = await checkMultiElements(page, '.menu-item'); console.log(' 菜单项:', menuItems.length > 0 ? '✓ ' + menuItems.length + '个' : '✗'); if (menuItems.length > 0) { menuItems.forEach((m, i) => console.log(' [' + i + ']', m)); } console.log(' 退出登录:', els.logout.found ? '✓ ' + els.logout.text : '✗'); if (menuItems.length === 0) issues.push('个人中心: 缺少菜单项'); if (!els.logout.found) issues.push('个人中心: 缺少退出登录按钮'); } catch (e) { console.log(' [FAIL]', e.message); issues.push('个人中心: ' + e.message); } // ═══ 8. 就诊人管理 ═══ console.log('\n━━━ 8. 就诊人管理 ━━━'); try { await mp.reLaunch('/pages/profile/family/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); const addBtn = await checkElements(page, { btn: '.family-add-text' }); console.log(' "添加就诊人":', addBtn.btn.found ? '✓' : '✗'); const patients = await checkMultiElements(page, '.family-item'); console.log(' 就诊人列表:', patients.length + '个'); if (!addBtn.btn.found) issues.push('就诊人管理: 缺少添加按钮'); } catch (e) { console.log(' [FAIL]', e.message); issues.push('就诊人管理: ' + e.message); } // ═══ 9. 登录页 ═══ console.log('\n━━━ 9. 登录页 ━━━'); try { await mp.reLaunch('/pages/login/index'); await sleep(3000); const page = await mp.currentPage(); console.log(' 路径:', page.path); const els = await checkElements(page, { logo: '.login-logo-text', title: '.login-title', subtitle: '.login-subtitle', btn: '.login-btn', agreement: '.agreement-text', checkbox: '.checkbox', userLink: '.agreement-link', }); console.log(' Logo:', els.logo.found ? '✓ ' + els.logo.text : '✗'); console.log(' 标题:', els.title.found ? '✓ ' + els.title.text : '✗'); console.log(' 副标题:', els.subtitle.found ? '✓ ' + els.subtitle.text : '✗'); console.log(' 登录按钮:', els.btn.found ? '✓' : '✗'); console.log(' 协议勾选:', els.checkbox.found ? '✓' : '✗'); console.log(' 协议文案:', els.agreement.found ? '✓ ' + els.agreement.text.substring(0, 30) : '✗'); const links = await checkMultiElements(page, '.agreement-link'); console.log(' 协议链接:', links.length > 0 ? '✓ ' + links.length + '个' : '✗'); if (!els.btn.found) issues.push('登录页: 缺少登录按钮'); if (!els.checkbox.found) issues.push('登录页: 缺少协议勾选框'); if (links.length < 2) issues.push('登录页: 缺少用户协议/隐私政策链接'); } catch (e) { console.log(' [FAIL]', e.message); issues.push('登录页: ' + e.message); } // ═══ 10. 法律页面 ═══ console.log('\n━━━ 10. 用户协议 & 隐私政策 ━━━'); for (const [name, path] of [['用户协议', '/pages/legal/user-agreement'], ['隐私政策', '/pages/legal/privacy-policy']]) { try { await mp.reLaunch(path); await sleep(2000); const page = await mp.currentPage(); const views = await checkMultiElements(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); issues.push(name + ': ' + 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.substring(0, 30).padEnd(32) + '║'); }); } console.log('╚══════════════════════════════════════╝'); // 不用 mp.close() 避免关闭自动化 mp.disconnect(); } main().catch(err => { console.error('Fatal:', err.message); process.exit(1); });