UNPKG

@unblessed/vrt

Version:

Visual Regression Testing tools for @unblessed terminal UI applications

372 lines (361 loc) 10.7 kB
import { Screen } from '@unblessed/core'; /** * VRT (Visual Regression Testing) Type Definitions */ /** * A single frame in a VRT recording */ interface VRTFrame { /** Screenshot data (SGR-encoded string from screen.screenshot()) */ screenshot: string; /** Timestamp when frame was captured (milliseconds since recording start) */ timestamp: number; /** Optional metadata for this frame */ metadata?: Record<string, any>; } /** * Complete VRT recording with metadata */ interface VRTRecording { /** Format version for future compatibility */ version: string; /** Terminal dimensions when recording was made */ dimensions: { cols: number; rows: number; }; /** Recording metadata */ metadata: { /** When recording was created */ createdAt: string; /** Total recording duration in milliseconds */ duration: number; /** Number of frames captured */ frameCount: number; /** Description or test name */ description?: string; /** Additional custom metadata */ [key: string]: any; }; /** Array of captured frames */ frames: VRTFrame[]; } /** * Options for VRTRecorder */ interface VRTRecorderOptions { /** How often to capture screenshots (in milliseconds, default: 100) */ interval?: number; /** Path where the recording should be saved */ outputPath?: string; /** Description for this recording */ description?: string; /** Additional metadata to include in recording */ metadata?: Record<string, any>; } /** * Options for VRTPlayer */ interface VRTPlayerOptions { /** Playback speed multiplier (1.0 = normal, 2.0 = 2x speed, default: 1.0) */ speed?: number; /** Callback called for each frame */ onFrame?: (frame: VRTFrame, index: number) => void; /** Whether to write frames to stdout (default: false) */ writeToStdout?: boolean; } /** * Options for VRTComparator */ interface VRTComparatorOptions { /** Allow this many different characters before considering frames different (default: 0) */ threshold?: number; /** Ignore ANSI color codes when comparing (default: false) */ ignoreColors?: boolean; /** Ignore whitespace differences (default: false) */ ignoreWhitespace?: boolean; } /** * Result of comparing two VRT recordings */ interface VRTComparisonResult { /** Whether the recordings match */ match: boolean; /** Total number of frames compared */ totalFrames: number; /** Number of frames that matched */ matchedFrames: number; /** Number of frames that differed */ differentFrames: number; /** Indices of frames that differed */ differentFrameIndices: number[]; /** Detailed differences per frame (if any) */ differences?: VRTFrameDifference[]; } /** * Detailed difference information for a single frame */ interface VRTFrameDifference { /** Frame index */ frameIndex: number; /** Expected screenshot */ expected: string; /** Actual screenshot */ actual: string; /** Number of different characters */ diffCount: number; /** Human-readable diff (optional) */ diff?: string; } /** * VRT Comparator - Compare VRT recordings for regression testing */ /** * VRTComparator compares two VRT recordings to detect visual regressions. * * @example * ```typescript * const result = VRTComparator.compare( * './golden.vrt.json', * './current.vrt.json' * ); * * if (!result.match) { * console.log(`${result.differentFrames} frames differ`); * result.differences?.forEach(diff => { * console.log(`Frame ${diff.frameIndex}: ${diff.diffCount} chars different`); * }); * } * ``` */ declare class VRTComparator { /** * Compare two VRT recordings * @param expected - Path to expected recording or VRTRecording object * @param actual - Path to actual recording or VRTRecording object * @param options - Comparison options * @returns Comparison result */ static compare(expected: string | VRTRecording, actual: string | VRTRecording, options?: VRTComparatorOptions): VRTComparisonResult; /** * Compare two VRTRecording objects * @param expected - Expected recording * @param actual - Actual recording * @param options - Comparison options * @returns Comparison result */ static compareRecordings(expected: VRTRecording, actual: VRTRecording, options?: VRTComparatorOptions): VRTComparisonResult; /** * Compare two frames * @param expected - Expected frame * @param actual - Actual frame * @param options - Comparison options * @returns Difference information */ private static compareFrames; /** * Strip ANSI color codes from string * @param str - String with ANSI codes * @returns String without ANSI codes */ private static stripAnsiColors; /** * Count character differences between two strings * @param str1 - First string * @param str2 - Second string * @returns Number of different characters */ private static countDifferences; /** * Generate a simple text diff (first 200 chars of each) * @param expected - Expected string * @param actual - Actual string * @returns Diff string */ private static generateSimpleDiff; } /** * Golden Snapshot Utilities for VRT * * Provides reusable golden snapshot workflow that packages can use * in their test helpers. */ /** * Result of golden snapshot comparison */ interface GoldenComparisonResult { /** Whether test should pass (golden created, updated, or matched) */ pass: boolean; /** Action taken (created, updated, matched, or failed) */ action: "created" | "updated" | "matched" | "failed"; /** Detailed comparison result (only present on failed) */ comparisonResult?: VRTComparisonResult; /** Formatted error message (only present on failed) */ errorMessage?: string; } /** * Save a golden snapshot to file * * Automatically creates directories if they don't exist. * * @param goldenPath - Path to save the golden snapshot * @param recording - VRT recording to save * * @example * ```typescript * saveGoldenSnapshot('__tests__/fixtures/box.vrt.json', recording); * ``` */ declare function saveGoldenSnapshot(goldenPath: string, recording: VRTRecording): void; /** * Compare recording with golden snapshot and handle the complete workflow * * This function handles: * - Creating golden snapshot on first run * - Updating golden when UPDATE_SNAPSHOTS=1 * - Comparing with golden on normal runs * - Formatting detailed error messages on mismatch * * @param fixturePath - Path to golden snapshot file * @param recording - Current recording to compare * @param testName - Name of the test (for error messages) * @returns Result indicating whether test should pass and why * * @example * ```typescript * const result = compareWithGolden( * '__tests__/fixtures/box.vrt.json', * recording, * 'box renders correctly' * ); * * if (!result.pass) { * throw new Error(result.errorMessage); * } * ``` */ declare function compareWithGolden(fixturePath: string, recording: VRTRecording, testName: string): GoldenComparisonResult; /** * VRT Player - Replay recorded terminal UI sessions */ /** * VRTPlayer replays VRT recordings for visual inspection or automated testing. * * @example * ```typescript * const player = new VRTPlayer('./test.vrt.json'); * * // Play back to stdout * await player.play({ writeToStdout: true }); * * // Process each frame * await player.play({ * onFrame: (frame, index) => { * console.log(`Frame ${index}: ${frame.screenshot.length} bytes`); * } * }); * ``` */ declare class VRTPlayer { private recording; /** * Create a player from a recording file or object * @param source - Path to VRT recording file or VRTRecording object */ constructor(source: string | VRTRecording); /** * Load a recording from file * @param filePath - Path to the VRT recording file * @returns Parsed VRT recording */ private load; /** * Play the recording * @param options - Playback options * @returns Promise that resolves when playback is complete */ play(options?: VRTPlayerOptions): Promise<void>; /** * Get all frames from the recording * @returns Array of frames */ getFrames(): VRTFrame[]; /** * Get recording metadata * @returns Recording metadata */ getMetadata(): VRTRecording["metadata"]; /** * Get recording dimensions * @returns Terminal dimensions */ getDimensions(): VRTRecording["dimensions"]; /** * Get a specific frame by index * @param index - Frame index * @returns The frame at the given index */ getFrame(index: number): VRTFrame | undefined; /** * Sleep for specified milliseconds * @param ms - Milliseconds to sleep */ private sleep; } /** * VRT Recorder - Capture terminal UI screenshots over time */ /** * VRTRecorder captures screen screenshots at regular intervals for visual regression testing. * * @example * ```typescript * const screen = new Screen({ smartCSR: true }); * const recorder = new VRTRecorder(screen, { * interval: 100, * outputPath: './test.vrt.json', * description: 'Box rendering test' * }); * * recorder.start(); * // ... interact with UI ... * const recording = await recorder.stop(); * ``` */ declare class VRTRecorder { private screen; private options; private frames; private startTime; private timer; private recording; constructor(screen: Screen, options?: VRTRecorderOptions); /** * Start recording screenshots */ start(): void; /** * Capture a single frame */ private captureFrame; /** * Stop recording and save to file * @returns The complete VRT recording */ stop(): VRTRecording; /** * Save recording to file * @param recording - The recording to save * @param outputPath - Path to save the recording */ save(recording: VRTRecording, outputPath: string): void; /** * Check if recording is in progress */ isRecording(): boolean; /** * Get current frame count */ getFrameCount(): number; } export { type GoldenComparisonResult, VRTComparator, type VRTComparatorOptions, type VRTComparisonResult, type VRTFrame, type VRTFrameDifference, VRTPlayer, type VRTPlayerOptions, VRTRecorder, type VRTRecorderOptions, type VRTRecording, compareWithGolden, saveGoldenSnapshot };