/** * 健康档案完整功能测试脚本 * 测试内容: * 1. 基础信息 - 所有9个字段的编辑和保存 * 2. 生活习惯 - 所有10个字段的编辑和保存 * 3. 病史记录 - 添加新记录(5个字段) * 4. 家族病史 - 添加新记录(3个字段) * 5. 过敏记录 - 添加新记录(4个字段) * 6. 验证保存后数据是否正确显示 */ const { chromium } = require('playwright'); const APP_URL = 'http://localhost:8081'; const TEST_PHONE = '13800138000'; const TEST_CODE = '123456'; // 测试数据 const TEST_DATA = { basicInfo: { name: '测试用户' + Date.now().toString().slice(-4), gender: 'female', // 改为女 birth_date: '1990-05-15', height: '175', weight: '68', blood_type: 'A', occupation: '软件工程师', marital_status: 'married', // 改为已婚 region: '北京市海淀区', }, lifestyle: { sleep_time: '23:00', wake_time: '07:30', sleep_quality: 'good', // 良好 meal_regularity: 'regular', // 规律 diet_preference: '清淡饮食', daily_water_ml: '2000', exercise_frequency: 'often', // 经常 exercise_type: '跑步、游泳', is_smoker: false, alcohol_frequency: 'sometimes', // 偶尔 }, medicalHistory: { disease_name: '高血压', disease_type: 'chronic', // 慢性病 diagnosed_date: '2020-03', status: 'controlled', // 已控制 notes: '定期服药控制中', }, familyHistory: { relation: 'father', // 父亲 disease_name: '糖尿病', notes: '2型糖尿病', }, allergyRecord: { allergy_type: 'drug', // 药物 allergen: '青霉素', severity: 'severe', // 重度 reaction_desc: '全身过敏反应,需立即就医', } }; // 测试结果统计 const testResults = { passed: 0, failed: 0, tests: [] }; function logTest(name, passed, detail = '') { const status = passed ? '✓' : '✗'; const msg = `${status} ${name}${detail ? ': ' + detail : ''}`; console.log(msg); testResults.tests.push({ name, passed, detail }); if (passed) testResults.passed++; else testResults.failed++; } // 使用坐标点击元素 async function clickByText(page, text) { const pos = await page.evaluate((searchText) => { const allElements = document.querySelectorAll('*'); for (const el of allElements) { if (el.textContent?.trim() === searchText && el.children.length === 0) { const rect = el.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }; } } } return null; }, text); if (pos) { await page.mouse.click(pos.x, pos.y); return true; } return false; } // 等待并检查提示消息 async function waitForToast(page, text, timeout = 3000) { try { const toast = page.locator(`text=${text}`).first(); return await toast.isVisible({ timeout }); } catch { return false; } } // 关闭所有弹窗 async function closeAllModals(page) { for (let i = 0; i < 5; i++) { const cancelClicked = await clickByText(page, '取消'); if (cancelClicked) { await page.waitForTimeout(500); continue; } const closeBtn = page.locator('[aria-label="Close"]').first(); if (await closeBtn.isVisible({ timeout: 300 }).catch(() => false)) { await closeBtn.click({ force: true }); await page.waitForTimeout(500); continue; } break; } } // 填写 TextInput 输入框 async function fillInput(page, label, value) { // 方式1: 通过 label 属性查找 let input = page.locator(`input[label="${label}"]`).first(); if (await input.isVisible({ timeout: 500 }).catch(() => false)) { await input.clear(); await input.fill(value); return true; } // 方式2: 通过 placeholder 查找 input = page.locator(`input[placeholder*="${label}"]`).first(); if (await input.isVisible({ timeout: 500 }).catch(() => false)) { await input.clear(); await input.fill(value); return true; } // 方式3: 通过文本标签位置查找附近的输入框 const inputPos = await page.evaluate((labelText) => { const allElements = document.querySelectorAll('*'); for (const el of allElements) { const text = el.textContent?.trim(); if (text && (text === labelText || text.startsWith(labelText))) { const rect = el.getBoundingClientRect(); // 找到标签后,查找附近的输入框 const inputs = document.querySelectorAll('input:not([type="checkbox"]):not([role="switch"])'); let closestInput = null; let minDistance = Infinity; for (const inp of inputs) { const inpRect = inp.getBoundingClientRect(); // 输入框在标签下方或右侧 const distance = Math.abs(inpRect.y - rect.y) + Math.abs(inpRect.x - rect.x); if (distance < minDistance && inpRect.y >= rect.y - 50) { minDistance = distance; closestInput = inp; } } if (closestInput) { return { found: true }; } } } return { found: false }; }, label); return false; } // 点击 SegmentedButtons 中的选项 async function clickSegmentOption(page, optionText) { const clicked = await page.evaluate((text) => { const buttons = document.querySelectorAll('*'); for (const btn of buttons) { if (btn.textContent?.trim() === text && btn.children.length === 0) { const rect = btn.getBoundingClientRect(); if (rect.width > 20 && rect.height > 15) { btn.click(); return true; } } } return false; }, optionText); if (!clicked) { // 备用: 使用 Playwright 定位器 const btn = page.locator(`text=${optionText}`).first(); if (await btn.isVisible({ timeout: 500 }).catch(() => false)) { await btn.click({ force: true }); return true; } } return clicked; } async function login(page) { console.log('\n【准备工作】登录账号...'); const loginBtn = page.locator('text=登录').first(); if (!(await loginBtn.isVisible({ timeout: 2000 }).catch(() => false))) { logTest('已登录状态', true, '跳过登录流程'); return true; } await page.locator('input').first().fill(TEST_PHONE); await page.waitForTimeout(300); const getCodeBtn = page.locator('text=获取验证码').first(); if (await getCodeBtn.isVisible()) { await getCodeBtn.click(); await page.waitForTimeout(1000); } await page.locator('input').nth(1).fill(TEST_CODE); await page.waitForTimeout(300); await loginBtn.click(); await page.waitForTimeout(3000); const homeVisible = await page.locator('text=/.*好,.*$/').first().isVisible({ timeout: 5000 }).catch(() => false); logTest('登录', homeVisible); return homeVisible; } async function navigateToHealthProfile(page) { console.log('\n【步骤1】导航到健康档案页面...'); // 点击"我的" Tab const tabPos = await page.evaluate(() => { const allElements = document.querySelectorAll('*'); for (const el of allElements) { if (el.textContent?.trim() === '我的' && el.children.length === 0) { const rect = el.getBoundingClientRect(); if (rect.y > 500) { return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }; } } } return null; }); if (tabPos) { await page.mouse.click(tabPos.x, tabPos.y); await page.waitForTimeout(1500); } // 滚动到顶部 await page.evaluate(() => window.scrollTo(0, 0)); await page.waitForTimeout(500); // 点击健康档案菜单 const healthMenuPos = await page.evaluate(() => { const allElements = document.querySelectorAll('*'); for (const el of allElements) { const text = el.textContent; if (text?.includes('健康档案') && text?.includes('查看和管理') && !text?.includes('用药/治疗记录')) { const rect = el.getBoundingClientRect(); if (rect.width > 100 && rect.height > 30 && rect.height < 100) { return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }; } } } return null; }); if (healthMenuPos) { await page.mouse.click(healthMenuPos.x, healthMenuPos.y); await page.waitForTimeout(2000); } else { // 备用方法 const healthItem = page.locator('text=健康档案').first(); if (await healthItem.isVisible()) { await healthItem.click({ force: true }); await page.waitForTimeout(2000); } } // 验证进入健康档案页面 const basicInfo = await page.locator('text=基础信息').first().isVisible({ timeout: 3000 }).catch(() => false); const backBtn = await page.locator('text=← 返回').first().isVisible({ timeout: 2000 }).catch(() => false); const success = basicInfo && backBtn; logTest('导航到健康档案页面', success); await page.screenshot({ path: 'tests/screenshots/hp-initial.png' }); return success; } async function testBasicInfoEdit(page) { console.log('\n【步骤2】测试基础信息编辑(9个字段)...'); await page.evaluate(() => window.scrollTo(0, 0)); await page.waitForTimeout(500); // 点击基础信息的编辑按钮 const editPos = await page.evaluate(() => { const basicTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '基础信息' ); if (basicTitle) { const rect = basicTitle.getBoundingClientRect(); return { x: window.innerWidth - 50, y: rect.y }; } return null; }); if (!editPos) { logTest('打开基础信息编辑弹窗', false, '未找到编辑按钮'); return false; } await page.mouse.click(editPos.x, editPos.y); await page.waitForTimeout(1000); const modalOpened = await page.locator('text=编辑基础信息').first().isVisible({ timeout: 2000 }).catch(() => false); logTest('打开基础信息编辑弹窗', modalOpened); if (!modalOpened) return false; await page.screenshot({ path: 'tests/screenshots/hp-basic-edit-before.png' }); // 获取所有输入框 const inputs = page.locator('input:not([type="checkbox"]):not([role="switch"])'); const inputCount = await inputs.count(); console.log(` 找到 ${inputCount} 个输入框`); // 测试1: 填写姓名 let nameInput = inputs.first(); if (await nameInput.isVisible()) { await nameInput.clear(); await nameInput.fill(TEST_DATA.basicInfo.name); console.log(` 填写姓名: ${TEST_DATA.basicInfo.name}`); logTest('基础信息-姓名输入', true); } else { logTest('基础信息-姓名输入', false, '未找到输入框'); } // 测试2: 选择性别 - 女 const genderClicked = await clickSegmentOption(page, '女'); logTest('基础信息-性别选择', genderClicked); await page.waitForTimeout(300); // 测试3: 填写出生日期 - 找到第二个输入框 if (inputCount >= 2) { const birthInput = inputs.nth(1); if (await birthInput.isVisible()) { await birthInput.clear(); await birthInput.fill(TEST_DATA.basicInfo.birth_date); console.log(` 填写出生日期: ${TEST_DATA.basicInfo.birth_date}`); logTest('基础信息-出生日期输入', true); } } // 测试4 & 5: 填写身高和体重 - 找到第三、四个输入框 if (inputCount >= 4) { const heightInput = inputs.nth(2); const weightInput = inputs.nth(3); if (await heightInput.isVisible()) { await heightInput.clear(); await heightInput.fill(TEST_DATA.basicInfo.height); console.log(` 填写身高: ${TEST_DATA.basicInfo.height}cm`); logTest('基础信息-身高输入', true); } if (await weightInput.isVisible()) { await weightInput.clear(); await weightInput.fill(TEST_DATA.basicInfo.weight); console.log(` 填写体重: ${TEST_DATA.basicInfo.weight}kg`); logTest('基础信息-体重输入', true); } } // 测试6: 填写血型 if (inputCount >= 5) { const bloodInput = inputs.nth(4); if (await bloodInput.isVisible()) { await bloodInput.clear(); await bloodInput.fill(TEST_DATA.basicInfo.blood_type); console.log(` 填写血型: ${TEST_DATA.basicInfo.blood_type}`); logTest('基础信息-血型输入', true); } } // 测试7: 填写职业 if (inputCount >= 6) { const occupationInput = inputs.nth(5); if (await occupationInput.isVisible()) { await occupationInput.clear(); await occupationInput.fill(TEST_DATA.basicInfo.occupation); console.log(` 填写职业: ${TEST_DATA.basicInfo.occupation}`); logTest('基础信息-职业输入', true); } } // 测试8: 选择婚姻状况 - 已婚 const maritalClicked = await clickSegmentOption(page, '已婚'); logTest('基础信息-婚姻状况选择', maritalClicked); await page.waitForTimeout(300); // 测试9: 填写地区 if (inputCount >= 7) { const regionInput = inputs.nth(6); if (await regionInput.isVisible()) { await regionInput.clear(); await regionInput.fill(TEST_DATA.basicInfo.region); console.log(` 填写地区: ${TEST_DATA.basicInfo.region}`); logTest('基础信息-地区输入', true); } } await page.screenshot({ path: 'tests/screenshots/hp-basic-edit-after.png' }); // 点击保存按钮 const saveClicked = await clickByText(page, '保存'); if (!saveClicked) { logTest('基础信息-点击保存', false, '未找到保存按钮'); await closeAllModals(page); return false; } await page.waitForTimeout(2500); // 验证保存结果 const saveSuccess = await waitForToast(page, '保存成功', 3000); const modalClosed = !(await page.locator('text=编辑基础信息').first().isVisible({ timeout: 500 }).catch(() => false)); logTest('基础信息-保存成功', saveSuccess || modalClosed, saveSuccess ? '显示保存成功提示' : (modalClosed ? '弹窗已关闭' : '')); // 等待数据刷新 await page.waitForTimeout(1500); // 验证保存后的数据是否正确显示 await page.evaluate(() => window.scrollTo(0, 0)); await page.waitForTimeout(500); const nameDisplayed = await page.locator(`text=${TEST_DATA.basicInfo.name}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('基础信息-保存后姓名显示', nameDisplayed); const regionDisplayed = await page.locator(`text=${TEST_DATA.basicInfo.region}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('基础信息-保存后地区显示', regionDisplayed); const occupationDisplayed = await page.locator(`text=${TEST_DATA.basicInfo.occupation}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('基础信息-保存后职业显示', occupationDisplayed); await page.screenshot({ path: 'tests/screenshots/hp-basic-saved.png' }); return saveSuccess || modalClosed; } async function testLifestyleEdit(page) { console.log('\n【步骤3】测试生活习惯编辑(10个字段)...'); await page.evaluate(() => window.scrollTo(0, 300)); await page.waitForTimeout(500); // 点击生活习惯的编辑按钮 const editPos = await page.evaluate(() => { const title = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '生活习惯' ); if (title) { const rect = title.getBoundingClientRect(); return { x: window.innerWidth - 50, y: rect.y }; } return null; }); if (!editPos) { logTest('打开生活习惯编辑弹窗', false, '未找到编辑按钮'); return false; } await page.mouse.click(editPos.x, editPos.y); await page.waitForTimeout(1000); const modalOpened = await page.locator('text=编辑生活习惯').first().isVisible({ timeout: 2000 }).catch(() => false); logTest('打开生活习惯编辑弹窗', modalOpened); if (!modalOpened) return false; await page.screenshot({ path: 'tests/screenshots/hp-lifestyle-edit-before.png' }); // 获取所有输入框 const inputs = page.locator('input:not([type="checkbox"]):not([role="switch"])'); const inputCount = await inputs.count(); console.log(` 找到 ${inputCount} 个输入框`); // 测试1 & 2: 填写入睡时间和起床时间 if (inputCount >= 2) { const sleepInput = inputs.first(); const wakeInput = inputs.nth(1); if (await sleepInput.isVisible()) { await sleepInput.clear(); await sleepInput.fill(TEST_DATA.lifestyle.sleep_time); console.log(` 填写入睡时间: ${TEST_DATA.lifestyle.sleep_time}`); logTest('生活习惯-入睡时间输入', true); } if (await wakeInput.isVisible()) { await wakeInput.clear(); await wakeInput.fill(TEST_DATA.lifestyle.wake_time); console.log(` 填写起床时间: ${TEST_DATA.lifestyle.wake_time}`); logTest('生活习惯-起床时间输入', true); } } // 测试3: 选择睡眠质量 - 良好 const sleepQualityClicked = await clickSegmentOption(page, '良好'); logTest('生活习惯-睡眠质量选择', sleepQualityClicked); await page.waitForTimeout(300); // 测试4: 选择三餐规律 - 规律 const mealClicked = await clickSegmentOption(page, '规律'); logTest('生活习惯-三餐规律选择', mealClicked); await page.waitForTimeout(300); // 测试5: 填写饮食偏好 if (inputCount >= 3) { const dietInput = inputs.nth(2); if (await dietInput.isVisible()) { await dietInput.clear(); await dietInput.fill(TEST_DATA.lifestyle.diet_preference); console.log(` 填写饮食偏好: ${TEST_DATA.lifestyle.diet_preference}`); logTest('生活习惯-饮食偏好输入', true); } } // 测试6: 填写日饮水量 if (inputCount >= 4) { const waterInput = inputs.nth(3); if (await waterInput.isVisible()) { await waterInput.clear(); await waterInput.fill(TEST_DATA.lifestyle.daily_water_ml); console.log(` 填写日饮水量: ${TEST_DATA.lifestyle.daily_water_ml}ml`); logTest('生活习惯-日饮水量输入', true); } } // 测试7: 选择运动频率 - 经常 const exerciseClicked = await clickSegmentOption(page, '经常'); logTest('生活习惯-运动频率选择', exerciseClicked); await page.waitForTimeout(300); // 测试8: 填写运动类型 if (inputCount >= 5) { const exerciseTypeInput = inputs.nth(4); if (await exerciseTypeInput.isVisible()) { await exerciseTypeInput.clear(); await exerciseTypeInput.fill(TEST_DATA.lifestyle.exercise_type); console.log(` 填写运动类型: ${TEST_DATA.lifestyle.exercise_type}`); logTest('生活习惯-运动类型输入', true); } } // 测试9: 选择是否吸烟 - 否 const smokerClicked = await clickSegmentOption(page, '否'); logTest('生活习惯-吸烟选择', smokerClicked); await page.waitForTimeout(300); // 测试10: 选择饮酒频率 - 偶尔 const alcoholClicked = await clickSegmentOption(page, '偶尔'); logTest('生活习惯-饮酒频率选择', alcoholClicked); await page.waitForTimeout(300); await page.screenshot({ path: 'tests/screenshots/hp-lifestyle-edit-after.png' }); // 点击保存按钮 const saveClicked = await clickByText(page, '保存'); if (!saveClicked) { logTest('生活习惯-点击保存', false, '未找到保存按钮'); await closeAllModals(page); return false; } await page.waitForTimeout(2500); // 验证保存结果 const saveSuccess = await waitForToast(page, '保存成功', 3000); const modalClosed = !(await page.locator('text=编辑生活习惯').first().isVisible({ timeout: 500 }).catch(() => false)); logTest('生活习惯-保存成功', saveSuccess || modalClosed, saveSuccess ? '显示保存成功提示' : (modalClosed ? '弹窗已关闭' : '')); // 等待数据刷新 await page.waitForTimeout(1500); // 验证保存后的数据是否正确显示 await page.evaluate(() => { const lifestyleTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '生活习惯' ); if (lifestyleTitle) { lifestyleTitle.scrollIntoView({ behavior: 'instant', block: 'center' }); } }); await page.waitForTimeout(500); const sleepTimeDisplayed = await page.locator(`text=${TEST_DATA.lifestyle.sleep_time}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('生活习惯-保存后入睡时间显示', sleepTimeDisplayed); const dietDisplayed = await page.locator(`text=${TEST_DATA.lifestyle.diet_preference}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('生活习惯-保存后饮食偏好显示', dietDisplayed); await page.screenshot({ path: 'tests/screenshots/hp-lifestyle-saved.png' }); return saveSuccess || modalClosed; } async function testMedicalHistoryAdd(page) { console.log('\n【步骤4】测试添加病史记录(5个字段)...'); // 滚动到病史记录卡片 await page.evaluate(() => { const medicalTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '病史记录' ); if (medicalTitle) { medicalTitle.scrollIntoView({ behavior: 'instant', block: 'center' }); } }); await page.waitForTimeout(800); // 点击添加按钮 const addPos = await page.evaluate(() => { const medicalTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '病史记录' ); if (medicalTitle) { const rect = medicalTitle.getBoundingClientRect(); return { x: window.innerWidth - 50, y: Math.min(rect.y, window.innerHeight - 100) }; } return null; }); if (!addPos) { logTest('打开添加病史记录弹窗', false, '未找到添加按钮'); return false; } await page.mouse.click(addPos.x, addPos.y); await page.waitForTimeout(1000); const modalOpened = await page.locator('text=添加病史记录').first().isVisible({ timeout: 2000 }).catch(() => false); logTest('打开添加病史记录弹窗', modalOpened); if (!modalOpened) return false; await page.screenshot({ path: 'tests/screenshots/hp-medical-add-before.png' }); // 获取所有输入框 const inputs = page.locator('input:not([type="checkbox"]):not([role="switch"])'); // 测试1: 填写疾病名称 const diseaseInput = inputs.first(); if (await diseaseInput.isVisible()) { await diseaseInput.click(); await page.waitForTimeout(200); await diseaseInput.fill(TEST_DATA.medicalHistory.disease_name); console.log(` 填写疾病名称: ${TEST_DATA.medicalHistory.disease_name}`); logTest('病史记录-疾病名称输入', true); } // 测试2: 选择疾病类型 - 慢性病 const diseaseTypeClicked = await clickSegmentOption(page, '慢性病'); logTest('病史记录-疾病类型选择', diseaseTypeClicked); await page.waitForTimeout(300); // 测试3: 填写诊断日期 const dateInput = inputs.nth(1); if (await dateInput.isVisible({ timeout: 500 }).catch(() => false)) { await dateInput.click(); await page.waitForTimeout(200); await dateInput.fill(TEST_DATA.medicalHistory.diagnosed_date); console.log(` 填写诊断日期: ${TEST_DATA.medicalHistory.diagnosed_date}`); logTest('病史记录-诊断日期输入', true); } // 测试4: 选择治疗状态 - 已控制 const statusClicked = await clickSegmentOption(page, '已控制'); logTest('病史记录-治疗状态选择', statusClicked); await page.waitForTimeout(300); // 测试5: 填写备注 (多行输入框) const textAreas = page.locator('textarea'); const textAreaCount = await textAreas.count(); if (textAreaCount > 0) { const notesInput = textAreas.first(); if (await notesInput.isVisible({ timeout: 500 }).catch(() => false)) { await notesInput.click(); await page.waitForTimeout(200); await notesInput.fill(TEST_DATA.medicalHistory.notes); console.log(` 填写备注: ${TEST_DATA.medicalHistory.notes}`); logTest('病史记录-备注输入', true); } } await page.screenshot({ path: 'tests/screenshots/hp-medical-add-after.png' }); // 点击添加按钮 const addClicked = await clickByText(page, '添加'); if (!addClicked) { logTest('病史记录-点击添加', false, '未找到添加按钮'); await closeAllModals(page); return false; } await page.waitForTimeout(2500); // 验证添加结果 const addSuccess = await waitForToast(page, '添加成功', 3000); const modalClosed = !(await page.locator('text=添加病史记录').first().isVisible({ timeout: 500 }).catch(() => false)); logTest('病史记录-添加成功', addSuccess || modalClosed, addSuccess ? '显示添加成功提示' : (modalClosed ? '弹窗已关闭' : '')); // 等待数据刷新 await page.waitForTimeout(1500); // 验证添加后的数据是否正确显示 await page.evaluate(() => { const medicalTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '病史记录' ); if (medicalTitle) { medicalTitle.scrollIntoView({ behavior: 'instant', block: 'center' }); } }); await page.waitForTimeout(500); const diseaseDisplayed = await page.locator(`text=${TEST_DATA.medicalHistory.disease_name}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('病史记录-添加后疾病名称显示', diseaseDisplayed); await page.screenshot({ path: 'tests/screenshots/hp-medical-saved.png' }); return addSuccess || modalClosed; } async function testFamilyHistoryAdd(page) { console.log('\n【步骤5】测试添加家族病史(3个字段)...'); // 滚动到家族病史卡片 await page.evaluate(() => { const familyTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '家族病史' ); if (familyTitle) { familyTitle.scrollIntoView({ behavior: 'instant', block: 'center' }); } }); await page.waitForTimeout(800); // 点击添加按钮 const addPos = await page.evaluate(() => { const familyTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '家族病史' ); if (familyTitle) { const rect = familyTitle.getBoundingClientRect(); return { x: window.innerWidth - 50, y: Math.min(rect.y, window.innerHeight - 100) }; } return null; }); if (!addPos) { logTest('打开添加家族病史弹窗', false, '未找到添加按钮'); return false; } await page.mouse.click(addPos.x, addPos.y); await page.waitForTimeout(1000); const modalOpened = await page.locator('text=添加家族病史').first().isVisible({ timeout: 2000 }).catch(() => false); logTest('打开添加家族病史弹窗', modalOpened); if (!modalOpened) return false; await page.screenshot({ path: 'tests/screenshots/hp-family-add-before.png' }); // 测试1: 选择亲属关系 - 父亲 const relationClicked = await clickSegmentOption(page, '父亲'); logTest('家族病史-亲属关系选择', relationClicked); await page.waitForTimeout(300); // 测试2: 填写疾病名称 const inputs = page.locator('input:not([type="checkbox"]):not([role="switch"])'); const diseaseInput = inputs.first(); if (await diseaseInput.isVisible()) { await diseaseInput.click(); await page.waitForTimeout(200); await diseaseInput.fill(TEST_DATA.familyHistory.disease_name); console.log(` 填写疾病名称: ${TEST_DATA.familyHistory.disease_name}`); logTest('家族病史-疾病名称输入', true); } // 测试3: 填写备注 const textAreas = page.locator('textarea'); if (await textAreas.first().isVisible({ timeout: 500 }).catch(() => false)) { await textAreas.first().click(); await page.waitForTimeout(200); await textAreas.first().fill(TEST_DATA.familyHistory.notes); console.log(` 填写备注: ${TEST_DATA.familyHistory.notes}`); logTest('家族病史-备注输入', true); } await page.screenshot({ path: 'tests/screenshots/hp-family-add-after.png' }); // 点击添加按钮 const addClicked = await clickByText(page, '添加'); if (!addClicked) { logTest('家族病史-点击添加', false, '未找到添加按钮'); await closeAllModals(page); return false; } await page.waitForTimeout(2500); // 验证添加结果 const addSuccess = await waitForToast(page, '添加成功', 3000); const modalClosed = !(await page.locator('text=添加家族病史').first().isVisible({ timeout: 500 }).catch(() => false)); logTest('家族病史-添加成功', addSuccess || modalClosed, addSuccess ? '显示添加成功提示' : (modalClosed ? '弹窗已关闭' : '')); // 等待数据刷新 await page.waitForTimeout(1500); // 验证添加后的数据是否正确显示 const diseaseDisplayed = await page.locator(`text=${TEST_DATA.familyHistory.disease_name}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('家族病史-添加后疾病名称显示', diseaseDisplayed); await page.screenshot({ path: 'tests/screenshots/hp-family-saved.png' }); return addSuccess || modalClosed; } async function testAllergyRecordAdd(page) { console.log('\n【步骤6】测试添加过敏记录(4个字段)...'); // 滚动到过敏记录卡片 await page.evaluate(() => { const allergyTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '过敏记录' ); if (allergyTitle) { allergyTitle.scrollIntoView({ behavior: 'instant', block: 'center' }); } }); await page.waitForTimeout(800); // 点击添加按钮 const addPos = await page.evaluate(() => { const allergyTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '过敏记录' ); if (allergyTitle) { const rect = allergyTitle.getBoundingClientRect(); return { x: window.innerWidth - 50, y: Math.min(rect.y, window.innerHeight - 100) }; } return null; }); if (!addPos) { logTest('打开添加过敏记录弹窗', false, '未找到添加按钮'); return false; } await page.mouse.click(addPos.x, addPos.y); await page.waitForTimeout(1000); const modalOpened = await page.locator('text=添加过敏记录').first().isVisible({ timeout: 2000 }).catch(() => false); logTest('打开添加过敏记录弹窗', modalOpened); if (!modalOpened) return false; await page.screenshot({ path: 'tests/screenshots/hp-allergy-add-before.png' }); // 测试1: 选择过敏类型 - 药物 const typeClicked = await clickSegmentOption(page, '药物'); logTest('过敏记录-过敏类型选择', typeClicked); await page.waitForTimeout(300); // 测试2: 填写过敏原 const inputs = page.locator('input:not([type="checkbox"]):not([role="switch"])'); const allergenInput = inputs.first(); if (await allergenInput.isVisible()) { await allergenInput.click(); await page.waitForTimeout(200); await allergenInput.fill(TEST_DATA.allergyRecord.allergen); console.log(` 填写过敏原: ${TEST_DATA.allergyRecord.allergen}`); logTest('过敏记录-过敏原输入', true); } // 测试3: 选择严重程度 - 重度 const severityClicked = await clickSegmentOption(page, '重度'); logTest('过敏记录-严重程度选择', severityClicked); await page.waitForTimeout(300); // 测试4: 填写过敏反应描述 const textAreas = page.locator('textarea'); if (await textAreas.first().isVisible({ timeout: 500 }).catch(() => false)) { await textAreas.first().click(); await page.waitForTimeout(200); await textAreas.first().fill(TEST_DATA.allergyRecord.reaction_desc); console.log(` 填写过敏反应描述: ${TEST_DATA.allergyRecord.reaction_desc}`); logTest('过敏记录-过敏反应描述输入', true); } await page.screenshot({ path: 'tests/screenshots/hp-allergy-add-after.png' }); // 点击添加按钮 const addClicked = await clickByText(page, '添加'); if (!addClicked) { logTest('过敏记录-点击添加', false, '未找到添加按钮'); await closeAllModals(page); return false; } await page.waitForTimeout(2500); // 验证添加结果 const addSuccess = await waitForToast(page, '添加成功', 3000); const modalClosed = !(await page.locator('text=添加过敏记录').first().isVisible({ timeout: 500 }).catch(() => false)); logTest('过敏记录-添加成功', addSuccess || modalClosed, addSuccess ? '显示添加成功提示' : (modalClosed ? '弹窗已关闭' : '')); // 等待数据刷新 await page.waitForTimeout(1500); // 验证添加后的数据是否正确显示 const allergenDisplayed = await page.locator(`text=${TEST_DATA.allergyRecord.allergen}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('过敏记录-添加后过敏原显示', allergenDisplayed); await page.screenshot({ path: 'tests/screenshots/hp-allergy-saved.png' }); return addSuccess || modalClosed; } async function testMedicalHistoryDelete(page) { console.log('\n【步骤7】测试病史记录删除功能...'); // 滚动到病史记录区域 await page.evaluate(() => { const medicalTitle = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === '病史记录' ); if (medicalTitle) { medicalTitle.scrollIntoView({ behavior: 'instant', block: 'center' }); } }); await page.waitForTimeout(800); // 检查是否有病史记录可以删除 const diseaseVisible = await page.locator(`text=${TEST_DATA.medicalHistory.disease_name}`).first().isVisible({ timeout: 2000 }).catch(() => false); if (!diseaseVisible) { logTest('病史记录-删除测试', false, '未找到可删除的记录'); return false; } // 长按病史记录项 const diseasePos = await page.evaluate((diseaseName) => { const diseaseEl = Array.from(document.querySelectorAll('*')).find( el => el.textContent?.trim() === diseaseName && el.children.length === 0 ); if (diseaseEl) { const rect = diseaseEl.getBoundingClientRect(); return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }; } return null; }, TEST_DATA.medicalHistory.disease_name); if (!diseasePos) { logTest('病史记录-找到删除目标', false, '未找到记录位置'); return false; } logTest('病史记录-找到删除目标', true); // 长按触发删除 console.log(' 长按记录...'); await page.mouse.move(diseasePos.x, diseasePos.y); await page.mouse.down(); await page.waitForTimeout(800); // 长按时间 await page.mouse.up(); await page.waitForTimeout(1000); // 检查确认弹窗是否出现 const confirmVisible = await page.locator('text=确认删除').first().isVisible({ timeout: 3000 }).catch(() => false); logTest('病史记录-删除确认弹窗显示', confirmVisible); if (!confirmVisible) { // 尝试点击方式触发 onLongPress const touchEl = page.locator(`text=${TEST_DATA.medicalHistory.disease_name}`).first(); if (await touchEl.isVisible()) { // 使用 JavaScript 触发 long press await page.evaluate((diseaseName) => { const el = Array.from(document.querySelectorAll('*')).find( e => e.textContent?.trim() === diseaseName && e.children.length === 0 ); if (el) { // 尝试找到父级 TouchableOpacity 并触发长按 let parent = el.parentElement; for (let i = 0; i < 5 && parent; i++) { if (parent.onclick || parent.onlongpress) { const event = new Event('longpress', { bubbles: true }); parent.dispatchEvent(event); break; } parent = parent.parentElement; } } }, TEST_DATA.medicalHistory.disease_name); await page.waitForTimeout(1000); } // 再次检查 const confirmAgain = await page.locator('text=确认删除').first().isVisible({ timeout: 2000 }).catch(() => false); if (!confirmAgain) { logTest('病史记录-删除功能', false, '无法触发长按删除'); await page.screenshot({ path: 'tests/screenshots/hp-medical-delete-fail.png' }); return false; } } await page.screenshot({ path: 'tests/screenshots/hp-medical-delete-confirm.png' }); // 点击删除按钮 const deleteClicked = await clickByText(page, '删除'); if (!deleteClicked) { logTest('病史记录-点击删除按钮', false, '未找到删除按钮'); await closeAllModals(page); return false; } logTest('病史记录-点击删除按钮', true); await page.waitForTimeout(2000); // 验证删除结果 const deleteSuccess = await waitForToast(page, '删除成功', 3000); logTest('病史记录-删除成功', deleteSuccess, deleteSuccess ? '显示删除成功提示' : ''); // 验证记录已从页面移除 await page.waitForTimeout(1000); const diseaseStillVisible = await page.locator(`text=${TEST_DATA.medicalHistory.disease_name}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('病史记录-记录已删除', !diseaseStillVisible, diseaseStillVisible ? '记录仍然显示' : ''); await page.screenshot({ path: 'tests/screenshots/hp-medical-delete-done.png' }); return deleteSuccess || !diseaseStillVisible; } async function verifyAllSavedData(page) { console.log('\n【步骤8】验证所有保存的数据...'); // 刷新页面获取最新数据 await page.reload(); await page.waitForTimeout(3000); // 重新导航到健康档案 await navigateToHealthProfile(page); // 验证基础信息 await page.evaluate(() => window.scrollTo(0, 0)); await page.waitForTimeout(500); const nameVerified = await page.locator(`text=${TEST_DATA.basicInfo.name}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('验证-基础信息姓名', nameVerified); const regionVerified = await page.locator(`text=${TEST_DATA.basicInfo.region}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('验证-基础信息地区', regionVerified); // 验证生活习惯 await page.evaluate(() => window.scrollTo(0, 300)); await page.waitForTimeout(500); const sleepTimeVerified = await page.locator(`text=${TEST_DATA.lifestyle.sleep_time}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('验证-生活习惯入睡时间', sleepTimeVerified); // 验证病史记录 await page.evaluate(() => window.scrollTo(0, 600)); await page.waitForTimeout(500); const diseaseVerified = await page.locator(`text=${TEST_DATA.medicalHistory.disease_name}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('验证-病史记录疾病名称', diseaseVerified); // 验证家族病史 const familyDiseaseVerified = await page.locator(`text=${TEST_DATA.familyHistory.disease_name}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('验证-家族病史疾病名称', familyDiseaseVerified); // 验证过敏记录 const allergenVerified = await page.locator(`text=${TEST_DATA.allergyRecord.allergen}`).first().isVisible({ timeout: 2000 }).catch(() => false); logTest('验证-过敏记录过敏原', allergenVerified); await page.screenshot({ path: 'tests/screenshots/hp-final-verification.png' }); return nameVerified || sleepTimeVerified; } async function runTests() { console.log('═══════════════════════════════════════════════════════════'); console.log(' 健康档案完整功能自动化测试'); console.log('═══════════════════════════════════════════════════════════'); console.log('\n测试范围:'); console.log(' - 基础信息: 9个字段(姓名、性别、出生日期、身高、体重、血型、职业、婚姻、地区)'); console.log(' - 生活习惯: 10个字段(入睡时间、起床时间、睡眠质量、三餐规律、饮食偏好、'); console.log(' 日饮水量、运动频率、运动类型、吸烟、饮酒)'); console.log(' - 病史记录: 添加(5个字段)+ 长按删除'); console.log(' - 家族病史: 3个字段(亲属关系、疾病名称、备注)'); console.log(' - 过敏记录: 4个字段(过敏类型、过敏原、严重程度、过敏反应描述)'); console.log(''); const browser = await chromium.launch({ headless: false }); const context = await browser.newContext({ viewport: { width: 1280, height: 800 } }); const page = await context.newPage(); // 监听错误 page.on('console', msg => { if (msg.type() === 'error') { console.log(' [Console Error]', msg.text()); } }); page.on('pageerror', error => { console.log(' [Page Error]', error.message); }); try { console.log('\n打开应用...'); await page.goto(APP_URL); await page.waitForTimeout(2000); // 执行测试步骤 const loginOk = await login(page); if (!loginOk) throw new Error('登录失败'); const navOk = await navigateToHealthProfile(page); if (!navOk) throw new Error('导航失败'); await testBasicInfoEdit(page); await testLifestyleEdit(page); await testMedicalHistoryAdd(page); await testFamilyHistoryAdd(page); await testAllergyRecordAdd(page); await testMedicalHistoryDelete(page); await verifyAllSavedData(page); } catch (error) { console.error('\n测试中断:', error.message); await page.screenshot({ path: 'tests/screenshots/hp-error.png' }); } finally { // 打印测试摘要 console.log('\n═══════════════════════════════════════════════════════════'); console.log(' 测试结果摘要'); console.log('═══════════════════════════════════════════════════════════'); console.log(`通过: ${testResults.passed} 失败: ${testResults.failed}`); console.log('───────────────────────────────────────────────────────────'); // 按类别分组显示 const categories = { '导航': [], '基础信息': [], '生活习惯': [], '病史记录': [], '家族病史': [], '过敏记录': [], '验证': [] }; for (const test of testResults.tests) { const icon = test.passed ? '✓' : '✗'; const line = `${icon} ${test.name}${test.detail ? ' - ' + test.detail : ''}`; if (test.name.includes('导航') || test.name.includes('登录')) { categories['导航'].push(line); } else if (test.name.includes('基础信息')) { categories['基础信息'].push(line); } else if (test.name.includes('生活习惯')) { categories['生活习惯'].push(line); } else if (test.name.includes('病史记录')) { categories['病史记录'].push(line); } else if (test.name.includes('家族病史')) { categories['家族病史'].push(line); } else if (test.name.includes('过敏记录')) { categories['过敏记录'].push(line); } else if (test.name.includes('验证')) { categories['验证'].push(line); } else { categories['导航'].push(line); } } for (const [category, tests] of Object.entries(categories)) { if (tests.length > 0) { console.log(`\n【${category}】`); for (const test of tests) { console.log(` ${test}`); } } } console.log('\n═══════════════════════════════════════════════════════════'); await page.waitForTimeout(2000); await browser.close(); // 返回退出码 process.exit(testResults.failed > 0 ? 1 : 0); } } // 运行测试 runTests();