/** * ZCLAW Store 状态验证测试 * * 专注于验证 Zustand Store 的状态管理和转换 * 确保状态正确初始化、更新和持久化 */ import { test, expect, Page } from '@playwright/test'; import { storeInspectors, STORE_NAMES } from '../fixtures/store-inspectors'; import { chatAssertions, connectionAssertions, handAssertions, agentAssertions, storeAssertions, } from '../utils/store-assertions'; import { userActions, waitForAppReady, navigateToTab } from '../utils/user-actions'; import { messageFactory, cloneFactory, handFactory } from '../fixtures/test-data'; // 测试超时配置 test.setTimeout(120000); const BASE_URL = 'http://localhost:1420'; // ============================================ // 测试套件 1: Store 初始化验证 // ============================================ test.describe('Store 初始化验证', () => { test('STORE-INIT-01: Chat Store 初始化', async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); // 验证 Chat Store 存在并初始化 const state = await storeInspectors.getPersistedState<{ messages: unknown[]; isStreaming: boolean; currentModel: string; }>(page, STORE_NAMES.CHAT); expect(state).not.toBeNull(); expect(Array.isArray(state?.messages)).toBe(true); expect(typeof state?.isStreaming).toBe('boolean'); expect(typeof state?.currentModel).toBe('string'); }); test('STORE-INIT-02: Gateway Store 初始化', async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); // 验证 Gateway Store 存在 const state = await storeInspectors.getPersistedState<{ connectionState: string; hands: unknown[]; workflows: unknown[]; clones: unknown[]; }>(page, STORE_NAMES.GATEWAY); expect(state).not.toBeNull(); // 连接状态应该是有效值 const validStates = ['connected', 'disconnected', 'connecting', 'reconnecting', 'handshaking']; expect(validStates).toContain(state?.connectionState); }); test('STORE-INIT-03: Agent Store 初始化', async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); // 验证 Agent Store 存在 const state = await storeInspectors.getPersistedState<{ clones: unknown[]; isLoading: boolean; }>(page, STORE_NAMES.AGENT); expect(state).not.toBeNull(); expect(Array.isArray(state?.clones)).toBe(true); }); test('STORE-INIT-04: Hand Store 初始化', async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); // 验证 Hand Store 存在 const state = await storeInspectors.getPersistedState<{ hands: unknown[]; handRuns: Record; isLoading: boolean; }>(page, STORE_NAMES.HAND); expect(state).not.toBeNull(); expect(Array.isArray(state?.hands)).toBe(true); expect(typeof state?.handRuns).toBe('object'); }); test('STORE-INIT-05: Config Store 初始化', async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); // 验证 Config Store 存在 const state = await storeInspectors.getPersistedState<{ quickConfig: Record; models: unknown[]; }>(page, STORE_NAMES.CONFIG); expect(state).not.toBeNull(); }); }); // ============================================ // 测试套件 2: Store 持久化验证 // ============================================ test.describe('Store 持久化验证', () => { test('STORE-PERSIST-01: Chat Store 持久化', async ({ page }) => { // 1. 加载页面 await page.goto(BASE_URL); await waitForAppReady(page); // 2. 发送一条消息 await userActions.sendChatMessage(page, '持久化测试消息'); await page.waitForTimeout(3000); // 3. 获取当前状态 const stateBefore = await storeInspectors.getPersistedState<{ messages: Array<{ content: string }>; }>(page, STORE_NAMES.CHAT); const countBefore = stateBefore?.messages?.length ?? 0; // 4. 刷新页面 await page.reload(); await waitForAppReady(page); // 5. 验证状态恢复 const stateAfter = await storeInspectors.getPersistedState<{ messages: Array<{ content: string }>; }>(page, STORE_NAMES.CHAT); const countAfter = stateAfter?.messages?.length ?? 0; // 消息应该被恢复(数量相同或更多) expect(countAfter).toBeGreaterThanOrEqual(countBefore - 2); // 允许一定误差 }); test('STORE-PERSIST-02: 配置持久化', async ({ page }) => { // 1. 加载页面并获取配置 await page.goto(BASE_URL); await waitForAppReady(page); const configBefore = await storeInspectors.getPersistedState<{ quickConfig: Record; }>(page, STORE_NAMES.CONFIG); // 2. 刷新页面 await page.reload(); await waitForAppReady(page); // 3. 验证配置恢复 const configAfter = await storeInspectors.getPersistedState<{ quickConfig: Record; }>(page, STORE_NAMES.CONFIG); // 配置应该相同 expect(configAfter?.quickConfig).toEqual(configBefore?.quickConfig); }); test('STORE-PERSIST-03: 清除 Store 后重新初始化', async ({ page }) => { // 1. 加载页面 await page.goto(BASE_URL); await waitForAppReady(page); // 2. 清除所有 Store await storeInspectors.clearAllStores(page); // 3. 刷新页面 await page.reload(); await waitForAppReady(page); // 4. 验证 Store 重新初始化 const chatState = await storeInspectors.getPersistedState<{ messages: unknown[]; }>(page, STORE_NAMES.CHAT); // Store 应该被重新初始化(messages 为空数组) expect(Array.isArray(chatState?.messages)).toBe(true); }); }); // ============================================ // 测试套件 3: Chat Store 状态转换验证 // ============================================ test.describe('Chat Store 状态转换验证', () => { test.beforeEach(async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); }); test('CHAT-STATE-01: isStreaming 状态转换', async ({ page }) => { // 1. 初始状态应该是 false const initialState = await storeInspectors.getPersistedState<{ isStreaming: boolean; }>(page, STORE_NAMES.CHAT); expect(initialState?.isStreaming).toBe(false); // 2. 发送消息 await userActions.sendChatMessage(page, '测试消息'); // 3. 等待流式完成 await page.waitForTimeout(5000); // 4. 最终状态应该是 false const finalState = await storeInspectors.getPersistedState<{ isStreaming: boolean; }>(page, STORE_NAMES.CHAT); expect(finalState?.isStreaming).toBe(false); }); test('CHAT-STATE-02: messages 数组状态变化', async ({ page }) => { // 1. 获取初始消息数量 const initialState = await storeInspectors.getPersistedState<{ messages: unknown[]; }>(page, STORE_NAMES.CHAT); const initialCount = initialState?.messages?.length ?? 0; // 2. 发送消息 await userActions.sendChatMessage(page, '新消息'); await page.waitForTimeout(3000); // 3. 验证消息数量增加 const newState = await storeInspectors.getPersistedState<{ messages: unknown[]; }>(page, STORE_NAMES.CHAT); const newCount = newState?.messages?.length ?? 0; // 消息数量应该增加(至少用户消息) expect(newCount).toBeGreaterThan(initialCount); }); test('CHAT-STATE-03: currentModel 状态', async ({ page }) => { // 1. 获取当前模型 const state = await storeInspectors.getPersistedState<{ currentModel: string; }>(page, STORE_NAMES.CHAT); // 2. 验证模型是有效值 expect(state?.currentModel).toBeDefined(); expect(state?.currentModel.length).toBeGreaterThan(0); }); test('CHAT-STATE-04: sessionKey 状态', async ({ page }) => { // 1. 发送消息建立会话 await userActions.sendChatMessage(page, '建立会话'); await page.waitForTimeout(3000); // 2. 检查 sessionKey const state = await storeInspectors.getPersistedState<{ sessionKey: string | null; }>(page, STORE_NAMES.CHAT); // sessionKey 应该存在(如果后端返回了) console.log(`SessionKey exists: ${!!state?.sessionKey}`); }); }); // ============================================ // 测试套件 4: Agent Store 状态转换验证 // ============================================ test.describe('Agent Store 状态转换验证', () => { test.beforeEach(async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); await navigateToTab(page, '分身'); }); test('AGENT-STATE-01: clones 数组状态', async ({ page }) => { // 1. 获取 clones 列表 const state = await storeInspectors.getPersistedState<{ clones: Array<{ id: string; name: string }>; }>(page, STORE_NAMES.AGENT); // 2. 验证格式 expect(Array.isArray(state?.clones)).toBe(true); // 3. 每个 clone 应该有必需字段 if (state?.clones && state.clones.length > 0) { const firstClone = state.clones[0]; expect(firstClone).toHaveProperty('id'); expect(firstClone).toHaveProperty('name'); } }); test('AGENT-STATE-02: currentAgent 切换状态', async ({ page }) => { // 1. 获取当前 Agent const chatState = await storeInspectors.getPersistedState<{ currentAgent: { id: string } | null; }>(page, STORE_NAMES.CHAT); // 2. 验证 currentAgent 结构 if (chatState?.currentAgent) { expect(chatState.currentAgent).toHaveProperty('id'); } }); }); // ============================================ // 测试套件 5: Hand Store 状态转换验证 // ============================================ test.describe('Hand Store 状态转换验证', () => { test.beforeEach(async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); await navigateToTab(page, 'Hands'); await page.waitForTimeout(1500); }); test('HAND-STATE-01: hands 数组状态', async ({ page }) => { // 1. 获取 hands 列表 const state = await storeInspectors.getPersistedState<{ hands: Array<{ id: string; name: string; status: string; requirements_met?: boolean; }>; }>(page, STORE_NAMES.HAND); // 2. 验证格式 expect(Array.isArray(state?.hands)).toBe(true); // 3. 每个 hand 应该有必需字段 if (state?.hands && state.hands.length > 0) { const firstHand = state.hands[0]; expect(firstHand).toHaveProperty('id'); expect(firstHand).toHaveProperty('name'); expect(firstHand).toHaveProperty('status'); // 状态应该是有效值 const validStatuses = ['idle', 'running', 'needs_approval', 'error', 'unavailable', 'setup_needed']; expect(validStatuses).toContain(firstHand.status); } }); test('HAND-STATE-02: handRuns 记录状态', async ({ page }) => { // 1. 获取 handRuns const state = await storeInspectors.getPersistedState<{ handRuns: Record; }>(page, STORE_NAMES.HAND); // 2. 验证格式 expect(typeof state?.handRuns).toBe('object'); }); test('HAND-STATE-03: approvals 队列状态', async ({ page }) => { // 1. 获取 approvals const state = await storeInspectors.getPersistedState<{ approvals: unknown[]; }>(page, STORE_NAMES.HAND); // 2. 验证格式 expect(Array.isArray(state?.approvals)).toBe(true); }); }); // ============================================ // 测试套件 6: Workflow Store 状态转换验证 // ============================================ test.describe('Workflow Store 状态转换验证', () => { test.beforeEach(async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); await navigateToTab(page, '工作流'); await page.waitForTimeout(1000); }); test('WF-STATE-01: workflows 数组状态', async ({ page }) => { // 1. 获取 workflows 列表 const state = await storeInspectors.getPersistedState<{ workflows: Array<{ id: string; name: string; steps: number; }>; }>(page, STORE_NAMES.WORKFLOW); // 2. 验证格式 expect(Array.isArray(state?.workflows)).toBe(true); // 3. 每个 workflow 应该有必需字段 if (state?.workflows && state.workflows.length > 0) { const firstWorkflow = state.workflows[0]; expect(firstWorkflow).toHaveProperty('id'); expect(firstWorkflow).toHaveProperty('name'); } }); }); // ============================================ // 测试套件 7: Team Store 状态转换验证 // ============================================ test.describe('Team Store 状态转换验证', () => { test.beforeEach(async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); await navigateToTab(page, '团队'); await page.waitForTimeout(1000); }); test('TEAM-STATE-01: teams 数组状态', async ({ page }) => { // 1. 获取 teams 列表 const state = await storeInspectors.getPersistedState<{ teams: Array<{ id: string; name: string; members: unknown[]; tasks: unknown[]; }>; }>(page, STORE_NAMES.TEAM); // 2. 验证格式 expect(Array.isArray(state?.teams)).toBe(true); // 3. 每个 team 应该有必需字段 if (state?.teams && state.teams.length > 0) { const firstTeam = state.teams[0]; expect(firstTeam).toHaveProperty('id'); expect(firstTeam).toHaveProperty('name'); expect(Array.isArray(firstTeam.members)).toBe(true); expect(Array.isArray(firstTeam.tasks)).toBe(true); } }); test('TEAM-STATE-02: activeTeam 状态', async ({ page }) => { // 1. 获取 activeTeam const state = await storeInspectors.getPersistedState<{ activeTeam: { id: string } | null; }>(page, STORE_NAMES.TEAM); // 2. 验证状态 // activeTeam 可以是 null 或有 id 的对象 if (state?.activeTeam) { expect(state.activeTeam).toHaveProperty('id'); } }); }); // ============================================ // 测试套件 8: Connection Store 状态转换验证 // ============================================ test.describe('Connection Store 状态转换验证', () => { test.beforeEach(async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); }); test('CONN-STATE-01: connectionState 状态', async ({ page }) => { // 1. 获取连接状态 const state = await storeInspectors.getPersistedState<{ connectionState: string; }>(page, STORE_NAMES.CONNECTION); // 2. 验证状态是有效值 const validStates = ['connected', 'disconnected', 'connecting', 'reconnecting', 'handshaking']; expect(validStates).toContain(state?.connectionState); }); test('CONN-STATE-02: gatewayVersion 状态', async ({ page }) => { // 1. 等待连接尝试 await page.waitForTimeout(3000); // 2. 获取版本 const state = await storeInspectors.getPersistedState<{ gatewayVersion: string | null; }>(page, STORE_NAMES.CONNECTION); // 3. 如果连接成功,版本应该存在 console.log(`Gateway version: ${state?.gatewayVersion}`); }); test('CONN-STATE-03: error 状态', async ({ page }) => { // 1. 获取错误状态 const state = await storeInspectors.getPersistedState<{ error: string | null; }>(page, STORE_NAMES.CONNECTION); // 2. 正常情况下 error 应该是 null // 但如果连接失败,error 可能有值 console.log(`Connection error: ${state?.error}`); }); }); // ============================================ // 测试套件 9: Store 快照验证 // ============================================ test.describe('Store 快照验证', () => { test('SNAPSHOT-01: 获取所有 Store 快照', async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); // 1. 获取所有 Store 快照 const snapshot = await storeInspectors.getAllStoresSnapshot(page); // 2. 验证快照包含预期的 Store console.log('Store snapshot keys:', Object.keys(snapshot)); // 3. 验证每个 Store 的基本结构 for (const [storeName, state] of Object.entries(snapshot)) { console.log(`Store ${storeName}:`, typeof state); expect(state).toBeDefined(); } }); test('SNAPSHOT-02: Store 状态一致性', async ({ page }) => { await page.goto(BASE_URL); await waitForAppReady(page); // 1. 获取两次快照 const snapshot1 = await storeInspectors.getAllStoresSnapshot(page); await page.waitForTimeout(100); const snapshot2 = await storeInspectors.getAllStoresSnapshot(page); // 2. 验证状态一致性(无操作时状态应该相同) expect(snapshot1).toEqual(snapshot2); }); }); // 测试报告 test.afterAll(async ({}, testInfo) => { console.log('\n========================================'); console.log('ZCLAW Store 状态验证测试完成'); console.log('========================================'); console.log(`测试时间: ${new Date().toISOString()}`); console.log('========================================\n'); });