teacher-dashboard.spec.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. /**
  2. * ================================================================
  3. * 场景:规划师仪表盘 E2E 测试
  4. * ================================================================
  5. * 目标:规划师通过 cfc-web TeacherDashboard 查看统计数据、快捷操作、家庭/团队列表
  6. * 部署地址:http://cfc.iwintrue.com
  7. *
  8. * 测试场景:
  9. * 1. 统计卡片渲染(服务家庭、活跃家庭、本月销售额、我的佣金)
  10. * 2. 快捷操作导航(管理家庭、消息中心、查看订单、DAN测评)
  11. * 3. 今日概览数字展示(新增家庭、完成任务、新订单、待审核)
  12. * 4. 我的家庭列表渲染
  13. * 5. 空状态显示(无家庭/无团队)
  14. * 6. 我的团队列表渲染
  15. * 7. 最近订单表格渲染
  16. * 8. 家庭任务完成率进度条
  17. * 9. 团队成员等级标签
  18. * 10. 页面整体加载和标题
  19. *
  20. * 前置条件:已登录规划师账号
  21. * ================================================================
  22. */
  23. const { test, expect } = require('@playwright/test');
  24. /**
  25. * 确保已登录到管理后台(规划师角色)
  26. */
  27. async function ensureLoggedIn(page) {
  28. const sidebar = page.locator('.sidebar-container, .el-menu-vertical-demo, .el-aside, #app > div');
  29. try {
  30. await sidebar.waitFor({ state: 'visible', timeout: 5000 });
  31. return;
  32. } catch {
  33. // 未登录,继续执行登录流程
  34. }
  35. await page.goto('/login', { waitUntil: 'networkidle', timeout: 30000 });
  36. await page.waitForTimeout(2000);
  37. // 尝试多种输入框选择器
  38. const inputSelectors = [
  39. '.el-input__inner',
  40. 'input[type="text"]',
  41. 'input[placeholder*="账号"]',
  42. 'input[placeholder*="用户名"]',
  43. 'input[name="username"]',
  44. '.el-input input',
  45. ];
  46. let inputFound = false;
  47. for (const sel of inputSelectors) {
  48. try {
  49. const el = page.locator(sel).first();
  50. if (await el.isVisible({ timeout: 3000 })) {
  51. await el.fill('teacher');
  52. inputFound = true;
  53. break;
  54. }
  55. } catch {
  56. // 继续尝试下一个选择器
  57. }
  58. }
  59. if (!inputFound) {
  60. const allInputs = page.locator('input');
  61. const count = await allInputs.count();
  62. if (count > 0) {
  63. await allInputs.first().fill('teacher');
  64. if (count > 1) {
  65. await allInputs.nth(1).fill('teacher123');
  66. }
  67. }
  68. }
  69. // 填密码
  70. const pwdInputs = page.locator('input[type="password"]');
  71. if (await pwdInputs.count() > 0) {
  72. await pwdInputs.first().fill('teacher123');
  73. } else {
  74. const allInputs = page.locator('input');
  75. const count = await allInputs.count();
  76. if (count >= 2) {
  77. await allInputs.nth(1).fill('teacher123');
  78. }
  79. }
  80. // 点登录按钮
  81. const btnSelectors = [
  82. '.el-button--primary',
  83. 'button[type="submit"]',
  84. '.login-btn',
  85. '.el-button:has-text("登录")',
  86. '.el-button:has-text("登 录")',
  87. ];
  88. for (const sel of btnSelectors) {
  89. try {
  90. const btn = page.locator(sel).first();
  91. if (await btn.isVisible({ timeout: 2000 })) {
  92. await btn.click();
  93. break;
  94. }
  95. } catch {
  96. // 继续尝试
  97. }
  98. }
  99. // 等待登录完成
  100. await page.waitForTimeout(3000);
  101. try {
  102. await sidebar.waitFor({ state: 'visible', timeout: 10000 });
  103. } catch {
  104. // 超时仍继续,可能页面已跳转
  105. }
  106. }
  107. test.describe('【场景流程】规划师仪表盘 TeacherDashboard', () => {
  108. test('[场景1] 统计卡片渲染 - 期望显示4个统计卡片(服务家庭、活跃家庭、销售额、佣金)', async ({ page }) => {
  109. await ensureLoggedIn(page);
  110. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  111. await page.waitForTimeout(3000);
  112. await page.screenshot({ path: 'test-artifacts/teacher-dashboard-stats.png' }).catch(() => {});
  113. const statRow = page.locator('.td-stat-row');
  114. await expect(statRow).toBeVisible({ timeout: 15000 });
  115. // 应有4个统计卡片
  116. const statCards = page.locator('.td-stat-card');
  117. const count = await statCards.count();
  118. expect(count).toBeGreaterThanOrEqual(4);
  119. // 验证每个卡片包含数值和标签
  120. const statContents = page.locator('.td-stat-content');
  121. await expect(statContents.first()).toBeVisible();
  122. });
  123. test('[场景2] 快捷操作导航 - 期望显示4个快捷操作入口', async ({ page }) => {
  124. await ensureLoggedIn(page);
  125. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  126. await page.waitForTimeout(2000);
  127. const quickCard = page.locator('.td-quick-card');
  128. await expect(quickCard).toBeVisible({ timeout: 15000 });
  129. const actionItems = page.locator('.td-action-item');
  130. const count = await actionItems.count();
  131. expect(count).toBeGreaterThanOrEqual(4);
  132. });
  133. test('[场景3] 今日概览 - 期望显示4个今日统计数据', async ({ page }) => {
  134. await ensureLoggedIn(page);
  135. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  136. await page.waitForTimeout(2000);
  137. const todayStats = page.locator('.td-today-stats');
  138. await expect(todayStats).toBeVisible({ timeout: 15000 });
  139. const statItems = page.locator('.td-today-stat-item');
  140. const count = await statItems.count();
  141. expect(count).toBeGreaterThanOrEqual(4);
  142. });
  143. test('[场景4] 我的家庭列表 - 期望显示家庭列表或空状态', async ({ page }) => {
  144. await ensureLoggedIn(page);
  145. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  146. await page.waitForTimeout(2000);
  147. const familyListCard = page.locator('.td-list-card').first();
  148. await expect(familyListCard).toBeVisible({ timeout: 15000 });
  149. // 要么有家庭列表项,要么显示空状态
  150. const familyItems = page.locator('.td-family-item');
  151. const emptyState = page.locator('.td-empty-state');
  152. const hasItems = await familyItems.count();
  153. const hasEmpty = await emptyState.count();
  154. expect(hasItems > 0 || hasEmpty > 0).toBeTruthy();
  155. });
  156. test('[场景5] 空状态显示 - 无家庭或无团队时显示空状态图标和提示', async ({ page }) => {
  157. await ensureLoggedIn(page);
  158. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  159. await page.waitForTimeout(2000);
  160. const emptyIcons = page.locator('.td-empty-state i');
  161. const emptyTexts = page.locator('.td-empty-state p');
  162. const count = await emptyIcons.count();
  163. // 如果有空状态,验证它有图标和文本
  164. if (count > 0) {
  165. await expect(emptyIcons.first()).toBeVisible();
  166. await expect(emptyTexts.first()).toBeVisible();
  167. }
  168. });
  169. test('[场景6] 我的团队列表 - 期望显示团队成员或空状态', async ({ page }) => {
  170. await ensureLoggedIn(page);
  171. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  172. await page.waitForTimeout(2000);
  173. // 第二个td-list-card为团队列表
  174. const teamCards = page.locator('.td-list-card');
  175. const count = await teamCards.count();
  176. expect(count).toBeGreaterThanOrEqual(2);
  177. const teamItems = page.locator('.td-team-item');
  178. const emptyState = page.locator('.td-empty-state');
  179. const hasItems = await teamItems.count();
  180. const hasEmpty = await emptyState.count();
  181. expect(hasItems > 0 || hasEmpty > 0).toBeTruthy();
  182. });
  183. test('[场景7] 最近订单表格 - 期望显示订单表格', async ({ page }) => {
  184. await ensureLoggedIn(page);
  185. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  186. await page.waitForTimeout(2000);
  187. const ordersCard = page.locator('.td-orders-card');
  188. await expect(ordersCard).toBeVisible({ timeout: 15000 });
  189. const ordersTable = page.locator('.td-orders-table, .el-table');
  190. await expect(ordersTable.first()).toBeVisible();
  191. });
  192. test('[场景8] 团队成员等级标签 - 期望显示规划师等级标签', async ({ page }) => {
  193. await ensureLoggedIn(page);
  194. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  195. await page.waitForTimeout(2000);
  196. const levelTags = page.locator('.td-member-level .el-tag');
  197. const count = await levelTags.count();
  198. // 如果有团队成员,验证有等级标签
  199. if (count > 0) {
  200. await expect(levelTags.first()).toBeVisible();
  201. }
  202. });
  203. test('[场景9] 统计卡片数值格式 - 期望销售额和佣金显示货币格式', async ({ page }) => {
  204. await ensureLoggedIn(page);
  205. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  206. await page.waitForTimeout(2000);
  207. const statValues = page.locator('.td-stat-value');
  208. const count = await statValues.count();
  209. expect(count).toBeGreaterThanOrEqual(4);
  210. });
  211. test('[场景10] 页面标题 - 期望页面标题包含"规划师首页"', async ({ page }) => {
  212. await ensureLoggedIn(page);
  213. await page.goto('/teacher-dashboard', { waitUntil: 'networkidle' });
  214. await page.waitForTimeout(2000);
  215. await expect(page).toHaveTitle(/规划师|teacher/i);
  216. });
  217. });