Files
L2keka/web/e2e/ui-completeness.spec.ts
T

230 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect, type Page } from '@playwright/test';
const PASSWORD = 'test123';
const UNIQUE = Date.now().toString(36);
async function login(page: Page) {
await page.goto('/login');
await page.fill('input[type="password"]', PASSWORD);
await page.click('button[type="submit"]');
await page.waitForURL('/');
}
async function apiToken(request: any): Promise<string> {
const res = await request.post('http://localhost:3002/api/auth/login', { data: { password: PASSWORD } });
const { token } = await res.json();
return token;
}
// ══════════════════════════════════════
// 1. 成果物 TabDeliverablesView
// ══════════════════════════════════════
test.describe('成果物 Tab', () => {
let pid = '';
let token = '';
test.beforeAll(async ({ request }) => {
token = await apiToken(request);
const proj = await request.post('http://localhost:3002/api/projects', {
headers: { Authorization: `Bearer ${token}` }, data: { name: `deliv-${UNIQUE}`, track: '赛道一' },
});
pid = (await proj.json()).id;
await request.post(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` }, data: { name: 'd-std', content: '## 场景价值(8分)' },
});
await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { title: 'deliv-entry', repo_url: `file://C:\\deliv-${UNIQUE}` },
});
});
test.afterAll(async ({ request }) => {
await request.delete(`http://localhost:3002/api/projects/${pid}?force=true`, { headers: { Authorization: `Bearer ${token}` } });
});
test('初始化一覧 → 勾选 → 提交率更新 → CSV 下载', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.getByRole('button', { name: '成果物' }).click();
await expect(page.getByText('成果物确认')).toBeVisible();
// 初始化一覧
await page.getByRole('button', { name: '初始化一覧' }).click();
await expect(page.locator('.entry-table input[type="checkbox"]').first()).toBeVisible();
// 勾选第一个成果物 → 提交率 +1(经 API 验证)
const before = await page.request.get(`http://localhost:3002/api/projects/${pid}/entries/deliverables/summary`, {
headers: { Authorization: `Bearer ${token}` },
});
const beforeData = await before.json();
await page.locator('.entry-table input[type="checkbox"]').first().check();
await expect.poll(async () => {
const r = await page.request.get(`http://localhost:3002/api/projects/${pid}/entries/deliverables/summary`, {
headers: { Authorization: `Bearer ${token}` },
});
return (await r.json()).totalSubmitted;
}).toBe(beforeData.totalSubmitted + 1);
// CSV 下载
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: '下载CSV' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toContain('deliverables');
});
});
// ══════════════════════════════════════
// 2. 评审进度时间线(DetailPanelD7
// ══════════════════════════════════════
test.describe('评审进度时间线', () => {
let pid = ''; let eid = ''; let token = '';
test.beforeAll(async ({ request }) => {
token = await apiToken(request);
const proj = await request.post('http://localhost:3002/api/projects', {
headers: { Authorization: `Bearer ${token}` }, data: { name: `prog-${UNIQUE}`, track: '赛道一' },
});
pid = (await proj.json()).id;
await request.post(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` }, data: { name: 'p-std', content: '## 场景价值(8分)' },
});
const entry = await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { title: 'prog-entry', repo_url: `file://C:\\prog-${UNIQUE}` },
});
eid = (await entry.json()).id;
// 启动评审(外部路径 → 秒级 clone_fail),写入 progress_log
await request.post(`http://localhost:3002/api/projects/${pid}/entries/${eid}/start`, {
headers: { Authorization: `Bearer ${token}` },
});
await expect.poll(async () => {
const r = await request.get(`http://localhost:3002/api/projects/${pid}/entries/${eid}`, {
headers: { Authorization: `Bearer ${token}` },
});
return (await r.json()).status;
}).toBe('clone_fail');
});
test.afterAll(async ({ request }) => {
await request.delete(`http://localhost:3002/api/projects/${pid}?force=true`, { headers: { Authorization: `Bearer ${token}` } });
});
test('详情面板显示评审进度时间线(含克隆步骤)', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.click('.title-cell');
const progress = page.locator('details', { has: page.getByText('评审进度') });
await expect(progress).toBeVisible();
await expect(progress).toContainText('不允许克隆外部路径');
});
});
// ══════════════════════════════════════
// 3. PDF 下载(单条目报告 / 汇总报告)
// ══════════════════════════════════════
test.describe('PDF 下载', () => {
let pid = ''; let eid = ''; let token = '';
test.beforeAll(async ({ request }) => {
token = await apiToken(request);
const proj = await request.post('http://localhost:3002/api/projects', {
headers: { Authorization: `Bearer ${token}` }, data: { name: `pdf-${UNIQUE}`, track: '赛道一' },
});
pid = (await proj.json()).id;
await request.post(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` }, data: { name: 'pdf-std', content: '## 架构设计(10分)' },
});
const entry = await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { title: 'pdf-entry', repo_url: `file://C:\\pdf-${UNIQUE}` },
});
eid = (await entry.json()).id;
await request.put(`http://localhost:3002/api/projects/${pid}/entries/${eid}/force-review`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { dimensions: [{ name: '架构设计', score: 8, maxScore: 10 }] },
});
});
test.afterAll(async ({ request }) => {
await request.delete(`http://localhost:3002/api/projects/${pid}?force=true`, { headers: { Authorization: `Bearer ${token}` } });
});
test('单条目报告 PDF 下载', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.click('.title-cell');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: '下载报告' }).click();
const download = await downloadPromise;
// 服务端 Content-Disposition 中文文件名在部分平台会被搅乱,这里只断言"触发了 .pdf 下载"
expect(download.suggestedFilename().toLowerCase()).toContain('.pdf');
});
test('汇总报告 PDF 下载', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.getByRole('button', { name: '汇总' }).click();
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: '下载汇总PDF' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename().toLowerCase()).toContain('.pdf');
});
});
// ══════════════════════════════════════
// 4. 人才测评 L2/L3 分表展示
// ══════════════════════════════════════
test.describe('人才测评 L2/L3 分表', () => {
let pid = ''; let eid = ''; let token = '';
test.beforeAll(async ({ request }) => {
token = await apiToken(request);
const proj = await request.post('http://localhost:3002/api/projects', {
headers: { Authorization: `Bearer ${token}` }, data: { name: `l2l3-${UNIQUE}`, track: '人才测评' },
});
pid = (await proj.json()).id;
// 删除自动标准,替换为自定义 Q2 标准
const list = await request.get(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` },
});
for (const s of await list.json()) {
await request.delete(`http://localhost:3002/api/projects/${pid}/standards/${s.id}`, { headers: { Authorization: `Bearer ${token}` } });
}
await request.post(`http://localhost:3002/api/projects/${pid}/standards`, {
headers: { Authorization: `Bearer ${token}` },
data: { name: 'l2-std', category_tag: '人才测评', content: '## 功能完整性(40分)\n## 设计文档(10分)\n## [Q2] LLM生成问卷(15分)' },
});
const entry = await request.post(`http://localhost:3002/api/projects/${pid}/entries`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: { title: 'l2l3-entry', repo_url: `file://C:\\l2-${UNIQUE}`, question_id: 'Q2' },
});
eid = (await entry.json()).id;
await request.put(`http://localhost:3002/api/projects/${pid}/entries/${eid}/force-review`, {
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
data: {
dimensions: [
{ name: '功能完整性', score: 40, maxScore: 40, group: 'common' },
{ name: '设计文档', score: 10, maxScore: 10, group: 'common' },
{ name: 'LLM生成问卷', score: 15, maxScore: 15, group: 'Q2' },
],
},
});
});
test.afterAll(async ({ request }) => {
await request.delete(`http://localhost:3002/api/projects/${pid}?force=true`, { headers: { Authorization: `Bearer ${token}` } });
});
test('详情面板分别展示 L2共通 与 L3追加 分表', async ({ page }) => {
await login(page);
await page.goto(`/project/${pid}`);
await page.click('.title-cell');
await expect(page.getByText('L2共通评分')).toBeVisible();
await expect(page.getByText('L3追加评分')).toBeVisible();
});
});