mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +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,223 @@
|
||||
/**
|
||||
* Quick Start E2E Test - THE critical test
|
||||
* Tests the complete flow: click button -> case created -> session created -> screen created -> terminal visible
|
||||
*
|
||||
* Port: 3183 (see CLAUDE.md test port table)
|
||||
*/
|
||||
|
||||
import { describe, it, expect, afterAll } from 'vitest';
|
||||
import { execSync } from 'node:child_process';
|
||||
import {
|
||||
createServerFixture,
|
||||
destroyServerFixture,
|
||||
createBrowserFixture,
|
||||
destroyBrowserFixture,
|
||||
navigateTo,
|
||||
waitForVisible,
|
||||
clickElement,
|
||||
getText,
|
||||
isVisible,
|
||||
CleanupTracker,
|
||||
captureAndCompare,
|
||||
type ServerFixture,
|
||||
type BrowserFixture,
|
||||
} from '../fixtures/index.js';
|
||||
import { E2E_PORTS, E2E_TIMEOUTS, generateCaseName } from '../e2e.config.js';
|
||||
|
||||
const PORT = E2E_PORTS.QUICK_START;
|
||||
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> {
|
||||
// Create case via API
|
||||
const createRes = await fetch(`${baseUrl}/api/cases`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: caseName }),
|
||||
});
|
||||
expect(createRes.ok).toBe(true);
|
||||
|
||||
// Refresh page to get updated case list
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Select the case in dropdown
|
||||
await page.selectOption('#quickStartCase', caseName);
|
||||
}
|
||||
|
||||
describe('Quick Start E2E', () => {
|
||||
afterAll(async () => {
|
||||
if (cleanup) {
|
||||
await cleanup.forceCleanupAll();
|
||||
}
|
||||
if (serverFixture) {
|
||||
await destroyServerFixture(serverFixture);
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST);
|
||||
|
||||
it('should create session with real screen via quick-start button', async () => {
|
||||
let browser: BrowserFixture | null = null;
|
||||
const caseName = generateCaseName('quickstart');
|
||||
|
||||
try {
|
||||
// Start server
|
||||
serverFixture = await createServerFixture(PORT);
|
||||
cleanup = new CleanupTracker(serverFixture.baseUrl);
|
||||
cleanup.trackCase(caseName);
|
||||
|
||||
// Launch browser
|
||||
browser = await createBrowserFixture();
|
||||
const { page } = browser;
|
||||
|
||||
// Navigate to Claudeman
|
||||
await navigateTo(page, serverFixture.baseUrl);
|
||||
|
||||
// Verify page loaded
|
||||
const title = await page.title();
|
||||
expect(title).toBe('Claudeman');
|
||||
|
||||
// Create case via API and select it
|
||||
await createAndSelectCase(serverFixture.baseUrl, page, caseName);
|
||||
|
||||
// Click the Claude button to create session
|
||||
await clickElement(page, '.btn-claude');
|
||||
|
||||
// Wait for session tab to appear (this is the critical moment)
|
||||
await page.waitForSelector('.session-tab', { timeout: E2E_TIMEOUTS.SESSION_CREATE });
|
||||
|
||||
// Verify tab is active
|
||||
expect(await isVisible(page, '.session-tab.active')).toBe(true);
|
||||
|
||||
// Get session ID from API
|
||||
const response = await fetch(`${serverFixture.baseUrl}/api/sessions`);
|
||||
const data = await response.json();
|
||||
const session = data.sessions?.find((s: any) => s.workingDir?.includes(caseName));
|
||||
expect(session).toBeDefined();
|
||||
cleanup.trackSession(session.id);
|
||||
|
||||
// CRITICAL: Verify a real screen was created
|
||||
// Screen names are claudeman-{sessionId.slice(0,8)}
|
||||
// This is what would have caught the cpulimit bug
|
||||
const screenList = execSync('screen -ls 2>/dev/null || true', {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
});
|
||||
const sessionIdPrefix = session.id.slice(0, 8);
|
||||
const screenMatch = screenList.includes(`claudeman-${sessionIdPrefix}`);
|
||||
expect(screenMatch).toBe(true);
|
||||
|
||||
// Track the screen for cleanup
|
||||
cleanup.trackScreen(`claudeman-${sessionIdPrefix}`);
|
||||
|
||||
// Wait for terminal to be visible
|
||||
await page.waitForSelector('.xterm', { timeout: E2E_TIMEOUTS.TERMINAL_VISIBLE });
|
||||
expect(await isVisible(page, '.xterm')).toBe(true);
|
||||
|
||||
// Take screenshot for visual regression
|
||||
const screenshotResult = await captureAndCompare(page, 'quick-start-session-created', {
|
||||
mask: ['.xterm-screen'], // Mask terminal content as it varies
|
||||
});
|
||||
expect(screenshotResult.passed).toBe(true);
|
||||
|
||||
} finally {
|
||||
if (browser) {
|
||||
await destroyBrowserFixture(browser);
|
||||
}
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST);
|
||||
|
||||
it('should show screen exists after session creation', async () => {
|
||||
let browser: BrowserFixture | null = null;
|
||||
const caseName = generateCaseName('screen-verify');
|
||||
|
||||
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);
|
||||
await createAndSelectCase(serverFixture.baseUrl, page, caseName);
|
||||
await clickElement(page, '.btn-claude');
|
||||
|
||||
// Wait for session
|
||||
await page.waitForSelector('.session-tab', { timeout: E2E_TIMEOUTS.SESSION_CREATE });
|
||||
|
||||
// Get session from API
|
||||
const response = await fetch(`${serverFixture.baseUrl}/api/sessions`);
|
||||
const data = await response.json();
|
||||
expect(data.sessions?.length).toBeGreaterThan(0);
|
||||
|
||||
const session = data.sessions.find((s: any) => s.workingDir?.includes(caseName));
|
||||
expect(session).toBeDefined();
|
||||
cleanup.trackSession(session.id);
|
||||
|
||||
// Verify screen exists using cleanup tracker method
|
||||
// Screen name is claudeman-{sessionId.slice(0,8)}
|
||||
const screenName = `claudeman-${session.id.slice(0, 8)}`;
|
||||
const screenExists = cleanup.screenExists(screenName);
|
||||
expect(screenExists).toBe(true);
|
||||
|
||||
} finally {
|
||||
if (browser) {
|
||||
await destroyBrowserFixture(browser);
|
||||
}
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST);
|
||||
|
||||
it('should display tab counter correctly', async () => {
|
||||
let browser: BrowserFixture | null = null;
|
||||
const caseName = generateCaseName('tab-counter');
|
||||
|
||||
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 initial tab count (may have restored sessions from previous tests)
|
||||
const initialCountStr = await getText(page, '#tabCount');
|
||||
const initialCount = parseInt(initialCountStr, 10) || 0;
|
||||
|
||||
// Create case and session
|
||||
await createAndSelectCase(serverFixture.baseUrl, page, caseName);
|
||||
await clickElement(page, '.btn-claude');
|
||||
await page.waitForSelector('.session-tab', { timeout: E2E_TIMEOUTS.SESSION_CREATE });
|
||||
|
||||
// Check tab count increased
|
||||
await new Promise(r => setTimeout(r, 500)); // Wait for UI update
|
||||
const newCountStr = await getText(page, '#tabCount');
|
||||
const newCount = parseInt(newCountStr, 10) || 0;
|
||||
expect(newCount).toBeGreaterThan(initialCount);
|
||||
|
||||
// Track for cleanup
|
||||
const response = await fetch(`${serverFixture.baseUrl}/api/sessions`);
|
||||
const data = await response.json();
|
||||
if (data.sessions?.length > 0) {
|
||||
cleanup.trackSession(data.sessions[data.sessions.length - 1].id);
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (browser) {
|
||||
await destroyBrowserFixture(browser);
|
||||
}
|
||||
}
|
||||
}, E2E_TIMEOUTS.TEST);
|
||||
});
|
||||
Reference in New Issue
Block a user