| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- 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<string, unknown> = {}
- 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<typeof currentStatus>) {
- Object.assign(currentStatus, updates)
- }
|