Files
jerryandClaude Fable 5 1e94de7464 PlantAI-rag-new 首次提交:文献知识工作台
- BFF 薄适配层(FastAPI,8090):QA 代理/SSE 中继/Neo4j 直连/抽取代理/会话持久化
- Vue3 前端:五相位检索流水线、文献来源与证据链、知识图谱、提取任务四 tab
- 四档响应式(桌面/笔记本/平板/手机)、深浅主题
- 81 个 FR 全部实现,测试追溯矩阵 81/81,后端 121 测/前端 156 测
- 真实凭据已脱敏,运行时配置在 backend/.env(不入 git)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 20:47:52 +08:00

143 lines
6.4 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.
// session-theme.spec.js —— T 归属。L5 主线 4 会话管理(TC-E2E-04-*)+ 主线 6 主题(TC-E2E-06-*)。
// R1 结构:新建/重命名/搜索/分组(核心,硬断言,缺陷独立);活动高亮与删除入口为缺陷探针
// (独立 test,失败即缺陷证据,不连累核心用例)。三档视口行为一致但会话数据共享——桌面执行核心,
// 平板/手机执行新建+搜索精简分支。
import { test, expect } from '@playwright/test';
import { tier, newSession, cleanupSessionsByTitle, cleanupNewSessions, sessionIds } from './helpers.js';
const PREFIX = 'R1会话-';
test.describe('E2E-04 会话管理', () => {
let pageRef = null;
let preIds = null;
test.afterEach(async () => {
// 按 ID 快照精确清理(标题前缀只覆盖已重命名的会话;未重命名的「新会话」按 ID 删)
if (pageRef) {
await cleanupSessionsByTitle(pageRef, PREFIX);
await cleanupNewSessions(pageRef, preIds);
}
});
test('新建/重命名/搜索过滤/分组(核心)', async ({ page }) => {
test.setTimeout(120_000);
pageRef = page;
preIds = await sessionIds(page);
await cleanupSessionsByTitle(page, PREFIX);
await page.goto('/');
await expect(page.locator('.app-shell')).toBeVisible();
// 1. 新建两个会话(连点两次 → 两个独立会话,不重复)
const before = await page.locator('.session-item').count();
await newSession(page);
await newSession(page);
await expect(page.locator('.session-item')).toHaveCount(before + 2, { timeout: 10_000 });
// 2. 双击重命名(Enter 提交);标题同步到列表与顶栏(FR-SESSION-006)
const firstItem = page.locator('.session-item').first();
await firstItem.dblclick();
const renameInput = firstItem.locator('.rename-input');
await expect(renameInput).toBeVisible();
await renameInput.fill(`${PREFIX}甲`);
await renameInput.press('Enter');
await expect(page.locator('.session-item').first().locator('.title')).toContainText(`${PREFIX}甲`);
if (tier(page) !== 'mobile') {
await expect(page.locator('.top-title .t')).toContainText(`${PREFIX}甲`);
}
// 第二个会话也重命名,便于后文按前缀过滤断言
const secondItem = page.locator('.session-item').nth(1);
await secondItem.dblclick();
await secondItem.locator('.rename-input').fill(`${PREFIX}乙`);
await secondItem.locator('.rename-input').press('Enter');
await expect(page.locator('.session-item').nth(1).locator('.title')).toContainText(`${PREFIX}乙`);
// 3. 搜索过滤:大小写不敏感子串;无匹配文案;清空恢复;期间无网络请求(FR-SESSION-005)
let reqCount = 0;
const onReq = () => { reqCount += 1; };
page.on('request', onReq);
const searchBox = page.locator('.search-box input').first();
await searchBox.fill('r1会话'); // 小写 r1 应命中大写 R1(大小写不敏感)
await expect(page.locator('.session-item')).toHaveCount(2);
await searchBox.fill('甲');
await expect(page.locator('.session-item')).toHaveCount(1);
await searchBox.fill('不存在的会话名XYZ');
await expect(page.locator('.empty-search')).toContainText('无匹配会话');
await expect(page.locator('.session-item')).toHaveCount(0);
await searchBox.fill('');
await expect(page.locator('.session-item')).toHaveCount(before + 2);
expect(reqCount, '搜索过滤不应发网络请求').toBe(0);
page.off('request', onReq);
// 4. 分组标签(今天组存在——新会话更新时间为今天,FR-SESSION-001)
await expect(page.locator('.group-label').first()).toContainText('今天');
});
// 缺陷探针 A(BUG-R1-02 修复验证):点击会话应置活动态高亮(FR-SESSION-009)
test('活动态高亮探针', async ({ page }) => {
test.setTimeout(60_000);
pageRef = page;
preIds = await sessionIds(page);
await page.goto('/');
await expect(page.locator('.app-shell')).toBeVisible();
await newSession(page);
await newSession(page);
// 点击第二项(选中它,并自动切换到它)
const second = page.locator('.session-item').nth(1);
await second.click();
// 硬断言:活动项应有 .active(FR-SESSION-009)
await expect(second, '点击会话后应显示活动态高亮(FR-SESSION-009)').toHaveClass(/active/, { timeout: 10_000 });
});
// 缺陷探针 B(BUG-R1-03 修复验证):删除入口是否存在(FR-SESSION-007 二次确认弹层需可达)
test('删除入口探针', async ({ page }) => {
test.setTimeout(60_000);
pageRef = page;
preIds = await sessionIds(page);
await page.goto('/');
await expect(page.locator('.app-shell')).toBeVisible();
await newSession(page);
const firstItem = page.locator('.session-item').first();
await firstItem.click({ button: 'right' });
// 硬断言:右键应唤出删除确认弹层(BUG-R1-03 修复验证)
await expect(
page.locator('.confirm-box'),
'右键应唤出删除确认弹层(FR-SESSION-007 删除需二次确认)'
).toBeVisible({ timeout: 5_000 });
});
});
test.describe('E2E-06 主题', () => {
test('深浅切换即时生效 + 刷新持久化', async ({ page }) => {
test.setTimeout(60_000);
await page.goto('/');
await expect(page.locator('.app-shell')).toBeVisible();
// 初始主题(默认深色或 localStorage 已有值)
const initial = await page.evaluate(() => document.documentElement.dataset.theme);
const themeBtn = page.locator('.top-bar .icon-btn').last();
// 切换到另一主题 → dataset 即时更新 + localStorage 落值
await themeBtn.click();
await expect
.poll(() => page.evaluate(() => document.documentElement.dataset.theme), { timeout: 5_000 })
.not.toBe(initial);
const toggled = await page.evaluate(() => document.documentElement.dataset.theme);
const stored = await page.evaluate(() => localStorage.getItem('pa-theme'));
expect(stored).toBe(toggled);
// 刷新 → 保持
await page.reload();
await expect(page.locator('.app-shell')).toBeVisible();
await expect
.poll(() => page.evaluate(() => document.documentElement.dataset.theme), { timeout: 5_000 })
.toBe(toggled);
// 复原初始主题(避免影响后续用例的视觉断言)
if (toggled !== initial) await themeBtn.click();
});
});