Skip to content

Migrate to Termwright

Use this guide when you already test a CLI or TUI with another harness. If you know Playwright or Cypress but are starting a terminal test suite, see Coming from Playwright or Cypress instead.

Use mountInk() as the in-process replacement for render(). It supports Ink 7.1.1 and adds terminal input, resize, semantic locators, and retained traces beyond lastFrame() string assertions. Focus is available only when the application exposes it.

// before
import { render } from 'ink-testing-library';
const { lastFrame, stdin, rerender } = render(<Approve />);
stdin.write('\r');
expect(lastFrame()).toContain('approved');
// after
import { mountInk } from 'termwright/ink';
const harness = await mountInk(<Approve />);
await harness.press('Enter');
await harness.waitForText('approved');
await harness.close();
ink-testing-libraryTermwright
render(<App />)await mountInk(<App />)
lastFrame()harness.screen().text(), waitForText(), or toHaveText()
framesretained trace recording
stdin.write('\r')harness.press('Enter')
rerender(<App />)await harness.rerender(<App />)
unmount()await harness.close()

Calls are asynchronous because they wait for rendered terminal state. Add the Ink integration when the test needs semantic roles, names, focus, or state.

Translate stream expectations to terminal state and input operations:

# before
child.expect('Permission required')
child.sendline('y')
child.expect('running:')
// after
await app.waitForText('Permission required');
await app.press('y');
await app.waitForText('running:');

Use waitForText() for rendered output and press(), type(), or paste() for input. Termwright models the current terminal grid rather than matching only an output stream, and a failed test can retain a replayable trace.

There is no send / expect(pattern) compatibility API.

From a custom PTY, spawn(), or tmux harness

Section titled “From a custom PTY, spawn(), or tmux harness”

Keep the executable and user-visible scenarios. Replace process lifecycle, terminal parsing, polling, and artifact collection with the corresponding Termwright surfaces:

Existing harnessTermwright
spawn() or PTY setupterminal.launch()
raw stdout bufferapp.screen()
polling loopwaitForText() or a retrying assertion
stdin writespress(), type(), or paste()
shared fixture directorylaunch({files}) or launch({template})
transcript on failureretained trace and HTML report

Start by preserving the existing keyboard-driven workflow. Add semantic locators only after the relevant framework integration is running and verified.