import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron'; test('personal chat displays thinking, tools and Markdown, then restores them after reload', async ({ launchElectronApp }, testInfo) => { const app = await launchElectronApp({ skipSetup: true }); try { const page = await getStableWindow(app); await expect(page.getByTestId('ai-module-selection-page')).toBeVisible(); const applicationUrl = page.url(); await page.goto('about:blank'); // Cloud provider is deterministic here; the real history HTTP/PG boundary is tested in Yuxi. await app.evaluate(({ ipcMain, BrowserWindow }) => { BrowserWindow.getAllWindows()[0].setSize(1180, 820); let complete = false; Object.assign(globalThis, { finishRenderRun: () => { complete = true; } }); const slug = 'ml-' + 'a'.repeat(32); const configuration = { model: 'test', tools: [], knowledges: [], mcps: [], skills: [], preload_skills: [], subagents: [], tool_approval_mode: 'always_trust', max_execution_steps: 300, max_output_tokens: 4096, max_run_seconds: 600 }; const draft = { slug, name: '渲染测试助手', purpose: '整理资料', system_prompt: '', configuration, draft_revision: 1, published_version: 1, enabled: true, updated_at: '' }; const run = { agent_run_id: 'render-run', request_id: 'render-request', thread_id: 'render-thread', agent_slug: slug, status: 'running', output: '', version: '1' }; const thread = { thread_id: 'render-thread', client_thread_id: 'client', agent_slug: slug, title: '整理资料', mode: 'published', updated_at: '', run_id: run.agent_run_id, unread: false, status: 'running' }; const result = (json: unknown) => ({ ok: true, data: { status: 200, ok: true, json } }); ipcMain.removeHandler('hostapi:fetch'); ipcMain.handle('hostapi:fetch', async (_event, request: { path?: string; body?: string }) => { const path = request.path ?? ''; const body = request.body ? JSON.parse(request.body) : {}; if (path === '/api/auth/session/sync') return result({ success: true, session: { accessToken: 'fixture', tokenType: 'Bearer', expiresAt: Date.now() + 3600000, lastActiveAt: Date.now(), canRefresh: false } }); if (path === '/api/auth/me') return result({ success: true, user: { username: 'creator', userId: 'creator', tenantId: null, deptId: null, authorities: [] }, moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true } }); if (path === '/api/works/user/agent-profile') return result({ success: true, profile: { display_name: '创作者', completed: true, version: 1 } }); if (path.endsWith('/recovery')) return result({ recent: null, pending: [] }); if (path.endsWith('/recovery/recent')) return result({}); if (path.endsWith('/agents') || path.endsWith('/bootstrap')) return result({ agents: [draft], next_cursor: null }); if (path.endsWith('/actions')) { if (body.operation === 'threads') return result({ threads: [thread], next_offset: null }); if (body.operation === 'history') return result({ thread_id: thread.thread_id, run: { ...run, status: complete ? 'completed' : 'running' }, next_offset: null, messages: complete ? [{ id: 1, message_id: 'answer', role: 'assistant', run_id: run.agent_run_id, request_id: run.request_id, created_at: '', content: '## 整理结果\n\n| 项目 | 状态 |\n| --- | --- |\n| 资料 | 已核对 |\n\n**可以使用**', reasoning_content: '先核对来源,再整理结果。', tool_calls: [{ id: 'lookup', name: '资料检索', args: { query: '实际检索资料' }, status: 'success', tool_call_result: { content: '找到两条资料' } }], }] : [] }); if (body.operation === 'run') return result({ ...run, status: complete ? 'completed' : 'running' }); if (body.operation === 'viewed') return result({}); if (body.operation === 'files') return result({ files: [] }); if (body.operation === 'attachments') return result({ attachments: [] }); if (body.operation === 'catalog') return result({ models: [{ id: 'test', name: '测试模型' }], resources: {}, pricing: null }); if (body.operation === 'access') return result({ published_version: 1, enabled: true, versions: [], grants: [], applications: [], share_url: '', api_url: '' }); if (body.operation === 'channelBindings') return result({ items: [], available_accounts: [] }); return result({}); } if (path.startsWith('/api/cloud-agents/agents/')) return result(draft); return result({ success: true }); }); }); await page.addInitScript(() => { const sources: EventTarget[] = []; Object.assign(window, { renderSources: sources }); class FixtureSource extends EventTarget { onopen = null; onerror = null; close() {} constructor(url: string) { super(); if (url.includes('/cloud-agents/runs/')) sources.push(this); } } Object.defineProperty(window, 'EventSource', { value: FixtureSource }); }); await page.goto(applicationUrl); await page.getByTestId('ai-module-option-cloud_agents').click(); await page.getByRole('button', { name: /渲染测试助手.*打开对话/ }).click(); const emit = async (items: unknown[], cursor: string) => page.evaluate(({ items, cursor }) => { const sources = (window as unknown as { renderSources: EventTarget[] }).renderSources; sources.at(-1)!.dispatchEvent(new MessageEvent('messages', { lastEventId: cursor, data: JSON.stringify({ run_id: 'render-run', thread_id: 'render-thread', payload: { items } }) })); }, { items, cursor }); await expect.poll(() => page.evaluate(() => (window as unknown as { renderSources: EventTarget[] }).renderSources.length)).toBeGreaterThan(0); await emit([{ stream_event: { type: 'message_delta', message_id: 'answer', reasoning_content: '先核对来源,再整理结果。' } }], '100-1'); const process = page.getByTestId('cloud-message-process'); await expect(page.getByLabel('智能体思考过程')).toBeVisible(); await emit([ { stream_event: { type: 'tool_call', message_id: 'answer', tool_call_id: 'lookup', name: '资料检索', args: { query: '测试资料' } } }, { event: { method: 'tools', data: { event: 'tool-started', tool_call_id: 'lookup', input: { query: '实际检索资料' } } } }, { event: { method: 'tools', data: { event: 'tool-finished', tool_call_id: 'lookup', output: { content: '找到两条资料' } } } }, { stream_event: { type: 'message_delta', message_id: 'answer', content: '## 整理结果\n\n| 项目 | 状态 |\n| --- | --- |\n| 资料 | 已核对 |\n\n**可以使用**' } }, ], '100-2'); await expect(page.getByRole('heading', { name: '整理结果' })).toBeVisible(); await expect(page.getByRole('table')).toBeVisible(); await process.locator(':scope > summary').click(); await process.getByText('资料检索 · 已完成', { exact: true }).click(); await expect(process.getByText('找到两条资料', { exact: true })).toBeVisible(); await expect(process.getByText('"实际检索资料"', { exact: false })).toBeVisible(); await page.screenshot({ path: testInfo.outputPath('personal-chat-process.png') }); await app.evaluate(() => (globalThis as unknown as { finishRenderRun: () => void }).finishRenderRun()); await page.evaluate(() => (window as unknown as { renderSources: EventTarget[] }).renderSources.at(-1)!.dispatchEvent(new MessageEvent('end', { data: JSON.stringify({ thread_id: 'render-thread', payload: { status: 'completed' } }) }))); await expect(page.getByRole('heading', { name: '整理结果' })).toHaveCount(1); await page.reload(); await page.getByRole('button', { name: /渲染测试助手.*打开对话/ }).click(); await expect(page.getByRole('heading', { name: '整理结果' })).toBeVisible(); await page.getByTestId('cloud-message-process').locator(':scope > summary').click(); await expect(page.getByLabel('智能体思考过程')).toHaveText('先核对来源,再整理结果。'); await expect(page.getByText('资料检索 · 已完成', { exact: true })).toBeVisible(); await page.screenshot({ path: testInfo.outputPath('personal-chat-reopened.png') }); } finally { await closeElectronApp(app); } });