Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

224 wiersze
11 KiB

  1. import { captureScreenshot, expect, test } from './fixtures'
  2. import { confirmMessageBox, loginAsAdmin, readAdminCredentials } from './helpers'
  3. // 认证用例会临时把真实凭据填入密码框。密码框本身不会进入成功截图,
  4. // 同时关闭 trace/video,避免失败制品保存表单快照。
  5. test.use({ trace: 'off', video: 'off' })
  6. const roleCases = [
  7. { code: 'administrative', label: '行政', accountLabel: '行政账号', organizationSelects: 1 },
  8. { code: 'teacher', label: '教员', accountLabel: '教员账号', organizationSelects: 2 },
  9. { code: 'student', label: '学员', accountLabel: '学员账号', organizationSelects: 0 },
  10. { code: 'admin', label: '管理员', accountLabel: '管理员账号', organizationSelects: 0 },
  11. ] as const
  12. test('登录页按行政、教员、学员、管理员展示真实身份控件', async ({ page }, testInfo) => {
  13. await page.goto('/login')
  14. await expect(page.getByRole('heading', { name: '登录数字人平台', exact: true })).toBeVisible()
  15. await expect(page.locator('.login-role-options > button')).toHaveCount(4)
  16. await expect(page.locator('[data-role-code="teacher"]')).toHaveAttribute('aria-pressed', 'true')
  17. expect((await page.locator('.login-role-options > button').allTextContents()).map((item) => item.trim()))
  18. .toEqual(roleCases.map((item) => item.label))
  19. await expect(page.getByRole('button', { name: '登录', exact: true })).toBeDisabled()
  20. await expect(page.getByText('管理员输入账号登录,其他身份请选择对应账号后输入密码。')).toBeVisible()
  21. for (const role of roleCases) {
  22. await test.step(`${role.label}身份控件`, async () => {
  23. await page.locator(`[data-role-code="${role.code}"]`).click()
  24. await expect(page.locator(`[data-role-code="${role.code}"]`)).toHaveAttribute('aria-pressed', 'true')
  25. await expect(page.locator('.login-role-fields')).toContainText(role.accountLabel)
  26. const username = page.locator('input[name="username"]')
  27. if (role.code === 'admin') {
  28. await expect(username).toBeVisible()
  29. await expect(username).toHaveAttribute('placeholder', '请输入管理员账号')
  30. await expect(page.locator('.login-role-fields .el-select')).toHaveCount(0)
  31. } else {
  32. await expect(username).toHaveCount(0)
  33. // 非管理员由“组织选择(如需要)+ 账号选择”组成,不允许手工输入账号。
  34. await expect(page.locator('.login-role-fields .el-select')).toHaveCount(role.organizationSelects + 1)
  35. await expect(page.locator('.login-identity-picker')).toBeVisible()
  36. }
  37. })
  38. }
  39. await page.locator('[data-role-code="admin"]').click()
  40. const password = page.locator('input[name="password"]')
  41. const passwordToggle = page.locator('.login-password-toggle')
  42. await expect(passwordToggle).toHaveCount(1)
  43. await expect(passwordToggle.locator('svg')).toHaveCount(1)
  44. await expect(password).toHaveAttribute('type', 'password')
  45. await expect(page.getByTestId('password-hidden-icon')).toBeVisible()
  46. await expect(page.getByTestId('password-visible-icon')).toHaveCount(0)
  47. await page.getByRole('button', { name: '显示密码' }).click()
  48. await expect(password).toHaveAttribute('type', 'text')
  49. await expect(page.getByTestId('password-visible-icon')).toBeVisible()
  50. await expect(page.getByTestId('password-hidden-icon')).toHaveCount(0)
  51. await page.getByRole('button', { name: '隐藏密码' }).click()
  52. await expect(password).toHaveAttribute('type', 'password')
  53. await expect(page.getByTestId('password-hidden-icon')).toBeVisible()
  54. await captureScreenshot(page, testInfo, 'login-four-identities')
  55. })
  56. test('深色主题下浏览器自动填充保持深色输入样式', async ({ page }, testInfo) => {
  57. await page.addInitScript(() => {
  58. window.localStorage.setItem('digital-human-theme', 'dark')
  59. })
  60. await page.goto('/login')
  61. await expect(page.locator('.login-shell')).toHaveAttribute('data-theme', 'dark')
  62. await page.locator('[data-role-code="admin"]').click()
  63. const username = page.locator('input[name="username"]')
  64. await username.fill('saved-admin-preview')
  65. const client = await page.context().newCDPSession(page)
  66. await client.send('DOM.enable')
  67. await client.send('CSS.enable')
  68. const documentNode = await client.send('DOM.getDocument')
  69. const usernameNode = await client.send('DOM.querySelector', {
  70. nodeId: documentNode.root.nodeId,
  71. selector: 'input[name="username"]',
  72. })
  73. await client.send('CSS.forcePseudoState', {
  74. nodeId: usernameNode.nodeId,
  75. forcedPseudoClasses: ['autofill'],
  76. })
  77. const autofillStyle = await username.evaluate((element) => {
  78. const style = window.getComputedStyle(element)
  79. return {
  80. background: style.getPropertyValue('--login-autofill-background').trim(),
  81. boxShadow: style.webkitBoxShadow,
  82. textFill: style.webkitTextFillColor,
  83. }
  84. })
  85. expect(autofillStyle.background).toBe('rgb(9 42 34 / 96%)')
  86. expect(autofillStyle.textFill).toBe('rgb(223, 244, 238)')
  87. expect(autofillStyle.boxShadow).toContain('9, 42, 34')
  88. await username.fill('')
  89. await captureScreenshot(page, testInfo, 'login-dark-autofill-theme')
  90. })
  91. test('错误密码被拒绝且不会建立登录会话', async ({ page }, testInfo) => {
  92. const credentials = readAdminCredentials()
  93. await page.goto('/login')
  94. await page.locator('[data-role-code="admin"]').click()
  95. await page.locator('input[name="username"]').fill(credentials.username)
  96. await page.locator('input[name="password"]').fill('APE2E-invalid-password-2026')
  97. await page.getByRole('button', { name: /登录系统/ }).click()
  98. await expect(page).toHaveURL(/\/login(?:\?|$)/)
  99. await expect(page.locator('#login-error')).not.toBeEmpty()
  100. await expect(page.locator('.app-shell')).toHaveCount(0)
  101. // 成功截图不保留任何账号或密码输入值。
  102. await page.locator('input[name="username"]').fill('')
  103. await page.locator('input[name="password"]').fill('')
  104. await captureScreenshot(page, testInfo, 'login-invalid-credential-rejected')
  105. })
  106. test('管理员可以登录、进入个人菜单并安全退出', async ({ page }, testInfo) => {
  107. await loginAsAdmin(page)
  108. await expect(page).toHaveURL(/\/overview(?:\?|$)/)
  109. await expect(page.locator('.app-shell')).toBeVisible()
  110. await expect(page.getByRole('heading', { name: '实时数字人智能体' })).toBeVisible()
  111. await expect(page.locator('input[name="password"]')).toHaveCount(0)
  112. await captureScreenshot(page, testInfo, 'admin-login-success')
  113. await page.locator('.user-menu-trigger').click()
  114. await expect(page.locator('#user-popover')).toBeVisible()
  115. await expect(page.getByRole('menuitem', { name: /个人中心/ })).toBeVisible()
  116. await expect(page.getByRole('menuitem', { name: /退出登录/ })).toBeVisible()
  117. await captureScreenshot(page, testInfo, 'admin-account-menu')
  118. await page.getByRole('menuitem', { name: /退出登录/ }).click()
  119. await confirmMessageBox(page, '退出登录')
  120. await expect(page).toHaveURL(/\/login(?:\?|$)/)
  121. await expect(page.getByRole('heading', { name: '登录数字人平台', exact: true })).toBeVisible()
  122. })
  123. test('智能体配置未保存时取消退出应保留完整登录态', async ({ page }) => {
  124. await loginAsAdmin(page)
  125. await page.goto('/agents/manage')
  126. const configure = page.getByRole('button', { name: '配置', exact: true }).first()
  127. await expect(configure).toBeVisible()
  128. await configure.click()
  129. await expect(page).toHaveURL(/\/agents\/[^/?]+(?:\?|$)/)
  130. const tabs = page.getByRole('navigation', { name: '配置分区' })
  131. await tabs.getByRole('button', { name: /^身份设置/ }).click()
  132. const prompt = page.locator('.detail-body:visible .identity-editor textarea')
  133. await expect(prompt).toBeVisible()
  134. await prompt.fill(`${await prompt.inputValue()} 临时未保存内容`)
  135. await expect(page.getByText('有未保存的修改', { exact: true })).toBeVisible()
  136. let logoutRequests = 0
  137. page.on('request', (request) => {
  138. if (request.method() === 'POST' && new URL(request.url()).pathname.endsWith('/auth/logout')) logoutRequests += 1
  139. })
  140. const detailUrl = page.url()
  141. await page.locator('.user-menu-trigger').click()
  142. await page.getByRole('menuitem', { name: /退出登录/ }).click()
  143. await confirmMessageBox(page, '退出登录')
  144. const unsavedDialog = page.getByRole('dialog', { name: '未保存的修改' })
  145. await expect(unsavedDialog).toBeVisible()
  146. await unsavedDialog.getByRole('button', { name: '返回继续编辑' }).click()
  147. await expect(page).toHaveURL(detailUrl)
  148. await expect(page.locator('.app-shell')).toBeVisible()
  149. await expect(page.locator('#main-sidebar')).toBeVisible()
  150. await expect(page.locator('.user-menu-trigger')).not.toContainText('当前用户')
  151. await expect(page.getByText('有未保存的修改', { exact: true })).toBeVisible()
  152. expect(logoutRequests, '取消离开时不应调用注销接口').toBe(0)
  153. // 再次退出并明确放弃改动,验证正常退出链路仍然可用。
  154. await page.locator('.user-menu-trigger').click()
  155. await page.getByRole('menuitem', { name: /退出登录/ }).click()
  156. await confirmMessageBox(page, '退出登录')
  157. await page.getByRole('dialog', { name: '未保存的修改' })
  158. .getByRole('button', { name: '放弃修改并离开' }).click()
  159. await expect(page).toHaveURL(/\/login(?:\?|$)/)
  160. expect(logoutRequests, '确认离开后应且仅应注销一次').toBe(1)
  161. })
  162. test('兼容入口和未知地址重定向到正式路由', async ({ page }) => {
  163. await loginAsAdmin(page)
  164. const redirects = [
  165. { source: '/agents/hot-words', target: /\/knowledge\/hot-words(?:\?|$)/ },
  166. { source: '/studio?mode=offline', target: /\/videos\/create\?.*mode=offline/ },
  167. { source: '/works', target: /\/agents\/manage(?:\?|$)/ },
  168. { source: '/videos', target: /\/videos\/manage(?:\?|$)/ },
  169. { source: '/avatars', target: /\/assets\/avatars(?:\?|$)/ },
  170. { source: '/knowledge', target: /\/knowledge\/library(?:\?|$)/ },
  171. { source: '/operations/sensitive-words', target: /\/knowledge\/sensitive-words(?:\?|$)/ },
  172. { source: '/organization/structure', target: /\/organization\/departments(?:\?|$)/ },
  173. { source: '/change-password', target: /\/overview(?:\?|$)/ },
  174. { source: '/APE2E-route-does-not-exist', target: /\/overview(?:\?|$)/ },
  175. ]
  176. for (const item of redirects) {
  177. await test.step(item.source, async () => {
  178. await page.goto(item.source)
  179. await expect(page).toHaveURL(item.target)
  180. await expect(page.locator('.app-shell')).toBeVisible()
  181. })
  182. }
  183. await page.goto('/agents/create')
  184. await expect(page).toHaveURL(/\/agents\/manage\?.*create=1/)
  185. await expect(page.getByRole('dialog', { name: '新建智能体' })).toBeVisible()
  186. await page.getByRole('button', { name: '取消', exact: true }).click()
  187. })
  188. test('兼容入口 /studio 应能解析到实时智能体创建入口', async ({ page }) => {
  189. await loginAsAdmin(page)
  190. await page.goto('/studio')
  191. await expect(page).toHaveURL(/\/agents\/manage\?.*create=1/)
  192. await expect(page.getByRole('dialog', { name: '新建智能体' })).toBeVisible()
  193. })