Back to list
ctchen222

tui-visual-testing

by ctchen222

0🍴 0📅 Jan 21, 2026

SKILL.md


name: tui-visual-testing description: Visual regression testing for Bubble Tea TUI applications. This skill should be used when testing terminal UI appearance, capturing TUI screenshots, comparing visual output against golden files, or detecting UI regressions after code changes. Supports both static screen capture and interactive flow testing with simulated key sequences.

TUI Visual Testing

This skill enables visual regression testing for Bubble Tea terminal applications. It provides tools to capture TUI output, convert it to screenshots, and compare against golden files to detect visual regressions.

When to Use

  • After modifying UI rendering code to verify appearance
  • Testing that keyboard interactions produce expected visual results
  • Capturing baseline screenshots for new UI states
  • Comparing current output against approved golden files
  • Debugging visual discrepancies in terminal output

Core Workflow

1. Write test using teatest    →  Capture TUI output as text
2. Run capture script          →  Convert ANSI output to PNG
3. Compare against golden      →  Detect visual differences
4. Update golden if approved   →  Store new baseline

Testing Approaches

Use Bubble Tea's official teatest package for programmatic testing.

Setup test file (e.g., ui_test.go):

package main

import (
    "testing"
    "time"

    tea "github.com/charmbracelet/bubbletea"
    "github.com/charmbracelet/x/exp/teatest"
)

func TestMainView(t *testing.T) {
    // Create model
    m := NewModel()

    // Create test model with terminal size
    tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(80, 24))

    // Wait for initial render
    time.Sleep(100 * time.Millisecond)

    // Capture output
    out := tm.FinalOutput(t)

    // Compare against golden file
    teatest.RequireEqualOutput(t, out)
}

func TestKeyboardNavigation(t *testing.T) {
    m := NewModel()
    tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(80, 24))

    // Simulate key presses
    tm.Send(tea.KeyMsg{Type: tea.KeyDown})
    tm.Send(tea.KeyMsg{Type: tea.KeyDown})
    tm.Send(tea.KeyMsg{Type: tea.KeyEnter})

    time.Sleep(100 * time.Millisecond)

    out := tm.FinalOutput(t)
    teatest.RequireEqualOutput(t, out)
}

Run tests:

# Run tests and create golden files
go test -v ./...

# Update golden files after approved changes
go test -v ./... -update

Approach 2: Screenshot Comparison

For pixel-perfect visual testing, convert ANSI output to images.

Step 1: Capture TUI output

# Run the capture script to get ANSI output
python scripts/capture_tui.py ./your-app --keys "jjk" --output /tmp/tui_output.txt

Step 2: Convert to screenshot

# Convert ANSI to PNG image
python scripts/ansi_to_image.py /tmp/tui_output.txt --output /tmp/screenshot.png

Step 3: Compare screenshots

# Compare against golden screenshot
python scripts/compare_screenshots.py golden/main_view.png /tmp/screenshot.png

Scripts Reference

scripts/capture_tui.py

Captures terminal output from a Bubble Tea application.

python scripts/capture_tui.py <app-binary> [options]

Options:
  --keys KEY_SEQUENCE    Keys to send (e.g., "jjk<enter>")
  --width WIDTH          Terminal width (default: 80)
  --height HEIGHT        Terminal height (default: 24)
  --output FILE          Output file (default: stdout)
  --timeout SECONDS      Max wait time (default: 5)

scripts/ansi_to_image.py

Converts ANSI terminal output to a PNG image.

python scripts/ansi_to_image.py <input-file> [options]

Options:
  --output FILE          Output PNG file
  --font-size SIZE       Font size in pixels (default: 14)
  --theme THEME          Color theme: dark, light (default: dark)

scripts/compare_screenshots.py

Compares two screenshots and reports differences.

python scripts/compare_screenshots.py <golden-file> <actual-file> [options]

Options:
  --threshold PERCENT    Acceptable difference percentage (default: 0.1)
  --output FILE          Save diff image to file
  --fail-on-diff         Exit with code 1 if different

Directory Structure for Tests

Recommended project structure for visual tests:

your-project/
├── cmd/app/
│   ├── main.go
│   └── ui_test.go           # teatest-based tests
├── testdata/
│   └── TestMainView.golden  # Golden files (auto-generated)
└── visual-tests/
    ├── golden/              # Screenshot golden files
    │   ├── main_view.png
    │   └── edit_modal.png
    └── run_visual_tests.sh  # Test runner script

結合設計檢查

建議在進行視覺回歸測試時,同步使用 tui-design-inspector skill 進行人工設計審查。 你可以將自動化產生的截圖或 ANSI 輸出,交由設計檢查 skill 進行人工審查與設計建議,確保 UI 不僅外觀穩定,也符合最佳 UX 標準。


Workflow: Adding a New Visual Test

  1. Write the test case

    func TestNewFeature(t *testing.T) {
        m := NewModel()
        tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(80, 24))
        // ... setup and interactions
        teatest.RequireEqualOutput(t, tm.FinalOutput(t))
    }
    
  2. Run test to generate golden file

    go test -v -run TestNewFeature -update
    
  3. Review the golden file

    • Check testdata/TestNewFeature.golden
    • Verify it looks correct
  4. Commit the golden file

    git add testdata/TestNewFeature.golden
    

Workflow: Updating Golden Files After UI Changes

  1. Make UI changes in source code

  2. Run tests to see failures

    go test -v ./...
    
  3. Review the diff - Verify changes are intentional

  4. Update golden files

    go test -v ./... -update
    
  5. Verify and commit

    git diff testdata/
    git add testdata/
    git commit -m "Update golden files for UI changes"
    

Common Patterns

Testing Different Terminal Sizes

func TestResponsiveLayout(t *testing.T) {
    sizes := []struct {
        name   string
        width  int
        height int
    }{
        {"small", 80, 24},
        {"medium", 120, 40},
        {"large", 200, 60},
    }

    for _, size := range sizes {
        t.Run(size.name, func(t *testing.T) {
            m := NewModel()
            tm := teatest.NewTestModel(t, m,
                teatest.WithInitialTermSize(size.width, size.height))
            teatest.RequireEqualOutput(t, tm.FinalOutput(t))
        })
    }
}

Testing State Transitions

func TestStateTransitions(t *testing.T) {
    m := NewModel()
    tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(80, 24))

    // Initial state
    t.Run("initial", func(t *testing.T) {
        teatest.RequireEqualOutput(t, tm.FinalOutput(t))
    })

    // After navigation
    tm.Send(tea.KeyMsg{Type: tea.KeyTab})
    t.Run("after_tab", func(t *testing.T) {
        teatest.RequireEqualOutput(t, tm.FinalOutput(t))
    })

    // After action
    tm.Send(tea.KeyMsg{Type: tea.KeyEnter})
    t.Run("after_enter", func(t *testing.T) {
        teatest.RequireEqualOutput(t, tm.FinalOutput(t))
    })
}

Troubleshooting

Golden file mismatch but looks the same

  • Check for trailing whitespace differences
  • Verify terminal size matches between runs
  • Check for timestamp or dynamic content in output

ANSI codes in golden files look garbled

  • Golden files contain raw ANSI - this is expected
  • Use cat testdata/Test.golden to render properly
  • Or use scripts/ansi_to_image.py to visualize

Tests pass locally but fail in CI

  • Ensure consistent terminal size in CI
  • Set TERM=xterm-256color in CI environment
  • Use fixed dimensions in test setup

References

See references/teatest_patterns.md for advanced testing patterns and API documentation.

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon