family-earnings-flow.spec.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /**
  2. * ================================================================
  3. * 场景:家庭收益管理流程 E2E 测试
  4. * ================================================================
  5. * 用户故事:家长可以管理家庭公共账户收支,兑换心愿,
  6. * 查看家庭成员待分配余额,审核提现申请。
  7. *
  8. * 流程步骤:
  9. * 1. 家长登录,进入个人中心
  10. * 2. 打开家庭收益管理页面
  11. * 3. 查看家庭收益概览
  12. * 4. 兑换心愿(能量到现金)
  13. * 5. 分配收益给成员
  14. * 6. 查看收益记录
  15. *
  16. * API 端点:
  17. * - POST /api/admin/earnings/families (家庭列表)
  18. * - POST /api/admin/earnings/family/{familyId}/summary (家庭概览)
  19. * - POST /api/admin/earnings/family/{familyId}/records (收支记录)
  20. *
  21. * 运行:npx playwright test tests/e2e/family-earnings-flow.spec.js
  22. * ================================================================
  23. */
  24. const { test, expect } = require('@playwright/test');
  25. test.describe('【场景流程】家庭收益管理流程', () => {
  26. test.beforeEach(async ({ page }) => {
  27. // 家长登录
  28. await page.goto('/#/pages/login/login');
  29. await page.waitForLoadState('networkidle');
  30. const phone = '13701366187'; // 测试家长账号
  31. const phoneInput = page.locator('input[type=tel], input[placeholder="手机号"]');
  32. await phoneInput.fill(phone);
  33. await page.waitForTimeout(500);
  34. await page.locator('.login-btn, button:has-text("登录")').click();
  35. await page.waitForTimeout(2000);
  36. try {
  37. await expect(page.locator('.home-tab, .tab-bar, .mine-tab')).toBeVisible({ timeout: 10000 });
  38. } catch (e) {
  39. // 跳过重复登录检查
  40. }
  41. });
  42. /**
  43. * 场景1:家庭收益概览页面加载
  44. * 校验:页面加载成功,显示能量、可提现、已提现数据
  45. */
  46. test('[场景1] 家庭收益概览页面加载', async ({ page }) => {
  47. await page.goto('/#/pages/profile/family-earnings');
  48. await page.waitForLoadState('networkidle');
  49. // 验证页面标题
  50. const title = page.locator('.nav-bar, .title-wrapper, h2:has-text("家庭收益")');
  51. expect(await title.isVisible()).toBeTruthy();
  52. // 验证卡片展示
  53. const energyCard = page.locator('.earnings-card, .energy-card, .balance-card');
  54. expect(await energyCard.first().isVisible()).toBeTruthy();
  55. // 验证统计数据
  56. const energyData = page.locator('.balance-data, .energy-amount');
  57. const energyText = await energyData.first().textContent();
  58. expect(energyText.length).toBeGreaterThan(0);
  59. });
  60. /**
  61. * 场景2:兑换心愿(能量→现金)
  62. * 校验:兑换成功,记录更新
  63. */
  64. test('[场景2] 兑换心愿', async ({ page }) => {
  65. await page.goto('/#/pages/profile/family-earnings');
  66. await page.waitForLoadState('networkidle');
  67. // 必须确保家庭有收益
  68. const energyCard = page.locator('.earnings-card, .energy-card');
  69. if (await energyCard.first().isVisible()) {
  70. const energyBefore = await page.locator('.energy-amount').textContent();
  71. const availableEnergy = parseInt(energyBefore.replace(/[^0-9]/g, '')) || 0;
  72. if (availableEnergy > 0) {
  73. // 点击兑换按钮
  74. const exchangeBtn = page.locator('button:has-text("兑换"), .exchange-btn');
  75. await exchangeBtn.click();
  76. await page.waitForTimeout(1000);
  77. // 输入金额
  78. const amountInput = page.locator('input[placeholder="兑换金额"]');
  79. await amountInput.fill('100');
  80. // 提交
  81. const submitBtn = page.locator('.confirm-btn, button:has-text("确定")');
  82. await submitBtn.click();
  83. await page.waitForTimeout(1000);
  84. // 验证提示
  85. const successTip = page.locator('.modal-success, .toast-success, text="兑换成功"');
  86. expect(await successTip.isVisible().catch(() => false)).toBeTruthy();
  87. }
  88. }
  89. expect(true).toBeTruthy();
  90. });
  91. /**
  92. * 场景3:分配收益给家庭成员
  93. * 校验:分配成功
  94. */
  95. test('[场景3] 分配收益给成员', async ({ page }) => {
  96. await page.goto('/#/pages/profile/family-earnings');
  97. await page.waitForLoadState('networkidle');
  98. // 点击分配按钮
  99. const distributeBtn = page.locator('button:has-text("分配"), .distribute-btn');
  100. await distributeBtn.click();
  101. await page.waitForTimeout(1000);
  102. // 选择成员
  103. const memberSelect = page.locator('.el-select-dropdown, .member-select');
  104. if (await memberSelect.isVisible()) {
  105. await memberSelect.locator('.el-select-dropdown__item:first-child').click();
  106. } else {
  107. await page.locator('.member-item:first-child').click();
  108. }
  109. // 填写金额
  110. const amountInput = page.locator('input[placeholder="分配金额"]');
  111. await amountInput.fill('50');
  112. // 提交
  113. const submitBtn = page.locator('.confirm-btn, button:has-text("确定")');
  114. await submitBtn.click();
  115. await page.waitForTimeout(1000);
  116. // 验证提示
  117. const successTip = page.locator('.modal-success, .toast-success, text="分配成功"');
  118. expect(await successTip.isVisible().catch(() => false)).toBeTruthy();
  119. });
  120. /**
  121. * 场景4:查看收支记录列表
  122. * 校验:列表加载正常数据
  123. */
  124. test('[场景4] 查看收支记录', async ({ page }) => {
  125. await page.goto('/#/pages/profile/family-earnings');
  126. await page.waitForLoadState('networkidle');
  127. // 切换到收支记录 tab
  128. const recordsTab = page.locator('.tab-item:has-text("收支记录")');
  129. await recordsTab.click();
  130. await page.waitForTimeout(1000);
  131. // 验证列表加载
  132. const records = page.locator('.record-item, .earnings-record');
  133. expect(await records.first().isVisible().catch(() => false)).toBeTruthy();
  134. });
  135. /**
  136. * 场景5:管理员视角 — 家庭收益管理
  137. * 角色:管理员
  138. * 触发条件:进入后台家庭收益管理页面
  139. * 校验:查看家庭列表和收益记录
  140. */
  141. test('[场景5] 管理员管理家庭收益', async ({ page }) => {
  142. // 管理员登录(admin 后台)
  143. await page.goto('http://cfc.iwintrue.com/admin/login');
  144. await page.waitForTimeout(1000);
  145. const adminPhone = '13701366188';
  146. await page.locator('input[placeholder="请输入手机号"]').fill(adminPhone);
  147. await page.locator('button:has-text("登录")').click();
  148. await page.waitForTimeout(2000);
  149. // 进入家庭收益管理
  150. await page.goto('http://cfc.iwintrue.com/admin/family-earnings');
  151. await page.waitForLoadState('networkidle');
  152. // 验证家庭列表加载
  153. const familyRows = page.locator('.el-table__row, .family-row');
  154. expect(await familyRows.count()).toBeGreaterThanOrEqual(0);
  155. // 点击查看详情
  156. if (await familyRows.count() > 0) {
  157. const firstRow = familyRows.first();
  158. const viewBtn = firstRow.locator('button:has-text("查看")');
  159. await viewBtn.click();
  160. await page.waitForTimeout(500);
  161. // 验证概览数据加载
  162. const summaryData = page.locator('.summary-card, .earnings-summary');
  163. expect(await summaryData.isVisible()).toBeTruthy();
  164. // 查看收益记录
  165. const recordTab = page.locator('.tab-item:has-text("收益记录")');
  166. await recordTab.click();
  167. await page.waitForTimeout(500);
  168. const recordRows = page.locator('.el-table__row, .record-row');
  169. expect(await recordRows.count()).toBeGreaterThanOrEqual(0);
  170. }
  171. });
  172. /**
  173. * 场景6:提现申请审核流程
  174. * 角色:管理员
  175. * 触发条件:进入后台提现审核页面
  176. * 校验:能查看待审核记录并操作
  177. */
  178. test('[场景6] 提现申请审核', async ({ page }) => {
  179. // 管理员登录(admin 后台)
  180. await page.goto('http://cfc.iwintrue.com/admin/login');
  181. await page.locator('input[placeholder="请输入手机号"]').fill('13701366188');
  182. await page.locator('button:has-text("登录")').click();
  183. await page.waitForTimeout(2000);
  184. // 进入提现审核
  185. await page.goto('http://cfc.iwintrue.com/admin/family-earnings/withdraw');
  186. await page.waitForLoadState('networkidle');
  187. // 验证待审核记录加载
  188. const withdrawRows = page.locator('.el-table__row, .withdraw-row');
  189. const rowCount = await withdrawRows.count();
  190. if (rowCount > 0) {
  191. // 审核通过第一条
  192. const approveBtn = withdrawRows.first().locator('button:has-text("通过")');
  193. await approveBtn.click();
  194. await page.waitForTimeout(500);
  195. // 提交
  196. const submitBtn = page.locator('.confirm-btn, .el-button--primary');
  197. await submitBtn.click();
  198. await page.waitForTimeout(1000);
  199. // 验证提示
  200. const successTip = page.locator('.modal-success, .toast-success');
  201. expect(await successTip.isVisible().catch(() => false)).toBeTruthy();
  202. } else {
  203. // 无待审核记录,跳过
  204. expect(true).toBeTruthy();
  205. }
  206. });
  207. });