完整实现 Swarm 多智能体协作系统

- 新增 CLIPluginAdapter 统一接口 (backend/app/core/agent_adapter.py)
- 新增 LLM 服务层,支持 Anthropic/OpenAI/DeepSeek/Ollama (backend/app/services/llm_service.py)
- 新增 Agent 执行引擎,支持文件锁自动管理 (backend/app/services/agent_executor.py)
- 新增 NativeLLMAgent 原生 LLM 适配器 (backend/app/adapters/native_llm_agent.py)
- 新增进程管理器 (backend/app/services/process_manager.py)
- 新增 Agent 控制 API (backend/app/routers/agents_control.py)
- 新增 WebSocket 实时通信 (backend/app/routers/websocket.py)
- 更新前端 AgentsPage,支持启动/停止 Agent
- 测试通过:Agent 启动、批量操作、栅栏同步

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-03-09 17:32:11 +08:00
commit dc398d7c7b
118 changed files with 23120 additions and 0 deletions

View File

@@ -0,0 +1,267 @@
import { test, expect, Page } from '@playwright/test';
/**
* Swarm Command Center - 端到端工作流测试
* 测试完整用户场景和交互流程
*/
// 辅助函数:等待页面加载完成
async function waitForPageLoad(page: Page) {
await page.waitForLoadState('networkidle');
await page.waitForSelector('text=Swarm Center', { state: 'visible' });
}
// 辅助函数:导航到指定页面
async function navigateTo(page: Page, menuText: string) {
await page.click(`text=${menuText}`);
await page.waitForLoadState('networkidle');
}
test.describe('端到端工作流测试', () => {
const baseUrl = 'http://localhost:3000';
test.beforeEach(async ({ page }) => {
await page.goto(baseUrl);
await waitForPageLoad(page);
});
test('完整用户旅程 - 从仪表盘到各功能模块', async ({ page }) => {
// 1. 验证仪表盘首页
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
await expect(page.locator('text=系统概览与实时监控')).toBeVisible();
// 2. 点击统计卡片导航到 Agent 管理
await page.click('text=Agent 总数');
await expect(page).toHaveURL(/\/agents/);
await expect(page.locator('h1:has-text("Agent 管理")')).toBeVisible();
// 3. 返回仪表盘
await navigateTo(page, '仪表盘');
await expect(page).toHaveURL(/\/$/);
// 4. 点击会议统计卡片
await page.click('text=今日会议');
await expect(page).toHaveURL(/\/meetings/);
await expect(page.locator('h1:has-text("会议管理")')).toBeVisible();
});
test('Agent 管理 - 完整 CRUD 流程', async ({ page }) => {
await navigateTo(page, 'Agent 管理');
// 1. 检查页面元素
await expect(page.locator('button:has-text("注册 Agent")')).toBeVisible();
await expect(page.locator('input[placeholder="搜索 Agent..."]')).toBeVisible();
// 2. 打开注册对话框
await page.click('button:has-text("注册 Agent")');
await expect(page.locator('text=注册新 Agent')).toBeVisible();
// 3. 填写表单
const testAgentName = `TestAgent-${Date.now()}`;
await page.fill('input[name="name"]', testAgentName);
await page.fill('input[name="model"]', 'gpt-4');
await page.selectOption('select[name="role"]', 'developer');
await page.fill('textarea[name="description"]', 'Playwright 端到端测试用 Agent');
// 4. 验证表单填写成功
await expect(page.locator('input[name="name"]')).toHaveValue(testAgentName);
await expect(page.locator('select[name="role"]')).toHaveValue('developer');
// 5. 关闭对话框(取消提交,避免影响实际数据)
await page.click('button:has-text("取消")');
await expect(page.locator('text=注册新 Agent')).not.toBeVisible();
// 6. 测试搜索功能
await page.fill('input[placeholder="搜索 Agent..."]', 'nonexistent');
await expect(page.locator('text=没有找到匹配的 Agent')).toBeVisible();
});
test('会议管理 - 创建会议流程', async ({ page }) => {
await navigateTo(page, '会议管理');
// 1. 检查页面元素
await expect(page.locator('button:has-text("创建会议")')).toBeVisible();
// 2. 打开创建会议对话框
await page.click('button:has-text("创建会议")');
await expect(page.locator('text=创建新会议')).toBeVisible();
// 3. 填写会议表单
const testMeetingTitle = `TestMeeting-${Date.now()}`;
await page.fill('input[name="title"]', testMeetingTitle);
await page.fill('textarea[name="agenda"]', '测试议程内容\n1. 测试项1\n2. 测试项2');
await page.selectOption('select[name="type"]', 'design_review');
// 4. 添加参与者
await page.click('button:has-text("添加参与者")');
// 注:实际实现可能需要选择具体的 Agent
// 5. 验证表单填写
await expect(page.locator('input[name="title"]')).toHaveValue(testMeetingTitle);
// 6. 关闭对话框
await page.click('button:has-text("取消")');
await expect(page.locator('text=创建新会议')).not.toBeVisible();
});
test('资源监控 - 切换标签页和数据展示', async ({ page }) => {
await navigateTo(page, '资源监控');
// 1. 验证标签页存在
const tabs = ['文件锁', '心跳状态', 'Agent 状态'];
for (const tab of tabs) {
await expect(page.locator(`button:has-text("${tab}")`)).toBeVisible();
}
// 2. 切换标签页
await page.click('button:has-text("心跳状态")');
await page.waitForTimeout(300); // 等待切换动画
await page.click('button:has-text("Agent 状态")');
await page.waitForTimeout(300);
await page.click('button:has-text("文件锁")');
await page.waitForTimeout(300);
// 3. 验证刷新按钮
await expect(page.locator('button:has-text("刷新")')).toBeVisible();
await page.click('button:has-text("刷新")');
await expect(page.locator('text=加载中...')).not.toBeVisible();
});
test('工作流页面 - 工作流操作', async ({ page }) => {
await navigateTo(page, '工作流');
// 1. 验证页面元素
await expect(page.locator('h1:has-text("工作流")')).toBeVisible();
await expect(page.locator('button:has-text("新建工作流")')).toBeVisible();
// 2. 打开新建工作流对话框
await page.click('button:has-text("新建工作流")');
await expect(page.locator('text=新建工作流')).toBeVisible();
// 3. 填写工作流名称
const workflowName = `TestWorkflow-${Date.now()}`;
await page.fill('input[name="name"]', workflowName);
// 4. 关闭对话框
await page.click('button:has-text("取消")');
});
test('系统配置 - 保存设置', async ({ page }) => {
await navigateTo(page, '配置');
// 1. 验证页面元素
await expect(page.locator('h1:has-text("系统配置")')).toBeVisible();
await expect(page.locator('text=API 基础地址')).toBeVisible();
// 2. 修改配置
await page.fill('input[name="apiBaseUrl"]', 'http://localhost:9000');
await page.fill('input[name="heartbeatInterval"]', '15');
// 3. 保存配置
await page.click('button:has-text("保存配置")');
// 4. 验证成功提示
await expect(page.locator('text=配置已保存')).toBeVisible();
// 5. 恢复原始配置
await page.fill('input[name="apiBaseUrl"]', 'http://localhost:8000');
await page.fill('input[name="heartbeatInterval"]', '30');
await page.click('button:has-text("保存配置")');
});
test('响应式布局 - 侧边栏折叠展开', async ({ page }) => {
// 1. 验证侧边栏默认展开
await expect(page.locator('text=仪表盘')).toBeVisible();
await expect(page.locator('text=Agent 管理')).toBeVisible();
// 2. 测试移动端视图(如果支持)
await page.setViewportSize({ width: 375, height: 667 });
await page.waitForTimeout(500);
// 3. 恢复桌面视图
await page.setViewportSize({ width: 1280, height: 720 });
await page.waitForTimeout(500);
// 4. 验证侧边栏仍然可见
await expect(page.locator('text=仪表盘')).toBeVisible();
});
});
test.describe('错误处理和边界情况', () => {
const baseUrl = 'http://localhost:3000';
test('后端连接失败时的错误显示', async ({ page }) => {
// 模拟后端不可用的情况
await page.route('http://localhost:8000/api/**', (route) => {
route.abort('failed');
});
await page.goto(baseUrl);
await waitForPageLoad(page);
// 验证错误提示显示
await expect(page.locator('text=连接后端失败')).toBeVisible();
});
test('404 页面处理', async ({ page }) => {
await page.goto(`${baseUrl}/nonexistent-page`);
await waitForPageLoad(page);
// 应该重定向到首页
await expect(page).toHaveURL(/\/$/);
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
});
test('快速导航切换', async ({ page }) => {
await page.goto(baseUrl);
await waitForPageLoad(page);
// 快速切换多个页面
const pages = ['Agent 管理', '会议管理', '资源监控', '工作流', '配置'];
for (const pageName of pages) {
await navigateTo(page, pageName);
}
// 最后回到仪表盘
await navigateTo(page, '仪表盘');
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
});
});
test.describe('性能和加载测试', () => {
const baseUrl = 'http://localhost:3000';
test('页面加载性能', async ({ page }) => {
const startTime = Date.now();
await page.goto(baseUrl);
await waitForPageLoad(page);
const loadTime = Date.now() - startTime;
// 验证页面加载时间在合理范围内5秒内
expect(loadTime).toBeLessThan(5000);
// 验证关键元素渲染
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
});
test('数据刷新功能', async ({ page }) => {
await page.goto(baseUrl);
await waitForPageLoad(page);
// 等待自动刷新间隔10秒
// 这里我们只是验证初始加载成功
await expect(page.locator('text=自动刷新')).toBeVisible();
await expect(page.locator('text=10 秒')).toBeVisible();
});
test('大量数据渲染性能', async ({ page }) => {
// 这个测试需要后端返回大量数据
// 目前只是验证页面结构可以支持列表渲染
await navigateTo(page, 'Agent 管理');
// 验证列表容器存在
await expect(page.locator('[data-testid="agent-list"]').or(page.locator('text=暂无 Agent 数据'))).toBeVisible();
});
});