import { test, expect } from '@playwright/test'; import * as fs from 'fs'; import * as path from 'path'; // Use unique filename for each test run to avoid conflicts const TEST_FILE = `e2e-autoaccept-test-${Date.now()}.md`; const TEST_FILE_PATH = path.join(process.env.HOME || '/home/ajet', TEST_FILE); test.describe('Claude Auto-Accept Edits', () => { test.afterEach(async () => { // Cleanup: Delete the test file directly via filesystem console.log('[Cleanup] Attempting to delete test file:', TEST_FILE_PATH); try { if (fs.existsSync(TEST_FILE_PATH)) { fs.unlinkSync(TEST_FILE_PATH); console.log('[Cleanup] Deleted test file'); } else { console.log('[Cleanup] Test file does not exist'); } } catch (e) { console.log('[Cleanup] Could not clean up:', e); } }); test('auto-accept enables file operations without permission prompts', async ({ page }) => { // Increase timeout for this test since it involves real Claude interaction test.setTimeout(300000); // 5 minutes // 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 Claude Code from the dropdown const claudeOption = page.locator('button:has-text("Claude Code")'); await expect(claudeOption).toBeVisible(); await claudeOption.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(); // 6. Enable auto-accept edits via settings const settingsButton = page.locator('button[aria-label="Session settings"]'); await expect(settingsButton).toBeVisible(); await settingsButton.click(); console.log('[Test] Opened settings dropdown'); // Wait for dropdown to appear and click the auto-accept checkbox const autoAcceptLabel = page.locator('label:has-text("Auto-accept edits")'); await expect(autoAcceptLabel).toBeVisible(); const autoAcceptCheckbox = autoAcceptLabel.locator('input[type="checkbox"]'); // Listen for the API response to ensure setting is persisted const updatePromise = page.waitForResponse( (response) => response.url().includes('/api/sessions/') && response.request().method() === 'PATCH' ); await autoAcceptCheckbox.check(); const updateResponse = await updatePromise; console.log('[Test] Auto-accept update response:', updateResponse.status()); // Verify the response contains auto-accept-edits: true const responseBody = await updateResponse.json(); console.log('[Test] Updated session:', JSON.stringify(responseBody)); expect(responseBody['auto-accept-edits']).toBe(true); console.log('[Test] Verified auto-accept-edits is persisted'); // Close dropdown by clicking elsewhere await page.locator('header').click(); await expect(autoAcceptLabel).not.toBeVisible(); // 7. Send message asking Claude to CREATE a file const textarea = page.locator('textarea'); await expect(textarea).toBeVisible(); await textarea.fill( `Create a file called ${TEST_FILE} with the content "# Test File\\n\\nOriginal content for testing auto-accept.". Just create the file, no other commentary.` ); const sendButton = page.locator('button[type="submit"]'); await expect(sendButton).toBeEnabled(); await sendButton.click(); console.log('[Test] Sent create file request'); // 8. Verify user message appears await expect(page.locator(`text=Create a file called ${TEST_FILE}`)).toBeVisible(); // 9. Verify thinking indicator appears const bouncingDots = page.locator('.animate-bounce'); await expect(bouncingDots.first()).toBeVisible({ timeout: 5000 }); console.log('[Test] Thinking indicator appeared'); // 10. CRITICAL: Permission UI should NOT appear (auto-accept should handle it) // Wait a bit to give permission request time to appear if it would await page.waitForTimeout(3000); // Check that permission UI is not visible const permissionUI = page.locator('text=Claude needs permission'); const permissionVisible = await permissionUI.isVisible(); if (permissionVisible) { // If permission UI appears, the auto-accept didn't work - fail the test console.log('[Test] ERROR: Permission UI appeared when auto-accept should have handled it'); // Take screenshot for debugging await page.screenshot({ path: 'test-results/autoaccept-permission-appeared.png' }); expect(permissionVisible).toBe(false); } console.log('[Test] Confirmed: No permission UI appeared (auto-accept working)'); // 11. Wait for streaming to complete const pulsingCursor = page.locator('.markdown-content .animate-pulse'); await expect(bouncingDots).toHaveCount(0, { timeout: 120000 }); await expect(pulsingCursor).toHaveCount(0, { timeout: 120000 }); console.log('[Test] Create file streaming complete'); // 12. Check for permission message in history with "accept" status (green) // The permission message should be in the message list with status "accept" // Look for the "Permission granted" header text which indicates accepted status const acceptedPermissionHeader = page.locator('text=Permission granted'); await expect(acceptedPermissionHeader.first()).toBeVisible({ timeout: 10000 }); console.log('[Test] Found "Permission granted" header in history'); // Find the permission message container with green styling const acceptedPermission = page.locator('.rounded-lg.border.bg-green-500\\/10'); const permissionCount = await acceptedPermission.count(); console.log('[Test] Found accepted permission messages with green styling:', permissionCount); expect(permissionCount).toBeGreaterThan(0); // Verify the permission message contains Write tool and the test file const firstPermission = acceptedPermission.first(); const permissionText = await firstPermission.textContent(); console.log('[Test] Permission message content:', permissionText?.substring(0, 200)); expect(permissionText).toContain('Write'); expect(permissionText).toContain('e2e-autoaccept-test'); console.log('[Test] Verified accepted permission shows Write tool and test file'); // Verify the green checkmark icon is present const greenCheckmark = firstPermission.locator('svg.text-green-400'); await expect(greenCheckmark).toBeVisible(); console.log('[Test] Verified green checkmark icon is present'); // 13. Verify file was created by asking Claude to read it await textarea.fill(`Read the contents of ${TEST_FILE} and quote what it says.`); await sendButton.click(); console.log('[Test] Sent read file request'); // Wait for response await expect(bouncingDots.first()).toBeVisible({ timeout: 5000 }); await expect(bouncingDots).toHaveCount(0, { timeout: 60000 }); await expect(pulsingCursor).toHaveCount(0, { timeout: 60000 }); console.log('[Test] Read file streaming complete'); // Verify response contains the original content const assistantMessages = page.locator('.rounded-lg.border').filter({ has: page.locator('.markdown-content') }); const readResponse = await assistantMessages.last().locator('.markdown-content').textContent(); console.log('[Test] Read response:', readResponse?.substring(0, 200)); expect(readResponse).toContain('Original content'); console.log('[Test] Verified file was created with correct content'); // 14. EDIT the file (should also auto-accept) await textarea.fill( `Edit the file ${TEST_FILE} to change "Original content" to "UPDATED content". Just make the edit.` ); await sendButton.click(); console.log('[Test] Sent edit file request'); // Wait and verify no permission UI await expect(bouncingDots.first()).toBeVisible({ timeout: 5000 }); await page.waitForTimeout(3000); const editPermissionVisible = await permissionUI.isVisible(); if (editPermissionVisible) { console.log('[Test] ERROR: Permission UI appeared for edit when auto-accept should have handled it'); await page.screenshot({ path: 'test-results/autoaccept-edit-permission-appeared.png' }); expect(editPermissionVisible).toBe(false); } console.log('[Test] Confirmed: No permission UI for edit (auto-accept working)'); // Wait for edit to complete await expect(bouncingDots).toHaveCount(0, { timeout: 120000 }); await expect(pulsingCursor).toHaveCount(0, { timeout: 120000 }); console.log('[Test] Edit file streaming complete'); // 14b. Verify Edit permission was also auto-accepted and appears in history // Should now have at least 2 accepted permissions (Write + Edit) const acceptedPermissionCountAfterEdit = await acceptedPermission.count(); console.log('[Test] Accepted permission count after edit:', acceptedPermissionCountAfterEdit); expect(acceptedPermissionCountAfterEdit).toBeGreaterThanOrEqual(2); // Find the Edit permission message (should be the latest one) const editPermissionMessages = page.locator('.rounded-lg.border.bg-green-500\\/10').filter({ hasText: /Edit.*e2e-autoaccept-test/i }); const editPermCount = await editPermissionMessages.count(); console.log('[Test] Found Edit permission messages:', editPermCount); expect(editPermCount).toBeGreaterThan(0); console.log('[Test] Verified Edit permission was auto-accepted and appears in history'); // 15. Verify edit worked by reading file again await textarea.fill(`Read ${TEST_FILE} again and tell me what it says now.`); await sendButton.click(); console.log('[Test] Sent second read request'); await expect(bouncingDots.first()).toBeVisible({ timeout: 5000 }); await expect(bouncingDots).toHaveCount(0, { timeout: 60000 }); await expect(pulsingCursor).toHaveCount(0, { timeout: 60000 }); const editVerifyResponse = await assistantMessages.last().locator('.markdown-content').textContent(); console.log('[Test] Edit verify response:', editVerifyResponse?.substring(0, 200)); expect(editVerifyResponse).toContain('UPDATED'); console.log('[Test] Verified file was edited successfully'); // 16. Verify file exists on filesystem const fileExists = fs.existsSync(TEST_FILE_PATH); expect(fileExists).toBe(true); console.log('[Test] Verified file exists on filesystem'); // Read actual file content to double-check const fileContent = fs.readFileSync(TEST_FILE_PATH, 'utf-8'); console.log('[Test] Actual file content:', fileContent); expect(fileContent).toContain('UPDATED'); console.log('[Test] Verified file contains UPDATED content'); console.log('[Test] Auto-accept E2E test completed successfully!'); // File cleanup happens in afterEach }); });