Files

149 lines
7.2 KiB
JavaScript
Raw Permalink 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.
// bench.spec.js —— T 归属。L5 主线 7 图谱(TC-E2E-07-*)+ 主线 8 抽取(TC-E2E-08-*)。
// 图谱:答案图谱(真实 BFF 返回 243 节点 → 桌面/平板超 50 降级列表视图;移动端上限 30)、
// 实体搜索(真实 Neo4j name_norm)、邻域展开、节点详情。
// 抽取:真实上游 8000 任务列表/详情/日志;上传在 mock 拦截下模拟成功。
import { test, expect } from '@playwright/test';
import { tier, selectBenchTab, openBench, benchScope, openSessionPanel, closeSessionPanel, cleanupSessionsByTitle } from './helpers.js';
test.describe('E2E-07 知识图谱', () => {
let pageRef = null;
test.afterEach(async () => {
if (pageRef) await cleanupSessionsByTitle(pageRef, 'R1图谱');
});
test('实体搜索 → 邻域渲染 → 节点详情 → 答案图谱降级/渲染', async ({ page }) => {
test.setTimeout(180_000);
const t = tier(page);
pageRef = page;
await cleanupSessionsByTitle(page, 'R1图谱');
await page.goto('/');
await expect(page.locator('.app-shell')).toBeVisible();
// 打开知识图谱 tab
await selectBenchTab(page, '知识图谱');
const sc = benchScope(page);
const kgPane = sc.locator('.kg-pane');
await expect(kgPane).toBeVisible({ timeout: 10_000 });
// 1. 空态(无问答时提示引导搜索)
await expect(kgPane.locator('.empty-note')).toContainText('图谱', { timeout: 10_000 });
// 2. 实体搜索(真实 Neo4j;精确/前缀匹配,<1s 内返回——超时上限放宽到 10s)
const searchInput = kgPane.locator('.search-box');
await searchInput.fill('OsSPL14');
await kgPane.locator('.search-btn').click();
await expect(kgPane.locator('.result-item').first()).toBeVisible({ timeout: 10_000 });
await expect(kgPane.locator('.result-item').first()).toContainText('OsSPL14');
// 3. 选中结果 → 邻域渲染(一跳)。真实数据 OsSPL14 一跳返回 51 节点,
// 按 FR-KG-001 节点数 >50 产品降级为列表视图——先等待两种终态之一出现,再按形态断言
const firstResult = kgPane.locator('.result-item').first();
await firstResult.click();
await expect(kgPane.locator('.degrade-note, .graph-svg').first()).toBeVisible({ timeout: 20_000 });
const degraded = (await kgPane.locator('.degrade-note').count()) > 0;
if (degraded) {
await expect(kgPane.locator('.node-list')).toBeVisible();
await expect(kgPane.locator('.node-list-item').first()).toBeVisible();
} else {
await expect(kgPane.locator('.node-g').first()).toBeVisible();
}
await expect(kgPane.locator('.nb-center')).toContainText(/OsSPL14/i, { timeout: 20_000 });
// 4. 节点详情:图模式点 node-g;列表模式点 node-list-item(均打开侧滑面板)
if (degraded) {
await kgPane.locator('.node-list-item').first().click();
} else {
await kgPane.locator('.node-g').first().click();
}
await expect(page.locator('.kg-entity-detail')).toBeVisible({ timeout: 10_000 });
await expect(page.locator('.kg-entity-detail .close-btn')).toBeVisible();
await page.locator('.kg-entity-detail .close-btn').click();
await expect(page.locator('.kg-entity-detail')).toHaveCount(0);
// 5. 邻域深度切换(深度 2 较慢,断言慢提示存在即可)
if (await kgPane.locator('.depth-row').count()) {
await kgPane.locator('.depth-row .chip').nth(1).click();
await expect(kgPane.locator('.nb-slow, .depth-hint').first()).toBeAttached({ timeout: 60_000 });
}
// 6. 答案图谱路径:经历史会话(已有消息)验证 graph 块渲染路径。
// 先关闭工作台(手机覆盖层/平板抽屉 scrim 会拦截后续顶栏点击),再打开会话面板
if (tier(page) === 'mobile') {
await page.locator('.bench-overlay .bench-close-btn').click();
await expect(page.locator('.bench-overlay')).toHaveCount(0);
} else if (tier(page) === 'tablet') {
await page.keyboard.press('Escape');
await expect(page.locator('.drawer.right.open')).toHaveCount(0);
}
await openSessionPanel(page);
const item = page.locator('.session-item').first();
await item.click();
await expect(page.locator('.msg.assistant, .empty-state')).toBeAttached({ timeout: 10_000 });
// 关闭会话抽屉(scrim 会拦截工作台按钮点击),再打开知识图谱 tab
await closeSessionPanel(page);
await selectBenchTab(page, '知识图谱');
// 历史消息 graph 块渲染为画布或降级列表(节点 >50 → 降级提示 + 列表视图)
await expect(
page.locator('.kg-pane .graph-svg, .kg-pane .node-list, .kg-pane .empty-note').first()
).toBeVisible({ timeout: 15_000 });
if (await page.locator('.kg-pane .degrade-note').count()) {
await expect(page.locator('.kg-pane .node-list')).toBeVisible();
}
});
});
test.describe('E2E-08 上传文献', () => {
test('任务列表 → 详情展开 → 运行日志 → 上传(mock 成功)', async ({ page }) => {
test.setTimeout(120_000);
await cleanupSessionsByTitle(page, 'R1抽取');
// 上传接口 mock:成功响应(避免真实 MinerU 环境问题影响 UI 断言)
await page.route('**/api/extraction/upload', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, job_id: 'mockjob000001', status_url: '/api/extraction/jobs/mockjob000001', message: '已提交抽取任务' }),
})
);
await page.goto('/');
await expect(page.locator('.app-shell')).toBeVisible();
await selectBenchTab(page, '上传文献');
const sc = benchScope(page);
const extPane = sc.locator('.extraction-pane');
// 1. 任务列表(真实上游 49 个历史任务)
await expect(extPane.locator('.prog-item').first()).toBeVisible({ timeout: 15_000 });
const pctText = await extPane.locator('.pi-pct').first().textContent();
expect(pctText).toMatch(/%|失败|错误/); // 百分比或 failed 错误态
// 2. 详情展开(点击头部)
const firstJob = extPane.locator('.prog-item').first();
await firstJob.locator('.pi-head').click();
await expect(extPane.locator('.job-detail').first()).toBeVisible({ timeout: 15_000 });
await expect(extPane.locator('.job-detail .status-row')).toBeVisible();
// 3. 运行日志(mono 展示;真实上游为空属正常——DISCOVERY.md §3.4)
await extPane.locator('.log-toggle').click();
await expect(extPane.locator('.run-log')).toBeVisible({ timeout: 10_000 });
// 4. 上传(mock 成功):选择文件 → 成功 ✓ 提示 + 刷新列表
const fileInput = extPane.locator('.hidden-input');
await fileInput.setInputFiles({
name: 'r1-sample.pdf',
mimeType: 'application/pdf',
buffer: Buffer.from('%PDF-1.4 mock'),
});
await expect(extPane.locator('.upload-msg.ok')).toBeVisible({ timeout: 10_000 });
// 5. 非 PDF 被拒(前端拦截,不发请求)
await fileInput.setInputFiles({
name: 'not-a-pdf.txt',
mimeType: 'text/plain',
buffer: Buffer.from('hello'),
});
await expect(extPane.locator('.upload-msg.err')).toContainText('仅支持 PDF', { timeout: 10_000 });
});
});