add tmux sessions

This commit is contained in:
2026-01-20 14:04:19 -05:00
parent 2b50c91267
commit 66b4acaf42
37 changed files with 2888 additions and 327 deletions
+248
View File
@@ -0,0 +1,248 @@
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
});
});
+23
View File
@@ -1,6 +1,29 @@
import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
// Path to the test file that gets created during the test
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const TEST_FILE_PATH = path.resolve(__dirname, '../../server/test-opencode.md');
test.describe('OpenCode File Workflow', () => {
// Clean up any leftover test file before each test
test.beforeEach(async () => {
if (fs.existsSync(TEST_FILE_PATH)) {
fs.unlinkSync(TEST_FILE_PATH);
console.log('[Setup] Cleaned up leftover test file:', TEST_FILE_PATH);
}
});
// Clean up test file after each test (even on failure)
test.afterEach(async () => {
if (fs.existsSync(TEST_FILE_PATH)) {
fs.unlinkSync(TEST_FILE_PATH);
console.log('[Teardown] Cleaned up test file:', TEST_FILE_PATH);
}
});
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);
-113
View File
@@ -1,113 +0,0 @@
import { test, expect } from '@playwright/test';
import { E2E_BACKEND_URL } from '../playwright.config';
test.describe('Claude Working Directory Auto-Update', () => {
test('working directory updates automatically after cd command', async ({ page, request }) => {
// Increase timeout for this test since it involves real Claude interaction
test.setTimeout(180000);
// Enable console logging to debug issues
page.on('console', (msg) => {
console.log(`[Browser ${msg.type()}]`, msg.text());
});
// Log WebSocket frames
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));
ws.on('close', () => console.log('[WebSocket] Closed'));
});
// Log network requests to /api
page.on('request', (req) => {
if (req.url().includes('/api')) {
console.log(`[Request] ${req.method()} ${req.url()}`);
}
});
page.on('response', (response) => {
if (response.url().includes('/api')) {
console.log(`[Response] ${response.status()} ${response.url()}`);
}
});
// 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\/.+/);
const sessionUrl = page.url();
const sessionId = sessionUrl.split('/session/')[1];
console.log('[Test] Navigated to session page:', sessionUrl);
console.log('[Test] Session ID:', sessionId);
// 5. Wait for the page to load (no loading spinner)
await expect(page.locator('text=Loading')).not.toBeVisible({ timeout: 5000 });
// 6. Verify we see the empty message state
await expect(page.locator('text=No messages yet')).toBeVisible();
// 7. Send a message to Claude asking it to cd into repos (natural language)
// Claude should run the cd command and ideally output the current directory
const textarea = page.locator('textarea');
await expect(textarea).toBeVisible();
await textarea.fill('change directory to ~/repos and tell me where you are now');
// 8. Click the send button
const sendButton = page.locator('button[type="submit"]');
await expect(sendButton).toBeEnabled();
await sendButton.click();
// 9. Wait for streaming to complete
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');
await expect(bouncingDots).toHaveCount(0, { timeout: 60000 });
await expect(pulsingCursor).toHaveCount(0, { timeout: 60000 });
console.log('[Test] Message complete');
// 10. The working directory bar should now show the repos path (automatically updated)
// The working dir bar is in a specific container with bg-zinc-900/50
const workingDirBar = page.locator('div.bg-zinc-900\\/50');
await expect(workingDirBar).toBeVisible({ timeout: 10000 });
// The working dir text is in a span.truncate.font-mono inside the bar
const workingDirText = workingDirBar.locator('span.truncate.font-mono');
await expect(workingDirText).toBeVisible();
// 11. Wait for the working directory to contain 'repos' (automatic update from tool result)
await expect(workingDirText).toContainText('repos', { timeout: 10000 });
const displayedWorkingDir = await workingDirText.textContent();
console.log('[Test] Working directory in UI:', displayedWorkingDir);
expect(displayedWorkingDir).toContain('repos');
// 12. Verify the working directory in the database via API
const sessionResponse = await request.get(`${E2E_BACKEND_URL}/api/sessions/${sessionId}`);
expect(sessionResponse.ok()).toBeTruthy();
const sessionData = await sessionResponse.json();
console.log('[Test] Session data from API:', JSON.stringify(sessionData, null, 2));
// The API returns session data directly (not nested under 'session')
const dbWorkingDir = sessionData['working-dir'] || sessionData.workingDir || '';
console.log('[Test] Working directory from DB:', dbWorkingDir);
// DB should have the repos path
expect(dbWorkingDir).toContain('repos');
// UI and DB should match
expect(displayedWorkingDir).toBe(dbWorkingDir);
console.log('[Test] Auto-sync test passed - working directory automatically updated to repos path');
});
});
+224
View File
@@ -0,0 +1,224 @@
import { test, expect } from '@playwright/test';
import { E2E_BACKEND_URL } from '../playwright.config';
// Helper to clean up any existing tmux sessions via UI
async function cleanupTmuxSessions(page: import('@playwright/test').Page) {
await page.goto('/');
// Delete all tmux session cards
while (true) {
const tmuxCards = page.locator('a.card').filter({ has: page.locator('span:text-is("tmux")') });
const count = await tmuxCards.count();
if (count === 0) break;
// Set up one-time dialog handler for this deletion
page.once('dialog', async (dialog) => {
await dialog.accept();
});
const deleteButton = tmuxCards.first().locator('button[title="Delete session"]');
await deleteButton.click();
await page.waitForTimeout(500); // Wait for deletion to process
}
}
test.describe('Tmux Terminal Session', () => {
test('create tmux session and run shell commands', async ({ page }) => {
// Clean up any stale tmux sessions first
await cleanupTmuxSessions(page);
// Enable console logging for debugging
page.on('console', (msg) => {
console.log(`[Browser ${msg.type()}]`, msg.text());
});
// Log network requests to /api
page.on('request', (request) => {
if (request.url().includes('/api')) {
console.log(`[Request] ${request.method()} ${request.url()}`);
}
});
page.on('response', (response) => {
if (response.url().includes('/api')) {
console.log(`[Response] ${response.status()} ${response.url()}`);
}
});
// 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 Terminal (tmux) from the dropdown
const tmuxOption = page.locator('button:has-text("Terminal (tmux)")');
await expect(tmuxOption).toBeVisible();
await tmuxOption.click();
// 4. Wait for navigation to session page
await page.waitForURL(/\/session\/.+/);
const sessionUrl = page.url();
const sessionId = decodeURIComponent(sessionUrl.split('/session/')[1]);
console.log('[Test] Navigated to session page:', sessionUrl);
// 5. Wait for terminal view to load (should see the terminal pre element)
const terminalOutput = page.locator('pre.text-green-400');
await expect(terminalOutput).toBeVisible({ timeout: 10000 });
console.log('[Test] Terminal view loaded');
// 6. Verify the session status indicator shows active
const statusIndicator = page.locator('.bg-green-500').first();
await expect(statusIndicator).toBeVisible({ timeout: 5000 });
console.log('[Test] Session is active');
// 7. Verify the terminal badge shows "TERMINAL"
const terminalBadge = page.locator('text=TERMINAL');
await expect(terminalBadge).toBeVisible();
// 8. Find the command input
const commandInput = page.locator('input[placeholder*="Type command"]');
await expect(commandInput).toBeVisible();
await expect(commandInput).toBeEnabled();
// 9. Run `pwd` command
await commandInput.fill('pwd');
await commandInput.press('Enter');
console.log('[Test] Sent pwd command');
// 10. Wait for output and verify it contains a path (starts with /)
await page.waitForTimeout(1000); // Give time for command to execute
let terminalContent = await terminalOutput.textContent();
console.log('[Test] Terminal content after pwd:', terminalContent);
// The output should contain a path starting with /
await expect(async () => {
terminalContent = await terminalOutput.textContent();
expect(terminalContent).toMatch(/\/[a-zA-Z]/);
}).toPass({ timeout: 5000 });
console.log('[Test] pwd output verified - contains path');
// 11. Run `ls -al` command
await commandInput.fill('ls -al');
await commandInput.press('Enter');
console.log('[Test] Sent ls -al command');
// 12. Wait for output and verify it contains typical ls -al output
await page.waitForTimeout(1000); // Give time for command to execute
await expect(async () => {
terminalContent = await terminalOutput.textContent();
// ls -al output typically contains "total" at the start and permission strings like "drwx"
expect(terminalContent).toMatch(/total \d+/);
}).toPass({ timeout: 5000 });
console.log('[Test] ls -al output verified - contains "total" line');
// 13. Verify the output contains directory entries with permissions
await expect(async () => {
terminalContent = await terminalOutput.textContent();
// Should see permission patterns like drwx or -rw-
expect(terminalContent).toMatch(/[d-][rwx-]{9}/);
}).toPass({ timeout: 5000 });
console.log('[Test] ls -al output verified - contains permission strings');
// 14. Verify the output contains the . and .. directory entries
terminalContent = await terminalOutput.textContent();
// The . entry appears at end of line as " .\n" or " ." followed by newline
expect(terminalContent).toMatch(/ \.$/m);
console.log('[Test] ls -al output verified - contains current directory entry');
console.log('[Test] Tmux terminal test completed successfully');
// 15. Cleanup: Delete the session via UI
await page.goto('/');
await expect(page).toHaveTitle(/Spiceflow/i);
// Find the tmux session card and delete it
const tmuxCards = page.locator('a.card').filter({ has: page.locator('span:text-is("tmux")') });
await expect(tmuxCards).toHaveCount(1);
// Set up one-time dialog handler
page.once('dialog', async (dialog) => {
await dialog.accept();
});
const deleteButton = tmuxCards.first().locator('button[title="Delete session"]');
await deleteButton.click();
// Wait for no tmux cards to remain
await expect(tmuxCards).toHaveCount(0, { timeout: 5000 });
console.log('[Test] Cleanup: Session deleted');
});
test('deleting tmux session kills the tmux process', async ({ page, request }) => {
// Clean up any stale tmux sessions first
await cleanupTmuxSessions(page);
// 1. Navigate to homepage and create a tmux session
await page.goto('/');
await expect(page).toHaveTitle(/Spiceflow/i);
const createButton = page.locator('button[title="New Session"]');
await createButton.click();
const tmuxOption = page.locator('button:has-text("Terminal (tmux)")');
await tmuxOption.click();
// Wait for navigation to session page
await page.waitForURL(/\/session\/.+/);
const sessionUrl = page.url();
// URL decode the session ID since it contains special characters
const sessionId = decodeURIComponent(sessionUrl.split('/session/')[1]);
console.log('[Test] Created session:', sessionId);
// 2. Wait for terminal to load and session to be active
const terminalOutput = page.locator('pre.text-green-400');
await expect(terminalOutput).toBeVisible({ timeout: 10000 });
// 3. For ephemeral tmux sessions, the session ID IS the tmux session name
const tmuxSessionName = sessionId;
console.log('[Test] Tmux session name:', tmuxSessionName);
// 4. Verify the tmux session exists by running a command
const commandInput = page.locator('input[placeholder*="Type command"]');
await commandInput.fill('echo "session-alive"');
await commandInput.press('Enter');
await page.waitForTimeout(500);
// 5. Go back to home page
await page.goto('/');
await expect(page).toHaveTitle(/Spiceflow/i);
// 6. Find and delete the session by provider badge (more generic than session ID)
const tmuxCards = page.locator('a.card').filter({ has: page.locator('span:text-is("tmux")') });
await expect(tmuxCards).toHaveCount(1);
// Set up one-time dialog handler to accept the confirmation
page.once('dialog', async (dialog) => {
console.log('[Test] Dialog appeared:', dialog.message());
await dialog.accept();
});
// Click the delete button (X icon) on the session card
const deleteButton = tmuxCards.first().locator('button[title="Delete session"]');
await deleteButton.click();
// 7. Wait for session to be deleted (no more tmux cards)
await expect(tmuxCards).toHaveCount(0, { timeout: 5000 });
console.log('[Test] Session deleted from UI');
// 8. Verify the tmux session was killed by checking the API
// The session should no longer exist
const sessionResponse = await request.get(`${E2E_BACKEND_URL}/api/sessions/${sessionId}`);
expect(sessionResponse.status()).toBe(404);
console.log('[Test] Session no longer exists in API');
// 9. Verify the tmux session is no longer alive
// We can check this by trying to get terminal content - it should fail
const terminalCheckResponse = await request.get(`${E2E_BACKEND_URL}/api/sessions/${sessionId}/terminal`);
expect(terminalCheckResponse.status()).toBe(404);
console.log('[Test] Tmux session properly cleaned up');
});
});
-8
View File
@@ -92,13 +92,5 @@ test.describe('Claude Chat Workflow', () => {
console.log('[Test] Assistant response text:', responseText);
expect(responseText).toBeTruthy();
expect(responseText!.length).toBeGreaterThan(0);
// 13. Verify working directory indicator appears
// The working directory should be captured from the init event and displayed
const workingDirIndicator = page.locator('.font-mono').filter({ hasText: /^\// }).first();
await expect(workingDirIndicator).toBeVisible({ timeout: 5000 });
const workingDirText = await workingDirIndicator.textContent();
console.log('[Test] Working directory displayed:', workingDirText);
expect(workingDirText).toMatch(/^\//); // Should start with /
});
});
-8
View File
@@ -100,13 +100,5 @@ test.describe('OpenCode Chat Workflow', () => {
console.log('[Test] Assistant response text:', responseText);
expect(responseText).toBeTruthy();
expect(responseText!.length).toBeGreaterThan(0);
// 13. Verify working directory indicator appears
// The working directory should be captured from the init event and displayed
const workingDirIndicator = page.locator('.font-mono').filter({ hasText: /^\// }).first();
await expect(workingDirIndicator).toBeVisible({ timeout: 5000 });
const workingDirText = await workingDirIndicator.textContent();
console.log('[Test] Working directory displayed:', workingDirText);
expect(workingDirText).toMatch(/^\//); // Should start with /
});
});