admin-menu-survey.spec.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /**
  2. * ================================================================
  3. * 场景:后台管理端逐菜单可达性测试
  4. * ================================================================
  5. * 目标:管理员通过 cfc-web 管理后台(远程部署 http://cfc.iwintrue.com/admin/)
  6. * 逐一访问 router/index.js 中的全部菜单路由,验证:
  7. * - 未因权限不足被重定向到 /login 或 /403
  8. * - 页面主体渲染成功(非白屏)
  9. *
  10. * 部署信息(已验证):
  11. * - SPA 静态资源: http://cfc.iwintrue.com/admin/* (200)
  12. * - 后端 API: http://cfc.iwintrue.com/api/* (openresty 代理)
  13. * - 管理员登录: POST /api/admin-auth/login-by-password {phone, password}
  14. * - 测试账号: 13800138000 / admin123(role=admin, adminId=1001)
  15. * - 滑块验证码: 纯前端模拟(拖动 >=88% 即成功),UI 登录可自动化;
  16. * 本测试采用 API 登录 + localStorage 注入,更稳定
  17. *
  18. * 运行:npx playwright test tests/e2e/admin-menu-survey.spec.js --project=desktop
  19. * ================================================================
  20. */
  21. const { test, expect } = require('@playwright/test');
  22. var BASE_URL = process.env.PW_BASE_URL || 'http://cfc.iwintrue.com';
  23. var APP_PREFIX = '/admin';
  24. // ================================================================
  25. // 全部菜单路由(来自 cfc-web/src/router/index.js,admin 角色可见)
  26. // 参数化路由(:orderNo / :id / :systemId)单独处理
  27. // ================================================================
  28. var ADMIN_MENUS = [
  29. // 首页/看板
  30. { path: '/dashboard', title: '首页' },
  31. { path: '/admin/dashboard', title: '数据大屏' },
  32. // 家庭管理
  33. { path: '/families', title: '家庭列表' },
  34. { path: '/children', title: '孩子管理' },
  35. { path: '/points', title: '积分管理' },
  36. { path: '/points-log', title: '积分记录' },
  37. { path: '/membership-center', title: '会员中心' },
  38. // 任务管理
  39. { path: '/tasks', title: '任务列表' },
  40. { path: '/task-templates', title: '任务模板' },
  41. { path: '/growth-task', title: '成长任务管理' },
  42. // 奖励管理
  43. { path: '/rewards', title: '奖励列表' },
  44. { path: '/wishes', title: '心愿管理' },
  45. // 系统管理
  46. { path: '/users', title: '用户管理' },
  47. { path: '/review-center', title: '审核中心' },
  48. { path: '/operation-logs', title: '操作日志' },
  49. // 服务管理(管理员)
  50. { path: '/service-role-applications', title: '服务角色申请审核' },
  51. // 测评管理
  52. { path: '/assessment-admin', title: '测评管理' },
  53. { path: '/assessment-assign', title: '待分配规划师' },
  54. { path: '/assessment-orders', title: '测评订单' },
  55. { path: '/dan-execution', title: 'DAN服务执行' },
  56. { path: '/dan-settlement', title: 'DAN结算管理' },
  57. // 商品/订单管理
  58. { path: '/product-manage', title: '商品管理' },
  59. { path: '/product-edit', title: '商品编辑' },
  60. { path: '/assessment-products', title: '测评商品管理' },
  61. { path: '/assessment-plan-rules', title: '方案生成规则' },
  62. { path: '/plan-management', title: '方案管理' },
  63. { path: '/category-manage', title: '商品分类' },
  64. { path: '/order-manage', title: '订单管理' },
  65. { path: '/pending-refund', title: '待退款管理' },
  66. // 系统配置
  67. { path: '/sys-config', title: '系统配置' },
  68. { path: '/energy-sandbox', title: '五维能量' },
  69. { path: '/energy-rule', title: '能量规则管理' },
  70. { path: '/energy-behavior-config', title: '能量行为配置' },
  71. { path: '/periodic-service-config', title: '周期性服务配置' },
  72. { path: '/virtual-goods-config', title: '虚拟支付道具' },
  73. { path: '/knowledge-tags', title: '知识标签' },
  74. // 文章管理
  75. { path: '/article-categories', title: '文章分类' },
  76. { path: '/article-manage', title: '文章管理' },
  77. { path: '/article-edit', title: '文章编辑' },
  78. { path: '/comment-review', title: '评论审核' },
  79. // 佣金推荐系统
  80. { path: '/product-profit-rate', title: '产品利润率' },
  81. { path: '/product-ppoint', title: 'P点配置' },
  82. { path: '/supply-system', title: '供应商体系' },
  83. { path: '/supplier-products', title: '供应商商品管理' },
  84. { path: '/supply-hierarchy/change-requests', title: '更换上级审核' },
  85. // 供应商管理
  86. { path: '/supply-manage', title: '供应商管理' },
  87. { path: '/supply-manage/create', title: '新增供应商' },
  88. // 星座/八字/血型配置
  89. { path: '/zodiac-configs', title: '星座配置' },
  90. { path: '/bazi-configs', title: '八字配置' },
  91. { path: '/blood-type-configs', title: '血型配置' },
  92. // 饮食管理
  93. { path: '/foods', title: '食材管理' },
  94. { path: '/recipes', title: '食谱管理' },
  95. { path: '/seasonal-foods', title: '应季食材' },
  96. // 健康/报告
  97. { path: '/health-reports', title: '健康报告' },
  98. { path: '/report-management', title: '报告管理' },
  99. { path: '/report-audit', title: '报告草稿审核' },
  100. { path: '/health-indicators', title: '健康指标' },
  101. { path: '/health-checkins', title: '健康打卡' },
  102. { path: '/emotion-alert', title: '情绪告警管理' },
  103. { path: '/nutrition-mappings', title: '营养素映射管理' },
  104. // 活动管理
  105. { path: '/activities', title: '活动列表' },
  106. { path: '/activity-edit', title: '活动编辑' },
  107. { path: '/activity-review', title: '活动审核' },
  108. { path: '/activity-registration-review', title: '活动报名审核' },
  109. // 营销管理
  110. { path: '/coupon', title: '优惠券管理' },
  111. { path: '/promotion', title: '推广管理' },
  112. { path: '/family-earnings', title: '家庭收益管理' },
  113. { path: '/family-earnings-withdraw', title: '家庭收益提现审核' },
  114. // 维度配置 + 知识库
  115. { path: '/dimension-config', title: '维度配置' },
  116. { path: '/knowledge-base', title: '知识库管理' },
  117. { path: '/health-knowledge', title: '健康知识库管理' },
  118. { path: '/health-norm-config', title: '健康常模配置' },
  119. { path: '/health-data-source', title: '健康数据源配置' },
  120. { path: '/health-energy-config', title: '五维能量配置' },
  121. { path: '/energy-config', title: '五行能量配置' },
  122. { path: '/indicators', title: '指标管理' },
  123. // 虚拟团队
  124. { path: '/virtual-teams', title: '虚拟团队管理' },
  125. // 电商供应商
  126. { path: '/ecom-supplier', title: '电商供应商管理' },
  127. { path: '/badge-manage', title: '勋章管理' }
  128. ];
  129. // 参数化路由(需要具体 ID,用占位 ID 验证可达性)
  130. var PARAM_MENUS = [
  131. { path: '/order-detail/1', title: '订单详情' },
  132. { path: '/supply-system/1', title: '供应商体系详情' },
  133. { path: '/supply-system/1/edit', title: '编辑体系' },
  134. { path: '/supply-manage/1', title: '供应商详情' },
  135. { path: '/supply-manage/1/edit', title: '编辑供应商' },
  136. { path: '/virtual-team-detail/1', title: '团队成员管理' }
  137. ];
  138. // ================================================================
  139. // 登录 helper:API 登录 → 注入 localStorage(含 roles 数组)
  140. // ================================================================
  141. var ADMIN_PHONE = '13800138000';
  142. var ADMIN_PASSWORD = 'admin123';
  143. async function loginAsAdmin(page) {
  144. var res = await page.request.post(BASE_URL + '/api/admin-auth/login-by-password', {
  145. data: { phone: ADMIN_PHONE, password: ADMIN_PASSWORD },
  146. headers: { 'Content-Type': 'application/json' }
  147. });
  148. expect(res.ok()).toBeTruthy();
  149. var body = await res.json();
  150. expect(body.code).toBe(200);
  151. expect(body.data && body.data.token).toBeTruthy();
  152. var token = body.data.token;
  153. var role = body.data.role || 'admin';
  154. var roles = body.data.roles && body.data.roles.length ? body.data.roles : [role];
  155. // 注意:addInitScript 只接受一个 arg,必须打包成单个对象传参
  156. await page.addInitScript(function (auth) {
  157. localStorage.setItem('token', auth.token);
  158. localStorage.setItem('access_token', auth.token);
  159. localStorage.setItem('role', auth.role);
  160. localStorage.setItem('roles', JSON.stringify(auth.roles));
  161. localStorage.setItem('adminId', String(auth.adminId));
  162. }, { token: token, role: role, roles: roles, adminId: body.data.adminId });
  163. }
  164. // ================================================================
  165. // 菜单可达性检查:goto → 非 /login、非 /403、主体渲染
  166. // ================================================================
  167. async function assertMenuAccessible(page, route) {
  168. var url = BASE_URL + APP_PREFIX + route.path;
  169. await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 });
  170. // 1. 不应被踢回登录页(token 失效/权限不足)
  171. var currentUrl = page.url();
  172. expect(currentUrl).not.toContain('/login');
  173. expect(currentUrl).not.toContain('/403');
  174. // 2. 页面主体应渲染:侧边栏 + 内容区
  175. var sidebar = page.locator('.sidebar-container, .el-aside, .el-menu');
  176. await expect(sidebar.first()).toBeVisible({ timeout: 15000 });
  177. // 3. 内容区非空(.app-container / .el-main / 页面根)
  178. var content = page.locator('.app-container, .el-main, #app > div');
  179. var contentCount = await content.count();
  180. expect(contentCount).toBeGreaterThan(0);
  181. }
  182. test.describe('【后台管理】逐菜单可达性测试', () => {
  183. test('管理员登录并访问全部菜单 - 期望全部可达', async ({ page }) => {
  184. await loginAsAdmin(page);
  185. var failures = [];
  186. for (var i = 0; i < ADMIN_MENUS.length; i++) {
  187. var menu = ADMIN_MENUS[i];
  188. try {
  189. await assertMenuAccessible(page, menu);
  190. console.log('[PASS] ' + menu.path + ' (' + menu.title + ')');
  191. } catch (e) {
  192. failures.push(menu.path + ' (' + menu.title + '): ' + (e.message || e).split('\n')[0]);
  193. console.log('[FAIL] ' + menu.path + ' (' + menu.title + ')');
  194. await page.screenshot({ path: 'test-artifacts/menu-fail-' + menu.path.replace(/\//g, '_') + '.png' })
  195. .catch(function () {});
  196. }
  197. }
  198. expect(failures, '以下菜单访问失败:\n' + failures.join('\n')).toEqual([]);
  199. });
  200. test('参数化路由可达性 - 期望不跳登录/403', async ({ page }) => {
  201. await loginAsAdmin(page);
  202. for (var j = 0; j < PARAM_MENUS.length; j++) {
  203. var pmenu = PARAM_MENUS[j];
  204. await assertMenuAccessible(page, pmenu);
  205. console.log('[PASS] ' + pmenu.path + ' (' + pmenu.title + ')');
  206. }
  207. });
  208. });