import type { ElectronApplication, Page } from '@playwright/test'; import { EMPTY_CLOUD_CONFIGURATION, type CloudAgentDraft } from '../../shared/cloud-agents'; import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron'; const agent = (character: string, name: string, published: boolean, extra: Partial = {}): CloudAgentDraft => ({ slug: 'ml-' + character.repeat(32), name, purpose: `让${name}按照我的要求帮助我。`, system_prompt: '用简单清楚的中文回答,先理解我的问题。', draft_revision: 2, updated_at: '2026-10-12T00:00:00Z', configuration: { ...EMPTY_CLOUD_CONFIGURATION, model: 'fixture-model' }, published_version: published ? 1 : null, enabled: true, ...extra, }); const readyAgent = agent('a', '我的故事搭档', true); const draftAgent = agent('b', '我的英语陪练', false, { purpose: '' }); const disabledAgent = agent('c', '暂停使用的助手', true, { enabled: false }); const archivedAgent = agent('d', '以前的学习助手', true, { archived: true }); async function openHome(app: ElectronApplication, agents: CloudAgentDraft[], failArchiveOnce = false): Promise { const page = await getStableWindow(app); await expect(page.getByTestId('ai-module-selection-page')).toBeVisible(); const applicationUrl = page.url(); await page.goto('about:blank'); // The UI fixture owns the Host API boundary. These tests never send a real message or publish an Agent. await app.evaluate(({ ipcMain, BrowserWindow }, { items, failArchiveOnce }) => { BrowserWindow.getAllWindows()[0].setSize(1366, 768); let recent: unknown = null; let archiveAttempts = 0; 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; method?: string; body?: string }) => { const path = request.path ?? ''; if (path === '/api/auth/session/sync') return result({ success: true, session: { accessToken: 'ui-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: '创作者', age: null, gender: null, avatar_url: null, share_age_with_agents: false, share_gender_with_agents: false, analysis_enabled: true, completed: true, version: 1, updated_at: '2026-10-12T00:00:00Z' }, }); if (!path.startsWith('/api/cloud-agents/')) return result({ success: true }); if (path.endsWith('/recovery')) return result({ recent, pending: [] }); if (path.endsWith('/recovery/recent')) { recent = typeof request.body === 'string' ? JSON.parse(request.body) : null; return result({ saved: true }); } if (path.startsWith('/api/cloud-agents/agents')) { const url = new URL(path, 'http://fixture.local'); if (url.pathname === '/api/cloud-agents/agents') return result({ agents: items.filter(item => url.searchParams.get('archived') === 'true' || !item.archived), next_cursor: null, }); const selected = items.find(item => item.slug === url.pathname.split('/')[4]); if (!selected) throw new Error('Unknown fixture Agent ' + url.pathname); return result(selected); } if (path.endsWith('/actions')) { const body = JSON.parse(request.body ?? '{}'); const selected = items.find(item => item.slug === body.input?.slug) ?? items[0]; switch (body.operation) { case 'archiveAgent': { archiveAttempts += 1; if (failArchiveOnce && archiveAttempts === 1) throw new Error('暂时无法删除,请稍后重试'); if (!selected) throw new Error('Unknown fixture Agent'); selected.archived = body.input.archived; return result(selected); } case 'catalog': return result({ models: [{ id: 'fixture-model', name: '创作模型' }], resources: {}, pricing: null }); case 'resources': return result({ mcps: [], skills: [], subagents: [] }); case 'knowledge': return result({ databases: [], models: [] }); case 'budget': return result({ agent_slug: selected?.slug, request_limit_points: null, daily_limit_points: null, daily_committed_points: '0.00', timezone: 'Asia/Shanghai', unit: '词元点数', resets_at: '2026-10-13T00:00:00+08:00' }); case 'access': return result({ published_version: selected?.published_version, enabled: selected?.enabled, share_url: 'niancode://agents/' + selected?.slug, grants: [], applications: [], versions: selected?.published_version ? [{ version: 1, draft_revision: selected.draft_revision, created_at: '2026-10-12T00:00:00Z' }] : [], }); case 'threads': return result({ threads: [], next_offset: null }); case 'received': return result({ agents: [] }); case 'channelSelfCallers': return result({ items: [] }); case 'channelBindings': return result({ items: [], available_accounts: [] }); default: throw new Error('Unexpected homepage fixture operation ' + body.operation); } } throw new Error('Unexpected homepage fixture request ' + path); }); }, { items: agents, failArchiveOnce }); await page.goto(applicationUrl); await page.getByTestId('ai-module-option-cloud_agents').click(); await expect(page.getByTestId('cloud-agents-page')).toBeVisible(); return page; } test('Agent homepage guides the first creation without starter tiles', async ({ launchElectronApp }, testInfo) => { const app = await launchElectronApp({ skipSetup: true }); try { const page = await openHome(app, []); const home = page.getByTestId('cloud-agents-page'); await expect(home.getByRole('heading', { name: 'Hi,今天想创造还是想聊聊?', exact: true })).toBeVisible(); await expect(home.getByRole('heading', { name: '我的智能体', exact: true })).toHaveCount(0); await expect(page.getByText('智能体工作区', { exact: true })).toHaveCount(0); await expect(home.getByRole('button', { name: '更多', exact: true })).toHaveCount(0); await expect(home.getByRole('heading', { name: '做一个属于你的 AI 助手吧', exact: true })).toBeVisible(); await expect(home.getByRole('article')).toHaveCount(0); await expect(home.getByRole('button', { name: /故事搭档|英语陪练|学习Friend/ })).toHaveCount(0); await expect(home.getByText('收到的分享', { exact: true })).toBeHidden(); await expect(home.getByText('活动', { exact: true })).toBeHidden(); await page.screenshot({ path: testInfo.outputPath('cloud-agent-home-empty.png') }); await home.getByRole('button', { name: '创建我的第一个智能体', exact: true }).click(); await expect(page.getByRole('heading', { name: '创建智能体', exact: true })).toBeVisible(); await expect(page.getByLabel('名称', { exact: true })).toBeVisible(); await expect(page.getByLabel('名称', { exact: true })).toHaveValue(''); await expect(page.getByRole('group', { name: '智能体头像', exact: true })).toBeVisible(); await expect(page.getByRole('button', { name: '上传头像', exact: true })).toBeVisible(); await expect(page.getByLabel('用途', { exact: true })).toHaveCount(0); await expect(page.getByLabel('提示词', { exact: true })).toHaveCount(0); await expect(page.getByLabel('特别要求', { exact: true })).toHaveCount(0); await expect(page.getByRole('button', { name: /简单易懂|一步一步来|先给我提示/ })).toHaveCount(0); await expect(page.getByRole('button', { name: '下一步', exact: true })).toBeDisabled(); await expect(page.getByRole('button', { name: /科学问答助手|故事搭档|英语陪练|整理小帮手/ })).toHaveCount(0); await expect(page.getByRole('button', { name: '我自己想一个', exact: true })).toHaveCount(0); await expect(page.getByRole('button', { name: '上一步', exact: true })).toHaveCount(0); await page.screenshot({ path: testInfo.outputPath('cloud-agent-home-direct-create.png') }); } finally { await closeElectronApp(app); } }); test('Agent cards open conversation, adjustment and unfinished creation explicitly', async ({ launchElectronApp }, testInfo) => { const app = await launchElectronApp({ skipSetup: true }); try { const page = await openHome(app, [readyAgent, draftAgent, disabledAgent, archivedAgent]); const home = page.getByTestId('cloud-agents-page'); const ready = home.getByRole('article', { name: readyAgent.name, exact: true }); const draft = home.getByRole('article', { name: draftAgent.name, exact: true }); const disabled = home.getByRole('article', { name: disabledAgent.name, exact: true }); await expect(home.getByRole('article')).toHaveCount(3); await expect(ready.getByRole('button', { name: '和它聊聊', exact: true })).toBeVisible(); await expect(ready.getByRole('button', { name: '调整', exact: true })).toBeVisible(); await expect(draft.getByRole('button', { name: '继续制作', exact: true })).toBeVisible(); await expect(draft.getByRole('button', { name: '和它聊聊', exact: true })).toHaveCount(0); await expect(draft.locator('.agent-home-purpose')).toHaveCount(0); await expect(draft.getByRole('button', { name: `删除${draftAgent.name}`, exact: true })).toBeVisible(); await expect(disabled.getByRole('button', { name: '查看与调整', exact: true })).toBeVisible(); await expect(disabled.getByRole('button', { name: '和它聊聊', exact: true })).toHaveCount(0); await page.screenshot({ path: testInfo.outputPath('cloud-agent-home-populated.png') }); await app.evaluate(({ BrowserWindow }) => { const win = BrowserWindow.getAllWindows()[0]; win.setMinimumSize(800, 600); win.setSize(980, 680); }); await expect.poll(() => home.evaluate(el => el.scrollWidth - el.clientWidth)).toBeLessThanOrEqual(1); expect(await page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth)).toBeLessThanOrEqual(1); await page.screenshot({ path: testInfo.outputPath('cloud-agent-home-compact.png') }); await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].setSize(1366, 768)); await ready.getByRole('button', { name: '和它聊聊', exact: true }).click(); await expect(page.getByRole('region', { name: '智能体对话', exact: true })).toBeVisible(); await expect(page.getByTestId('cloud-agent-editor').getByRole('button', { name: '聊天', exact: true })).toHaveCount(0); await page.getByTestId('cloud-agent-editor').getByRole('button', { name: '我的智能体', exact: true }).click(); await expect(ready).toBeVisible(); await ready.getByRole('button', { name: '调整', exact: true }).click(); await expect(page.getByRole('tab', { name: '介绍与提示词', exact: true })).toHaveAttribute('aria-selected', 'true'); await expect(page.getByTestId('cloud-agent-editor').getByRole('button', { name: /^(聊天|自动任务|使用与接入)$/ })).toHaveCount(0); await expect(page.getByTestId('cloud-agent-editor').getByRole('button', { name: '修改头像', exact: true })).toBeVisible(); await expect(page.getByRole('img', { name: readyAgent.name + '默认头像', exact: true })).toBeVisible(); await expect(page.getByLabel('提示词', { exact: true })).toHaveValue(readyAgent.system_prompt); await page.getByTestId('cloud-agent-editor').getByRole('button', { name: '我的智能体', exact: true }).click(); await draft.getByRole('button', { name: '继续制作', exact: true }).click(); await expect(page.getByRole('tab', { name: '介绍与提示词', exact: true })).toHaveAttribute('aria-selected', 'true'); await expect(page.getByLabel('一句话介绍', { exact: true })).toHaveValue(''); await expect(page.getByLabel('名称', { exact: true })).toHaveValue(draftAgent.name); await expect(page.getByLabel('提示词', { exact: true })).toHaveValue(draftAgent.system_prompt); } finally { await closeElectronApp(app); } }); test('Agent deletion confirms, preserves a failed card for retry and keeps other cards', async ({ launchElectronApp }, testInfo) => { const app = await launchElectronApp({ skipSetup: true }); try { const page = await openHome(app, [readyAgent, draftAgent, archivedAgent], true); const home = page.getByTestId('cloud-agents-page'); await expect(home.getByRole('article', { name: archivedAgent.name, exact: true })).toHaveCount(0); await expect(home.getByRole('button', { name: '更多', exact: true })).toHaveCount(0); await expect(home.getByText('收到的分享', { exact: true })).toHaveCount(0); await expect(home.getByText('活动', { exact: true })).toHaveCount(0); await expect(home.getByText('查看已归档智能体', { exact: true })).toHaveCount(0); const ready = home.getByRole('article', { name: readyAgent.name, exact: true }); const draft = home.getByRole('article', { name: draftAgent.name, exact: true }); await ready.getByRole('button', { name: `删除${readyAgent.name}`, exact: true }).click(); const dialog = page.getByRole('dialog', { name: `删除「${readyAgent.name}」?`, exact: true }); await expect(dialog.getByText('它会从「我的智能体」中移除,并停止后续使用。已有的聊天记录会保留。', { exact: true })).toBeVisible(); await expect(dialog.getByRole('button', { name: '取消', exact: true })).toBeFocused(); await dialog.getByRole('button', { name: '取消', exact: true }).click(); await expect(dialog).toBeHidden(); await expect(ready).toBeVisible(); await ready.getByRole('button', { name: `删除${readyAgent.name}`, exact: true }).click(); await dialog.getByRole('button', { name: '删除', exact: true }).click(); await expect(dialog.getByRole('alert')).toContainText('智能体仍在列表中,你可以重试。'); await page.screenshot({ path: testInfo.outputPath('cloud-agent-home-delete-retry.png') }); await dialog.getByRole('button', { name: '重试删除', exact: true }).click(); await expect(dialog).toBeHidden(); await expect(ready).toHaveCount(0); await expect(draft).toBeVisible(); await expect(home.getByRole('article')).toHaveCount(1); await page.reload(); await expect(home.getByRole('heading', { name: 'Hi,今天想创造还是想聊聊?', exact: true })).toBeVisible(); await expect(ready).toHaveCount(0); await expect(draft).toBeVisible(); } finally { await closeElectronApp(app); } });