Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

155 linhas
6.3 KiB

  1. import type { Page, Route } from '@playwright/test'
  2. import { captureScreenshot, expect, test } from './fixtures'
  3. test.use({ trace: 'off', video: 'off' })
  4. const loginContext = {
  5. defaultRoleCode: 'admin',
  6. roles: [
  7. { code: 'teacher', label: '教员', accountLabel: '教员账号', organizationMode: 'CASCADE', organizationLabels: ['教学系', '教研室'] },
  8. { code: 'admin', label: '管理员', accountLabel: '管理员账号', organizationMode: 'NONE', organizationLabels: [] },
  9. ],
  10. departments: [
  11. {
  12. id: '100', code: 'EQUIPMENT', name: '工程装备系', parentId: null,
  13. children: [{ id: '110', code: 'MAINTENANCE', name: '维修教研室', parentId: '100', children: [] }],
  14. },
  15. ],
  16. }
  17. const identities = [
  18. { id: '402', displayName: '杜晴', departmentId: '110', departmentName: '维修教研室' },
  19. ]
  20. const envelope = (data: unknown) => JSON.stringify({
  21. code: 200,
  22. message: '成功',
  23. data,
  24. timestamp: '2026-08-29T12:00:00+08:00',
  25. requestId: 'ape2e-login-identity-preference',
  26. })
  27. const fulfill = (route: Route, data: unknown) => route.fulfill({
  28. status: 200,
  29. contentType: 'application/json',
  30. body: envelope(data),
  31. })
  32. async function mockLoginDependencies(page: Page) {
  33. await page.route('**/api/auth/v1/**', async (route) => {
  34. const path = new URL(route.request().url()).pathname
  35. if (path.endsWith('/auth/login-context')) return fulfill(route, loginContext)
  36. if (path.endsWith('/auth/login-identities')) return fulfill(route, identities)
  37. if (path.endsWith('/auth/login')) {
  38. return fulfill(route, { accessToken: 'remember-access-token', refreshToken: 'remember-refresh-token', expiresIn: 3600 })
  39. }
  40. if (path.endsWith('/auth/me')) {
  41. return fulfill(route, {
  42. user: { id: '1', username: 'admin.saved', displayName: '系统管理员', mustChangePassword: true },
  43. activeRoleId: '1', roles: [{ id: '1', code: 'admin', name: '管理员', enabled: true }],
  44. permissions: [], authorizationMode: 'SINGLE_ACTIVE',
  45. })
  46. }
  47. if (path.endsWith('/system-config/public')) return fulfill(route, {})
  48. return fulfill(route, {})
  49. })
  50. }
  51. test('登录页恢复上次成功登录的部门和账号但不保存密码', async ({ page }, testInfo) => {
  52. await page.addInitScript(() => {
  53. window.localStorage.setItem('ai-person:web:login-identity-preference:v1', JSON.stringify({
  54. lastRoleCode: 'teacher',
  55. selections: {
  56. teacher: { departmentId: '110', userId: '402', username: '' },
  57. },
  58. }))
  59. })
  60. await mockLoginDependencies(page)
  61. await page.goto('/login')
  62. await expect(page.locator('[data-role-code="teacher"]')).toHaveAttribute('aria-pressed', 'true')
  63. const organizationSelects = page.locator('.login-role-fields .login-picker:not(.login-identity-picker)')
  64. await expect(organizationSelects.nth(0)).toContainText('工程装备系')
  65. await expect(organizationSelects.nth(1)).toContainText('维修教研室')
  66. await expect(page.locator('.login-identity-picker')).toContainText('杜晴')
  67. await expect(page.locator('input[name="password"]')).toHaveValue('')
  68. await expect(page.getByRole('button', { name: '登录' })).toBeDisabled()
  69. await captureScreenshot(page, testInfo, 'restored-login-identity-without-password')
  70. })
  71. test('按登录身份分别恢复记住的账号和密码', async ({ page }) => {
  72. await page.addInitScript(() => {
  73. window.localStorage.setItem('ai-person:web:login-identity-preference:v1', JSON.stringify({
  74. lastRoleCode: 'teacher',
  75. selections: {
  76. teacher: {
  77. departmentId: '110', userId: '402', username: '',
  78. password: 'Teacher-Remembered-1', remember: true,
  79. },
  80. admin: {
  81. departmentId: '', userId: '', username: 'admin.remembered',
  82. password: 'Admin-Remembered-2', remember: true,
  83. },
  84. },
  85. }))
  86. })
  87. await mockLoginDependencies(page)
  88. await page.goto('/login')
  89. await expect(page.locator('.login-identity-picker')).toContainText('杜晴')
  90. await expect(page.locator('input[name="password"]')).toHaveValue('Teacher-Remembered-1')
  91. await expect(page.locator('input[name="rememberMe"]')).toBeChecked()
  92. await page.locator('[data-role-code="admin"]').click()
  93. await expect(page.locator('input[name="username"]')).toHaveValue('admin.remembered')
  94. await expect(page.locator('input[name="password"]')).toHaveValue('Admin-Remembered-2')
  95. await expect(page.locator('input[name="rememberMe"]')).toBeChecked()
  96. await page.locator('[data-role-code="teacher"]').click()
  97. await expect(page.locator('.login-identity-picker')).toContainText('杜晴')
  98. await expect(page.locator('input[name="password"]')).toHaveValue('Teacher-Remembered-1')
  99. })
  100. test('成功登录后按勾选状态保存账号密码', async ({ page }) => {
  101. await mockLoginDependencies(page)
  102. await page.goto('/login')
  103. await page.locator('[data-role-code="admin"]').click()
  104. await page.locator('input[name="username"]').fill('admin.saved')
  105. await page.locator('input[name="password"]').fill('Saved-Password-3')
  106. await page.locator('input[name="rememberMe"]').check()
  107. await page.getByRole('button', { name: '登录' }).click()
  108. await expect.poll(() => page.evaluate(() => {
  109. const raw = window.localStorage.getItem('ai-person:web:login-identity-preference:v1') || '{}'
  110. return JSON.parse(raw).selections?.admin
  111. })).toEqual({
  112. departmentId: '', userId: '', username: 'admin.saved',
  113. password: 'Saved-Password-3', remember: true,
  114. })
  115. })
  116. test('已删除的部门或账号不会被本地偏好错误回填', async ({ page }) => {
  117. await page.addInitScript(() => {
  118. window.localStorage.setItem('ai-person:web:login-identity-preference:v1', JSON.stringify({
  119. lastRoleCode: 'teacher',
  120. selections: {
  121. teacher: { departmentId: '999', userId: '999', username: '' },
  122. },
  123. }))
  124. })
  125. await mockLoginDependencies(page)
  126. await page.goto('/login')
  127. await expect(page.locator('[data-role-code="teacher"]')).toHaveAttribute('aria-pressed', 'true')
  128. const organizationSelects = page.locator('.login-role-fields .login-picker:not(.login-identity-picker)')
  129. await expect(organizationSelects.nth(0).locator('.el-select__placeholder').first()).toContainText('请选择教学系')
  130. await expect(organizationSelects.nth(1).getByRole('combobox')).toBeDisabled()
  131. await expect(page.locator('.login-identity-picker').getByRole('combobox')).toBeDisabled()
  132. await expect(page.locator('input[name="password"]')).toHaveValue('')
  133. })