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

tty-assert

v0.1.3

Published

PTY transcript assertions: ANSI strip, xterm replay, and explicit search surfaces for tests

Downloads

4,423

Readme

tty-assert

Node-only helpers for asserting on PTY (pseudo-terminal) transcripts: ANSI stripping, xterm.js replay, and explicit search surfaces so tests do not rely on “search the whole history and hope labels disambiguate.”

This package is test-runner neutral (no Cypress peer dependency). Integrators use it from Vitest, Playwright-sidecar tasks, or custom harnesses.

Imports: package.json exposes only the package root. Published installs resolve tty-assert to dist/ (compiled ESM and types). In this repo, tests and implementation use src/ via relative imports.

Internal layout: ansi/ (strip escapes), defaults/ (geometry), diagnostics/ (failure formatting and dump payloads), pty/ (buffered PTY), xterm/ (headless replay, viewport PNG/GIF, live-terminal surface attempts), surface/ (waitForTextInSurface, poll loop, cell expectations, strict errors), managed/ (managed session).


Strip vs replay vs locators

| Concept | What it is | Typical use | |--------|------------|-------------| | Stripped transcript | Cumulative PTY bytes with ANSI/OSC escapes removed (stripAnsiCliPty). One long string — not an emulator layout. | “Did this text ever appear in the session?” (plugin startup wait, many cumulative assertions). | | Viewport replay | Feed raw bytes through headless xterm, then read the visible viewport as plain text: one row per screen line, rows joined with \n (ptyTranscriptToViewportPlaintext / ptyTranscriptToVisiblePlaintextViaXterm). | Current screen plain text; product-specific parsing (e.g. “current guidance”) stays in adapters. | | Locator surfaces | After the same xterm replay, search a named slice of the buffer (waitForTextInSurface), or search the stripped transcript as a single haystack. | “Is this text visible in the viewable region?” vs “anywhere in scrollback + viewport?” vs “in the stripped log?” |

Stripped text and replayed viewport text differ: layout, wrapping, and scrollback mean a substring can appear in one model and not the other. Pick the surface that matches what the user sees for that assertion.


Flattening contract (locators)

Viewport replay (ptyTranscriptToViewportPlaintext): newline-joined rows (see implementation in src/xterm/ptyTranscriptToVisiblePlaintextViaXterm.ts).

waitForTextInSurface for xterm-backed surfaces (viewableBuffer, fullBuffer):

  • Search haystack: each row is cols cells; empty cells become a space; rows are concatenated with no \n between rows (row-major flat block).
  • Failure snapshots: newline-separated rows, each row trimEnd’d — readable and not identical to the flat search string.

For strippedTranscript, the haystack and snapshot are the same: the full ANSI-stripped string (no xterm geometry).


Assertion failure messages

When waitForTextInSurface or ManagedTtySession.assert fails, the message body includes:

  1. Detail and Search surface: "…" plus a short note (transcript vs row-major matching).
  2. A --- block with the snapshot as numbered lines ( 1 | …, split on \n). The block is truncated after numbering (about 8000 characters).
  3. ManagedTtySession.assert only: --- Final visible screen (viewport) --- plus the same numbered-line form of the xterm viewport (current visible rows), then a closing ---. This is the plain-text “screenshot” of what is on screen at failure time, before the cumulative transcript dump.
  4. A raw PTY appendix with ANSI stripped and safe-visible escaping, capped at about 12_000 visible characters.

Package root (tty-assert)

Exported today: startManagedTtySession, BufferedPtySession, and managed-session types (ManagedTtySession, ManagedTtyAssertInput, ManagedTtyAssertOptions, …). The return type of dumpDiagnostics() is internal to the package. Lower-level modules (waitForTextInSurface, ptySession, replay helpers, stripAnsi, …) are not separate entry points; they are composed internally and covered by this package’s unit tests.


Managed interactive session

Use this when one process owns the same PTY buffer, xterm headless instance, and assertion loop (retry + sync). That matches interactive CLI tests: start once, write many times, assert many times, dispose once.

Lifecycle

  1. startManagedTtySession(opts) — spawns the PTY (ptySession), returns ManagedTtySession.
  2. write / submit — send bytes or a line (\r appended for submit).
  3. assert(opts) — polls until timeout: syncs new raw bytes into xterm, then runs the same surface logic as waitForTextInSurface (or stripped-transcript path without replay). No need to pass an updated raw string from outside; it always reads session.buf.text.
  4. dumpDiagnostics() — async diagnostic snapshot (previews of viewport, stripped tail, etc.).
  5. getViewportAnimationPngs() / buildViewportAnimationGif() — when the session was started with startManagedTtySession, each PTY onData schedules a debounced viewport sample (deduped by viewport plaintext, ring buffer capped at 56 PNGs). buildViewportAnimationGif flushes the pending sample, then encodes those PNGs to an animated GIF (requires at least two distinct frames). Sessions from attachManagedTtySession without the internal bridge omit recording (empty PNG list; GIF build throws).
  6. dispose() — idempotent: tears down xterm, disposes the buffered PTY session. Safe if the child already exited.

ManagedTtyAssertInput is what assert(opts) accepts: same assertion knobs as waitForTextInSurface except raw is omitted, and needle / startAfterAnchor may use { source, flags? } instead of RegExp (normalized inside assert). After normalization, the shape is ManagedTtyAssertOptions (needle: string or RegExp only).

Integrators (e.g. Cypress): map a task such as cliAssert to managed.assert(payload) — no separate conversion step. On failure, save artifacts from getViewportAnimationPngs / buildViewportAnimationGif() as needed. The task body must stay JSON-serializable: use { source, flags? } instead of RegExp objects for needles and anchors.

Node tests: Prefer startManagedTtySession from tty-assert; do not round-trip PTY text through a browser.


waitForTextInSurface (internal module)

Used by ManagedTtySession.assert and unit-tested under tests/. Direct use is via relative imports inside this package (see those tests), not a separate publish entry.

// e.g. from tests/waitForTextInSurface.test.ts
import { waitForTextInSurface } from '../src/surface/waitForTextInSurface'

await waitForTextInSurface({
  raw: ptyBytes,
  needle: 'Expected',
  surface: 'viewableBuffer', // or 'fullBuffer' | 'strippedTranscript'
  timeoutMs: 3000, // default 0 = single attempt (good for Vitest)
  retryMs: 50,
  strict: true, // default; throws if multiple non-overlapping matches
})

After the needle matches, optional cellExpectations assert on xterm cells (string needle, viewableBuffer / fullBuffer only). Example — bold on the first match and palette 8 background on the last:

await waitForTextInSurface({
  raw: ptyBytes,
  needle: 'User paste',
  surface: 'viewableBuffer',
  strict: false,
  cellExpectations: [
    { match: 'first', expectations: [{ kind: 'allBold' }] },
    { match: 'last', expectations: [{ kind: 'allBgPalette', index: 8 }] },
  ],
})
  • viewableBuffer: lines from xterm baseY through length - 1 (the scrollable view’s active region, not the full scrollback).
  • fullBuffer: lines 0 through length - 1 (scrollback + active).
  • strippedTranscript: no replay; search stripAnsiCliPty(raw).
  • raw: string or a getter () => string so each poll can see updated buffer data.

Scripts

With Nix (recommended — includes libraries needed to build canvas and node-pty):

nix develop -c pnpm install
nix develop -c pnpm test
nix develop -c pnpm lint
nix develop -c pnpm format

Without Nix, install Node 22, pnpm, and native deps for canvas (see CI workflow), then:

pnpm install
pnpm test
pnpm lint
pnpm format

Releasing

Tagged v* pushes run .github/workflows/release.yml, which lint-checks, tests, builds, and publishes to npm (Trusted Publishing / OIDC). Full steps, changelog format, and troubleshooting: docs/release.md.