Skip to main content
Glama
ooples

MCP Console Automation Server

TestRecorder.ts7.38 kB
/** * TestRecorder - Records console interactions for test replay * * This class captures all console operations including session creation, * input, output, and timing information for later replay or code generation. */ import * as fs from 'fs'; import * as path from 'path'; import { TestRecording, RecordingStep, RecordingMetadata, } from '../types/test-framework.js'; export interface RecorderOptions { name: string; author?: string; description?: string; tags?: string[]; environment?: Record<string, string>; outputDir?: string; } export class TestRecorder { private recording: TestRecording | null = null; private isRecording = false; private startTime = 0; private outputDir: string; private currentSessionId: string | null = null; constructor(outputDir = 'data/recordings') { this.outputDir = outputDir; this.ensureOutputDir(); } private ensureOutputDir(): void { if (!fs.existsSync(this.outputDir)) { fs.mkdirSync(this.outputDir, { recursive: true }); } } /** * Start recording a new test session */ public startRecording(options: RecorderOptions): void { if (this.isRecording) { throw new Error('Recording already in progress'); } this.startTime = Date.now(); this.isRecording = true; this.currentSessionId = null; const metadata: RecordingMetadata = { author: options.author, description: options.description, tags: options.tags, environment: options.environment || this.captureEnvironment(), }; this.recording = { name: options.name, version: '1.0.0', createdAt: new Date().toISOString(), duration: 0, steps: [], metadata, }; if (options.outputDir) { this.outputDir = options.outputDir; this.ensureOutputDir(); } } /** * Stop recording and save to file */ public stopRecording(): TestRecording { if (!this.isRecording || !this.recording) { throw new Error('No recording in progress'); } this.recording.duration = Date.now() - this.startTime; this.isRecording = false; // Save to file const filename = this.sanitizeFilename(this.recording.name) + '.json'; const filepath = path.join(this.outputDir, filename); fs.writeFileSync( filepath, JSON.stringify(this.recording, null, 2), 'utf-8' ); const result = this.recording; this.recording = null; this.currentSessionId = null; return result; } /** * Record a create_session step */ public recordCreateSession( sessionId: string, data: any, output?: string ): void { this.addStep({ type: 'create_session', timestamp: this.getRelativeTimestamp(), data, output, sessionId, }); this.currentSessionId = sessionId; } /** * Record a send_input step */ public recordSendInput( input: string, sessionId?: string, output?: string ): void { this.addStep({ type: 'send_input', timestamp: this.getRelativeTimestamp(), data: { input }, output, sessionId: sessionId || this.currentSessionId || undefined, }); } /** * Record a send_key step */ public recordSendKey(key: string, sessionId?: string, output?: string): void { this.addStep({ type: 'send_key', timestamp: this.getRelativeTimestamp(), data: { key }, output, sessionId: sessionId || this.currentSessionId || undefined, }); } /** * Record a wait_for_output step */ public recordWaitForOutput( pattern: string, timeout: number, sessionId?: string, output?: string ): void { this.addStep({ type: 'wait_for_output', timestamp: this.getRelativeTimestamp(), data: { pattern, timeout }, output, sessionId: sessionId || this.currentSessionId || undefined, }); } /** * Record an assertion step (for future Phase 2 integration) */ public recordAssertion(assertion: any, sessionId?: string): void { this.addStep({ type: 'assert', timestamp: this.getRelativeTimestamp(), data: assertion, sessionId: sessionId || this.currentSessionId || undefined, }); } /** * Record a snapshot step (for future Phase 2 integration) */ public recordSnapshot(snapshot: any, sessionId?: string): void { this.addStep({ type: 'snapshot', timestamp: this.getRelativeTimestamp(), data: snapshot, sessionId: sessionId || this.currentSessionId || undefined, }); } /** * Get current recording state */ public getRecording(): TestRecording | null { return this.recording; } /** * Check if currently recording */ public isCurrentlyRecording(): boolean { return this.isRecording; } /** * List all recordings in the output directory */ public static listRecordings(outputDir = 'data/recordings'): string[] { if (!fs.existsSync(outputDir)) { return []; } return fs .readdirSync(outputDir) .filter((file) => file.endsWith('.json')) .map((file) => path.basename(file, '.json')); } /** * Load a recording from file */ public static loadRecording( name: string, outputDir = 'data/recordings' ): TestRecording { const filename = name.endsWith('.json') ? name : `${name}.json`; const filepath = path.join(outputDir, filename); if (!fs.existsSync(filepath)) { throw new Error(`Recording not found: ${name}`); } const content = fs.readFileSync(filepath, 'utf-8'); return JSON.parse(content) as TestRecording; } /** * Delete a recording file */ public static deleteRecording( name: string, outputDir = 'data/recordings' ): void { const filename = name.endsWith('.json') ? name : `${name}.json`; const filepath = path.join(outputDir, filename); if (fs.existsSync(filepath)) { fs.unlinkSync(filepath); } } // Private helper methods private addStep(step: RecordingStep): void { if (!this.isRecording || !this.recording) { throw new Error('Cannot add step: no recording in progress'); } this.recording.steps.push(step); } private getRelativeTimestamp(): number { if (!this.isRecording) { throw new Error('Cannot get timestamp: no recording in progress'); } return Date.now() - this.startTime; } private captureEnvironment(): Record<string, string> { return { platform: process.platform, nodeVersion: process.version, cwd: process.cwd(), timestamp: new Date().toISOString(), }; } private sanitizeFilename(name: string): string { return name.replace(/[^a-z0-9_-]/gi, '_').toLowerCase(); } /** * Get recording statistics */ public getStats(): { steps: number; duration: number; sessionId: string | null; } | null { if (!this.recording) { return null; } return { steps: this.recording.steps.length, duration: Date.now() - this.startTime, sessionId: this.currentSessionId, }; } /** * Cancel recording without saving */ public cancelRecording(): void { if (!this.isRecording) { throw new Error('No recording in progress'); } this.recording = null; this.isRecording = false; this.currentSessionId = null; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ooples/mcp-console-automation'

If you have feedback or need assistance with the MCP directory API, please join our Discord server