- 新增 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>
268 lines
9.4 KiB
TypeScript
268 lines
9.4 KiB
TypeScript
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();
|
||
});
|
||
});
|