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 & Playback

Record terminal sessions and play them back as animated output -- no ffmpeg, no Chromium, no external tools:

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

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

# Cross-terminal comparison — same tape, different backends
$ termless play -b vterm,ghostty --compare side-by-side demo.tape

Output formats: GIF, animated SVG, APNG, PNG, asciicast v2 -- all rendered with pure JS encoders. See Recording & Playback 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 (includes xterm.js backend)
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)
Spawn and test real processes via PTYBuilt into @termless/core (used via any backend)
Automate a real terminal app (OS-level)@termless/peekaboo
Use the CLI or MCP server@termless/cli

Most users only need @termless/test -- it includes everything for writing Vitest terminal tests with the xterm.js backend. Add extra backend packages only if you want multi-backend testing.

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-nativelibghostty-vt 1.3Native Ghostty via Zig 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/vt100-rustRust vt100 crate via napi-rs (reference implementation)
@termless/libvtermneovim's libvterm via Emscripten WASM
@termless/ghostty-nativeNative Ghostty backend via Zig N-API bindings (libghostty-vt)
@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/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 (no Chromium)NonePNG (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