/** * ZCLAW Team Collaboration E2E Tests * * Tests for multi-agent team collaboration features with mocked Gateway responses. * Covers team management, member assignment, task allocation, and Dev<->QA loops. * * Test Categories: * - Team Management: Create, select, delete teams * - Member Management: Add, remove, update member roles * - Task Management: Create, assign, update task status * - Dev<->QA Loop: Developer/Reviewer workflow, approval cycles */ import { test, expect, Page } from '@playwright/test'; import { setupMockGateway, mockResponses } from '../fixtures/mock-gateway'; import { storeInspectors, STORAGE_KEYS } from '../fixtures/store-inspectors'; import { userActions, waitForAppReady, skipOnboarding, navigateToTab } from '../utils/user-actions'; // Test configuration test.setTimeout(120000); const BASE_URL = 'http://localhost:1420'; // Helper to generate unique IDs const generateId = () => `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // ============================================ // Test Suite 1: Team Management Tests // ============================================ test.describe('Team Collaboration - Team Management Tests', () => { test.describe.configure({ mode: 'parallel' }); test.beforeEach(async ({ page }) => { await setupMockGateway(page); await skipOnboarding(page); await page.goto(BASE_URL); await waitForAppReady(page); }); test('TEAM-MGMT-01: Create new team with valid configuration', async ({ page }) => { // Clear existing teams first await page.evaluate(() => { localStorage.removeItem('zclaw-teams'); }); // Navigate to team tab await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Create team via store const teamData = { name: 'E2E Test Team', description: 'A team for E2E testing', pattern: 'sequential' as const, memberAgents: [ { agentId: 'agent-dev-1', role: 'worker' as const }, { agentId: 'agent-qa-1', role: 'reviewer' as const }, ], }; const createResult = await page.evaluate(async (data) => { try { // Access team store directly const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const team = await stores.team.getState().createTeam(data); return { success: true, team }; } return { success: false, error: 'Team store not available' }; } catch (e) { return { success: false, error: String(e) }; } }, teamData); expect(createResult.success).toBe(true); expect(createResult.team).toBeDefined(); expect(createResult.team?.name).toBe(teamData.name); expect(createResult.team?.pattern).toBe(teamData.pattern); expect(createResult.team?.members.length).toBe(2); }); test('TEAM-MGMT-02: Team list loads and displays correctly', async ({ page }) => { // Pre-populate teams await page.evaluate(() => { const teams = [ { id: 'team-1', name: 'Development Team', description: 'Main dev team', members: [], tasks: [], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: 'team-2', name: 'QA Team', description: 'Quality assurance', members: [], tasks: [], pattern: 'parallel', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Verify teams loaded const teamsState = await page.evaluate(() => { const stored = localStorage.getItem('zclaw-teams'); if (!stored) return null; try { return JSON.parse(stored); } catch { return null; } }); expect(teamsState).not.toBeNull(); expect(Array.isArray(teamsState)).toBe(true); expect(teamsState.length).toBe(2); }); test('TEAM-MGMT-03: Select team sets active team', async ({ page }) => { // Pre-populate teams const teamId = generateId(); await page.evaluate((id) => { const teams = [ { id, name: 'Active Test Team', description: 'Team to select', members: [], tasks: [], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, teamId); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Select team via store const selectResult = await page.evaluate(async (id) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const state = stores.team.getState(); const team = state.teams.find((t: any) => t.id === id); if (team) { state.setActiveTeam(team); return { success: true, activeTeam: stores.team.getState().activeTeam }; } } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, teamId); expect(selectResult.success).toBe(true); expect(selectResult.activeTeam?.id).toBe(teamId); }); test('TEAM-MGMT-04: Delete team removes from list', async ({ page }) => { const teamId1 = generateId(); const teamId2 = generateId(); // Pre-populate teams await page.evaluate(({ id1, id2 }) => { const teams = [ { id: id1, name: 'Team to Delete', members: [], tasks: [], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: id2, name: 'Team to Keep', members: [], tasks: [], pattern: 'parallel', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { id1: teamId1, id2: teamId2 }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Delete team via store const deleteResult = await page.evaluate(async (id) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const success = await stores.team.getState().deleteTeam(id); return { success, remainingTeams: stores.team.getState().teams.length }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, teamId1); expect(deleteResult.success).toBe(true); expect(deleteResult.remainingTeams).toBe(1); }); test('TEAM-MGMT-05: Team pattern affects workflow execution', async ({ page }) => { // Create teams with different patterns await page.evaluate(() => { const teams = [ { id: 'team-sequential', name: 'Sequential Team', pattern: 'sequential', members: [], tasks: [], activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: 'team-parallel', name: 'Parallel Team', pattern: 'parallel', members: [], tasks: [], activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: 'team-pipeline', name: 'Pipeline Team', pattern: 'pipeline', members: [], tasks: [], activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Verify patterns const teamsState = await page.evaluate(() => { const stored = localStorage.getItem('zclaw-teams'); return stored ? JSON.parse(stored) : []; }); const patterns = teamsState.map((t: any) => t.pattern); expect(patterns).toContain('sequential'); expect(patterns).toContain('parallel'); expect(patterns).toContain('pipeline'); }); }); // ============================================ // Test Suite 2: Member Management Tests // ============================================ test.describe('Team Collaboration - Member Management Tests', () => { test.describe.configure({ mode: 'parallel' }); test.beforeEach(async ({ page }) => { await setupMockGateway(page); await skipOnboarding(page); await page.goto(BASE_URL); await waitForAppReady(page); }); test('TEAM-MEMBER-01: Add member to team', async ({ page }) => { const teamId = generateId(); // Create team await page.evaluate((id) => { const teams = [ { id, name: 'Member Test Team', members: [], tasks: [], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, teamId); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Add member via store const addResult = await page.evaluate(async ({ teamId, agentId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const member = await stores.team.getState().addMember(teamId, agentId, 'worker'); return { success: true, member }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, agentId: 'agent-test-1' }); expect(addResult.success).toBe(true); expect(addResult.member).toBeDefined(); expect(addResult.member?.role).toBe('worker'); expect(addResult.member?.agentId).toBe('agent-test-1'); }); test('TEAM-MEMBER-02: Remove member from team', async ({ page }) => { const teamId = generateId(); const memberId = generateId(); // Create team with member await page.evaluate(({ teamId, memberId }) => { const teams = [ { id: teamId, name: 'Remove Member Team', members: [ { id: memberId, agentId: 'agent-to-remove', name: 'Agent to Remove', role: 'worker', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [], }, ], tasks: [], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, memberId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Remove member via store const removeResult = await page.evaluate(async ({ teamId, memberId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const success = await stores.team.getState().removeMember(teamId, memberId); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); return { success, memberCount: team?.members.length }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, memberId }); expect(removeResult.success).toBe(true); expect(removeResult.memberCount).toBe(0); }); test('TEAM-MEMBER-03: Update member role', async ({ page }) => { const teamId = generateId(); const memberId = generateId(); // Create team with member await page.evaluate(({ teamId, memberId }) => { const teams = [ { id: teamId, name: 'Role Update Team', members: [ { id: memberId, agentId: 'agent-role-test', name: 'Agent Role Test', role: 'worker', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [], }, ], tasks: [], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, memberId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Update role via store const updateResult = await page.evaluate(async ({ teamId, memberId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const success = await stores.team.getState().updateMemberRole(teamId, memberId, 'reviewer'); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const member = team?.members.find((m: any) => m.id === memberId); return { success, newRole: member?.role }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, memberId }); expect(updateResult.success).toBe(true); expect(updateResult.newRole).toBe('reviewer'); }); test('TEAM-MEMBER-04: Member workload tracking', async ({ page }) => { const teamId = generateId(); const memberId = generateId(); const taskId = generateId(); // Create team with member and task await page.evaluate(({ teamId, memberId, taskId }) => { const teams = [ { id: teamId, name: 'Workload Team', members: [ { id: memberId, agentId: 'agent-workload', name: 'Workload Agent', role: 'worker', skills: [], workload: 50, status: 'working', maxConcurrentTasks: 2, currentTasks: [taskId], }, ], tasks: [ { id: taskId, title: 'Test Task', status: 'in_progress', priority: 'medium', assigneeId: memberId, dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, memberId, taskId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Verify workload const workloadState = await page.evaluate(({ teamId, memberId }) => { const stored = localStorage.getItem('zclaw-teams'); if (!stored) return null; const teams = JSON.parse(stored); const team = teams.find((t: any) => t.id === teamId); const member = team?.members.find((m: any) => m.id === memberId); return { workload: member?.workload, status: member?.status, currentTasksCount: member?.currentTasks?.length, }; }, { teamId, memberId }); expect(workloadState?.workload).toBe(50); expect(workloadState?.status).toBe('working'); expect(workloadState?.currentTasksCount).toBe(1); }); test('TEAM-MEMBER-05: Multiple members with different roles', async ({ page }) => { const teamId = generateId(); // Create team with multiple members await page.evaluate((id) => { const teams = [ { id, name: 'Multi-Member Team', members: [ { id: 'member-orchestrator', agentId: 'agent-orchestrator', name: 'Orchestrator', role: 'orchestrator', skills: ['planning', 'coordination'], workload: 20, status: 'idle', maxConcurrentTasks: 5, currentTasks: [], }, { id: 'member-dev-1', agentId: 'agent-dev-1', name: 'Developer 1', role: 'worker', skills: ['typescript', 'react'], workload: 75, status: 'working', maxConcurrentTasks: 2, currentTasks: ['task-1'], }, { id: 'member-qa-1', agentId: 'agent-qa-1', name: 'QA 1', role: 'reviewer', skills: ['testing', 'review'], workload: 30, status: 'idle', maxConcurrentTasks: 2, currentTasks: [], }, ], tasks: [], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, teamId); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Verify roles distribution const rolesState = await page.evaluate((id) => { const stored = localStorage.getItem('zclaw-teams'); if (!stored) return null; const teams = JSON.parse(stored); const team = teams.find((t: any) => t.id === id); return { memberCount: team?.members.length, roles: team?.members.map((m: any) => m.role), }; }, teamId); expect(rolesState?.memberCount).toBe(3); expect(rolesState?.roles).toContain('orchestrator'); expect(rolesState?.roles).toContain('worker'); expect(rolesState?.roles).toContain('reviewer'); }); }); // ============================================ // Test Suite 3: Task Management Tests // ============================================ test.describe('Team Collaboration - Task Management Tests', () => { test.describe.configure({ mode: 'parallel' }); test.beforeEach(async ({ page }) => { await setupMockGateway(page); await skipOnboarding(page); await page.goto(BASE_URL); await waitForAppReady(page); }); test('TEAM-TASK-01: Create task in team', async ({ page }) => { const teamId = generateId(); // Create team await page.evaluate((id) => { const teams = [ { id, name: 'Task Test Team', members: [], tasks: [], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, teamId); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Add task via store const taskResult = await page.evaluate(async (teamId) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const task = await stores.team.getState().addTask({ teamId, title: 'E2E Test Task', description: 'A task created during E2E testing', priority: 'high', type: 'development', }); return { success: true, task }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, teamId); expect(taskResult.success).toBe(true); expect(taskResult.task).toBeDefined(); expect(taskResult.task?.title).toBe('E2E Test Task'); expect(taskResult.task?.status).toBe('pending'); }); test('TEAM-TASK-02: Assign task to member', async ({ page }) => { const teamId = generateId(); const memberId = generateId(); const taskId = generateId(); // Create team with member and task await page.evaluate(({ teamId, memberId, taskId }) => { const teams = [ { id: teamId, name: 'Assign Task Team', members: [ { id: memberId, agentId: 'agent-assign', name: 'Assign Agent', role: 'worker', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [], }, ], tasks: [ { id: taskId, title: 'Task to Assign', status: 'pending', priority: 'medium', dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, memberId, taskId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Assign task via store const assignResult = await page.evaluate(async ({ teamId, taskId, memberId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const success = await stores.team.getState().assignTask(teamId, taskId, memberId); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const task = team?.tasks.find((t: any) => t.id === taskId); return { success, assigneeId: task?.assigneeId, taskStatus: task?.status }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, taskId, memberId }); expect(assignResult.success).toBe(true); expect(assignResult.assigneeId).toBe(memberId); expect(assignResult.taskStatus).toBe('assigned'); }); test('TEAM-TASK-03: Update task status through workflow', async ({ page }) => { const teamId = generateId(); const taskId = generateId(); // Create team with task await page.evaluate(({ teamId, taskId }) => { const teams = [ { id: teamId, name: 'Status Update Team', members: [], tasks: [ { id: taskId, title: 'Status Update Task', status: 'assigned', priority: 'medium', dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, taskId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Update status to in_progress const progressResult = await page.evaluate(async ({ teamId, taskId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { await stores.team.getState().updateTaskStatus(teamId, taskId, 'in_progress'); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const task = team?.tasks.find((t: any) => t.id === taskId); return { status: task?.status, startedAt: task?.startedAt }; } return { status: null }; } catch (e) { return { error: String(e) }; } }, { teamId, taskId }); expect(progressResult.status).toBe('in_progress'); expect(progressResult.startedAt).toBeDefined(); // Update status to completed const completeResult = await page.evaluate(async ({ teamId, taskId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { await stores.team.getState().updateTaskStatus(teamId, taskId, 'completed'); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const task = team?.tasks.find((t: any) => t.id === taskId); return { status: task?.status, completedAt: task?.completedAt }; } return { status: null }; } catch (e) { return { error: String(e) }; } }, { teamId, taskId }); expect(completeResult.status).toBe('completed'); expect(completeResult.completedAt).toBeDefined(); }); test('TEAM-TASK-04: Submit deliverable for review', async ({ page }) => { const teamId = generateId(); const taskId = generateId(); // Create team with task in progress await page.evaluate(({ teamId, taskId }) => { const teams = [ { id: teamId, name: 'Deliverable Team', members: [], tasks: [ { id: taskId, title: 'Deliverable Task', status: 'in_progress', priority: 'high', dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, taskId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Submit deliverable const deliverableResult = await page.evaluate(async ({ teamId, taskId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const deliverable = { type: 'code', content: 'function test() { return true; }', files: ['src/test.ts'], summary: 'Completed test function', }; const success = await stores.team.getState().submitDeliverable(teamId, taskId, deliverable); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const task = team?.tasks.find((t: any) => t.id === taskId); return { success, taskStatus: task?.status, hasDeliverable: !!task?.deliverable }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, taskId }); expect(deliverableResult.success).toBe(true); expect(deliverableResult.taskStatus).toBe('review'); expect(deliverableResult.hasDeliverable).toBe(true); }); test('TEAM-TASK-05: Task dependencies respected', async ({ page }) => { const teamId = generateId(); const task1Id = generateId(); const task2Id = generateId(); // Create team with dependent tasks await page.evaluate(({ teamId, task1Id, task2Id }) => { const teams = [ { id: teamId, name: 'Dependencies Team', members: [], tasks: [ { id: task1Id, title: 'Base Task', status: 'pending', priority: 'high', dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: task2Id, title: 'Dependent Task', status: 'pending', priority: 'medium', dependencies: [task1Id], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], pattern: 'pipeline', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, task1Id, task2Id }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Verify dependencies const depState = await page.evaluate(({ teamId, task2Id }) => { const stored = localStorage.getItem('zclaw-teams'); if (!stored) return null; const teams = JSON.parse(stored); const team = teams.find((t: any) => t.id === teamId); const task = team?.tasks.find((t: any) => t.id === task2Id); return { hasDependencies: (task?.dependencies?.length ?? 0) > 0, dependencyCount: task?.dependencies?.length, }; }, { teamId, task2Id }); expect(depState?.hasDependencies).toBe(true); expect(depState?.dependencyCount).toBe(1); }); }); // ============================================ // Test Suite 4: Dev<->QA Loop Tests // ============================================ test.describe('Team Collaboration - Dev<->QA Loop Tests', () => { test.describe.configure({ mode: 'serial' }); test.beforeEach(async ({ page }) => { await setupMockGateway(page); await skipOnboarding(page); await page.goto(BASE_URL); await waitForAppReady(page); }); test('TEAM-LOOP-01: Start Dev<->QA loop', async ({ page }) => { const teamId = generateId(); const taskId = generateId(); const developerId = generateId(); const reviewerId = generateId(); // Create team with developer and reviewer await page.evaluate(({ teamId, taskId, developerId, reviewerId }) => { const teams = [ { id: teamId, name: 'Loop Team', members: [ { id: developerId, agentId: 'agent-dev', name: 'Developer', role: 'worker', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [], }, { id: reviewerId, agentId: 'agent-qa', name: 'Reviewer', role: 'reviewer', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [], }, ], tasks: [ { id: taskId, title: 'Loop Test Task', status: 'assigned', priority: 'high', assigneeId: developerId, dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, taskId, developerId, reviewerId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Start loop const loopResult = await page.evaluate(async ({ teamId, taskId, developerId, reviewerId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const loop = await stores.team.getState().startDevQALoop(teamId, taskId, developerId, reviewerId); return { success: true, loop }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, taskId, developerId, reviewerId }); expect(loopResult.success).toBe(true); expect(loopResult.loop).toBeDefined(); expect(loopResult.loop?.state).toBe('developing'); expect(loopResult.loop?.developerId).toBe(developerId); expect(loopResult.loop?.reviewerId).toBe(reviewerId); }); test('TEAM-LOOP-02: Submit review with approval', async ({ page }) => { const teamId = generateId(); const taskId = generateId(); const developerId = generateId(); const reviewerId = generateId(); const loopId = generateId(); // Create team with active loop await page.evaluate(({ teamId, taskId, developerId, reviewerId, loopId }) => { const teams = [ { id: teamId, name: 'Review Team', members: [ { id: developerId, agentId: 'agent-dev', name: 'Developer', role: 'worker', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [] }, { id: reviewerId, agentId: 'agent-qa', name: 'Reviewer', role: 'reviewer', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [] }, ], tasks: [ { id: taskId, title: 'Review Task', status: 'review', priority: 'high', assigneeId: developerId, dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, ], pattern: 'sequential', activeLoops: [ { id: loopId, developerId, reviewerId, taskId, state: 'reviewing', iterationCount: 0, maxIterations: 3, feedbackHistory: [], startedAt: new Date().toISOString(), lastUpdatedAt: new Date().toISOString(), }, ], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, taskId, developerId, reviewerId, loopId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Submit approval const approveResult = await page.evaluate(async ({ teamId, loopId, reviewerId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const success = await stores.team.getState().submitReview(teamId, loopId, { verdict: 'approved', comments: 'Great work!', }); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const loop = team?.activeLoops.find((l: any) => l.id === loopId); return { success, loopState: loop?.state }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, loopId, reviewerId }); expect(approveResult.success).toBe(true); expect(approveResult.loopState).toBe('approved'); }); test('TEAM-LOOP-03: Submit review with revision request', async ({ page }) => { const teamId = generateId(); const taskId = generateId(); const developerId = generateId(); const reviewerId = generateId(); const loopId = generateId(); // Create team with active loop await page.evaluate(({ teamId, taskId, developerId, reviewerId, loopId }) => { const teams = [ { id: teamId, name: 'Revision Team', members: [ { id: developerId, agentId: 'agent-dev', name: 'Developer', role: 'worker', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [] }, { id: reviewerId, agentId: 'agent-qa', name: 'Reviewer', role: 'reviewer', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [] }, ], tasks: [ { id: taskId, title: 'Revision Task', status: 'review', priority: 'high', assigneeId: developerId, dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, ], pattern: 'sequential', activeLoops: [ { id: loopId, developerId, reviewerId, taskId, state: 'reviewing', iterationCount: 0, maxIterations: 3, feedbackHistory: [], startedAt: new Date().toISOString(), lastUpdatedAt: new Date().toISOString(), }, ], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, taskId, developerId, reviewerId, loopId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Request revision const revisionResult = await page.evaluate(async ({ teamId, loopId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const success = await stores.team.getState().submitReview(teamId, loopId, { verdict: 'needs_work', comments: 'Please add more tests', }); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const loop = team?.activeLoops.find((l: any) => l.id === loopId); return { success, loopState: loop?.state, iterationCount: loop?.iterationCount }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, loopId }); expect(revisionResult.success).toBe(true); expect(revisionResult.loopState).toBe('revising'); expect(revisionResult.iterationCount).toBe(1); }); test('TEAM-LOOP-04: Max iterations triggers escalation', async ({ page }) => { const teamId = generateId(); const taskId = generateId(); const developerId = generateId(); const reviewerId = generateId(); const loopId = generateId(); // Create team with loop at max iterations await page.evaluate(({ teamId, taskId, developerId, reviewerId, loopId }) => { const teams = [ { id: teamId, name: 'Escalation Team', members: [ { id: developerId, agentId: 'agent-dev', name: 'Developer', role: 'worker', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [] }, { id: reviewerId, agentId: 'agent-qa', name: 'Reviewer', role: 'reviewer', skills: [], workload: 0, status: 'idle', maxConcurrentTasks: 2, currentTasks: [] }, ], tasks: [ { id: taskId, title: 'Escalation Task', status: 'review', priority: 'high', assigneeId: developerId, dependencies: [], type: 'development', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }, ], pattern: 'sequential', activeLoops: [ { id: loopId, developerId, reviewerId, taskId, state: 'reviewing', iterationCount: 2, // At max - 1 maxIterations: 3, feedbackHistory: [ { verdict: 'needs_work', comments: 'First revision', reviewedAt: new Date().toISOString(), reviewerId }, { verdict: 'needs_work', comments: 'Second revision', reviewedAt: new Date().toISOString(), reviewerId }, ], startedAt: new Date().toISOString(), lastUpdatedAt: new Date().toISOString(), }, ], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, taskId, developerId, reviewerId, loopId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Submit another revision request - should escalate const escalateResult = await page.evaluate(async ({ teamId, loopId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const success = await stores.team.getState().submitReview(teamId, loopId, { verdict: 'needs_work', comments: 'Still needs work', }); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const loop = team?.activeLoops.find((l: any) => l.id === loopId); return { success, loopState: loop?.state }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, loopId }); expect(escalateResult.success).toBe(true); expect(escalateResult.loopState).toBe('escalated'); }); test('TEAM-LOOP-05: Update loop state directly', async ({ page }) => { const teamId = generateId(); const loopId = generateId(); // Create team with developing loop await page.evaluate(({ teamId, loopId }) => { const teams = [ { id: teamId, name: 'State Update Team', members: [], tasks: [], pattern: 'sequential', activeLoops: [ { id: loopId, developerId: 'dev-1', reviewerId: 'rev-1', taskId: 'task-1', state: 'developing', iterationCount: 0, maxIterations: 3, feedbackHistory: [], startedAt: new Date().toISOString(), lastUpdatedAt: new Date().toISOString(), }, ], status: 'active', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, loopId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Update state to reviewing const stateResult = await page.evaluate(async ({ teamId, loopId }) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const success = await stores.team.getState().updateLoopState(teamId, loopId, 'reviewing'); const team = stores.team.getState().teams.find((t: any) => t.id === teamId); const loop = team?.activeLoops.find((l: any) => l.id === loopId); return { success, loopState: loop?.state }; } return { success: false }; } catch (e) { return { success: false, error: String(e) }; } }, { teamId, loopId }); expect(stateResult.success).toBe(true); expect(stateResult.loopState).toBe('reviewing'); }); }); // ============================================ // Test Suite 5: Team Metrics Tests // ============================================ test.describe('Team Collaboration - Metrics Tests', () => { test.describe.configure({ mode: 'parallel' }); test.beforeEach(async ({ page }) => { await setupMockGateway(page); await skipOnboarding(page); await page.goto(BASE_URL); await waitForAppReady(page); }); test('TEAM-METRIC-01: Team metrics calculate correctly', async ({ page }) => { const teamId = generateId(); // Create team with completed tasks await page.evaluate((id) => { const now = new Date(); const oneHourAgo = new Date(now.getTime() - 3600000); const thirtyMinAgo = new Date(now.getTime() - 1800000); const teams = [ { id, name: 'Metrics Team', members: [], tasks: [ { id: 'task-completed-1', title: 'Completed Task 1', status: 'completed', priority: 'high', dependencies: [], type: 'development', startedAt: oneHourAgo.toISOString(), completedAt: thirtyMinAgo.toISOString(), reviewFeedback: { verdict: 'approved' }, createdAt: oneHourAgo.toISOString(), updatedAt: now.toISOString(), }, { id: 'task-completed-2', title: 'Completed Task 2', status: 'completed', priority: 'medium', dependencies: [], type: 'development', startedAt: oneHourAgo.toISOString(), completedAt: now.toISOString(), reviewFeedback: { verdict: 'approved' }, createdAt: oneHourAgo.toISOString(), updatedAt: now.toISOString(), }, { id: 'task-pending', title: 'Pending Task', status: 'pending', priority: 'low', dependencies: [], type: 'development', createdAt: now.toISOString(), updatedAt: now.toISOString(), }, ], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: now.toISOString(), updatedAt: now.toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, teamId); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Get metrics const metricsResult = await page.evaluate(async (teamId) => { try { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const state = stores.team.getState(); const team = state.teams.find((t: any) => t.id === teamId); if (team) { state.setActiveTeam(team); return { metrics: stores.team.getState().metrics }; } } return { metrics: null }; } catch (e) { return { metrics: null, error: String(e) }; } }, teamId); expect(metricsResult.metrics).not.toBeNull(); expect(metricsResult.metrics?.tasksCompleted).toBe(2); expect(metricsResult.metrics?.passRate).toBeGreaterThan(0); expect(metricsResult.metrics?.efficiency).toBeGreaterThan(0); }); test('TEAM-METRIC-02: Metrics update after task completion', async ({ page }) => { const teamId = generateId(); const taskId = generateId(); // Create team with one task await page.evaluate(({ teamId, taskId }) => { const now = new Date(); const teams = [ { id: teamId, name: 'Update Metrics Team', members: [], tasks: [ { id: taskId, title: 'Task to Complete', status: 'in_progress', priority: 'high', dependencies: [], type: 'development', startedAt: now.toISOString(), createdAt: now.toISOString(), updatedAt: now.toISOString(), }, ], pattern: 'sequential', activeLoops: [], status: 'active', createdAt: now.toISOString(), updatedAt: now.toISOString(), }, ]; localStorage.setItem('zclaw-teams', JSON.stringify(teams)); }, { teamId, taskId }); await navigateToTab(page, '团队'); await page.waitForTimeout(500); // Complete the task await page.evaluate(async ({ teamId, taskId }) => { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { await stores.team.getState().updateTaskStatus(teamId, taskId, 'completed'); } }, { teamId, taskId }); // Check metrics const metricsResult = await page.evaluate(async (teamId) => { const stores = (window as any).__ZCLAW_STORES__; if (stores?.team) { const state = stores.team.getState(); const team = state.teams.find((t: any) => t.id === teamId); if (team) { state.setActiveTeam(team); return { metrics: stores.team.getState().metrics }; } } return { metrics: null }; }, teamId); expect(metricsResult.metrics?.tasksCompleted).toBe(1); }); }); // ============================================ // Test Report // ============================================ test.afterAll(async ({}, testInfo) => { console.log('\n========================================'); console.log('ZCLAW Team Collaboration E2E Tests Complete'); console.log('========================================'); console.log(`Test Time: ${new Date().toISOString()}`); console.log('========================================\n'); });