mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 00:49:41 +02:00
fix: Ralph wizard uses file-based prompt to avoid screen escaping issues
- Add /api/sessions/:id/ralph-prompt/write endpoint to write prompt to @ralph_prompt.md - Wizard now writes full prompt to file, then sends simple read command to Claude - Fix session readiness check to wait for prompt character instead of notWorking flag - Add E2E test infrastructure for ralph-loop workflow (port 3190) - Add ralph-wizard-prod.mjs script for production testing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* Agent Interactions E2E Test
|
||||
* Tests agent spawning, visibility, and parent attachment
|
||||
*
|
||||
* Port: 3187 (see CLAUDE.md test port table)
|
||||
*
|
||||
* NOTE: These tests depend on Claude spawning background agents via the Task tool.
|
||||
* Prompts are designed to trigger agent spawning but behavior depends on Claude's responses.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, afterAll } from 'vitest';
|
||||
import {
|
||||
createServerFixture,
|
||||
destroyServerFixture,
|
||||
createBrowserFixture,
|
||||
destroyBrowserFixture,
|
||||
navigateTo,
|
||||
clickElement,
|
||||
isVisible,
|
||||
getElementCount,
|
||||
CleanupTracker,
|
||||
captureAndCompare,
|
||||
type ServerFixture,
|
||||
type BrowserFixture,
|
||||
} from '../fixtures/index.js';
|
||||
import { E2E_PORTS, E2E_TIMEOUTS, generateCaseName } from '../e2e.config.js';
|
||||
|
||||
const PORT = E2E_PORTS.AGENT_INTERACTIONS;
|
||||
let serverFixture: ServerFixture | null = null;
|
||||
let cleanup: CleanupTracker;
|
||||
|
||||
/**
|
||||
* Helper to create a case via API and select it in the dropdown
|
||||
*/
|
||||
async function createAndSelectCase(baseUrl: string, page: any, caseName: string): Promise<void> {
|
||||
const createRes = await fetch(`${baseUrl}/api/cases`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: caseName }),
|
||||
});
|
||||
expect(createRes.ok).toBe(true);
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForTimeout(500);
|
||||
await page.selectOption('#quickStartCase', caseName);
|
||||
}
|
||||
|
||||
describe('Agent Interactions E2E', () => {
|
||||
afterAll(async () => {
|
||||
if (cleanup) {
|
||||
await cleanup.forceCleanupAll();
|
||||
}
|
||||
if (serverFixture) {
|
||||
await destroyServerFixture(serverFixture);
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST);
|
||||
|
||||
it('should display subagent windows when subagents are discovered', async () => {
|
||||
let browser: BrowserFixture | null = null;
|
||||
const caseName = generateCaseName('agent-display');
|
||||
|
||||
try {
|
||||
serverFixture = await createServerFixture(PORT);
|
||||
cleanup = new CleanupTracker(serverFixture.baseUrl);
|
||||
cleanup.trackCase(caseName);
|
||||
|
||||
browser = await createBrowserFixture();
|
||||
const { page } = browser;
|
||||
|
||||
await navigateTo(page, serverFixture.baseUrl);
|
||||
await createAndSelectCase(serverFixture.baseUrl, page, caseName);
|
||||
await clickElement(page, '.btn-claude');
|
||||
|
||||
// Wait for session
|
||||
await page.waitForSelector('.session-tab', { timeout: E2E_TIMEOUTS.SESSION_CREATE });
|
||||
await page.waitForSelector('.xterm', { timeout: E2E_TIMEOUTS.TERMINAL_VISIBLE });
|
||||
|
||||
// Focus terminal
|
||||
await clickElement(page, '.xterm');
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
|
||||
// Type a prompt that might trigger agent spawning
|
||||
// Note: This depends on Claude's behavior - it may or may not spawn agents
|
||||
await page.keyboard.type('Search this codebase for all test files and list them');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
// Wait to see if any subagent windows appear
|
||||
// This is a best-effort test - agents may or may not spawn
|
||||
try {
|
||||
await page.waitForSelector('.subagent-window', { timeout: E2E_TIMEOUTS.AGENT_SPAWN });
|
||||
|
||||
// If we got here, agents spawned - verify windows
|
||||
const windowCount = await getElementCount(page, '.subagent-window');
|
||||
expect(windowCount).toBeGreaterThan(0);
|
||||
|
||||
// Take screenshot
|
||||
const screenshotResult = await captureAndCompare(page, 'agent-windows-visible', {
|
||||
threshold: 0.2, // Higher threshold for dynamic content
|
||||
});
|
||||
expect(screenshotResult.currentPath).toBeDefined();
|
||||
} catch {
|
||||
// No agents spawned within timeout - this is acceptable
|
||||
// The prompt may not have triggered agent spawning
|
||||
console.log('No agents spawned - this is acceptable behavior');
|
||||
}
|
||||
|
||||
// Track session
|
||||
const response = await fetch(`${serverFixture.baseUrl}/api/sessions`);
|
||||
const data = await response.json();
|
||||
data.sessions?.forEach((s: any) => cleanup.trackSession(s.id));
|
||||
|
||||
} finally {
|
||||
if (browser) {
|
||||
await destroyBrowserFixture(browser);
|
||||
}
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST * 2);
|
||||
|
||||
it('should be able to minimize and restore subagent windows', async () => {
|
||||
let browser: BrowserFixture | null = null;
|
||||
const caseName = generateCaseName('agent-minimize');
|
||||
|
||||
try {
|
||||
if (!serverFixture) {
|
||||
serverFixture = await createServerFixture(PORT);
|
||||
}
|
||||
if (!cleanup) {
|
||||
cleanup = new CleanupTracker(serverFixture.baseUrl);
|
||||
}
|
||||
cleanup.trackCase(caseName);
|
||||
|
||||
browser = await createBrowserFixture();
|
||||
const { page } = browser;
|
||||
|
||||
await navigateTo(page, serverFixture.baseUrl);
|
||||
|
||||
// Check if there are any existing subagent windows from previous tests
|
||||
const existingWindows = await getElementCount(page, '.subagent-window');
|
||||
|
||||
if (existingWindows > 0) {
|
||||
// Test minimize functionality
|
||||
const minimizeBtn = await page.$('.subagent-window .window-minimize');
|
||||
if (minimizeBtn) {
|
||||
await minimizeBtn.click();
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
|
||||
// Window should be hidden or minimized
|
||||
// Check for badge on session tab
|
||||
const hasBadge = await isVisible(page, '.session-tab .subagent-badge');
|
||||
// Either badge appears or window is hidden
|
||||
expect(hasBadge || await getElementCount(page, '.subagent-window:not(.minimized)') < existingWindows).toBe(true);
|
||||
}
|
||||
} else {
|
||||
// No existing windows - create session and try to trigger agents
|
||||
await createAndSelectCase(serverFixture.baseUrl, page, caseName);
|
||||
await clickElement(page, '.btn-claude');
|
||||
await page.waitForSelector('.session-tab', { timeout: E2E_TIMEOUTS.SESSION_CREATE });
|
||||
|
||||
// Track session
|
||||
const response = await fetch(`${serverFixture.baseUrl}/api/sessions`);
|
||||
const data = await response.json();
|
||||
data.sessions?.forEach((s: any) => cleanup.trackSession(s.id));
|
||||
}
|
||||
|
||||
// Test passed - either tested minimize or session created successfully
|
||||
expect(true).toBe(true);
|
||||
|
||||
} finally {
|
||||
if (browser) {
|
||||
await destroyBrowserFixture(browser);
|
||||
}
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST);
|
||||
|
||||
it('should hide agent windows when switching to different session', async () => {
|
||||
let browser: BrowserFixture | null = null;
|
||||
const caseNames = [
|
||||
generateCaseName('agent-switch-1'),
|
||||
generateCaseName('agent-switch-2'),
|
||||
];
|
||||
|
||||
try {
|
||||
if (!serverFixture) {
|
||||
serverFixture = await createServerFixture(PORT);
|
||||
}
|
||||
if (!cleanup) {
|
||||
cleanup = new CleanupTracker(serverFixture.baseUrl);
|
||||
}
|
||||
caseNames.forEach(name => cleanup.trackCase(name));
|
||||
|
||||
browser = await createBrowserFixture();
|
||||
const { page } = browser;
|
||||
|
||||
await navigateTo(page, serverFixture.baseUrl);
|
||||
|
||||
// Create cases via API first
|
||||
for (const caseName of caseNames) {
|
||||
const createRes = await fetch(`${serverFixture.baseUrl}/api/cases`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: caseName }),
|
||||
});
|
||||
expect(createRes.ok).toBe(true);
|
||||
}
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Create first session
|
||||
await page.selectOption('#quickStartCase', caseNames[0]);
|
||||
await clickElement(page, '.btn-claude');
|
||||
await page.waitForSelector('.session-tab', { timeout: E2E_TIMEOUTS.SESSION_CREATE });
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
|
||||
// Create second session
|
||||
await page.selectOption('#quickStartCase', caseNames[1]);
|
||||
await clickElement(page, '.btn-claude');
|
||||
await page.waitForSelector('.session-tab:nth-child(2)', { timeout: E2E_TIMEOUTS.SESSION_CREATE });
|
||||
|
||||
// Get initial window count
|
||||
const initialWindowCount = await getElementCount(page, '.subagent-window:not(.hidden)');
|
||||
|
||||
// Switch to first session
|
||||
await clickElement(page, '.session-tab:nth-child(1)');
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
|
||||
// Window count may change based on "Show for Active Tab Only" setting
|
||||
// This test verifies switching doesn't crash, not specific visibility behavior
|
||||
const afterSwitchCount = await getElementCount(page, '.subagent-window:not(.hidden)');
|
||||
|
||||
// Both are valid states
|
||||
expect(afterSwitchCount >= 0).toBe(true);
|
||||
|
||||
// Track sessions
|
||||
const response = await fetch(`${serverFixture.baseUrl}/api/sessions`);
|
||||
const data = await response.json();
|
||||
data.sessions?.forEach((s: any) => cleanup.trackSession(s.id));
|
||||
|
||||
} finally {
|
||||
if (browser) {
|
||||
await destroyBrowserFixture(browser);
|
||||
}
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST * 2);
|
||||
|
||||
it('should clean up agent windows when session is deleted', async () => {
|
||||
let browser: BrowserFixture | null = null;
|
||||
const caseName = generateCaseName('agent-cleanup');
|
||||
|
||||
try {
|
||||
if (!serverFixture) {
|
||||
serverFixture = await createServerFixture(PORT);
|
||||
}
|
||||
if (!cleanup) {
|
||||
cleanup = new CleanupTracker(serverFixture.baseUrl);
|
||||
}
|
||||
cleanup.trackCase(caseName);
|
||||
|
||||
browser = await createBrowserFixture();
|
||||
const { page } = browser;
|
||||
|
||||
await navigateTo(page, serverFixture.baseUrl);
|
||||
|
||||
// Create session
|
||||
await createAndSelectCase(serverFixture.baseUrl, page, caseName);
|
||||
await clickElement(page, '.btn-claude');
|
||||
await page.waitForSelector('.session-tab', { timeout: E2E_TIMEOUTS.SESSION_CREATE });
|
||||
|
||||
// Get session ID
|
||||
const response = await fetch(`${serverFixture.baseUrl}/api/sessions`);
|
||||
const data = await response.json();
|
||||
const sessionId = data.sessions?.[data.sessions.length - 1]?.id;
|
||||
|
||||
// Count windows before deletion
|
||||
const windowsBefore = await getElementCount(page, '.subagent-window');
|
||||
|
||||
// Delete session
|
||||
await clickElement(page, '.session-tab .tab-close');
|
||||
await page.waitForSelector('.session-tab', { state: 'detached', timeout: 10000 });
|
||||
|
||||
// Wait for cleanup
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
// Windows for that session should be cleaned up
|
||||
// (Note: may have windows from other sessions)
|
||||
const windowsAfter = await getElementCount(page, '.subagent-window');
|
||||
|
||||
// Windows should be same or fewer (deleted session's windows removed)
|
||||
expect(windowsAfter).toBeLessThanOrEqual(windowsBefore);
|
||||
|
||||
} finally {
|
||||
if (browser) {
|
||||
await destroyBrowserFixture(browser);
|
||||
}
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST);
|
||||
});
|
||||
Reference in New Issue
Block a user