/** * Store 断言工具 * 提供类型安全的 Store 状态断言方法 */ import { Page } from '@playwright/test'; import { expect } from '@playwright/test'; import { storeInspectors, STORE_NAMES, type StoreName } from '../fixtures/store-inspectors'; /** * 通用断言工具 */ export const storeAssertions = { /** * 断言 Store 状态匹配预期对象 */ async assertStoreState( page: Page, storeName: StoreName, expected: Partial ): Promise { const state = await storeInspectors.getPersistedState(page, storeName); expect(state).not.toBeNull(); for (const [key, value] of Object.entries(expected)) { expect(state).toHaveProperty(key, value); } }, /** * 断言 Store 字段值等于预期 */ async assertFieldEquals( page: Page, storeName: StoreName, fieldPath: string, expected: T ): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); expect(value).toEqual(expected); }, /** * 断言 Store 字段包含特定值(数组或字符串) */ async assertFieldContains( page: Page, storeName: StoreName, fieldPath: string, expected: unknown ): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); if (Array.isArray(value)) { expect(value).toContainEqual(expected); } else if (typeof value === 'string') { expect(value).toContain(expected); } else { throw new Error(`Field ${fieldPath} is not an array or string`); } }, /** * 断言 Store 字段数组长度 */ async assertArrayLength( page: Page, storeName: StoreName, fieldPath: string, expected: number ): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); expect(Array.isArray(value)).toBe(true); expect(value?.length).toBe(expected); }, /** * 断言 Store 字段数组长度大于指定值 */ async assertArrayLengthGreaterThan( page: Page, storeName: StoreName, fieldPath: string, min: number ): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); expect(Array.isArray(value)).toBe(true); expect(value?.length).toBeGreaterThan(min); }, /** * 断言 Store 字段为真值 */ async assertFieldTruthy(page: Page, storeName: StoreName, fieldPath: string): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); expect(value).toBeTruthy(); }, /** * 断言 Store 字段为假值 */ async assertFieldFalsy(page: Page, storeName: StoreName, fieldPath: string): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); expect(value).toBeFalsy(); }, /** * 断言 Store 字段为 null */ async assertFieldNull(page: Page, storeName: StoreName, fieldPath: string): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); expect(value).toBeNull(); }, /** * 断言 Store 字段不为 null */ async assertFieldNotNull(page: Page, storeName: StoreName, fieldPath: string): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); expect(value).not.toBeNull(); }, /** * 断言 Store 字段匹配正则表达式 */ async assertFieldMatches( page: Page, storeName: StoreName, fieldPath: string, pattern: RegExp ): Promise { const value = await storeInspectors.getStateField(page, storeName, fieldPath); expect(value).toMatch(pattern); }, }; /** * 聊天相关断言 */ export const chatAssertions = { /** * 断言消息数量 */ async assertMessageCount(page: Page, expected: number): Promise { const state = await storeInspectors.getPersistedState<{ messages: unknown[]; }>(page, STORE_NAMES.CHAT); expect(state?.messages?.length).toBe(expected); }, /** * 断言消息数量大于 */ async assertMessageCountGreaterThan(page: Page, min: number): Promise { const state = await storeInspectors.getPersistedState<{ messages: unknown[]; }>(page, STORE_NAMES.CHAT); expect(state?.messages?.length).toBeGreaterThan(min); }, /** * 断言最后一条消息内容 */ async assertLastMessageContent(page: Page, expected: string | RegExp): Promise { const state = await storeInspectors.getPersistedState<{ messages: Array<{ content: string }>; }>(page, STORE_NAMES.CHAT); const lastMessage = state?.messages?.[state.messages.length - 1]; expect(lastMessage).toBeDefined(); if (expected instanceof RegExp) { expect(lastMessage?.content).toMatch(expected); } else { expect(lastMessage?.content).toContain(expected); } }, /** * 断言最后一条消息角色 */ async assertLastMessageRole( page: Page, role: 'user' | 'assistant' | 'tool' | 'hand' | 'workflow' ): Promise { const state = await storeInspectors.getPersistedState<{ messages: Array<{ role: string }>; }>(page, STORE_NAMES.CHAT); const lastMessage = state?.messages?.[state.messages.length - 1]; expect(lastMessage?.role).toBe(role); }, /** * 断言流式状态 */ async assertStreamingState(page: Page, expected: boolean): Promise { const state = await storeInspectors.getPersistedState<{ isStreaming: boolean }>( page, STORE_NAMES.CHAT ); expect(state?.isStreaming).toBe(expected); }, /** * 断言当前模型 */ async assertCurrentModel(page: Page, modelId: string): Promise { const state = await storeInspectors.getPersistedState<{ currentModel: string }>( page, STORE_NAMES.CHAT ); expect(state?.currentModel).toBe(modelId); }, /** * 断言当前 Agent */ async assertCurrentAgent(page: Page, agentId: string): Promise { const state = await storeInspectors.getPersistedState<{ currentAgent: { id: string } | null; }>(page, STORE_NAMES.CHAT); expect(state?.currentAgent?.id).toBe(agentId); }, /** * 断言 sessionKey 存在 */ async assertSessionKeyExists(page: Page): Promise { const state = await storeInspectors.getPersistedState<{ sessionKey: string | null }>( page, STORE_NAMES.CHAT ); expect(state?.sessionKey).not.toBeNull(); }, /** * 断言消息包含错误 */ async assertLastMessageHasError(page: Page): Promise { const state = await storeInspectors.getPersistedState<{ messages: Array<{ error?: string }>; }>(page, STORE_NAMES.CHAT); const lastMessage = state?.messages?.[state.messages.length - 1]; expect(lastMessage?.error).toBeDefined(); }, }; /** * 连接相关断言 */ export const connectionAssertions = { /** * 断言连接状态 */ async assertConnectionState( page: Page, expected: 'connected' | 'disconnected' | 'connecting' | 'reconnecting' ): Promise { const state = await storeInspectors.getPersistedState<{ connectionState: string }>( page, STORE_NAMES.CONNECTION ); expect(state?.connectionState).toBe(expected); }, /** * 断言已连接 */ async assertConnected(page: Page): Promise { await this.assertConnectionState(page, 'connected'); }, /** * 断言已断开 */ async assertDisconnected(page: Page): Promise { await this.assertConnectionState(page, 'disconnected'); }, /** * 断言 Gateway 版本 */ async assertGatewayVersion(page: Page, expected: string): Promise { const state = await storeInspectors.getPersistedState<{ gatewayVersion: string | null }>( page, STORE_NAMES.CONNECTION ); expect(state?.gatewayVersion).toBe(expected); }, /** * 断言无连接错误 */ async assertNoError(page: Page): Promise { const state = await storeInspectors.getPersistedState<{ error: string | null }>( page, STORE_NAMES.CONNECTION ); expect(state?.error).toBeNull(); }, }; /** * Hands 相关断言 */ export const handAssertions = { /** * 断言 Hands 列表非空 */ async assertHandsNotEmpty(page: Page): Promise { const state = await storeInspectors.getPersistedState<{ hands: unknown[] }>( page, STORE_NAMES.HAND ); expect(state?.hands?.length).toBeGreaterThan(0); }, /** * 断言 Hands 列表数量 */ async assertHandsCount(page: Page, expected: number): Promise { const state = await storeInspectors.getPersistedState<{ hands: unknown[] }>( page, STORE_NAMES.HAND ); expect(state?.hands?.length).toBe(expected); }, /** * 断言 Hand 状态 */ async assertHandStatus( page: Page, handId: string, expected: string ): Promise { const state = await storeInspectors.getPersistedState<{ hands: Array<{ id: string; status: string }>; }>(page, STORE_NAMES.HAND); const hand = state?.hands?.find((h) => h.id === handId); expect(hand?.status).toBe(expected); }, /** * 断言存在运行中的 Hand */ async assertHasRunningHand(page: Page): Promise { const state = await storeInspectors.getPersistedState<{ hands: Array<{ status: string }>; }>(page, STORE_NAMES.HAND); const hasRunning = state?.hands?.some((h) => h.status === 'running'); expect(hasRunning).toBe(true); }, /** * 断言存在待审批的 Hand */ async assertHasPendingApproval(page: Page): Promise { const state = await storeInspectors.getPersistedState<{ approvals: unknown[]; }>(page, STORE_NAMES.HAND); expect(state?.approvals?.length).toBeGreaterThan(0); }, }; /** * 分身/Agent 相关断言 */ export const agentAssertions = { /** * 断言分身列表数量 */ async assertClonesCount(page: Page, expected: number): Promise { const state = await storeInspectors.getPersistedState<{ clones: unknown[] }>( page, STORE_NAMES.AGENT ); expect(state?.clones?.length).toBe(expected); }, /** * 断言分身列表包含指定名称 */ async assertClonesContains(page: Page, name: string): Promise { const state = await storeInspectors.getPersistedState<{ clones: Array<{ name: string }>; }>(page, STORE_NAMES.AGENT); const hasClone = state?.clones?.some((c) => c.name === name); expect(hasClone).toBe(true); }, }; /** * 团队相关断言 */ export const teamAssertions = { /** * 断言团队数量 */ async assertTeamsCount(page: Page, expected: number): Promise { const state = await storeInspectors.getPersistedState<{ teams: unknown[] }>( page, STORE_NAMES.TEAM ); expect(state?.teams?.length).toBe(expected); }, /** * 断言活跃团队 */ async assertActiveTeam(page: Page, teamId: string): Promise { const state = await storeInspectors.getPersistedState<{ activeTeam: { id: string } | null; }>(page, STORE_NAMES.TEAM); expect(state?.activeTeam?.id).toBe(teamId); }, }; /** * 工作流相关断言 */ export const workflowAssertions = { /** * 断言工作流数量 */ async assertWorkflowsCount(page: Page, expected: number): Promise { const state = await storeInspectors.getPersistedState<{ workflows: unknown[] }>( page, STORE_NAMES.WORKFLOW ); expect(state?.workflows?.length).toBe(expected); }, }; /** * 组合断言 - 用于复杂场景 */ export const compositeAssertions = { /** * 断言完整的聊天状态(发送消息后) */ async assertChatStateAfterSend( page: Page, expected: { messageCount?: number; isStreaming?: boolean; lastMessageRole?: 'user' | 'assistant'; } ): Promise { if (expected.messageCount !== undefined) { await chatAssertions.assertMessageCount(page, expected.messageCount); } if (expected.isStreaming !== undefined) { await chatAssertions.assertStreamingState(page, expected.isStreaming); } if (expected.lastMessageRole !== undefined) { await chatAssertions.assertLastMessageRole(page, expected.lastMessageRole); } }, /** * 断言完整的应用状态(健康检查) */ async assertHealthyAppState(page: Page): Promise { // 连接正常 await connectionAssertions.assertNoError(page); // 聊天可用 const chatState = await storeInspectors.getPersistedState<{ isStreaming: boolean }>( page, STORE_NAMES.CHAT ); expect(chatState?.isStreaming).toBe(false); }, };