Install and run your first test
This tutorial starts a real terminal program, presses a key, and checks the rendered result. It does not require a framework integration.
Install Termwright
Section titled “Install Termwright”- Use Node.js 22 or 24. Other major versions are not supported.
- You do not need to install Vitest separately. Termwright includes Vitest 4.1.11.
The native PTY package supports macOS 13.5+, Windows 10 1809+ or Server 2019+, and glibc 2.35+ Linux. Alpine/musl is not supported. See supported platforms for architectures and other limits.
Install the termwright package:
npm install --save-dev termwrightThen check that Termwright can load its test engine and native PTY backend:
npx termwright doctorThe command exits with code 0 when each required check passes.
Create a program to test
Section titled “Create a program to test”Save this as app.mjs. The .mjs extension keeps the example self-contained
and does not require changing your project’s module type.
import readline from 'node:readline';
readline.emitKeypressEvents(process.stdin);process.stdin.setRawMode?.(true);
process.stdout.write('Permission required\n[Approve] Reject\n');process.stdin.once('keypress', (_input, key) => { if (key.name === 'return') { process.stdout.write('running: ls -la\n'); process.exit(0); }});The program waits for Enter before printing its result. It uses raw input, so a
plain redirected stdin/stdout test would not reproduce how it runs in a
terminal.
Write the test
Section titled “Write the test”Create tests/permission.test.ts:
import { fileURLToPath } from 'node:url';import { expect, test } from 'termwright/test';
const program = fileURLToPath(new URL('../app.mjs', import.meta.url));
test('approves a command', async ({ terminal }) => { const app = await terminal.launch({ command: [process.execPath, program] });
await app.waitForText('Permission required'); await app.press('Enter');
await expect(app).toHaveText('running: ls -la');});Run the test:
npx termwright testThe command reports one passing test and exits with code 0. Termwright closes the application and removes the test’s temporary working directory after the test completes.
A runnable version of this example is available in
examples/getting-started.
Inspect a failure
Section titled “Inspect a failure”Change the expected text to running: pwd and run the test again. The assertion
waits for the configured timeout, reports the observed terminal state, and keeps
a trace for the failed attempt.
Open the Runner:
npx termwright uiThe Runner starts in watch mode and will see the failed test in the current project. Select its failed attempt to inspect the terminal, steps, and replay timeline. Put the original expectation back when you are done.
Test your own program
Section titled “Test your own program”Replace program with the command your users run. Keep command arguments as
separate array items:
const app = await terminal.launch({ command: ['my-cli', 'deploy', '--environment', 'staging'],});Use text and keyboard APIs for a black-box test. If your application uses Ink, OpenTUI, Textual, tview, Bubble Tea, or Ratatui, follow its integration guide before using controls by role or label. With an integration enabled:
const approve = app.getByRole('button', { name: 'Approve' });await expect(approve).toBeAttached();await app.press('Enter');await expect(app.getByRole('status')).toHaveText('Approved');