Skip to content

fiffeek/teastraw

Repository files navigation

teastraw logo

Teastraw


License Code Size Go Version Go Report Card Build

A Go package for end-to-end testing of Terminal User Interface (TUI) applications.

Table of Contents

Overview

Teastraw provides a test runner that can interact with TUI applications by:

  • Starting your TUI application in a pseudo-terminal (PTY)
  • Sending keyboard input and terminal events
  • Capturing and validating screen output
  • Managing application lifecycle and graceful shutdown

Installation

go get github.com/fiffeek/teastraw

Quick Start

The following example uses stretchr/testify for clarity, however, teastraw does not make any assumptions about the testing library. You get raw errors and can decide yourself what "failing" the test means exactly.

Here's a simple example of testing a list-based TUI application:

package main

import (
    "bytes"
    "context"
    "os"
    "os/exec"
    "testing"
    "time"

    "github.com/fiffeek/teastraw/pkg/exp"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestMyTUIApp(t *testing.T) {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    // Create command to run your TUI application
    cmd := exec.CommandContext(ctx, "./my-tui-app")
    cmd.Env = append(os.Environ(), "NO_COLOR=1") // Disable colors for consistent testing

    // Create test runner with terminal dimensions
    runner, err := exp.NewTestRunner(
        exp.WithInitialTermSize(80, 24), // width, height
        exp.WithCommand(cmd),
    )
    require.NoError(t, err)

    // Start the application
    require.NoError(t, runner.Start())

    // Wait for initial screen to load
    err = runner.WaitFor(func(screen []byte) bool {
        return bytes.Contains(screen, []byte("Welcome"))
    }, exp.WithCheckInterval(50*time.Millisecond), exp.WithDuration(200*time.Millisecond))
    assert.NoError(t, err)

    // Send keyboard input
    assert.NoError(t, runner.Send([]byte("j"))) // Move down

    // Wait for screen to update
    err = runner.WaitFor(func(screen []byte) bool {
        return bytes.Contains(screen, []byte("Item 2"))
    }, exp.WithCheckInterval(50*time.Millisecond), exp.WithDuration(200*time.Millisecond))
    assert.NoError(t, err)

    // Capture current screen state
    screen := runner.ScreenNow()
    // Validate screen content...

    // Send quit command
    assert.NoError(t, runner.Send([]byte("\x03"))) // Ctrl+C

    // Get final screen before exit
    finalScreen, err := runner.FinalScreen(exp.WithFinalTimeout(500*time.Millisecond))
    assert.NoError(t, err)

    // Validate output.
    // Run the tests with `-update` to create the golden file.
    exp.RequireEqualSubtest(t, []byte(finalScreen), "__final_screen")

    // Get complete output history
    output, err := runner.FinalOutput()
    assert.NoError(t, err)
    // Validate output...
}

Key Features

Screen Validation

Capture and validate screen content at any point:

// Get current screen state
screen := runner.ScreenNow()

// Use golden file testing for screen validation
exp.RequireEqualSubtest(t, screen, "expected_screen_state")

Waiting for Conditions

Wait for specific content to appear on screen:

err := runner.WaitFor(func(screen []byte) bool {
    return bytes.Contains(screen, []byte("Loading complete"))
}, exp.WithCheckInterval(10*time.Millisecond), exp.WithDuration(1*time.Second))

Input Simulation

Send various types of input:

// Send regular keys
runner.Send([]byte("hello"))

// Send special keys
runner.Send([]byte("\x03"))  // Ctrl+C
runner.Send([]byte("\r"))    // Enter
runner.Send([]byte("\x1b[A")) // Up arrow

Caution

It is recommended to grab output after each key press to let the app process it correctly, see run_test.go example.

Graceful Shutdown

Capture final state before application exits:

// Get the last screen before exit
finalScreen, err := runner.FinalScreen(exp.WithFinalTimeout(500*time.Millisecond))

// Get complete output history
allOutput, err := runner.FinalOutput()

Configuration Options

Terminal Size

runner, err := exp.NewTestRunner(
    exp.WithInitialTermSize(120, 30), // Custom terminal dimensions
    exp.WithCommand(cmd),
)

Exit Sequences

For applications with specific exit sequences:

runner, err := exp.NewTestRunner(
    exp.WithExitSequences([]string{"\x1b[?1049l"}), // Clear alternate screen
    exp.WithCommand(cmd),
)

A few common ones are passed by default, you can also refer to them in your code.

Testing Patterns

Golden File Testing

Teastraw includes utilities for golden file testing to validate screen output against known good states:

// This will create/update golden files in testdata/
exp.RequireEqualSubtest(t, screenOutput, "test_name")

If you prefer to handle errors yourself (e.g. do not fail a test when it doesn't match), you can use exp.IsEqualToFixture (exp.RequireEqualSubtest is just a wrapper).

Time-based Testing

Set appropriate timeouts for your application's responsiveness:

// Quick interactions
exp.WithDuration(100*time.Millisecond)

// Slower operations
exp.WithDuration(2*time.Second)

Examples

See the test/ directory for complete working examples of testing different types of TUI applications.

Comparison to Teatest

Bubble Tea provides an experimental test framework called teatest. While teastest is great for testing your Bubble Tea models directly, teastraw takes a different approach by testing your complete TUI application end-to-end.

Key differences:

  • teatest: Tests your Bubble Tea model in isolation (unit/integration testing)
  • teastraw: Tests your compiled binary as a black box (end-to-end testing)

With teatest, you interact directly with your model, but external dependencies need to be mocked. With teastraw, you can test the real application behavior including CLI arguments, environment variables, file system interactions, and any other external integrations your app uses.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

See LICENSE

Credits

The code was modeled and adapted from:

About

Golang library for end-to-end testing TUI applications.

Topics

Resources

License

Stars

Watchers

Forks