ui replay: Improve big (tizi) replay coverage (#37468)

* fix pairing qr code

* test pair device

* merge and pick from explore-more

* key

* fast click

* again

* add branch selection test

* click uninstall

* test prime states

* view regulatory

* test expand calibration desc

* override interactive timeout

* reorder

* remove todo

* update

* clarify

* test reset calibration

* update

* add calibration params test

* comments

* reorganize

* clarify

* add click through training guide
This commit is contained in:
David
2026-03-10 18:02:55 -05:00
committed by GitHub
parent b750229e70
commit f85b3473a2
2 changed files with 133 additions and 29 deletions
+12 -2
View File
@@ -7,9 +7,12 @@ import pyray as rl
from typing import Literal
from collections.abc import Callable
from cereal.messaging import PubMaster
from openpilot.common.api import Api
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR
from openpilot.system.updated.updated import parse_release_notes
from openpilot.system.version import terms_version, training_version
LayoutVariant = Literal["mici", "tizi"]
@@ -25,13 +28,16 @@ def setup_state():
params.put("DongleId", "test123456789")
# Combined description for layouts that still use it (BIG home, settings/software)
params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30")
params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR))
# Params for mici home
params.put("Version", "0.10.1")
params.put("GitBranch", "test-branch")
params.put("GitCommit", "abc12340ff9131237ba23a1d0fbd8edf9c80e87")
params.put("GitCommitDate", "'1732924800 2024-11-30 00:00:00 +0000'")
# Patch Api.get_token to return a static token so the pairing QR code is deterministic across runs
Api.get_token = lambda self, payload_extra=None, expiry_hours=0: "test_token"
def run_replay(variant: LayoutVariant) -> None:
if HEADLESS:
@@ -41,7 +47,7 @@ def run_replay(variant: LayoutVariant) -> None:
setup_state()
os.makedirs(DIFF_OUT_DIR, exist_ok=True)
from openpilot.selfdrive.ui.ui_state import ui_state # Import within OpenpilotPrefix context so param values are setup correctly
from openpilot.selfdrive.ui.ui_state import ui_state, device # Import within OpenpilotPrefix context so param values are setup correctly
from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage
from openpilot.selfdrive.ui.tests.diff.replay_script import build_script
@@ -54,6 +60,10 @@ def run_replay(variant: LayoutVariant) -> None:
from openpilot.selfdrive.ui.layouts.main import MainLayout
main_layout = MainLayout()
# Disable interactive timeout — replay clicks use left_down=False so they never reset the timer,
# and after 30s of real wall-clock time the settings panel would close automatically.
device.set_override_interactive_timeout(99999)
pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"])
script = build_script(pm, main_layout, variant)
script_index = 0
+121 -27
View File
@@ -3,15 +3,20 @@ from typing import TYPE_CHECKING
from collections.abc import Callable
from dataclasses import dataclass
import math
from cereal import car, log, messaging
from cereal.messaging import PubMaster
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.selfdrive.ui.lib.prime_state import PrimeType
from openpilot.selfdrive.ui.tests.diff.replay import FPS, LayoutVariant
from openpilot.system.updated.updated import parse_release_notes
WAIT = int(FPS * 0.5) # Default frames to wait after events
# Default frames to wait after events
WAIT = FPS // 2
FAST_CLICK = FPS // 6
AlertSize = log.SelfdriveState.AlertSize
AlertStatus = log.SelfdriveState.AlertStatus
@@ -79,26 +84,48 @@ class Script:
# --- Setup functions ---
def put_update_params(params: Params | None = None) -> None:
if params is None:
params = Params()
params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR))
params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR))
params.put("UpdaterTargetBranch", BRANCH_NAME)
def set_prime_state(prime_type: PrimeType) -> None:
from openpilot.selfdrive.ui.ui_state import ui_state
ui_state.prime_state.set_type(prime_type)
def setup_offroad_alerts() -> None:
put_update_params(Params())
set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C')
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal')
set_offroad_alert("Offroad_IsTakingSnapshot", True)
def setup_update_available() -> None:
def setup_update_available(available: bool = True) -> None:
params = Params()
params.put_bool("UpdateAvailable", True)
params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01")
put_update_params(params)
params.put_bool("UpdateAvailable", available)
params.put("UpdaterAvailableBranches", ",".join(["test-branch", "test-branch-2", BRANCH_NAME]))
if available:
params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01")
params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR))
params.put("UpdaterTargetBranch", BRANCH_NAME)
else:
params.remove("UpdaterNewDescription")
params.remove("UpdaterNewReleaseNotes")
params.remove("UpdaterTargetBranch")
def setup_calibration_params() -> None:
params = Params()
# live calibration
calib = messaging.new_message('liveCalibration')
calib.liveCalibration.calStatus = log.LiveCalibrationData.Status.calibrated
calib.liveCalibration.rpyCalib = [0.0, math.radians(2.5), math.radians(-1.2)]
params.put("CalibrationParams", calib.to_bytes())
# live delay
delay = messaging.new_message('liveDelay')
delay.liveDelay.calPerc = 75
params.put("LiveDelay", delay.to_bytes())
# live torque parameters
torque = messaging.new_message('liveTorqueParameters')
torque.liveTorqueParameters.useParams = True
torque.liveTorqueParameters.calPerc = 60
params.put("LiveTorqueParameters", torque.to_bytes())
def setup_developer_params() -> None:
@@ -132,7 +159,6 @@ def make_network_state_setup(pm: PubMaster, network_type) -> Callable:
def make_alert_setup(pm: PubMaster, size, text1, text2, status) -> Callable:
def _send() -> None:
send_onroad(pm)
alert = messaging.new_message('selfdriveState')
ss = alert.selfdriveState
ss.alertSize = size
@@ -171,34 +197,104 @@ def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None:
return setup
def add_prime_state_setup(prime_type: PrimeType) -> None:
script.set_send(lambda: set_prime_state(prime_type))
def do_onboarding() -> None:
"""Click through the training guide and close."""
from openpilot.selfdrive.ui.layouts.onboarding import STEP_RECTS
step = 0
for step_rect in STEP_RECTS:
if step < len(STEP_RECTS) - 1:
script.click(int(step_rect.x), int(step_rect.y), wait_after=FAST_CLICK)
else:
script.click(950, 900) # On the last step, click Finish instead of restart
step += 1
def type_keyboard() -> None:
"""Types 8 characters using the big keyboard to test different layouts and interactions."""
KEY = (150, 430) # e.g. 'Q' key
SHIFT = (150, 750) # also symbols key in number mode
NUMBERS = (150, 950)
SPACE = (1060, 950)
BACKSPACE = (2000, 780)
for key in [
SHIFT, KEY, KEY, SHIFT, SHIFT, KEY, KEY, # test casing (upper, lower, caps lock)
SPACE, SPACE, BACKSPACE, BACKSPACE, # test multiple space and backspace
NUMBERS, KEY, KEY, SHIFT, KEY, KEY # test numbers and symbols
]:
script.click(*key, wait_after=FAST_CLICK)
# TODO: Better way of organizing the events
# === Homescreen ===
script.set_send(make_network_state_setup(pm, log.DeviceState.NetworkType.wifi))
# === Offroad Alerts (auto-transitions via HomeLayout refresh) ===
script.setup(make_home_refresh_setup(setup_offroad_alerts))
# Go through different prime state layouts
add_prime_state_setup(PrimeType.LITE)
add_prime_state_setup(PrimeType.NONE)
add_prime_state_setup(PrimeType.UNPAIRED)
# === Update Available (auto-transitions via HomeLayout refresh) ===
script.setup(make_home_refresh_setup(setup_update_available))
# === Settings - Device (click sidebar settings button) ===
# === Offroad Alerts (auto-transitions via HomeLayout refresh, overrides update) ===
script.setup(make_home_refresh_setup(setup_offroad_alerts))
script.click(620, 950) # close alerts
# === Settings (click sidebar settings button) ===
script.click(150, 90)
script.click(1985, 790) # reset calibration confirmation
script.click(650, 750) # cancel
# === Settings - Device ===
# pair device
script.click(2000, 450) # pair device
script.click(110, 110) # close pairing dialog
add_prime_state_setup(PrimeType.NONE) # changed from unpaired to hide pair device button
# calibration
script.setup(setup_calibration_params, wait_after=0)
script.click(1000, 620) # expand calibration description
script.click(2000, 620) # reset calibration confirmation
script.click(1500, 750) # confirm reset
script.click(1000, 620) # collapse calibration description
# training guide
script.click(2000, 800) # open training guide
do_onboarding()
# regulatory info
script.click(2000, 970) # regulatory button
script.click(2000, 970) # OK
# === Settings - Network ===
script.click(278, 450)
# TODO: mock networks
script.click(1880, 100) # advanced network settings
script.click(630, 80) # back
# Keyboard (tethering password)
script.click(2000, 420, wait_after=FAST_CLICK) # open tether password keyboard
script.click(2000, 950, wait_after=FAST_CLICK) # click confirm (disabled, should not close)
script.click(2000, 115) # cancel (close without typing)
script.click(2000, 420, wait_after=FAST_CLICK) # open keyboard again
type_keyboard() # test various keyboard layouts and interactions
script.click(2050, 250, wait_after=FAST_CLICK) # toggle show/hide password
script.click(2000, 950) # confirm (close keyboard)
script.click(630, 80) # back from advanced network
# === Settings - Toggles ===
script.click(278, 600)
script.click(1200, 280) # experimental mode description
script.click(1200, 280) # expand experimental mode description
# === Settings - Software ===
script.setup(put_update_params, wait_after=0)
script.click(278, 720)
script.setup(lambda: setup_update_available(False), wait_after=0) # start with no update available
script.click(278, 720) # software
for _ in range(2):
script.click(720, 120) # toggle current release notes
script.setup(setup_update_available) # set update available
for _ in range(2):
script.click(720, 450) # toggle new release notes
script.click(2000, 630) # open select branch dialog
script.click(1000, 300) # select 1st option
script.click(1600, 900) # confirm selection
script.click(2000, 800) # uninstall
script.click(650, 750) # cancel uninstall
# === Settings - Firehose ===
script.click(278, 845)
@@ -206,13 +302,11 @@ def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None:
# === Settings - Developer (set CarParamsPersistent first) ===
script.setup(setup_developer_params, wait_after=0)
script.click(278, 950)
script.click(1930, 470) # SSH keys (keyboard)
script.click(1930, 115) # click cancel on keyboard
script.click(2000, 960) # toggle alpha long
script.click(1500, 875) # confirm
# === Keyboard modal (SSH keys button in developer panel) ===
script.click(1930, 470) # click SSH keys
script.click(1930, 115) # click cancel on keyboard
# === Close settings ===
script.click(250, 160)