tuistory — ターミナルUI向けのPlaywrightライクなテストライブラリ

Library

概要

tuistoryは、ターミナルベースのユーザーインターフェース(TUI)アプリケーション向けに設計されたテストライブラリです。Playwrightのアイデアをターミナルに持ち込み、プロセスの起動や端末サイズ(cols/rows)の指定、出力に対する待機(waitForText)や入力送信などを非同期APIで提供します。Bun向けの配布が想定され、TypeScriptで実装されているためモダンなJS/TSエコシステムに自然に馴染みます。CLIツールや対話型アプリのE2Eテストをプログラム的に書きたい開発者向けのライブラリです。

GitHub

リポジトリの統計情報

  • スター数: 10
  • フォーク数: 0
  • ウォッチャー数: 10
  • コミット数: 8
  • ファイル数: 7
  • メインの言語: TypeScript

主な特徴

  • Playwrightに似たAPIデザインでターミナルアプリのE2Eテストを直感的に記述可能
  • コマンド起動、TTYサイズ指定、出力の待機・検証、入力送信など基本操作をシンプルに提供
  • Bunでのインストールが想定された軽量パッケージ(bun.lockを含む)
  • TypeScript実装で型安全、モダンな開発体験に対応

技術的なポイント

tuistoryは「ターミナルのためのPlaywright」というコンセプトを中心に、CLIアプリケーションの自動化で必要となる要素を揃えています。主な機能はプロセスの起動(launchTerminal)、端末の列数/行数(cols/rows)を指定して疑似TTY環境を構築する点、標準出力/標準エラーの内容に対して特定文字列が出るまで待機するwaitForTextのような同期的な検証APIを備える点です。これにより、対話的なプロンプトや段階的に更新されるUI、ページネーションされた出力などに対して確実にアサーションを行えます。

内部ではNode系のプロセス制御(またはBunのランタイムAPI)を利用して擬似TTY(pseudoterminal)を生成し、アプリと双方向通信(入力送信と出力取得)を行う設計が想定されます。TypeScriptで型定義が行われており、async/awaitベースのAPIで直感的に記述できるためテストコードの読みやすさが高いです。タイムアウトや部分一致、正規表現によるマッチングといった待機条件の指定が可能で、リアルタイムに変化する画面にも対応します。

パッケージはbun add tuistoryでインストールできる点から、Bun環境での利用を考慮した構成になっています。リポジトリ構成は小規模で、READMEやCHANGELOG、package.json、bun.lockなど基本ファイルを備え、早期段階のプロジェクトであることが窺えます。テストフレームワーク自体を内包するのではなく、既存のテストランナー(JestやVitestなど)と組み合わせてE2Eの一部として利用する設計が想像され、CI環境での自動実行やスクリーンショット取得(将来的な拡張)など拡張の余地もあります。

現状ではコミット数・ファイル数ともに小規模なため、APIの安定性やエッジケースの対応(複雑な端末制御、カラーコードやカーソル移動の処理、シグナルハンドリングなど)は今後の発展が期待されます。導入時はREADMEのUsage例に従い、まずは基本的な起動/出力待機/入力送信のワークフローを確認すると良いでしょう。

プロジェクトの構成

主要なファイルとディレクトリ:

  • .gitignore: file
  • CHANGELOG.md: file
  • README.md: file
  • bun.lock: file
  • package.json: file

…他 2 ファイル

まとめ

ターミナルUIのE2EテストをPlaywrightライクに扱える軽量ライブラリ。導入が容易で将来性あり。

リポジトリ情報:

READMEの抜粋:



tuistory

Playwright for terminal user interfaces

Write end-to-end tests for terminal applications



Installation

bun add tuistory

Usage

import { launchTerminal } from 'tuistory'

const session = await launchTerminal({
  command: 'claude',
  args: [],
  cols: 100,
  rows: 30,
})

await session.waitForText('claude', { timeout: 10000 })

const initialText = await session....