@unblessed/vrt
v1.0.0-alpha.23
Published
Visual Regression Testing tools for @unblessed terminal UI applications
Maintainers
Readme
@unblessed/vrt
Visual Regression Testing tools for @unblessed terminal UI applications.
Overview
@unblessed/vrt provides infrastructure for recording, playing back, and comparing terminal UI screenshots to detect visual regressions. It captures the raw SGR-encoded terminal output from screen.screenshot() and allows you to verify that UI rendering remains consistent across code changes.
Installation
npm install --save-dev @unblessed/vrt
# or
pnpm add -D @unblessed/vrtCLI Tools
The vrt command provides tools for working with VRT recordings directly from the terminal.
Available Commands
vrt play - Play back a recording
vrt play recording.vrt.json
vrt play recording.vrt.json --speed 2.0 # 2x speedPlays the recording to your terminal, showing exactly what was captured.
vrt info - Show recording information
vrt info recording.vrt.jsonDisplays metadata about the recording including dimensions, duration, frame count, and timestamps.
vrt compare - Compare two recordings
# Basic comparison
vrt compare expected.vrt.json actual.vrt.json
# With options
vrt compare expected.vrt.json actual.vrt.json \
--threshold 5 \
--ignore-colors \
--verboseOptions:
-t, --threshold <number>- Allow N character differences (default: 0)--ignore-colors- Ignore ANSI color code differences--ignore-whitespace- Ignore whitespace differences-v, --verbose- Show detailed diff information
vrt export - Export to image formats
vrt export recording.vrt.json output.gif # Animated GIF
vrt export recording.vrt.json output.png --frame 0 # Single frame PNGNote: Export functionality coming soon
Usage in Scripts
Add CLI commands to your package.json scripts:
{
"scripts": {
"vrt:info": "vrt info tests/fixtures/golden.vrt.json",
"vrt:compare": "vrt compare tests/fixtures/golden.vrt.json tests/fixtures/current.vrt.json"
}
}Quick Start - Golden Snapshot Testing
The most common use case is golden snapshot testing with the compareWithGolden utility:
import { compareWithGolden } from "@unblessed/vrt";
import { Screen, Box } from "@unblessed/node";
it("box renders correctly", async () => {
const screen = new Screen();
const box = new Box({
parent: screen,
content: "Hello",
border: { type: "line" },
});
screen.render();
// Capture screenshot
const recording = {
version: "1.0.0",
dimensions: { cols: screen.cols, rows: screen.rows },
metadata: {
createdAt: new Date().toISOString(),
duration: 0,
frameCount: 1,
description: "box test",
},
frames: [{ screenshot: screen.screenshot(), timestamp: 0 }],
};
// Compare with golden snapshot
const result = compareWithGolden(
"__tests__/fixtures/box.vrt.json",
recording,
"box renders correctly",
);
if (!result.pass) {
throw new Error(result.errorMessage);
}
screen.destroy();
});Commands:
# Normal - compare with golden
pnpm test
# Update golden snapshots
UPDATE_SNAPSHOTS=1 pnpm testAPI Reference
Golden Snapshot Utilities
compareWithGolden(fixturePath, recording, testName)
Complete golden snapshot workflow that handles:
- Creating golden on first run
- Updating golden when
UPDATE_SNAPSHOTS=1 - Comparing with golden on normal runs
- Formatting detailed error messages
Returns: GoldenComparisonResult
pass: boolean- Whether test should passaction: 'created' | 'updated' | 'matched' | 'failed'errorMessage?: string- Formatted error (on failure)
saveGoldenSnapshot(path, recording)
Save a VRT recording as a golden snapshot file.
Recording & Playback
Recording a Session
import { VRTRecorder } from "@unblessed/vrt";
import { Screen, Box } from "@unblessed/node";
const screen = new Screen({ smartCSR: true });
const recorder = new VRTRecorder(screen, {
interval: 100, // Capture every 100ms
outputPath: "./recording.vrt.json",
});
// Start recording
recorder.start();
// Create UI and interact
const box = new Box({
parent: screen,
content: "Hello World",
border: { type: "line" },
});
screen.render();
// Stop and save
const recording = recorder.stop();
// Saved to ./recording.vrt.jsonPlaying Back a Recording
import { VRTPlayer } from "@unblessed/vrt";
const player = new VRTPlayer("./recording.vrt.json");
// Play to stdout
await player.play({ writeToStdout: true });
// Process each frame
await player.play({
onFrame: (frame, index) => {
console.log(`Frame ${index}: ${frame.screenshot.length} bytes`);
},
speed: 2.0, // 2x speed
});Comparing Recordings
import { VRTComparator } from "@unblessed/vrt";
const result = VRTComparator.compare(
"./golden.vrt.json", // Expected
"./current.vrt.json", // Actual
{
threshold: 5, // Allow up to 5 char differences
ignoreColors: false, // Compare colors too
},
);
if (!result.match) {
console.error(`Visual regression detected!`);
console.error(
` ${result.differentFrames} of ${result.totalFrames} frames differ`,
);
result.differences?.forEach((diff) => {
console.error(
` Frame ${diff.frameIndex}: ${diff.diffCount} chars different`,
);
});
}Testing Integration
Use VRT in your Vitest tests:
import { describe, it, expect } from "vitest";
import { Screen, Box } from "@unblessed/node";
import { VRTRecorder, VRTComparator } from "@unblessed/vrt";
describe("Box rendering", () => {
it("renders with border correctly", async () => {
const screen = new Screen();
const recorder = new VRTRecorder(screen);
recorder.start();
const box = new Box({
parent: screen,
top: 0,
left: 0,
width: 20,
height: 5,
content: "Test",
border: { type: "line" },
});
screen.render();
await new Promise((resolve) => setTimeout(resolve, 100));
const recording = recorder.stop();
screen.destroy();
// Compare with golden snapshot
const result = VRTComparator.compare(
"./__tests__/fixtures/box-golden.vrt.json",
recording,
);
expect(result.match).toBe(true);
});
});API Reference
VRTRecorder
Records screen screenshots at regular intervals.
Constructor:
new VRTRecorder(screen: Screen, options?: VRTRecorderOptions)Methods:
start()- Start recordingstop()- Stop recording and return VRTRecordingisRecording()- Check if recording is in progressgetFrameCount()- Get current frame count
VRTPlayer
Plays back VRT recordings.
Constructor:
new VRTPlayer(source: string | VRTRecording)Methods:
play(options?: VRTPlayerOptions)- Play the recordinggetFrames()- Get all framesgetFrame(index)- Get specific framegetMetadata()- Get recording metadatagetDimensions()- Get terminal dimensions
VRTComparator
Compares two VRT recordings.
Static Methods:
compare(expected, actual, options?)- Compare two recordingscompareRecordings(expected, actual, options?)- Compare VRTRecording objects
Types
interface VRTRecorderOptions {
interval?: number;
outputPath?: string;
description?: string;
metadata?: Record<string, any>;
}
interface VRTPlayerOptions {
speed?: number;
onFrame?: (frame: VRTFrame, index: number) => void;
writeToStdout?: boolean;
}
interface VRTComparatorOptions {
threshold?: number;
ignoreColors?: boolean;
ignoreWhitespace?: boolean;
}
interface VRTComparisonResult {
match: boolean;
totalFrames: number;
matchedFrames: number;
differentFrames: number;
differentFrameIndices: number[];
differences?: VRTFrameDifference[];
}Recording Format
VRT recordings are JSON files with this structure:
{
"version": "1.0.0",
"dimensions": {
"cols": 80,
"rows": 24
},
"metadata": {
"createdAt": "2025-10-21T...",
"duration": 1000,
"frameCount": 10,
"description": "Test recording"
},
"frames": [
{
"screenshot": "\u001b[H\u001b[2J...",
"timestamp": 0
}
]
}Future Work
Image Export
The vrt export command is planned to support exporting VRT recordings to image formats:
Planned Features:
PNG Export: Export individual frames as static PNG images
- Useful for documentation, GitHub PRs, and CI reports
- Renders ANSI escape codes to pixel buffers
- Uses
pngjslibrary (already included)
GIF Export: Export entire recordings as animated GIFs
- Perfect for showcasing UI interactions
- Configurable frame rate and speed
- Uses
omggiflibrary (already included)
Implementation Requirements:
- ANSI-to-image renderer that converts terminal escape codes to pixel buffers
- Font rendering (monospace font required)
- Color palette mapping (256-color and true color support)
- Text rendering with proper character spacing
Potential Extensions:
- SVG export for scalable terminal output
- HTML export with interactive playback controls
- Video export (MP4/WebM) for high-quality recordings
- Side-by-side diff visualization
License
MIT
