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

@akaoio/tester

v1.1.0

Published

Comprehensive terminal application testing framework with advanced assertions, deep state inspection, and test orchestration

Readme

Tester - Advanced Terminal Application Testing Framework

Go Reference Go Report Card License: MIT GitHub release Go Version PRs Welcome

A powerful Go framework for testing terminal applications (TUI) in real-time, with crash detection, performance monitoring, and visual verification.

Features

🔍 Comprehensive Testing

  • Virtual Terminal Emulation - Run real terminal apps in PTY
  • Keyboard Simulation - Send any key combination
  • Screen Capture - ASCII screenshots of terminal state
  • Layout Verification - Check positioning, centering, borders

🛡️ Reliability Testing

  • Crash Detection - Detect segfaults, panics, OOM kills
  • Advanced Hang Detection - Multi-layer hang prevention system
  • Memory Leak Detection - Monitor memory usage patterns
  • Performance Monitoring - Track CPU, memory, response times

🚨 Hang Prevention System (NEW!)

  • Watchdog Protection - Prevents tester from hanging when apps hang
  • Operation Timeouts - Configurable timeouts for all operations
  • Emergency Stop - Force termination of problematic applications
  • Graceful Recovery - Automatic cleanup and restart capabilities

🎨 Visual Testing

  • Color/ANSI Verification - Validate color output
  • Responsive Testing - Test different terminal sizes
  • Theme Testing - Verify theme changes
  • Unicode Support - Test international characters

📊 Reporting

  • Detailed Reports - Markdown reports with metrics
  • ASCII Screenshots - Visual proof of UI states
  • Performance Metrics - Response times, resource usage
  • Test Logs - Complete execution traces

Installation

go get github.com/akaoio/tester

Quick Start

package main

import (
    "log"
    "time"
    "github.com/akaoio/tester"
)

func main() {
    // Create virtual terminal with built-in hang protection
    term := tester.NewTerminal(80, 24)
    defer term.Close()
    
    // Configure timeouts (optional - has sensible defaults)
    term.SetTimeouts(30*time.Second, 5*time.Second) // global, operation
    
    // Start your app (protected against hanging)
    err := term.Start("./myapp")
    if err != nil {
        log.Fatal(err)
    }
    
    // All operations are now hang-protected
    term.Wait(500 * time.Millisecond)
    term.SendKeys("hello world")     // ✅ Won't hang
    term.SendKeys("<Enter>")         // ✅ Won't hang  
    term.SendKeys("<Ctrl+C>")        // ✅ Won't hang
    
    // Screenshot with protection
    screenshot := term.Screenshot()
    fmt.Println(screenshot)
    
    // Verify content
    if term.Contains("expected text") {
        fmt.Println("Test passed!")
    }
    
    // Check health + hang status
    health := term.Health()
    if health.Crashed {
        log.Fatalf("App crashed: %s", health.CrashReason)
    }
    
    if term.IsHanging() {
        log.Println("App is hanging - watchdog will handle it")
    }
}

Advanced Usage

Stress Testing

// Test crash scenarios
tester := tester.NewStressTester("./myapp")

// Try various crash scenarios
tester.TestCrash(tester.RapidInput)
tester.TestCrash(tester.InvalidEscape)
tester.TestCrash(tester.BufferOverflow)
tester.TestCrash(tester.RapidResize)

// Check for memory leaks
leaks := tester.DetectMemoryLeaks()
if len(leaks) > 0 {
    log.Printf("Memory leak detected: %+v", leaks)
}

// Monitor performance
metrics := tester.GetPerformanceMetrics()
fmt.Printf("Avg Response Time: %v\n", metrics.AvgResponseTime)
fmt.Printf("Peak Memory: %d MB\n", metrics.PeakMemory/1024/1024)

Visual Testing

// Test different screen sizes
sizes := []tester.Size{
    {30, 10},  // Watch
    {50, 20},  // Mobile
    {80, 24},  // Standard
    {120, 40}, // Desktop
}

for _, size := range sizes {
    term := tester.NewTerminal(size.Width, size.Height)
    term.Start("./myapp")
    
    // Verify layout adapts
    if !term.VerifyLayout(tester.Centered) {
        log.Printf("Layout broken at %dx%d", size.Width, size.Height)
    }
    
    term.Close()
}

Color Verification

term := tester.NewTerminal(80, 24)
term.Start("./myapp")

// Get color report
colors := term.GetColorReport()

if !colors.HasColors {
    log.Fatal("No colors detected")
}

if colors.Has256Colors {
    fmt.Println("256 color support verified")
}

fmt.Printf("ANSI sequences used: %d\n", colors.TotalSequences)

Test Suite Builder

suite := tester.NewSuite("MyApp Tests")

// Add test cases
suite.AddTest(tester.TestCase{
    Name: "Startup Test",
    Steps: []tester.Step{
        {Action: "wait", Duration: 500*time.Millisecond},
        {Action: "screenshot"},
    },
    Assertions: []tester.Assertion{
        {Type: "contains", Expected: "Welcome"},
        {Type: "no_crash"},
    },
})

suite.AddTest(tester.TestCase{
    Name: "Keyboard Navigation",
    Steps: []tester.Step{
        {Action: "keys", Input: "<Tab>"},
        {Action: "keys", Input: "<Enter>"},
        {Action: "screenshot"},
    },
    Assertions: []tester.Assertion{
        {Type: "contains", Expected: "Selected"},
    },
})

// Run all tests
report := suite.Run("./myapp")
report.SaveMarkdown("test_report.md")
report.SaveJSON("test_report.json")

Hang Prevention & Recovery

// Configure aggressive hang protection for problematic apps
term := tester.NewTerminal(80, 24)
defer term.Close()

// Set strict timeouts
term.SetTimeouts(10*time.Second, 2*time.Second) // global, operation

// Configure watchdog behavior
term.ConfigureWatchdog(tester.WatchdogConfig{
    MaxResponse:      1 * time.Second,   // Max response time
    CheckInterval:    200 * time.Millisecond, // Check frequency  
    ForceKillTimeout: 1 * time.Second,   // Time before force kill
})

// Start potentially problematic app
err := term.Start("./problematic-app")
if err != nil {
    log.Fatal(err)
}

// All operations are protected - won't hang the tester
err = term.SendKeys("some input that might cause hang")
if err != nil {
    log.Printf("Operation failed safely: %v", err)
}

// Check if hang was detected
if term.IsHanging() {
    log.Println("App is hanging, but tester continues")
    
    // Get watchdog statistics  
    stats := term.GetWatchdogStats()
    log.Printf("Watchdog interventions: %d timeouts, %d force kills", 
        stats.TimeoutCount, stats.ForceKillCount)
}

// Emergency stop if needed
if term.IsHanging() {
    term.ForceStop("Manual intervention")
}

// Use timeout wrapper for custom operations
err = term.WithTimeout("custom_operation", 3*time.Second, func() error {
    // Your custom operation that might hang
    return doSomethingRisky()
})

Testing Hanging Applications

The tester framework now includes robust protection against hanging applications:

Problem Solved

  • Before: Testing hanging apps would freeze the entire test suite
  • After: Watchdog system detects and terminates hanging apps automatically
  • Result: Test suites never hang, always complete with results

Example: Testing Dex Project

// Test the hanging dex project safely
func TestDexWithHangProtection(t *testing.T) {
    term := tester.NewTerminal(120, 40)
    defer term.Close()
    
    // Configure for known problematic app
    term.SetTimeouts(10*time.Second, 3*time.Second)
    
    // Start dex (known to hang)
    err := term.Start("./dex")
    if err != nil {
        t.Fatalf("Failed to start: %v", err)
    }
    
    // These operations won't hang the test
    term.SendKeys("test input")
    screenshot := term.Screenshot()
    
    // Test completes even if dex hangs
    stats := term.GetWatchdogStats()
    t.Logf("Watchdog protected against %d hangs", stats.HangCount)
}

Real-World Example - Testing a TUI App

func TestTUIApp(t *testing.T) {
    term := tester.NewTerminal(80, 24)
    defer term.Close()
    
    // Start app
    err := term.Start("./tui-app")
    require.NoError(t, err)
    
    // Test menu navigation
    term.SendKeys("<Down>")
    term.SendKeys("<Down>")
    term.SendKeys("<Enter>")
    
    // Verify we're in settings
    assert.True(t, term.Contains("Settings"))
    
    // Test form input
    term.SendKeys("John Doe")
    term.SendKeys("<Tab>")
    term.SendKeys("[email protected]")
    term.SendKeys("<Enter>")
    
    // Verify form submission
    assert.True(t, term.Contains("Saved successfully"))
    
    // Check no crashes
    health := term.Health()
    assert.False(t, health.Crashed)
    assert.False(t, health.IsHanging)
}

API Reference

Terminal

type Terminal struct {
    // Core methods
    Start(cmd string, args ...string) error
    Close() error
    Wait(duration time.Duration)
    
    // Input methods
    SendKeys(keys string) error
    SendRaw(bytes []byte) error
    
    // Screen methods
    Screenshot() string
    GetScreen() string
    Contains(text string) bool
    
    // Verification
    VerifyLayout(layout LayoutType) bool
    VerifyColors() ColorReport
    
    // Health monitoring
    Health() HealthReport
    IsRunning() bool
    
    // NEW: Hang prevention and control
    SetTimeouts(global, operation time.Duration)
    ConfigureWatchdog(config WatchdogConfig)
    GetWatchdogStats() WatchdogStats
    IsHanging() bool
    ForceStop(reason string)
    WithTimeout(operation string, timeout time.Duration, fn func() error) error
}

Key Notation

<Enter>     - Enter key
<Tab>       - Tab key
<Esc>       - Escape
<Space>     - Space bar
<Backspace> - Backspace

<Ctrl+A>    - Control combinations
<Alt+X>     - Alt combinations
<Shift+Tab> - Shift combinations

<Up>        - Arrow keys
<Down>
<Left>
<Right>

<F1>-<F12>  - Function keys
<PgUp>      - Page up
<PgDown>    - Page down
<Home>      - Home
<End>       - End

Testing Patterns

1. Smoke Test

term.Start(app)
term.Wait(1*time.Second)
assert.False(t, term.Health().Crashed)

2. Navigation Test

term.SendKeys("<Tab><Tab><Enter>")
assert.True(t, term.Contains("Expected Screen"))

3. Data Entry Test

term.SendKeys("[email protected]")
term.SendKeys("<Tab>")
term.SendKeys("password123")
term.SendKeys("<Enter>")

4. Responsive Test

for width := 20; width <= 200; width += 20 {
    term.Resize(width, 24)
    assert.True(t, term.VerifyLayout(tester.Responsive))
}

5. Hang Prevention Test

// Test apps that might hang
term.SetTimeouts(5*time.Second, 2*time.Second)
term.Start(problematicApp)

// Won't hang the test
err := term.SendKeys("input")
if err != nil {
    t.Logf("Operation failed safely: %v", err)
}

assert.False(t, term.IsHanging())

6. Emergency Recovery Test

term.Start(hangingApp)
time.Sleep(1*time.Second)

if term.IsHanging() {
    term.ForceStop("Test cleanup")
    time.Sleep(500*time.Millisecond)
    assert.False(t, term.IsRunning())
}

CI/CD Integration

GitHub Actions

name: TUI Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - uses: actions/setup-go@v4
      with:
        go-version: '1.21'
    
    - name: Install dependencies
      run: go mod download
    
    - name: Run TUI tests
      run: go test -v ./...
    
    - name: Upload screenshots
      if: failure()
      uses: actions/upload-artifact@v3
      with:
        name: screenshots
        path: test_reports/

Troubleshooting

"PTY not available"

  • Run in a real terminal or use script command
  • In Docker, use -t flag: docker run -t

"Colors not detected"

  • Ensure TERM environment variable is set
  • Try TERM=xterm-256color

"Hanging tests"

  • Increase timeouts for slow systems
  • Check if app requires specific environment

Contributing

Contributions are welcome! Please read CONTRIBUTING.md for details.

License

MIT License - see LICENSE file for details.

Credits

Created by AKAO.IO for testing terminal applications with confidence.