/** * ZCLAW 前端功能验证测试 * * 验证所有核心功能的完整性和可用性 */ import { test, expect, Page } from '@playwright/test'; // 测试超时配置 test.setTimeout(60000); // 辅助函数:等待组件加载 async function waitForAppReady(page: Page) { await page.waitForLoadState('networkidle'); // 等待主应用容器出现 await page.waitForSelector('.h-screen', { timeout: 10000 }); } // 辅助函数:截图并保存 async function takeScreenshot(page: Page, name: string) { await page.screenshot({ path: `test-results/screenshots/${name}.png`, fullPage: true }); } test.describe('ZCLAW 前端功能验证', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await waitForAppReady(page); }); test.describe('1. 应用基础渲染', () => { test('应用容器正确渲染', async ({ page }) => { // 检查主容器存在 const appContainer = page.locator('.h-screen'); await expect(appContainer).toBeVisible(); // 检查三栏布局 - sidebar 和 main 都应该存在 const sidebar = page.locator('aside'); const mainContent = page.locator('main'); // 验证 sidebar 和 main 都存在 await expect(sidebar.first()).toBeVisible(); await expect(mainContent).toBeVisible(); await takeScreenshot(page, '01-app-layout'); }); test('页面标题正确', async ({ page }) => { await expect(page).toHaveTitle(/ZCLAW/); }); }); test.describe('2. Sidebar 侧边栏导航', () => { test('侧边栏可见并包含导航项', async ({ page }) => { // 验证侧边栏存在 const sidebar = page.locator('aside').first(); await expect(sidebar).toBeVisible(); // 检查导航按钮存在 - 使用 role="tab" 匹配 const cloneBtn = page.getByRole('tab', { name: '分身' }); const handsBtn = page.getByRole('tab', { name: 'Hands' }); const workflowBtn = page.getByRole('tab', { name: '工作流' }); const teamBtn = page.getByRole('tab', { name: '团队' }); const swarmBtn = page.getByRole('tab', { name: '协作' }); // 验证所有导航标签都存在 await expect(cloneBtn).toBeVisible(); await expect(handsBtn).toBeVisible(); await expect(workflowBtn).toBeVisible(); await expect(teamBtn).toBeVisible(); await expect(swarmBtn).toBeVisible(); await takeScreenshot(page, '02-sidebar-navigation'); }); test('导航切换功能', async ({ page }) => { // 尝试点击不同的导航项 const navButtons = page.locator('button').filter({ has: page.locator('svg') }); const count = await navButtons.count(); if (count > 1) { await navButtons.nth(1).click(); await page.waitForTimeout(500); // 验证视图切换 await takeScreenshot(page, '03-navigation-switch'); } }); test('设置按钮可用', async ({ page }) => { const settingsBtn = page.getByRole('button', { name: /settings|设置|⚙/i }).or( page.locator('button').filter({ hasText: /设置|Settings/ }) ); if (await settingsBtn.isVisible()) { await settingsBtn.click(); await page.waitForTimeout(300); await takeScreenshot(page, '04-settings-access'); } }); }); test.describe('3. ChatArea 聊天功能', () => { test('聊天区域渲染', async ({ page }) => { // 查找聊天输入框 const chatInput = page.locator('textarea').or( page.locator('input[type="text"]') ).or( page.locator('[contenteditable="true"]') ); // 检查消息区域 const messageArea = page.locator('[class*="flex-1"]').filter({ has: page.locator('[class*="message"], [class*="chat"]') }); await takeScreenshot(page, '05-chat-area'); // 记录聊天组件状态 const inputExists = await chatInput.count() > 0; console.log(`Chat input found: ${inputExists}`); }); test('消息发送功能', async ({ page }) => { const chatInput = page.locator('textarea').first(); if (await chatInput.isVisible()) { await chatInput.fill('测试消息'); const sendBtn = page.getByRole('button', { name: '发送消息' }); if (await sendBtn.isVisible()) { await sendBtn.click(); await page.waitForTimeout(500); } else { // 可能支持回车发送 await chatInput.press('Enter'); } await takeScreenshot(page, '06-message-send'); } }); test('会话列表渲染', async ({ page }) => { const conversationList = page.locator('[class*="conversation"]').or( page.locator('[class*="session"]') ).or( page.locator('ul, ol').filter({ has: page.locator('li') }) ); await takeScreenshot(page, '07-conversation-list'); }); }); test.describe('4. Hands 系统UI', () => { test.beforeEach(async ({ page }) => { // 导航到 Hands 视图 const handsBtn = page.getByRole('button', { name: 'Hands' }); if (await handsBtn.isVisible()) { await handsBtn.click(); await page.waitForTimeout(1000); // 等待数据加载 } }); test('Hands 列表渲染', async ({ page }) => { const handsList = page.locator('[class*="hand"]').or( page.locator('[class*="capability"]') ); await takeScreenshot(page, '08-hands-list'); // 检查是否有 Hand 卡片 const handCards = page.locator('[class*="card"]').filter({ hasText: /Clip|Lead|Collector|Predictor|Researcher|Twitter|Browser/i }); const cardCount = await handCards.count(); console.log(`Found ${cardCount} hand cards`); }); test('Hand 触发按钮', async ({ page }) => { const triggerBtn = page.getByRole('button', { name: /trigger|触发|执行|run/i }); if (await triggerBtn.first().isVisible()) { await takeScreenshot(page, '09-hand-trigger'); } }); }); test.describe('5. Workflow/Scheduler 面板', () => { test.beforeEach(async ({ page }) => { const workflowBtn = page.getByRole('button', { name: '工作流' }); if (await workflowBtn.isVisible()) { await workflowBtn.click(); await page.waitForTimeout(500); } }); test('Scheduler 面板渲染', async ({ page }) => { const schedulerPanel = page.locator('[class*="scheduler"]').or( page.locator('[class*="workflow"]') ); await takeScreenshot(page, '10-scheduler-panel'); // 检查定时任务列表 const taskList = page.locator('table, ul, [class*="list"]'); const hasTaskList = await taskList.count() > 0; console.log(`Task list found: ${hasTaskList}`); }); test('工作流编辑器', async ({ page }) => { const workflowEditor = page.locator('[class*="editor"]').or( page.locator('[class*="workflow-editor"]') ); if (await workflowEditor.isVisible()) { await takeScreenshot(page, '11-workflow-editor'); } }); }); test.describe('6. Team 协作视图', () => { test.beforeEach(async ({ page }) => { const teamBtn = page.getByRole('button', { name: '团队' }); if (await teamBtn.isVisible()) { await teamBtn.click(); await page.waitForTimeout(500); } }); test('Team 列表和创建', async ({ page }) => { const teamList = page.locator('[class*="team"]').or( page.locator('[class*="group"]') ); const createBtn = page.getByRole('button', { name: /create|创建|new|新建|\+/i }); await takeScreenshot(page, '12-team-view'); if (await createBtn.first().isVisible()) { console.log('Team create button available'); } }); test('团队成员显示', async ({ page }) => { const members = page.locator('[class*="member"]').or( page.locator('[class*="agent"]') ); const memberCount = await members.count(); console.log(`Found ${memberCount} team members`); }); }); test.describe('7. Swarm Dashboard', () => { test.beforeEach(async ({ page }) => { const swarmBtn = page.getByRole('button', { name: '协作' }); if (await swarmBtn.isVisible()) { await swarmBtn.click(); await page.waitForTimeout(500); } }); test('Swarm 仪表板渲染', async ({ page }) => { const dashboard = page.locator('[class*="swarm"]').or( page.locator('[class*="dashboard"]') ); await takeScreenshot(page, '13-swarm-dashboard'); // 检查状态指示器 const statusIndicators = page.locator('[class*="status"]'); const statusCount = await statusIndicators.count(); console.log(`Found ${statusCount} status indicators`); }); }); test.describe('8. Settings 设置页面', () => { test.beforeEach(async ({ page }) => { const settingsBtn = page.getByRole('button', { name: /settings|设置|⚙/i }); if (await settingsBtn.isVisible()) { await settingsBtn.click(); await page.waitForTimeout(500); } }); test('设置页面渲染', async ({ page }) => { const settingsLayout = page.locator('[class*="settings"]').or( page.locator('form') ); await takeScreenshot(page, '14-settings-page'); // 检查设置分类 const settingsTabs = page.locator('[role="tab"]').or( page.locator('button').filter({ hasText: /General|通用|Security|安全|Model|模型/i }) ); const tabCount = await settingsTabs.count(); console.log(`Found ${tabCount} settings tabs`); }); test('通用设置', async ({ page }) => { const generalSettings = page.locator('[class*="general"]').or( page.getByText(/general|通用设置/i) ); if (await generalSettings.isVisible()) { await takeScreenshot(page, '15-general-settings'); } }); test('模型配置', async ({ page }) => { // 检查设置页面是否有模型相关内容 const modelSection = page.getByRole('button', { name: /模型|Model/i }).or( page.locator('text=/模型|Model/i') ); // 这个测试是可选的,因为模型配置可能在不同的标签页 const isVisible = await modelSection.first().isVisible().catch(() => false); if (isVisible) { await takeScreenshot(page, '16-model-settings'); } else { // 如果没有找到模型配置,跳过测试 console.log('Model settings section not found - may be in a different tab'); } }); }); test.describe('9. RightPanel 右侧面板', () => { test('右侧面板渲染', async ({ page }) => { // 查找右侧面板 const rightPanel = page.locator('[class*="w-"][class*="border-l"]').or( page.locator('aside').last() ); if (await rightPanel.isVisible()) { await takeScreenshot(page, '17-right-panel'); // 检查面板内容 const panelContent = rightPanel.locator('[class*="info"], [class*="detail"], [class*="context"]'); console.log(`Right panel content found: ${await panelContent.count() > 0}`); } }); }); test.describe('10. 错误处理和边界情况', () => { test('网络错误处理', async ({ page }) => { // 模拟离线 await page.context().setOffline(true); await page.waitForTimeout(1000); // 检查错误提示 const errorMessage = page.locator('[class*="error"]').or( page.locator('[role="alert"]') ); await takeScreenshot(page, '18-offline-error'); // 恢复网络 await page.context().setOffline(false); }); test('空状态显示', async ({ page }) => { // 检查空状态组件 const emptyState = page.locator('[class*="empty"]').or( page.locator('[class*="no-data"]') ); if (await emptyState.isVisible()) { await takeScreenshot(page, '19-empty-state'); } }); }); test.describe('11. 响应式布局', () => { test('移动端布局', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.waitForTimeout(500); await takeScreenshot(page, '20-mobile-layout'); // 检查移动端导航 const mobileMenu = page.locator('[class*="mobile"]').or( page.locator('button[aria-label*="menu"]') ); console.log(`Mobile menu found: ${await mobileMenu.count() > 0}`); }); test('平板布局', async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); await page.waitForTimeout(500); await takeScreenshot(page, '21-tablet-layout'); }); test('桌面布局', async ({ page }) => { await page.setViewportSize({ width: 1920, height: 1080 }); await page.waitForTimeout(500); await takeScreenshot(page, '22-desktop-layout'); }); }); test.describe('12. 性能检查', () => { test('页面加载性能', async ({ page }) => { const startTime = Date.now(); await page.goto('/'); await waitForAppReady(page); const loadTime = Date.now() - startTime; console.log(`Page load time: ${loadTime}ms`); // 页面加载时间应该小于 5 秒 expect(loadTime).toBeLessThan(5000); }); test('内存使用检查', async ({ page }) => { // 获取页面指标 const metrics = await page.evaluate(() => { return { memory: (performance as any).memory?.usedJSHeapSize || 0, domNodes: document.querySelectorAll('*').length, }; }); console.log(`DOM nodes: ${metrics.domNodes}`); console.log(`Memory used: ${Math.round(metrics.memory / 1024 / 1024)}MB`); }); }); }); test.describe('13. 控制台错误检查', () => { test('无 JavaScript 错误', async ({ page }) => { const errors: string[] = []; page.on('pageerror', error => { errors.push(error.message); }); await page.goto('/'); await waitForAppReady(page); // 执行一些交互 await page.click('body'); await page.waitForTimeout(1000); // 检查是否有严重错误 const criticalErrors = errors.filter(e => !e.includes('Warning:') && !e.includes('DevTools') && !e.includes('extension') ); console.log(`Console errors: ${criticalErrors.length}`); criticalErrors.forEach(e => console.log(` - ${e}`)); // 允许少量非严重错误 expect(criticalErrors.length).toBeLessThan(5); }); test('无网络请求失败', async ({ page }) => { const failedRequests: string[] = []; page.on('requestfailed', request => { failedRequests.push(request.url()); }); await page.goto('/'); await waitForAppReady(page); await page.waitForTimeout(2000); console.log(`Failed requests: ${failedRequests.length}`); failedRequests.forEach(r => console.log(` - ${r}`)); }); });