FAQ
Do I need a real terminal to run tests?
No. Termless runs entirely headless with in-process terminal emulators. There is no display, no window, no GPU required. Tests run on bare CI machines, containers, and SSH sessions.
Which backend should I use?
Start with the default. @termless/test ships with xterm.js, which covers most testing needs. If you need higher fidelity:
| Backend | When to use it |
|---|---|
| xterm.js (default) | General-purpose testing. Ships with @termless/test. |
| vterm.js | Full standards compliance. 100% terminfo.dev coverage. Best for conformance testing. |
| Ghostty | Test against Ghostty's VT parser specifically. |
| vt100 | Zero native dependencies. Pure TypeScript. Good for environments where WASM or native modules are problematic. |
See Backend Capabilities for a full feature comparison.
Can I test Go/Rust/Python TUI apps?
Yes. Termless spawns any process via PTY -- it doesn't care what language the app is written in. If it runs in a terminal, Termless can test it:
await term.spawn(["./my-go-app"]) // Go (Bubbletea, etc.)
await term.spawn(["./target/release/app"]) // Rust (Ratatui, etc.)
await term.spawn(["python", "-m", "myapp"]) // Python (Textual, etc.)See Recipes for complete examples.
How is this different from pexpect?
pexpect matches text patterns on a raw byte stream. It has no understanding of the terminal -- it can't tell you what color a character is, whether text is bold, or where the cursor is.
Termless runs a real terminal emulator in-process. You get structured access to the full terminal state: cell attributes, cursor position, terminal modes, scrollback history, and more.
See Comparison for a detailed breakdown.
Does it work with Jest?
The custom matchers (toBeBold(), toContainText(), etc.) are built for Vitest. However, @termless/core is framework-agnostic -- you can use the Terminal API directly with any test runner:
// Works with any test framework
import { createTerminal } from "@termless/core"
import { createXtermBackend } from "@termless/xtermjs"
const term = createTerminal({ backend: createXtermBackend(), cols: 80, rows: 24 })
term.feed("Hello")
// Use your framework's built-in assertions
assert(term.screen.getText().includes("Hello"))
assert(term.cell(0, 0).bold === false)Can I use it without Vitest?
Yes. @termless/core is standalone -- no test framework dependency. You get the full Terminal API (feed, spawn, screen, cell, cursor, screenshots) and can use whatever assertion library you prefer. The @termless/test package adds Vitest-specific matchers and fixtures on top.
How do I test on CI?
Termless needs no display server. Add it to your CI pipeline the same way you'd run any Node.js/Bun test:
# GitHub Actions
- run: bun vitest run
# No xvfb, no DISPLAY, no Docker-in-DockerSee Recipes for full CI configuration examples.
What about Windows?
PTY features (term.spawn()) require a Unix PTY and only work on macOS and Linux. In-memory testing (term.feed()) and all pure backends work everywhere, including Windows.
How do I debug failing tests?
Three approaches, from quick to thorough:
1. Screenshot to SVG -- save the terminal state as a visual snapshot:
import { writeFileSync } from "node:fs"
// Inside a failing test:
writeFileSync("/tmp/debug.svg", term.screenshotSvg())2. Print the text -- dump what the terminal actually contains:
console.log(term.screen.getText())3. Debug logging -- enable verbose output for the Termless internals:
DEBUG=termless:* bun vitest run tests/my-test.tsHow fast are the tests?
In-memory tests (feed ANSI data, assert on state) typically run in under 1ms each. PTY tests that spawn real processes are slower -- startup time depends on the application, but Termless adds negligible overhead beyond process launch.
Can I test multiple backends in the same test suite?
Yes. Use createTestTerminalByName() to run the same test logic against different backends:
const backends = ["xtermjs", "vterm", "ghostty"]
for (const name of backends) {
test(`renders bold on ${name}`, async () => {
const term = await createTestTerminalByName({ backendName: name })
term.feed("\x1b[1mBold\x1b[0m")
expect(term.cell(0, 0)).toBeBold()
})
}See Multi-Backend Testing for patterns and strategies.
Does Termless support mouse events?
Yes. Send SGR mouse events with click() and dblclick():
term.click(10, 5) // Single click at column 10, row 5
await term.dblclick(10, 5) // Double-click
term.click(10, 5, { ctrl: true }) // Ctrl+clickThe target app must enable mouse tracking for these events to be received. See Best Practices for details.