/**
* Browser Connection Test
*
* Tests the complete browser connection flow:
* 1. Start MCP server (via backend instance or standalone)
* 2. Open browser to http://localhost:3000
* 3. Connect WebSocket
* 4. Run Claude CLI with tool calls
* 5. Verify UI updates appear in browser
*
* This test can run in two modes:
* - Automated: Uses headless browser or programmatic WebSocket
* - Manual: Opens real browser for visual verification
*/
import { spawn } from 'child_process';
import WebSocket from 'ws';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join, resolve } from 'path';
import { createIsolatedClaudeWithMCP } from '../../scripts/create-isolated-claude.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PORT = 3000;
const HOST = '127.0.0.1';
let serverProcess = null;
let browserWs = null;
let receivedMessages = [];
function cleanupPort(port) {
try {
const pids = execSync(`lsof -ti:${port}`, { encoding: 'utf-8' }).trim();
if (pids) {
execSync(`kill -9 ${pids}`, { stdio: 'ignore' });
return true;
}
} catch (e) {
// Port not in use
}
return false;
}
function startServer() {
return new Promise((resolve, reject) => {
console.log('π Starting MCP server...');
// Clean up any existing server on port
cleanupPort(PORT);
serverProcess = spawn('node', ['src/server-mcp.js'], {
cwd: __dirname,
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
NODE_ENV: 'test',
HOST: HOST,
PORT: PORT.toString()
}
});
let output = '';
const timeout = setTimeout(() => {
serverProcess.kill();
reject(new Error('Server start timeout'));
}, 10000);
serverProcess.stderr.on('data', (data) => {
output += data.toString();
if (output.includes('Local Imagine Server running')) {
clearTimeout(timeout);
console.log(` β
Server running at http://${HOST}:${PORT}`);
setTimeout(() => resolve(), 1000); // Give it time to fully start
}
});
serverProcess.on('error', reject);
});
}
function connectBrowser() {
return new Promise((resolve, reject) => {
console.log('π Connecting browser WebSocket...');
const wsUrl = `ws://${HOST}:${PORT}`;
browserWs = new WebSocket(wsUrl);
browserWs.on('open', () => {
console.log(' β
Browser WebSocket connected');
// Set up message listener
browserWs.on('message', (data) => {
try {
const msg = JSON.parse(data.toString());
receivedMessages.push(msg);
console.log(` π¨ Received: ${msg.type}${msg.html ? ' (UI update)' : ''}${msg.message ? ` - ${msg.message.substring(0, 50)}...` : ''}`);
} catch (e) {
// Not JSON, ignore
}
});
resolve();
});
browserWs.on('error', (error) => {
reject(new Error(`WebSocket error: ${error.message}`));
});
setTimeout(() => {
if (browserWs.readyState !== WebSocket.OPEN) {
reject(new Error('Browser connection timeout'));
}
}, 5000);
});
}
async function testToolExecution() {
console.log('\nπ§ͺ Testing tool execution...');
if (!process.env.ANTHROPIC_API_KEY) {
throw new Error('ANTHROPIC_API_KEY not set');
}
// Test 1: log_thought
console.log('\n1οΈβ£ Testing log_thought tool...');
const result1 = await createIsolatedClaudeWithMCP(
'Use mcp__imagine__log_thought to log "Hello from Claude!"',
{
model: 'sonnet',
authToken: process.env.ANTHROPIC_API_KEY
}
);
if (result1.code !== 0) {
throw new Error(`log_thought failed with code ${result1.code}`);
}
// Wait a bit for WebSocket message
await new Promise(resolve => setTimeout(resolve, 1000));
// Check if we received LOG message
const logMessage = receivedMessages.find(m => m.type === 'LOG');
if (!logMessage) {
console.log(' β οΈ No LOG message received (may be normal if tool executed before browser connected)');
} else {
console.log(` β
Received LOG message: ${logMessage.message}`);
}
// Test 2: update_ui
console.log('\n2οΈβ£ Testing update_ui tool...');
const result2 = await createIsolatedClaudeWithMCP(
'Use mcp__imagine__update_ui to create a simple div with text "Hello World"',
{
model: 'sonnet',
authToken: process.env.ANTHROPIC_API_KEY
}
);
if (result2.code !== 0) {
throw new Error(`update_ui failed with code ${result2.code}`);
}
// Wait a bit for WebSocket message
await new Promise(resolve => setTimeout(resolve, 1000));
// Check if we received UPDATE_DOM message
const updateMessage = receivedMessages.find(m => m.type === 'UPDATE_DOM');
if (!updateMessage) {
console.log(' β οΈ No UPDATE_DOM message received (may be normal if tool executed before browser connected)');
} else {
console.log(` β
Received UPDATE_DOM message`);
console.log(` π HTML preview: ${updateMessage.html.substring(0, 100)}...`);
}
// Test 3: Combined
console.log('\n3οΈβ£ Testing combined tools...');
const result3 = await createIsolatedClaudeWithMCP(
'First use mcp__imagine__log_thought to log "Building UI...", then use mcp__imagine__update_ui to create a header with text "Test Header"',
{
model: 'sonnet',
authToken: process.env.ANTHROPIC_API_KEY
}
);
if (result3.code !== 0) {
throw new Error(`Combined tools failed with code ${result3.code}`);
}
// Wait a bit for WebSocket messages
await new Promise(resolve => setTimeout(resolve, 2000));
const logMessages = receivedMessages.filter(m => m.type === 'LOG');
const updateMessages = receivedMessages.filter(m => m.type === 'UPDATE_DOM');
console.log(` β
Received ${logMessages.length} LOG messages`);
console.log(` β
Received ${updateMessages.length} UPDATE_DOM messages`);
return {
logMessages,
updateMessages,
totalMessages: receivedMessages.length
};
}
function stopServer() {
return new Promise((resolve) => {
if (browserWs) {
browserWs.close();
browserWs = null;
}
if (serverProcess) {
serverProcess.kill();
serverProcess = null;
}
setTimeout(() => {
cleanupPort(PORT);
resolve();
}, 1000);
});
}
async function runTest() {
console.log('\n' + '='.repeat(60));
console.log('π Browser Connection Test');
console.log('='.repeat(60));
// Claude CLI handles authentication (OAuth credentials automatically copied)
// Just verify Claude CLI is installed
try {
const { execSync } = await import('child_process');
execSync('which claude', { stdio: 'ignore' });
} catch (e) {
console.error('\nβ Error: Claude CLI not found');
console.error(' Please install Claude CLI first');
process.exit(1);
}
try {
// Start server
await startServer();
// Connect browser WebSocket
await connectBrowser();
// Test tool execution
const results = await testToolExecution();
// Summary
console.log('\n' + '='.repeat(60));
console.log('π Test Summary');
console.log('='.repeat(60));
console.log(`\nβ
Server started successfully`);
console.log(`β
Browser WebSocket connected`);
console.log(`β
Tool execution completed`);
console.log(`\nπ¨ Messages received:`);
console.log(` - LOG messages: ${results.logMessages.length}`);
console.log(` - UPDATE_DOM messages: ${results.updateMessages.length}`);
console.log(` - Total messages: ${results.totalMessages}`);
console.log('\nπ To view in browser:');
console.log(` Open: http://${HOST}:${PORT}`);
console.log(` Then run Claude CLI with tool calls to see updates`);
console.log('\nβ
Browser connection test completed successfully!');
console.log('\nπ‘ Note: This test verifies WebSocket connection programmatically.');
console.log(' For visual verification, open http://localhost:3000 in a browser');
console.log(' and run Claude CLI with tool calls.');
// Don't stop server - let user test manually
console.log('\nβΈοΈ Server still running. Press Ctrl+C to stop.');
console.log(' Or run: kill $(lsof -ti:3000)');
} catch (error) {
console.error('\nβ Test failed:', error.message);
console.error(error.stack);
await stopServer();
process.exit(1);
}
}
// Handle cleanup on exit
process.on('SIGINT', async () => {
console.log('\n\nπ Stopping server...');
await stopServer();
process.exit(0);
});
process.on('SIGTERM', async () => {
await stopServer();
process.exit(0);
});
runTest();