npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@microsoft/tui-test

v0.1.1

Published

Node bindings for the tui_test terminal engine

Readme

@microsoft/tui-test

Control, inspect, and test terminal apps from JavaScript or TypeScript.

Install

npm install @microsoft/tui-test

Node 20+ is supported. The package is ESM only. Bun and Deno support is best effort. Deno 2 needs local node_modules and --allow-ffi.

Each platform package contains the tui-test-ffi shared library. The JavaScript adapter uses Koffi to call the same typed C ABI as Go and Python. It copies native results before it frees their Rust allocations. The library stays loaded until process exit on all supported platforms. Native operations can finish after JavaScript worker exit. Native thread teardown releases unclaimed task handles and results without canceling operations or closing shared sessions. Each binding copy uses its own anonymous Koffi layouts, so copies can share one Koffi module without type-name collisions. Koffi supplies its own native addon; this package no longer builds a separate N-API adapter. Keep optional dependencies enabled during installation; they contain the platform binaries for both packages. The package needs no CLI. Set TUI_TEST_NATIVE_LIBRARY to an absolute path to select a library from the same source revision.

For a local build, run npm ci, then npm run build. Use npm run build:native:debug for a debug build. These commands build tui-test-ffi and put the shared library in native/. Run cargo run --locked -p tui-test-bindgen from the repository root to regenerate the native declarations.

For a distributable GNU Linux build, use:

npm run build:native -- --zig --target x86_64-unknown-linux-gnu

This requires Docker and builds the shared engine inside manylinux 2.28. The container constrains both Cargo and Ghostty to the supported libc. Use aarch64-unknown-linux-gnu for ARM64, with Docker emulation on an x86_64 host. Musl builds with --zig still use the local Rust and Zig toolchains.

Quick start

import { TuiTest } from "@microsoft/tui-test";

const terminal = TuiTest.ephemeral();

try {
  await terminal.run(process.execPath, [
    "-e",
    "console.log('Ready'); setInterval(() => {}, 1000)",
  ]);
  await terminal.getByText("Ready").expect();
  console.log(await terminal.text());
} finally {
  await terminal.closeQuiet();
}

API

TuiTest

new TuiTest(session?: string, options?: ClientOptions)

| Option | Type | Default | | --- | --- | --- | | session | string | TUI_TEST_SESSION or "default" | | backend | "alacritty" \| "ghostty" \| "rio" \| "xtermjs" | "alacritty" | | timeouts | Timeouts | built-in defaults | | profile | Profile | built-in profile | | screenHistoryLimit | number | 10 | | artifacts | { dir, onFailure?, includeRecording? } | off | | recording | { directory? } | default recording directory | | trace | { mode?, directory? } | { mode: "off", directory: ".tui-test/traces" } |

Set trace.mode to "on" for every session or "on-failure" for failures. Users can open trace.html; agents should read trace.md, trace.json, and timeline.json.

artifacts.onFailure selects "none", "text", "html", or "all". Use includeRecording to include the cast in failure artifacts.

Properties

| Property | Type | | --- | --- | | session | string | | keyboard | Keyboard | | mouse | Mouse |

Lifecycle

| Method | Description | | --- | --- | | TuiTest.ephemeral(prefix?, options?) | Create a unique session. | | open(options?) | Open a shell. | | run(program, args?, options?) | Run a program. | | restart(options?) | Restart the session. | | close() | Close the session. | | closeQuiet() | Close without throwing. | | [Symbol.asyncDispose]() | Close from await using. |

open() options are shell, backend, cols, rows, cwd, env, waitReady, restart, retries, profile, timeouts, and screenHistoryLimit. run() accepts the same options except shell.

The default size is 80 by 30. Timeout defaults are 5 seconds for text and idle, and 30 seconds for command, exit, and ready.

Input

| Method | Description | | --- | --- | | submit(text?) | Type text and press Enter. | | type(text) | Type text. | | write(data) | Write raw bytes. | | press(...keys) | Alias for keyboard.press(). | | resize(cols, rows) | Resize the terminal. | | scroll(lines) | Move through native history; positive values show older output. | | scrollToTop() / scrollToBottom() | Show the oldest output or the live screen. | | getViewport() | Return { offset, history }, in rows. | | signal(name) | Send INT, TERM, KILL, or QUIT. | | kill() | Kill the child process. |

Native history

scroll(lines) accepts integers from -2147483648 through 2147483647. Positive values show older output. Negative values move toward the live screen. Zero leaves the viewport unchanged. Movement stops at the available history boundaries. getViewport() returns a typed Viewport with numeric offset and history fields. Both fields count rows. An offset of zero means the live screen. Counts outside JavaScript's safe integer range produce an error instead of lost precision.

Visible text, locators, and captures use this viewport. text({ full: true }) remains independent: it returns history plus the live screen. New output preserves the historical view until retained history expires. Keyboard input returns to the live screen. Mouse input and historical locator clicks fail while the viewport shows history. Call scrollToBottom() before mouse input. This API is separate from mouse.scroll().

getCursor() and State.cursor retain the application cursor coordinates. Captures project that cursor into the viewport and hide it outside the view. Raster recordings capture viewport changes. Asciicast remains a PTY stream.

After a source build, run node examples/scrollback.mjs from this directory. The example prints the live screen, history view, and unchanged full text.

State

| Method | Returns | | --- | --- | | state() | State | | text({ full? }) | string | | cells(x, y, w?, h?) | Cell[] | | getCommand() | string \| null | | getOutput() | string \| null | | getExitCode() | number \| null | | getCwd() | string \| null | | getCursor() | Cursor | | getSize() | Size | | getTitle() | string \| null | | getClipboard() | string | | getBellCount() | number | | getBellEvents() | BellEvent[] |

Waits and assertions

| Method | Description | | --- | --- | | waitTitle(text, { regex?, not?, timeout? }) | Wait for a title. | | waitClipboard(pattern?, { timeout? }) | Wait for a clipboard change or match. | | waitIdle({ timeout? }) | Wait for the screen to stop changing. | | waitCommand({ timeout? }) | Wait for a submitted command. | | waitExit({ timeout? }) | Wait for the program to exit. | | waitReady({ timeout? }) | Wait for a shell prompt. | | waitBell({ timeout? }) | Wait for a bell. | | expectTitle(text, { regex?, not?, timeout? }) | Assert the title. | | expectExitCode(code, { timeout? }) | Assert the last exit code. | | expectOutput(text, { regex? }) | Assert command output. | | expectBellCount(count, { timeout? }) | Wait until the cumulative bell count reaches count. | | expectSnapshot(name, options?) | Assert or update a snapshot. |

waitClipboard() waits for the next change. A string matches text. A RegExp keeps its JavaScript flags.

Snapshot options are update, includeStyle, and includeTitle.

Capture

| Method | Description | | --- | --- | | screenshot(path?, { full?, zoom?, background?, transparent? }) | Return text or save SVG or PNG. | | startRecording(path, options?) | Start APNG, GIF, MP4, or asciinema recording. | | stopRecording() | Finish the recording and return its path. |

Recording options: format, fps, speed, idleTimeLimit, zoom, background, and transparent. MP4 requires ffmpeg and does not support transparency.

The extension selects the format: .png or .apng, .gif, .mp4, or .cast. format overrides it.

Locator

Locators resolve against the latest terminal screen before every read or action.

const save = terminal
  .getByText("Settings")
  .getByText("Save", { direction: "after" })
  .getByStyle({ foreground: "green" })
  .unique();

await save.click();

Create a locator

| Method | Options | | --- | --- | | terminal.getByText(text, options?) | regex, full, whitespace | | terminal.getByStyle(style, options?) | full | | terminal.getByLink(uri, options?) | full | | locator.getByText(text, options?) | regex, full, whitespace, direction | | locator.getByStyle(style, options?) | full, direction | | locator.getByLink(uri, options?) | full, direction |

whitespace is "exact" or "normalize". direction is "within", "after", or "before".

Style fields are foreground, background, bold, dim, italic, underlineStyle, underlineColor, inverse, hidden, strikethrough, and blink.

getByLink(uri) matches an exact OSC 8 target, not visible URL text; getByLink("") requires no link. Root style/link selectors find runs within each row. Chained calls with the default within direction check whole matches. Styles skip blanks if visible text exists; links check every cell.

Compose locators

const link = terminal.getByLink("https://example.com");
const bold = terminal.getByStyle({ bold: true });
const boldLinkCells = bold.and(link);
const either = link.or(terminal.getByText("Help"));
const sections = terminal.getByText("Docs and Help");
const containsLink = sections.filter({ has: link });
const withoutOldText = sections.filter({ hasNot: terminal.getByText("old") });
const entirelyLinked = sections.getByLink("https://example.com");

.and() keeps shared cells; .or() combines cells without duplicates. Adjacent cells merge within each physical row, even across original matches. Gaps and row breaks split runs. Counts and clicks use these runs, not Playwright element identities. Text keeps exact whitespace.

filter accepts only locators. has requires a match inside each candidate; hasNot requires none. Both conditions apply when supplied, and the inner match may cover the whole candidate. For partially linked "Docs", filter({ has: link }) keeps the whole word, getByLink(uri) rejects it, and .and(link) returns its linked cells.

Use locators from one TuiTest. Composition leaves them unchanged and reads one fresh snapshot when used. Selection order matters: a.first().and(b) differs from a.and(b).first(). Any full branch includes scrollback for the whole query. Errors, including unique() failures, propagate.

Select matches

| Method | Description | | --- | --- | | any() | Keep all matches. | | unique() | Require one match. | | first() | Select the first match. | | last() | Select the last match. | | nth(index) | Select a zero-based match. |

Read and act

| Method | Description | | --- | --- | | locations() | Return all selected locations. | | location() | Return one location. | | count() | Return the current count. | | all() | Return one locator per current match. | | wait({ state?, timeout? }) | Wait for "visible" or "hidden". | | expect({ not?, timeout? }) | Assert the locator. | | click(options?) | Click the middle cell. | | highlight({ timeout? }) | Highlight matches. |

click() accepts button, alt, ctrl, shift, clicks, and timeout. button is "left", "middle", or "right".

location() and click() require one match. all() does not wait.

Keyboard

| Method | Description | | --- | --- | | press(...keys) | Press keys. | | down(...keys) | Send keydown events. | | repeat(...keys) | Send repeat events. | | up(...keys) | Send keyup events. |

await terminal.keyboard.press("Ctrl+C");
await terminal.keyboard.press("Escape", ":", "w", "q", "Enter");

Named keys include Up, Down, Left, Right, Home, End, PageUp, PageDown, Insert, Delete, Backspace, Tab, Enter, Space, Escape, and F1 through F12. Join modifiers such as Ctrl, Alt, Shift, Super, Meta, or Hyper with +.

Mouse

Coordinates are zero-based terminal cells.

| Method | Description | | --- | --- | | click(x?, y?, options?) | Click a cell or onText. | | move(x, y) | Move the pointer. | | down(x, y, options?) | Press a button. | | up(x, y, options?) | Release a button. | | drag(x1, y1, x2, y2, options?) | Drag between cells. | | scroll("up" \| "down", { amount? }) | Scroll. |

Button options are button, alt, ctrl, and shift. Click also accepts onText and clicks.

await terminal.mouse.click(10, 5, { button: "right", ctrl: true });

Module functions

| Function | Description | | --- | --- | | sessions() | List sessions in this process. | | closeAll() | Close all sessions in this process. | | getRecording(session?) | Return an automatic asciinema recording. | | uniqueSession(prefix?) | Create a unique session name. |

Test helpers

Import from @microsoft/tui-test/test.

| Function | Description | | --- | --- | | createTerminal(options?) | Create, open, and track a terminal. | | withTerminal(options, callback) | Run a callback and close the terminal. | | closeAllTracked() | Close tracked terminals. | | setTerminalDefaults(options) | Set suite defaults. | | resetTerminalDefaults() | Reset suite defaults. | | trackTerminal(terminal) | Track a terminal. | | untrackTerminal(terminal) | Stop tracking a terminal. | | trackedCount() | Count tracked terminals. | | terminalSnapshot(text) | Normalize text for snapshots. |

CreateTerminalOptions adds shell, program, session, and prefix to the client and spawn options.

defaultShell is the platform default.

import { withTerminal } from "@microsoft/tui-test/test";

await withTerminal({ program: ["my-app"] }, async (terminal) => {
  await terminal.getByText("Ready").expect();
});

Configuration

const terminal = new TuiTest("test", {
  profile: {
    scrollback: 500,
    colors: { foreground: "#ffffff", background: "#000000" },
  },
  timeouts: { text: 10_000, command: 60_000 },
  screenHistoryLimit: 10,
  artifacts: {
    dir: "artifacts/failures",
    onFailure: "all",
    includeRecording: true,
  },
  trace: { mode: "on-failure", directory: "artifacts/traces" },
  recording: { directory: "artifacts/casts" },
});

Types

| Type | Description | | --- | --- | | State | Session state and visible text. | | Cell | One terminal cell and its style. | | TextMatch | Matched text, positions, and spans. | | TextStyleExpectation | Locator style fields. | | Profile, Colors | Scrollback and colors. | | Timeouts | Text, idle, command, exit, and ready timeouts. | | AutomaticRecording | Automatic recording mode and directory. | | FailureDetails | Structured operation, locator, process, runtime, and screen evidence. | | FailureArtifactRef | Paths and write status for a failure artifact. | | MouseButton | "left", "middle", or "right". | | MouseClickOptions | Button, modifiers, target text, and click count. | | MouseButtonOptions | Button and modifiers. | | LocatorClickOptions | Button, modifiers, click count, and timeout. | | OpenResult | Session start result. |

VERSION contains the package version.

Errors

| Error | exitCode | | --- | --- | | ExpectationError | 1 | | UsageError | 2 | | NoSessionError | 3 | | InternalError | 5 |

All errors extend TuiTestError and include kind and exitCode. Structured native failures expose details and artifact; expectation errors continue to populate compatibility terminal.text and terminal.screenshot fields. Failure artifacts can contain terminal output, titles, locator operands, and recordings, so review them before uploading.

Sessions are local to the current process and cannot be controlled by the CLI. Cancelling a promise does not stop an active terminal operation.