@unblessed/vrt
Version:
Visual Regression Testing tools for @unblessed terminal UI applications
372 lines (361 loc) • 10.7 kB
text/typescript
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 };