import { captureScreenshot, expect, test } from './fixtures' import { confirmMessageBox, loginAsAdmin, readAdminCredentials } from './helpers' // 认证用例会临时把真实凭据填入密码框。密码框本身不会进入成功截图, // 同时关闭 trace/video,避免失败制品保存表单快照。 test.use({ trace: 'off', video: 'off' }) const roleCases = [ { code: 'administrative', label: '行政', accountLabel: '行政账号', organizationSelects: 1 }, { code: 'teacher', label: '教员', accountLabel: '教员账号', organizationSelects: 2 }, { code: 'student', label: '学员', accountLabel: '学员账号', organizationSelects: 0 }, { code: 'admin', label: '管理员', accountLabel: '管理员账号', organizationSelects: 0 }, ] as const test('登录页按行政、教员、学员、管理员展示真实身份控件', async ({ page }, testInfo) => { await page.goto('/login') await expect(page.getByRole('heading', { name: '登录数字人平台', exact: true })).toBeVisible() await expect(page.locator('.login-role-options > button')).toHaveCount(4) await expect(page.locator('[data-role-code="teacher"]')).toHaveAttribute('aria-pressed', 'true') expect((await page.locator('.login-role-options > button').allTextContents()).map((item) => item.trim())) .toEqual(roleCases.map((item) => item.label)) await expect(page.getByRole('button', { name: '登录', exact: true })).toBeDisabled() await expect(page.getByText('管理员输入账号登录,其他身份请选择对应账号后输入密码。')).toBeVisible() for (const role of roleCases) { await test.step(`${role.label}身份控件`, async () => { await page.locator(`[data-role-code="${role.code}"]`).click() await expect(page.locator(`[data-role-code="${role.code}"]`)).toHaveAttribute('aria-pressed', 'true') await expect(page.locator('.login-role-fields')).toContainText(role.accountLabel) const username = page.locator('input[name="username"]') if (role.code === 'admin') { await expect(username).toBeVisible() await expect(username).toHaveAttribute('placeholder', '请输入管理员账号') await expect(page.locator('.login-role-fields .el-select')).toHaveCount(0) } else { await expect(username).toHaveCount(0) // 非管理员由“组织选择(如需要)+ 账号选择”组成,不允许手工输入账号。 await expect(page.locator('.login-role-fields .el-select')).toHaveCount(role.organizationSelects + 1) await expect(page.locator('.login-identity-picker')).toBeVisible() } }) } await page.locator('[data-role-code="admin"]').click() const password = page.locator('input[name="password"]') const passwordToggle = page.locator('.login-password-toggle') await expect(passwordToggle).toHaveCount(1) await expect(passwordToggle.locator('svg')).toHaveCount(1) await expect(password).toHaveAttribute('type', 'password') await expect(page.getByTestId('password-hidden-icon')).toBeVisible() await expect(page.getByTestId('password-visible-icon')).toHaveCount(0) await page.getByRole('button', { name: '显示密码' }).click() await expect(password).toHaveAttribute('type', 'text') await expect(page.getByTestId('password-visible-icon')).toBeVisible() await expect(page.getByTestId('password-hidden-icon')).toHaveCount(0) await page.getByRole('button', { name: '隐藏密码' }).click() await expect(password).toHaveAttribute('type', 'password') await expect(page.getByTestId('password-hidden-icon')).toBeVisible() await captureScreenshot(page, testInfo, 'login-four-identities') }) test('深色主题下浏览器自动填充保持深色输入样式', async ({ page }, testInfo) => { await page.addInitScript(() => { window.localStorage.setItem('digital-human-theme', 'dark') }) await page.goto('/login') await expect(page.locator('.login-shell')).toHaveAttribute('data-theme', 'dark') await page.locator('[data-role-code="admin"]').click() const username = page.locator('input[name="username"]') await username.fill('saved-admin-preview') const client = await page.context().newCDPSession(page) await client.send('DOM.enable') await client.send('CSS.enable') const documentNode = await client.send('DOM.getDocument') const usernameNode = await client.send('DOM.querySelector', { nodeId: documentNode.root.nodeId, selector: 'input[name="username"]', }) await client.send('CSS.forcePseudoState', { nodeId: usernameNode.nodeId, forcedPseudoClasses: ['autofill'], }) const autofillStyle = await username.evaluate((element) => { const style = window.getComputedStyle(element) return { background: style.getPropertyValue('--login-autofill-background').trim(), boxShadow: style.webkitBoxShadow, textFill: style.webkitTextFillColor, } }) expect(autofillStyle.background).toBe('rgb(9 42 34 / 96%)') expect(autofillStyle.textFill).toBe('rgb(223, 244, 238)') expect(autofillStyle.boxShadow).toContain('9, 42, 34') await username.fill('') await captureScreenshot(page, testInfo, 'login-dark-autofill-theme') }) test('错误密码被拒绝且不会建立登录会话', async ({ page }, testInfo) => { const credentials = readAdminCredentials() await page.goto('/login') await page.locator('[data-role-code="admin"]').click() await page.locator('input[name="username"]').fill(credentials.username) await page.locator('input[name="password"]').fill('APE2E-invalid-password-2026') await page.getByRole('button', { name: /登录系统/ }).click() await expect(page).toHaveURL(/\/login(?:\?|$)/) await expect(page.locator('#login-error')).not.toBeEmpty() await expect(page.locator('.app-shell')).toHaveCount(0) // 成功截图不保留任何账号或密码输入值。 await page.locator('input[name="username"]').fill('') await page.locator('input[name="password"]').fill('') await captureScreenshot(page, testInfo, 'login-invalid-credential-rejected') }) test('管理员可以登录、进入个人菜单并安全退出', async ({ page }, testInfo) => { await loginAsAdmin(page) await expect(page).toHaveURL(/\/overview(?:\?|$)/) await expect(page.locator('.app-shell')).toBeVisible() await expect(page.getByRole('heading', { name: '实时数字人智能体' })).toBeVisible() await expect(page.locator('input[name="password"]')).toHaveCount(0) await captureScreenshot(page, testInfo, 'admin-login-success') await page.locator('.user-menu-trigger').click() await expect(page.locator('#user-popover')).toBeVisible() await expect(page.getByRole('menuitem', { name: /个人中心/ })).toBeVisible() await expect(page.getByRole('menuitem', { name: /退出登录/ })).toBeVisible() await captureScreenshot(page, testInfo, 'admin-account-menu') await page.getByRole('menuitem', { name: /退出登录/ }).click() await confirmMessageBox(page, '退出登录') await expect(page).toHaveURL(/\/login(?:\?|$)/) await expect(page.getByRole('heading', { name: '登录数字人平台', exact: true })).toBeVisible() }) test('智能体配置未保存时取消退出应保留完整登录态', async ({ page }) => { await loginAsAdmin(page) await page.goto('/agents/manage') const configure = page.getByRole('button', { name: '配置', exact: true }).first() await expect(configure).toBeVisible() await configure.click() await expect(page).toHaveURL(/\/agents\/[^/?]+(?:\?|$)/) const tabs = page.getByRole('navigation', { name: '配置分区' }) await tabs.getByRole('button', { name: /^身份设置/ }).click() const prompt = page.locator('.detail-body:visible .identity-editor textarea') await expect(prompt).toBeVisible() await prompt.fill(`${await prompt.inputValue()} 临时未保存内容`) await expect(page.getByText('有未保存的修改', { exact: true })).toBeVisible() let logoutRequests = 0 page.on('request', (request) => { if (request.method() === 'POST' && new URL(request.url()).pathname.endsWith('/auth/logout')) logoutRequests += 1 }) const detailUrl = page.url() await page.locator('.user-menu-trigger').click() await page.getByRole('menuitem', { name: /退出登录/ }).click() await confirmMessageBox(page, '退出登录') const unsavedDialog = page.getByRole('dialog', { name: '未保存的修改' }) await expect(unsavedDialog).toBeVisible() await unsavedDialog.getByRole('button', { name: '返回继续编辑' }).click() await expect(page).toHaveURL(detailUrl) await expect(page.locator('.app-shell')).toBeVisible() await expect(page.locator('#main-sidebar')).toBeVisible() await expect(page.locator('.user-menu-trigger')).not.toContainText('当前用户') await expect(page.getByText('有未保存的修改', { exact: true })).toBeVisible() expect(logoutRequests, '取消离开时不应调用注销接口').toBe(0) // 再次退出并明确放弃改动,验证正常退出链路仍然可用。 await page.locator('.user-menu-trigger').click() await page.getByRole('menuitem', { name: /退出登录/ }).click() await confirmMessageBox(page, '退出登录') await page.getByRole('dialog', { name: '未保存的修改' }) .getByRole('button', { name: '放弃修改并离开' }).click() await expect(page).toHaveURL(/\/login(?:\?|$)/) expect(logoutRequests, '确认离开后应且仅应注销一次').toBe(1) }) test('兼容入口和未知地址重定向到正式路由', async ({ page }) => { await loginAsAdmin(page) const redirects = [ { source: '/agents/hot-words', target: /\/knowledge\/hot-words(?:\?|$)/ }, { source: '/studio?mode=offline', target: /\/videos\/create\?.*mode=offline/ }, { source: '/works', target: /\/agents\/manage(?:\?|$)/ }, { source: '/videos', target: /\/videos\/manage(?:\?|$)/ }, { source: '/avatars', target: /\/assets\/avatars(?:\?|$)/ }, { source: '/knowledge', target: /\/knowledge\/library(?:\?|$)/ }, { source: '/operations/sensitive-words', target: /\/knowledge\/sensitive-words(?:\?|$)/ }, { source: '/organization/structure', target: /\/organization\/departments(?:\?|$)/ }, { source: '/change-password', target: /\/overview(?:\?|$)/ }, { source: '/APE2E-route-does-not-exist', target: /\/overview(?:\?|$)/ }, ] for (const item of redirects) { await test.step(item.source, async () => { await page.goto(item.source) await expect(page).toHaveURL(item.target) await expect(page.locator('.app-shell')).toBeVisible() }) } await page.goto('/agents/create') await expect(page).toHaveURL(/\/agents\/manage\?.*create=1/) await expect(page.getByRole('dialog', { name: '新建智能体' })).toBeVisible() await page.getByRole('button', { name: '取消', exact: true }).click() }) test('兼容入口 /studio 应能解析到实时智能体创建入口', async ({ page }) => { await loginAsAdmin(page) await page.goto('/studio') await expect(page).toHaveURL(/\/agents\/manage\?.*create=1/) await expect(page.getByRole('dialog', { name: '新建智能体' })).toBeVisible() })