energy-system.spec.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /**
  2. * ================================================================
  3. * 场景:能量系统收支全流程 E2E 测试
  4. * ================================================================
  5. * 用户故事:作为家长,我希望了解和管理家庭成员的五维能量收支情况,
  6. * 通过完成任务获取能量,用能量兑换奖励。
  7. *
  8. * 流程步骤:
  9. * 1. 查看家庭能量沙盘,了解各维度能量分布
  10. * 2. 孩子完成任务,获得能量奖励(收入)
  11. * 3. 使用能量兑换心愿或参与活动(支出)
  12. * 4. 系统根据收支计算新的维度比例
  13. * 5. 综合健康指数更新
  14. *
  15. * 关键里程碑:
  16. * - [Milestone-1] 获取能量沙盘,了解当前能量状态
  17. * - [Milestone-2] 任务完成,能量发放成功
  18. * - [Milestone-3] 能量兑换,积分扣减
  19. * - [Milestone-4] 维度比例重算
  20. * - [Milestone-5] 健康指数更新
  21. * ================================================================
  22. *
  23. * 运行:npx playwright test tests/e2e/energy-system.spec.js
  24. */
  25. const { test, expect } = require('@playwright/test');
  26. test.describe('【场景流程】能量系统收支全流程', () => {
  27. // ===== 场景1:获取家庭能量沙盘 =====
  28. /**
  29. * 场景:家长查看家庭的五维能量分布
  30. * 角色:家长
  31. * 触发条件:进入能量沙盘页面
  32. */
  33. test('[场景1] 获取家庭能量沙盘 - 期望返回五维能量数据', async ({ page }) => {
  34. // ========== Given:进入能量沙盘页面 ==========
  35. await page.goto('/#/pages/energy/sandbox');
  36. await page.waitForLoadState('networkidle');
  37. // ========== When:页面加载完成 ==========
  38. await page.waitForSelector('.energy-sandbox, .sandbox-container', { timeout: 10000 });
  39. // ========== Then:Milestone-1 - 返回完整能量数据 ========
  40. // 验证五维能量显示
  41. const bodyEnergy = page.locator('.body-energy, .energy-body');
  42. const mindEnergy = page.locator('.mind-energy, .energy-mind');
  43. const wisdomEnergy = page.locator('.wisdom-energy, .energy-wisdom');
  44. const actionEnergy = page.locator('.action-energy, .energy-action');
  45. const wealthEnergy = page.locator('.wealth-energy, .energy-wealth');
  46. expect(await bodyEnergy.isVisible()).toBeTruthy();
  47. expect(await mindEnergy.isVisible()).toBeTruthy();
  48. expect(await wisdomEnergy.isVisible()).toBeTruthy();
  49. expect(await actionEnergy.isVisible()).toBeTruthy();
  50. expect(await wealthEnergy.isVisible()).toBeTruthy();
  51. // 验证健康指数显示
  52. const healthIndex = page.locator('.health-index, .index-value');
  53. if (await healthIndex.isVisible()) {
  54. const indexText = await healthIndex.textContent();
  55. expect(indexText).toMatch(/\d+/);
  56. }
  57. });
  58. /**
  59. * 场景:用户未加入任何家庭
  60. * 角色:用户
  61. * 触发条件:用户尝试查看能量但未加入家庭
  62. */
  63. test('[场景1b] 用户未加入家庭 - 期望返回错误提示', async ({ page }) => {
  64. // ========== Given:用户未加入家庭(使用模拟数据)==========
  65. await page.goto('/#/pages/energy/sandbox');
  66. await page.waitForLoadState('networkidle');
  67. // ========== When:请求能量沙盘 ==========
  68. await page.waitForTimeout(2000);
  69. // ========== Then:返回错误或空状态 ========
  70. const errorTip = page.locator('.error-tip, .no-family-tip');
  71. const emptyState = page.locator('.empty-state, .no-data');
  72. const hasError = await errorTip.isVisible();
  73. const hasEmpty = await emptyState.isVisible();
  74. expect(hasError || hasEmpty).toBeTruthy();
  75. });
  76. // ===== 场景2:任务完成能量发放 =====
  77. /**
  78. * 场景:孩子完成任务后,系统自动发放能量奖励
  79. * 角色:系统(自动触发)
  80. * 触发条件:任务状态变更为"已完成"
  81. */
  82. test('[场景2] 任务完成能量发放 - 期望能量增加且流水记录', async ({ page }) => {
  83. // ========== Given:完成任务后进入任务详情 ==========
  84. await page.goto('/#/pages/tasks/complete/1');
  85. await page.waitForLoadState('networkidle');
  86. // ========== When:查看任务完成状态 ==========
  87. await page.waitForSelector('.task-complete, .task-done', { timeout: 10000 });
  88. // ========== Then:Milestone-2 - 能量发放成功 ========
  89. // 验证能量奖励显示
  90. const energyReward = page.locator('.energy-reward, .reward-amount');
  91. if (await energyReward.isVisible()) {
  92. const rewardText = await energyReward.textContent();
  93. expect(rewardText).toMatch(/\d+/);
  94. }
  95. // 验证能量发放提示
  96. const grantTip = page.locator('.energy-granted, .grant-success');
  97. if (await grantTip.isVisible()) {
  98. expect(await grantTip.isVisible()).toBeTruthy();
  99. }
  100. });
  101. // ===== 场景3:能量兑换心愿 =====
  102. /**
  103. * 场景:孩子使用积累的能量兑换心愿
  104. * 角色:孩子
  105. * 触发条件:兑换心愿时选择能量支付
  106. */
  107. test('[场景3] 能量兑换心愿 - 期望能量扣减', async ({ page }) => {
  108. // ========== Given:进入心愿兑换页面 ==========
  109. await page.goto('/#/pages/wishes/exchange/1');
  110. await page.waitForLoadState('networkidle');
  111. // ========== Step 1:选择能量支付 ==========
  112. const energyPayOption = page.locator('.pay-method:has-text("能量"), .energy-pay');
  113. if (await energyPayOption.isVisible()) {
  114. await energyPayOption.click();
  115. }
  116. // ========== When:确认兑换 ==========
  117. await page.click('.confirm-exchange-btn, .exchange-btn');
  118. // ========== Then:Milestone-3 - 能量扣减成功 ========
  119. await page.waitForSelector('.exchange-success, .energy-deducted, .success-tip', { timeout: 10000 });
  120. // 验证能量扣减提示
  121. const deductTip = page.locator('.energy-deducted, .deduct-success');
  122. expect(await deductTip.isVisible()).toBeTruthy();
  123. // 验证剩余能量显示
  124. const remainingEnergy = page.locator('.remaining-energy, .current-energy');
  125. if (await remainingEnergy.isVisible()) {
  126. const energyText = await remainingEnergy.textContent();
  127. expect(energyText).toMatch(/\d+/);
  128. }
  129. });
  130. /**
  131. * 场景:能量不足时兑换心愿
  132. * 角色:孩子
  133. * 触发条件:当前能量不足以支付
  134. */
  135. test('[场景3b] 能量不足时兑换 - 期望返回错误', async ({ page }) => {
  136. // ========== Given:能量不足 ==========
  137. await page.goto('/#/pages/wishes/exchange/999');
  138. await page.waitForLoadState('networkidle');
  139. // ========== When:点击兑换 ==========
  140. const exchangeBtn = page.locator('.exchange-btn, .confirm-exchange-btn');
  141. if (await exchangeBtn.isVisible()) {
  142. await exchangeBtn.click();
  143. // ========== Then:返回错误 ========
  144. await page.waitForSelector('.error-tip, .insufficient-energy', { timeout: 5000 });
  145. const errorTip = page.locator('.error-tip, .insufficient-energy');
  146. expect(await errorTip.isVisible()).toBeTruthy();
  147. expect(await errorTip.textContent()).toMatch(/能量|不足/);
  148. }
  149. });
  150. // ===== 场景4:查询能量流水 =====
  151. /**
  152. * 场景:查看能量收支明细
  153. * 角色:家长或孩子
  154. * 触发条件:进入能量明细页面
  155. */
  156. test('[场景4] 查询能量流水 - 期望返回分页记录', async ({ page }) => {
  157. // ========== Given:进入能量流水页面 ==========
  158. await page.goto('/#/pages/energy/logs');
  159. await page.waitForLoadState('networkidle');
  160. // ========== Step 1:选择孩子(如果是家长)==========
  161. const childPicker = page.locator('.child-picker');
  162. if (await childPicker.isVisible()) {
  163. await childPicker.click();
  164. await page.waitForSelector('.child-option', { timeout: 5000 });
  165. await page.click('.child-option:first-child');
  166. }
  167. // ========== When:查看流水列表 ==========
  168. await page.waitForSelector('.log-item, .energy-log', { timeout: 10000 });
  169. // ========== Then:返回流水列表 ========
  170. const logItems = page.locator('.log-item, .energy-log');
  171. const count = await logItems.count();
  172. expect(count).toBeGreaterThan(0);
  173. // 验证流水类型显示(收入/支出)
  174. const firstLog = logItems.first();
  175. const logType = firstLog.locator('.log-type, .type-tag');
  176. if (await logType.isVisible()) {
  177. const typeText = await logType.textContent();
  178. expect(typeText).toMatch(/收入|支出|奖励|消耗/);
  179. }
  180. });
  181. // ===== 场景5:获取五维能量概览 =====
  182. /**
  183. * 场景:查看孩子的五维能量概览
  184. * 角色:家长
  185. * 触发条件:查看孩子的能量总览
  186. */
  187. test('[场景5] 获取五维能量概览 - 期望返回五维汇总数据', async ({ page }) => {
  188. // ========== Given:进入能量概览页面 ==========
  189. await page.goto('/#/pages/energy/overview');
  190. await page.waitForLoadState('networkidle');
  191. // ========== Step 1:选择孩子 ==========
  192. const childPicker = page.locator('.child-picker');
  193. if (await childPicker.isVisible()) {
  194. await childPicker.click();
  195. await page.waitForSelector('.child-option', { timeout: 5000 });
  196. await page.click('.child-option:first-child');
  197. }
  198. // ========== When:查看能量概览 ==========
  199. await page.waitForSelector('.energy-overview, .overview-container', { timeout: 10000 });
  200. // ========== Then:Milestone-5 - 返回概览数据包含健康指数 ========
  201. // 验证总能量显示
  202. const totalEnergy = page.locator('.total-energy, .energy-total');
  203. expect(await totalEnergy.isVisible()).toBeTruthy();
  204. // 验证五维数据存在
  205. const dimensionEnergies = page.locator('.dimension-energy');
  206. const dimensionCount = await dimensionEnergies.count();
  207. expect(dimensionCount).toBeGreaterThanOrEqual(5);
  208. // 验证健康指数
  209. const healthIndex = page.locator('.health-index, .index-value');
  210. if (await healthIndex.isVisible()) {
  211. const indexText = await healthIndex.textContent();
  212. expect(indexText).toMatch(/\d+/);
  213. }
  214. });
  215. // ===== 场景6:完整能量收支流程(端到端)=====
  216. test('[场景6] 完整能量收支流程 - 期望全流程成功', async ({ page }) => {
  217. // ========== Step 1:查看能量沙盘 ==========
  218. await page.goto('/#/pages/energy/sandbox');
  219. await page.waitForLoadState('networkidle');
  220. await page.waitForSelector('.energy-sandbox', { timeout: 10000 });
  221. // 记录初始能量值
  222. const initialTotal = page.locator('.total-energy, .energy-total');
  223. let initialValue = 0;
  224. if (await initialTotal.isVisible()) {
  225. const text = await initialTotal.textContent();
  226. initialValue = parseInt(text.replace(/\D/g, '')) || 0;
  227. }
  228. // ========== Step 2:完成任务获得能量 ==========
  229. await page.goto('/#/pages/tasks/complete/1');
  230. await page.waitForLoadState('networkidle');
  231. await page.waitForSelector('.task-complete, .energy-reward', { timeout: 10000 });
  232. // ========== Step 3:查看流水确认收入 ==========
  233. await page.goto('/#/pages/energy/logs');
  234. await page.waitForLoadState('networkidle');
  235. await page.waitForSelector('.log-item', { timeout: 10000 });
  236. // 验证有收入记录
  237. const incomeLog = page.locator('.log-item:has-text("收入"), .log-item:has-text("奖励")').first();
  238. expect(await incomeLog.isVisible()).toBeTruthy();
  239. // ========== Step 4:查看更新后的能量值 ==========
  240. await page.goto('/#/pages/energy/sandbox');
  241. await page.waitForLoadState('networkidle');
  242. const updatedTotal = page.locator('.total-energy, .energy-total');
  243. if (await updatedTotal.isVisible()) {
  244. const text = await updatedTotal.textContent();
  245. const updatedValue = parseInt(text.replace(/\D/g, '')) || 0;
  246. // 能量应该增加
  247. expect(updatedValue).toBeGreaterThanOrEqual(initialValue);
  248. }
  249. // ========== Step 5:查看概览 ==========
  250. await page.goto('/#/pages/energy/overview');
  251. await page.waitForLoadState('networkidle');
  252. await page.waitForSelector('.energy-overview', { timeout: 10000 });
  253. // 验证五维数据
  254. const dimensions = page.locator('.dimension-energy');
  255. expect(await dimensions.count()).toBeGreaterThanOrEqual(5);
  256. });
  257. });