From 15fa0b7a6f56a4a2e29da4ea243720bfc0717f61 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 16:38:21 -0500 Subject: [PATCH 01/20] create TestScriptUI class for testing setup, updater, and reset UIs --- .../ui/tests/test_ui/raylib_screenshots.py | 95 +++++++++++++------ 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 6de774648ac461..db532b7af9c0db 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -5,6 +5,7 @@ import time import pathlib from collections import namedtuple +import subprocess import pyautogui import pywinctl @@ -221,30 +222,31 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): CASES = { "homescreen": setup_homescreen, - "homescreen_paired": setup_homescreen, - "homescreen_prime": setup_homescreen, - "homescreen_update_available": setup_homescreen_update_available, - "settings_device": setup_settings, - "settings_network": setup_settings_network, - "settings_network_advanced": setup_settings_network_advanced, - "settings_toggles": setup_settings_toggles, - "settings_software": setup_settings_software, - "settings_software_download": setup_settings_software_download, - "settings_software_release_notes": setup_settings_software_release_notes, - "settings_firehose": setup_settings_firehose, - "settings_developer": setup_settings_developer, - "keyboard": setup_keyboard, - "pair_device": setup_pair_device, - "offroad_alert": setup_offroad_alert, - "confirmation_dialog": setup_confirmation_dialog, - "experimental_mode_description": setup_experimental_mode_description, - "onroad": setup_onroad, - "onroad_sidebar": setup_onroad_sidebar, - "onroad_small_alert": setup_onroad_small_alert, - "onroad_medium_alert": setup_onroad_medium_alert, - "onroad_full_alert": setup_onroad_full_alert, - "onroad_full_alert_multiline": setup_onroad_full_alert_multiline, - "onroad_full_alert_long_text": setup_onroad_full_alert_long_text, + "setup": setup_homescreen, + # "homescreen_paired": setup_homescreen, + # "homescreen_prime": setup_homescreen, + # "homescreen_update_available": setup_homescreen_update_available, + # "settings_device": setup_settings, + # "settings_network": setup_settings_network, + # "settings_network_advanced": setup_settings_network_advanced, + # "settings_toggles": setup_settings_toggles, + # "settings_software": setup_settings_software, + # "settings_software_download": setup_settings_software_download, + # "settings_software_release_notes": setup_settings_software_release_notes, + # "settings_firehose": setup_settings_firehose, + # "settings_developer": setup_settings_developer, + # "keyboard": setup_keyboard, + # "pair_device": setup_pair_device, + # "offroad_alert": setup_offroad_alert, + # "confirmation_dialog": setup_confirmation_dialog, + # "experimental_mode_description": setup_experimental_mode_description, + # "onroad": setup_onroad, + # "onroad_sidebar": setup_onroad_sidebar, + # "onroad_small_alert": setup_onroad_small_alert, + # "onroad_medium_alert": setup_onroad_medium_alert, + # "onroad_full_alert": setup_onroad_full_alert, + # "onroad_full_alert_multiline": setup_onroad_full_alert_multiline, + # "onroad_full_alert_long_text": setup_onroad_full_alert_long_text, } @@ -253,7 +255,7 @@ def __init__(self): os.environ["SCALE"] = os.getenv("SCALE", "1") sys.modules["mouseinfo"] = False - def setup(self): + def setup(self, window_title: str): # Seed minimal offroad state self.pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) ds = messaging.new_message('deviceState') @@ -264,7 +266,7 @@ def setup(self): time.sleep(0.05) time.sleep(0.5) try: - self.ui = pywinctl.getWindowsWithTitle("UI")[0] + self.ui = pywinctl.getWindowsWithTitle(window_title)[0] except Exception as e: print(f"failed to find ui window, assuming that it's in the top left (for Xvfb) {e}") self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0, 0, 2160, 1080) @@ -279,12 +281,40 @@ def click(self, x: int, y: int, *args, **kwargs): time.sleep(0.01) pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) + def run_test(self, name: str, setup_case, window_title = "UI"): + self.setup(window_title) # setup UI + time.sleep(UI_DELAY) # wait for UI to start + setup_case(self.click, self.pm) # setup case + self.screenshot(name) # take screenshot + @with_processes(["ui"]) def test_ui(self, name, setup_case): - self.setup() - time.sleep(UI_DELAY) # wait for UI to start - setup_case(self.click, self.pm) - self.screenshot(name) + self.run_test(name, setup_case) + + +class TestScriptUI(TestUI): + def __init__(self, script_path: str, window_title: str): + super().__init__() + self._script_path = script_path + self._window_title = window_title + self._process = None + + def __enter__(self): + self._process = subprocess.Popen([sys.executable, self._script_path]) + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self._process: + self._process.terminate() + try: + self._process.wait(timeout=5) + except subprocess.TimeoutExpired: + self._process.kill() + self._process = None + + # override the TestUI method to not start another UI process + def test_ui(self, name, setup_case): + self.run_test(name, setup_case, self._window_title) def create_screenshots(): @@ -308,6 +338,11 @@ def create_screenshots(): elif name == "homescreen_prime": params.put("PrimeType", 2) # LITE + if name == "setup": + with TestScriptUI("system/ui/setup.py", "Setup") as launcher: + launcher.test_ui(name, setup) + continue + t.test_ui(name, setup) From 9a5ae5efc507f564a5a8b5574e25f79246bc8be3 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 16:40:27 -0500 Subject: [PATCH 02/20] add other processes --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index db532b7af9c0db..a5011f3a6a01b3 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -223,6 +223,8 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): CASES = { "homescreen": setup_homescreen, "setup": setup_homescreen, + "updater": setup_homescreen_update_available, + "reset": setup_homescreen, # "homescreen_paired": setup_homescreen, # "homescreen_prime": setup_homescreen, # "homescreen_update_available": setup_homescreen_update_available, @@ -338,8 +340,8 @@ def create_screenshots(): elif name == "homescreen_prime": params.put("PrimeType", 2) # LITE - if name == "setup": - with TestScriptUI("system/ui/setup.py", "Setup") as launcher: + if name == "setup" or name == "updater" or name == "reset": + with TestScriptUI(f"system/ui/{name}.py", name.capitalize()) as launcher: launcher.test_ui(name, setup) continue From d454f3852980bb8659c5e1c175767ad0f28cc400 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 16:44:27 -0500 Subject: [PATCH 03/20] move window title to class init --- .../ui/tests/test_ui/raylib_screenshots.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index a5011f3a6a01b3..f38ef065c94ebc 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -223,7 +223,7 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): CASES = { "homescreen": setup_homescreen, "setup": setup_homescreen, - "updater": setup_homescreen_update_available, + "updater": setup_homescreen, "reset": setup_homescreen, # "homescreen_paired": setup_homescreen, # "homescreen_prime": setup_homescreen, @@ -253,11 +253,13 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): class TestUI: - def __init__(self): + def __init__(self, window_title="UI"): + self.window_title = window_title + os.environ["SCALE"] = os.getenv("SCALE", "1") sys.modules["mouseinfo"] = False - def setup(self, window_title: str): + def setup(self): # Seed minimal offroad state self.pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) ds = messaging.new_message('deviceState') @@ -268,7 +270,7 @@ def setup(self, window_title: str): time.sleep(0.05) time.sleep(0.5) try: - self.ui = pywinctl.getWindowsWithTitle(window_title)[0] + self.ui = pywinctl.getWindowsWithTitle(self.window_title)[0] except Exception as e: print(f"failed to find ui window, assuming that it's in the top left (for Xvfb) {e}") self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0, 0, 2160, 1080) @@ -283,8 +285,8 @@ def click(self, x: int, y: int, *args, **kwargs): time.sleep(0.01) pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) - def run_test(self, name: str, setup_case, window_title = "UI"): - self.setup(window_title) # setup UI + def run_test(self, name: str, setup_case): + self.setup() # setup UI time.sleep(UI_DELAY) # wait for UI to start setup_case(self.click, self.pm) # setup case self.screenshot(name) # take screenshot @@ -295,10 +297,9 @@ def test_ui(self, name, setup_case): class TestScriptUI(TestUI): - def __init__(self, script_path: str, window_title: str): - super().__init__() + def __init__(self, script_path: str, *args, **kwargs): + super().__init__(*args, **kwargs) self._script_path = script_path - self._window_title = window_title self._process = None def __enter__(self): @@ -316,7 +317,7 @@ def __exit__(self, exc_type, exc_value, traceback): # override the TestUI method to not start another UI process def test_ui(self, name, setup_case): - self.run_test(name, setup_case, self._window_title) + self.run_test(name, setup_case) def create_screenshots(): From 6b365291bcc720a0e4f6c2e788597d11b5a402d3 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:34:22 -0500 Subject: [PATCH 04/20] add new test case system and implement for setup.py --- .../ui/tests/test_ui/raylib_screenshots.py | 83 ++++++++++++++----- 1 file changed, 64 insertions(+), 19 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index f38ef065c94ebc..9d2f51ef5f53f6 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -4,12 +4,14 @@ import shutil import time import pathlib -from collections import namedtuple import subprocess import pyautogui import pywinctl +from collections import namedtuple +from collections.abc import Callable + from cereal import log from cereal import messaging from cereal.messaging import PubMaster @@ -220,11 +222,8 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): time.sleep(0.05) -CASES = { +CASES: dict[str, Callable] = { "homescreen": setup_homescreen, - "setup": setup_homescreen, - "updater": setup_homescreen, - "reset": setup_homescreen, # "homescreen_paired": setup_homescreen, # "homescreen_prime": setup_homescreen, # "homescreen_update_available": setup_homescreen_update_available, @@ -252,6 +251,47 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): } +def software_setup_click_primary_button(click, pm: PubMaster): + click(1600, 950) + + +def software_setup_click_secondary_button(click, pm: PubMaster): + click(500, 950) + + +def software_setup_get_started_next(click, pm: PubMaster): + click(2000, 630) + + +def software_setup_choose_software_click_openpilot(click, pm: PubMaster): + click(1200, 320) + + +def software_setup_choose_software_click_custom(click, pm: PubMaster): + click(1200, 580) + + +# These cases are for scripts that launch their own UI process (setup, updater, reset). +# Each case is a list of additional steps to perform and screenshot, after initial screenshot. +# Each step can also be a list of steps to do, with the screenshot at the end. +SCRIPT_UI_CASES: dict[str, list | list[list]] = { + "setup_openpilot": [ + software_setup_click_primary_button, # Low voltage warning; click "Continue" + software_setup_get_started_next, # Get started page; click arrow + [ + # Do this in a group since we only want a screenshot of the warning + software_setup_choose_software_click_custom, # Choose software page; click "Custom" + software_setup_click_primary_button, # Click "Continue" + ], + [software_setup_click_secondary_button, software_setup_choose_software_click_openpilot], # Go back to choose software page and click "openpilot" + [software_setup_click_primary_button, lambda click, pm: time.sleep(1)], # Click "Continue"; wait for networks to load + software_setup_click_primary_button, # "Download" button + ], + "updater": [], + "reset": [], +} + + class TestUI: def __init__(self, window_title="UI"): self.window_title = window_title @@ -285,16 +325,13 @@ def click(self, x: int, y: int, *args, **kwargs): time.sleep(0.01) pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) - def run_test(self, name: str, setup_case): + @with_processes(["ui"]) + def test_ui(self, name, setup_case: Callable): self.setup() # setup UI time.sleep(UI_DELAY) # wait for UI to start - setup_case(self.click, self.pm) # setup case + setup_case(self.click, self.pm) self.screenshot(name) # take screenshot - @with_processes(["ui"]) - def test_ui(self, name, setup_case): - self.run_test(name, setup_case) - class TestScriptUI(TestUI): def __init__(self, script_path: str, *args, **kwargs): @@ -315,9 +352,17 @@ def __exit__(self, exc_type, exc_value, traceback): self._process.kill() self._process = None - # override the TestUI method to not start another UI process - def test_ui(self, name, setup_case): - self.run_test(name, setup_case) + # Override the TestUI method to to run multiple tests, and to avoid starting another UI process + def test_ui(self, name, setup_cases: list[Callable] | list[list[Callable]]): + self.setup() # setup UI + time.sleep(UI_DELAY) # wait for UI to start + self.screenshot(name) # initial screenshot + # Run each setup case, taking a screenshot after each group + for i, case in enumerate(setup_cases): + group = case if isinstance(case, list) else [case] # each case can be a single step or group of steps + for setup_case in group: + setup_case(self.click, self.pm) # run each step in the group + self.screenshot(f"{name}_{i + 1}") # take screenshot after each case group def create_screenshots(): @@ -341,13 +386,13 @@ def create_screenshots(): elif name == "homescreen_prime": params.put("PrimeType", 2) # LITE - if name == "setup" or name == "updater" or name == "reset": - with TestScriptUI(f"system/ui/{name}.py", name.capitalize()) as launcher: - launcher.test_ui(name, setup) - continue - t.test_ui(name, setup) + for name, setup_cases in SCRIPT_UI_CASES.items(): + with OpenpilotPrefix(): + with TestScriptUI(f"system/ui/{name}.py", "System Reset" if name == "reset" else name.capitalize()) as launcher: + launcher.test_ui(name, setup_cases) + if __name__ == "__main__": create_screenshots() From 7441cba7723e11a46bbec98663ee67d2775f0a14 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:35:05 -0500 Subject: [PATCH 05/20] fix --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 9d2f51ef5f53f6..efbc1d4b1d4956 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -275,7 +275,7 @@ def software_setup_choose_software_click_custom(click, pm: PubMaster): # Each case is a list of additional steps to perform and screenshot, after initial screenshot. # Each step can also be a list of steps to do, with the screenshot at the end. SCRIPT_UI_CASES: dict[str, list | list[list]] = { - "setup_openpilot": [ + "setup": [ software_setup_click_primary_button, # Low voltage warning; click "Continue" software_setup_get_started_next, # Get started page; click arrow [ From 7b60fe11a86d327fcf4184c46ac01a40913f4687 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:36:16 -0500 Subject: [PATCH 06/20] lower wait time --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index efbc1d4b1d4956..2d943404ef0185 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -284,7 +284,7 @@ def software_setup_choose_software_click_custom(click, pm: PubMaster): software_setup_click_primary_button, # Click "Continue" ], [software_setup_click_secondary_button, software_setup_choose_software_click_openpilot], # Go back to choose software page and click "openpilot" - [software_setup_click_primary_button, lambda click, pm: time.sleep(1)], # Click "Continue"; wait for networks to load + [software_setup_click_primary_button, lambda click, pm: time.sleep(0.5)], # Click "Continue"; wait for networks to load software_setup_click_primary_button, # "Download" button ], "updater": [], From e4a8d370fa1275479bba5e852b64a373dfe643ed Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:38:31 -0500 Subject: [PATCH 07/20] update docs --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 2d943404ef0185..29ceeb720b9b49 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -272,8 +272,8 @@ def software_setup_choose_software_click_custom(click, pm: PubMaster): # These cases are for scripts that launch their own UI process (setup, updater, reset). -# Each case is a list of additional steps to perform and screenshot, after initial screenshot. -# Each step can also be a list of steps to do, with the screenshot at the end. +# Each case is a list of additional steps to perform and screenshot (after initial screenshot). +# Each item can also be a group of steps to do, with the screenshot at the end. SCRIPT_UI_CASES: dict[str, list | list[list]] = { "setup": [ software_setup_click_primary_button, # Low voltage warning; click "Continue" From 26e7d836f2466b1f7d7f2f4c282e9db8b94f6ad2 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:41:01 -0500 Subject: [PATCH 08/20] remove comments --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 29ceeb720b9b49..b80f29e5fd81b8 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -327,10 +327,10 @@ def click(self, x: int, y: int, *args, **kwargs): @with_processes(["ui"]) def test_ui(self, name, setup_case: Callable): - self.setup() # setup UI - time.sleep(UI_DELAY) # wait for UI to start + self.setup() + time.sleep(UI_DELAY) setup_case(self.click, self.pm) - self.screenshot(name) # take screenshot + self.screenshot(name) class TestScriptUI(TestUI): @@ -354,8 +354,8 @@ def __exit__(self, exc_type, exc_value, traceback): # Override the TestUI method to to run multiple tests, and to avoid starting another UI process def test_ui(self, name, setup_cases: list[Callable] | list[list[Callable]]): - self.setup() # setup UI - time.sleep(UI_DELAY) # wait for UI to start + self.setup() + time.sleep(UI_DELAY) self.screenshot(name) # initial screenshot # Run each setup case, taking a screenshot after each group for i, case in enumerate(setup_cases): From 2ed08a3c58f9ca9dafde6d18e2918798d2fd2ef5 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:44:37 -0500 Subject: [PATCH 09/20] refactor --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index b80f29e5fd81b8..51576a8899e34d 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -328,7 +328,7 @@ def click(self, x: int, y: int, *args, **kwargs): @with_processes(["ui"]) def test_ui(self, name, setup_case: Callable): self.setup() - time.sleep(UI_DELAY) + time.sleep(UI_DELAY) # wait for UI to start setup_case(self.click, self.pm) self.screenshot(name) @@ -390,7 +390,8 @@ def create_screenshots(): for name, setup_cases in SCRIPT_UI_CASES.items(): with OpenpilotPrefix(): - with TestScriptUI(f"system/ui/{name}.py", "System Reset" if name == "reset" else name.capitalize()) as launcher: + window_title = "System Reset" if name == "reset" else name.capitalize() + with TestScriptUI(f"system/ui/{name}.py", window_title) as launcher: launcher.test_ui(name, setup_cases) From c4b0902ee6422dd2efcb06e9d75a3b685447bb2b Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:46:55 -0500 Subject: [PATCH 10/20] add reset screens --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 51576a8899e34d..58d95591146d8d 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -284,11 +284,14 @@ def software_setup_choose_software_click_custom(click, pm: PubMaster): software_setup_click_primary_button, # Click "Continue" ], [software_setup_click_secondary_button, software_setup_choose_software_click_openpilot], # Go back to choose software page and click "openpilot" - [software_setup_click_primary_button, lambda click, pm: time.sleep(0.5)], # Click "Continue"; wait for networks to load + [software_setup_click_primary_button, lambda click, pm: time.sleep(1)], # Click "Continue"; wait for networks to load software_setup_click_primary_button, # "Download" button ], "updater": [], - "reset": [], + "reset": [ + software_setup_click_primary_button, # Click "Confirm" on initial confirmation + software_setup_click_primary_button, # Click "Confirm" on final warning + ], } From ea9468ea43611e084de61c97bedb2f033a6c41ab Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:47:52 -0500 Subject: [PATCH 11/20] rename --- .../ui/tests/test_ui/raylib_screenshots.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 58d95591146d8d..cc4d3d27febc6f 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -251,11 +251,11 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): } -def software_setup_click_primary_button(click, pm: PubMaster): +def fullscreen_click_primary_button(click, pm: PubMaster): click(1600, 950) -def software_setup_click_secondary_button(click, pm: PubMaster): +def fullscreen_click_secondary_button(click, pm: PubMaster): click(500, 950) @@ -276,21 +276,21 @@ def software_setup_choose_software_click_custom(click, pm: PubMaster): # Each item can also be a group of steps to do, with the screenshot at the end. SCRIPT_UI_CASES: dict[str, list | list[list]] = { "setup": [ - software_setup_click_primary_button, # Low voltage warning; click "Continue" + fullscreen_click_primary_button, # Low voltage warning; click "Continue" software_setup_get_started_next, # Get started page; click arrow [ # Do this in a group since we only want a screenshot of the warning software_setup_choose_software_click_custom, # Choose software page; click "Custom" - software_setup_click_primary_button, # Click "Continue" + fullscreen_click_primary_button, # Click "Continue" ], - [software_setup_click_secondary_button, software_setup_choose_software_click_openpilot], # Go back to choose software page and click "openpilot" - [software_setup_click_primary_button, lambda click, pm: time.sleep(1)], # Click "Continue"; wait for networks to load - software_setup_click_primary_button, # "Download" button + [fullscreen_click_secondary_button, software_setup_choose_software_click_openpilot], # Go back to choose software page and click "openpilot" + [fullscreen_click_primary_button, lambda click, pm: time.sleep(1)], # Click "Continue"; wait for networks to load + fullscreen_click_primary_button, # "Download" button ], "updater": [], "reset": [ - software_setup_click_primary_button, # Click "Confirm" on initial confirmation - software_setup_click_primary_button, # Click "Confirm" on final warning + fullscreen_click_primary_button, # Click "Confirm" on initial confirmation + fullscreen_click_primary_button, # Click "Confirm" on final warning ], } From 4db7287475b9aef6b7cace24a36f8e908dab8746 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:52:32 -0500 Subject: [PATCH 12/20] update comments and rename --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index cc4d3d27febc6f..5e3b6f568a705c 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -252,11 +252,11 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): def fullscreen_click_primary_button(click, pm: PubMaster): - click(1600, 950) + click(1600, 950) # Bottom right button def fullscreen_click_secondary_button(click, pm: PubMaster): - click(500, 950) + click(500, 950) # Bottom left button def software_setup_get_started_next(click, pm: PubMaster): @@ -271,10 +271,11 @@ def software_setup_choose_software_click_custom(click, pm: PubMaster): click(1200, 580) -# These cases are for scripts that launch their own UI process (setup, updater, reset). +# These cases are for the setup, updater, and reset screens that have their own UI process. +# The key is the name of the script. # Each case is a list of additional steps to perform and screenshot (after initial screenshot). # Each item can also be a group of steps to do, with the screenshot at the end. -SCRIPT_UI_CASES: dict[str, list | list[list]] = { +SOFTWARE_SETUP_CASES: dict[str, list | list[list]] = { "setup": [ fullscreen_click_primary_button, # Low voltage warning; click "Continue" software_setup_get_started_next, # Get started page; click arrow @@ -391,7 +392,7 @@ def create_screenshots(): t.test_ui(name, setup) - for name, setup_cases in SCRIPT_UI_CASES.items(): + for name, setup_cases in SOFTWARE_SETUP_CASES.items(): with OpenpilotPrefix(): window_title = "System Reset" if name == "reset" else name.capitalize() with TestScriptUI(f"system/ui/{name}.py", window_title) as launcher: From a2ee72d28f67cf2f70a2a6678eba86e7c060dce7 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 16 Oct 2025 23:59:31 -0500 Subject: [PATCH 13/20] add support for scripts with args in test cases, and implement for updater --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 5e3b6f568a705c..22ab229c08df8f 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -338,13 +338,14 @@ def test_ui(self, name, setup_case: Callable): class TestScriptUI(TestUI): - def __init__(self, script_path: str, *args, **kwargs): + def __init__(self, script_path: str, script_args: list[str] | None, *args, **kwargs): super().__init__(*args, **kwargs) self._script_path = script_path + self._script_args = script_args or [] self._process = None def __enter__(self): - self._process = subprocess.Popen([sys.executable, self._script_path]) + self._process = subprocess.Popen([sys.executable, self._script_path] + self._script_args) return self def __exit__(self, exc_type, exc_value, traceback): @@ -395,7 +396,8 @@ def create_screenshots(): for name, setup_cases in SOFTWARE_SETUP_CASES.items(): with OpenpilotPrefix(): window_title = "System Reset" if name == "reset" else name.capitalize() - with TestScriptUI(f"system/ui/{name}.py", window_title) as launcher: + args = ["updater", "manifest"] if name == "updater" else None + with TestScriptUI(f"system/ui/{name}.py", args, window_title=window_title) as launcher: launcher.test_ui(name, setup_cases) From 2d49f6c4e4902d4dc872788e3b92309e959e2564 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Oct 2025 00:06:28 -0500 Subject: [PATCH 14/20] add updater screens --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 22ab229c08df8f..00c851a887d5c2 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -252,11 +252,11 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): def fullscreen_click_primary_button(click, pm: PubMaster): - click(1600, 950) # Bottom right button + click(1800, 950) # Bottom right button def fullscreen_click_secondary_button(click, pm: PubMaster): - click(500, 950) # Bottom left button + click(200, 950) # Bottom left button def software_setup_get_started_next(click, pm: PubMaster): @@ -288,7 +288,10 @@ def software_setup_choose_software_click_custom(click, pm: PubMaster): [fullscreen_click_primary_button, lambda click, pm: time.sleep(1)], # Click "Continue"; wait for networks to load fullscreen_click_primary_button, # "Download" button ], - "updater": [], + "updater": [ + fullscreen_click_secondary_button, # Click "Connect to Wi-Fi" + [fullscreen_click_secondary_button, fullscreen_click_primary_button], # Click "Back", then "Install" + ], "reset": [ fullscreen_click_primary_button, # Click "Confirm" on initial confirmation fullscreen_click_primary_button, # Click "Confirm" on final warning From 456258b4f86404872833c0ea90da040ef6ea0a2d Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Oct 2025 00:09:14 -0500 Subject: [PATCH 15/20] adjust button coords --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 00c851a887d5c2..b8eb6847493e16 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -252,11 +252,11 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): def fullscreen_click_primary_button(click, pm: PubMaster): - click(1800, 950) # Bottom right button + click(1950, 950) # Bottom right button def fullscreen_click_secondary_button(click, pm: PubMaster): - click(200, 950) # Bottom left button + click(150, 950) # Bottom left button def software_setup_get_started_next(click, pm: PubMaster): From b505200102e877817d63a3d0a1cec8ec72fc55da Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Oct 2025 00:10:36 -0500 Subject: [PATCH 16/20] fix comment --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index b8eb6847493e16..c976635cf0e93a 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -360,7 +360,7 @@ def __exit__(self, exc_type, exc_value, traceback): self._process.kill() self._process = None - # Override the TestUI method to to run multiple tests, and to avoid starting another UI process + # Override the TestUI method to run multiple tests, and to avoid starting another UI process def test_ui(self, name, setup_cases: list[Callable] | list[list[Callable]]): self.setup() time.sleep(UI_DELAY) From 9689ca2ea03b9462b8a89d23d7c9c3eb98989142 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Oct 2025 00:20:17 -0500 Subject: [PATCH 17/20] add delay --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index c976635cf0e93a..e4e496a403750f 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -280,8 +280,8 @@ def software_setup_choose_software_click_custom(click, pm: PubMaster): fullscreen_click_primary_button, # Low voltage warning; click "Continue" software_setup_get_started_next, # Get started page; click arrow [ - # Do this in a group since we only want a screenshot of the warning - software_setup_choose_software_click_custom, # Choose software page; click "Custom" + # Take a screenshot of the custom software warning first, so we can go back + software_setup_choose_software_click_custom, # Click "Custom" on choose software page fullscreen_click_primary_button, # Click "Continue" ], [fullscreen_click_secondary_button, software_setup_choose_software_click_openpilot], # Go back to choose software page and click "openpilot" @@ -370,6 +370,7 @@ def test_ui(self, name, setup_cases: list[Callable] | list[list[Callable]]): group = case if isinstance(case, list) else [case] # each case can be a single step or group of steps for setup_case in group: setup_case(self.click, self.pm) # run each step in the group + time.sleep(0.1) # allow UI to update between steps self.screenshot(f"{name}_{i + 1}") # take screenshot after each case group From 87a6b37d807d88ecab1cf953a9f2000a600a220a Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Oct 2025 00:25:22 -0500 Subject: [PATCH 18/20] add rest of screenshots --- .../ui/tests/test_ui/raylib_screenshots.py | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index e4e496a403750f..82b0d184caac6c 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -224,30 +224,30 @@ def setup_onroad_full_alert_long_text(click, pm: PubMaster): CASES: dict[str, Callable] = { "homescreen": setup_homescreen, - # "homescreen_paired": setup_homescreen, - # "homescreen_prime": setup_homescreen, - # "homescreen_update_available": setup_homescreen_update_available, - # "settings_device": setup_settings, - # "settings_network": setup_settings_network, - # "settings_network_advanced": setup_settings_network_advanced, - # "settings_toggles": setup_settings_toggles, - # "settings_software": setup_settings_software, - # "settings_software_download": setup_settings_software_download, - # "settings_software_release_notes": setup_settings_software_release_notes, - # "settings_firehose": setup_settings_firehose, - # "settings_developer": setup_settings_developer, - # "keyboard": setup_keyboard, - # "pair_device": setup_pair_device, - # "offroad_alert": setup_offroad_alert, - # "confirmation_dialog": setup_confirmation_dialog, - # "experimental_mode_description": setup_experimental_mode_description, - # "onroad": setup_onroad, - # "onroad_sidebar": setup_onroad_sidebar, - # "onroad_small_alert": setup_onroad_small_alert, - # "onroad_medium_alert": setup_onroad_medium_alert, - # "onroad_full_alert": setup_onroad_full_alert, - # "onroad_full_alert_multiline": setup_onroad_full_alert_multiline, - # "onroad_full_alert_long_text": setup_onroad_full_alert_long_text, + "homescreen_paired": setup_homescreen, + "homescreen_prime": setup_homescreen, + "homescreen_update_available": setup_homescreen_update_available, + "settings_device": setup_settings, + "settings_network": setup_settings_network, + "settings_network_advanced": setup_settings_network_advanced, + "settings_toggles": setup_settings_toggles, + "settings_software": setup_settings_software, + "settings_software_download": setup_settings_software_download, + "settings_software_release_notes": setup_settings_software_release_notes, + "settings_firehose": setup_settings_firehose, + "settings_developer": setup_settings_developer, + "keyboard": setup_keyboard, + "pair_device": setup_pair_device, + "offroad_alert": setup_offroad_alert, + "confirmation_dialog": setup_confirmation_dialog, + "experimental_mode_description": setup_experimental_mode_description, + "onroad": setup_onroad, + "onroad_sidebar": setup_onroad_sidebar, + "onroad_small_alert": setup_onroad_small_alert, + "onroad_medium_alert": setup_onroad_medium_alert, + "onroad_full_alert": setup_onroad_full_alert, + "onroad_full_alert_multiline": setup_onroad_full_alert_multiline, + "onroad_full_alert_long_text": setup_onroad_full_alert_long_text, } From 2f3a1e0c545d768c69edc47f1e679f765db46b1a Mon Sep 17 00:00:00 2001 From: David Date: Fri, 17 Oct 2025 00:34:57 -0500 Subject: [PATCH 19/20] fix types --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 82b0d184caac6c..02ce2ca588c03a 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -361,7 +361,7 @@ def __exit__(self, exc_type, exc_value, traceback): self._process = None # Override the TestUI method to run multiple tests, and to avoid starting another UI process - def test_ui(self, name, setup_cases: list[Callable] | list[list[Callable]]): + def test_ui(self, name, setup_cases: list[Callable | list[Callable]]): self.setup() time.sleep(UI_DELAY) self.screenshot(name) # initial screenshot From 8f09b02764afa4c88a3d7c4e0e63bf348477b9be Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:41:55 -0500 Subject: [PATCH 20/20] Fix screenshot naming Second screenshot should have _2 instead of _1 --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 376900204790ff..f365fc1bb419b0 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -381,7 +381,7 @@ def test_ui(self, name, setup_cases: list[Callable | list[Callable]]): for setup_case in group: setup_case(self.click, self.pm) # run each step in the group time.sleep(0.1) # allow UI to update between steps - self.screenshot(f"{name}_{i + 1}") # take screenshot after each case group + self.screenshot(f"{name}_{i + 2}") # take screenshot after each case group def create_screenshots():