mocha-multiple-sessions-detailed-runner
Version:
High-level API for multi-session Mocha tests with detailed reporting
363 lines (312 loc) • 11.5 kB
text/typescript
import type {
IInitializeRunnerUseCase,
IDefineSessionUseCase,
IRunSingleSessionUseCase,
IRunAllSessionsUseCase,
IClearAllSessionsUseCase,
IDestroyRunnerUseCase
} from '../../interfaces/use-cases/runner-use-cases';
import type { MultiSessionConfig, SessionResult, SessionMeta } from '../../interfaces/core/types';
import type { BatchExecutionOptions } from '../../interfaces/core/batch-execution';
import type { ISessionMetaRepository } from '../../interfaces/repositories/session-meta-repository';
import type { IReportRepository } from '../../interfaces/repositories/report-repository';
import type { IUnifiedEventBus } from '../../interfaces/events/unified-event-bus';
import type { IMochaIntegration } from '../../interfaces/infrastructure/mocha-integration';
// Use the global interface from mocha-integration.ts
declare global {
interface Window {
MochaMultipleSessions?: {
testSessionSetup: (config: any) => void;
onAnySessionEvent: (callback: (event: any) => void) => () => void;
testSession: (label: string, setupFn: () => void, options?: any) => Promise<string>;
runSession: (label: string) => Promise<any>;
runAllSessions: () => Promise<any[]>;
};
}
}
export class InitializeRunnerUseCase implements IInitializeRunnerUseCase {
constructor(
private mochaIntegration: IMochaIntegration,
private eventBus: IUnifiedEventBus
) {}
execute(config?: Partial<MultiSessionConfig>): void {
const finalConfig: MultiSessionConfig = {
windowVariablePrefix: 'mochaSession__',
autoInit: true,
captureConsoleLog: true,
sourceCode: true,
attachStats: true,
...config
};
try {
this.mochaIntegration.setupTestEnvironment(finalConfig);
this.eventBus.emitRunnerEvent({
type: 'runner:initialized',
timestamp: new Date(),
data: {
configUsed: finalConfig,
sessionCount: 0,
timestamp: new Date()
}
});
} catch (error) {
this.eventBus.emitRunnerEvent({
type: 'runner:config-updated',
timestamp: new Date(),
data: {
oldConfig: {},
newConfig: finalConfig,
changes: ['initialization_failed']
}
});
throw error;
}
}
}
export class DefineSessionUseCase implements IDefineSessionUseCase {
constructor(
private sessionRepository: ISessionMetaRepository,
private reportRepository: IReportRepository,
private eventBus: IUnifiedEventBus
) {}
execute(label: string, setupFn: () => void): void {
const resultKey = this.reportRepository.generateResultKey(label);
const sessionMeta: SessionMeta = {
label,
resultKey,
setupFn,
status: 'ready',
timestamp: new Date()
};
this.sessionRepository.save(sessionMeta);
this.eventBus.emitRunnerEvent({
type: 'runner:session-defined',
timestamp: new Date(),
data: {
sessionLabel: label,
sessionMeta,
totalSessions: this.sessionRepository.findAll().length
}
});
}
}
export class RunSingleSessionUseCase implements IRunSingleSessionUseCase {
constructor(
private sessionRepository: ISessionMetaRepository,
private reportRepository: IReportRepository,
private eventBus: IUnifiedEventBus
) {}
async execute(label: string): Promise<SessionResult> {
const sessionMeta = this.sessionRepository.findByLabel(label);
if (!sessionMeta) {
throw new Error(`Session '${label}' not found`);
}
try {
// Mark as running
this.sessionRepository.markAsRunning(label);
// Use the existing mocha-multiple-sessions library
if (!window.MochaMultipleSessions) {
throw new Error('MochaMultipleSessions library not available');
}
// Create the session first with better error handling
try {
await window.MochaMultipleSessions.testSession(label, sessionMeta.setupFn);
console.log(`Session '${label}' created successfully`);
} catch (sessionCreationError) {
console.error(`Failed to create session '${label}':`, sessionCreationError);
throw new Error(`Session creation failed for '${label}': ${sessionCreationError}`);
}
// Run the session with better error handling
let result;
try {
result = await window.MochaMultipleSessions.runSession(label);
console.log(`Session '${label}' execution completed:`, result);
} catch (sessionRunError) {
console.error(`Failed to run session '${label}':`, sessionRunError);
throw new Error(`Session execution failed for '${label}': ${sessionRunError}`);
}
// Wait for detailed report to be available
try {
await this.reportRepository.waitForReport(sessionMeta.resultKey, 5000);
console.log(`Detailed report available for '${label}'`);
} catch (waitError) {
console.warn(`Timeout waiting for detailed report for ${label}:`, waitError);
// Don't throw here, continue with basic result
}
// Mark as completed
this.sessionRepository.markAsCompleted(label, result);
const sessionResult: SessionResult = {
label,
status: result.success ? 'passed' : 'failed',
success: result.success,
timestamp: new Date(),
duration: result.duration,
stats: result.stats
};
console.log(`Session '${label}' result:`, sessionResult);
return sessionResult;
} catch (error) {
console.error(`Error in session '${label}':`, error);
this.sessionRepository.markAsFailed(label, error as Error);
const sessionResult: SessionResult = {
label,
status: 'failed',
success: false,
timestamp: new Date(),
error: error as Error
};
return sessionResult;
}
}
}
export class RunAllSessionsUseCase implements IRunAllSessionsUseCase {
constructor(
private sessionRepository: ISessionMetaRepository,
private runSingleSessionUseCase: IRunSingleSessionUseCase,
private eventBus: IUnifiedEventBus
) {}
async execute(options?: BatchExecutionOptions): Promise<Record<string, SessionResult>> {
const allSessions = this.sessionRepository.findAll();
if (allSessions.length === 0) {
throw new Error('No sessions defined');
}
const sessionLabels = allSessions.map(s => s.label);
const startTime = Date.now();
// Emit batch start event
this.eventBus.emitRunnerEvent({
type: 'runner:batch-started',
timestamp: new Date(),
data: {
sessionLabels,
totalSessions: sessionLabels.length,
executionMode: options?.mode || 'sequential',
options
}
});
const results: Record<string, SessionResult> = {};
let completed = 0;
try {
if (options?.mode === 'parallel') {
// Run in parallel
const promises = sessionLabels.map(async (label: string) => {
const result = await this.runSingleSessionUseCase.execute(label);
results[label] = result;
completed++;
this.eventBus.emitRunnerEvent({
type: 'runner:batch-progress',
timestamp: new Date(),
data: {
completed,
total: sessionLabels.length,
currentSession: label,
completedSessions: Object.keys(results),
failedSessions: Object.keys(results).filter((l: string) => !results[l]?.success),
progress: Math.round((completed / sessionLabels.length) * 100)
}
});
return result;
});
await Promise.all(promises);
} else {
// Run sequentially
for (const label of sessionLabels) {
if (options?.stopOnFirstFailure && completed > 0) {
const hasFailures = Object.values(results).some((r: SessionResult) => !r.success);
if (hasFailures) break;
}
const result = await this.runSingleSessionUseCase.execute(label);
results[label] = result;
completed++;
this.eventBus.emitRunnerEvent({
type: 'runner:batch-progress',
timestamp: new Date(),
data: {
completed,
total: sessionLabels.length,
currentSession: label,
completedSessions: Object.keys(results),
failedSessions: Object.keys(results).filter((l: string) => !results[l]?.success),
progress: Math.round((completed / sessionLabels.length) * 100)
}
});
}
}
const duration = Date.now() - startTime;
const successfulSessions = Object.values(results).filter(r => r.success).length;
const failedSessions = Object.values(results).filter(r => !r.success).length;
// Emit completion event
this.eventBus.emitRunnerEvent({
type: 'runner:batch-completed',
timestamp: new Date(),
data: {
totalSessions: sessionLabels.length,
successfulSessions,
failedSessions,
duration,
sessionResults: results,
overallSuccess: failedSessions === 0
}
});
return results;
} catch (error) {
// Emit failure event
this.eventBus.emitRunnerEvent({
type: 'runner:batch-failed',
timestamp: new Date(),
data: {
error: error as Error,
completedSessions: Object.keys(results),
partialResults: results
}
});
throw error;
}
}
}
export class ClearAllSessionsUseCase implements IClearAllSessionsUseCase {
constructor(
private sessionRepository: ISessionMetaRepository,
private reportRepository: IReportRepository,
private eventBus: IUnifiedEventBus
) {}
async execute(): Promise<void> {
const allSessions = this.sessionRepository.findAll();
const clearedSessions = allSessions.map(s => s.label);
const clearedReports: string[] = [];
// Clear all reports
for (const session of allSessions) {
if (this.reportRepository.hasReport(session.resultKey)) {
this.reportRepository.clearReport(session.resultKey);
clearedReports.push(session.resultKey);
}
}
// Clear session repository
this.sessionRepository.clear();
// Emit cleared event
this.eventBus.emitRunnerEvent({
type: 'runner:cleared',
timestamp: new Date(),
data: {
clearedSessions,
clearedReports
}
});
}
}
export class DestroyRunnerUseCase implements IDestroyRunnerUseCase {
constructor(
private clearAllSessionsUseCase: IClearAllSessionsUseCase,
private eventBus: IUnifiedEventBus
) {}
async execute(): Promise<void> {
// Clear all sessions first
await this.clearAllSessionsUseCase.execute();
// Emit destroy event
this.eventBus.emitRunnerEvent({
type: 'runner:destroyed',
timestamp: new Date()
});
// Destroy event bus
this.eventBus.destroy();
}
}