import { test, expect, type Page } from '@playwright/test' async function fixture(page: Page, options: { fail?: boolean; deny?: boolean; slow?: boolean } = {}) { const revoked: string[] = [], queries: string[] = [] const now = new Date().toISOString(), expires = new Date(Date.now() + 3600000).toISOString() await page.addInitScript(() => sessionStorage.setItem('vdi.accessToken', 'session-fixture')) await page.route('**/api/v1/**', async route => { const request = route.request(), url = new URL(request.url()), path = url.pathname.replace('/api/v1', '') const list = (items: unknown[], total = items.length) => route.fulfill({ json: { items, total, page: 1, pageSize: 20, requestId: 'sessions-ui' } }) if (path === '/auth/me') { await route.fulfill({ json: { id: 'owner', username: 'root', displayName: '管理员', role: 'super_admin' } }); return } if (path === '/settings') { await route.fulfill({ json: { mode: 'production', capabilities: ['administrator-sessions'], pveConfigured: true } }); return } if (path === '/overview') { await route.fulfill({ json: { terminalCount: 0, onlineTerminals: 0, desktopCount: 0, runningDesktops: 0, activeSessions: 0, failedTasks: 0, mode: 'production' } }); return } if (path === '/administrators') { await list([{ id: 'owner', username: 'root', displayName: '管理员', role: 'super_admin', enabled: true }, { id: 'other', username: 'maintainer', displayName: '运维', role: 'operator', enabled: true }]); return } const match = path.match(/^\/administrators\/(owner|other)\/sessions(?:\/(.+))?$/) if (match) { const id = match[1]!, key = match[2] if (request.method() === 'DELETE') { revoked.push(`${id}/${key}`) if (options.deny) { await route.fulfill({ status: 403, json: { code: 'forbidden', message: '当前账号没有该操作权限' } }); return } await route.fulfill({ status: 204 }); return } queries.push(id) if (options.fail && id === 'other') { await route.fulfill({ status: 500, json: { code: 'database_error', message: '登录列表暂不可用' } }); return } if (options.slow && id === 'owner') await new Promise(resolve => setTimeout(resolve, 700)) const p = Number(url.searchParams.get('page') || 1), size = 20, total = 21 - revoked.filter(r => r.startsWith(`${id}/`)).length await list(Array.from({ length: Math.max(0, Math.min(size, total - (p - 1) * size)) }, (_, i) => ({ key: `${id}-${(p - 1) * size + i}`, createdAt: i === 1 ? null : now, expiresAt: expires, clientIp: i === 1 ? '' : `192.0.2.${(p - 1) * size + i + 1}`, userAgent: 'Example browser ', current: id === 'owner' && p === 1 && i === 0 })), total); return } if (['/resources', '/desktops', '/users', '/terminals', '/tasks'].includes(path)) { await list([]); return } await route.fulfill({ status: 404, json: { message: '测试接口未定义' } }) }) await page.goto('/settings'); await page.getByRole('button', { name: '管理员账号', exact: true }).click() await expect(page.getByRole('heading', { name: '管理员账号', exact: true })).toBeVisible() return { revoked, queries } } async function open(page: Page, name: string) { await page.getByRole('row').filter({ hasText: name }).getByRole('button', { name: '在线登录', exact: true }).click() return page.getByRole('dialog', { name: `在线登录 · ${name}`, exact: true }) } test('登录来源、旧登录未知信息、分页、当前登录保护及深浅窄屏', async ({ page }, info) => { const calls = await fixture(page), dialog = await open(page, 'root') await expect(dialog.getByText('当前登录', { exact: true })).toBeVisible() await expect(dialog.getByRole('row').filter({ hasText: '当前登录' }).getByRole('button', { name: '撤销登录' })).toBeDisabled() await expect(dialog.getByText('升级前登录,时间未知', { exact: true })).toBeVisible() await expect(dialog.getByText('升级前登录,来源未知', { exact: true })).toBeVisible() await page.screenshot({ path: info.outputPath('admin-sessions-dark.png'), fullPage: true, animations: 'disabled' }) await dialog.locator('.el-pagination .btn-next').click(); await expect(dialog.getByText('192.0.2.21', { exact: true })).toBeVisible() await dialog.getByRole('button', { name: '关闭', exact: true }).click() await page.getByRole('button', { name: '切换浅色主题' }).click(); await page.setViewportSize({ width: 390, height: 844 }) await open(page, 'root'); await expect(dialog.getByText('当前登录', { exact: true })).toBeVisible() expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(391) await page.screenshot({ path: info.outputPath('admin-sessions-mobile.png'), fullPage: true, animations: 'disabled' }) expect(calls.revoked).toEqual([]) }) test('撤销指定登录需确认,取消不提交,成功刷新', async ({ page }) => { const calls = await fixture(page), dialog = await open(page, 'maintainer') await expect(dialog.getByText('共 21 次有效登录', { exact: true })).toBeVisible() await dialog.getByRole('button', { name: '撤销登录', exact: true }).first().click() await page.locator('.el-message-box').getByRole('button', { name: '取消', exact: true }).click() expect(calls.revoked).toEqual([]) await dialog.getByRole('button', { name: '撤销登录', exact: true }).first().click() await page.locator('.el-message-box').getByRole('button', { name: '确认提交', exact: true }).click() await expect(dialog.getByText('共 20 次有效登录', { exact: true })).toBeVisible() expect(calls.revoked).toEqual(['other/other-0']) }) test('查询和撤销失败清除旧结果并显示错误,不误报成功', async ({ page }) => { const calls = await fixture(page, { deny: true }), dialog = await open(page, 'maintainer') await dialog.getByRole('button', { name: '撤销登录', exact: true }).first().click() await page.locator('.el-message-box').getByRole('button', { name: '确认提交', exact: true }).click() await expect(dialog.getByText('当前账号没有该操作权限', { exact: true })).toBeVisible() await expect(dialog.getByRole('button', { name: '撤销登录', exact: true })).toHaveCount(0) expect(calls.revoked).toHaveLength(1) await page.unrouteAll(); await fixture(page, { fail: true }); await open(page, 'maintainer') await expect(page.getByRole('dialog').getByText('登录列表暂不可用', { exact: true })).toBeVisible() await expect(page.getByRole('dialog').getByRole('button', { name: '撤销登录', exact: true })).toHaveCount(0) }) test('切换账号后旧请求不能覆盖新的登录列表', async ({ page }) => { const calls = await fixture(page, { slow: true }), old = await open(page, 'root') await expect.poll(() => calls.queries.includes('owner')).toBe(true) await old.getByRole('button', { name: '关闭', exact: true }).click() const current = await open(page, 'maintainer') await expect(current.getByText('共 21 次有效登录', { exact: true })).toBeVisible() await page.waitForTimeout(850) await expect(current.getByText('当前登录', { exact: true })).toHaveCount(0) expect(calls.revoked).toEqual([]) })