Skip to content

TermlessHeadless terminal testing

Terminal apps are hard to test — no DOM to inspect, just invisible escape sequences. Termless gives you a real terminal emulator in-process with full access to cells, colors, cursor, and modes. Like Playwright, but for terminals.

Quick Start

bash
npm install -D @termless/test
bash
bun add -d @termless/test
bash
pnpm add -D @termless/test
bash
yarn add -D @termless/test
typescript
import { test, expect } from "vitest"
import { createTestTerminal } from "@termless/test"

// ANSI helpers — real apps use @silvery/ag-term or @silvery/ansi, these are just for test data
const BOLD = (s: string) => `\x1b[1m${s}\x1b[0m`
const GREEN = (s: string) => `\x1b[38;2;0;255;0m${s}\x1b[0m`

test("inspect what string matching can't see", () => {
  // Creates an xterm.js terminal by default. Ghostty, Alacritty, WezTerm, vt100,
  // and Peekaboo backends are also available — see Multi-Backend Testing below.
  const term = createTestTerminal({ cols: 40, rows: 3 })

  // Simulate a build pipeline — 4 lines overflow a 3-row terminal
  term.feed("Step 1: install\r\n")
  term.feed(`Step 2: ${GREEN("build ok")}\r\n`)
  term.feed(`Step 3: ${BOLD("test")}\r\n`)
  term.feed("Step 4: deploy")

  // Region selectors — screen, scrollback, buffer
  expect(term.scrollback).toContainText("install") // scrolled off, still in history
  expect(term.screen).toContainText("deploy") // visible area
  expect(term.buffer).toContainText("install") // everything (scrollback + screen)
  expect(term.row(0)).toHaveText("Step 2: build ok") // specific row

  // Cell styles — colors that getText() can't see
  expect(term.cell(0, 8)).toHaveFg("#00ff00") // "build ok" is green
  expect(term.cell(1, 8)).toBeBold() // "test" is bold

  // Scroll up, then assert on viewport
  term.backend.scrollViewport(1)
  expect(term.viewport).toContainText("install")

  // Resize — verify content survives
  term.resize(20, 3)
  expect(term.screen).toContainText("deploy")

  // Terminal state — window title, cursor, modes
  term.feed("\x1b]2;Build Pipeline\x07") // OSC 2 — set window title
  expect(term).toHaveTitle("Build Pipeline")
  expect(term).toHaveCursorAt(14, 2) // after "Step 4: deploy"
  expect(term).toBeInMode("autoWrap") // default mode
  expect(term).not.toBeInMode("altScreen") // not in alternate screen
})

Recording Sessions

Capture a terminal session as a Recording, then play it back as animated output -- no ffmpeg, no Chromium, no external tools:

bash
# Record a command to a recording
$ termless record -o demo.tape ls -la

# Play it back as an animated GIF
$ termless play -o demo.gif demo.tape

# Compare across backends — same recording, different emulators
$ termless compare demo.tape -b vterm,ghostty --compare side-by-side

Output formats: GIF, animated SVG, APNG, PNG -- all rendered with pure JS encoders. See Recording Sessions for full details.

Why Not Just Assert on Strings?

String assertions on terminal output break constantly:

  • ANSI codes make string matching fragile (\x1b[1m litters your test)
  • Trailing whitespace differs between terminals and runs
  • Wide characters (emoji, CJK) occupy 2 columns but 1 string position
  • Colors are invisible in getText() — a red error looks the same as a green success

Termless gives you structured access to the terminal buffer. Assert on what matters:

typescript
// Instead of fragile string matching...
expect(output).toContain("\x1b[1;31mError\x1b[0m")

// ...assert on terminal state
expect(term.screen).toContainText("Error")
expect(term.cell(0, 0)).toBeBold()
expect(term.cell(0, 0)).toHaveFg("#ff0000")
expect(term).toBeInMode("altScreen")
expect(term).toHaveTitle("my-app")

Which Package Do I Need?

You want to...Install
Test a terminal UI in Vitest@termless/test (bundles the default xterm.js fixture)
Use the core Terminal API without test matchers@termless/core + a backend (@termless/xtermjs, etc.)
Test against Ghostty's VT parser@termless/ghostty
Test with a zero-dependency emulator@termless/vt100
Take SVG/PNG screenshotsBuilt into @termless/core (PNG needs @resvg/resvg-js; browser-shaped PNG needs playwright)
Spawn and test real processes via PTYBuilt into @termless/core (used via any backend)
Automate a real terminal app (OS-level)@termless/peekaboo
Embed .cast / .tape playback in browser docs@termless/web-player
Use the CLI or MCP server@termless/cli

Most test suites start with @termless/test. Add backend packages for multi-backend testing, @termless/peekaboo for real terminal automation, or @termless/web-player when publishing recordings in browser docs.

Start Here

If you need...Read
First install and first testGetting Started
Locator-style terminal regionsWriting Tests and Terminal API
Assertions and matchersWriting Tests, Matchers, and Matcher Reference
Screenshots and visual snapshotsScreenshots
Real process / PTY testsGetting Started: Spawning Real Processes
Cross-backend coverageMulti-Backend Testing and Backend Capabilities
Less flaky terminal testsBest Practices
Full API surfaceTerminal, Backend, Cell & Types

Backend Management CLI

bash
$ bunx termless backends                       # List all backends and install status
$ bunx termless backends install               # Install default backends
$ bunx termless backends install ghostty       # Install a specific backend
$ bunx termless doctor                         # Health check installed backends

Two Ways to Choose a Backend

typescript
// 1. Factory function (explicit, sync)
import { createXtermBackend } from "@termless/xtermjs"
const term = createTerminal({ backend: createXtermBackend() })
typescript
// 2. String name via registry (async — handles WASM/native init)
import { backend } from "@termless/core"
const b = await backend("ghostty")
const term = createTerminal({ backend: b })

Backends

Every backend wraps a real terminal emulator and implements the same interface — write once, test everywhere:

BackendEngineHighlightsType
xtermjs@xterm/headless 5.5VS Code's terminal. Most mature, zero native deps.JS
ghosttyghostty-web 0.4Modern GPU-accelerated parser. Best standards compliance, Kitty keyboard protocol.WASM
vt100(built-in)Pure TypeScript, zero dependencies. Fastest backend, ideal for CI.JS
alacrittyalacritty_terminal 0.26Rust parser via napi-rs. Strong reflow behavior.Native
weztermtattoy-wezterm-termBroadest protocol support: sixel graphics, semantic prompts, Kitty keyboard.Native
peekaboo(OS automation)Tests against a real terminal app via OS accessibility APIs. macOS only.OS
vt100-rustvt100 0.15 (Rust)Reference Rust implementation — cross-validates the TS vt100 backend.Native
libvtermlibvterm (neovim)Neovim's C VT parser via WASM. Different implementation = different bugs found.WASM
ghostty-nativeghostty-vt Zig 1.3.1Native Ghostty via napigen N-API bindings. Same parser as ghostty, no WASM overhead.Native
kittykitty (C, GPL source)Kitty's parser built from source. Only backend with Kitty graphics protocol.Native

See Backend Capabilities for the full feature matrix, per-backend details, and usage examples (factory function + string name).

Packages

PackageDescription
@termless/coreCore: Terminal API, PTY, SVG/PNG screenshots, key mapping, region views
@termless/testVitest integration: 21+ matchers, fixtures, snapshot serializer
@termless/xtermjsxterm.js backend via @xterm/headless
@termless/ghosttyGhostty backend via ghostty-web WASM
@termless/vt100Pure TypeScript VT100 emulator, zero native deps
@termless/alacrittyAlacritty backend via alacritty_terminal (napi-rs)
@termless/weztermWezTerm backend via wezterm-term (napi-rs)
@termless/peekabooOS-level terminal automation (xterm.js + real app)
@termless/web-playerBrowser xterm.js player for .cast / .tape playback
@termless/vt100-rustRust vt100 crate via napi-rs (reference implementation)
@termless/libvtermneovim's libvterm via Emscripten WASM
@termless/ghostty-nativeNative Ghostty backend via napigen N-API bindings (ghostty-vt Zig module)
@termless/kittyKitty VT parser built from GPL source (not distributed)
@termless/cliCLI tools + MCP server for AI agents

How It Compares

FeatureTermlessManual string testingPlaywright
Speed<1ms/unit-style test (in-memory, no PTY)<1ms/test~100ms+/test
Terminal internalsScrollback, cursor, modes, cell attrsNoneN/A
ANSI awarenessFull (colors, bold, cursor)NoneN/A
Multi-backend10 terminal emulatorsN/A3 browsers
Protocol capabilitiesKitty, sixel, OSC 8, reflowNoneN/A
Wide char supportCell-level width trackingBrokenN/A
ScreenshotsSVG + PNG; optional browser PNGNonePNG (Chromium)
PTY supportSpawn real processesManualN/A

See Also

Termless is part of the terminal tools ecosystem:

  • vterm.js — full-featured terminal emulator (161/161 features on terminfo.dev), included as @termless/vterm
  • vt100.js — VT220-era baseline emulator, included as @termless/vt100
  • terminfo.dev — terminal feature compatibility database, powered by Termless
  • Silvery — React framework for terminal UIs (30+ components, incremental rendering)
  • Flexily — pure JS flexbox layout engine (Yoga-compatible, zero WASM)
  • Loggily — debug + structured logging + tracing in one library