init: 2026Technology-Competition initial commit

This commit is contained in:
xunhe
2026-08-27 17:35:01 +08:00
commit e2a4d40c9e
311 changed files with 37037 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
import { test, expect } from '@playwright/test'
import { ADMIN } from '../utils/auth'
test.describe('登录', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login')
await page.evaluate(() => localStorage.clear())
await page.reload()
})
test('成功登录:跳转工作台并存储 token', async ({ page }) => {
await page.getByPlaceholder('账号').fill(ADMIN.username)
await page.getByPlaceholder('密码').fill(ADMIN.password)
await page.getByRole('button', { name: /登\s*录/i }).click()
await expect(page).toHaveURL(/\/dashboard/)
const token = await page.evaluate(() => localStorage.getItem('accessToken'))
expect(token).toBeTruthy()
})
test('失败登录:错误密码提示', async ({ page }) => {
await page.getByPlaceholder('账号').fill(ADMIN.username)
await page.getByPlaceholder('密码').fill('wrong-password')
await page.getByRole('button', { name: /登\s*录/i }).click()
await expect(page.getByText('账号或密码错误')).toBeVisible()
})
test('未登录访问受保护路由:重定向到登录页', async ({ page }) => {
await page.goto('/issues')
await expect(page).toHaveURL(/\/login/)
})
})
+57
View File
@@ -0,0 +1,57 @@
import { test, expect } from '@playwright/test'
import { uiLogin } from '../utils/auth'
import { createIssueViaApi, deleteIssueViaApi, changeStatusViaApi } from '../utils/api-helpers'
test.describe('指摘 CRUD', () => {
let apiToken = ''
let issueId = 0
let issueNo = ''
test.beforeEach(async ({ page, request }) => {
const created = await createIssueViaApi(request)
issueId = created.issue.id
issueNo = created.issue.issueNo
apiToken = created.token
await uiLogin(page)
})
test.afterEach(async ({ request }) => {
if (issueId) {
await deleteIssueViaApi(request, issueId, apiToken).catch(() => {})
}
})
test('列表页显示新建的指摘', async ({ page }) => {
await page.goto('/issues')
await expect(page.getByText('指摘列表')).toBeVisible()
await expect(page.getByText(issueNo)).toBeVisible()
})
test('详情页显示完整信息与状态流转历史', async ({ page }) => {
await page.goto(`/issues/${issueId}`)
await expect(page.getByText(issueNo)).toBeVisible()
await expect(page.getByText('状态流转历史')).toBeVisible()
await expect(page.getByText('创建指摘').first()).toBeVisible()
})
test('合法状态流转 draft -> open', async ({ page }) => {
await page.goto(`/issues/${issueId}`)
const dialog = page.getByRole('dialog')
await page.getByRole('button', { name: /更改状态/i }).click()
await expect(dialog).toBeVisible()
await dialog.locator('.ant-select-input').click()
const opt = page.locator('.ant-select-item-option[title="待处理"]')
await expect(opt).toBeVisible()
await opt.click()
await expect(dialog.locator('.ant-select-content')).toContainText('待处理')
await dialog.getByRole('button', { name: /确认变更/i }).click()
await expect(page.getByText('状态已更新')).toBeVisible()
})
test('非法状态流转返回 400 错误提示', async ({ request }) => {
const ok = await changeStatusViaApi(request, issueId, apiToken, 'open')
expect(ok.status()).toBe(200)
const illegal = await changeStatusViaApi(request, issueId, apiToken, 'closed')
expect(illegal.status()).toBe(400)
})
})
+35
View File
@@ -0,0 +1,35 @@
import { test, expect } from '@playwright/test'
import { uiLogin } from '../utils/auth'
import { createIssueViaApi, deleteIssueViaApi } from '../utils/api-helpers'
test.describe('附件功能', () => {
let apiToken = ''
let issueId = 0
test.beforeEach(async ({ page, request }) => {
const created = await createIssueViaApi(request)
issueId = created.issue.id
apiToken = created.token
await uiLogin(page)
})
test.afterEach(async ({ request }) => {
if (issueId) {
await deleteIssueViaApi(request, issueId, apiToken).catch(() => {})
}
})
test('上传附件后列表显示文件信息', async ({ page }) => {
await page.goto(`/issues/${issueId}`)
await expect(page.getByText('附件 (0)')).toBeVisible()
await page.setInputFiles('input[type=file]', {
name: 'e2e-test.txt',
mimeType: 'text/plain',
buffer: Buffer.from('IMS E2E attachment content'),
})
await expect(page.getByText('上传成功')).toBeVisible()
await expect(page.getByText(/e2e-test\.txt/)).toBeVisible()
await expect(page.getByText(/附件 \(1\)/)).toBeVisible()
})
})
@@ -0,0 +1,45 @@
import { test, expect } from '@playwright/test'
import { uiLogin } from '../utils/auth'
import { createIssueViaApi, deleteIssueViaApi } from '../utils/api-helpers'
test.describe('Agent 驾驶舱', () => {
let apiToken = ''
let issueId = 0
test.beforeEach(async ({ page, request }) => {
const created = await createIssueViaApi(request)
issueId = created.issue.id
apiToken = created.token
await uiLogin(page)
})
test.afterEach(async ({ request }) => {
if (issueId) {
await deleteIssueViaApi(request, issueId, apiToken).catch(() => {})
}
})
test('执行指令后显示工具卡片', async ({ page }) => {
test.setTimeout(560000)
await page.goto(`/issues/${issueId}`)
await expect(page.getByText('IMS Agent 驾驶舱')).toBeVisible()
await page.getByPlaceholder(/给 Agent 下达指令/).fill('查找知识库相似案例并生成对应方案')
await page.getByRole('button', { name: /执行指令/i }).click()
// 模型输出工具调用后 SSE 渲染工具卡片(真实模型可能只输出 search_knowledge,不强依赖具体工具)
await expect(page.getByText(/call:/).first()).toBeVisible({ timeout: 480000 })
})
test('写工具触发审批后可批准', async ({ page }) => {
test.setTimeout(480000)
await page.goto(`/issues/${issueId}`)
await page.getByPlaceholder(/给 Agent 下达指令/).fill('请调用 update_issue 工具,把本指摘的优先级改为 high')
await page.getByRole('button', { name: /执行指令/i }).click()
await expect(page.getByText('需要您的审批')).toBeVisible({ timeout: 420000 })
await page.getByRole('button', { name: /批准执行/i }).click()
await expect(page.getByText('已批准执行')).toBeVisible({ timeout: 30000 })
await expect(page.getByText(/Agent 指令已批准/)).toBeVisible({ timeout: 30000 })
})
})
+44
View File
@@ -0,0 +1,44 @@
import { test, expect } from '@playwright/test'
import { uiLogin, apiLogin } from '../utils/auth'
import { getDashboardViaApi } from '../utils/api-helpers'
test.describe('工作台 Dashboard', () => {
let apiToken = ''
test.beforeEach(async ({ request }) => {
apiToken = (await apiLogin(request)).accessToken
})
test('登录后工作台显示统计与图表', async ({ page }) => {
await uiLogin(page)
await page.goto('/dashboard')
await expect(page.getByText('工作台概览')).toBeVisible()
await expect(page.getByText('Agent 快捷指令')).toBeVisible()
await expect(page.getByText('指摘处理趋势')).toBeVisible()
await expect(page.getByText('Agent 洞察')).toBeVisible()
await expect(page.getByText('最新动态')).toBeVisible()
await expect(page.getByText('指摘状态分布')).toBeVisible()
})
test('统计卡显示正确的数量(API 对比)', async ({ page, request }) => {
const stats = await getDashboardViaApi(request, apiToken)
await uiLogin(page)
await page.goto('/dashboard')
const pending = page.locator('.ant-statistic', { hasText: '待处理指摘' }).first()
await expect(pending.getByText('待处理指摘', { exact: true })).toBeVisible()
await expect(pending.getByText(String(stats.pendingCount))).toBeVisible()
await expect(page.locator('.ant-statistic', { hasText: '进行中指摘' }).first()).toBeVisible()
await expect(page.locator('.ant-statistic', { hasText: '本月已完成' }).first()).toBeVisible()
})
test('Agent 快捷指令输入框存在且可输入', async ({ page }) => {
await uiLogin(page)
await page.goto('/dashboard')
const input = page.getByPlaceholder(/例如:查找知识库/)
await input.fill('测试指令')
await expect(input).toHaveValue('测试指令')
})
})
@@ -0,0 +1,30 @@
import { test, expect } from '@playwright/test'
import { uiLogin } from '../utils/auth'
test.describe('通知铃铛', () => {
test('铃铛显示未读数且下拉展开', async ({ page }) => {
await uiLogin(page)
await page.goto('/dashboard')
const bell = page.locator('.ant-badge')
await expect(bell.first()).toBeVisible()
await bell.first().click()
await page.waitForTimeout(1000)
const menu = page.locator('.ant-dropdown')
if (await menu.count() > 0) {
await expect(menu.first()).toBeVisible()
}
})
test('通知下拉包含通知标题或空提示', async ({ page }) => {
await uiLogin(page)
await page.goto('/dashboard')
await page.locator('.ant-badge').first().click()
await page.waitForTimeout(1000)
const hasTitle = await page.getByText('通知').count()
const hasEmpty = await page.getByText('暂无通知').count()
expect(hasTitle + hasEmpty).toBeGreaterThan(0)
})
})
+32
View File
@@ -0,0 +1,32 @@
import { test, expect } from '@playwright/test'
import { uiLogin } from '../utils/auth'
test.describe('新建页 Agent 智能填充', () => {
test('点击「让 Agent 智能填充」真实调用 AI 并回填字段', async ({ page }) => {
test.setTimeout(420000)
await uiLogin(page)
await page.goto('/issues/new')
await page.getByPlaceholder('请输入指摘标题').fill('登录页面在低分辨率下按钮错位、样式异常,影响操作')
const fillBtn = page.getByRole('button', { name: /让 Agent 智能填充/i })
await fillBtn.click()
await expect(fillBtn).toHaveClass(/ant-btn-loading/, { timeout: 15000 })
// 模型调用耗时波动大(数十秒到数分钟),axios 侧 180s 超时后走降级回填,故等待上限放宽到 240s
await expect(fillBtn).not.toHaveClass(/ant-btn-loading/, { timeout: 240000 })
// 工程阶段/区分/影响度三个 Select 至少被回填 3 个非"请选择"值(AI 成功或降级都会回填)
await expect.poll(async () => {
return page.locator('.ant-select').evaluateAll(els =>
els.filter(e => {
const t = e.textContent?.trim() ?? ''
return t !== '' && t !== '请选择'
}).length)
}, { timeout: 30000, intervals: [2000] }).toBeGreaterThanOrEqual(3)
// 优先级 Radio 也被回填(高/中/低 任一)
const checked = (await page.locator('.ant-radio-button-wrapper-checked').allTextContents()).map(s => s.trim()).filter(Boolean)
expect(checked.length).toBeGreaterThan(0)
console.log('filled selects:', JSON.stringify(checked))
})
})
+41
View File
@@ -0,0 +1,41 @@
import { test, expect } from '@playwright/test'
import { uiLogin } from '../utils/auth'
import { createIssueViaApi, deleteIssueViaApi } from '../utils/api-helpers'
test.describe('Agent SSE 流式渲染', () => {
let apiToken = ''
let issueId = 0
test.beforeEach(async ({ page, request }) => {
const created = await createIssueViaApi(request)
issueId = created.issue.id
apiToken = created.token
await uiLogin(page)
})
test.afterEach(async ({ request }) => {
if (issueId) {
await deleteIssueViaApi(request, issueId, apiToken).catch(() => {})
}
})
test('执行指令后 SSE 事件实时渲染 thought / prompt_info / model_info', async ({ page }) => {
test.setTimeout(120000)
await page.goto(`/issues/${issueId}`)
await expect(page.getByText('IMS Agent 驾驶舱')).toBeVisible()
await page.getByPlaceholder(/给 Agent 下达指令/).fill('查找知识库相似案例并生成对应方案')
await page.getByRole('button', { name: /执行指令/i }).click()
// thought:初始连接消息经 SSE 渲染为绿色状态框
await expect(page.getByText(/已连接执行流/)).toBeVisible({ timeout: 20000 })
// model_info:引擎来源 Tag
await expect(page.getByText(/引擎:/)).toBeVisible({ timeout: 20000 })
// prompt_info:展开模板信息,模板标识非"未提供"
await page.getByText('Prompt 模板信息').click()
await expect(page.getByText(/系统角色:\s*\S+/)).toBeVisible()
await expect(page.getByText(/规划模板:\s*\S+/)).toBeVisible()
})
})
@@ -0,0 +1,22 @@
import { test, expect } from '@playwright/test'
import { uiLogin } from '../utils/auth'
test.describe('工作台 Agent 快捷指令执行面板', () => {
test('执行「生成本周报告」打开执行流面板并渲染 SSE 事件', async ({ page }) => {
await uiLogin(page)
await page.goto('/dashboard')
await expect(page.getByText('Agent 快捷指令')).toBeVisible()
const input = page.getByPlaceholder(/例如:查找知识库/)
await input.fill('生成本周指摘处理统计报告')
await page.getByRole('button', { name: /执\s*行/ }).click()
await expect(page.getByText(/Agent 执行流/)).toBeVisible({ timeout: 30000 })
await expect(page.getByText(/指令已提交,正在连接执行流/)).toBeVisible({ timeout: 30000 })
await expect(page.getByText(/已连接执行流/)).toBeVisible({ timeout: 60000 })
const execBox = page.getByText(/Agent 执行流/)
await expect(execBox).toBeVisible()
})
})