/** * ================================================================ * 场景:后台管理端逐菜单可达性测试 * ================================================================ * 目标:管理员通过 cfc-web 管理后台(远程部署 http://cfc.iwintrue.com/admin/) * 逐一访问 router/index.js 中的全部菜单路由,验证: * - 未因权限不足被重定向到 /login 或 /403 * - 页面主体渲染成功(非白屏) * * 部署信息(已验证): * - SPA 静态资源: http://cfc.iwintrue.com/admin/* (200) * - 后端 API: http://cfc.iwintrue.com/api/* (openresty 代理) * - 管理员登录: POST /api/admin-auth/login-by-password {phone, password} * - 测试账号: 13800138000 / admin123(role=admin, adminId=1001) * - 滑块验证码: 纯前端模拟(拖动 >=88% 即成功),UI 登录可自动化; * 本测试采用 API 登录 + localStorage 注入,更稳定 * * 运行:npx playwright test tests/e2e/admin-menu-survey.spec.js --project=desktop * ================================================================ */ const { test, expect } = require('@playwright/test'); var BASE_URL = process.env.PW_BASE_URL || 'http://cfc.iwintrue.com'; var APP_PREFIX = '/admin'; // ================================================================ // 全部菜单路由(来自 cfc-web/src/router/index.js,admin 角色可见) // 参数化路由(:orderNo / :id / :systemId)单独处理 // ================================================================ var ADMIN_MENUS = [ // 首页/看板 { path: '/dashboard', title: '首页' }, { path: '/admin/dashboard', title: '数据大屏' }, // 家庭管理 { path: '/families', title: '家庭列表' }, { path: '/children', title: '孩子管理' }, { path: '/points', title: '积分管理' }, { path: '/points-log', title: '积分记录' }, { path: '/membership-center', title: '会员中心' }, // 任务管理 { path: '/tasks', title: '任务列表' }, { path: '/task-templates', title: '任务模板' }, { path: '/growth-task', title: '成长任务管理' }, // 奖励管理 { path: '/rewards', title: '奖励列表' }, { path: '/wishes', title: '心愿管理' }, // 系统管理 { path: '/users', title: '用户管理' }, { path: '/review-center', title: '审核中心' }, { path: '/operation-logs', title: '操作日志' }, // 服务管理(管理员) { path: '/service-role-applications', title: '服务角色申请审核' }, // 测评管理 { path: '/assessment-admin', title: '测评管理' }, { path: '/assessment-assign', title: '待分配规划师' }, { path: '/assessment-orders', title: '测评订单' }, { path: '/dan-execution', title: 'DAN服务执行' }, { path: '/dan-settlement', title: 'DAN结算管理' }, // 商品/订单管理 { path: '/product-manage', title: '商品管理' }, { path: '/product-edit', title: '商品编辑' }, { path: '/assessment-products', title: '测评商品管理' }, { path: '/assessment-plan-rules', title: '方案生成规则' }, { path: '/plan-management', title: '方案管理' }, { path: '/category-manage', title: '商品分类' }, { path: '/order-manage', title: '订单管理' }, { path: '/pending-refund', title: '待退款管理' }, // 系统配置 { path: '/sys-config', title: '系统配置' }, { path: '/energy-sandbox', title: '五维能量' }, { path: '/energy-rule', title: '能量规则管理' }, { path: '/energy-behavior-config', title: '能量行为配置' }, { path: '/periodic-service-config', title: '周期性服务配置' }, { path: '/virtual-goods-config', title: '虚拟支付道具' }, { path: '/knowledge-tags', title: '知识标签' }, // 文章管理 { path: '/article-categories', title: '文章分类' }, { path: '/article-manage', title: '文章管理' }, { path: '/article-edit', title: '文章编辑' }, { path: '/comment-review', title: '评论审核' }, // 佣金推荐系统 { path: '/product-profit-rate', title: '产品利润率' }, { path: '/product-ppoint', title: 'P点配置' }, { path: '/supply-system', title: '供应商体系' }, { path: '/supplier-products', title: '供应商商品管理' }, { path: '/supply-hierarchy/change-requests', title: '更换上级审核' }, // 供应商管理 { path: '/supply-manage', title: '供应商管理' }, { path: '/supply-manage/create', title: '新增供应商' }, // 星座/八字/血型配置 { path: '/zodiac-configs', title: '星座配置' }, { path: '/bazi-configs', title: '八字配置' }, { path: '/blood-type-configs', title: '血型配置' }, // 饮食管理 { path: '/foods', title: '食材管理' }, { path: '/recipes', title: '食谱管理' }, { path: '/seasonal-foods', title: '应季食材' }, // 健康/报告 { path: '/health-reports', title: '健康报告' }, { path: '/report-management', title: '报告管理' }, { path: '/report-audit', title: '报告草稿审核' }, { path: '/health-indicators', title: '健康指标' }, { path: '/health-checkins', title: '健康打卡' }, { path: '/emotion-alert', title: '情绪告警管理' }, { path: '/nutrition-mappings', title: '营养素映射管理' }, // 活动管理 { path: '/activities', title: '活动列表' }, { path: '/activity-edit', title: '活动编辑' }, { path: '/activity-review', title: '活动审核' }, { path: '/activity-registration-review', title: '活动报名审核' }, // 营销管理 { path: '/coupon', title: '优惠券管理' }, { path: '/promotion', title: '推广管理' }, { path: '/family-earnings', title: '家庭收益管理' }, { path: '/family-earnings-withdraw', title: '家庭收益提现审核' }, // 维度配置 + 知识库 { path: '/dimension-config', title: '维度配置' }, { path: '/knowledge-base', title: '知识库管理' }, { path: '/health-knowledge', title: '健康知识库管理' }, { path: '/health-norm-config', title: '健康常模配置' }, { path: '/health-data-source', title: '健康数据源配置' }, { path: '/health-energy-config', title: '五维能量配置' }, { path: '/energy-config', title: '五行能量配置' }, { path: '/indicators', title: '指标管理' }, // 虚拟团队 { path: '/virtual-teams', title: '虚拟团队管理' }, // 电商供应商 { path: '/ecom-supplier', title: '电商供应商管理' }, { path: '/badge-manage', title: '勋章管理' } ]; // 参数化路由(需要具体 ID,用占位 ID 验证可达性) var PARAM_MENUS = [ { path: '/order-detail/1', title: '订单详情' }, { path: '/supply-system/1', title: '供应商体系详情' }, { path: '/supply-system/1/edit', title: '编辑体系' }, { path: '/supply-manage/1', title: '供应商详情' }, { path: '/supply-manage/1/edit', title: '编辑供应商' }, { path: '/virtual-team-detail/1', title: '团队成员管理' } ]; // ================================================================ // 登录 helper:API 登录 → 注入 localStorage(含 roles 数组) // ================================================================ var ADMIN_PHONE = '13800138000'; var ADMIN_PASSWORD = 'admin123'; async function loginAsAdmin(page) { var res = await page.request.post(BASE_URL + '/api/admin-auth/login-by-password', { data: { phone: ADMIN_PHONE, password: ADMIN_PASSWORD }, headers: { 'Content-Type': 'application/json' } }); expect(res.ok()).toBeTruthy(); var body = await res.json(); expect(body.code).toBe(200); expect(body.data && body.data.token).toBeTruthy(); var token = body.data.token; var role = body.data.role || 'admin'; var roles = body.data.roles && body.data.roles.length ? body.data.roles : [role]; // 注意:addInitScript 只接受一个 arg,必须打包成单个对象传参 await page.addInitScript(function (auth) { localStorage.setItem('token', auth.token); localStorage.setItem('access_token', auth.token); localStorage.setItem('role', auth.role); localStorage.setItem('roles', JSON.stringify(auth.roles)); localStorage.setItem('adminId', String(auth.adminId)); }, { token: token, role: role, roles: roles, adminId: body.data.adminId }); } // ================================================================ // 菜单可达性检查:goto → 非 /login、非 /403、主体渲染 // ================================================================ async function assertMenuAccessible(page, route) { var url = BASE_URL + APP_PREFIX + route.path; await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }); // 1. 不应被踢回登录页(token 失效/权限不足) var currentUrl = page.url(); expect(currentUrl).not.toContain('/login'); expect(currentUrl).not.toContain('/403'); // 2. 页面主体应渲染:侧边栏 + 内容区 var sidebar = page.locator('.sidebar-container, .el-aside, .el-menu'); await expect(sidebar.first()).toBeVisible({ timeout: 15000 }); // 3. 内容区非空(.app-container / .el-main / 页面根) var content = page.locator('.app-container, .el-main, #app > div'); var contentCount = await content.count(); expect(contentCount).toBeGreaterThan(0); } test.describe('【后台管理】逐菜单可达性测试', () => { test('管理员登录并访问全部菜单 - 期望全部可达', async ({ page }) => { await loginAsAdmin(page); var failures = []; for (var i = 0; i < ADMIN_MENUS.length; i++) { var menu = ADMIN_MENUS[i]; try { await assertMenuAccessible(page, menu); console.log('[PASS] ' + menu.path + ' (' + menu.title + ')'); } catch (e) { failures.push(menu.path + ' (' + menu.title + '): ' + (e.message || e).split('\n')[0]); console.log('[FAIL] ' + menu.path + ' (' + menu.title + ')'); await page.screenshot({ path: 'test-artifacts/menu-fail-' + menu.path.replace(/\//g, '_') + '.png' }) .catch(function () {}); } } expect(failures, '以下菜单访问失败:\n' + failures.join('\n')).toEqual([]); }); test('参数化路由可达性 - 期望不跳登录/403', async ({ page }) => { await loginAsAdmin(page); for (var j = 0; j < PARAM_MENUS.length; j++) { var pmenu = PARAM_MENUS[j]; await assertMenuAccessible(page, pmenu); console.log('[PASS] ' + pmenu.path + ' (' + pmenu.title + ')'); } }); });