diff --git a/cypress.config.ts b/cypress.config.ts
index 290f1c5e..1442c75d 100644
--- a/cypress.config.ts
+++ b/cypress.config.ts
@@ -15,10 +15,6 @@ export default defineConfig({
APPFLOWY_WS_BASE_URL: process.env.APPFLOWY_WS_BASE_URL || 'ws://localhost/ws/v2',
GOTRUE_ADMIN_EMAIL: process.env.GOTRUE_ADMIN_EMAIL || 'admin@example.com',
GOTRUE_ADMIN_PASSWORD: process.env.GOTRUE_ADMIN_PASSWORD || 'password',
- // WebSocket mocking configuration
- MOCK_WEBSOCKET: process.env.MOCK_WEBSOCKET === 'true' || false,
- WS_AUTO_RESPOND: process.env.WS_AUTO_RESPOND === 'true' || false,
- WS_RESPONSE_DELAY: process.env.WS_RESPONSE_DELAY || '100',
},
e2e: {
chromeWebSecurity: false,
@@ -38,10 +34,6 @@ export default defineConfig({
config.env.APPFLOWY_WS_BASE_URL = process.env.APPFLOWY_WS_BASE_URL || config.env.APPFLOWY_WS_BASE_URL;
config.env.GOTRUE_ADMIN_EMAIL = process.env.GOTRUE_ADMIN_EMAIL || config.env.GOTRUE_ADMIN_EMAIL;
config.env.GOTRUE_ADMIN_PASSWORD = process.env.GOTRUE_ADMIN_PASSWORD || config.env.GOTRUE_ADMIN_PASSWORD;
- // Pass WebSocket mock configuration
- config.env.MOCK_WEBSOCKET = process.env.MOCK_WEBSOCKET === 'true' || config.env.MOCK_WEBSOCKET;
- config.env.WS_AUTO_RESPOND = process.env.WS_AUTO_RESPOND === 'true' || config.env.WS_AUTO_RESPOND;
- config.env.WS_RESPONSE_DELAY = process.env.WS_RESPONSE_DELAY || config.env.WS_RESPONSE_DELAY;
// Add task for logging to Node.js console
on('task', {
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index 83e055ac..b018db01 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -6,10 +6,6 @@ import './auth-utils';
import './page-utils';
// Import console logger v2 (improved version)
import './console-logger';
-// Import WebSocket mock utilities
-import './websocket-mock';
-// Import WebSocket collab mock for document sync
-import './websocket-collab-mock';
// ***********************************************
// This example commands.ts shows you how to
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
index c6b0e3c2..ed5709b9 100644
--- a/cypress/support/e2e.ts
+++ b/cypress/support/e2e.ts
@@ -18,33 +18,11 @@ import 'cypress-file-upload';
import 'cypress-plugin-api';
import 'cypress-real-events';
import './commands';
-import { CollabWebSocketMock } from './websocket-collab-mock';
-
-
-// Install WebSocket mock before window loads if enabled
-if (Cypress.env('MOCK_WEBSOCKET') === true || Cypress.env('MOCK_WEBSOCKET') === 'true') {
- const delay = parseInt(Cypress.env('WS_RESPONSE_DELAY') || '50');
-
- Cypress.on('window:before:load', (win) => {
- // Install mock on every window load
- if (!(win as any).__collabMockInstance) {
- (win as any).__collabMockInstance = new CollabWebSocketMock(win as any, delay);
- // eslint-disable-next-line no-console
- console.log('[E2E] Collab WebSocket mock installed on window:', win.location.href);
- }
- });
-}
// Global hooks for console logging
beforeEach(() => {
// Start capturing console logs for each test
cy.startConsoleCapture();
-
- // Log if WebSocket mocking is enabled
- if (Cypress.env('MOCK_WEBSOCKET') === true || Cypress.env('MOCK_WEBSOCKET') === 'true') {
- cy.task('log', 'Collab WebSocket mocking enabled for this test');
- cy.log('Collab WebSocket mocking enabled');
- }
});
afterEach(() => {
@@ -54,11 +32,6 @@ afterEach(() => {
// Stop capturing to clean up
cy.stopConsoleCapture();
-
- // Restore WebSocket if it was mocked
- if (Cypress.env('MOCK_WEBSOCKET') === true || Cypress.env('MOCK_WEBSOCKET') === 'true') {
- cy.restoreCollabWebSocket();
- }
});
// Globally ignore transient app bootstrap errors during tests
diff --git a/cypress/support/page/flows.ts b/cypress/support/page/flows.ts
index 30803f22..370ed2ef 100644
--- a/cypress/support/page/flows.ts
+++ b/cypress/support/page/flows.ts
@@ -204,20 +204,9 @@ export function createPage(pageName: string) {
// Check if there are any WebSocket connection indicators
const wsConnected = win.localStorage.getItem('ws_connected');
cy.task('log', `WebSocket connection status: ${wsConnected || 'unknown'}`);
+ // Log any global WebSocket state if available
+ cy.task('log', `Window has WebSocket: ${!!win.WebSocket}`);
});
-
- if (Cypress.env('MOCK_WEBSOCKET')) {
- cy.task('log', 'Waiting for document to sync (MOCK_WEBSOCKET mode)');
- cy.waitForDocumentSync();
- cy.task('log', 'Document synced');
- } else {
- cy.task('log', 'WebSocket mode: Real-time sync expected');
- // Check if WebSocket is connected
- cy.window().then((win) => {
- // Log any global WebSocket state if available
- cy.task('log', `Window has WebSocket: ${!!win.WebSocket}`);
- });
- }
// Wait for document to load properly - be more generous with WebSocket sync
const isCi = Cypress.env('CI') || Cypress.env('GITHUB_ACTIONS');
diff --git a/cypress/support/websocket-collab-mock.ts b/cypress/support/websocket-collab-mock.ts
deleted file mode 100644
index cbfb84e3..00000000
--- a/cypress/support/websocket-collab-mock.ts
+++ /dev/null
@@ -1,381 +0,0 @@
-///
-import * as Y from 'yjs';
-import { messages } from '../../src/proto/messages';
-// Keys expected by the app Yjs utils
-const YjsEditorKey = {
- data_section: 'data_section',
- document: 'document',
- meta: 'meta',
- blocks: 'blocks',
- page_id: 'page_id',
- children_map: 'children_map',
- text_map: 'text_map',
-};
-
-/**
- * Enhanced WebSocket mock that simulates AppFlowy's collab protocol
- * This mock ensures that the Document component receives the necessary
- * Y.Doc structure to render properly during tests.
- */
-
-// Message types from AppFlowy protocol
-interface CollabMessage {
- objectId: string;
- collabType: number;
- syncRequest?: {
- stateVector?: Uint8Array;
- lastMessageId?: { timestamp: number; counter: number };
- };
- update?: {
- flags: number;
- payload: Uint8Array;
- messageId?: { timestamp: number; counter: number };
- };
- awarenessUpdate?: {
- payload: Uint8Array;
- };
-}
-
-interface ProtobufMessage {
- collabMessage?: CollabMessage;
-}
-
-// Helper to create initial document structure
-function createInitialDocumentUpdate(objectId: string): Uint8Array {
- // Create a Y.Doc with the expected structure
- const doc = new Y.Doc({ guid: objectId });
-
- // Create the data_section map
- const dataSection = doc.getMap(YjsEditorKey.data_section);
-
- // Create the document map with initial structure
- const document = new Y.Map();
-
- // Set page_id as a plain string (app expects string, not Y.Text)
- document.set(YjsEditorKey.page_id, objectId);
-
- // Add blocks map for document content with a root page block
- const blocks = new Y.Map();
- const rootBlock = new Y.Map();
- rootBlock.set('id', objectId);
- rootBlock.set('ty', 0); // BlockType.Page
- rootBlock.set('children', objectId);
- rootBlock.set('data', '');
- rootBlock.set('parent', objectId);
- rootBlock.set('external_id', objectId);
- blocks.set(objectId, rootBlock);
-
- // Create a paragraph block for the editor to render
- const paragraphId = `${objectId}-p1`;
- const paragraphBlock = new Y.Map();
- paragraphBlock.set('id', paragraphId);
- paragraphBlock.set('ty', 1); // BlockType.Paragraph
- paragraphBlock.set('children', paragraphId);
- paragraphBlock.set('data', '{}');
- paragraphBlock.set('parent', objectId);
- paragraphBlock.set('external_id', paragraphId);
- blocks.set(paragraphId, paragraphBlock);
-
- document.set(YjsEditorKey.blocks, blocks);
-
- // Add meta information
- const meta = new Y.Map();
- const childrenMap = new Y.Map();
-
- // Add the paragraph as a child of the root page
- const rootChildrenArray = new Y.Array();
- rootChildrenArray.push([paragraphId]);
- childrenMap.set(objectId, rootChildrenArray);
-
- // Add empty children array for the paragraph
- const paragraphChildrenArray = new Y.Array();
- childrenMap.set(paragraphId, paragraphChildrenArray);
-
- meta.set(YjsEditorKey.children_map, childrenMap);
-
- // Add text for the paragraph
- const textMap = new Y.Map();
- const paragraphText = new Y.Text();
- textMap.set(paragraphId, paragraphText);
- meta.set(YjsEditorKey.text_map, textMap);
-
- document.set(YjsEditorKey.meta, meta);
-
- // Set the document in data_section
- dataSection.set(YjsEditorKey.document, document);
-
- // Log the structure for debugging
- console.log('[CollabWebSocketMock] Created initial document structure:', {
- objectId,
- hasDataSection: doc.getMap(YjsEditorKey.data_section) !== undefined,
- hasDocument: dataSection.get(YjsEditorKey.document) !== undefined,
- blocksCount: blocks.size,
- hasRootBlock: blocks.has(objectId),
- });
-
- // Encode the state as an update
- return Y.encodeStateAsUpdate(doc);
-}
-
-// WebSocket mock class with collab protocol support
-class CollabWebSocketMock {
- private url: string;
- private ws: WebSocket | null = null;
- private originalWebSocket: typeof WebSocket;
- private pendingDocs: Set = new Set();
- private syncedDocs: Set = new Set();
- private messageQueue: any[] = [];
- private responseDelay: number;
- private targetWindow: Window & typeof globalThis;
-
- constructor(targetWindow: Window & typeof globalThis, responseDelay: number = 50) {
- this.targetWindow = targetWindow;
- this.url = '';
- this.originalWebSocket = targetWindow.WebSocket;
- this.responseDelay = responseDelay;
- this.setupMock();
- }
-
- private setupMock() {
- const self = this;
-
- // Replace WebSocket constructor on AUT window
- (this.targetWindow as any).WebSocket = function (url: string, protocols?: string | string[]) {
- // Check if this is the AppFlowy WebSocket URL
- if (!url.includes('/ws/v2/')) {
- // Use real WebSocket for non-collab URLs
- return new self.originalWebSocket(url, protocols);
- }
-
- // Intercept collab socket
- self.url = url;
- // Create mock WebSocket instance
- const mockWs: any = {
- url: url,
- readyState: 0, // CONNECTING
- binaryType: 'arraybuffer',
- onopen: null,
- onclose: null,
- onerror: null,
- onmessage: null,
- listeners: new Map>(),
-
- send: (data: any) => {
- if (mockWs.readyState !== 1) {
- throw new Error('WebSocket is not open');
- }
- // Heartbeat support: echo back plain text heartbeats
- if (typeof data === 'string' && data === 'echo') {
- const echoEvent = new MessageEvent('message', { data: 'echo' });
- setTimeout(() => {
- mockWs.onmessage?.(echoEvent);
- mockWs.dispatchEvent(echoEvent);
- }, 10);
- return;
- }
- self.handleMessage(mockWs, data);
- },
-
- close: (code?: number, reason?: string) => {
- if (mockWs.readyState === 2 || mockWs.readyState === 3) return;
-
- mockWs.readyState = 2; // CLOSING
- setTimeout(() => {
- mockWs.readyState = 3; // CLOSED
- const closeEvent = new CloseEvent('close', {
- code: code || 1000,
- reason: reason || '',
- wasClean: true,
- });
- mockWs.onclose?.(closeEvent);
- mockWs.dispatchEvent(closeEvent);
- }, 10);
- },
-
- addEventListener: (type: string, listener: EventListener) => {
- if (!mockWs.listeners.has(type)) {
- mockWs.listeners.set(type, new Set());
- }
- mockWs.listeners.get(type)!.add(listener);
- },
-
- removeEventListener: (type: string, listener: EventListener) => {
- mockWs.listeners.get(type)?.delete(listener);
- },
-
- dispatchEvent: (event: Event) => {
- const listeners = mockWs.listeners.get(event.type);
- if (listeners) {
- listeners.forEach((listener: EventListener) => listener(event));
- }
- return true;
- }
- };
-
- // Ensure instanceof checks pass
- try {
- (mockWs as any).constructor = (self.targetWindow as any).WebSocket;
- Object.setPrototypeOf(mockWs, (self.targetWindow as any).WebSocket.prototype);
- } catch (_) {
- // ignore
- }
-
- self.ws = mockWs;
-
- // Store mock on AUT window for debugging
- (self.targetWindow as any).__mockCollabWebSocket = mockWs;
-
- // Simulate connection opening
- setTimeout(() => {
- mockWs.readyState = 1; // OPEN
- const openEvent = new Event('open');
- mockWs.onopen?.(openEvent);
- mockWs.dispatchEvent(openEvent);
- console.log('[CollabWebSocketMock] WebSocket connection opened for URL:', url);
-
- // Log what page IDs we're dealing with
- const urlMatch = url.match(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g);
- if (urlMatch) {
- console.log('[CollabWebSocketMock] Found object IDs in URL:', urlMatch);
- }
-
- // Process any queued messages
- self.processMessageQueue(mockWs);
- }, 50);
-
- return mockWs;
- } as any;
-
- // Copy WebSocket static properties
- Object.setPrototypeOf(this.targetWindow.WebSocket, this.originalWebSocket);
- // Align prototype so `instanceof WebSocket` works
- try {
- (this.targetWindow as any).WebSocket.prototype = this.originalWebSocket.prototype;
- } catch (_) {
- // no-op
- }
- // Copy static constants (read-only properties)
- try {
- Object.defineProperty(this.targetWindow.WebSocket, 'CONNECTING', { value: 0, writable: false });
- Object.defineProperty(this.targetWindow.WebSocket, 'OPEN', { value: 1, writable: false });
- Object.defineProperty(this.targetWindow.WebSocket, 'CLOSING', { value: 2, writable: false });
- Object.defineProperty(this.targetWindow.WebSocket, 'CLOSED', { value: 3, writable: false });
- } catch (_) {
- // no-op if already defined
- }
- }
-
- private handleMessage(ws: any, data: ArrayBuffer | Uint8Array | string) {
- try {
- // Ignore heartbeat or text messages
- if (typeof data === 'string') {
- return;
- }
-
- const buffer = data instanceof ArrayBuffer
- ? new Uint8Array(data)
- : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
-
- const decoded = messages.Message.decode(buffer);
- const collabMsg = decoded.collabMessage;
-
- if (collabMsg?.syncRequest && collabMsg.objectId) {
- const objectId = collabMsg.objectId;
- const collabType = collabMsg.collabType ?? 0;
- setTimeout(() => {
- this.sendSyncResponse(ws, objectId, collabType);
- }, this.responseDelay);
- }
- } catch (error) {
- // If decoding fails, ignore silently to avoid breaking app
- // Useful when app sends non-collab binary messages
- }
- }
-
- private sendSyncResponse(ws: any, objectId: string, collabType: number) {
- if (this.syncedDocs.has(objectId)) return;
-
- const update = createInitialDocumentUpdate(objectId);
- const msg: messages.IMessage = {
- collabMessage: {
- objectId,
- collabType,
- update: {
- flags: 0,
- payload: update,
- messageId: { timestamp: Date.now(), counter: 1 },
- },
- },
- };
- const encoded = messages.Message.encode(msg).finish();
- const dataBuf = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
- const messageEvent = new MessageEvent('message', { data: dataBuf });
-
- ws.onmessage?.(messageEvent);
- ws.dispatchEvent(messageEvent);
- this.syncedDocs.add(objectId);
- }
-
- private processMessageQueue(ws: any) {
- while (this.messageQueue.length > 0) {
- const msg = this.messageQueue.shift();
- this.handleMessage(ws, msg);
- }
- }
-
- public restore() {
- this.targetWindow.WebSocket = this.originalWebSocket;
- delete (this.targetWindow as any).__mockCollabWebSocket;
- this.pendingDocs.clear();
- this.syncedDocs.clear();
- }
-
- public addPendingDocument(objectId: string) {
- this.pendingDocs.add(objectId);
- }
-}
-
-// Cypress commands for collab WebSocket mocking
-declare global {
- namespace Cypress {
- interface Chainable {
- mockCollabWebSocket(responseDelay?: number): Chainable;
- restoreCollabWebSocket(): Chainable;
- waitForDocumentSync(objectId?: string, timeout?: number): Chainable;
- }
- }
-}
-
-// Store mock instance globally
-let collabMockInstance: CollabWebSocketMock | null = null;
-
-// Add Cypress commands
-if ((window as any).Cypress) {
- Cypress.Commands.add('mockCollabWebSocket', (responseDelay = 50) => {
- cy.window().then((win) => {
- if (!collabMockInstance) {
- collabMockInstance = new CollabWebSocketMock(win, responseDelay);
- (win as any).__collabMockInstance = collabMockInstance;
- }
- });
- });
-
- Cypress.Commands.add('restoreCollabWebSocket', () => {
- cy.window().then((win) => {
- if (collabMockInstance) {
- collabMockInstance.restore();
- collabMockInstance = null;
- delete (win as any).__collabMockInstance;
- }
- });
- });
-
- Cypress.Commands.add('waitForDocumentSync', (objectId?: string, timeout = 10000) => {
- // When mocking, just wait for the modal to be visible
- // The Document component might not render immediately even with mocked data
- cy.get('[role="dialog"]', { timeout: 5000 }).should('be.visible');
- cy.wait(1000); // Give time for the document to render
- });
-}
-
-export { CollabWebSocketMock };
diff --git a/cypress/support/websocket-mock.ts b/cypress/support/websocket-mock.ts
deleted file mode 100644
index 73a5520e..00000000
--- a/cypress/support/websocket-mock.ts
+++ /dev/null
@@ -1,269 +0,0 @@
-///
-
-interface MockWebSocketConfig {
- enabled: boolean;
- url?: string | RegExp;
- responseDelay?: number;
- autoRespond?: boolean;
- mockResponses?: Map;
-}
-
-interface MockWebSocketInstance {
- url: string;
- readyState: number;
- binaryType: BinaryType;
- onopen: ((event: Event) => void) | null;
- onclose: ((event: CloseEvent) => void) | null;
- onerror: ((event: Event) => void) | null;
- onmessage: ((event: MessageEvent) => void) | null;
- send: (data: any) => void;
- close: (code?: number, reason?: string) => void;
- addEventListener: (type: string, listener: EventListener) => void;
- removeEventListener: (type: string, listener: EventListener) => void;
- dispatchEvent: (event: Event) => boolean;
-}
-
-class MockWebSocket implements MockWebSocketInstance {
- url: string;
- readyState: number = 0; // CONNECTING
- binaryType: BinaryType = 'arraybuffer';
- onopen: ((event: Event) => void) | null = null;
- onclose: ((event: CloseEvent) => void) | null = null;
- onerror: ((event: Event) => void) | null = null;
- onmessage: ((event: MessageEvent) => void) | null = null;
-
- private listeners: Map> = new Map();
- private config: MockWebSocketConfig;
- private sentMessages: any[] = [];
-
- constructor(url: string, protocols?: string | string[], config?: MockWebSocketConfig) {
- this.url = url;
- this.config = config || { enabled: true };
-
- // Store the mock instance for Cypress access
- if ((window as any).Cypress) {
- (window as any).__mockWebSocket = this;
- (window as any).__mockWebSocketMessages = this.sentMessages;
- }
-
- // Simulate connection opening
- setTimeout(() => {
- this.readyState = 1; // OPEN
- const openEvent = new Event('open');
- this.onopen?.(openEvent);
- this.dispatchEvent(openEvent);
-
- // Log connection opened
- console.log('[MockWebSocket] Connection opened:', url);
- }, this.config.responseDelay || 100);
- }
-
- send(data: any): void {
- if (this.readyState !== 1) {
- throw new Error('WebSocket is not open');
- }
-
- this.sentMessages.push(data);
- console.log('[MockWebSocket] Message sent:', data);
-
- // Auto-respond with echo if configured
- if (this.config.autoRespond) {
- setTimeout(() => {
- this.receiveMessage(data);
- }, this.config.responseDelay || 50);
- }
-
- // Check for mock responses
- if (this.config.mockResponses) {
- const messageStr = typeof data === 'string' ? data : JSON.stringify(data);
- for (const [pattern, response] of this.config.mockResponses) {
- if (messageStr.includes(pattern)) {
- setTimeout(() => {
- this.receiveMessage(response);
- }, this.config.responseDelay || 50);
- break;
- }
- }
- }
- }
-
- receiveMessage(data: any): void {
- const messageEvent = new MessageEvent('message', { data });
- this.onmessage?.(messageEvent);
- this.dispatchEvent(messageEvent);
- console.log('[MockWebSocket] Message received:', data);
- }
-
- close(code: number = 1000, reason?: string): void {
- if (this.readyState === 2 || this.readyState === 3) {
- return; // Already closing or closed
- }
-
- this.readyState = 2; // CLOSING
-
- setTimeout(() => {
- this.readyState = 3; // CLOSED
- const closeEvent = new CloseEvent('close', {
- code,
- reason,
- wasClean: true,
- });
- this.onclose?.(closeEvent);
- this.dispatchEvent(closeEvent);
- console.log('[MockWebSocket] Connection closed:', code, reason);
- }, 10);
- }
-
- addEventListener(type: string, listener: EventListener): void {
- if (!this.listeners.has(type)) {
- this.listeners.set(type, new Set());
- }
- this.listeners.get(type)!.add(listener);
- }
-
- removeEventListener(type: string, listener: EventListener): void {
- this.listeners.get(type)?.delete(listener);
- }
-
- dispatchEvent(event: Event): boolean {
- const listeners = this.listeners.get(event.type);
- if (listeners) {
- listeners.forEach(listener => listener(event));
- }
- return true;
- }
-}
-
-// WebSocket mock management functions
-export function setupWebSocketMock(config?: Partial): void {
- const defaultConfig: MockWebSocketConfig = {
- enabled: true,
- autoRespond: false,
- responseDelay: 100,
- mockResponses: new Map(),
- ...config,
- };
-
- if (!defaultConfig.enabled) {
- console.log('[MockWebSocket] Mocking disabled');
- return;
- }
-
- // Store original WebSocket
- const OriginalWebSocket = window.WebSocket;
- (window as any).__OriginalWebSocket = OriginalWebSocket;
-
- // Replace WebSocket with mock
- (window as any).WebSocket = function(url: string, protocols?: string | string[]) {
- // Check if URL matches the pattern to mock
- if (defaultConfig.url) {
- const shouldMock = typeof defaultConfig.url === 'string'
- ? url.includes(defaultConfig.url)
- : defaultConfig.url.test(url);
-
- if (!shouldMock) {
- console.log('[MockWebSocket] URL not matched, using real WebSocket:', url);
- return new OriginalWebSocket(url, protocols);
- }
- }
-
- console.log('[MockWebSocket] Creating mock WebSocket for:', url);
- return new MockWebSocket(url, protocols, defaultConfig);
- };
-
- // Copy static properties
- Object.setPrototypeOf(window.WebSocket, OriginalWebSocket);
- Object.keys(OriginalWebSocket).forEach(key => {
- (window.WebSocket as any)[key] = (OriginalWebSocket as any)[key];
- });
-
- console.log('[MockWebSocket] WebSocket mocking enabled');
-}
-
-export function restoreWebSocket(): void {
- if ((window as any).__OriginalWebSocket) {
- window.WebSocket = (window as any).__OriginalWebSocket;
- delete (window as any).__OriginalWebSocket;
- delete (window as any).__mockWebSocket;
- delete (window as any).__mockWebSocketMessages;
- console.log('[MockWebSocket] WebSocket restored to original');
- }
-}
-
-export function getMockWebSocketInstance(): MockWebSocketInstance | null {
- return (window as any).__mockWebSocket || null;
-}
-
-export function getMockWebSocketMessages(): any[] {
- return (window as any).__mockWebSocketMessages || [];
-}
-
-// Cypress commands
-declare global {
- namespace Cypress {
- interface Chainable {
- mockWebSocket(config?: Partial): Chainable;
- restoreWebSocket(): Chainable;
- getWebSocketMessages(): Chainable;
- sendWebSocketMessage(data: any): Chainable;
- waitForWebSocketMessage(predicate?: (msg: any) => boolean, timeout?: number): Chainable;
- }
- }
-}
-
-// Add Cypress commands
-if ((window as any).Cypress) {
- Cypress.Commands.add('mockWebSocket', (config?: Partial) => {
- cy.window().then((win) => {
- (win as any).setupWebSocketMock = setupWebSocketMock;
- (win as any).setupWebSocketMock(config);
- });
- });
-
- Cypress.Commands.add('restoreWebSocket', () => {
- cy.window().then((win) => {
- (win as any).restoreWebSocket = restoreWebSocket;
- (win as any).restoreWebSocket();
- });
- });
-
- Cypress.Commands.add('getWebSocketMessages', () => {
- cy.window().then((win) => {
- return (win as any).__mockWebSocketMessages || [];
- });
- });
-
- Cypress.Commands.add('sendWebSocketMessage', (data: any) => {
- cy.window().then((win) => {
- const mockWs = (win as any).__mockWebSocket;
- if (mockWs) {
- mockWs.receiveMessage(data);
- } else {
- throw new Error('No mock WebSocket instance found');
- }
- });
- });
-
- Cypress.Commands.add('waitForWebSocketMessage', (predicate?: (msg: any) => boolean, timeout = 5000) => {
- return cy.window().then((win) => {
- return new Cypress.Promise((resolve) => {
- const startTime = Date.now();
- const checkMessages = () => {
- const messages = (win as any).__mockWebSocketMessages || [];
- const foundMessage = predicate
- ? messages.find(predicate)
- : messages[messages.length - 1];
-
- if (foundMessage) {
- resolve(foundMessage);
- } else if (Date.now() - startTime > timeout) {
- throw new Error('Timeout waiting for WebSocket message');
- } else {
- setTimeout(checkMessages, 100);
- }
- };
- checkMessages();
- });
- });
- });
-}
\ No newline at end of file