Просмотр исходного кода

test(e2e): Playwright E2E 端到端流程测试套件 (8个核心业务流程)

Xiaogang Liao 4 месяцев назад
Родитель
Сommit
71f2bd3007

+ 366 - 0
tests/e2e/assessment-order-flow.spec.js

@@ -0,0 +1,366 @@
+/**
+ * ================================================================
+ * 场景:测评订单全流程 E2E 测试
+ * ================================================================
+ * 用户故事:作为家长,我希望为孩子购买专业测评服务,
+ *           以便了解孩子的认知发展状况并获得成长建议。
+ *
+ * 流程步骤:
+ *   1. 家长选择规划师和测评套餐,进入测评申请页面
+ *   2. 家长填写孩子信息,提交测评申请
+ *   3. 家长完成支付,订单状态变更为"已支付"
+ *   4. 系统自动确认预约,预约状态变更为"待测评"
+ *   5. 规划师确认预约,查看孩子的基本信息(快照)
+ *   6. 规划师完成测评,录入认知评估结果
+ *   7. 家长/孩子查看测评报告
+ *
+ * 关键里程碑:
+ *   - [Milestone-1] 订单创建成功,获得订单号
+ *   - [Milestone-2] 支付成功,预约自动创建
+ *   - [Milestone-3] 规划师确认预约
+ *   - [Milestone-4] 测评结果录入完成
+ *   - [Milestone-5] 家长可查询到孩子的最新报告
+ * ================================================================
+ *
+ * 运行:npx playwright test tests/e2e/assessment-order-flow.spec.js
+ */
+
+const { test, expect } = require('@playwright/test');
+
+test.describe('【场景流程】测评订单全流程', () => {
+
+  // ===== 场景1:家长选购测评套餐 =====
+
+  /**
+   * 场景:家长选择规划师并购买测评套餐
+   * 角色:家长
+   * 触发条件:家长选择了具体的规划师和测评套餐
+   */
+  test('[场景1] 家长购买测评套餐并完成支付 - 期望全流程成功', async ({ page }) => {
+    // ========== Given:家长进入测评申请页面 ==========
+    await page.goto('/#/pages/assessment/apply');
+    await page.waitForLoadState('networkidle');
+
+    // 验证页面加载成功
+    await expect(page.locator('.assessment-apply-page')).toBeVisible({ timeout: 10000 });
+
+    // ========== Step 1:选择孩子 ==========
+    const childSelector = page.locator('.child-picker');
+    if (await childSelector.isVisible()) {
+      await childSelector.click();
+      await page.waitForSelector('.child-option', { timeout: 5000 });
+      await page.click('.child-option:first-child');
+    }
+
+    // ========== Step 2:选择规划师(可选)==========
+    const guideSelector = page.locator('.guide-picker');
+    if (await guideSelector.isVisible()) {
+      await guideSelector.click();
+      await page.waitForSelector('.guide-option', { timeout: 5000 });
+      await page.click('.guide-option:first-child');
+    }
+
+    // ========== Step 3:选择测评套餐 ==========
+    await page.click('.package-option:first-child');
+
+    // ========== When:提交申请 ==========
+    await page.click('.submit-btn');
+
+    // ========== Then:Milestone-1 - 订单创建成功 ========
+    // 等待订单确认弹窗或跳转
+    await page.waitForSelector('.order-confirm-modal, .order-success-tip', { timeout: 10000 });
+
+    // 验证订单号生成(订单号应该在页面某处显示)
+    const orderSuccessTip = page.locator('.order-success-tip, .order-no');
+    if (await orderSuccessTip.isVisible()) {
+      const orderText = await orderSuccessTip.textContent();
+      expect(orderText).toMatch(/订单|ASR|成功/);
+    }
+
+    // ========== Step 4:点击去支付 ==========
+    await page.click('.go-pay-btn');
+
+    // ========== Then:Milestone-2 - 支付成功 ========
+    // 选择支付方式
+    const wechatPayOption = page.locator('.pay-method:has-text("微信支付")');
+    if (await wechatPayOption.isVisible()) {
+      await wechatPayOption.click();
+    }
+
+    // 确认支付
+    await page.click('.confirm-pay-btn');
+
+    // 等待支付成功提示
+    await page.waitForSelector('.pay-success-tip, .payment-success', { timeout: 15000 });
+
+    // 验证支付状态
+    const successTip = page.locator('.pay-success-tip, .payment-success');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景2:自动分配规划师流程 =====
+
+  /**
+   * 场景:家长未指定规划师,系统自动分配家庭绑定的规划师
+   * 角色:家长
+   * 触发条件:家长未指定 guideId,但家庭已绑定规划师
+   */
+  test('[场景2] 未指定规划师时系统自动分配 - 期望使用家庭绑定的规划师', async ({ page }) => {
+    // ========== Given:家庭已绑定规划师 ==========
+    await page.goto('/#/pages/assessment/apply');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择孩子 ==========
+    const childSelector = page.locator('.child-picker');
+    if (await childSelector.isVisible()) {
+      await childSelector.click();
+      await page.waitForSelector('.child-option', { timeout: 5000 });
+      await page.click('.child-option:first-child');
+    }
+
+    // ========== Step 2:直接选择套餐(不选规划师)==========
+    await page.click('.package-option:first-child');
+
+    // ========== When:提交申请 ==========
+    await page.click('.submit-btn');
+
+    // ========== Then:系统自动使用家庭绑定的规划师 ========
+    // 应该显示自动分配提示或规划师名称
+    await page.waitForSelector('.auto-assign-tip, .guide-name', { timeout: 10000 });
+
+    const guideName = page.locator('.guide-name, .guide-info');
+    if (await guideName.isVisible()) {
+      const nameText = await guideName.textContent();
+      expect(nameText.length).toBeGreaterThan(0);
+    }
+  });
+
+  // ===== 场景3:规划师录入测评结果 =====
+
+  /**
+   * 场景:规划师完成测评后,录入孩子的认知评估结果
+   * 角色:成长规划师
+   * 触发条件:预约已完成,规划师完成了一对一测评
+   */
+  test('[场景3] 规划师录入测评结果 - 期望结果保存并可查询', async ({ page }) => {
+    // ========== Given:规划师进入测评结果录入页面 ==========
+    await page.goto('/#/pages/guide/assessment/record');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择要录入的预约 ==========
+    await page.waitForSelector('.appointment-item', { timeout: 10000 });
+    await page.click('.appointment-item:first-child');
+
+    // ========== Step 2:填写测评结果 ==========
+    // 输入各项分数
+    const scoreInputs = page.locator('.score-input');
+    const scoreCount = await scoreInputs.count();
+
+    for (let i = 0; i < scoreCount; i++) {
+      await scoreInputs.nth(i).fill(String(Math.floor(Math.random() * 20) + 80));
+    }
+
+    // 输入综合评语
+    const reportTextarea = page.locator('.analysis-report textarea, .report-textarea');
+    if (await reportTextarea.isVisible()) {
+      await reportTextarea.fill('孩子整体认知发展良好,在注意力和逻辑思维方面表现突出...');
+    }
+
+    // ========== When:提交测评结果 ==========
+    await page.click('.submit-result-btn');
+
+    // ========== Then:Milestone-4 - 测评结果保存成功 ========
+    await page.waitForSelector('.submit-success-tip, .result-saved', { timeout: 10000 });
+
+    const successTip = page.locator('.submit-success-tip, .result-saved');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景4:家长查询孩子测评报告 =====
+
+  /**
+   * 场景:家长为孩子购买测评后,查看孩子的最新认知评估报告
+   * 角色:家长
+   * 触发条件:规划师已完成测评并录入结果
+   */
+  test('[场景4] 家长查询孩子最新报告 - 期望返回完整报告内容', async ({ page }) => {
+    // ========== Given:进入测评报告页面 ==========
+    await page.goto('/#/pages/assessment/results');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择孩子 ==========
+    const childSelector = page.locator('.child-picker');
+    if (await childSelector.isVisible()) {
+      await childSelector.click();
+      await page.waitForSelector('.child-option', { timeout: 5000 });
+      await page.click('.child-option:first-child');
+    }
+
+    // ========== When:查看报告 ==========
+    await page.click('.view-report-btn');
+
+    // ========== Then:Milestone-5 - 返回完整报告内容 ========
+    await page.waitForSelector('.report-detail, .result-card', { timeout: 10000 });
+
+    // 验证报告包含必要内容
+    const reportDetail = page.locator('.report-detail, .result-content');
+    expect(await reportDetail.isVisible()).toBeTruthy();
+
+    // 验证分数显示
+    const scoreDisplay = page.locator('.overall-score, .score-value');
+    if (await scoreDisplay.isVisible()) {
+      const scoreText = await scoreDisplay.textContent();
+      expect(scoreText).toMatch(/\d+/);
+    }
+  });
+
+  // ===== 场景5:取消未支付订单 =====
+
+  /**
+   * 场景:家长创建订单后决定取消,选择关闭交易
+   * 角色:家长
+   * 触发条件:订单状态为"待支付"
+   */
+  test('[场景5] 家长取消未支付订单 - 期望订单关闭成功', async ({ page }) => {
+    // ========== Given:进入我的订单页面 ==========
+    await page.goto('/#/pages/assessment/orders');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到待支付订单 ==========
+    await page.waitForSelector('.order-item', { timeout: 10000 });
+    const pendingOrder = page.locator('.order-item:has-text("待支付"), .order-item:has-text("待付款")').first();
+    await pendingOrder.click();
+
+    // ========== When:点击取消订单 ==========
+    await page.click('.cancel-order-btn');
+
+    // 确认取消
+    const confirmBtn = page.locator('.confirm-cancel-btn, .van-dialog__confirm');
+    if (await confirmBtn.isVisible()) {
+      await confirmBtn.click();
+    }
+
+    // ========== Then:订单成功取消 ========
+    await page.waitForSelector('.cancel-success-tip, .order-closed', { timeout: 10000 });
+
+    // 订单状态应变为"已取消"
+    const orderStatus = page.locator('.order-status');
+    if (await orderStatus.isVisible()) {
+      const statusText = await orderStatus.textContent();
+      expect(statusText).toMatch(/已取消|已关闭/);
+    }
+  });
+
+  // ===== 场景6:已支付订单退款 =====
+
+  /**
+   * 场景:家长支付后因故需要退款
+   * 角色:家长(发起退款)→ 系统处理
+   * 触发条件:订单状态为"已支付"
+   */
+  test('[场景6] 已支付订单申请退款 - 期望退款成功', async ({ page }) => {
+    // ========== Given:进入订单详情 ==========
+    await page.goto('/#/pages/assessment/order-detail?orderNo=ASR20260628001');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:申请退款 ==========
+    await page.click('.apply-refund-btn');
+
+    // 填写退款原因
+    const reasonInput = page.locator('.refund-reason textarea, .reason-input');
+    if (await reasonInput.isVisible()) {
+      await reasonInput.fill('因计划变更,需要退款');
+    }
+
+    // 确认提交
+    await page.click('.submit-refund-btn');
+
+    // ========== Then:退款申请提交成功 ========
+    await page.waitForSelector('.refund-submitted, .refund-success', { timeout: 10000 });
+
+    const refundTip = page.locator('.refund-submitted, .refund-success');
+    expect(await refundTip.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景7:完整测评流程(端到端)=====
+
+  test('[场景7] 完整测评生命周期 - 期望全流程成功', async ({ page }) => {
+    // ========== Step 1:申请测评 ==========
+    await page.goto('/#/pages/assessment/apply');
+    await page.waitForLoadState('networkidle');
+
+    // 选择孩子
+    const childSelector = page.locator('.child-picker');
+    if (await childSelector.isVisible()) {
+      await childSelector.click();
+      await page.waitForSelector('.child-option', { timeout: 5000 });
+      await page.click('.child-option:first-child');
+    }
+
+    // 选择套餐并提交
+    await page.click('.package-option:first-child');
+    await page.click('.submit-btn');
+
+    // 等待订单创建
+    await page.waitForSelector('.order-no, .go-pay-btn', { timeout: 10000 });
+
+    // ========== Step 2:支付订单 ==========
+    const goPayBtn = page.locator('.go-pay-btn');
+    if (await goPayBtn.isVisible()) {
+      await goPayBtn.click();
+
+      // 选择微信支付并确认
+      const wechatPay = page.locator('.pay-method:has-text("微信")');
+      if (await wechatPay.isVisible()) {
+        await wechatPay.click();
+      }
+
+      await page.click('.confirm-pay-btn');
+      await page.waitForSelector('.pay-success-tip', { timeout: 15000 });
+    }
+
+    // ========== Step 3:规划师确认预约 ==========
+    await page.goto('/#/pages/guide/assessment/list');
+    await page.waitForLoadState('networkidle');
+
+    const appointmentItem = page.locator('.appointment-item').first();
+    if (await appointmentItem.isVisible()) {
+      await appointmentItem.click();
+      await page.click('.confirm-appointment-btn');
+    }
+
+    // ========== Step 4:规划师录入结果 ==========
+    await page.goto('/#/pages/guide/assessment/record');
+    await page.waitForLoadState('networkidle');
+
+    const pendingRecord = page.locator('.pending-record-item').first();
+    if (await pendingRecord.isVisible()) {
+      await pendingRecord.click();
+
+      // 填写分数
+      const scoreInputs = page.locator('.score-input');
+      for (let i = 0; i < await scoreInputs.count(); i++) {
+        await scoreInputs.nth(i).fill('85');
+      }
+
+      await page.click('.submit-result-btn');
+      await page.waitForSelector('.result-saved', { timeout: 10000 });
+    }
+
+    // ========== Step 5:家长查看报告 ==========
+    await page.goto('/#/pages/assessment/results');
+    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');
+    }
+
+    // 验证报告存在
+    await page.waitForSelector('.report-card, .result-item', { timeout: 10000 });
+  });
+
+});

+ 409 - 0
tests/e2e/daily-checkin-streak.spec.js

@@ -0,0 +1,409 @@
+/**
+ * ================================================================
+ * 场景:连续签到奖励全流程 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();
+  });
+
+});

+ 310 - 0
tests/e2e/energy-system.spec.js

@@ -0,0 +1,310 @@
+/**
+ * ================================================================
+ * 场景:能量系统收支全流程 E2E 测试
+ * ================================================================
+ * 用户故事:作为家长,我希望了解和管理家庭成员的五维能量收支情况,
+ *           通过完成任务获取能量,用能量兑换奖励。
+ *
+ * 流程步骤:
+ *   1. 查看家庭能量沙盘,了解各维度能量分布
+ *   2. 孩子完成任务,获得能量奖励(收入)
+ *   3. 使用能量兑换心愿或参与活动(支出)
+ *   4. 系统根据收支计算新的维度比例
+ *   5. 综合健康指数更新
+ *
+ * 关键里程碑:
+ *   - [Milestone-1] 获取能量沙盘,了解当前能量状态
+ *   - [Milestone-2] 任务完成,能量发放成功
+ *   - [Milestone-3] 能量兑换,积分扣减
+ *   - [Milestone-4] 维度比例重算
+ *   - [Milestone-5] 健康指数更新
+ * ================================================================
+ *
+ * 运行:npx playwright test tests/e2e/energy-system.spec.js
+ */
+
+const { test, expect } = require('@playwright/test');
+
+test.describe('【场景流程】能量系统收支全流程', () => {
+
+  // ===== 场景1:获取家庭能量沙盘 =====
+
+  /**
+   * 场景:家长查看家庭的五维能量分布
+   * 角色:家长
+   * 触发条件:进入能量沙盘页面
+   */
+  test('[场景1] 获取家庭能量沙盘 - 期望返回五维能量数据', async ({ page }) => {
+    // ========== Given:进入能量沙盘页面 ==========
+    await page.goto('/#/pages/energy/sandbox');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:页面加载完成 ==========
+    await page.waitForSelector('.energy-sandbox, .sandbox-container', { timeout: 10000 });
+
+    // ========== Then:Milestone-1 - 返回完整能量数据 ========
+    // 验证五维能量显示
+    const bodyEnergy = page.locator('.body-energy, .energy-body');
+    const mindEnergy = page.locator('.mind-energy, .energy-mind');
+    const wisdomEnergy = page.locator('.wisdom-energy, .energy-wisdom');
+    const actionEnergy = page.locator('.action-energy, .energy-action');
+    const wealthEnergy = page.locator('.wealth-energy, .energy-wealth');
+
+    expect(await bodyEnergy.isVisible()).toBeTruthy();
+    expect(await mindEnergy.isVisible()).toBeTruthy();
+    expect(await wisdomEnergy.isVisible()).toBeTruthy();
+    expect(await actionEnergy.isVisible()).toBeTruthy();
+    expect(await wealthEnergy.isVisible()).toBeTruthy();
+
+    // 验证健康指数显示
+    const healthIndex = page.locator('.health-index, .index-value');
+    if (await healthIndex.isVisible()) {
+      const indexText = await healthIndex.textContent();
+      expect(indexText).toMatch(/\d+/);
+    }
+  });
+
+  /**
+   * 场景:用户未加入任何家庭
+   * 角色:用户
+   * 触发条件:用户尝试查看能量但未加入家庭
+   */
+  test('[场景1b] 用户未加入家庭 - 期望返回错误提示', async ({ page }) => {
+    // ========== Given:用户未加入家庭(使用模拟数据)==========
+    await page.goto('/#/pages/energy/sandbox');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:请求能量沙盘 ==========
+    await page.waitForTimeout(2000);
+
+    // ========== Then:返回错误或空状态 ========
+    const errorTip = page.locator('.error-tip, .no-family-tip');
+    const emptyState = page.locator('.empty-state, .no-data');
+
+    const hasError = await errorTip.isVisible();
+    const hasEmpty = await emptyState.isVisible();
+
+    expect(hasError || hasEmpty).toBeTruthy();
+  });
+
+  // ===== 场景2:任务完成能量发放 =====
+
+  /**
+   * 场景:孩子完成任务后,系统自动发放能量奖励
+   * 角色:系统(自动触发)
+   * 触发条件:任务状态变更为"已完成"
+   */
+  test('[场景2] 任务完成能量发放 - 期望能量增加且流水记录', async ({ page }) => {
+    // ========== Given:完成任务后进入任务详情 ==========
+    await page.goto('/#/pages/tasks/complete/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:查看任务完成状态 ==========
+    await page.waitForSelector('.task-complete, .task-done', { timeout: 10000 });
+
+    // ========== Then:Milestone-2 - 能量发放成功 ========
+    // 验证能量奖励显示
+    const energyReward = page.locator('.energy-reward, .reward-amount');
+    if (await energyReward.isVisible()) {
+      const rewardText = await energyReward.textContent();
+      expect(rewardText).toMatch(/\d+/);
+    }
+
+    // 验证能量发放提示
+    const grantTip = page.locator('.energy-granted, .grant-success');
+    if (await grantTip.isVisible()) {
+      expect(await grantTip.isVisible()).toBeTruthy();
+    }
+  });
+
+  // ===== 场景3:能量兑换心愿 =====
+
+  /**
+   * 场景:孩子使用积累的能量兑换心愿
+   * 角色:孩子
+   * 触发条件:兑换心愿时选择能量支付
+   */
+  test('[场景3] 能量兑换心愿 - 期望能量扣减', async ({ page }) => {
+    // ========== Given:进入心愿兑换页面 ==========
+    await page.goto('/#/pages/wishes/exchange/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择能量支付 ==========
+    const energyPayOption = page.locator('.pay-method:has-text("能量"), .energy-pay');
+    if (await energyPayOption.isVisible()) {
+      await energyPayOption.click();
+    }
+
+    // ========== When:确认兑换 ==========
+    await page.click('.confirm-exchange-btn, .exchange-btn');
+
+    // ========== Then:Milestone-3 - 能量扣减成功 ========
+    await page.waitForSelector('.exchange-success, .energy-deducted, .success-tip', { timeout: 10000 });
+
+    // 验证能量扣减提示
+    const deductTip = page.locator('.energy-deducted, .deduct-success');
+    expect(await deductTip.isVisible()).toBeTruthy();
+
+    // 验证剩余能量显示
+    const remainingEnergy = page.locator('.remaining-energy, .current-energy');
+    if (await remainingEnergy.isVisible()) {
+      const energyText = await remainingEnergy.textContent();
+      expect(energyText).toMatch(/\d+/);
+    }
+  });
+
+  /**
+   * 场景:能量不足时兑换心愿
+   * 角色:孩子
+   * 触发条件:当前能量不足以支付
+   */
+  test('[场景3b] 能量不足时兑换 - 期望返回错误', async ({ page }) => {
+    // ========== Given:能量不足 ==========
+    await page.goto('/#/pages/wishes/exchange/999');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:点击兑换 ==========
+    const exchangeBtn = page.locator('.exchange-btn, .confirm-exchange-btn');
+    if (await exchangeBtn.isVisible()) {
+      await exchangeBtn.click();
+
+      // ========== Then:返回错误 ========
+      await page.waitForSelector('.error-tip, .insufficient-energy', { timeout: 5000 });
+
+      const errorTip = page.locator('.error-tip, .insufficient-energy');
+      expect(await errorTip.isVisible()).toBeTruthy();
+      expect(await errorTip.textContent()).toMatch(/能量|不足/);
+    }
+  });
+
+  // ===== 场景4:查询能量流水 =====
+
+  /**
+   * 场景:查看能量收支明细
+   * 角色:家长或孩子
+   * 触发条件:进入能量明细页面
+   */
+  test('[场景4] 查询能量流水 - 期望返回分页记录', async ({ page }) => {
+    // ========== Given:进入能量流水页面 ==========
+    await page.goto('/#/pages/energy/logs');
+    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('.log-item, .energy-log', { timeout: 10000 });
+
+    // ========== Then:返回流水列表 ========
+    const logItems = page.locator('.log-item, .energy-log');
+    const count = await logItems.count();
+    expect(count).toBeGreaterThan(0);
+
+    // 验证流水类型显示(收入/支出)
+    const firstLog = logItems.first();
+    const logType = firstLog.locator('.log-type, .type-tag');
+    if (await logType.isVisible()) {
+      const typeText = await logType.textContent();
+      expect(typeText).toMatch(/收入|支出|奖励|消耗/);
+    }
+  });
+
+  // ===== 场景5:获取五维能量概览 =====
+
+  /**
+   * 场景:查看孩子的五维能量概览
+   * 角色:家长
+   * 触发条件:查看孩子的能量总览
+   */
+  test('[场景5] 获取五维能量概览 - 期望返回五维汇总数据', async ({ page }) => {
+    // ========== Given:进入能量概览页面 ==========
+    await page.goto('/#/pages/energy/overview');
+    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('.energy-overview, .overview-container', { timeout: 10000 });
+
+    // ========== Then:Milestone-5 - 返回概览数据包含健康指数 ========
+    // 验证总能量显示
+    const totalEnergy = page.locator('.total-energy, .energy-total');
+    expect(await totalEnergy.isVisible()).toBeTruthy();
+
+    // 验证五维数据存在
+    const dimensionEnergies = page.locator('.dimension-energy');
+    const dimensionCount = await dimensionEnergies.count();
+    expect(dimensionCount).toBeGreaterThanOrEqual(5);
+
+    // 验证健康指数
+    const healthIndex = page.locator('.health-index, .index-value');
+    if (await healthIndex.isVisible()) {
+      const indexText = await healthIndex.textContent();
+      expect(indexText).toMatch(/\d+/);
+    }
+  });
+
+  // ===== 场景6:完整能量收支流程(端到端)=====
+
+  test('[场景6] 完整能量收支流程 - 期望全流程成功', async ({ page }) => {
+    // ========== Step 1:查看能量沙盘 ==========
+    await page.goto('/#/pages/energy/sandbox');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.energy-sandbox', { timeout: 10000 });
+
+    // 记录初始能量值
+    const initialTotal = page.locator('.total-energy, .energy-total');
+    let initialValue = 0;
+    if (await initialTotal.isVisible()) {
+      const text = await initialTotal.textContent();
+      initialValue = parseInt(text.replace(/\D/g, '')) || 0;
+    }
+
+    // ========== Step 2:完成任务获得能量 ==========
+    await page.goto('/#/pages/tasks/complete/1');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.task-complete, .energy-reward', { timeout: 10000 });
+
+    // ========== Step 3:查看流水确认收入 ==========
+    await page.goto('/#/pages/energy/logs');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.log-item', { timeout: 10000 });
+
+    // 验证有收入记录
+    const incomeLog = page.locator('.log-item:has-text("收入"), .log-item:has-text("奖励")').first();
+    expect(await incomeLog.isVisible()).toBeTruthy();
+
+    // ========== Step 4:查看更新后的能量值 ==========
+    await page.goto('/#/pages/energy/sandbox');
+    await page.waitForLoadState('networkidle');
+
+    const updatedTotal = page.locator('.total-energy, .energy-total');
+    if (await updatedTotal.isVisible()) {
+      const text = await updatedTotal.textContent();
+      const updatedValue = parseInt(text.replace(/\D/g, '')) || 0;
+      // 能量应该增加
+      expect(updatedValue).toBeGreaterThanOrEqual(initialValue);
+    }
+
+    // ========== Step 5:查看概览 ==========
+    await page.goto('/#/pages/energy/overview');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.energy-overview', { timeout: 10000 });
+
+    // 验证五维数据
+    const dimensions = page.locator('.dimension-energy');
+    expect(await dimensions.count()).toBeGreaterThanOrEqual(5);
+  });
+
+});

+ 457 - 0
tests/e2e/family-member-management.spec.js

@@ -0,0 +1,457 @@
+/**
+ * ================================================================
+ * 场景:家庭成员管理全流程 E2E 测试
+ * ================================================================
+ * 用户故事:作为家庭管理员(家长),我希望管理家庭成员,
+ *           以便正确维护家庭关系结构并授权其他成员使用。
+ *
+ * 流程步骤:
+ *   1. 管理员进入家庭成员管理页面
+ *   2. 管理员获取可用的关系类型列表
+ *   3. 管理员添加新家庭成员,指定关系类型
+ *   4. 管理员查看当前家庭成员列表
+ *   5. 管理员编辑成员信息(如修改关系类型)
+ *   6. 管理员切换到成员视角,体验成员端功能
+ *   7. 管理员移除不需要的成员
+ *
+ * 关键里程碑:
+ *   - [Milestone-1] 成功获取关系类型列表
+ *   - [Milestone-2] 新成员添加成功,获得成员ID
+ *   - [Milestone-3] 成员信息更新成功
+ *   - [Milestone-4] 成功切换到成员视角,获得切换凭证
+ *   - [Milestone-5] 成员移除成功
+ * ================================================================
+ *
+ * 运行:npx playwright test tests/e2e/family-member-management.spec.js
+ */
+
+const { test, expect } = require('@playwright/test');
+
+test.describe('【场景流程】家庭成员管理全流程', () => {
+
+  // ===== 场景1:获取关系类型列表 =====
+
+  /**
+   * 场景:管理员进入添加成员页面,需要选择关系类型
+   * 角色:家长(管理员)
+   * 触发条件:打开添加家庭成员页面
+   */
+  test('[场景1] 获取关系类型列表 - 期望返回可用的关系类型', async ({ page }) => {
+    // ========== Given:进入添加家庭成员页面 ==========
+    await page.goto('/#/pages/family/add-member');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:请求关系类型列表 ==========
+    // 关系类型通常在选择器中自动加载
+    await page.waitForSelector('.relationship-picker, .relation-type-selector', { timeout: 10000 });
+
+    // ========== Then:Milestone-1 - 返回完整的关系类型 ========
+    const relationOptions = page.locator('.relationship-picker .option, .relation-item, .type-option');
+    const count = await relationOptions.count();
+
+    expect(count).toBeGreaterThan(0);
+
+    // 验证常见的关系类型存在
+    const pickerOptions = page.locator('.relationship-picker');
+    if (await pickerOptions.isVisible()) {
+      await pickerOptions.click();
+      await page.waitForSelector('.option-item', { timeout: 5000 });
+
+      // 验证至少包含父母或孩子类型
+      const optionList = page.locator('.option-item');
+      const hasParentType = await page.locator('.option-item:has-text("父"), .option-item:has-text("母")').count() > 0;
+      const hasChildType = await page.locator('.option-item:has-text("子"), .option-item:has-text("女")').count() > 0;
+      expect(hasParentType || hasChildType).toBeTruthy();
+    }
+  });
+
+  // ===== 场景2:添加新家庭成员 =====
+
+  /**
+   * 场景:家长添加一个新的孩子成员到家庭
+   * 角色:家长(管理员)
+   * 触发条件:填写完成员信息并提交
+   */
+  test('[场景2] 添加新家庭成员 - 期望成员添加成功并返回完整信息', async ({ page }) => {
+    // ========== Given:进入添加成员页面 ==========
+    await page.goto('/#/pages/family/add-member');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:填写成员信息 ==========
+    // 输入姓名
+    const nameInput = page.locator('input[placeholder*="姓名"], .name-input, input[name="nickname"]');
+    if (await nameInput.isVisible()) {
+      await nameInput.fill('王小明');
+    }
+
+    // 选择性别
+    const genderMale = page.locator('.gender-option:has-text("男"), .gender-radio:has-text("男")');
+    if (await genderMale.isVisible()) {
+      await genderMale.click();
+    }
+
+    // 输入生日
+    const birthdayInput = page.locator('input[placeholder*="生日"], .birthday-input');
+    if (await birthdayInput.isVisible()) {
+      await birthdayInput.fill('2015-06-15');
+    }
+
+    // ========== Step 2:选择关系类型 ==========
+    const relationPicker = page.locator('.relationship-picker, .relation-selector');
+    if (await relationPicker.isVisible()) {
+      await relationPicker.click();
+      await page.waitForSelector('.option-item', { timeout: 5000 });
+      // 选择"儿子"或"女儿"
+      const childOption = page.locator('.option-item:has-text("儿子"), .option-item:has-text("女儿"), .option-item:has-text("孩子")').first();
+      if (await childOption.isVisible()) {
+        await childOption.click();
+      } else {
+        // 如果没有精确匹配,点击第一个
+        await page.locator('.option-item').first().click();
+      }
+    }
+
+    // ========== When:提交添加 ==========
+    await page.click('.submit-btn, .add-btn, .confirm-btn');
+
+    // ========== Then:Milestone-2 - 成员添加成功 ========
+    await page.waitForSelector('.success-tip, .add-success, .member-added', { timeout: 10000 });
+
+    // 验证添加成功提示
+    const successTip = page.locator('.success-tip, .add-success, .toast-success');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  /**
+   * 场景:家长添加成员时未填写必填信息(姓名)
+   * 角色:家长
+   * 触发条件:提交表单时 nickname 为空
+   */
+  test('[场景2b] 添加成员时姓名为空 - 期望返回错误提示', async ({ page }) => {
+    // ========== Given:进入添加成员页面 ==========
+    await page.goto('/#/pages/family/add-member');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择关系类型但不填写姓名 ==========
+    const relationPicker = page.locator('.relationship-picker, .relation-selector');
+    if (await relationPicker.isVisible()) {
+      await relationPicker.click();
+      await page.waitForSelector('.option-item', { timeout: 5000 });
+      await page.locator('.option-item').first().click();
+    }
+
+    // ========== When:尝试提交 ==========
+    await page.click('.submit-btn, .add-btn, .confirm-btn');
+
+    // ========== Then:返回错误信息 ========
+    // 验证错误提示出现(姓名不能为空)
+    await page.waitForSelector('.error-tip, .van-field__error, .form-error', { timeout: 5000 });
+
+    const errorTip = page.locator('.error-tip, .van-field__error, .form-error');
+    expect(await errorTip.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景3:查看家庭成员列表 =====
+
+  /**
+   * 场景:家长查看当前家庭的所有成员
+   * 角色:家长
+   * 触发条件:进入家庭成员管理页面
+   */
+  test('[场景3] 查看家庭成员列表 - 期望返回所有成员及关系信息', async ({ page }) => {
+    // ========== Given:进入家庭成员管理页面 ==========
+    await page.goto('/#/pages/profile/family-members');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:查看成员列表 ==========
+    await page.waitForSelector('.member-item, .family-member', { timeout: 10000 });
+
+    // ========== Then:返回完整的成员列表 ========
+    const memberItems = page.locator('.member-item, .family-member');
+    const count = await memberItems.count();
+
+    expect(count).toBeGreaterThan(0);
+
+    // 验证每个成员都显示关系类型
+    const firstMember = memberItems.first();
+    expect(await firstMember.isVisible()).toBeTruthy();
+
+    // 验证关系标签存在
+    const relationTag = page.locator('.relation-tag, .member-role, .relationship-label');
+    if (await relationTag.first().isVisible()) {
+      const tagText = await relationTag.first().textContent();
+      expect(tagText.length).toBeGreaterThan(0);
+    }
+  });
+
+  // ===== 场景4:更新成员信息 =====
+
+  /**
+   * 场景:家长需要修改成员的关系类型(如孩子长大后变更角色)
+   * 角色:家长
+   * 触发条件:编辑成员信息并保存
+   */
+  test('[场景4] 更新成员关系类型 - 期望更新成功并返回新信息', async ({ page }) => {
+    // ========== Given:进入家庭成员管理页面 ==========
+    await page.goto('/#/pages/profile/family-members');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到要编辑的成员 ==========
+    await page.waitForSelector('.member-item, .family-member', { timeout: 10000 });
+    const firstMember = page.locator('.member-item, .family-member').first();
+
+    // 点击编辑按钮
+    const editBtn = firstMember.locator('.edit-btn, .btn-edit');
+    if (await editBtn.isVisible()) {
+      await editBtn.click();
+    } else {
+      // 直接点击成员项进入编辑页
+      await firstMember.click();
+    }
+
+    // ========== Step 2:修改关系类型 ==========
+    await page.waitForSelector('.edit-form, .member-edit-form', { timeout: 5000 });
+
+    const relationPicker = page.locator('.relationship-picker, .relation-selector');
+    if (await relationPicker.isVisible()) {
+      await relationPicker.click();
+      await page.waitForSelector('.option-item', { timeout: 5000 });
+      // 选择一个新的关系类型
+      await page.locator('.option-item').nth(2).click();
+    }
+
+    // ========== When:保存修改 ==========
+    await page.click('.save-btn, .confirm-btn');
+
+    // ========== Then:Milestone-3 - 更新成功 ========
+    await page.waitForSelector('.success-tip, .update-success', { timeout: 10000 });
+
+    const successTip = page.locator('.success-tip, .update-success');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景5:切换到成员视角 =====
+
+  /**
+   * 场景:家长需要以孩子视角体验功能,验证孩子端显示是否正常
+   * 角色:家长(代理操作)
+   * 触发条件:选择成员并点击"切换视角"
+   */
+  test('[场景5] 切换到成员视角 - 期望返回切换凭证并正确设置上下文', async ({ page }) => {
+    // ========== Given:进入家庭成员管理页面 ==========
+    await page.goto('/#/pages/profile/family-members');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到要切换的成员 ==========
+    await page.waitForSelector('.member-item, .family-member', { timeout: 10000 });
+
+    // 找到切换按钮
+    const switchBtn = page.locator('.switch-btn, .switch-view-btn, .btn-switch');
+    if (await switchBtn.isVisible()) {
+      await switchBtn.first().click();
+    }
+
+    // ========== When:执行切换视角操作 ==========
+    // 如果有成员选择列表,先选择成员
+    const memberOption = page.locator('.member-option, .switch-member-item').first();
+    if (await memberOption.isVisible()) {
+      await memberOption.click();
+    }
+
+    // 确认切换
+    const confirmSwitch = page.locator('.confirm-switch-btn, .btn-confirm-switch');
+    if (await confirmSwitch.isVisible()) {
+      await confirmSwitch.click();
+    }
+
+    // ========== Then:Milestone-4 - 切换成功 ========
+    // 等待页面刷新到孩子视角
+    await page.waitForTimeout(1000);
+
+    // 验证切换成功(通常是页面标题或内容变化)
+    const childIndicator = page.locator('.child-view, .child-mode, .switched-indicator');
+    const hasSwitched = await childIndicator.isVisible() ||
+      page.url().includes('child') ||
+      page.url().includes('child-index');
+
+    expect(hasSwitched).toBeTruthy();
+  });
+
+  /**
+   * 场景:家长尝试切换到自己(不允许自己切换到自己)
+   * 角色:家长
+   * 触发条件:选择成员ID为自己
+   */
+  test('[场景5b] 切换到本人视角 - 期望返回错误提示', async ({ page }) => {
+    // ========== Given:进入切换视角页面 ==========
+    await page.goto('/#/pages/family/switch-view');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:尝试切换到当前用户自己 ==========
+    // 选择自己的选项(通常被禁用或隐藏)
+    const selfOption = page.locator('.member-option:has-text("我"), .self-option');
+
+    if (await selfOption.isVisible()) {
+      // 如果可见,尝试点击(应该被禁用)
+      await selfOption.click();
+
+      // 验证按钮被禁用
+      const switchBtn = page.locator('.switch-btn, .confirm-btn');
+      const isDisabled = await switchBtn.getAttribute('disabled');
+      expect(isDisabled).not.toBeNull();
+    } else {
+      // 如果没有显示自己,说明设计正确(不允许切换到自己)
+      expect(true).toBeTruthy();
+    }
+  });
+
+  // ===== 场景6:移除家庭成员 =====
+
+  /**
+   * 场景:家庭成员关系结束,家长移除不再属于家庭的成员
+   * 角色:家长(管理员)
+   * 触发条件:确认移除操作
+   */
+  test('[场景6] 移除家庭成员 - 期望成员从家庭中移除', async ({ page }) => {
+    // ========== Given:进入家庭成员管理页面 ==========
+    await page.goto('/#/pages/profile/family-members');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到要移除的成员 ==========
+    await page.waitForSelector('.member-item, .family-member', { timeout: 10000 });
+
+    // 获取移除前的成员数量
+    const beforeCount = await page.locator('.member-item, .family-member').count();
+
+    // 找到最后一个成员(通常是测试数据)
+    const lastMember = page.locator('.member-item, .family-member').last();
+    const removeBtn = lastMember.locator('.remove-btn, .delete-btn, .btn-remove');
+
+    if (await removeBtn.isVisible()) {
+      await removeBtn.click();
+
+      // ========== Step 2:确认移除 ==========
+      // 等待确认弹窗
+      await page.waitForSelector('.confirm-dialog, .van-dialog, .remove-confirm', { timeout: 5000 });
+
+      const confirmBtn = page.locator('.confirm-dialog .van-button--primary, .confirm-remove-btn, .btn-confirm');
+      if (await confirmBtn.isVisible()) {
+        await confirmBtn.click();
+      }
+
+      // ========== Then:Milestone-5 - 移除成功 ========
+      await page.waitForTimeout(1000);
+
+      // 验证成员数量减少
+      const afterCount = await page.locator('.member-item, .family-member').count();
+      expect(afterCount).toBeLessThan(beforeCount);
+    }
+  });
+
+  /**
+   * 场景:普通成员(非管理员)尝试踢出成员
+   * 角色:普通家庭成员(非管理员)
+   * 触发条件:普通成员尝试执行踢人操作
+   */
+  test('[场景6b] 普通成员尝试踢人 - 期望权限不足拒绝', async ({ page }) => {
+    // ========== Given:普通成员登录并进入家庭成员页面 ==========
+    // 先登出并用孩子账号登录
+    await page.goto('/#/pages/login/login');
+    await page.waitForLoadState('networkidle');
+
+    // 模拟孩子登录(实际测试中需要真实的登录流程)
+    // ...
+
+    // ========== When:尝试踢人操作 ==========
+    await page.goto('/#/pages/profile/family-members');
+    await page.waitForLoadState('networkidle');
+
+    // 检查是否有移除按钮
+    const removeBtn = page.locator('.remove-btn, .delete-btn, .btn-remove');
+
+    // ========== Then:权限不足,拒绝操作 ========
+    if (await removeBtn.isVisible()) {
+      // 如果显示了按钮,尝试点击应该被拒绝
+      await removeBtn.click();
+
+      // 应该显示权限错误
+      await page.waitForSelector('.auth-error, .permission-denied, .no-permission', { timeout: 5000 });
+      const errorTip = page.locator('.auth-error, .permission-denied, .no-permission');
+      expect(await errorTip.isVisible()).toBeTruthy();
+    } else {
+      // 如果没有显示按钮,说明权限控制正确
+      expect(true).toBeTruthy();
+    }
+  });
+
+  // ===== 场景7:完整家庭成员管理流程(端到端)=====
+
+  test('[场景7] 完整家庭成员管理流程 - 期望全流程成功', async ({ page }) => {
+    // ========== Step 1:进入添加成员页面 ==========
+    await page.goto('/#/pages/family/add-member');
+    await page.waitForLoadState('networkidle');
+
+    // 验证关系类型加载
+    await page.waitForSelector('.relationship-picker', { timeout: 10000 });
+
+    // ========== Step 2:填写并提交新成员 ==========
+    const nameInput = page.locator('input[name="nickname"], .name-input');
+    if (await nameInput.isVisible()) {
+      await nameInput.fill('测试成员_' + Date.now());
+    }
+
+    const genderMale = page.locator('.gender-option:has-text("男")');
+    if (await genderMale.isVisible()) {
+      await genderMale.click();
+    }
+
+    // 选择关系类型
+    const relationPicker = page.locator('.relationship-picker');
+    if (await relationPicker.isVisible()) {
+      await relationPicker.click();
+      await page.waitForSelector('.option-item', { timeout: 5000 });
+      await page.locator('.option-item').nth(2).click();
+    }
+
+    await page.click('.submit-btn');
+    await page.waitForSelector('.success-tip, .add-success', { timeout: 10000 });
+
+    // ========== Step 3:查看成员列表 ==========
+    await page.goto('/#/pages/profile/family-members');
+    await page.waitForSelector('.member-item', { timeout: 10000 });
+
+    const memberCount = await page.locator('.member-item').count();
+    expect(memberCount).toBeGreaterThan(0);
+
+    // ========== Step 4:编辑成员 ==========
+    const firstMember = page.locator('.member-item').first();
+    const editBtn = firstMember.locator('.edit-btn');
+    if (await editBtn.isVisible()) {
+      await editBtn.click();
+      await page.waitForSelector('.edit-form', { timeout: 5000 });
+      await page.click('.save-btn');
+      await page.waitForSelector('.success-tip', { timeout: 10000 });
+    }
+
+    // ========== Step 5:切换视角 ==========
+    const switchBtn = page.locator('.switch-btn').first();
+    if (await switchBtn.isVisible()) {
+      await switchBtn.click();
+      await page.waitForTimeout(1000);
+    }
+
+    // ========== Step 6:返回并移除成员 ==========
+    await page.goto('/#/pages/profile/family-members');
+    await page.waitForSelector('.member-item', { timeout: 10000 });
+
+    const lastMember = page.locator('.member-item').last();
+    const removeBtn = lastMember.locator('.remove-btn');
+    if (await removeBtn.isVisible()) {
+      await removeBtn.click();
+      await page.waitForSelector('.confirm-dialog', { timeout: 5000 });
+      await page.click('.confirm-dialog .van-button--primary');
+      await page.waitForTimeout(500);
+    }
+  });
+
+});

+ 353 - 0
tests/e2e/growth-record-sync.spec.js

@@ -0,0 +1,353 @@
+/**
+ * ================================================================
+ * 场景:成长记录同步全流程 E2E 测试
+ * ================================================================
+ * 用户故事:作为家长,我希望记录孩子的成长轨迹,
+ *           上传健康报告并与测评结果关联,获得全面的成长分析。
+ *
+ * 流程步骤:
+ *   1. 家长创建孩子的成长档案(基本信息、学校、年级等)
+ *   2. 家长上传孩子的健康体检报告
+ *   3. 系统解析报告,提取身高、体重、视力等数据
+ *   4. 成长记录与认知测评结果关联
+ *   5. 系统更新孩子的五维能量数据
+ *
+ * 关键里程碑:
+ *   - [Milestone-1] 成长档案创建成功
+ *   - [Milestone-2] 健康报告上传并解析成功
+ *   - [Milestone-3] 成长记录与测评报告关联
+ *   - [Milestone-4] 五维能量数据更新
+ * ================================================================
+ *
+ * 运行:npx playwright test tests/e2e/growth-record-sync.spec.js
+ */
+
+const { test, expect } = require('@playwright/test');
+
+test.describe('【场景流程】成长记录同步全流程', () => {
+
+  // ===== 场景1:创建成长档案 =====
+
+  /**
+   * 场景:家长为孩子创建一个成长档案
+   * 角色:家长
+   * 触发条件:家长首次使用成长记录功能
+   */
+  test('[场景1] 创建孩子成长档案 - 期望档案创建成功', async ({ page }) => {
+    // ========== Given:进入创建成长档案页面 ==========
+    await page.goto('/#/pages/growth/create');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择孩子 ==========
+    const childPicker = page.locator('.child-picker, .select-child');
+    if (await childPicker.isVisible()) {
+      await childPicker.click();
+      await page.waitForSelector('.child-option, .option-item', { timeout: 5000 });
+      await page.click('.child-option:first-child, .option-item:first-child');
+    }
+
+    // ========== Step 2:填写基本信息 ==========
+    // 输入学校名称
+    const schoolInput = page.locator('input[placeholder*="学校"], .school-input, input[name="school"]');
+    if (await schoolInput.isVisible()) {
+      await schoolInput.fill('北京市第一小学');
+    }
+
+    // 选择年级
+    const gradePicker = page.locator('.grade-picker, .select-grade');
+    if (await gradePicker.isVisible()) {
+      await gradePicker.click();
+      await page.waitForSelector('.grade-option, .option-item', { timeout: 5000 });
+      await page.click('.grade-option:has-text("三年级"), .grade-option:nth-child(3)');
+    }
+
+    // ========== When:提交创建 ==========
+    await page.click('.submit-btn, .create-btn');
+
+    // ========== Then:Milestone-1 - 档案创建成功 ========
+    await page.waitForSelector('.success-tip, .created, .record-success', { timeout: 10000 });
+
+    const successTip = page.locator('.success-tip, .created, .record-success');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  /**
+   * 场景:家长创建档案时未选择孩子
+   * 角色:家长
+   * 触发条件:表单中孩子ID为空
+   */
+  test('[场景1b] 创建档案时未选择孩子 - 期望返回错误', async ({ page }) => {
+    // ========== Given:进入创建页面 ==========
+    await page.goto('/#/pages/growth/create');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:填写学校但选择孩子 ==========
+    const schoolInput = page.locator('input[placeholder*="学校"], .school-input');
+    if (await schoolInput.isVisible()) {
+      await schoolInput.fill('北京市第一小学');
+    }
+
+    // ========== When:尝试提交 ==========
+    await page.click('.submit-btn');
+
+    // ========== Then:返回错误提示 ========
+    await page.waitForSelector('.error-tip, .van-field__error, .form-error', { timeout: 5000 });
+
+    const errorTip = page.locator('.error-tip, .van-field__error');
+    expect(await errorTip.isVisible()).toBeTruthy();
+    expect(await errorTip.textContent()).toMatch(/孩子|选择/);
+  });
+
+  // ===== 场景2:创建档案并购买测评 =====
+
+  /**
+   * 场景:家长创建成长档案的同时购买认知测评服务
+   * 角色:家长
+   * 触发条件:勾选"同时购买测评"选项
+   */
+  test('[场景2] 创建档案并购买测评套餐 - 期望档案和订单同时创建', async ({ page }) => {
+    // ========== Given:进入创建档案页面 ==========
+    await page.goto('/#/pages/growth/create-with-order');
+    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 schoolInput = page.locator('input[name="school"], .school-input');
+    if (await schoolInput.isVisible()) {
+      await schoolInput.fill('北京市第二小学');
+    }
+
+    // ========== Step 3:勾选同时购买测评 ==========
+    const purchaseCheckbox = page.locator('.purchase-checkbox, .also-buy-assessment');
+    if (await purchaseCheckbox.isVisible()) {
+      await purchaseCheckbox.click();
+    }
+
+    // 选择套餐
+    const packageOption = page.locator('.package-option:first-child');
+    if (await packageOption.isVisible()) {
+      await packageOption.click();
+    }
+
+    // ========== When:提交 ==========
+    await page.click('.submit-btn, .create-with-order-btn');
+
+    // ========== Then:档案和订单都创建成功 ========
+    await page.waitForSelector('.success-tip, .record-created', { timeout: 10000 });
+
+    // 验证跳转或显示订单信息
+    const orderInfo = page.locator('.order-info, .order-no');
+    // 可能跳转到了订单页面或留在创建页但显示了订单信息
+    expect(await page.locator('.success-tip, .record-created, .order-info').isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景3:上传健康报告并解析 =====
+
+  /**
+   * 场景:家长上传孩子的健康体检报告,系统自动解析
+   * 角色:家长
+   * 触发条件:上传体检报告文件
+   */
+  test('[场景3] 上传并解析健康报告 - 期望解析出健康数据', async ({ page }) => {
+    // ========== Given:进入上传报告页面 ==========
+    await page.goto('/#/pages/growth/upload-report');
+    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 uploadBtn = page.locator('.upload-btn, .upload-report-btn');
+    if (await uploadBtn.isVisible()) {
+      await uploadBtn.click();
+    }
+
+    // 选择文件(模拟)
+    const fileInput = page.locator('input[type="file"], .file-input');
+    if (await fileInput.isVisible()) {
+      // 在实际测试中,这里会上传真实的PDF文件
+      // await fileInput.setInputFiles('/path/to/test-report.pdf');
+    }
+
+    // ========== When:确认上传 ==========
+    const confirmBtn = page.locator('.confirm-upload-btn, .submit-upload-btn');
+    if (await confirmBtn.isVisible()) {
+      await confirmBtn.click();
+    }
+
+    // ========== Then:Milestone-2 - 报告解析成功 ========
+    // 等待解析完成(可能需要几秒钟)
+    await page.waitForSelector('.parsed-result, .report-data, .parse-success', { timeout: 30000 });
+
+    // 验证解析结果显示
+    const parsedData = page.locator('.parsed-result, .health-data');
+    expect(await parsedData.isVisible()).toBeTruthy();
+
+    // 验证关键数据提取
+    const heightData = page.locator('.height-value, .parsed-height');
+    const weightData = page.locator('.weight-value, .parsed-weight');
+    if (await heightData.isVisible()) {
+      expect(await heightData.textContent()).toMatch(/\d+/);
+    }
+  });
+
+  // ===== 场景4:关联成长记录与测评结果 =====
+
+  /**
+   * 场景:成长记录与认知测评结果进行关联
+   * 角色:系统(自动关联)
+   * 触发条件:测评报告生成后
+   */
+  test('[场景4] 成长记录关联测评报告 - 期望关联成功', async ({ page }) => {
+    // ========== Given:进入成长记录详情 ==========
+    await page.goto('/#/pages/growth/record-detail/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:查看关联状态 ==========
+    await page.waitForSelector('.record-detail, .growth-record', { timeout: 10000 });
+
+    // ========== Then:Milestone-3 - 关联成功 ========
+    // 查看是否有测评报告关联卡片
+    const assessmentCard = page.locator('.assessment-link, .related-assessment, .linked-report');
+    if (await assessmentCard.isVisible()) {
+      // 验证关联的测评报告显示
+      expect(await assessmentCard.isVisible()).toBeTruthy();
+    } else {
+      // 验证关联按钮存在,可以进行关联
+      const linkBtn = page.locator('.link-assessment-btn, .add-assessment-btn');
+      expect(await linkBtn.isVisible()).toBeTruthy();
+    }
+  });
+
+  // ===== 场景5:查询孩子的成长记录列表 =====
+
+  /**
+   * 场景:家长查看孩子的历史成长记录
+   * 角色:家长
+   * 触发条件:进入成长记录历史页面
+   */
+  test('[场景5] 查询成长记录历史 - 期望返回记录列表', async ({ page }) => {
+    // ========== Given:进入成长记录列表 ==========
+    await page.goto('/#/pages/growth/records');
+    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('.record-item, .growth-item', { timeout: 10000 });
+
+    // ========== Then:返回历史记录列表 ========
+    const recordItems = page.locator('.record-item, .growth-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();
+  });
+
+  // ===== 场景6:补充成长记录 =====
+
+  /**
+   * 场景:家长为已有记录补充信息
+   * 角色:家长
+   * 触发条件:点击记录的"补充"按钮
+   */
+  test('[场景6] 补充成长记录 - 期望补充成功', async ({ page }) => {
+    // ========== Given:进入记录详情 ==========
+    await page.goto('/#/pages/growth/record-detail/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:点击补充按钮 ==========
+    const supplementBtn = page.locator('.supplement-btn, .add-note-btn');
+    if (await supplementBtn.isVisible()) {
+      await supplementBtn.click();
+
+      // 填写补充内容
+      const noteInput = page.locator('textarea[name="note"], .note-input');
+      if (await noteInput.isVisible()) {
+        await noteInput.fill('本学期孩子在数学方面有明显进步,建议加强逻辑思维训练。');
+      }
+
+      await page.click('.submit-supplement-btn');
+
+      // ========== Then:补充成功 ========
+      await page.waitForSelector('.success-tip, .supplement-success', { timeout: 10000 });
+
+      // 验证补充内容显示
+      const supplementList = page.locator('.supplement-list, .note-list');
+      expect(await supplementList.isVisible()).toBeTruthy();
+    }
+  });
+
+  // ===== 场景7:完整成长记录流程(端到端)=====
+
+  test('[场景7] 完整成长记录生命周期 - 期望全流程成功', async ({ page }) => {
+    // ========== Step 1:创建成长档案 ==========
+    await page.goto('/#/pages/growth/create');
+    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 schoolInput = page.locator('input[name="school"], .school-input');
+    if (await schoolInput.isVisible()) {
+      await schoolInput.fill('北京市第一小学');
+    }
+
+    // 选择年级
+    const gradePicker = page.locator('.grade-picker');
+    if (await gradePicker.isVisible()) {
+      await gradePicker.click();
+      await page.waitForSelector('.grade-option', { timeout: 5000 });
+      await page.click('.grade-option:nth-child(3)');
+    }
+
+    await page.click('.submit-btn');
+    await page.waitForSelector('.success-tip, .record-success', { timeout: 10000 });
+
+    // ========== Step 2:查询记录列表 ==========
+    await page.goto('/#/pages/growth/records');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.record-item', { timeout: 10000 });
+
+    // 验证记录存在
+    const recordCount = await page.locator('.record-item').count();
+    expect(recordCount).toBeGreaterThan(0);
+
+    // ========== Step 3:查看详情 ==========
+    const firstRecord = page.locator('.record-item').first();
+    await firstRecord.click();
+    await page.waitForSelector('.record-detail, .growth-detail', { timeout: 10000 });
+
+    // 验证详情页面显示
+    expect(await page.locator('.record-detail, .growth-detail').isVisible()).toBeTruthy();
+  });
+
+});

+ 304 - 0
tests/e2e/planner-bind-invite.spec.js

@@ -0,0 +1,304 @@
+/**
+ * ================================================================
+ * 场景:规划师绑定邀请全流程 E2E 测试
+ * ================================================================
+ * 用户故事:作为成长规划师,我希望邀请家长加入我的服务团队,
+ *           以便建立长期服务关系并管理我的客户家庭。
+ *
+ * 流程步骤:
+ *   1. 规划师登录系统,生成绑定邀请链接
+ *   2. 规划师将邀请链接发送给家长
+ *   3. 家长点击链接,查看邀请详情并确认
+ *   4. 家长授权绑定,成为规划师的客户
+ *   5. 规划师可在客户列表中看到新绑定的家庭
+ *
+ * 关键里程碑:
+ *   - [Milestone-1] 邀请链接生成成功,获得邀请凭证
+ *   - [Milestone-2] 邀请链接验证成功,展示邀请详情
+ *   - [Milestone-3] 家长确认绑定成功,与规划师建立关系
+ *   - [Milestone-4] 规划师客户列表更新,可以看到新家庭
+ *   - [Milestone-5] 解绑成功,关系终止
+ * ================================================================
+ *
+ * 运行:npx playwright test tests/e2e/planner-bind-invite.spec.js
+ */
+
+const { test, expect } = require('@playwright/test');
+
+test.describe('【场景流程】规划师绑定邀请全流程', () => {
+
+  // ===== 场景1:规划师生成邀请 =====
+
+  /**
+   * 场景:规划师登录后生成一个绑定邀请
+   * 角色:成长规划师
+   * 触发条件:规划师点击"邀请家长"按钮
+   */
+  test('[场景1] 规划师生成邀请链接 - 期望返回邀请凭证', async ({ page }) => {
+    // ========== Given:规划师进入邀请页面 ==========
+    await page.goto('/#/pages/guide/bind/invite');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:点击生成邀请 ==========
+    await page.waitForSelector('.invite-btn, .generate-invite-btn', { timeout: 10000 });
+    await page.click('.generate-invite-btn, .create-invite-btn');
+
+    // ========== Then:Milestone-1 - 邀请生成成功 ========
+    await page.waitForSelector('.invite-token, .invite-link, .invite-success', { timeout: 10000 });
+
+    // 验证邀请凭证显示
+    const inviteToken = page.locator('.invite-token, .invite-code');
+    expect(await inviteToken.isVisible()).toBeTruthy();
+
+    // 复制按钮可用
+    const copyBtn = page.locator('.copy-btn, .copy-invite-btn');
+    expect(await copyBtn.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景2:家长验证邀请 =====
+
+  /**
+   * 场景:家长点击邀请链接,系统验证邀请有效性
+   * 角色:家长
+   * 触发条件:家长打开邀请链接
+   */
+  test('[场景2] 家长验证邀请链接 - 期望展示邀请详情', async ({ page }) => {
+    // ========== Given:家长打开邀请链接 ==========
+    await page.goto('/#/pages/guide/bind/validate?token=invite_test_abc123');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:页面加载验证 ======
+    await page.waitForSelector('.invite-detail, .guide-info, .invite-validation', { timeout: 10000 });
+
+    // ========== Then:Milestone-2 - 邀请验证成功,展示详情 ========
+    // 验证规划师名称显示
+    const guideName = page.locator('.guide-name, .guide-nickname');
+    expect(await guideName.isVisible()).toBeTruthy();
+
+    // 验证服务类型显示
+    const serviceTypes = page.locator('.service-type, .service-tag');
+    const serviceCount = await serviceTypes.count();
+    expect(serviceCount).toBeGreaterThan(0);
+
+    // 验证有效期显示
+    const expireTime = page.locator('.expire-time, .valid-period');
+    if (await expireTime.isVisible()) {
+      const expireText = await expireTime.textContent();
+      expect(expireText).toMatch(/\d{4}|\d{2}:\d{2}/);
+    }
+  });
+
+  /**
+   * 场景:家长使用过期或无效的邀请链接
+   * 角色:家长
+   * 触发条件:链接已过期或不存在
+   */
+  test('[场景2b] 使用无效邀请链接 - 期望返回错误提示', async ({ page }) => {
+    // ========== Given:打开无效邀请链接 ==========
+    await page.goto('/#/pages/guide/bind/validate?token=invalid_token');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:系统验证链接 ==========
+    await page.waitForSelector('.error-tip, .invalid-invite, .invite-error', { timeout: 10000 });
+
+    // ========== Then:返回错误信息 ========
+    const errorTip = page.locator('.error-tip, .invalid-invite, .invite-error');
+    expect(await errorTip.isVisible()).toBeTruthy();
+
+    const errorText = await errorTip.textContent();
+    expect(errorText).toMatch(/无效|过期|不存在/);
+  });
+
+  // ===== 场景3:家长确认绑定 =====
+
+  /**
+   * 场景:家长查看邀请详情后,确认绑定该规划师
+   * 角色:家长
+   * 触发条件:家长点击"确认绑定"按钮
+   */
+  test('[场景3] 家长确认绑定规划师 - 期望绑定成功', async ({ page }) => {
+    // ========== Given:进入邀请确认页面 ==========
+    await page.goto('/#/pages/guide/bind/confirm?token=invite_valid_token');
+    await page.waitForLoadState('networkidle');
+
+    // 验证邀请详情已显示
+    await page.waitForSelector('.guide-info, .invite-detail', { timeout: 10000 });
+
+    // ========== Step 1:选择服务类型(可选)==========
+    const serviceOption = page.locator('.service-option, .service-type-item').first();
+    if (await serviceOption.isVisible()) {
+      await serviceOption.click();
+    }
+
+    // ========== When:点击确认绑定 ==========
+    await page.click('.confirm-bind-btn, .bind-confirm-btn');
+
+    // ========== Then:Milestone-3 - 绑定成功 ========
+    await page.waitForSelector('.success-tip, .bind-success, .binded', { timeout: 10000 });
+
+    // 验证绑定成功提示
+    const successTip = page.locator('.success-tip, .bind-success');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  /**
+   * 场景:家长尝试重复绑定同一个规划师
+   * 角色:家长
+   * 触发条件:已经绑定过该规划师,再次确认绑定
+   */
+  test('[场景3b] 重复绑定同一规划师 - 期望返回错误提示', async ({ page }) => {
+    // ========== Given:已绑定该规划师 ==========
+    await page.goto('/#/pages/guide/bind/confirm?token=invite_already_bound');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:点击确认绑定 ==========
+    await page.click('.confirm-bind-btn');
+
+    // ========== Then:返回错误提示 ========
+    await page.waitForSelector('.error-tip, .already-bound-error', { timeout: 10000 });
+
+    const errorTip = page.locator('.error-tip, .already-bound-error');
+    expect(await errorTip.isVisible()).toBeTruthy();
+
+    const errorText = await errorTip.textContent();
+    expect(errorText).toMatch(/已绑定|重复|已在/);
+  });
+
+  // ===== 场景4:查看绑定规划师列表 =====
+
+  /**
+   * 场景:家长查看已绑定的规划师列表
+   * 角色:家长
+   * 触发条件:家长进入"我的规划师"页面
+   */
+  test('[场景4] 家长查看绑定规划师列表 - 期望返回已绑定的规划师', async ({ page }) => {
+    // ========== Given:进入我的规划师页面 ==========
+    await page.goto('/#/pages/profile/my-guides');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:查看规划师列表 ==========
+    await page.waitForSelector('.guide-item, .bound-guide', { timeout: 10000 });
+
+    // ========== Then:Milestone-4 - 返回已绑定规划师列表 ========
+    const guideItems = page.locator('.guide-item, .bound-guide');
+    const count = await guideItems.count();
+    expect(count).toBeGreaterThan(0);
+
+    // 验证规划师信息显示
+    const firstGuide = guideItems.first();
+    const guideName = firstGuide.locator('.guide-name');
+    expect(await guideName.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景5:家长解除绑定 =====
+
+  /**
+   * 场景:家长决定解除与某个规划师的绑定关系
+   * 角色:家长
+   * 触发条件:家长在规划师详情页点击"解除绑定"
+   */
+  test('[场景5] 家长解除绑定规划师 - 期望解除成功并发送通知', async ({ page }) => {
+    // ========== Given:进入规划师详情页 ==========
+    await page.goto('/#/pages/guide/detail/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到解除绑定按钮 ==========
+    await page.waitForSelector('.guide-info, .guide-detail', { timeout: 10000 });
+    const unbindBtn = page.locator('.unbind-btn, .unbind-guide-btn');
+
+    // ========== When:点击解除绑定 ==========
+    if (await unbindBtn.isVisible()) {
+      await unbindBtn.click();
+
+      // 确认解除
+      await page.waitForSelector('.confirm-dialog, .van-dialog', { timeout: 5000 });
+      const confirmBtn = page.locator('.confirm-dialog .van-button--primary, .confirm-unbind');
+      if (await confirmBtn.isVisible()) {
+        await confirmBtn.click();
+      }
+
+      // ========== Then:Milestone-5 - 解绑成功 ========
+      await page.waitForSelector('.success-tip, .unbind-success', { timeout: 10000 });
+
+      const successTip = page.locator('.success-tip, .unbind-success');
+      expect(await successTip.isVisible()).toBeTruthy();
+    }
+  });
+
+  // ===== 场景6:规划师查看已绑定的家庭 =====
+
+  /**
+   * 场景:规划师查看已绑定的家庭列表
+   * 角色:成长规划师
+   * 触发条件:规划师进入"我的客户"页面
+   */
+  test('[场景6] 规划师查看绑定家庭列表 - 期望返回所有客户家庭', async ({ page }) => {
+    // ========== Given:规划师进入客户列表 ==========
+    await page.goto('/#/pages/guide/families');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:查看客户列表 ==========
+    await page.waitForSelector('.family-item, .client-family', { timeout: 10000 });
+
+    // ========== Then:返回客户家庭列表 ========
+    const familyItems = page.locator('.family-item, .client-family');
+    const count = await familyItems.count();
+    expect(count).toBeGreaterThan(0);
+
+    // 验证家庭信息显示
+    const firstFamily = familyItems.first();
+    const familyName = firstFamily.locator('.family-name, .family-title');
+    if (await familyName.isVisible()) {
+      const nameText = await familyName.textContent();
+      expect(nameText.length).toBeGreaterThan(0);
+    }
+  });
+
+  // ===== 场景7:完整绑定流程(端到端)=====
+
+  test('[场景7] 完整邀请绑定流程 - 期望全流程成功', async ({ page }) => {
+    // ========== Step 1:规划师生成邀请 ==========
+    await page.goto('/#/pages/guide/bind/invite');
+    await page.waitForLoadState('networkidle');
+
+    const generateBtn = page.locator('.generate-invite-btn, .create-invite-btn');
+    if (await generateBtn.isVisible()) {
+      await generateBtn.click();
+      await page.waitForSelector('.invite-token, .invite-link', { timeout: 10000 });
+    }
+
+    // 获取邀请链接
+    const inviteLink = page.locator('.invite-link');
+    let inviteUrl = '';
+    if (await inviteLink.isVisible()) {
+      inviteUrl = await inviteLink.getAttribute('data-url') || await inviteLink.textContent();
+    }
+
+    // ========== Step 2:家长验证邀请(模拟)==========
+    await page.goto('/#/pages/guide/bind/validate?token=invite_test_flow');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.guide-info, .invite-detail', { timeout: 10000 });
+
+    // ========== Step 3:家长确认绑定 ==========
+    await page.goto('/#/pages/guide/bind/confirm?token=invite_test_flow');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.guide-info', { timeout: 10000 });
+
+    const confirmBindBtn = page.locator('.confirm-bind-btn, .bind-confirm-btn');
+    if (await confirmBindBtn.isVisible()) {
+      await confirmBindBtn.click();
+      await page.waitForSelector('.success-tip, .bind-success', { timeout: 10000 });
+    }
+
+    // ========== Step 4:规划师查看客户列表更新 ==========
+    await page.goto('/#/pages/guide/families');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.family-item', { timeout: 10000 });
+
+    // 验证新绑定的家庭出现在列表中
+    const familyItems = page.locator('.family-item');
+    const count = await familyItems.count();
+    expect(count).toBeGreaterThan(0);
+  });
+
+});

+ 393 - 0
tests/e2e/product-purchase.spec.js

@@ -0,0 +1,393 @@
+/**
+ * ================================================================
+ * 场景:商品购买支付全流程 E2E 测试
+ * ================================================================
+ * 用户故事:作为家长,我希望在商城购买孩子的用品,
+ *           完成支付后等待商品配送到家。
+ *
+ * 流程步骤:
+ *   1. 家长浏览商品列表,选择商品
+ *   2. 家长创建商品订单
+ *   3. 家长选择支付方式并完成支付
+ *   4. 服务商发货,填写物流信息
+ *   5. 家长确认收货,订单完成
+ *   6. 如有问题,家长申请退款
+ *
+ * 关键里程碑:
+ *   - [Milestone-1] 订单创建成功,获得订单号
+ *   - [Milestone-2] 支付成功,订单状态变更为"已支付"
+ *   - [Milestone-3] 服务商发货,订单状态变更为"已发货"
+ *   - [Milestone-4] 家长确认收货,订单完成
+ *   - [Milestone-5] 退款申请提交成功
+ * ================================================================
+ *
+ * 运行:npx playwright test tests/e2e/product-purchase.spec.js
+ */
+
+const { test, expect } = require('@playwright/test');
+
+test.describe('【场景流程】商品购买支付全流程', () => {
+
+  // ===== 场景1:创建商品订单 =====
+
+  /**
+   * 场景:家长选购商品后创建订单
+   * 角色:家长
+   * 触发条件:点击"立即购买"或"加入购物车结算"
+   */
+  test('[场景1] 创建商品订单 - 期望订单创建成功', async ({ page }) => {
+    // ========== Given:进入商品详情页 ==========
+    await page.goto('/#/pages/shop/product/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择数量 ==========
+    const quantityInput = page.locator('.quantity-input, input[name="quantity"]');
+    if (await quantityInput.isVisible()) {
+      await quantityInput.fill('1');
+    } else {
+      // 尝试点击增加按钮
+      const increaseBtn = page.locator('.increase-btn, .btn-increase');
+      if (await increaseBtn.isVisible()) {
+        await increaseBtn.click();
+      }
+    }
+
+    // ========== Step 2:选择收货地址 ==========
+    const addressPicker = page.locator('.address-picker, .select-address');
+    if (await addressPicker.isVisible()) {
+      await addressPicker.click();
+      await page.waitForSelector('.address-option, .address-item', { timeout: 5000 });
+      await page.click('.address-option:first-child, .address-item:first-child');
+    }
+
+    // ========== When:点击立即购买 ==========
+    await page.click('.buy-now-btn, .purchase-btn');
+
+    // ========== Then:Milestone-1 - 订单创建成功 ========
+    await page.waitForSelector('.order-created, .order-success, .order-no', { timeout: 10000 });
+
+    // 验证订单号显示
+    const orderNo = page.locator('.order-no, .order-number');
+    expect(await orderNo.isVisible()).toBeTruthy();
+
+    const orderNoText = await orderNo.textContent();
+    expect(orderNoText).toMatch(/ORD|PRD|\d+/);
+  });
+
+  // ===== 场景2:支付订单 =====
+
+  /**
+   * 场景:家长选择微信支付完成订单支付
+   * 角色:家长
+   * 触发条件:点击"去支付"并选择支付方式
+   */
+  test('[场景2] 支付商品订单 - 期望支付成功', async ({ page }) => {
+    // ========== Given:进入订单支付页面 ==========
+    await page.goto('/#/pages/shop/pay?orderNo=ORD20260628001');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择支付方式 ==========
+    const wechatPayOption = page.locator('.pay-method:has-text("微信支付"), .wechat-pay');
+    if (await wechatPayOption.isVisible()) {
+      await wechatPayOption.click();
+    }
+
+    // ========== When:确认支付 ==========
+    await page.click('.confirm-pay-btn, .pay-now-btn');
+
+    // ========== Then:Milestone-2 - 支付成功 ========
+    await page.waitForSelector('.pay-success, .payment-success, .success-tip', { timeout: 30000 });
+
+    // 验证支付成功提示
+    const successTip = page.locator('.pay-success, .payment-success');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景3:服务商发货 =====
+
+  /**
+   * 场景:服务商收到订单后发货,填写物流信息
+   * 角色:服务商/供应商
+   * 触发条件:商家点击"发货"并填写物流单号
+   */
+  test('[场景3] 服务商发货 - 期望订单状态更新为已发货', async ({ page }) => {
+    // ========== Given:进入商家订单管理 ==========
+    await page.goto('/#/pages/vendor/orders');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到待发货订单 ==========
+    await page.waitForSelector('.order-item', { timeout: 10000 });
+    const paidOrder = page.locator('.order-item:has-text("已支付"), .order-item:has-text("待发货")').first();
+    await paidOrder.click();
+
+    // ========== Step 2:点击发货按钮 ==========
+    await page.waitForSelector('.ship-btn, .deliver-btn', { timeout: 5000 });
+    await page.click('.ship-btn');
+
+    // ========== Step 3:填写物流信息 ==========
+    await page.waitForSelector('.express-company, .tracking-company', { timeout: 5000 });
+
+    // 选择快递公司
+    const companyPicker = page.locator('.express-company, .tracking-company');
+    if (await companyPicker.isVisible()) {
+      await companyPicker.click();
+      await page.waitForSelector('.company-option', { timeout: 5000 });
+      await page.click('.company-option:first-child');
+    }
+
+    // 输入运单号
+    const trackingInput = page.locator('input[name="trackingNumber"], .tracking-input');
+    if (await trackingInput.isVisible()) {
+      await trackingInput.fill('SF' + Date.now());
+    }
+
+    // ========== When:确认发货 ==========
+    await page.click('.confirm-ship-btn, .submit-btn');
+
+    // ========== Then:Milestone-3 - 发货成功 ========
+    await page.waitForSelector('.ship-success, .shipped', { timeout: 10000 });
+
+    // 验证订单状态变更为"已发货"
+    const orderStatus = page.locator('.order-status, .status-tag');
+    if (await orderStatus.isVisible()) {
+      const statusText = await orderStatus.textContent();
+      expect(statusText).toMatch(/已发货|待收货/);
+    }
+
+    // 验证运单号显示
+    const trackingNo = page.locator('.tracking-number, .tracking-no');
+    if (await trackingNo.isVisible()) {
+      const trackingText = await trackingNo.textContent();
+      expect(trackingText).toMatch(/SF|\d{10,}/);
+    }
+  });
+
+  // ===== 场景4:家长确认收货 =====
+
+  /**
+   * 场景:家长收到商品后确认收货
+   * 角色:家长
+   * 触发条件:确认收到商品无误
+   */
+  test('[场景4] 家长确认收货 - 期望订单完成', async ({ page }) => {
+    // ========== Given:进入待收货订单 ==========
+    await page.goto('/#/pages/shop/orders?to=received');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到待收货订单 ==========
+    await page.waitForSelector('.order-item', { timeout: 10000 });
+    const shippedOrder = page.locator('.order-item:has-text("已发货"), .order-item:has-text("待收货")').first();
+    await shippedOrder.click();
+
+    // ========== When:点击确认收货 ==========
+    await page.waitForSelector('.confirm-receive-btn, .receive-btn', { timeout: 5000 });
+    await page.click('.confirm-receive-btn, .receive-btn');
+
+    // ========== Then:Milestone-4 - 订单完成 ========
+    await page.waitForSelector('.receive-success, .completed', { timeout: 10000 });
+
+    // 验证订单状态变更为"已完成"
+    const orderStatus = page.locator('.order-status, .status-tag');
+    if (await orderStatus.isVisible()) {
+      const statusText = await orderStatus.textContent();
+      expect(statusText).toMatch(/已完成|已收货/);
+    }
+  });
+
+  // ===== 场景5:申请退款 =====
+
+  /**
+   * 场景:家长发现问题,申请退款
+   * 角色:家长
+   * 触发条件:点击"申请退款"并填写原因
+   */
+  test('[场景5] 申请退款 - 期望退款申请提交成功', async ({ page }) => {
+    // ========== Given:进入订单详情 ==========
+    await page.goto('/#/pages/shop/order-detail?orderNo=ORD20260628002');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:点击申请退款 ==========
+    await page.waitForSelector('.refund-btn, .apply-refund-btn', { timeout: 10000 });
+    await page.click('.refund-btn');
+
+    // ========== Step 2:填写退款原因 ==========
+    await page.waitForSelector('.refund-reason-input, .reason-textarea', { timeout: 5000 });
+    const reasonInput = page.locator('.refund-reason-input textarea, .reason-textarea');
+    if (await reasonInput.isVisible()) {
+      await reasonInput.fill('商品与描述不符,收到后发现质量问题');
+    }
+
+    // ========== When:提交退款申请 ==========
+    await page.click('.submit-refund-btn, .confirm-refund-btn');
+
+    // ========== Then:Milestone-5 - 退款申请成功 ========
+    await page.waitForSelector('.refund-submitted, .refund-success', { timeout: 10000 });
+
+    // 验证退款状态
+    const refundStatus = page.locator('.refund-status, .status-tag');
+    if (await refundStatus.isVisible()) {
+      const statusText = await refundStatus.textContent();
+      expect(statusText).toMatch(/退款|审核中/);
+    }
+  });
+
+  // ===== 场景6:取消未支付订单 =====
+
+  /**
+   * 场景:家长改变主意,取消未支付的订单
+   * 角色:家长
+   * 触发条件:点击"取消订单"
+   */
+  test('[场景6] 取消未支付订单 - 期望订单关闭', async ({ page }) => {
+    // ========== Given:进入我的订单 ==========
+    await page.goto('/#/pages/shop/orders');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到待支付订单 ==========
+    await page.waitForSelector('.order-item', { timeout: 10000 });
+    const pendingOrder = page.locator('.order-item:has-text("待支付"), .order-item:has-text("待付款")').first();
+
+    // 点击进入订单详情
+    await pendingOrder.click();
+
+    // ========== Step 2:点击取消订单 ==========
+    await page.waitForSelector('.cancel-order-btn', { timeout: 5000 });
+    await page.click('.cancel-order-btn');
+
+    // 确认取消
+    await page.waitForSelector('.confirm-dialog, .van-dialog', { timeout: 5000 });
+    await page.click('.confirm-dialog .van-button--primary, .confirm-cancel');
+
+    // ========== Then:订单关闭 ========
+    await page.waitForSelector('.cancel-success, .order-closed', { timeout: 10000 });
+
+    const orderStatus = page.locator('.order-status, .status-tag');
+    if (await orderStatus.isVisible()) {
+      const statusText = await orderStatus.textContent();
+      expect(statusText).toMatch(/已取消|已关闭/);
+    }
+  });
+
+  // ===== 场景7:查看我的订单列表 =====
+
+  /**
+   * 场景:家长查看自己的订单列表
+   * 角色:家长
+   * 触发条件:进入"我的订单"页面
+   */
+  test('[场景7] 查看我的订单列表 - 期望返回订单列表', async ({ page }) => {
+    // ========== Given:进入我的订单页面 ==========
+    await page.goto('/#/pages/shop/orders');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:查看订单列表 ==========
+    await page.waitForSelector('.order-item', { timeout: 10000 });
+
+    // ========== Then:返回订单列表 ========
+    const orderItems = page.locator('.order-item');
+    const count = await orderItems.count();
+    expect(count).toBeGreaterThan(0);
+
+    // 验证订单状态显示
+    const firstOrder = orderItems.first();
+    const statusTag = firstOrder.locator('.status-tag, .order-status');
+    expect(await statusTag.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景8:查看订单详情 =====
+
+  /**
+   * 场景:家长查看具体订单的详细信息
+   * 角色:家长
+   * 触发条件:点击某个订单查看详情
+   */
+  test('[场景8] 查看订单详情 - 期望返回完整订单信息', async ({ page }) => {
+    // ========== Given:进入订单详情 ==========
+    await page.goto('/#/pages/shop/order-detail?orderNo=ORD20260628001');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:查看订单详情 ==========
+    await page.waitForSelector('.order-detail, .detail-container', { timeout: 10000 });
+
+    // ========== Then:返回完整订单信息 ========
+    // 验证订单号显示
+    const orderNo = page.locator('.order-no, .order-number');
+    expect(await orderNo.isVisible()).toBeTruthy();
+
+    // 验证商品信息显示
+    const productInfo = page.locator('.product-info, .product-name');
+    expect(await productInfo.isVisible()).toBeTruthy();
+
+    // 验证金额显示
+    const amountInfo = page.locator('.total-amount, .order-amount');
+    expect(await amountInfo.isVisible()).toBeTruthy();
+
+    // 验证物流信息(如果有)
+    const logisticsInfo = page.locator('.logistics-info, .tracking-info');
+    // 物流信息可能不显示(未发货时),但不应该报错
+  });
+
+  // ===== 场景9:完整商品购买流程(端到端)=====
+
+  test('[场景9] 完整商品购买流程 - 期望全流程成功', async ({ page }) => {
+    // ========== Step 1:浏览商品 ==========
+    await page.goto('/#/pages/shop/list');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.product-item', { timeout: 10000 });
+
+    // 点击第一个商品
+    await page.locator('.product-item').first().click();
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 2:创建订单 ==========
+    const buyBtn = page.locator('.buy-now-btn, .purchase-btn');
+    if (await buyBtn.isVisible()) {
+      await buyBtn.click();
+      await page.waitForSelector('.order-no, .order-created', { timeout: 10000 });
+    }
+
+    // 获取订单号
+    let orderNo = '';
+    const orderNoEl = page.locator('.order-no');
+    if (await orderNoEl.isVisible()) {
+      orderNo = await orderNoEl.textContent();
+    }
+
+    // ========== Step 3:支付订单 ==========
+    const payBtn = page.locator('.go-pay-btn, .pay-now-btn');
+    if (await payBtn.isVisible()) {
+      await payBtn.click();
+
+      // 选择微信支付
+      const wechatPay = page.locator('.pay-method:has-text("微信")');
+      if (await wechatPay.isVisible()) {
+        await wechatPay.click();
+      }
+
+      await page.click('.confirm-pay-btn');
+      await page.waitForSelector('.pay-success, .payment-success', { timeout: 30000 });
+    }
+
+    // ========== Step 4:查看我的订单 ==========
+    await page.goto('/#/pages/shop/orders');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.order-item', { timeout: 10000 });
+
+    // 验证订单存在
+    const orderItems = page.locator('.order-item');
+    const count = await orderItems.count();
+    expect(count).toBeGreaterThan(0);
+
+    // 如果有已发货的订单,确认收货
+    const shippedOrder = page.locator('.order-item:has-text("已发货")').first();
+    if (await shippedOrder.isVisible()) {
+      await shippedOrder.click();
+      const receiveBtn = page.locator('.confirm-receive-btn');
+      if (await receiveBtn.isVisible()) {
+        await receiveBtn.click();
+        await page.waitForSelector('.receive-success', { timeout: 10000 });
+      }
+    }
+  });
+
+});

+ 472 - 0
tests/e2e/wish-exchange-flow.spec.js

@@ -0,0 +1,472 @@
+/**
+ * ================================================================
+ * 场景:心愿兑换审批全流程 E2E 测试
+ * ================================================================
+ * 用户故事:作为孩子,我希望创建心愿并通过积累积分兑换,
+ *           以获得家长的认可和实际的奖励物品。
+ *
+ * 流程步骤:
+ *   1. 孩子创建心愿(如"想要一套科学实验套装")
+ *   2. 家长查看心愿并设定所需积分(如 500 积分)
+ *   3. 孩子通过完成任务积累积分后,申请兑换心愿
+ *   4. 家长审批通过,系统自动扣除积分
+ *   5. 家长购买物品后标记"已购买"
+ *   6. 孩子确认收货,心愿流程完成
+ *
+ * 关键里程碑:
+ *   - [Milestone-1] 心愿创建成功,获得心愿ID
+ *   - [Milestone-2] 家长定价成功,心愿状态变更为"已定价"
+ *   - [Milestone-3] 兑换申请提交,状态变更为"兑换中"
+ *   - [Milestone-4] 家长审批通过,积分立即扣减
+ *   - [Milestone-5] 标记已购买,等待孩子确认
+ *   - [Milestone-6] 孩子确认收货,心愿完成
+ * ================================================================
+ *
+ * 运行:npx playwright test tests/e2e/wish-exchange-flow.spec.js
+ */
+
+const { test, expect } = require('@playwright/test');
+
+test.describe('【场景流程】心愿兑换审批全流程', () => {
+
+  // ===== 场景1:孩子创建心愿 =====
+
+  /**
+   * 场景:孩子创建一个新的心愿
+   * 角色:孩子
+   * 触发条件:孩子点击"创建心愿"并填写表单
+   */
+  test('[场景1] 孩子创建心愿 - 期望心愿创建成功', async ({ page }) => {
+    // ========== Given:进入创建心愿页面 ==========
+    await page.goto('/#/pages/wishes/create');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:填写心愿信息 ==========
+    // 输入心愿标题
+    const titleInput = page.locator('input[placeholder*="心愿"], .wish-title-input, input[name="title"]');
+    if (await titleInput.isVisible()) {
+      await titleInput.fill('科学实验套装');
+    }
+
+    // 输入心愿描述
+    const descInput = page.locator('textarea[placeholder*="描述"], .wish-desc-input');
+    if (await descInput.isVisible()) {
+      await descInput.fill('包含50个实验的科学实验套装,寓教于乐');
+    }
+
+    // 输入预估价格
+    const priceInput = page.locator('input[placeholder*="价格"], .wish-price-input, input[name="estimatedPrice"]');
+    if (await priceInput.isVisible()) {
+      await priceInput.fill('299');
+    }
+
+    // ========== Then:提交创建 ==========
+    await page.click('.submit-btn, .create-btn');
+
+    // ========== Then:Milestone-1 - 心愿创建成功 ========
+    await page.waitForSelector('.success-tip, .wish-created, .create-success', { timeout: 10000 });
+
+    // 验证心愿创建成功
+    const successTip = page.locator('.success-tip, .wish-created, .create-success');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景2:家长定价 =====
+
+  /**
+   * 场景:家长为孩子的心愿设定所需积分
+   * 角色:家长
+   * 触发条件:家长打开心愿详情,决定设定多少积分可兑换
+   */
+  test('[场景2] 家长设定心愿积分 - 期望积分设定成功且状态更新', async ({ page }) => {
+    // ========== Given:进入待定价心愿列表 ==========
+    await page.goto('/#/pages/wishes/pending');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到待定价心愿 ==========
+    await page.waitForSelector('.wish-item', { timeout: 10000 });
+    const pendingWish = page.locator('.wish-item:has-text("待定价"), .wish-item:has-text("待设置")').first();
+    await pendingWish.click();
+
+    // ========== Step 2:设定积分值 ==========
+    await page.waitForSelector('.price-input, .points-input', { timeout: 5000 });
+    const priceInput = page.locator('.price-input, .points-input, input[name="pointsRequired"]');
+    await priceInput.fill('500');
+
+    // ========== When:提交定价 ==========
+    await page.click('.set-price-btn, .confirm-price-btn, .submit-btn');
+
+    // ========== Then:Milestone-2 - 定价成功 ========
+    await page.waitForSelector('.success-tip, .price-set, .set-success', { timeout: 10000 });
+
+    // 验证状态变更
+    const wishStatus = page.locator('.wish-status, .status-tag');
+    if (await wishStatus.isVisible()) {
+      const statusText = await wishStatus.textContent();
+      expect(statusText).toMatch(/已定价|待兑换/);
+    }
+  });
+
+  /**
+   * 场景:家长设定的积分值为负数或零
+   * 角色:家长
+   * 触发条件:表单校验未通过
+   */
+  test('[场景2b] 家长设定无效积分值 - 期望返回错误提示', async ({ page }) => {
+    // ========== Given:进入心愿编辑页面 ==========
+    await page.goto('/#/pages/wishes/edit/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:设定无效积分值 ==========
+    const priceInput = page.locator('.price-input, .points-input, input[name="pointsRequired"]');
+    if (await priceInput.isVisible()) {
+      await priceInput.fill('0');
+    }
+
+    await page.click('.submit-btn');
+
+    // ========== Then:返回错误提示 ========
+    await page.waitForSelector('.error-tip, .van-field__error, .form-error', { timeout: 5000 });
+
+    const errorTip = page.locator('.error-tip, .van-field__error, .form-error');
+    expect(await errorTip.isVisible()).toBeTruthy();
+  });
+
+  // ===== 场景3:孩子申请兑换 =====
+
+  /**
+   * 场景:孩子积累足够积分后,申请兑换心愿
+   * 角色:孩子
+   * 触发条件:孩子点击心愿的"申请兑换"按钮
+   */
+  test('[场景3] 孩子申请兑换心愿 - 期望兑换申请提交成功', async ({ page }) => {
+    // ========== Given:进入我的心愿列表 ==========
+    await page.goto('/#/pages/wishes/my');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到已定价心愿 ==========
+    await page.waitForSelector('.wish-item', { timeout: 10000 });
+    const pricedWish = page.locator('.wish-item:has-text("已定价"), .wish-item:has-text("可兑换")').first();
+    await pricedWish.click();
+
+    // ========== Step 2:点击兑换按钮 ==========
+    await page.waitForSelector('.exchange-btn, .apply-exchange-btn', { timeout: 5000 });
+    await page.click('.exchange-btn, .apply-exchange-btn');
+
+    // ========== Then:Milestone-3 - 兑换申请提交成功 ========
+    await page.waitForSelector('.success-tip, .exchange-applied, .apply-success', { timeout: 10000 });
+
+    const successTip = page.locator('.success-tip, .exchange-applied, .apply-success');
+    expect(await successTip.isVisible()).toBeTruthy();
+  });
+
+  /**
+   * 场景:孩子积分不足时尝试兑换
+   * 角色:孩子
+   * 触发条件:孩子积分不足以兑换该心愿
+   */
+  test('[场景3b] 积分不足时申请兑换 - 期望返回错误提示', async ({ page }) => {
+    // ========== Given:进入心愿详情 ==========
+    await page.goto('/#/pages/wishes/detail/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:点击兑换 ==========
+    const exchangeBtn = page.locator('.exchange-btn, .apply-exchange-btn');
+    if (await exchangeBtn.isVisible()) {
+      await exchangeBtn.click();
+
+      // ========== Then:返回积分不足提示 ========
+      await page.waitForSelector('.error-tip, .insufficient-points, .points-error', { timeout: 5000 });
+
+      const errorTip = page.locator('.error-tip, .insufficient-points, .points-error');
+      expect(await errorTip.isVisible()).toBeTruthy();
+
+      // 验证错误信息包含积分相关字样
+      const errorText = await errorTip.textContent();
+      expect(errorText).toMatch(/积分|不足/);
+    }
+  });
+
+  // ===== 场景4:家长审批兑换 =====
+
+  /**
+   * 场景:家长审批通过孩子的兑换申请,积分立即扣除
+   * 角色:家长
+   * 触发条件:家长查看兑换申请并点击"批准"
+   */
+  test('[场景4] 家长审批通过兑换 - 期望审批成功且积分扣除', async ({ page }) => {
+    // ========== Given:进入待审批兑换列表 ==========
+    await page.goto('/#/pages/wishes/pending-approval');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到待审批的兑换申请 ==========
+    await page.waitForSelector('.exchange-item, .approval-item', { timeout: 10000 });
+    const pendingApproval = page.locator('.exchange-item:has-text("申请中"), .approval-item:has-text("待审批")').first();
+    await pendingApproval.click();
+
+    // ========== Step 2:查看积分信息 ==========
+    await page.waitForSelector('.points-required, .wish-points', { timeout: 5000 });
+
+    // ========== When:点击批准 ==========
+    await page.click('.approve-btn, .confirm-btn, .approve-exchange-btn');
+
+    // ========== Then:Milestone-4 - 审批通过 + 积分扣减 ========
+    await page.waitForSelector('.success-tip, .approved, .approve-success', { timeout: 10000 });
+
+    // 验证积分已扣减(如果有显示)
+    const remainingPoints = page.locator('.remaining-points, .current-points');
+    if (await remainingPoints.isVisible()) {
+      const pointsText = await remainingPoints.textContent();
+      expect(pointsText).toMatch(/\d+/);
+    }
+  });
+
+  /**
+   * 场景:家长拒绝孩子的兑换申请
+   * 角色:家长
+   * 触发条件:家长认为该心愿不合适,点击"拒绝"
+   */
+  test('[场景4b] 家长拒绝兑换申请 - 期望心愿状态回退', async ({ page }) => {
+    // ========== Given:进入待审批兑换详情 ==========
+    await page.goto('/#/pages/wishes/approval/1');
+    await page.waitForLoadState('networkidle');
+
+    // ========== When:点击拒绝 ==========
+    await page.click('.reject-btn, .reject-exchange-btn');
+
+    // 填写拒绝原因
+    const reasonInput = page.locator('textarea[placeholder*="原因"], .reject-reason-input');
+    if (await reasonInput.isVisible()) {
+      await reasonInput.fill('本月预算已用完,下个月再兑换好吗?');
+    }
+
+    // 确认拒绝
+    await page.click('.confirm-reject-btn, .submit-btn');
+
+    // ========== Then:拒绝成功,心愿状态回退 ========
+    await page.waitForSelector('.rejected, .reject-success', { timeout: 10000 });
+
+    // 验证心愿状态回退到"已定价"
+    const wishStatus = page.locator('.wish-status, .status-tag');
+    if (await wishStatus.isVisible()) {
+      const statusText = await wishStatus.textContent();
+      expect(statusText).toMatch(/已定价|待兑换/);
+    }
+  });
+
+  // ===== 场景5:标记已购买 =====
+
+  /**
+   * 场景:家长购买物品后标记心愿为"已购买"
+   * 角色:家长
+   * 触发条件:家长完成购买操作
+   */
+  test('[场景5] 家长标记心愿已购买 - 期望状态变更为待收货', async ({ page }) => {
+    // ========== Given:进入已批准兑换列表 ==========
+    await page.goto('/#/pages/wishes/approved');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到已批准的兑换 ==========
+    await page.waitForSelector('.exchange-item, .approved-item', { timeout: 10000 });
+    const approvedItem = page.locator('.exchange-item:has-text("已批准"), .approved-item').first();
+    await approvedItem.click();
+
+    // ========== When:点击"已购买" ==========
+    await page.click('.mark-purchased-btn, .fulfill-btn');
+
+    // ========== Then:Milestone-5 - 标记成功 ========
+    await page.waitForSelector('.success-tip, .purchased, .fulfill-success', { timeout: 10000 });
+
+    // 验证状态变更为"待收货"
+    const wishStatus = page.locator('.wish-status, .status-tag');
+    if (await wishStatus.isVisible()) {
+      const statusText = await wishStatus.textContent();
+      expect(statusText).toMatch(/待收货|已购买/);
+    }
+  });
+
+  // ===== 场景6:孩子确认收货 =====
+
+  /**
+   * 场景:孩子收到物品后确认收货,心愿流程完成
+   * 角色:孩子
+   * 触发条件:孩子收到实物并确认无误
+   */
+  test('[场景6] 孩子确认收货 - 期望心愿流程完成', async ({ page }) => {
+    // ========== Given:进入待确认收货列表 ==========
+    await page.goto('/#/pages/wishes/to-confirm');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:找到待确认的心愿 ==========
+    await page.waitForSelector('.wish-item', { timeout: 10000 });
+    const toConfirm = page.locator('.wish-item:has-text("待收货"), .wish-item:has-text("已购买")').first();
+    await toConfirm.click();
+
+    // ========== When:点击"确认收货" ==========
+    await page.click('.confirm-receive-btn, .confirm-btn');
+
+    // ========== Then:Milestone-6 - 心愿完成 ========
+    await page.waitForSelector('.success-tip, .completed, .confirm-success', { timeout: 10000 });
+
+    // 验证状态变更为"已完成"
+    const wishStatus = page.locator('.wish-status, .status-tag');
+    if (await wishStatus.isVisible()) {
+      const statusText = await wishStatus.textContent();
+      expect(statusText).toMatch(/已完成|已收货/);
+    }
+  });
+
+  // ===== 场景7:完整心愿流程(端到端)=====
+
+  test('[场景7] 完整心愿生命周期 - 期望全流程成功', async ({ page }) => {
+    // ========== Step 1:孩子创建心愿 ==========
+    await page.goto('/#/pages/wishes/create');
+    await page.waitForLoadState('networkidle');
+
+    const titleInput = page.locator('input[name="title"], .wish-title-input');
+    if (await titleInput.isVisible()) {
+      await titleInput.fill('科学杂志订阅_' + Date.now());
+    }
+
+    const descInput = page.locator('textarea[name="description"], .wish-desc-input');
+    if (await descInput.isVisible()) {
+      await descInput.fill('儿童科学爱好者订阅一年');
+    }
+
+    await page.click('.submit-btn');
+    await page.waitForSelector('.success-tip, .wish-created', { timeout: 10000 });
+
+    // ========== Step 2:家长定价 ==========
+    await page.goto('/#/pages/wishes/pending');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.wish-item', { timeout: 10000 });
+
+    const wishItem = page.locator('.wish-item').first();
+    const editBtn = wishItem.locator('.edit-btn, .price-btn');
+    if (await editBtn.isVisible()) {
+      await editBtn.click();
+      await page.waitForSelector('.price-input', { timeout: 5000 });
+      const priceInput = page.locator('.price-input, input[name="pointsRequired"]');
+      await priceInput.fill('100');
+      await page.click('.set-price-btn');
+      await page.waitForSelector('.success-tip', { timeout: 10000 });
+    }
+
+    // ========== Step 3:孩子申请兑换 ==========
+    await page.goto('/#/pages/wishes/my');
+    await page.waitForLoadState('networkidle');
+    await page.waitForSelector('.wish-item', { timeout: 10000 });
+
+    const exchangeBtn = page.locator('.exchange-btn').first();
+    if (await exchangeBtn.isVisible()) {
+      await exchangeBtn.click();
+      await page.waitForSelector('.success-tip, .exchange-applied', { timeout: 10000 });
+    }
+
+    // ========== Step 4:家长审批通过 ==========
+    await page.goto('/#/pages/wishes/pending-approval');
+    await page.waitForLoadState('networkidle');
+
+    const approvalItem = page.locator('.exchange-item, .approval-item').first();
+    if (await approvalItem.isVisible()) {
+      await approvalItem.click();
+      await page.waitForSelector('.approve-btn', { timeout: 5000 });
+      await page.click('.approve-btn');
+      await page.waitForSelector('.success-tip, .approved', { timeout: 10000 });
+    }
+
+    // ========== Step 5:家长标记已购买 ==========
+    await page.goto('/#/pages/wishes/approved');
+    await page.waitForLoadState('networkidle');
+
+    const purchasedBtn = page.locator('.mark-purchased-btn, .fulfill-btn').first();
+    if (await purchasedBtn.isVisible()) {
+      await purchasedBtn.click();
+      await page.waitForSelector('.success-tip, .purchased', { timeout: 10000 });
+    }
+
+    // ========== Step 6:孩子确认收货 ==========
+    await page.goto('/#/pages/wishes/to-confirm');
+    await page.waitForLoadState('networkidle');
+
+    const confirmBtn = page.locator('.confirm-receive-btn').first();
+    if (await confirmBtn.isVisible()) {
+      await confirmBtn.click();
+      await page.waitForSelector('.success-tip, .completed', { timeout: 10000 });
+    }
+
+    // 验证最终状态
+    const finalStatus = page.locator('.wish-status');
+    if (await finalStatus.isVisible()) {
+      const statusText = await finalStatus.textContent();
+      expect(statusText).toMatch(/已完成/);
+    }
+  });
+
+  // ===== 场景8:家长拒绝心愿 =====
+
+  test('[场景8] 家长直接拒绝心愿 - 期望心愿被关闭', async ({ page }) => {
+    // ========== Given:进入待处理心愿 ==========
+    await page.goto('/#/pages/wishes/pending');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择心愿 ==========
+    await page.waitForSelector('.wish-item', { timeout: 10000 });
+    const wishItem = page.locator('.wish-item').first();
+    await wishItem.click();
+
+    // ========== When:点击拒绝 ==========
+    await page.waitForSelector('.reject-btn, .refuse-btn', { timeout: 5000 });
+    await page.click('.reject-btn');
+
+    // 填写原因
+    const reasonInput = page.locator('textarea[name="reason"], .reject-reason-input');
+    if (await reasonInput.isVisible()) {
+      await reasonInput.fill('这套书家里已经有了,换个其他的吧');
+    }
+
+    await page.click('.confirm-reject-btn');
+
+    // ========== Then:心愿被关闭 ========
+    await page.waitForSelector('.rejected, .reject-success', { timeout: 10000 });
+
+    const wishStatus = page.locator('.wish-status, .status-tag');
+    if (await wishStatus.isVisible()) {
+      const statusText = await wishStatus.textContent();
+      expect(statusText).toMatch(/已拒绝|已关闭/);
+    }
+  });
+
+  // ===== 场景9:孩子取消心愿 =====
+
+  test('[场景9] 孩子取消心愿 - 期望心愿被取消', async ({ page }) => {
+    // ========== Given:进入我的心愿 ==========
+    await page.goto('/#/pages/wishes/my');
+    await page.waitForLoadState('networkidle');
+
+    // ========== Step 1:选择心愿 ==========
+    await page.waitForSelector('.wish-item', { timeout: 10000 });
+    const wishItem = page.locator('.wish-item').first();
+
+    // 点击取消按钮
+    const cancelBtn = wishItem.locator('.cancel-btn, .cancel-wish-btn');
+    if (await cancelBtn.isVisible()) {
+      await cancelBtn.click();
+
+      // 确认取消
+      await page.waitForSelector('.confirm-dialog, .van-dialog', { timeout: 5000 });
+      await page.click('.confirm-dialog .van-button--primary, .confirm-cancel-btn');
+
+      // ========== Then:心愿被取消 ========
+      await page.waitForTimeout(1000);
+
+      // 验证心愿不在列表中或状态变更
+      const statusTag = wishItem.locator('.status-tag');
+      if (await statusTag.isVisible()) {
+        const statusText = await statusTag.textContent();
+        expect(statusText).toMatch(/已取消|已关闭/);
+      }
+    }
+  });
+
+});