Skip to main content

Record Xvfb virtual displays as MP4 video with live panel overlays and interactive HTML reports.

Project description

thea-recorder

Record your Xvfb virtual display as MP4, GIF, or WebM video with live panel overlays and interactive HTML reports.

thea-recorder captures anything running in a virtual display — browser-based E2E tests, GUI applications, desktop automation scripts, product demos — as MP4, GIF, or WebM video with a live overlay bar showing status panels, step timelines, and custom context. It then generates an interactive HTML report where you can click any step and watch exactly what happened.

Why

When something visual runs in CI — a test, a demo, a GUI app — and it fails, you're left staring at logs trying to imagine what the screen was doing. The typical debugging cycle:

  1. Read the failure log
  2. Try to reproduce locally
  3. Add more logging
  4. Push, wait for CI, read logs again
  5. Repeat

This is absurd. The screen was right there doing the thing. You just weren't watching.

thea-recorder fixes this by recording the virtual display during execution. Every session gets its own MP4 (with optional GIF or WebM conversion). The panel overlay shows you the current status and any custom context you want. The HTML report lets you click a step and the video seeks to that exact moment.

You stop guessing. You start watching.

What can you record?

Anything that runs in an Xvfb virtual display:

  • Browser-based E2E tests — Selenium, Playwright, Cypress, any browser automation
  • GUI applications — GTK, Qt, Electron, or any X11 windowed app
  • Terminal sessions — xterm, xfce4-terminal, or any terminal emulator running in X11
  • Games and simulations — solitaire, chess, or any graphical program
  • Product demos — scripted walkthroughs for sales, onboarding, or documentation

If it has a window and can run on X11, thea can record it.

Features

  • HTTP server + native SDKs — use from Go, Python, Ruby, TypeScript, or Java
  • CLI — server mode and client mode, scriptable from any language
  • Panel overlay system — named columns below the viewport with live-updating text
  • Smart scrolling — panels auto-scroll to keep the active content visible
  • GIF and WebM output — convert recordings to GIF (palette-based, ideal for PRs) or WebM (VP9)
  • Interactive HTML reports — embedded videos with clickable step timelines
  • Video composition — tile multiple recordings side-by-side with highlight borders
  • Framework agnostic — works with any test runner, GUI app, or automation script
  • Docker ready — example Dockerfile and E2E test suite included

Install

pip install thea-recorder

System dependencies (for the server, in your Docker image or CI runner):

apt install xvfb ffmpeg x11-xserver-utils x11-utils openbox xdotool fonts-dejavu-core

Check everything is in place with:

thea doctor

Quick start — HTTP server

Start the server:

thea serve --port 9123

Then use any SDK:

Go

client := recorder.NewClient("http://localhost:9123")
client.StartDisplay(ctx)
stop, _ := client.Recording(ctx, "login_test")
defer stop()
// ... run your application ...

Python

from thea import RecorderClient
client = RecorderClient("http://localhost:9123")
client.start_display()
with client.recording("login_test"):
    # ... run your application ...
    pass

TypeScript

const client = new RecorderClient({ url: "http://localhost:9123" });
await client.startDisplay();
await client.recording("login_test", async () => {
  // ... run your application ...
});

Ruby

client = Recorder::Client.new("http://localhost:9123")
client.start_display
client.recording("login_test") do
  # ... run your application ...
end

Java

try (var client = new RecorderClient("http://localhost:9123")) {
    client.startDisplay();
    client.recording("login_test", c -> {
        // ... run your application ...
    });
}

Quick start — Python library

from thea import Recorder, generate_report

recorder = Recorder(output_dir="./recordings", display=99)
recorder.add_panel("status", title="Status", width=120)
recorder.add_panel("steps", title="Steps")
recorder.start_display()

recorder.start_recording("login_test")
recorder.update_panel("status", "Running")
recorder.update_panel("steps", "  Given a user\n* When I log in\n  Then I see the dashboard")

# ... run your application here (on DISPLAY :99) ...

video = recorder.stop_recording()

generate_report(
    videos=[{
        "feature": "Authentication",
        "scenario": "Login",
        "status": "passed",
        "video": video,
        "steps": [
            {"keyword": "Given", "name": "a user", "status": "passed", "offset": 0.0},
            {"keyword": "When", "name": "I log in", "status": "passed", "offset": 2.5},
            {"keyword": "Then", "name": "I see the dashboard", "status": "passed", "offset": 5.0},
        ],
    }],
    output_dir="./recordings",
    title="My Test Report",
)

recorder.cleanup()

CLI

# Server mode
thea serve --host 0.0.0.0 --port 9123 --output-dir ./recordings --cors

# Client mode (talks to running server)
thea --server http://localhost:9123 start-display
thea --server http://localhost:9123 add-panel --name editor --title "Code" --width 80
thea --server http://localhost:9123 start-recording --name login_test
thea --server http://localhost:9123 stop-recording
thea --server http://localhost:9123 list-recordings
thea --server http://localhost:9123 health

Set THEA_URL to avoid repeating --server:

export THEA_URL=http://localhost:9123
thea health
thea start-display
thea start-recording --name my-test

Output Formats

Recordings are captured as MP4 by default. You can also produce GIF or WebM:

  • GIF — high-quality two-pass palette-based ffmpeg conversion (10fps, 720px width by default). Perfect for embedding in Pull Requests.
  • WebM — VP9 encoding for efficient web-friendly video.

Three ways to get alternate formats:

  1. CLI: thea stop-recording --gif or thea stop-recording --output-format gif. Convert existing recordings with thea convert-gif --name my_test or thea convert --name my_test --format webm.
  2. API/SDK: client.stop_recording(gif=True) or client.stop_recording(output_formats=["gif", "webm"]).
  3. HTTP: POST /recordings/<name>/gif or POST /recordings/<name>/webm to convert existing recordings. GET /recordings/<name>?format=gif to download in a specific format.

Docker

The easiest way to run Thea is the prebuilt base image — Alpine-based, containing Thea plus exactly the system dependencies recording needs:

docker run -p 9123:9123 -v "$PWD/recordings:/recordings" \
  ghcr.io/barkingiguana/thea-recorder:latest

Layer your application on top:

FROM ghcr.io/barkingiguana/thea-recorder:latest
RUN apk add --no-cache chromium chromium-chromedriver

Or with Docker Compose (the image has a built-in healthcheck, so service_healthy works out of the box):

services:
  recorder:
    image: ghcr.io/barkingiguana/thea-recorder:latest
    ports: ["9123:9123"]
    shm_size: "2g"
    volumes: ["./recordings:/recordings"]

  tests:
    build: .
    depends_on:
      recorder:
        condition: service_healthy
    environment:
      THEA_URL: http://recorder:9123

Or build your own image from scratch:

FROM python:3.12-slim

RUN apt-get update && apt-get install -qyy --no-install-recommends \
    chromium-driver xvfb ffmpeg x11-xserver-utils x11-utils openbox xdotool \
    fonts-dejavu-core \
    && rm -rf /var/lib/apt/lists/*

RUN pip install thea-recorder
EXPOSE 9123
CMD ["thea", "serve", "--host", "0.0.0.0", "--port", "9123"]

Panel system

Panels are named overlay columns rendered below the application viewport in a dark bar. They update in real-time during recording.

Panels are burned into the video by the encoder (not drawn on the X display), and the layout is fixed when recording starts: create panels before start_recording, then update their content as often as you like while recording. Adding or removing a panel mid-recording raises an error. Display screenshots composite panels the same way the recording does.

# Fixed-width panel
recorder.add_panel("status", title="Status", width=120)

# Auto-width panel (shares remaining space)
recorder.add_panel("log", title="Activity Log")

# Update content (atomically — no tearing in the video)
recorder.update_panel("log", "Line 1\nLine 2\nLine 3")

# Scroll to keep a specific line visible
recorder.update_panel("log", long_text, focus_line=current_step)

Report

The HTML report is a single self-contained file with:

  • Embedded MP4 video players per scenario
  • Clickable step timelines that seek the video
  • Video playback highlights the current step
  • Feature/scenario grouping with pass/fail badges
  • Dark theme, responsive layout
  • Customisable title, subtitle, and logo

Documentation

API

Recorder(output_dir, display, display_size, framerate, font, font_bold)

Param Default Description
output_dir /tmp/recordings Where MP4 files are saved
display 99 X11 display number
display_size 1920x1080 Application viewport resolution
framerate 15 Recording FPS
font auto-detect Path to regular TTF font
font_bold auto-detect Path to bold TTF font

Methods

Method Description
start_display() Launch Xvfb
stop_display() Terminate Xvfb
add_panel(name, title, width) Register a panel
remove_panel(name) Remove a panel
update_panel(name, text, focus_line) Update panel content
start_recording(filename) Start ffmpeg capture
stop_recording(gif, output_formats) Stop capture, return path(s). Optional gif=True or output_formats=["gif","webm"] for format conversion
recording_elapsed Seconds since recording started
cleanup() Stop everything, remove temp files

generate_report(videos, output_dir, title, subtitle, logo_text)

Takes a list of video metadata dicts and writes report.html.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

thea_recorder-0.19.9.tar.gz (117.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

thea_recorder-0.19.9-py3-none-any.whl (82.6 kB view details)

Uploaded Python 3

File details

Details for the file thea_recorder-0.19.9.tar.gz.

File metadata

  • Download URL: thea_recorder-0.19.9.tar.gz
  • Upload date:
  • Size: 117.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for thea_recorder-0.19.9.tar.gz
Algorithm Hash digest
SHA256 d07fe2617048ff7b0a2d5424bc159974953143df94e92491748abc3d9d1ef187
MD5 7ae6796bac52207d99cfb82c82915479
BLAKE2b-256 6151d6c3e985105478eb96699d29301ade5f9a06b597131da50e1c5cc629c3e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for thea_recorder-0.19.9.tar.gz:

Publisher: release.yml on barkingiguana/thea-recorder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file thea_recorder-0.19.9-py3-none-any.whl.

File metadata

  • Download URL: thea_recorder-0.19.9-py3-none-any.whl
  • Upload date:
  • Size: 82.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for thea_recorder-0.19.9-py3-none-any.whl
Algorithm Hash digest
SHA256 4dfed8f8810ae2c92f0129777db8668270cb07dceb675c0c2389472266be8257
MD5 a6c00b139fcccaa43e03afe9acda98b9
BLAKE2b-256 de743027b4c582699f79936fececc63972bca7fb31dfda96f69c0ab269a50e23

See more details on using hashes here.

Provenance

The following attestation bundles were made for thea_recorder-0.19.9-py3-none-any.whl:

Publisher: release.yml on barkingiguana/thea-recorder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page