import { expect, test } from './fixtures'; /** * 部门默认透明测试 * * 测试 CI unionid 测试用户,其所有机构都只有 1 个 active 部门(默认部门), * 因此前端不应出现部门选择入口: * - localStorage 自动写入 CurrentDepartmentId * - 页面路径不包含 department 参数 * - 角色选择、排课列表、课包商店等已有页面不受影响 * * 注意:CI 用户可能有多机构,需要先选机构,再触发部门自动选中。 */ const moicenUnionid = process.env.MOICEN_E2E_UNIONID?.trim(); /** * 登录 → 依次处理机构选择 + 身份选择 → 到达首页 * 适用于多机构用户的场景 */ async function loginAndDismissSelectors(page: any) { const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' }); await page.goto(`/?${q.toString()}`, { waitUntil: 'domcontentloaded', timeout: 60_000, }); await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 }); // 等待 auth tokens await expect(async () => { const auth = await page.evaluate(() => window.localStorage.getItem('Authorization') ); expect(auth).toBeTruthy(); }).toPass({ timeout: 30_000 }); // 逐层处理选择页:机构选择 → 身份选择(可能有 0~n 层) for (let i = 0; i < 5; i++) { const path = await page.evaluate(() => window.location.pathname); console.log(`[DeptTest] iteration ${i}: path=${path}`); console.log(`[DeptTest] has Authorization:`, !!await page.evaluate(() => window.localStorage.getItem('Authorization'))); console.log(`[DeptTest] has HtySudoerToken:`, !!await page.evaluate(() => window.localStorage.getItem('HtySudoerToken'))); if (path === '/org/select') { await page.locator('.van-cell').first().click(); console.log('[DeptTest] clicked org cell'); await page.waitForTimeout(3_000); } else if (path === '/') { // 用 waitFor 避免 SPA 路由守卫异步加载角色时的竞态(isVisible 可能返回 false) const rs = page.getByText('请选择您的登录身份'); try { await rs.waitFor({ state: 'visible', timeout: 10_000 }); await page.locator('.van-grid-item').first().click(); console.log('[DeptTest] clicked role'); await page.waitForTimeout(3_000); } catch { console.log('[DeptTest] at / but no role selector'); // 无角色选择器 → 当前用户角色已自动选中(1个活跃角色)。 // chooseRole 内部调用 router.push('/role/profile') 是异步的, // 等待重定向完成,让该 navigtion 的 route guard 走完 org/dept 加载。 // 避免后续 page.goto 的完整页面加载冲掉未完成的 SPA 导航。 try { await page.waitForFunction( () => window.location.pathname !== '/', { timeout: 10_000 } ); const newPath = await page.evaluate(() => window.location.pathname); console.log('[DeptTest] role auto-redirected to', newPath); // route guard 已在这个导航中完成 org/dept 加载,CurrentDepartmentId 已写入 } catch { // 10s 内未重定向(可能没有活跃角色),不做处理 console.log('[DeptTest] no redirect within 10s (0 roles?)'); } break; } } else { console.log('[DeptTest] unexpected path, break'); break; } } } test.describe('单部门透明', () => { test.skip(!moicenUnionid, '需要 MOICEN_E2E_UNIONID(Secret 或 .env.e2e)'); function decodeJwtPayloadCompact(token: string): Record { const parts = token.split('.'); if (parts.length < 2) return {}; const raw = parts[1].replace(/-/g, '+').replace(/_/g, '/'); const padded = raw.padEnd(Math.ceil(raw.length / 4) * 4, '='); const decoded = decodeURIComponent( atob(padded) .split('') .map(c => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`) .join('') ); return JSON.parse(decoded); } test('自动选中默认部门,不出现部门选择 UI', async ({ page }) => { test.setTimeout(180_000); await loginAndDismissSelectors(page); // 检查登录后的 JWT const loginJwt = await page.evaluate(() => window.localStorage.getItem('Authorization')); if (loginJwt) { const payload = decodeJwtPayloadCompact(loginJwt); let sub: Record = {}; if (typeof payload.sub === 'string') sub = JSON.parse(payload.sub); console.log('[DeptTest JWT before] top-level keys:', Object.keys(payload)); console.log('[DeptTest JWT before] payload.current_org_id:', payload.current_org_id); console.log('[DeptTest JWT before] sub.current_org_id:', (sub as any).current_org_id); console.log('[DeptTest JWT before] sub.current_department_id:', (sub as any).current_department_id); } // 导航到排课页触发路由守卫中的 org 加载 + 部门自动选中 await page.goto('/clazz', { waitUntil: 'networkidle', timeout: 90_000, }); await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 }); // 打印当前页面状态 const pageState = await page.evaluate(() => { const ls = (k: string) => window.localStorage.getItem(k); return { pathname: window.location.pathname, CurrentOrgId: ls('CurrentOrgId'), CurrentDepartmentId: ls('CurrentDepartmentId'), hasAuth: !!ls('Authorization'), hasSudo: !!ls('HtySudoerToken'), guardError: ls('__guardError'), orgSwitchDebug: ls('OrgSwitchDebug'), }; }); console.log('[DeptTest] state after goto:', JSON.stringify(pageState, null, 2)); console.log('[DeptTest] deptId after goto:', pageState.CurrentDepartmentId); // 如果部门尚未设置,给 route guard 额外时间完成异步加载 if (!pageState.CurrentDepartmentId) { console.log('[DeptTest] waiting extra for department loading...'); await page.waitForTimeout(8_000); const retry = await page.evaluate(() => window.localStorage.getItem('CurrentDepartmentId')); console.log('[DeptTest] CurrentDepartmentId after extra wait:', retry); if (retry) { pageState.CurrentDepartmentId = retry; pageState.pathname = await page.evaluate(() => window.location.pathname); } } // 验证 localStorage 写入了 CurrentDepartmentId const deptId = pageState.CurrentDepartmentId; expect(deptId).toBeTruthy(); expect(deptId).toContain('dept_default_'); // 验证页面路径不包含 department 相关参数 expect(pageState.pathname).not.toContain('department'); // 验证 token 中包含 department_id const authToken = await page.evaluate(() => window.localStorage.getItem('Authorization') ); expect(authToken).toBeTruthy(); if (authToken) { const parts = authToken.split('.'); expect(parts.length).toBe(3); const payloadRaw = parts[1].replace(/-/g, '+').replace(/_/g, '/'); const payload = JSON.parse(atob(payloadRaw)); console.log('[JWT Debug] top-level keys:', Object.keys(payload)); console.log('[JWT Debug] payload.current_department_id:', payload.current_department_id); console.log('[JWT Debug] payload.current_org_id:', payload.current_org_id); let subjectPayload: Record = {}; if (typeof payload.sub === 'string') { subjectPayload = JSON.parse(payload.sub); console.log('[JWT Debug] sub keys:', Object.keys(subjectPayload)); console.log('[JWT Debug] sub.current_department_id:', subjectPayload.current_department_id); console.log('[JWT Debug] sub.current_org_id:', subjectPayload.current_org_id); console.log('[JWT Debug] sub.current_org_id type:', typeof subjectPayload.current_org_id); } const deptIdInJwt = payload.current_department_id || subjectPayload.current_department_id; expect(deptIdInJwt).toBeTruthy(); expect(typeof deptIdInJwt).toBe('string'); } }); test('已有部门上下文时不重复加载部门列表', async ({ page }) => { await loginAndDismissSelectors(page); // 导航到排课页触发 org 加载 + 部门自动选中,验证页面正常渲染 await page.goto('/clazz', { waitUntil: 'domcontentloaded', timeout: 60_000, }); await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 }); }); });