import { test, expect } from '@playwright/test'; test.describe('OpenCode File Workflow', () => { test('create, read, and delete file without permission prompts', async ({ page }) => { // Increase timeout for this test since it involves multiple AI interactions test.setTimeout(180000); // Enable console logging for debugging page.on('console', (msg) => { console.log(`[Browser ${msg.type()}]`, msg.text()); }); // Log WebSocket frames for debugging page.on('websocket', (ws) => { console.log(`[WebSocket] Connected to ${ws.url()}`); ws.on('framesent', (frame) => console.log(`[WS Sent]`, frame.payload)); ws.on('framereceived', (frame) => console.log(`[WS Received]`, frame.payload)); }); // 1. Navigate to homepage await page.goto('/'); await expect(page).toHaveTitle(/Spiceflow/i); // 2. Click the + button to open new session menu const createButton = page.locator('button[title="New Session"]'); await expect(createButton).toBeVisible(); await createButton.click(); // 3. Select OpenCode from the dropdown const opencodeOption = page.locator('button:has-text("OpenCode")'); await expect(opencodeOption).toBeVisible(); await opencodeOption.click(); // 4. Wait for navigation to session page await page.waitForURL(/\/session\/.+/); console.log('[Test] Navigated to session page:', page.url()); // 5. Wait for the page to load await expect(page.locator('text=Loading')).not.toBeVisible({ timeout: 5000 }); await expect(page.locator('text=No messages yet')).toBeVisible(); const textarea = page.locator('textarea'); const sendButton = page.locator('button[type="submit"]'); const bouncingDots = page.locator('.animate-bounce'); // Only look for pulsing cursor inside markdown-content (not the header status indicator) const pulsingCursor = page.locator('.markdown-content .animate-pulse'); // Messages with .markdown-content are rendered assistant/user messages const messagesWithContent = page.locator('.rounded-lg.border').filter({ has: page.locator('.markdown-content') }); // Helper to wait for streaming to complete const waitForStreamingComplete = async () => { await expect(bouncingDots).toHaveCount(0, { timeout: 60000 }); await expect(pulsingCursor).toHaveCount(0, { timeout: 60000 }); }; // ============================================================ // STEP 1: Create a file // ============================================================ console.log('[Test] Step 1: Creating file'); await expect(textarea).toBeVisible(); await textarea.fill( 'Create a file called test-opencode.md with the content "Hello from OpenCode test". Just create the file, no other commentary.' ); await expect(sendButton).toBeEnabled(); await sendButton.click(); // Verify user message appears await expect(page.locator('text=Create a file called test-opencode.md')).toBeVisible(); console.log('[Test] User message displayed'); // Verify thinking indicator appears (bouncing dots) await expect(bouncingDots.first()).toBeVisible({ timeout: 2000 }); console.log('[Test] Thinking indicator appeared'); // OpenCode should NOT show permission request - it auto-approves // Wait a moment to ensure no permission UI appears await page.waitForTimeout(2000); const permissionUI = page.locator('text=needs permission'); await expect(permissionUI).not.toBeVisible(); console.log('[Test] Confirmed no permission prompt for file creation'); // Wait for streaming to complete await waitForStreamingComplete(); console.log('[Test] Step 1 complete: File created'); // ============================================================ // STEP 2: Read the file // ============================================================ console.log('[Test] Step 2: Reading file'); const messageCountAfterCreate = await messagesWithContent.count(); await textarea.fill( 'Read the contents of test-opencode.md and tell me exactly what it says.' ); await sendButton.click(); // Wait for new assistant message await expect(messagesWithContent).toHaveCount(messageCountAfterCreate + 1, { timeout: 60000 }); console.log('[Test] New assistant message appeared for read'); // Wait for streaming to complete await waitForStreamingComplete(); // Verify the response contains the file content const readResponseMessage = messagesWithContent.last(); const readResponseText = await readResponseMessage.locator('.markdown-content').textContent(); console.log('[Test] OpenCode read back:', readResponseText); expect(readResponseText).toBeTruthy(); expect(readResponseText).toContain('Hello from OpenCode test'); console.log('[Test] Step 2 complete: File content verified'); // ============================================================ // STEP 3: Delete the file // ============================================================ console.log('[Test] Step 3: Deleting file'); const messageCountAfterRead = await messagesWithContent.count(); await textarea.fill( 'Delete the file test-opencode.md. Confirm when done.' ); await sendButton.click(); // Wait for new assistant message await expect(messagesWithContent).toHaveCount(messageCountAfterRead + 1, { timeout: 60000 }); console.log('[Test] New assistant message appeared for delete'); // OpenCode should NOT show permission request for delete either await page.waitForTimeout(1000); await expect(permissionUI).not.toBeVisible(); console.log('[Test] Confirmed no permission prompt for file deletion'); // Wait for streaming to complete await waitForStreamingComplete(); // Verify delete confirmation const deleteResponseMessage = messagesWithContent.last(); const deleteResponseText = await deleteResponseMessage.locator('.markdown-content').textContent(); console.log('[Test] OpenCode delete response:', deleteResponseText); expect(deleteResponseText).toBeTruthy(); // Response should indicate the file was deleted (various phrasings possible) expect(deleteResponseText!.toLowerCase()).toMatch(/delet|remov|done|success/); console.log('[Test] Step 3 complete: File deleted'); // ============================================================ // STEP 4: Verify file is gone // ============================================================ console.log('[Test] Step 4: Verifying file no longer exists'); const messageCountAfterDelete = await messagesWithContent.count(); await textarea.fill( 'Try to read test-opencode.md again. Does it exist?' ); await sendButton.click(); // Wait for new assistant message await expect(messagesWithContent).toHaveCount(messageCountAfterDelete + 1, { timeout: 60000 }); // Wait for streaming to complete await waitForStreamingComplete(); // Verify the response indicates file doesn't exist const verifyResponseMessage = messagesWithContent.last(); const verifyResponseText = await verifyResponseMessage.locator('.markdown-content').textContent(); console.log('[Test] OpenCode verify response:', verifyResponseText); expect(verifyResponseText).toBeTruthy(); // Response should indicate file doesn't exist expect(verifyResponseText!.toLowerCase()).toMatch(/not exist|not found|no such|doesn't exist|does not exist|cannot find|can't find/); console.log('[Test] Step 4 complete: Confirmed file no longer exists'); console.log('[Test] All steps completed successfully!'); }); });