| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- /**
- * ================================================================
- * 场景:健康营养师全流程 E2E 测试
- * ================================================================
- * 用户故事:作为健康营养师,我希望能家庭提供专业的营养指导服务,
- * 以便帮助家庭成员建立健康的饮食习惯。
- *
- * 流程步骤:
- * 1. 营养师登录系统,查看营养偏好
- * 2. 营养师更新家庭营养档案(口味/预算/烹饪能力)
- * 3. 营养师添加家庭成员的饮食记录
- * 4. 营养师查看饮食记录列表和分析报告
- * 5. 营养师创建和管理食谱
- * 6. 营养师查看应季食材
- * 7. 营养师绑定家庭,开始服务
- *
- * 关键里程碑:
- * - [Milestone-1] 营养师成功获取家庭营养档案
- * - [Milestone-2] 营养偏好更新成功
- * - [Milestone-3] 饮食记录添加成功
- * - [Milestone-4] 食谱创建成功
- * - [Milestone-5] 家庭绑定关系建立
- * ================================================================
- *
- * 运行:npx playwright test tests/e2e/nutritionist-flow.spec.js
- */
- const { test, expect } = require('@playwright/test');
- test.describe('【场景流程】健康营养师全流程', () => {
- // ===== 场景1:营养师登录并查看营养偏好 =====
- /**
- * 场景:营养师登录后,查看家庭的营养档案
- * 角色:健康营养师
- * 触发条件:营养师进入营养偏好页面
- */
- test('[场景1] 营养师查看家庭营养档案 - 期望返回完整营养信息', async ({ page }) => {
- // ========== Given:营养师已登录,进入营养偏好页面 ==========
- await page.goto('/#/pages/body/nutrition-profile');
- await page.waitForLoadState('networkidle');
- // 验证页面加载成功
- await expect(page.locator('.nutrition-profile-page')).toBeVisible({ timeout: 10000 });
- // ========== When:获取营养档案 ==========
- // 等待营养信息加载
- await page.waitForSelector('.profile-card, .nutrition-info', { timeout: 10000 });
- // ========== Then:Milestone-1 - 返回完整营养档案 ========
- const profileCard = page.locator('.profile-card, .nutrition-info');
- if (await profileCard.isVisible()) {
- // 验证包含关键信息
- const profileText = await profileCard.textContent();
- expect(profileText.length).toBeGreaterThan(0);
- }
- // 验证口味偏好存在
- const tastePrefs = page.locator('.taste-prefs, .family-taste');
- if (await tastePrefs.isVisible()) {
- const tasteText = await tastePrefs.textContent();
- expect(tasteText).toBeTruthy();
- }
- });
- // ===== 场景2:营养师更新营养偏好 =====
- /**
- * 场景:营养师根据家庭情况更新营养偏好设置
- * 角色:健康营养师
- * 触发条件:营养师编辑营养偏好
- */
- test('[场景2] 营养师更新营养偏好 - 期望保存成功', async ({ page }) => {
- // ========== Given:营养师进入营养偏好编辑页 ==========
- await page.goto('/#/pages/body/nutrition-profile');
- await page.waitForLoadState('networkidle');
- // ========== Step 1:点击编辑按钮 ==========
- const editBtn = page.locator('.edit-btn, .btn-edit, .edit-profile');
- if (await editBtn.isVisible()) {
- await editBtn.click();
- await page.waitForSelector('.edit-form, .profile-form', { timeout: 5000 });
- }
- // ========== Step 2:更新口味偏好 ==========
- const tasteInput = page.locator('input[name="familyTastePreferences"], .taste-input');
- if (await tasteInput.isVisible()) {
- await tasteInput.fill('清淡少盐,多蔬菜水果');
- }
- // ========== Step 3:更新烹饪能力 ==========
- const cookingSelect = page.locator('.cooking-ability-select, select[name="cookingAbility"]');
- if (await cookingSelect.isVisible()) {
- await cookingSelect.selectOption('intermediate');
- }
- // ========== Step 4:更新月度预算 ==========
- const budgetInput = page.locator('input[name="budgetMonthly"], .budget-input');
- if (await budgetInput.isVisible()) {
- await budgetInput.fill('3000');
- }
- // ========== When:保存修改 ==========
- await page.click('.save-btn, .confirm-btn, .submit-btn');
- // ========== Then:Milestone-2 - 保存成功 ========
- await page.waitForSelector('.success-tip, .save-success, .toast-success', { timeout: 10000 });
- const successTip = page.locator('.success-tip, .save-success, .toast-success');
- expect(await successTip.isVisible()).toBeTruthy();
- });
- // ===== 场景3:营养师添加饮食记录 =====
- /**
- * 场景:营养师为家庭成员记录一餐的饮食情况
- * 角色:健康营养师
- * 触发条件:营养师进入饮食记录页面并添加记录
- */
- test('[场景3] 营养师添加饮食记录 - 期望记录添加成功', async ({ page }) => {
- // ========== Given:营养师进入饮食记录页面 ==========
- await page.goto('/#/pages/body/meal-record');
- await page.waitForLoadState('networkidle');
- // 验证页面加载
- await expect(page.locator('.meal-record-page')).toBeVisible({ timeout: 10000 });
- // ========== Step 1:选择餐次类型 ==========
- const breakfastBtn = page.locator('.meal-type-btn:has-text("早餐"), .meal-type:has-text("早餐")');
- if (await breakfastBtn.isVisible()) {
- await breakfastBtn.click();
- }
- // ========== Step 2:输入食物名称 ==========
- const foodInput = page.locator('input[name="foodName"], .food-name-input');
- if (await foodInput.isVisible()) {
- await foodInput.fill('全麦面包 + 牛奶 + 鸡蛋');
- }
- // ========== Step 3:输入食物份量 ==========
- const amountInput = page.locator('input[name="foodAmount"], .amount-input');
- if (await amountInput.isVisible()) {
- await amountInput.fill('100g + 250ml + 1个');
- }
- // ========== Step 4:输入营养信息 ==========
- const caloriesInput = page.locator('input[name="calories"], .calories-input');
- if (await caloriesInput.isVisible()) {
- await caloriesInput.fill('350');
- }
- const proteinInput = page.locator('input[name="protein"], .protein-input');
- if (await proteinInput.isVisible()) {
- await proteinInput.fill('15');
- }
- // ========== When:提交记录 ==========
- await page.click('.submit-btn, .add-btn, .save-btn');
- // ========== Then:Milestone-3 - 记录添加成功 ========
- await page.waitForSelector('.success-tip, .record-added, .toast-success', { timeout: 10000 });
- const successTip = page.locator('.success-tip, .record-added, .toast-success');
- expect(await successTip.isVisible()).toBeTruthy();
- });
- // ===== 场景4:营养师查看饮食记录列表 =====
- /**
- * 场景:营养师查看家庭成员最近的饮食记录
- * 角色:健康营养师
- * 触发条件:营养师需要分析家庭饮食结构
- */
- test('[场景4] 营养师查看饮食记录列表 - 期望返回记录列表', async ({ page }) => {
- // ========== Given:营养师进入饮食记录页面 ==========
- await page.goto('/#/pages/body/meal-record');
- await page.waitForLoadState('networkidle');
- // ========== When:查看记录列表 ==========
- await page.waitForSelector('.record-list, .meal-list, .diet-records', { timeout: 10000 });
- // ========== Then:返回饮食记录列表 ========
- const recordItems = page.locator('.record-item, .meal-item, .diet-record');
- const count = await recordItems.count();
- expect(count).toBeGreaterThanOrEqual(0);
- // 验证记录包含日期信息
- if (count > 0) {
- const firstRecord = recordItems.first();
- expect(await firstRecord.isVisible()).toBeTruthy();
- }
- });
- // ===== 场景5:营养师分析营养摄入 =====
- /**
- * 场景:营养师分析家庭成员的营养摄入情况
- * 角色:健康营养师
- * 触发条件:需要生成营养分析报告
- */
- test('[场景5] 营养师分析营养摄入 - 期望返回分析报告', async ({ page }) => {
- // ========== Given:营养师进入营养分析页面 ==========
- await page.goto('/#/pages/body/nutrition-analysis');
- await page.waitForLoadState('networkidle');
- // ========== When:生成分析报告 ==========
- const analyzeBtn = page.locator('.analyze-btn, .generate-report-btn');
- if (await analyzeBtn.isVisible()) {
- await analyzeBtn.click();
- }
- // ========== Then:返回分析结果 ========
- await page.waitForSelector('.analysis-result, .nutrition-report, .report-card', { timeout: 15000 });
- const reportCard = page.locator('.analysis-result, .nutrition-report, .report-card');
- expect(await reportCard.isVisible()).toBeTruthy();
- // 验证包含营养素分析
- const nutrients = page.locator('.nutrient-item, .macro-item');
- if (await nutrients.first().isVisible()) {
- const nutrientCount = await nutrients.count();
- expect(nutrientCount).toBeGreaterThan(0);
- }
- });
- // ===== 场景6:营养师创建食谱 =====
- /**
- * 场景:营养师为家庭创建健康食谱
- * 角色:健康营养师
- * 触发条件:营养师进入食谱管理页面
- */
- test('[场景6] 营养师创建食谱 - 期望食谱创建成功', async ({ page }) => {
- // ========== Given:营养师进入食谱管理页面 ==========
- await page.goto('/#/pages/body/recipe-recommend');
- await page.waitForLoadState('networkidle');
- // ========== Step 1:点击创建食谱 ==========
- const createBtn = page.locator('.create-btn, .add-recipe-btn, .create-recipe');
- if (await createBtn.isVisible()) {
- await createBtn.click();
- } else {
- // 直接进入创建页
- await page.goto('/#/pages/body/recipe-create');
- await page.waitForLoadState('networkidle');
- }
- await page.waitForSelector('.recipe-form, .create-form', { timeout: 5000 });
- // ========== Step 2:填写食谱信息 ==========
- const nameInput = page.locator('input[name="name"], .recipe-name-input');
- if (await nameInput.isVisible()) {
- await nameInput.fill('营养早餐套餐_' + Date.now());
- }
- const ingredientsInput = page.locator('textarea[name="ingredients"], .ingredients-input');
- if (await ingredientsInput.isVisible()) {
- await ingredientsInput.fill('全麦面包2片、牛奶200ml、鸡蛋1个、水果100g');
- }
- const methodInput = page.locator('textarea[name="method"], .method-input');
- if (await methodInput.isVisible()) {
- await methodInput.fill('1. 鸡蛋煮熟切片\n2. 面包烤至微黄\n3. 倒入牛奶\n4. 摆盘水果');
- }
- // ========== Step 3:填写营养信息 ==========
- const caloriesInput = page.locator('input[name="calories"], .calories-input');
- if (await caloriesInput.isVisible()) {
- await caloriesInput.fill('420');
- }
- const proteinInput = page.locator('input[name="protein"], .protein-input');
- if (await proteinInput.isVisible()) {
- await proteinInput.fill('18');
- }
- // ========== When:提交食谱 ==========
- await page.click('.submit-btn, .save-btn, .publish-btn');
- // ========== Then:Milestone-4 - 食谱创建成功 ========
- await page.waitForSelector('.success-tip, .recipe-created, .toast-success', { timeout: 10000 });
- const successTip = page.locator('.success-tip, .recipe-created, .toast-success');
- expect(await successTip.isVisible()).toBeTruthy();
- });
- // ===== 场景7:营养师管理食谱库 =====
- /**
- * 场景:营养师编辑或删除已有食谱
- * 角色:健康营养师
- * 触发条件:食谱需要更新或清理
- */
- test('[场景7] 营养师管理食谱库 - 期望食谱更新/删除成功', async ({ page }) => {
- // ========== Given:营养师进入食谱列表页面 ==========
- await page.goto('/#/pages/body/recipe-list');
- await page.waitForLoadState('networkidle');
- // ========== Step 1:找到要编辑的食谱 ==========
- await page.waitForSelector('.recipe-item, .recipe-card', { timeout: 10000 });
- const recipeItem = page.locator('.recipe-item, .recipe-card').first();
- if (await recipeItem.isVisible()) {
- // 点击编辑
- const editBtn = recipeItem.locator('.edit-btn, .btn-edit');
- if (await editBtn.isVisible()) {
- await editBtn.click();
- } else {
- await recipeItem.click();
- }
- await page.waitForSelector('.edit-form, .recipe-form', { timeout: 5000 });
- // ========== Step 2:修改食谱 ==========
- const nameInput = page.locator('input[name="name"], .recipe-name-input');
- if (await nameInput.isVisible()) {
- const newName = '已更新食谱_' + Date.now();
- await nameInput.fill(newName);
- }
- // ========== When:保存修改 ==========
- await page.click('.save-btn, .confirm-btn');
- await page.waitForSelector('.success-tip, .update-success', { timeout: 10000 });
- const successTip = page.locator('.success-tip, .update-success');
- expect(await successTip.isVisible()).toBeTruthy();
- }
- });
- // ===== 场景8:营养师查看应季食材 =====
- /**
- * 场景:营养师查看当前季节的推荐食材
- * 角色:健康营养师
- * 触发条件:需要制定应季菜单
- */
- test('[场景8] 营养师查看应季食材 - 期望返回应季食材列表', async ({ page }) => {
- // ========== Given:营养师进入应季食材页面 ==========
- await page.goto('/#/pages/body/seasonal-foods');
- await page.waitForLoadState('networkidle');
- // ========== When:查看应季食材 ==========
- await page.waitForSelector('.seasonal-list, .food-list, .seasonal-foods', { timeout: 10000 });
- // ========== Then:返回应季食材列表 ========
- const foodItems = page.locator('.food-item, .seasonal-item');
- const count = await foodItems.count();
- expect(count).toBeGreaterThanOrEqual(0);
- if (count > 0) {
- // 验证包含食材名称
- const firstItem = foodItems.first();
- expect(await firstItem.isVisible()).toBeTruthy();
- // 验证包含季节标识
- const seasonTag = page.locator('.season-tag, .month-tag');
- if (await seasonTag.first().isVisible()) {
- const tagText = await seasonTag.first().textContent();
- expect(tagText).toBeTruthy();
- }
- }
- });
- // ===== 场景9:营养师获取饮食推荐 =====
- /**
- * 场景:营养师根据家庭偏好获取饮食推荐
- * 角色:健康营养师
- * 触发条件:需要为家庭推荐合适的三餐
- */
- test('[场景9] 营养师获取饮食推荐 - 期望返回个性化推荐', async ({ page }) => {
- // ========== Given:营养师进入饮食推荐页面 ==========
- await page.goto('/#/pages/meal/recommend');
- await page.waitForLoadState('networkidle');
- // ========== When:获取推荐 ==========
- await page.waitForSelector('.recommend-list, .meal-recommend, .recommend-cards', { timeout: 10000 });
- // ========== Then:返回推荐列表 ========
- const recommendCards = page.locator('.recommend-card, .meal-item, .recommend-item');
- const count = await recommendCards.count();
- expect(count).toBeGreaterThan(0);
- // 验证推荐包含营养信息
- const firstCard = recommendCards.first();
- expect(await firstCard.isVisible()).toBeTruthy();
- const nutritionInfo = page.locator('.nutrition-tag, .calories-tag');
- if (await nutritionInfo.first().isVisible()) {
- const tagText = await nutritionInfo.first().textContent();
- expect(tagText).toBeTruthy();
- }
- });
- // ===== 场景10:营养师绑定家庭 =====
- /**
- * 场景:营养师接受家庭邀请,建立服务关系
- * 角色:健康营养师
- * 触发条件:家长向营养师发送邀请
- */
- test('[场景10] 营养师绑定家庭 - 期望绑定成功', async ({ page }) => {
- // ========== Given:营养师收到绑定邀请 ==========
- await page.goto('/#/pages/invite/join');
- await page.waitForLoadState('networkidle');
- // ========== When:接受绑定邀请 ==========
- const acceptBtn = page.locator('.accept-btn, .confirm-bind-btn, .accept-invite');
- if (await acceptBtn.isVisible()) {
- await acceptBtn.click();
- }
- // 等待确认弹窗
- const confirmDialog = page.locator('.van-dialog, .confirm-dialog');
- if (await confirmDialog.isVisible()) {
- const confirmBtn = page.locator('.van-dialog__confirm, .dialog-confirm-btn');
- if (await confirmBtn.isVisible()) {
- await confirmBtn.click();
- }
- }
- // ========== Then:Milestone-5 - 绑定成功 ========
- await page.waitForSelector('.bind-success, .success-tip, .toast-success', { timeout: 10000 });
- // 验证绑定关系建立
- const bindIndicator = page.locator('.bind-status, .relation-status');
- if (await bindIndicator.isVisible()) {
- const statusText = await bindIndicator.textContent();
- expect(statusText).toMatch(/已绑定|服务中|active/i);
- }
- });
- });
|