Files
zclaw_openfang/desktop/tests/e2e/specs/functional-scenarios.spec.ts
iven 74dbf42644 refactor(startup): simplify stack to Tauri-managed OpenFang + optional ChromeDriver
- Remove OpenFang CLI dependency from startup scripts
- OpenFang now bundled with Tauri and managed via gateway_start/gateway_status commands
- Add bootstrap screen in App.tsx to auto-start local gateway before UI loads
- Update Makefile: replace start-no-gateway with start-desktop-only
- Fix gateway config endpoints: use /api/config instead of /api/config/quick
- Add Playwright dependencies for future E2E testing
2026-03-17 14:08:03 +08:00

1115 lines
33 KiB
TypeScript

/**
* ZCLAW 前端功能深度验证测试
*
* 模拟真实用户使用场景,测试功能完整性和可用性
*/
import { test, expect, Page, BrowserContext } from '@playwright/test';
// 测试超时配置
test.setTimeout(120000);
// 测试配置
const BASE_URL = 'http://localhost:1420';
const SCREENSHOT_DIR = 'test-results/screenshots';
// 辅助函数
async function waitForAppReady(page: Page) {
await page.waitForLoadState('networkidle');
await page.waitForSelector('aside', { timeout: 15000 });
await page.waitForTimeout(1000); // 等待状态初始化
}
async function takeScreenshot(page: Page, name: string) {
try {
await page.screenshot({
path: `${SCREENSHOT_DIR}/${name}.png`,
fullPage: true
});
} catch (e) {
console.log(`Screenshot failed: ${name}`);
}
}
async function navigateToTab(page: Page, tabName: string) {
// Map Chinese names to aria-labels
const tabLabels: Record<string, string> = {
'分身': '分身',
'Hands': 'Hands',
'工作流': '工作流',
'团队': '团队',
'协作': '协作',
};
const label = tabLabels[tabName] || tabName;
const tabButton = page.getByRole('button', { name: label });
if (await tabButton.isVisible()) {
await tabButton.click();
await page.waitForTimeout(500);
}
}
// 获取控制台日志
function captureConsoleLogs(page: Page) {
const logs: { type: string; message: string }[] = [];
page.on('console', msg => {
logs.push({ type: msg.type(), message: msg.text() });
});
page.on('pageerror', error => {
logs.push({ type: 'error', message: error.message });
});
return logs;
}
// ============================================
// 测试套件 1: 应用启动与初始化
// ============================================
test.describe('1. 应用启动与初始化', () => {
test('1.1 应用正常启动并渲染所有核心组件', async ({ page }) => {
const logs = captureConsoleLogs(page);
await page.goto(BASE_URL);
await waitForAppReady(page);
// 验证核心布局组件
const sidebar = page.locator('aside');
const main = page.locator('main');
await expect(sidebar).toBeVisible();
await expect(main).toBeVisible();
// 验证侧边栏标签
const tabs = ['分身', 'Hands', '工作流', '团队', '协作'];
for (const tab of tabs) {
const tabBtn = page.getByRole('button', { name: new RegExp(tab, 'i') });
await expect(tabBtn).toBeVisible();
}
await takeScreenshot(page, '01-app-initialized');
// 检查关键错误
const criticalErrors = logs.filter(l =>
l.type === 'error' &&
!l.message.includes('DevTools') &&
!l.message.includes('extension') &&
!l.message.includes('Warning:')
);
console.log(`Critical errors during startup: ${criticalErrors.length}`);
expect(criticalErrors.length).toBeLessThan(3);
});
test('1.2 Zustand 状态持久化正常加载', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
// 检查 localStorage 中的持久化状态
const chatStorage = await page.evaluate(() => {
return localStorage.getItem('zclaw-chat-storage');
});
const gatewayStorage = await page.evaluate(() => {
return localStorage.getItem('zclaw-gateway-storage');
});
console.log('Chat storage exists:', !!chatStorage);
console.log('Gateway storage exists:', !!gatewayStorage);
// 验证状态可以被解析
if (chatStorage) {
const parsed = JSON.parse(chatStorage);
expect(parsed).toHaveProperty('state');
console.log('Chat state keys:', Object.keys(parsed.state || {}));
}
});
test('1.3 Gateway 连接状态检查', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
// 等待连接尝试
await page.waitForTimeout(3000);
// 检查连接状态指示器(如果有)
const connectionStatus = page.locator('[class*="connection"]').or(
page.locator('[class*="status"]').filter({ hasText: /connected|disconnected|connecting/i })
);
// 获取控制台日志检查连接状态
const connectionLogs: string[] = [];
page.on('console', msg => {
if (msg.text().toLowerCase().includes('connect') ||
msg.text().toLowerCase().includes('gateway')) {
connectionLogs.push(msg.text());
}
});
await page.waitForTimeout(2000);
console.log('Connection logs:', connectionLogs.slice(-5));
await takeScreenshot(page, '02-connection-state');
});
});
// ============================================
// 测试套件 2: 聊天功能 (核心场景)
// ============================================
test.describe('2. 聊天功能', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
});
test('2.1 聊天输入框功能', async ({ page }) => {
// 查找聊天输入框
const chatInput = page.locator('textarea').or(
page.locator('[contenteditable="true"]')
).first();
if (await chatInput.isVisible()) {
// 测试输入
await chatInput.click();
await chatInput.fill('这是一条测试消息');
// 验证输入内容
const value = await chatInput.inputValue();
expect(value).toContain('测试消息');
await takeScreenshot(page, '03-chat-input');
} else {
console.log('Chat input not found - may need to navigate to chat view');
}
});
test('2.2 发送消息并检查响应', async ({ page }) => {
const chatInput = page.locator('textarea').first();
if (await chatInput.isVisible()) {
await chatInput.fill('你好,请介绍一下你自己');
// 发送消息 - 使用更精确的选择器
const sendBtn = page.getByRole('button', { name: '发送消息' });
await sendBtn.click();
// 等待响应
await page.waitForTimeout(5000);
// 检查用户消息是否显示
const userMessage = page.locator('[class*="message"]').filter({ hasText: '你好' });
const hasUserMessage = await userMessage.count() > 0;
// 检查是否有响应(可能是错误或实际响应)
const messages = page.locator('[class*="message"], [class*="assistant"]');
const messageCount = await messages.count();
console.log(`Messages found: ${messageCount}`);
console.log(`User message visible: ${hasUserMessage}`);
await takeScreenshot(page, '04-chat-response');
}
});
test('2.3 会话切换功能', async ({ page }) => {
// 导航到分身标签查看会话列表
await navigateToTab(page, '分身');
const conversationItems = page.locator('[class*="conversation"]').or(
page.locator('li').filter({ has: page.locator('[class*="message"]') })
);
const count = await conversationItems.count();
console.log(`Conversation items found: ${count}`);
if (count > 1) {
// 点击第二个会话
await conversationItems.nth(1).click();
await page.waitForTimeout(500);
await takeScreenshot(page, '05-conversation-switch');
}
});
test('2.4 新建会话功能', async ({ page }) => {
// 查找新建会话按钮
const newChatBtn = page.getByRole('button', { name: /新|new|create|\+/i }).first();
if (await newChatBtn.isVisible()) {
await newChatBtn.click();
await page.waitForTimeout(500);
// 验证消息列表已清空
const messages = page.locator('[class*="message"]');
const count = await messages.count();
console.log(`Messages after new chat: ${count}`);
await takeScreenshot(page, '06-new-conversation');
}
});
test('2.5 消息流式显示', async ({ page }) => {
const chatInput = page.locator('textarea').first();
if (await chatInput.isVisible()) {
await chatInput.fill('请写一首短诗');
await page.getByRole('button', { name: '发送消息' }).click();
// 检查是否有 streaming 状态
await page.waitForTimeout(1000);
const streamingIndicator = page.locator('[class*="streaming"]').or(
page.locator('[class*="loading"]')
);
const isStreaming = await streamingIndicator.count() > 0;
console.log(`Streaming indicator visible: ${isStreaming}`);
// 等待完成
await page.waitForTimeout(5000);
await takeScreenshot(page, '07-streaming-response');
}
});
test('2.6 错误处理 - 网络断开', async ({ page, context }) => {
const chatInput = page.locator('textarea').first();
if (await chatInput.isVisible()) {
// 模拟离线
await context.setOffline(true);
await chatInput.fill('离线测试消息');
await page.getByRole('button', { name: '发送消息' }).click();
await page.waitForTimeout(3000);
// 检查错误提示
const errorMsg = page.locator('[class*="error"]').or(
page.locator('[role="alert"]')
).or(
page.locator('text=/无法连接|网络|错误|failed|error/i')
);
const hasError = await errorMsg.count() > 0;
console.log(`Error message shown: ${hasError}`);
await takeScreenshot(page, '08-offline-error');
// 恢复网络
await context.setOffline(false);
}
});
});
// ============================================
// 测试套件 3: Agent/分身管理
// ============================================
test.describe('3. Agent/分身管理', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
await navigateToTab(page, '分身');
});
test('3.1 分身列表显示', async ({ page }) => {
await page.waitForTimeout(1000);
// 检查分身列表
const cloneItems = page.locator('[class*="clone"]').or(
page.locator('[class*="agent"]')
).or(
page.locator('li').filter({ hasText: /分身|agent|ZCLAW/i })
);
const count = await cloneItems.count();
console.log(`Clone/Agent items found: ${count}`);
// 至少应该有默认 Agent
expect(count).toBeGreaterThanOrEqual(1);
await takeScreenshot(page, '09-clone-list');
});
test('3.2 创建新分身', async ({ page }) => {
// 查找创建按钮
const createBtn = page.getByRole('button', { name: /创建|new|添加|\+/i }).first();
if (await createBtn.isVisible()) {
await createBtn.click();
await page.waitForTimeout(500);
// 检查创建表单/对话框
const createForm = page.locator('[role="dialog"]').or(
page.locator('[class*="modal"]')
).or(
page.locator('form')
);
if (await createForm.isVisible()) {
// 填写分身信息
const nameInput = createForm.locator('input').first();
if (await nameInput.isVisible()) {
await nameInput.fill('测试分身');
}
await takeScreenshot(page, '10-create-clone-form');
}
}
});
test('3.3 切换分身', async ({ page }) => {
const cloneItems = page.locator('[class*="clone"]').or(
page.locator('li').filter({ hasText: /分身|agent/i })
);
const count = await cloneItems.count();
if (count > 1) {
await cloneItems.nth(1).click();
await page.waitForTimeout(500);
// 验证切换后的状态
const activeIndicator = page.locator('[class*="active"]').or(
page.locator('[class*="selected"]')
);
console.log(`Active indicator visible: ${await activeIndicator.count() > 0}`);
await takeScreenshot(page, '11-clone-switch');
}
});
test('3.4 分身设置修改', async ({ page }) => {
const cloneItems = page.locator('[class*="clone"]').or(
page.locator('li').filter({ hasText: /分身|agent/i })
);
if (await cloneItems.first().isVisible()) {
// 查找设置/编辑按钮
const settingsBtn = cloneItems.first().locator('button').filter({
has: page.locator('svg')
});
if (await settingsBtn.isVisible()) {
await settingsBtn.click();
await page.waitForTimeout(500);
await takeScreenshot(page, '12-clone-settings');
}
}
});
});
// ============================================
// 测试套件 4: Hands 系统
// ============================================
test.describe('4. Hands 系统', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
await navigateToTab(page, 'Hands');
await page.waitForTimeout(1000);
});
test('4.1 Hands 列表显示', async ({ page }) => {
// 检查 Hand 卡片
const handCards = page.locator('[class*="hand"]').or(
page.locator('[class*="card"]')
).filter({
hasText: /Clip|Lead|Collector|Predictor|Researcher|Twitter|Browser|能力|自主/i
});
const count = await handCards.count();
console.log(`Hand cards found: ${count}`);
// OpenFang 应该有 7 个 Hands
expect(count).toBeGreaterThanOrEqual(1);
await takeScreenshot(page, '13-hands-list');
});
test('4.2 Hand 触发功能', async ({ page }) => {
// 查找触发按钮
const triggerBtn = page.getByRole('button', { name: /触发|trigger|执行|run|start/i }).first();
if (await triggerBtn.isVisible()) {
await triggerBtn.click();
await page.waitForTimeout(1000);
// 检查触发后的状态变化
const statusIndicator = page.locator('[class*="status"]').or(
page.locator('[class*="running"]').or(
page.locator('[class*="progress"]')
)
);
console.log(`Status indicator after trigger: ${await statusIndicator.count() > 0}`);
await takeScreenshot(page, '14-hand-triggered');
}
});
test('4.3 Hand 审批流程', async ({ page }) => {
// 查找需要审批的 Hand
const approvalBtn = page.getByRole('button', { name: /审批|approve|确认/i });
if (await approvalBtn.isVisible()) {
await approvalBtn.click();
await page.waitForTimeout(500);
// 检查审批对话框
const approvalDialog = page.locator('[role="dialog"]').or(
page.locator('[class*="modal"]')
);
if (await approvalDialog.isVisible()) {
// 批准/拒绝按钮
const confirmBtn = page.getByRole('button', { name: /批准|confirm|yes/i });
const rejectBtn = page.getByRole('button', { name: /拒绝|reject|no/i });
console.log(`Approval dialog buttons visible: ${await confirmBtn.isVisible() && await rejectBtn.isVisible()}`);
await takeScreenshot(page, '15-hand-approval');
}
}
});
test('4.4 Hand 任务历史', async ({ page }) => {
// 查找历史/日志按钮
const historyBtn = page.getByRole('button', { name: /历史|history|日志|log/i });
if (await historyBtn.isVisible()) {
await historyBtn.click();
await page.waitForTimeout(500);
await takeScreenshot(page, '16-hand-history');
}
});
});
// ============================================
// 测试套件 5: 工作流管理
// ============================================
test.describe('5. 工作流管理', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
await navigateToTab(page, '工作流');
await page.waitForTimeout(1000);
});
test('5.1 工作流列表显示', async ({ page }) => {
const workflowItems = page.locator('[class*="workflow"]').or(
page.locator('[class*="scheduler"]')
);
console.log(`Workflow items found: ${await workflowItems.count()}`);
await takeScreenshot(page, '17-workflow-list');
});
test('5.2 创建工作流', async ({ page }) => {
const createBtn = page.getByRole('button', { name: /创建|new|添加|\+/i }).first();
if (await createBtn.isVisible()) {
await createBtn.click();
await page.waitForTimeout(500);
// 检查工作流编辑器
const editor = page.locator('[class*="editor"]').or(
page.locator('form')
);
console.log(`Workflow editor visible: ${await editor.isVisible()}`);
await takeScreenshot(page, '18-workflow-create');
}
});
test('5.3 工作流执行状态', async ({ page }) => {
// 查找运行中的工作流
const runningWorkflow = page.locator('[class*="running"]').or(
page.locator('[class*="active"]').or(
page.locator('[class*="executing"]')
)
);
if (await runningWorkflow.isVisible()) {
await takeScreenshot(page, '19-workflow-running');
}
});
test('5.4 定时任务配置', async ({ page }) => {
const scheduleBtn = page.getByRole('button', { name: /定时|schedule|cron/i });
if (await scheduleBtn.isVisible()) {
await scheduleBtn.click();
await page.waitForTimeout(500);
await takeScreenshot(page, '20-scheduler-config');
}
});
});
// ============================================
// 测试套件 6: 团队协作
// ============================================
test.describe('6. 团队协作', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
await navigateToTab(page, '团队');
await page.waitForTimeout(1000);
});
test('6.1 团队列表显示', async ({ page }) => {
const teamItems = page.locator('[class*="team"]').or(
page.locator('li').filter({ hasText: /团队|team/i })
);
const count = await teamItems.count();
console.log(`Team items found: ${count}`);
await takeScreenshot(page, '21-team-list');
});
test('6.2 创建团队', async ({ page }) => {
const createBtn = page.getByRole('button', { name: /创建|new|\+/i }).first();
if (await createBtn.isVisible()) {
await createBtn.click();
await page.waitForTimeout(500);
// 填写团队信息
const nameInput = page.locator('input').first();
if (await nameInput.isVisible()) {
await nameInput.fill('测试团队');
}
await takeScreenshot(page, '22-team-create');
}
});
test('6.3 团队成员管理', async ({ page }) => {
const teamItems = page.locator('[class*="team"]');
if (await teamItems.first().isVisible()) {
await teamItems.first().click();
await page.waitForTimeout(500);
// 检查成员列表
const members = page.locator('[class*="member"]').or(
page.locator('[class*="agent"]')
);
console.log(`Team members found: ${await members.count()}`);
await takeScreenshot(page, '23-team-members');
}
});
});
// ============================================
// 测试套件 7: Swarm 协作
// ============================================
test.describe('7. Swarm 协作', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
await navigateToTab(page, '协作');
await page.waitForTimeout(1000);
});
test('7.1 Swarm 仪表板显示', async ({ page }) => {
const dashboard = page.locator('[class*="swarm"]').or(
page.locator('[class*="dashboard"]')
);
console.log(`Swarm dashboard visible: ${await dashboard.isVisible()}`);
await takeScreenshot(page, '24-swarm-dashboard');
});
test('7.2 创建协作任务', async ({ page }) => {
const createBtn = page.getByRole('button', { name: /创建|new|任务|task|\+/i }).first();
if (await createBtn.isVisible()) {
await createBtn.click();
await page.waitForTimeout(500);
// 填写任务描述
const descInput = page.locator('textarea').or(
page.locator('input[type="text"]')
).first();
if (await descInput.isVisible()) {
await descInput.fill('这是一个测试协作任务');
}
await takeScreenshot(page, '25-swarm-task-create');
}
});
test('7.3 协作模式选择', async ({ page }) => {
const modeSelector = page.locator('[class*="mode"]').or(
page.locator('select').or(
page.locator('[role="listbox"]')
)
);
if (await modeSelector.isVisible()) {
// 检查是否有并行/串行/辩论模式
const parallelOption = page.getByText(/parallel|并行/i);
const sequentialOption = page.getByText(/sequential|串行/i);
const debateOption = page.getByText(/debate|辩论/i);
console.log(`Mode options available:
parallel: ${await parallelOption.isVisible()},
sequential: ${await sequentialOption.isVisible()},
debate: ${await debateOption.isVisible()}`);
await takeScreenshot(page, '26-swarm-modes');
}
});
});
// ============================================
// 测试套件 8: 设置页面
// ============================================
test.describe('8. 设置页面', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
});
test('8.1 打开设置页面', async ({ page }) => {
const settingsBtn = page.getByRole('button', { name: /设置|settings|⚙/i });
if (await settingsBtn.isVisible()) {
await settingsBtn.click();
await page.waitForTimeout(500);
// 验证设置页面
const settingsLayout = page.locator('[class*="settings"]').or(
page.locator('form').or(
page.locator('[role="tabpanel"]')
)
);
console.log(`Settings layout visible: ${await settingsLayout.isVisible()}`);
await takeScreenshot(page, '27-settings-page');
}
});
test('8.2 通用设置', async ({ page }) => {
const settingsBtn = page.getByRole('button', { name: /设置|settings|⚙/i });
if (await settingsBtn.isVisible()) {
await settingsBtn.click();
await page.waitForTimeout(500);
// 检查用户名设置
const usernameInput = page.locator('input').filter({ hasText: '' }).first();
// 检查主题设置
const themeSelector = page.locator('[class*="theme"]').or(
page.locator('select').first()
);
console.log(`Username input visible: ${await usernameInput.isVisible()}`);
console.log(`Theme selector visible: ${await themeSelector.isVisible()}`);
await takeScreenshot(page, '28-general-settings');
}
});
test('8.3 模型配置', async ({ page }) => {
const settingsBtn = page.getByRole('button', { name: /设置|settings|⚙/i });
if (await settingsBtn.isVisible()) {
await settingsBtn.click();
await page.waitForTimeout(500);
// 查找模型选择器
const modelSelector = page.locator('[class*="model"]').or(
page.getByText(/模型|model/i)
);
if (await modelSelector.isVisible()) {
await modelSelector.click();
await page.waitForTimeout(300);
// 检查可用模型列表
const modelOptions = page.locator('[role="option"]').or(
page.locator('li')
);
console.log(`Model options found: ${await modelOptions.count()}`);
await takeScreenshot(page, '29-model-settings');
}
}
});
test('8.4 Gateway 配置', async ({ page }) => {
const settingsBtn = page.getByRole('button', { name: /设置|settings|⚙/i });
if (await settingsBtn.isVisible()) {
await settingsBtn.click();
await page.waitForTimeout(500);
// 查找 Gateway 配置
const gatewaySection = page.locator('[class*="gateway"]').or(
page.getByText(/gateway|服务器|server/i)
);
console.log(`Gateway section visible: ${await gatewaySection.isVisible()}`);
await takeScreenshot(page, '30-gateway-settings');
}
});
test('8.5 保存设置', async ({ page }) => {
const settingsBtn = page.getByRole('button', { name: /设置|settings|⚙/i });
if (await settingsBtn.isVisible()) {
await settingsBtn.click();
await page.waitForTimeout(500);
// 查找保存按钮
const saveBtn = page.getByRole('button', { name: /保存|save|apply/i });
if (await saveBtn.isVisible()) {
await saveBtn.click();
await page.waitForTimeout(500);
// 检查成功提示
const successMsg = page.locator('[class*="success"]').or(
page.locator('[class*="toast"]').or(
page.locator('[role="status"]')
)
);
console.log(`Success message shown: ${await successMsg.isVisible()}`);
await takeScreenshot(page, '31-settings-saved');
}
}
});
});
// ============================================
// 测试套件 9: 右侧面板
// ============================================
test.describe('9. 右侧面板', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
});
test('9.1 右侧面板显示', async ({ page }) => {
const rightPanel = page.locator('[class*="w-"][class*="border-l"]').or(
page.locator('aside').last()
);
if (await rightPanel.isVisible()) {
console.log(`Right panel visible`);
await takeScreenshot(page, '32-right-panel');
}
});
test('9.2 上下文信息显示', async ({ page }) => {
const contextInfo = page.locator('[class*="context"]').or(
page.locator('[class*="info"]')
);
if (await contextInfo.isVisible()) {
console.log(`Context info visible`);
await takeScreenshot(page, '33-context-info');
}
});
test('9.3 记忆面板', async ({ page }) => {
const memoryPanel = page.locator('[class*="memory"]').or(
page.getByText(/记忆|memory/i)
);
if (await memoryPanel.isVisible()) {
console.log(`Memory panel visible`);
await takeScreenshot(page, '34-memory-panel');
}
});
});
// ============================================
// 测试套件 10: 完整用户流程
// ============================================
test.describe('10. 完整用户流程', () => {
test('10.1 新用户首次使用流程', async ({ page }) => {
// 清除所有存储
await page.goto(BASE_URL);
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
await page.reload();
await waitForAppReady(page);
// 检查是否有引导/欢迎界面
const onboarding = page.locator('[class*="onboarding"]').or(
page.locator('[class*="welcome"]').or(
page.locator('[role="dialog"]').filter({ hasText: /欢迎|welcome|开始/i })
)
);
console.log(`Onboarding visible: ${await onboarding.isVisible()}`);
await takeScreenshot(page, '35-first-time-user');
});
test('10.2 完整聊天流程', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
const chatInput = page.locator('textarea').first();
if (await chatInput.isVisible()) {
// 发送多条消息
const messages = [
'你好',
'请帮我写一个简单的函数',
'谢谢'
];
for (const msg of messages) {
await chatInput.fill(msg);
await page.getByRole('button', { name: '发送消息' }).click();
await page.waitForTimeout(2000);
}
// 检查消息数量
const messageElements = page.locator('[class*="message"]');
const count = await messageElements.count();
console.log(`Total messages: ${count}`);
await takeScreenshot(page, '36-full-chat-flow');
}
});
test('10.3 跨视图切换流程', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
const tabs = ['分身', 'Hands', '工作流', '团队', '协作'];
for (const tab of tabs) {
await navigateToTab(page, tab);
await page.waitForTimeout(1000);
// 验证视图已切换
console.log(`Switched to: ${tab}`);
}
await takeScreenshot(page, '37-view-switching');
});
test('10.4 会话持久化测试', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
// 发送一条消息
const chatInput = page.locator('textarea').first();
if (await chatInput.isVisible()) {
await chatInput.fill('持久化测试消息');
await page.getByRole('button', { name: '发送消息' }).click();
await page.waitForTimeout(2000);
// 刷新页面
await page.reload();
await waitForAppReady(page);
// 检查消息是否恢复
const messages = page.locator('[class*="message"]');
const count = await messages.count();
console.log(`Messages after reload: ${count}`);
await takeScreenshot(page, '38-session-persistence');
}
});
});
// ============================================
// 测试套件 11: 性能与稳定性
// ============================================
test.describe('11. 性能与稳定性', () => {
test('11.1 页面加载性能', async ({ page }) => {
const startTime = Date.now();
await page.goto(BASE_URL);
await waitForAppReady(page);
const loadTime = Date.now() - startTime;
console.log(`Page load time: ${loadTime}ms`);
expect(loadTime).toBeLessThan(10000); // 应该在 10 秒内加载完成
});
test('11.2 内存使用', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
const metrics = await page.evaluate(() => {
return {
domNodes: document.querySelectorAll('*').length,
jsHeapSize: (performance as any).memory?.usedJSHeapSize || 0,
};
});
console.log(`DOM nodes: ${metrics.domNodes}`);
console.log(`JS heap: ${Math.round(metrics.jsHeapSize / 1024 / 1024)}MB`);
// DOM 节点不应该过多
expect(metrics.domNodes).toBeLessThan(5000);
});
test('11.3 快速操作稳定性', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
// 快速切换标签
for (let i = 0; i < 10; i++) {
const tabs = ['分身', 'Hands', '工作流', '团队', '协作'];
const tab = tabs[i % tabs.length];
await navigateToTab(page, tab);
await page.waitForTimeout(100);
}
// 检查是否有错误
const errorElements = page.locator('[class*="error"]');
const errorCount = await errorElements.count();
console.log(`Errors after rapid switching: ${errorCount}`);
await takeScreenshot(page, '39-rapid-switching');
});
test('11.4 长时间运行稳定性', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
// 模拟 5 分钟的使用
const chatInput = page.locator('textarea').first();
for (let i = 0; i < 5; i++) {
if (await chatInput.isVisible()) {
await chatInput.fill(`测试消息 ${i + 1}`);
await page.getByRole('button', { name: '发送消息' }).click();
}
await navigateToTab(page, ['分身', 'Hands', '工作流', '团队', '协作'][i % 5]);
await page.waitForTimeout(1000);
}
// 检查内存和状态
const metrics = await page.evaluate(() => {
return {
domNodes: document.querySelectorAll('*').length,
localStorage: Object.keys(localStorage).length,
};
});
console.log(`After extended use - DOM: ${metrics.domNodes}, localStorage keys: ${metrics.localStorage}`);
await takeScreenshot(page, '40-extended-use');
});
});
// ============================================
// 测试套件 12: 无障碍性
// ============================================
test.describe('12. 无障碍性', () => {
test('12.1 键盘导航', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
// 使用 Tab 键导航
for (let i = 0; i < 10; i++) {
await page.keyboard.press('Tab');
await page.waitForTimeout(100);
}
// 检查焦点元素
const focusedElement = page.locator(':focus');
console.log(`Focused element visible: ${await focusedElement.isVisible()}`);
await takeScreenshot(page, '41-keyboard-nav');
});
test('12.2 ARIA 属性', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
// 检查按钮有 accessible name
const buttons = page.locator('button');
const count = await buttons.count();
let buttonsWithoutLabel = 0;
for (let i = 0; i < Math.min(count, 20); i++) {
const btn = buttons.nth(i);
const label = await btn.getAttribute('aria-label');
const title = await btn.getAttribute('title');
const text = await btn.textContent();
if (!label && !title && !text?.trim()) {
buttonsWithoutLabel++;
}
}
console.log(`Buttons without accessible name: ${buttonsWithoutLabel} out of ${Math.min(count, 20)}`);
});
test('12.3 焦点管理', async ({ page }) => {
await page.goto(BASE_URL);
await waitForAppReady(page);
// 打开设置
const settingsBtn = page.getByRole('button', { name: /设置|settings|⚙/i });
if (await settingsBtn.isVisible()) {
await settingsBtn.click();
await page.waitForTimeout(500);
// 按 Escape 关闭
await page.keyboard.press('Escape');
await page.waitForTimeout(500);
// 检查焦点是否返回
const focusedElement = page.locator(':focus');
console.log(`Focus returned after Escape: ${await focusedElement.isVisible()}`);
}
});
});
// ============================================
// 测试报告生成
// ============================================
test.afterAll(async ({}, testInfo) => {
console.log('\n========================================');
console.log('ZCLAW 前端功能验证测试完成');
console.log('========================================');
console.log(`测试时间: ${new Date().toISOString()}`);
console.log(`截图目录: ${SCREENSHOT_DIR}`);
console.log('========================================\n');
});