- Add test helper library with assertion functions (scripts/lib/test-helpers.sh) - Add gateway integration test script (scripts/tests/gateway-test.sh) - Add configuration validation tool (scripts/validate-config.ts) - Add health-check.ts library with Tauri command wrappers - Add HealthStatusIndicator component to ConnectionStatus.tsx - Add E2E test specs for memory, settings, and team collaboration - Update ZCLAW-DEEP-ANALYSIS.md to reflect actual project state Key improvements: - Store architecture now properly documented as migrated - Tauri backend shown as 85-90% complete - Component integration status clarified Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1488 lines
48 KiB
TypeScript
1488 lines
48 KiB
TypeScript
/**
|
|
* 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');
|
|
});
|