11"""
2- Graphical testing helper module for MicroPythonOS.
2+ Graphical testing utilities for MicroPythonOS.
33
4- This module provides utilities for graphical/visual testing that work on both
5- desktop (unix/macOS) and device (ESP32).
4+ This module provides utilities for graphical/visual testing and UI automation
5+ that work on both desktop (unix/macOS) and device (ESP32). These functions can
6+ be used by:
7+ - Unit tests for verifying UI behavior
8+ - Apps that want to implement automation or testing features
9+ - Integration tests and end-to-end testing
610
7- Important: Tests using this module should be run with boot and main files
8- already executed (so display, theme, and UI infrastructure are initialized ).
11+ Important: Functions in this module assume the display, theme, and UI
12+ infrastructure are already initialized (boot.py and main.py executed ).
913
10- Usage:
11- from graphical_test_helper import wait_for_render, capture_screenshot
14+ Usage in tests :
15+ from mpos.ui.testing import wait_for_render, capture_screenshot
1216
1317 # Start your app
1418 mpos.apps.start_app("com.example.myapp")
2226 # Capture screenshot
2327 capture_screenshot("tests/screenshots/mytest.raw")
2428
25- # Simulate click at coordinates
29+ # Simulate user interaction
2630 simulate_click(160, 120) # Click at center of 320x240 screen
31+
32+ Usage in apps:
33+ from mpos.ui.testing import simulate_click, find_label_with_text
34+
35+ # Automated demo mode
36+ label = find_label_with_text(self.screen, "Start")
37+ if label:
38+ area = lv.area_t()
39+ label.get_coords(area)
40+ simulate_click(area.x1 + 10, area.y1 + 10)
2741"""
2842
2943import lvgl as lv
@@ -41,9 +55,15 @@ def wait_for_render(iterations=10):
4155
4256 This processes the LVGL task handler multiple times to ensure
4357 all UI updates, animations, and layout changes are complete.
58+ Essential for tests to avoid race conditions.
4459
4560 Args:
4661 iterations: Number of task handler iterations to run (default: 10)
62+
63+ Example:
64+ mpos.apps.start_app("com.example.myapp")
65+ wait_for_render() # Ensure UI is ready
66+ assert verify_text_present(lv.screen_active(), "Welcome")
4767 """
4868 import time
4969 for _ in range (iterations ):
@@ -52,14 +72,19 @@ def wait_for_render(iterations=10):
5272
5373
5474def capture_screenshot (filepath , width = 320 , height = 240 , color_format = lv .COLOR_FORMAT .RGB565 ):
55- print (f"capture_screenshot writing to { filepath } " )
5675 """
5776 Capture screenshot of current screen using LVGL snapshot.
5877
5978 The screenshot is saved as raw binary data in the specified color format.
60- To convert RGB565 to PNG, use:
79+ Useful for visual regression testing or documentation.
80+
81+ To convert RGB565 to PNG:
6182 ffmpeg -vcodec rawvideo -f rawvideo -pix_fmt rgb565 -s 320x240 -i file.raw file.png
6283
84+ Or use the conversion script:
85+ cd tests/screenshots
86+ ./convert_to_png.sh
87+
6388 Args:
6489 filepath: Path where to save the raw screenshot data
6590 width: Screen width in pixels (default: 320)
@@ -71,7 +96,13 @@ def capture_screenshot(filepath, width=320, height=240, color_format=lv.COLOR_FO
7196
7297 Raises:
7398 Exception: If screenshot capture fails
99+
100+ Example:
101+ from mpos.ui.testing import capture_screenshot
102+ capture_screenshot("tests/screenshots/home.raw")
74103 """
104+ print (f"capture_screenshot writing to { filepath } " )
105+
75106 # Calculate buffer size based on color format
76107 if color_format == lv .COLOR_FORMAT .RGB565 :
77108 bytes_per_pixel = 2
@@ -99,14 +130,19 @@ def get_all_labels(obj, labels=None):
99130 Recursively find all label widgets in the object hierarchy.
100131
101132 This traverses the entire widget tree starting from obj and
102- collects all LVGL label objects.
133+ collects all LVGL label objects. Useful for comprehensive
134+ text verification or debugging.
103135
104136 Args:
105137 obj: LVGL object to search (typically lv.screen_active())
106138 labels: Internal accumulator list (leave as None)
107139
108140 Returns:
109141 list: List of all label objects found in the hierarchy
142+
143+ Example:
144+ labels = get_all_labels(lv.screen_active())
145+ print(f"Found {len(labels)} labels")
110146 """
111147 if labels is None :
112148 labels = []
@@ -135,14 +171,20 @@ def find_label_with_text(obj, search_text):
135171 Find a label widget containing specific text.
136172
137173 Searches the entire widget hierarchy for a label whose text
138- contains the search string (substring match).
174+ contains the search string (substring match). Returns the
175+ first match found.
139176
140177 Args:
141178 obj: LVGL object to search (typically lv.screen_active())
142179 search_text: Text to search for (can be substring)
143180
144181 Returns:
145182 LVGL label object if found, None otherwise
183+
184+ Example:
185+ label = find_label_with_text(lv.screen_active(), "Settings")
186+ if label:
187+ print(f"Found Settings label at {label.get_coords()}")
146188 """
147189 labels = get_all_labels (obj )
148190 for label in labels :
@@ -160,12 +202,18 @@ def get_screen_text_content(obj):
160202 Extract all text content from all labels on screen.
161203
162204 Useful for debugging or comprehensive text verification.
205+ Returns a list of all text strings found in label widgets.
163206
164207 Args:
165208 obj: LVGL object to search (typically lv.screen_active())
166209
167210 Returns:
168211 list: List of all text strings found in labels
212+
213+ Example:
214+ texts = get_screen_text_content(lv.screen_active())
215+ assert "Welcome" in texts
216+ assert "Version 1.0" in texts
169217 """
170218 labels = get_all_labels (obj )
171219 texts = []
@@ -184,14 +232,18 @@ def verify_text_present(obj, expected_text):
184232 Verify that expected text is present somewhere on screen.
185233
186234 This is the primary verification method for graphical tests.
187- It searches all labels for the expected text.
235+ It searches all labels for the expected text (substring match) .
188236
189237 Args:
190238 obj: LVGL object to search (typically lv.screen_active())
191239 expected_text: Text that should be present (can be substring)
192240
193241 Returns:
194242 bool: True if text found, False otherwise
243+
244+ Example:
245+ assert verify_text_present(lv.screen_active(), "Settings")
246+ assert verify_text_present(lv.screen_active(), "Version")
195247 """
196248 return find_label_with_text (obj , expected_text ) is not None
197249
@@ -201,9 +253,21 @@ def print_screen_labels(obj):
201253 Debug helper: Print all label text found on screen.
202254
203255 Useful for debugging tests to see what text is actually present.
256+ Prints to stdout with numbered list.
204257
205258 Args:
206259 obj: LVGL object to search (typically lv.screen_active())
260+
261+ Example:
262+ # When a test fails, use this to see what's on screen
263+ print_screen_labels(lv.screen_active())
264+ # Output:
265+ # Found 5 labels on screen:
266+ # 0: MicroPythonOS
267+ # 1: Version 0.3.3
268+ # 2: Settings
269+ # 3: About
270+ # 4: WiFi
207271 """
208272 texts = get_screen_text_content (obj )
209273 print (f"Found { len (texts )} labels on screen:" )
@@ -216,7 +280,7 @@ def _touch_read_cb(indev_drv, data):
216280 Internal callback for simulated touch input device.
217281
218282 This callback is registered with LVGL and provides touch state
219- when simulate_click() is used.
283+ when simulate_click() is used. Not intended for direct use.
220284
221285 Args:
222286 indev_drv: Input device driver (LVGL internal)
@@ -237,6 +301,7 @@ def _ensure_touch_indev():
237301
238302 This is called automatically by simulate_click() on first use.
239303 Creates a pointer-type input device that uses _touch_read_cb.
304+ Not intended for direct use.
240305 """
241306 global _touch_indev
242307 if _touch_indev is None :
@@ -255,7 +320,13 @@ def simulate_click(x, y, press_duration_ms=50):
255320 processed through LVGL's normal input handling, so it triggers
256321 click events, focus changes, scrolling, etc. just like real input.
257322
258- To find object coordinates for clicking, use:
323+ Useful for:
324+ - Automated testing of UI interactions
325+ - Demo modes in apps
326+ - Accessibility automation
327+ - Integration testing
328+
329+ To find object coordinates for clicking:
259330 obj_area = lv.area_t()
260331 obj.get_coords(obj_area)
261332 center_x = (obj_area.x1 + obj_area.x2) // 2
@@ -268,13 +339,17 @@ def simulate_click(x, y, press_duration_ms=50):
268339 press_duration_ms: How long to hold the press (default: 50ms)
269340
270341 Example:
342+ from mpos.ui.testing import simulate_click, wait_for_render
343+
271344 # Click at screen center (320x240)
272345 simulate_click(160, 120)
346+ wait_for_render()
273347
274348 # Click on a specific button
275349 button_area = lv.area_t()
276- button .get_coords(button_area)
350+ my_button .get_coords(button_area)
277351 simulate_click(button_area.x1 + 10, button_area.y1 + 10)
352+ wait_for_render()
278353 """
279354 global _touch_x , _touch_y , _touch_pressed
280355
0 commit comments