A comprehensive testing and automation framework for LVGL-based user interfaces. Enables automated UI testing, screenshot capture, and interaction simulation for embedded and desktop applications. Includes a fully testable smartwatch demo application that demonstrates best practices for building LVGL UIs with testability from day one.
Background: LVGL's growing popularity in IoT and wearable devices created a need for robust UI automation solutions, but existing testing approaches were limited to basic coordinate clicking or required extensive manual setup. This framework bridges that gap by providing both immediate compatibility (coordinate-based) and production-grade reliability (widget-based) in a single, comprehensive solution.
- Real-time UI Automation: Click, longpress, and swipe gesture simulation
- Hybrid Testing Approach: Both widget ID-based and coordinate-based automation
- Coordinate-Based Commands: Direct pixel-level interaction for maximum compatibility
- Screenshot Capture: High-quality PNG screenshots for visual validation
- Cross-platform Support: Windows, Linux, and macOS compatibility
- Python Test Framework: Object-oriented testing with pytest integration
- Network Interface: JSON-based TCP communication protocol
- Test Isolation: Automatic UI state reset between test runs
- Reference Implementation: Complete smartwatch app with testability built-in
- Widget Registry System: Shows how to make LVGL widgets automation-friendly
- Best Practices: Demonstrates proper ID assignment and event handling
- Real-world Example: Multi-screen navigation, gestures, and state management
- CMake 3.16 or higher
- LVGL 9.x development libraries
- SDL2 development libraries
- Python 3.8+ with pip
- Git for cloning the repository
git clone --recursive https://github.com/barkleyli/LVGL-UI-Automation-Framework.gitNote: This repository uses Git submodules for LVGL dependencies. The --recursive flag automatically initializes and clones the submodules. SDL2 development libraries are included for Windows support.
If you forgot the --recursive flag, run:
git submodule update --init --recursive# Windows (with Visual Studio)
./build.bat
# Linux/macOS
./build.shcd python-client
pip install -e .# Start the automation server
./build/Release/lvgl-ui-automation.exe
# In another terminal, run the comprehensive demo
cd python-client
python demo.pyfrom lvgl_client import LVGLTestClient
# Connect to the automation server
with LVGLTestClient() as client:
# Take initial screenshot
client.screenshot('screenshots/initial.png')
# Widget-based interaction (semantic approach)
client.click('btn_heart')
client.wait(1000) # Wait 1 second
# Coordinate-based interaction (universal approach)
client.click_at(240, 240) # Click center of screen
client.drag(100, 200, 300, 200) # Horizontal drag
client.mouse_move(150, 150) # Move cursor
# Capture result
client.screenshot('screenshots/after_click.png')
```
### Test Suite Integration
```python
import pytest
from lvgl_client import LVGLTestClient
class TestSmartWatch:
@pytest.fixture
def client(self):
with LVGLTestClient() as client:
yield client
def test_heart_rate_measurement(self, client):
# Initial state verification
initial_text = client.get_state('lbl_bpm')
assert 'BPM' in initial_text
# Navigate to heart rate screen
client.click('btn_heart')
client.wait(500)
# Start measurement with longpress
client.longpress('hr_measure_area', 2000)
client.wait(500)
# Verify measurement started
client.screenshot('screenshots/measuring.png')
measuring_text = client.get_state('lbl_hr_instruction')
assert 'measuring' in measuring_text.lower()
# Wait for completion and verify result
client.wait(3500) # Wait for measurement cycle
final_value = client.get_state('lbl_hr_value')
assert 'BPM' in final_value+-------------------+ TCP/JSON +--------------------+
| Python Test |<--------------->| C++ Server |
| Client | Port 12345 | (LVGL + SDL2) |
+-------------------+ +--------------------+
| |
v v
+-------------------+ +--------------------+
| pytest | | UI Application |
| Test Suite | | (Smartwatch Face) |
+-------------------+ +--------------------+
- src/main.c: LVGL application host with SDL2 backend and gesture support
- src/test_harness.c: Widget interaction and state management
- src/screenshot.c: Real-time UI capture and PNG generation
- src/tcp_server.c: Network communication and command processing
- src/ui_watch.c: Smartwatch UI implementation with swipe gestures
- python-client/: High-level automation and testing framework
Advanced Input Simulation System
- Hybrid Approach: Both coordinate-based (like LVGL's built-in testing) AND semantic widget-based automation
- Universal Compatibility: Works with any LVGL application without code changes via coordinate commands
- Enhanced Reliability: Optional widget ID-based testing for production-grade test suites
- Complete Gesture Support: Click, longpress, drag, swipe, and mouse movement simulation
- Real-time Visual Feedback: Live panning during drag operations with configurable thresholds
- Progressive Enhancement: Start with coordinates, upgrade to semantic testing for better maintainability
Test Isolation
- Automatic UI state reset between test runs
- Multiple fallback methods for returning to main screen
- Robust error handling and recovery mechanisms
- Prevents test hanging on consecutive runs
| Command | Parameters | Description |
|---|---|---|
click |
id: string |
Simulate click on widget |
longpress |
id: string, ms: int |
Extended press simulation |
get_state |
id: string |
Retrieve widget properties |
set_text |
id: string, text: string |
Set widget text content |
| Command | Parameters | Description |
|---|---|---|
click_at |
x: int, y: int |
Click at specific coordinates |
mouse_move |
x: int, y: int |
Move mouse to coordinates |
drag |
x1, y1, x2, y2: int |
Drag from one point to another |
swipe |
x1, y1, x2, y2: int |
Gesture simulation with visual feedback |
| Command | Parameters | Description |
|---|---|---|
key |
code: int |
Send key event |
screenshot |
- | Capture current UI state |
wait |
ms: int |
Execution delay |
class LVGLTestClient:
# Connection
def connect() -> bool
# Widget-based methods (semantic)
def click(widget_id: str) -> bool
def longpress(widget_id: str, duration_ms: int = 1000) -> bool
def get_state(widget_id: str) -> str
def set_text(widget_id: str, text: str) -> bool
# Coordinate-based methods (universal)
def click_at(x: int, y: int) -> bool
def mouse_move(x: int, y: int) -> bool
def drag(x1: int, y1: int, x2: int, y2: int) -> bool
def swipe(x1: int, y1: int, x2: int, y2: int) -> bool
# General methods
def key_event(key_code: int) -> bool
def screenshot(save_path: str = None) -> bytes
def wait(duration_ms: int = 100) -> boolThe included smartwatch application serves as a complete reference implementation, demonstrating how to build LVGL UIs with testability from the ground up.
1. Widget Registration Pattern
// Register widgets with unique IDs for automation
reg_widget("btn_heart", heart_button);
reg_widget("lbl_time", time_label);
reg_widget("main_screen", main_screen_obj);2. Event Handler Design
// Design event handlers to be automation-friendly
static void heart_button_event_handler(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
// Handle both manual and automated clicks
show_screen(SCREEN_HEART_RATE);
}
}3. State Management
// Make widget states readable for automation
void update_heart_rate_display(int bpm) {
lv_label_set_text_fmt(lbl_hr_value, "%d BPM", bpm);
// Widget state now accessible via get_state() command
}The smartwatch demo includes the following testable elements:
Main Screen
lbl_time: Current time displaylbl_date: Date informationlbl_battery: Battery percentagelbl_bpm: Heart rate displaybtn_heart: Heart rate buttonbtn_activity: Activity/steps button
Heart Rate Screen
lbl_hr_value: Current heart rate valuelbl_hr_instruction: Measurement instructionshr_measure_area: Long press area for measurementshr_screen: Background for navigation
Activity Screen
lbl_steps_count: Step counterlbl_calories: Calories burnedactivity_screen: Background with swipe support
The automation server uses these default settings:
TCP_PORT=12345 # Server listen port
BIND_ADDRESS=127.0.0.1 # Local bind address
CONNECTION_TIMEOUT=30 # Command timeout (seconds)
WINDOW_SIZE=480x480 # UI window dimensionsCreate pytest.ini in your test directory:
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short --strict-markers
markers =
slow: marks tests as slow (>5 seconds)
integration: marks tests as integration tests
swipe: marks tests that require swipe gesturescd python-client/tests
python test_connection_check.pycd python-client
pytest tests/test_simple_demo.py -v -s# Basic UI validation
pytest tests/test_simple_demo.py::TestUIAutomation::test_01_main_watch_screen_validation -v
# Heart rate measurement workflow
pytest tests/test_simple_demo.py::TestUIAutomation::test_03_heart_rate_measurement_cycle -v
# Swipe gesture functionality
pytest tests/test_simple_demo.py::TestUIAutomation::test_05_swipe_functionality_validation -v# Clone the repository with submodules
git clone --recursive https://github.com/barkleyli/LVGL-UI-Automation-Framework.git
cd lvgl-ui-automation
# Build for development
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
cmake --build . --config DebugNote: This repository uses Git submodules for LVGL. Use --recursive flag or run git submodule update --init --recursive after cloning.
- Register widgets in
src/ui_watch.c - Add event handlers for interactions
- Update widget registry with unique IDs
- Create corresponding test cases
- Add new test methods to
TestUIAutomationclass - Use descriptive naming:
test_##_feature_description - Include proper setup/teardown with
reset_to_main_screen - Add screenshots for visual validation
- Use assertions with clear error messages
Server Connection Failed
- Ensure the automation server is running
- Check that port 12345 is not blocked by firewall
- Verify no other instances are using the port
Tests Hang on Second Run
- The framework includes automatic UI state reset
- If issues persist, restart the automation server
- Check console output for navigation error messages
Screenshot Capture Failed
- Ensure screenshots directory exists
- Check file permissions for write access
- Verify PNG encoding is working
Swipe Gestures Not Working
- Verify gesture events are enabled in LVGL configuration
- Check console for gesture detection debug messages
- Ensure no conflicting click handlers on swipe targets
Build Fails - Third-Party Dependencies Missing
- All dependencies are included in the repository
- If third_party/ directories appear empty after clone, check Git settings
- On Windows: ensure Git LFS is installed if large files are not downloading
- Verify CMake can find LVGL at third_party/lvgl
Enable detailed logging by setting environment variables:
export LVGL_AUTOMATION_DEBUG=1
export LVGL_AUTOMATION_VERBOSE=1
./build/Release/lvgl-ui-automation.exeThis project is licensed under the MIT License - see the LICENSE file for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure all tests pass and follow the existing code style conventions.
- LVGL team for the excellent graphics library
- SDL2 project for cross-platform windowing support
- pytest community for the testing framework
- STD stb_image_write for PNG processing
- Contributors who helped improve the swipe gesture system