Manage headless displays with Xvfb (X virtual framebuffer)
npx @tessl/cli install tessl/pypi-xvfbwrapper@0.2.0A Python wrapper for managing X11 virtual displays with Xvfb (X virtual framebuffer). This library enables headless GUI application testing and automation by providing a simple, context-manager-compatible API for starting and stopping virtual displays on headless servers.
pip install xvfbwrappersudo apt-get install xvfbsudo yum install xorg-x11-server-Xvfbsudo pacman -S xorg-server-xvfbfrom xvfbwrapper import Xvfbfrom xvfbwrapper import Xvfb
# Basic usage with manual start/stop
xvfb = Xvfb()
xvfb.start()
try:
# Launch applications that require X11 display here
# The DISPLAY environment variable is automatically set
pass
finally:
# Always stop to clean up resources
xvfb.stop()
# Context manager usage (recommended)
with Xvfb() as xvfb:
# Applications run inside virtual display
# Automatic cleanup when context exits
print(f"Virtual display running on :{xvfb.new_display}")
# Custom display geometry
with Xvfb(width=1280, height=720, colordepth=24) as xvfb:
# High-resolution virtual display
passThe package provides a simple wrapper around the Xvfb X11 server:
The design ensures thread-safe multi-display support and robust cleanup even when applications terminate unexpectedly.
Creates and manages X11 virtual framebuffer displays for headless applications. Provides automatic display number allocation, environment variable management, and resource cleanup.
class Xvfb:
def __init__(
self,
width: int = 800,
height: int = 680,
colordepth: int = 24,
tempdir: str | None = None,
display: int | None = None,
environ: dict | None = None,
timeout: int = 10,
**kwargs
):
"""
Initialize Xvfb virtual display manager.
Parameters:
- width: Display width in pixels (default: 800)
- height: Display height in pixels (default: 680)
- colordepth: Color depth in bits (default: 24)
- tempdir: Directory for lock files (default: system temp)
- display: Specific display number to use (default: auto-allocate)
- environ: Environment dict for isolation (default: os.environ)
- timeout: Startup timeout in seconds (default: 10)
- **kwargs: Additional Xvfb command-line arguments
"""Supports Python context manager protocol for automatic resource management and cleanup.
def __enter__(self) -> "Xvfb":
"""Enter context manager, starts the virtual display."""
def __exit__(self, exc_type, exc_val, exc_tb):
"""Exit context manager, stops the virtual display and cleans up."""Manual control methods for starting and stopping virtual displays with comprehensive error handling.
def start(self) -> None:
"""
Start the virtual display.
Sets DISPLAY environment variable and spawns Xvfb process.
Raises:
- ValueError: If specified display cannot be locked
- RuntimeError: If Xvfb fails to start or display doesn't open within timeout
"""
def stop(self) -> None:
"""
Stop the virtual display and restore original environment.
Terminates Xvfb process, restores original DISPLAY variable,
and cleans up lock files.
"""Runtime properties providing information about the virtual display state.
# Instance Properties
width: int # Display width in pixels
height: int # Display height in pixels
colordepth: int # Color depth in bits
new_display: int | None # Assigned display number (available after start())
environ: dict # Environment dictionary used
proc: subprocess.Popen | None # Xvfb process object (None when stopped)
orig_display_var: str | None # Original DISPLAY value (None if unset)
# Class Constants
MAX_DISPLAY: int = 2147483647 # Maximum display number valueFor concurrent virtual displays, use isolated environments to prevent interference between threads:
import os
from xvfbwrapper import Xvfb
# Create isolated environments for each display
env1 = os.environ.copy()
env2 = os.environ.copy()
xvfb1 = Xvfb(environ=env1)
xvfb2 = Xvfb(environ=env2)
xvfb1.start()
xvfb2.start()
try:
# Each display runs in isolation
# Use env1 and env2 in your subprocess calls
pass
finally:
xvfb1.stop()
xvfb2.stop()Pass additional Xvfb command-line options through keyword arguments:
# Disable TCP listening for security
with Xvfb(nolisten="tcp") as xvfb:
pass
# Multiple custom arguments
with Xvfb(nolisten="tcp", dpi="96", noreset=None) as xvfb:
passCommon pattern for headless browser testing:
import os
import unittest
from selenium import webdriver
from xvfbwrapper import Xvfb
# Force X11 on Wayland systems
os.environ["XDG_SESSION_TYPE"] = "x11"
class TestWebPages(unittest.TestCase):
def setUp(self):
self.xvfb = Xvfb()
self.addCleanup(self.xvfb.stop)
self.xvfb.start()
self.driver = webdriver.Chrome()
self.addCleanup(self.driver.quit)
def test_page_title(self):
self.driver.get("https://example.com")
self.assertIn("Example", self.driver.title)# Exceptions raised by xvfbwrapper
OSError # Xvfb binary not found or unsupported platform
ValueError # Specified display number cannot be locked
RuntimeError # Xvfb startup failure or timeoutsudo apt-get install xvfb