| @@ -138,6 +138,52 @@ test('管理员可以登录、进入个人菜单并安全退出', async ({ page | |||
| 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) | |||
| @@ -190,7 +190,7 @@ test('IAM 真实生命周期:部门、角色、用户、授权、首次改密 | |||
| await page.getByPlaceholder('请输入新密码').fill(data.finalPassword) | |||
| await page.getByPlaceholder('请再次输入').fill(data.finalPassword) | |||
| await captureScreenshot(page, testInfo, '05-initial-password-form', [page.locator('input[type="password"]')]) | |||
| await page.getByRole('button', { name: '保存并进入平台' }).click() | |||
| await page.getByRole('button', { name: '保存新密码并进入平台' }).click() | |||
| await expect(page).toHaveURL(/\/overview(?:\?|$)/) | |||
| await expect(page.locator('.user-menu-trigger')).toContainText(data.editedDisplayName) | |||
| await captureScreenshot(page, testInfo, '06-student-login-success') | |||
| @@ -279,7 +279,7 @@ async function completeInitialPasswordInBrowser( | |||
| response.request().method() === 'PUT' | |||
| && new URL(response.url()).pathname === '/api/auth/v1/auth/initial-password' | |||
| )) | |||
| await page.getByRole('button', { name: '保存并进入平台' }).click() | |||
| await page.getByRole('button', { name: '保存新密码并进入平台' }).click() | |||
| const response = await changedResponsePromise | |||
| assertStatus(response, 200, '浏览器完成首次改密') | |||
| await expect(page).toHaveURL(/\/overview(?:\?|$)/) | |||
| @@ -112,16 +112,33 @@ test('861:首次改密两次输入不一致时立即显示字段提示且不 | |||
| await seedSession(page) | |||
| const mocked = await mockAuth(page, { me: 'forced' }) | |||
| await page.goto('/change-password') | |||
| await expect(page.getByText('首次登录安全验证', { exact: true })).toBeVisible() | |||
| await expect(page.getByRole('heading', { name: '请先设置新密码' })).toBeVisible() | |||
| await expect(page.getByText('为保护账号安全,初始密码不可继续使用。此步骤仅需完成一次。')).toBeVisible() | |||
| const inputs = page.locator('input[autocomplete="new-password"]') | |||
| await inputs.nth(0).fill('Strong#Password8') | |||
| await inputs.nth(1).fill('Different#Pass9') | |||
| await expect(page.locator('.password-requirements .is-passed')).toHaveCount(4) | |||
| await expect(page.getByText('两次输入的新密码不一致', { exact: true })).toBeVisible() | |||
| await expect(page.getByRole('button', { name: '保存并进入平台' })).toBeDisabled() | |||
| await expect(page.getByRole('button', { name: '保存新密码并进入平台' })).toBeDisabled() | |||
| expect(mocked.initialPasswordRequests()).toBe(0) | |||
| await captureScreenshot(page, testInfo, '861-initial-password-mismatch', [inputs]) | |||
| }) | |||
| test('首次登录改密页在 375px 窄屏保持可读且无横向滚动', async ({ page }, testInfo) => { | |||
| await page.setViewportSize({ width: 375, height: 812 }) | |||
| await seedSession(page) | |||
| await mockAuth(page, { me: 'forced' }) | |||
| await page.goto('/change-password') | |||
| await expect(page.getByRole('heading', { name: '请先设置新密码' })).toBeVisible() | |||
| await expect(page.getByText('首次登录 · 安全设置', { exact: true })).toBeVisible() | |||
| const horizontalOverflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth) | |||
| expect(horizontalOverflow).toBeLessThanOrEqual(1) | |||
| await captureScreenshot(page, testInfo, 'initial-password-mobile-375', [page.locator('input[autocomplete="new-password"]')]) | |||
| }) | |||
| test('864:首次改密期间账号停用后仅显示一次固定登录提示且不刷新令牌', async ({ page }, testInfo) => { | |||
| await seedSession(page, true) | |||
| const mocked = await mockAuth(page, { me: 'forced', initialPassword401: true }) | |||
| @@ -129,7 +146,7 @@ test('864:首次改密期间账号停用后仅显示一次固定登录提示 | |||
| const inputs = page.locator('input[autocomplete="new-password"]') | |||
| await inputs.nth(0).fill('Strong#Password8') | |||
| await inputs.nth(1).fill('Strong#Password8') | |||
| await page.getByRole('button', { name: '保存并进入平台' }).click() | |||
| await page.getByRole('button', { name: '保存新密码并进入平台' }).click() | |||
| await expect(page).toHaveURL(/\/login\?redirect=/) | |||
| await expect(page.locator('#login-error')).toHaveText('账号、密码或登录身份不匹配') | |||