import { Page, WebSocketRoute } from '@playwright/test' // Mock data export const mockPatterns = [ { path: 'patterns/star.thr', name: 'star.thr', category: 'geometric', date_modified: Date.now(), coordinates_count: 150 }, { path: 'patterns/spiral.thr', name: 'spiral.thr', category: 'organic', date_modified: Date.now(), coordinates_count: 200 }, { path: 'patterns/wave.thr', name: 'wave.thr', category: 'organic', date_modified: Date.now(), coordinates_count: 175 }, ] export const mockPlaylists = { default: ['patterns/star.thr', 'patterns/spiral.thr'], favorites: ['patterns/star.thr'], } // Mutable status for simulating playback let currentStatus = { is_running: false, is_paused: false, current_file: null as string | null, speed: 100, progress: 0, playlist_mode: false, playlist_name: null as string | null, queue: [] as string[], connection_status: 'connected', theta: 0, rho: 0.5, } export function resetMockStatus() { currentStatus = { is_running: false, is_paused: false, current_file: null, speed: 100, progress: 0, playlist_mode: false, playlist_name: null, queue: [], connection_status: 'connected', theta: 0, rho: 0.5, } } export async function setupApiMocks(page: Page) { // Pattern endpoints await page.route('**/list_theta_rho_files_with_metadata', async route => { await route.fulfill({ json: mockPatterns }) }) await page.route('**/list_theta_rho_files', async route => { await route.fulfill({ json: mockPatterns.map(p => ({ name: p.name, path: p.path })) }) }) await page.route('**/preview_thr_batch', async route => { const request = route.request() const body = request.postDataJSON() as { files: string[] } const previews: Record = {} for (const file of body?.files || []) { previews[file] = { image_data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', first_coordinate: { x: 0, y: 0 }, last_coordinate: { x: 100, y: 100 }, } } await route.fulfill({ json: previews }) }) await page.route('**/run_theta_rho', async route => { const body = route.request().postDataJSON() as { file_name?: string; file?: string } const file = body?.file_name || body?.file || null currentStatus.is_running = true currentStatus.current_file = file await route.fulfill({ json: { success: true } }) }) // Playlist endpoints await page.route('**/list_all_playlists', async route => { await route.fulfill({ json: Object.keys(mockPlaylists) }) }) await page.route('**/get_playlist**', async route => { const url = new URL(route.request().url()) const name = url.searchParams.get('name') || '' await route.fulfill({ json: { name, files: mockPlaylists[name as keyof typeof mockPlaylists] || [] } }) }) await page.route('**/run_playlist', async route => { const body = route.request().postDataJSON() as { playlist_name?: string; name?: string } // Support both playlist_name (actual API) and name (legacy) const playlistName = body?.playlist_name || body?.name const playlist = mockPlaylists[playlistName as keyof typeof mockPlaylists] if (playlist && playlist.length > 0) { currentStatus.is_running = true currentStatus.playlist_mode = true currentStatus.playlist_name = playlistName || null currentStatus.current_file = playlist[0] currentStatus.queue = playlist.slice(1) } await route.fulfill({ json: { success: true } }) }) // Playback control endpoints await page.route('**/pause_execution', async route => { currentStatus.is_paused = true await route.fulfill({ json: { success: true } }) }) await page.route('**/resume_execution', async route => { currentStatus.is_paused = false await route.fulfill({ json: { success: true } }) }) await page.route('**/stop_execution', async route => { currentStatus.is_running = false currentStatus.is_paused = false currentStatus.current_file = null currentStatus.playlist_mode = false currentStatus.queue = [] await route.fulfill({ json: { success: true } }) }) // Status endpoint await page.route('**/serial_status', async route => { await route.fulfill({ json: currentStatus }) }) // Table info (for TableContext) await page.route('**/api/table-info', async route => { await route.fulfill({ json: { id: 'test-table', name: 'Test Table', version: '1.0.0' } }) }) // Settings await page.route('**/api/settings', async route => { await route.fulfill({ json: { app: { name: 'Dune Weaver' } } }) }) // Known tables await page.route('**/api/known-tables', async route => { await route.fulfill({ json: { tables: [] } }) }) // Pattern history await page.route('**/api/pattern_history_all', async route => { await route.fulfill({ json: {} }) }) // Logs endpoint await page.route('**/api/logs**', async route => { await route.fulfill({ json: { logs: [], total: 0, has_more: false } }) }) // Pattern history (individual) await page.route('**/api/pattern_history/**', async route => { await route.fulfill({ json: { actual_time_formatted: null, speed: null } }) }) // Serial ports await page.route('**/list_serial_ports', async route => { await route.fulfill({ json: [] }) }) // Static files - return 200 with placeholder await page.route('**/static/**', async route => { // Return a 1x1 transparent PNG for images const base64Png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' await route.fulfill({ status: 200, contentType: 'image/png', body: Buffer.from(base64Png, 'base64'), }) }) // WebSocket mocking - critical for bypassing the "Connecting to Backend" overlay // The Layout component shows a blocking overlay until WebSocket connects await page.routeWebSocket('**/ws/status', (ws: WebSocketRoute) => { // Don't connect to server - we're mocking everything // Send status updates to simulate backend status messages const statusMessage = JSON.stringify({ type: 'status_update', data: { ...currentStatus, connection_status: true, is_homing: false, } }) // Send initial status immediately after connection // The client's onopen handler will fire, setting isBackendConnected = true setTimeout(() => { ws.send(statusMessage) }, 100) // Send periodic updates const interval = setInterval(() => { ws.send(statusMessage) }, 1000) ws.onClose(() => { clearInterval(interval) }) }) // Mock other WebSocket endpoints await page.routeWebSocket('**/ws/logs', (_ws: WebSocketRoute) => { // Just accept the connection - don't need to send anything }) await page.routeWebSocket('**/ws/cache-progress', (ws: WebSocketRoute) => { // Send "not running" status setTimeout(() => { ws.send(JSON.stringify({ type: 'cache_progress', data: { is_running: false, stage: 'idle' } })) }, 100) }) } export function getMockStatus() { return { ...currentStatus } } export function setMockStatus(updates: Partial) { Object.assign(currentStatus, updates) }