Package Exports
- @akaoio/battle
Readme
@akaoio/battle
Universal terminal application testing framework with real PTY emulation and screenshots
๐ฎ Features
- ๐ฏ Real PTY Testing - Test actual terminal behavior, not fake I/O
- ๐ฌ JSON Replays - Record terminal sessions as JSON files
- ๐ผ๏ธ Screenshots - Capture terminal state in multiple formats
- โจ๏ธ Keyboard Simulation - Send any key combination
- ๐ Viewport Control - Resize terminal dimensions
- ๐ Pattern Matching - Regex and string expectations
- ๐ Test Runner - Built-in test suite execution
- ๐ Silent Mode - For non-interactive commands
- ๐ Universal - Test any terminal app in any language
๐ฆ Installation
# NPM
npm install @akaoio/battle
# Yarn
yarn add @akaoio/battle
# PNPM
pnpm add @akaoio/battle
# Bun
bun add @akaoio/battle๐ Quick Start
As a Module
import { Battle } from '@akaoio/battle'
const battle = new Battle({
verbose: false,
timeout: 10000
})
const result = await battle.run(async (b) => {
// Spawn a terminal application
b.spawn('echo', ['Hello, Battle!'])
// Wait for output
await b.expect('Hello, Battle!')
// Send keyboard input
b.sendKey('enter')
// Take a screenshot
b.screenshot('test-complete')
})
console.log('Test result:', result.success)
console.log('Replay saved:', result.replayPath)As a CLI Tool
# Install globally
npm install -g @akaoio/battle
# Run a simple test
battle run "echo 'Hello, World!'"
# Run with expectations
battle test "ls -la" --expect "package.json"
# Replay a recorded session
battle replay play ./logs/replay-*.json
# Export replay to HTML
battle replay export ./logs/replay-*.json --format html๐ฌ Replay System
Battle features a replay system that records terminal sessions as JSON files:
Recording
Every Battle test automatically records a replay file containing:
- All terminal input/output events
- Precise timestamps for perfect playback
- Terminal dimensions and environment
- Key presses and control sequences
Terminal Player
battle replay play recording.jsonYouTube-Style Controls:
- Space - Play/Pause
- S - Stop
- R - Restart
- E - Jump to End
- +/- - Speed Up/Down (0.1ร to 50ร)
- 0-4 - Speed Presets
- โโ - Skip Forward/Backward
- Q/ESC - Quit
HTML Export
battle replay export recording.json --format htmlGenerates an interactive HTML player with:
- Full media controls
- Speed control (0ร to unlimited)
- Progress bar with scrubbing
- Event timeline visualization
- Keyboard shortcuts
๐งช Testing Philosophy
Real PTY Testing
Battle uses actual PTY (pseudo-terminal) emulation, not fake stdin/stdout pipes. This reveals real bugs that pipe-based testing misses:
- Buffering issues
- ANSI escape sequences
- Terminal-specific behavior
- TTY detection
- Timing problems
Self-Testing Framework
Battle tests itself using its own framework - the ultimate validation:
npm test # Run self-test suite
npm test:replay # Test replay system
npm test:all # Run all tests๐ Core Components
Battle Class
Main testing interface with PTY control:
const battle = new Battle({
cols: 80, // Terminal width
rows: 24, // Terminal height
cwd: process.cwd(), // Working directory
env: process.env, // Environment variables
timeout: 30000, // Test timeout
verbose: false, // Show output
logDir: './logs', // Log directory
screenshotDir: './screenshots'
})Methods
- spawn(command, args) - Start a terminal application
- sendKey(key) - Send keyboard input
- expect(pattern, timeout) - Wait for output pattern
- screenshot(name) - Capture terminal state
- resize(cols, rows) - Resize terminal
- wait(ms) - Wait for duration
- getCursor() - Get cursor position
Runner Class
Test suite execution:
const runner = new Runner()
runner.test('Echo test', {
command: 'echo',
args: ['Hello'],
expectations: ['Hello']
})
await runner.run()Silent Class
For non-interactive system commands:
const silent = new Silent()
const result = silent.exec('ls -la')
const isRunning = silent.isRunning('node')
const portOpen = silent.isPortOpen(3000)Replay Class
Session recording and playback:
const replay = new Replay()
// Load a recording
replay.load('recording.json')
// Play in terminal
await replay.play({ speed: 2.0 })
// Export to HTML
const html = replay.export('html')๐๏ธ Architecture
Battle follows the Class = Directory + Method-per-file pattern:
Battle/
โโโ index.ts # Class definition
โโโ constructor.ts # Constructor logic
โโโ spawn.ts # spawn() method
โโโ expect.ts # expect() method
โโโ sendKey.ts # sendKey() method
โโโ screenshot.ts # screenshot() method
โโโ resize.ts # resize() method
โโโ run.ts # run() method๐ง Development
Building
npm run build # Build all formats
npm run build:watch # Watch mode
npm run typecheck # Type checkingTesting
npm test # Main test suite
npm test:replay # Replay tests
npm test:all # All tests
npm test:quick # Quick testsDocumentation
npm run doc # Generate docs
bun doc # Generate with Bun๐ Examples
Testing Interactive CLI
await battle.run(async (b) => {
b.spawn('npm', ['init'])
await b.expect('package name:')
b.sendKey('my-package')
b.sendKey('enter')
await b.expect('version:')
b.sendKey('enter') // Accept default
await b.expect('Is this OK?')
b.sendKey('y')
b.sendKey('enter')
})Testing TUI Applications
await battle.run(async (b) => {
b.spawn('vim', ['test.txt'])
await b.wait(500) // Wait for vim to start
b.sendKey('i') // Insert mode
b.sendKey('Hello, Vim!')
b.sendKey('escape')
b.sendKey(':wq')
b.sendKey('enter')
await b.expect('written')
})Cross-Platform Testing
const runner = new Runner()
runner.suite('Cross-platform tests', [
{
name: 'List files',
command: process.platform === 'win32' ? 'dir' : 'ls',
args: [],
expectations: [/\.json/]
},
{
name: 'Check Node',
command: 'node',
args: ['--version'],
expectations: [/v\d+\.\d+\.\d+/]
}
])
await runner.run()๐ค Contributing
Contributions are welcome! Please read our Contributing Guide for details.
๐ License
MIT - See LICENSE for details.
๐ Acknowledgments
- Built with node-pty for real terminal emulation
- JSON-based replay format
- Self-testing philosophy from test-driven development
Built with โค๏ธ by AKAO.IO