import { CLIExecutor, CLIExecutionResult, CLIExecutionOptions } from '../utils/cli-executor'; import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs'; import { join, dirname } from 'path'; import { BaselineNormalizer } from '../utils/baseline-normalizer'; export abstract class BaseE2ETest { protected cliExecutor: CLIExecutor; protected testConfig: any; constructor() { this.cliExecutor = new CLIExecutor(); this.testConfig = (global as any).TEST_CONFIG; } /** * Cleanup method to be called after tests */ async cleanup(): Promise { if (this.cliExecutor) { await this.cliExecutor.destroy(); } } /** * Execute a CLI command and return the result */ protected async executeCommand(command: string, options: CLIExecutionOptions = {}): Promise { return this.cliExecutor.executeCommand(command, options); } /** * Execute CLI command with arguments array */ protected async execute(args: string[], options: CLIExecutionOptions = {}): Promise { return this.cliExecutor.execute(args, options); } /** * Load test fixture data */ protected loadFixture(fixturePath: string): string { const fullPath = join(this.testConfig.FIXTURES_PATH, fixturePath); if (!existsSync(fullPath)) { throw new Error(`Fixture not found: ${fullPath}`); } return readFileSync(fullPath, 'utf-8'); } /** * Load JSON fixture data */ protected loadJsonFixture(fixturePath: string): T { const content = this.loadFixture(fixturePath); return JSON.parse(content); } /** * Load baseline data for comparison */ protected loadBaseline(baselinePath: string): string { const fullPath = join(this.testConfig.BASELINES_PATH, baselinePath); if (!existsSync(fullPath)) { throw new Error(`Baseline not found: ${fullPath}`); } return readFileSync(fullPath, 'utf-8'); } /** * Save baseline data (for baseline generation/updates) */ protected saveBaseline(baselinePath: string, content: string): void { const fullPath = join(this.testConfig.BASELINES_PATH, baselinePath); const dir = dirname(fullPath); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } writeFileSync(fullPath, content, 'utf-8'); } /** * Assert command executed successfully */ protected assertSuccess(result: CLIExecutionResult, message?: string): void { expect(result.exitCode).toBe(0); if (message) { expect(result.stderr).toBe(''); } } /** * Assert command failed with expected exit code */ protected assertFailure(result: CLIExecutionResult, expectedExitCode?: number): void { if (expectedExitCode !== undefined) { expect(result.exitCode).toBe(expectedExitCode); } else { expect(result.exitCode).not.toBe(0); } } /** * Assert output contains expected text */ protected assertOutputContains(result: CLIExecutionResult, expectedText: string): void { expect(result.stdout).toContain(expectedText); } /** * Assert output matches baseline with normalization */ protected assertOutputMatchesBaseline(result: CLIExecutionResult, baselinePath: string, maxLines?: number): void { const baseline = this.loadBaseline(baselinePath); let actualOutput = result.stdout; // Strip beta version warning before truncation so it doesn't affect line count actualOutput = actualOutput.replace(/You are running beta version[^\n]+\n/g, ''); // If maxLines is specified, truncate both actual and baseline to first N lines if (maxLines) { actualOutput = this.truncateToLines(actualOutput, maxLines); } const normalizedActual = BaselineNormalizer.normalize(actualOutput); const normalizedBaseline = BaselineNormalizer.normalize(baseline); if (normalizedActual !== normalizedBaseline) { console.log('Expected (baseline):'); console.log(normalizedBaseline); console.log('\nActual:'); console.log(normalizedActual); } expect(normalizedActual).toBe(normalizedBaseline); } /** * Truncate output to first N lines for baseline comparison */ private truncateToLines(output: string, maxLines: number): string { const lines = output.split('\n'); return lines.slice(0, maxLines).join('\n'); } /** * Generate or update a baseline file (for development/maintenance) */ protected updateBaseline(result: CLIExecutionResult, baselinePath: string): void { const normalizedOutput = BaselineNormalizer.normalize(result.stdout); this.saveBaseline(baselinePath, normalizedOutput); } }