/** * ================================================================ * 场景:连续签到奖励全流程 E2E 测试 * ================================================================ * 用户故事:作为孩子,我希望每天完成健康打卡, * 保持连续签到记录以获得更多奖励。 * * 流程步骤: * 1. 孩子每天完成健康打卡(饮食、运动、睡眠、饮水、心情) * 2. 系统记录打卡并更新连续签到天数 * 3. 达到特定天数阈值时发放奖励(积分或能量) * 4. 第二天继续打卡以保持连续 * 5. 如果中断,系统重置连续天数为0 * * 关键里程碑: * - [Milestone-1] 打卡记录创建成功 * - [Milestone-2] 连续签到天数正确累计 * - [Milestone-3] 达到奖励阈值,奖励发放 * - [Milestone-4] 中断后重置连续天数 * ================================================================ * * 运行:npx playwright test tests/e2e/daily-checkin-streak.spec.js */ const { test, expect } = require('@playwright/test'); test.describe('【场景流程】连续签到奖励全流程', () => { // ===== 场景1:每日健康打卡 ===== /** * 场景:孩子完成每日健康打卡 * 角色:孩子 * 触发条件:每天进入打卡页面填写健康数据 */ test('[场景1] 孩子完成每日打卡 - 期望打卡成功', async ({ page }) => { // ========== Given:进入健康打卡页面 ========== await page.goto('/#/pages/health/checkin'); await page.waitForLoadState('networkidle'); // ========== Step 1:选择孩子(如需要)========== const childPicker = page.locator('.child-picker'); if (await childPicker.isVisible()) { await childPicker.click(); await page.waitForSelector('.child-option', { timeout: 5000 }); await page.click('.child-option:first-child'); } // ========== Step 2:填写饮食分数 ========== const dietScore = page.locator('.diet-score-input, input[name="dietScore"]'); if (await dietScore.isVisible()) { await dietScore.fill('85'); } else { // 使用滑动条或评分组件 const dietSlider = page.locator('.diet-score .slider'); if (await dietSlider.isVisible()) { await dietSlider.evaluate(el => el.value = 85); } } // ========== Step 3:填写运动分数 ========== const exerciseScore = page.locator('.exercise-score-input, input[name="exerciseScore"]'); if (await exerciseScore.isVisible()) { await exerciseScore.fill('90'); } // ========== Step 4:填写睡眠分数 ========== const sleepScore = page.locator('.sleep-score-input, input[name="sleepScore"]'); if (await sleepScore.isVisible()) { await sleepScore.fill('80'); } // ========== Step 5:填写饮水量 ========== const waterIntake = page.locator('.water-intake-input, input[name="waterIntake"]'); if (await waterIntake.isVisible()) { await waterIntake.fill('2000'); } // ========== Step 6:填写心情分数 ========== const moodScore = page.locator('.mood-score-input, input[name="moodScore"]'); if (await moodScore.isVisible()) { await moodScore.fill('88'); } // ========== When:提交打卡 ========== await page.click('.submit-btn, .checkin-btn, .save-btn'); // ========== Then:Milestone-1 - 打卡成功 ======== await page.waitForSelector('.success-tip, .checkin-success, .submitted', { timeout: 10000 }); // 验证打卡成功提示 const successTip = page.locator('.success-tip, .checkin-success'); expect(await successTip.isVisible()).toBeTruthy(); }); // ===== 场景2:查看打卡列表 ===== /** * 场景:查看历史打卡记录 * 角色:孩子或家长 * 触发条件:进入打卡历史页面 */ test('[场景2] 查看打卡记录列表 - 期望返回历史记录', async ({ page }) => { // ========== Given:进入打卡记录页面 ========== await page.goto('/#/pages/health/checkin-list'); await page.waitForLoadState('networkidle'); // ========== Step 1:选择孩子 ========== const childPicker = page.locator('.child-picker'); if (await childPicker.isVisible()) { await childPicker.click(); await page.waitForSelector('.child-option', { timeout: 5000 }); await page.click('.child-option:first-child'); } // ========== When:查看打卡列表 ========== await page.waitForSelector('.checkin-item, .record-item', { timeout: 10000 }); // ========== Then:返回历史记录 ======== const recordItems = page.locator('.checkin-item, .record-item'); const count = await recordItems.count(); expect(count).toBeGreaterThan(0); // 验证日期显示 const firstRecord = recordItems.first(); const recordDate = firstRecord.locator('.record-date, .date-text'); expect(await recordDate.isVisible()).toBeTruthy(); }); // ===== 场景3:获取连续签到进度 ===== /** * 场景:孩子查看自己的连续签到进度 * 角色:孩子 * 触发条件:查看签到激励页面 */ test('[场景3] 获取连续签到进度 - 期望返回当前连续天数和奖励状态', async ({ page }) => { // ========== Given:进入签到进度页面 ========== await page.goto('/#/pages/streak/progress'); await page.waitForLoadState('networkidle'); // ========== When:页面加载完成 ========== await page.waitForSelector('.streak-progress, .progress-container', { timeout: 10000 }); // ========== Then:Milestone-2 - 返回连续天数等信息 ======== // 验证当前连续天数 const currentStreak = page.locator('.current-streak, .streak-count'); expect(await currentStreak.isVisible()).toBeTruthy(); const streakText = await currentStreak.textContent(); expect(streakText).toMatch(/\d+/); // 验证最长连续天数 const longestStreak = page.locator('.longest-streak, .best-streak'); if (await longestStreak.isVisible()) { const bestText = await longestStreak.textContent(); expect(bestText).toMatch(/\d+/); } // 验证今日打卡状态 const todayChecked = page.locator('.today-checked, .today-status'); if (await todayChecked.isVisible()) { const statusText = await todayChecked.textContent(); expect(statusText).toMatch(/已打卡|未打卡/); } }); // ===== 场景4:达到奖励阈值发放奖励 ===== /** * 场景:孩子连续签到达到7天阈值,系统发放奖励 * 角色:系统(自动触发) * 触发条件:连续签到天数达到特定阈值 */ test('[场景4] 连续7天打卡达成 - 期望奖励发放', async ({ page }) => { // ========== Given:已达到7天连续签到 ========== await page.goto('/#/pages/streak/reward'); await page.waitForLoadState('networkidle'); // ========== When:查看奖励状态 ========== await page.waitForSelector('.reward-info, .reward-container', { timeout: 10000 }); // ========== Then:Milestone-3 - 奖励发放或可领取 ======== // 检查是否有可领取奖励 const rewardBadge = page.locator('.reward-badge, .pending-reward'); const rewardClaimBtn = page.locator('.claim-reward-btn, .get-reward-btn'); if (await rewardBadge.isVisible() || await rewardClaimBtn.isVisible()) { // 有待领取奖励 expect(true).toBeTruthy(); // 可以点击领取(如果是待领取状态) if (await rewardClaimBtn.isVisible()) { await rewardClaimBtn.click(); await page.waitForSelector('.reward-claimed, .claim-success', { timeout: 10000 }); } } else { // 奖励已发放,检查发放记录 const rewardRecord = page.locator('.reward-record, .reward-item'); expect(await rewardRecord.isVisible()).toBeTruthy(); } }); /** * 场景:孩子连续签到达到30天,获得大奖励 * 角色:系统 * 触发条件:连续签到达到30天 */ test('[场景4b] 连续30天打卡达成 - 期望获得大额奖励', async ({ page }) => { // ========== Given:已达到30天连续签到 ========== await page.goto('/#/pages/streak/reward-detail?days=30'); await page.waitForLoadState('networkidle'); // ========== When:查看30天奖励 ========== await page.waitForSelector('.big-reward, .reward-detail', { timeout: 10000 }); // ========== Then:奖励发放 ======== // 验证大额奖励显示 const bigReward = page.locator('.big-reward, .reward-amount'); expect(await bigReward.isVisible()).toBeTruthy(); // 验证奖励领取状态 const rewardStatus = page.locator('.reward-status, .status'); if (await rewardStatus.isVisible()) { const statusText = await rewardStatus.textContent(); expect(statusText).toMatch(/已发放|已领取|待领取/); } }); // ===== 场景5:打卡中断重置 ===== /** * 场景:孩子忘记打卡,连续签到中断 * 角色:系统(定时任务触发) * 触发条件:凌晨检查发现昨天未打卡 */ test('[场景5] 打卡中断 - 期望连续天数重置', async ({ page }) => { // ========== Given:进入签到进度页面 ========== await page.goto('/#/pages/streak/progress'); await page.waitForLoadState('networkidle'); // ========== When:系统重置中断的签到 ========== // 等待系统检查并更新状态 await page.waitForTimeout(2000); // 查看当前连续天数显示 const currentStreak = page.locator('.current-streak, .streak-count'); if (await currentStreak.isVisible()) { const streakText = await currentStreak.textContent(); const streakValue = parseInt(streakText.replace(/\D/g, '')) || 0; // 如果昨天未打卡,连续天数应该被重置 // 注意:这里需要结合实际情况验证 // 可能的显示:当前连续天数为1或0(取决于系统设计) expect(streakValue).toBeLessThanOrEqual(1); } // ========== Then:Milestone-4 - 连续天数重置 ======== // 验证重置提示(如有) const resetTip = page.locator('.streak-reset, .reset-notice'); // 可能显示"已重置"或"重新开始"等提示 }); // ===== 场景6:更新打卡记录 ===== /** * 场景:孩子补充或修改今天的打卡数据 * 角色:孩子 * 触发条件:发现打卡数据填错需要修改 */ test('[场景6] 更新打卡记录 - 期望更新成功', async ({ page }) => { // ========== Given:进入打卡详情 ========== await page.goto('/#/pages/health/checkin-detail/1'); await page.waitForLoadState('networkidle'); // ========== When:修改打卡数据 ========== const editBtn = page.locator('.edit-btn, .modify-btn'); if (await editBtn.isVisible()) { await editBtn.click(); await page.waitForSelector('.edit-form, .checkin-form', { timeout: 5000 }); // 修改饮食分数 const dietScore = page.locator('input[name="dietScore"], .diet-score-input'); if (await dietScore.isVisible()) { await dietScore.clear(); await dietScore.fill('90'); } // 保存修改 await page.click('.save-btn, .submit-btn'); // ========== Then:更新成功 ======== await page.waitForSelector('.success-tip, .update-success', { timeout: 10000 }); // 验证修改后的数据显示 const updatedScore = page.locator('.diet-score-value, .updated-diet'); if (await updatedScore.isVisible()) { const scoreText = await updatedScore.textContent(); expect(scoreText).toMatch(/90/); } } }); // ===== 场景7:删除打卡记录 ===== /** * 场景:孩子删除一条错误的打卡记录 * 角色:孩子 * 触发条件:发现打卡记录有误需要删除 */ test('[场景7] 删除打卡记录 - 期望删除成功', async ({ page }) => { // ========== Given:进入打卡列表 ========== await page.goto('/#/pages/health/checkin-list'); await page.waitForLoadState('networkidle'); // 获取删除前的记录数 await page.waitForSelector('.checkin-item', { timeout: 10000 }); const beforeCount = await page.locator('.checkin-item').count(); // ========== Step 1:找到要删除的记录 ========== const lastItem = page.locator('.checkin-item').last(); const deleteBtn = lastItem.locator('.delete-btn, .btn-delete'); if (await deleteBtn.isVisible()) { await deleteBtn.click(); // 确认删除 await page.waitForSelector('.confirm-dialog, .van-dialog', { timeout: 5000 }); await page.click('.confirm-dialog .van-button--primary, .confirm-delete'); // ========== Then:删除成功 ======== await page.waitForTimeout(1000); // 验证记录数减少 const afterCount = await page.locator('.checkin-item').count(); expect(afterCount).toBeLessThan(beforeCount); } }); // ===== 场景8:完整连续签到流程(端到端)===== test('[场景8] 完整连续签到流程 - 期望全流程成功', async ({ page }) => { // ========== Step 1:查看签到进度 ========== await page.goto('/#/pages/streak/progress'); await page.waitForLoadState('networkidle'); await page.waitForSelector('.streak-progress', { timeout: 10000 }); // 记录当前连续天数 const currentStreakEl = page.locator('.current-streak'); let beforeStreak = 0; if (await currentStreakEl.isVisible()) { beforeStreak = parseInt(await currentStreakEl.textContent()) || 0; } // ========== Step 2:进行每日打卡 ========== await page.goto('/#/pages/health/checkin'); await page.waitForLoadState('networkidle'); // 选择孩子 const childPicker = page.locator('.child-picker'); if (await childPicker.isVisible()) { await childPicker.click(); await page.waitForSelector('.child-option', { timeout: 5000 }); await page.click('.child-option:first-child'); } // 填写各项分数 const inputs = ['diet', 'exercise', 'sleep', 'mood']; for (const type of inputs) { const input = page.locator(`.${type}-score-input, input[name="${type}Score"]`); if (await input.isVisible()) { await input.fill('85'); } } // 填写饮水量 const waterInput = page.locator('.water-intake-input, input[name="waterIntake"]'); if (await waterInput.isVisible()) { await waterInput.fill('2000'); } await page.click('.submit-btn'); await page.waitForSelector('.success-tip, .checkin-success', { timeout: 10000 }); // ========== Step 3:再次查看签到进度 ========== await page.goto('/#/pages/streak/progress'); await page.waitForLoadState('networkidle'); // 验证连续天数增加 await page.waitForSelector('.current-streak', { timeout: 10000 }); const afterStreakEl = page.locator('.current-streak'); if (await afterStreakEl.isVisible()) { const afterStreak = parseInt(await afterStreakEl.textContent()) || 0; // 连续天数应该增加(除非已经是最大值或今日已打过卡) expect(afterStreak).toBeGreaterThanOrEqual(beforeStreak); } // ========== Step 4:查看打卡记录列表 ========== await page.goto('/#/pages/health/checkin-list'); await page.waitForLoadState('networkidle'); await page.waitForSelector('.checkin-item', { timeout: 10000 }); // 验证今日打卡记录存在 const todayRecord = page.locator('.checkin-item').first(); expect(await todayRecord.isVisible()).toBeTruthy(); }); });