← All tools

beorn/termless

Popularity 75 Updated Network & Systems

Like Playwright, but for terminal apps — headless testing across xterm.js, Ghostty, Alacritty, WezTerm, and more

githubauto-collected

Installation

A directly usable install command is not verified yet. Check the project documentation or releases.

Termless

Headless terminal testing library. Like Playwright, but for terminal apps.

Terminal apps are hard to test because the terminal is a black box — you can see text on screen but can't programmatically inspect colors, cursor position, scrollback history, terminal modes, or cell attributes. Termless opens up the entire terminal buffer for structured testing, and runs the same tests against multiple terminal emulators to catch cross-terminal compatibility issues.

Two audiences use termless. Most people are here to test a TUI app — that's the Playwright-for-terminals pitch above, and it stays the headline. A second, growing audience builds terminal emulators and uses termless's differential conformance corpus to grade a new backend against the field; see Conformance corpus.

Built alongside silvery, a React TUI framework, but works with any terminal app.

  • Full terminal internals -- access scrollback, cursor state, cell colors, terminal modes, alt screen, resize behavior — everything that's invisible to string matching
  • Cross-terminal conformance -- run the same tests against xterm.js, Ghostty, Alacritty, WezTerm, vt100, vt100-rust, libvterm, Kitty, and Peekaboo to find where terminals disagree
  • Composable region selectors -- term.screen, term.scrollback, term.cell(r, c), term.row(n) for precise assertions
  • 21+ Vitest matchers -- text, cell style, cursor, mode, scrollback, and snapshot matchers
  • SVG & PNG screenshots -- fast default renderers (PNG via optional @resvg/resvg-js), plus optional Playwright/Chromium rendering for browser-shaped text
  • PTY support -- spawn real processes, send keypresses, wait for output
  • Fast -- typically under 1ms per unit-style test (in-memory backend, no PTY). No Chromium, no subprocesses
  • Recording & Playback -- record terminal sessions as .tape files, play back as GIF, animated SVG, APNG, or asciicast. Cross-terminal comparison in one command
  • Automation tooling -- termless record / termless play for scripting, plus an optional MCP server for AI agents

Quick Start

Install from npm; a git install resolves the TypeScript source and runs only under Bun.

import { createTerminal } from "@termless/core"
import { createXtermBackend } from "@termless/xtermjs"

const GREEN = (s: string) => `\x1b[38;2;0;255;0m${s}\x1b[0m`

const term = createTerminal({ backend: createXtermBackend(), cols: 80, rows: 24 })
term.feed(GREEN("● API online"))

// String matching sees text. termless sees everything.
term.screen.getText() // "● API online"
term.cell(0, 0).fg // { r: 0, g: 255, b: 0 } — the color getText() can't see

### Spawn a real process

const term = createTerminal({ backend: createXtermBackend(), cols: 120, rows: 40 }) await term.spawn(["my-tui-app"]) await term.waitFor("ready")

// Keyboard input term.press("ArrowDown") term.type("search query")

// Mouse input term.click(10, 5) // click at column 10, row 5 await term.dblclick(10, 5) // double-click (async — two clicks with delay) term.click(10, 5, { ctrl: true }) // ctrl+click

// Region selectors — inspect specific parts of the terminal console.log(term.screen.getText()) // visible area console.log(term.scrollback.getText()) // history above screen console.log(term.row(0).getText()) // first row console.log(term.lastRow().getText()) // last row console.log(term.out.getText()) // raw output bytes, including OSC/APC/CSI protocols

const svg = term.screenshotSvg() const png = await term.screenshot() // auto-picker: native canvas (@napi-rs/canvas + ghostty-web) with resvg fallback const canvasPng = await term.screenshotCanvasPng() // explicit native-canvas (requires @termless/ghostty) const resvgPng = await term.screenshotPng() // explicit resvg (requires @resvg/resvg-js) await term.close()


### Write tests

import { test, expect } from "vitest" import { createTestTerminal } from "@termless/test"