// ============================================================ // 计划员聊天编号回归:模型常把多个选项都写成 1.,界面必须连续显示 1/2/3。 // 全部离线:只桩会话历史,不连接现场后端。 // ============================================================ import { test, expect, type Page } from '@playwright/test'; const json = (body: unknown, status = 200) => ({ status, contentType: 'application/json', body: JSON.stringify(body), }); async function stubApi(page: Page): Promise { const message = { role: 'agent', text: [ '接下来你有三个选择:', '', '1. 修改现有订单', '', '把数量改成 50 套、交期改成 10 月 14 日。', '', '1. 新增一张急单', '', '换一个新订单号,按你给的数量和交期录入。', '', '1. 直接使用已有急单', '', '按现有急单的交期评估插入后的影响。', ].join('\n'), }; await page.route((url: URL) => url.pathname.startsWith('/api/'), route => { const path = new URL(route.request().url()).pathname; switch (path) { case '/api/health': return route.fulfill(json({ interfaceVersion: '1.0', authProvider: 'local', licenseProvider: 'local' })); case '/api/auth/me': return route.fulfill(json({ user: { user_id: 'user-1', username: 'e2e', fullname: 'E2E', tenant_uuid: 'tenant-1', roles: ['admin'], auth_kind: 'user', }, provider: 'local', license: null, })); case '/api/features': return route.fulfill(json({ version: 1, source: 'default', path: '', error: null, unknown: [], features: {} })); case '/api/workspace': return route.fulfill(json({ projects: [], sessions: [{ id: 'session-1', projectId: '__personal__', title: '编号测试', status: 'done', updatedAt: '2026-09-18 10:00:00', scope: 'personal', }], files: [], messages: { 'session-1': [message] }, activeProjectId: '__personal__', activeSessionId: 'session-1', })); case '/api/sessions/session-1/messages': return route.fulfill(json({ sessionId: 'session-1', messages: [message] })); case '/api/skills': return route.fulfill(json({ skills: [], skillsDir: '', mode: 'web' })); case '/api/skills/health': return route.fulfill(json({ health: [] })); default: return route.fulfill(json({ detail: 'e2e stub' }, 404)); } }); } test('多个备选项即使都写成 1.,页面也连续显示 1、2、3', async ({ page }) => { await stubApi(page); await page.goto('/'); const chat = page.locator('section.chat[data-session-ready="true"]'); await expect(chat).toBeVisible({ timeout: 30_000 }); const items = chat.locator('.chat-md ol.md-list > li'); await expect(items).toHaveCount(3); await expect(items.nth(0)).toContainText('修改现有订单'); await expect(items.nth(1)).toContainText('新增一张急单'); await expect(items.nth(2)).toContainText('直接使用已有急单'); const values = await items.evaluateAll(nodes => nodes.map(node => (node as HTMLLIElement).value)); expect(values).toEqual([1, 2, 3]); });