aps-agent/apps/web/e2e/session-titles.spec.ts

75 lines
5.3 KiB
TypeScript
Raw Permalink Normal View History

import { expect, test } from '@playwright/test';
test('会话标题保存、历史补名、手动命名保护和项目快捷新建', async ({ page }, testInfo) => {
test.setTimeout(90_000);
// Refuse all writes until the proxy proves it points at our isolated host.
const health = await page.request.get('/api/health');
const isolated = health.ok() && health.headers()['x-session-title-e2e'] === 'isolated';
if (process.env.SESSION_TITLE_E2E_REQUIRED === '1') expect(isolated).toBe(true);
test.skip(!isolated, 'Run tests/e2e/session_titles_server.py with an isolated E2E_API_TARGET.');
expect((await page.request.post('/api/auth/login', { data: { method: 'password', username: 'planner', password: 'test' } })).ok()).toBe(true);
const created = await (await page.request.post('/api/projects', { data: { name: '新工厂排产项目' } })).json();
const projectId = created.project.id;
const oldId = created.session.id;
await page.request.put(`/api/sessions/${oldId}/messages`, { data: { messages: [
{ role: 'agent', text: '欢迎使用' },
{ role: 'user', text: '【文件解析】订单.xlsx\n\n请检查新工厂订单数据。找出缺失字段。' },
] } });
const manual = await (await page.request.post('/api/sessions', { data: { projectId, title: '我的排产复核' } })).json();
await page.request.put(`/api/sessions/${manual.session.id}/messages`, { data: { messages: [{ role: 'user', text: '这段话不该覆盖手动标题' }] } });
const personal = await (await page.request.post('/api/sessions', { data: { projectId: '__personal__' } })).json();
await page.request.put(`/api/sessions/${personal.session.id}/messages`, { data: { messages: [{ role: 'user', text: '帮我查看本周产能缺口' }] } });
await page.goto('/');
await expect(page.locator('.sidebar-task-main').filter({ hasText: '检查新工厂订单数据' })).toBeVisible();
await expect(page.locator('.sidebar-task-main').filter({ hasText: '查看本周产能缺口' })).toBeVisible();
await expect(page.locator('.sidebar-task-main').filter({ hasText: '我的排产复核' })).toBeVisible();
const projectRow = page.locator('.sidebar-project').filter({ hasText: '新工厂排产项目' });
const quick = projectRow.getByRole('button', { name: '在项目「新工厂排产项目」中新建任务' });
await expect(quick).toBeVisible();
await projectRow.hover();
const quickBox = await quick.boundingBox();
const moreBox = await projectRow.getByTitle('项目操作').boundingBox();
expect(quickBox!.x + quickBox!.width).toBeLessThanOrEqual(moreBox!.x);
const newResponse = page.waitForResponse(res => new URL(res.url()).pathname === '/api/sessions' && res.request().method() === 'POST');
await quick.click();
const newSession = (await (await newResponse).json()).session;
expect(newSession.projectId).toBe(projectId);
await expect(page.locator('.sidebar-task.active .sidebar-task-main')).toContainText('新话题');
await expect.poll(async () => (await (await page.request.get('/api/workspace')).json()).activeSessionId).toBe(newSession.id);
// Only the model response is stubbed; session creation, title writes, history
// persistence, auth and workspace refresh all use the production gateway/DB.
await page.route('**/api/chat', route => route.fulfill({ status: 200, contentType: 'text/event-stream',
body: 'data: {"type":"token","text":"已收到排产检查请求。"}\n\ndata: {"type":"done"}\n\n' }));
const composer = page.locator('.chat textarea');
await composer.fill('你好,麻烦帮我检查下周排产冲突。优先看交期风险。');
await composer.press('Enter');
await expect(page.locator('.sidebar-task.active .sidebar-task-main')).toContainText('检查下周排产冲突');
await expect.poll(async () => (await (await page.request.get(`/api/sessions/${newSession.id}/messages`)).json()).messages.length).toBeGreaterThan(0);
await page.reload();
await expect(page.locator('.sidebar-task.active .sidebar-task-main')).toContainText('检查下周排产冲突');
const saved = await (await page.request.get('/api/workspace')).json();
expect(saved.sessions.find((s: any) => s.id === oldId).title).toBe('检查新工厂订单数据');
expect(saved.sessions.find((s: any) => s.id === manual.session.id).title).toBe('我的排产复核');
const desktop = testInfo.outputPath('sidebar-titles-desktop.png');
await projectRow.hover();
await page.screenshot({ path: desktop, animations: 'disabled' });
await testInfo.attach('desktop sidebar', { path: desktop, contentType: 'image/png' });
await page.setViewportSize({ width: 390, height: 844 });
await page.locator('.app-menu-icon[title="展开侧栏"]').click();
await expect(quick).toBeVisible();
const mobile = testInfo.outputPath('sidebar-titles-mobile.png');
await page.screenshot({ path: mobile, animations: 'disabled' });
expect((await projectRow.boundingBox())!.x).toBeGreaterThanOrEqual(0);
await testInfo.attach('mobile sidebar', { path: mobile, contentType: 'image/png' });
await page.request.post(`/api/projects/${projectId}/members`, { data: { userId: 1003, role: 'viewer' } });
await page.request.post('/api/auth/login', { data: { method: 'password', username: 'viewer', password: 'test' } });
await page.setViewportSize({ width: 1440, height: 900 });
await page.reload();
await expect(projectRow).toBeVisible();
await expect(quick).toHaveCount(0);
});