Files
spiceflow/e2e/tests/file-workflow-opencode.spec.ts

183 lines
7.5 KiB
TypeScript

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!');
});
});