#!/usr/bin/env python3
import os
import sys
import shutil
import time
import pathlib
from collections import namedtuple
import pyautogui
import pywinctl
from cereal import log
from cereal import messaging
from cereal . messaging import PubMaster
from openpilot . common . basedir import BASEDIR
from openpilot . common . params import Params
from openpilot . common . prefix import OpenpilotPrefix
from openpilot . selfdrive . test . helpers import with_processes
from openpilot . selfdrive . selfdrived . alertmanager import set_offroad_alert
from openpilot . system . updated . updated import parse_release_notes
AlertSize = log . SelfdriveState . AlertSize
AlertStatus = log . SelfdriveState . AlertStatus
TEST_DIR = pathlib . Path ( __file__ ) . parent
TEST_OUTPUT_DIR = TEST_DIR / " raylib_report "
SCREENSHOTS_DIR = TEST_OUTPUT_DIR / " screenshots "
UI_DELAY = 0.2
# Offroad alerts to test
OFFROAD_ALERTS = [ ' Offroad_IsTakingSnapshot ' ]
def put_update_params ( params : Params ) :
params . put ( " UpdaterCurrentReleaseNotes " , parse_release_notes ( BASEDIR ) )
params . put ( " UpdaterNewReleaseNotes " , parse_release_notes ( BASEDIR ) )
def setup_homescreen ( click , pm : PubMaster ) :
pass
def setup_homescreen_update_available ( click , pm : PubMaster ) :
params = Params ( )
params . put_bool ( " UpdateAvailable " , True )
put_update_params ( params )
setup_offroad_alert ( click , pm )
def setup_settings ( click , pm : PubMaster ) :
click ( 100 , 100 )
def close_settings ( click , pm : PubMaster ) :
click ( 240 , 216 )
def setup_settings_network ( click , pm : PubMaster ) :
setup_settings ( click , pm )
click ( 278 , 450 )
def setup_settings_network_advanced ( click , pm : PubMaster ) :
setup_settings_network ( click , pm )
click ( 1880 , 100 )
def setup_settings_toggles ( click , pm : PubMaster ) :
setup_settings ( click , pm )
click ( 278 , 600 )
def setup_settings_software ( click , pm : PubMaster ) :
put_update_params ( Params ( ) )
setup_settings ( click , pm )
click ( 278 , 720 )
def setup_settings_software_download ( click , pm : PubMaster ) :
params = Params ( )
# setup_settings_software but with "DOWNLOAD" button to test long text
params . put ( " UpdaterState " , " idle " )
params . put_bool ( " UpdaterFetchAvailable " , True )
setup_settings_software ( click , pm )
def setup_settings_software_release_notes ( click , pm : PubMaster ) :
setup_settings_software ( click , pm )
click ( 588 , 110 ) # expand description for current version
def setup_settings_firehose ( click , pm : PubMaster ) :
setup_settings ( click , pm )
click ( 278 , 845 )
def setup_settings_developer ( click , pm : PubMaster ) :
setup_settings ( click , pm )
click ( 278 , 950 )
def setup_keyboard ( click , pm : PubMaster ) :
setup_settings_developer ( click , pm )
click ( 1930 , 470 )
def setup_pair_device ( click , pm : PubMaster ) :
click ( 1950 , 800 )
def setup_offroad_alert ( click , pm : PubMaster ) :
put_update_params ( Params ( ) )
set_offroad_alert ( " Offroad_TemperatureTooHigh " , True , extra_text = ' 99C ' )
set_offroad_alert ( " Offroad_ExcessiveActuation " , True , extra_text = ' longitudinal ' )
for alert in OFFROAD_ALERTS :
set_offroad_alert ( alert , True )
setup_settings ( click , pm )
close_settings ( click , pm )
def setup_confirmation_dialog ( click , pm : PubMaster ) :
setup_settings ( click , pm )
click ( 1985 , 791 ) # reset calibration
def setup_experimental_mode_description ( click , pm : PubMaster ) :
setup_settings_toggles ( click , pm )
click ( 1200 , 280 ) # expand description for experimental mode
def setup_onroad ( click , pm : PubMaster ) :
ds = messaging . new_message ( ' deviceState ' )
ds . deviceState . started = True
ps = messaging . new_message ( ' pandaStates ' , 1 )
ps . pandaStates [ 0 ] . pandaType = log . PandaState . PandaType . dos
ps . pandaStates [ 0 ] . ignitionLine = True
driverState = messaging . new_message ( ' driverStateV2 ' )
driverState . driverStateV2 . leftDriverData . faceOrientation = [ 0 , 0 , 0 ]
for _ in range ( 5 ) :
pm . send ( ' deviceState ' , ds )
pm . send ( ' pandaStates ' , ps )
pm . send ( ' driverStateV2 ' , driverState )
ds . clear_write_flag ( )
ps . clear_write_flag ( )
driverState . clear_write_flag ( )
time . sleep ( 0.05 )
def setup_onroad_sidebar ( click , pm : PubMaster ) :
setup_onroad ( click , pm )
click ( 100 , 100 ) # open sidebar
def setup_onroad_small_alert ( click , pm : PubMaster ) :
setup_onroad ( click , pm )
alert = messaging . new_message ( ' selfdriveState ' )
alert . selfdriveState . alertSize = AlertSize . small
alert . selfdriveState . alertText1 = " Small Alert "
alert . selfdriveState . alertText2 = " This is a small alert "
alert . selfdriveState . alertStatus = AlertStatus . normal
for _ in range ( 5 ) :
pm . send ( ' selfdriveState ' , alert )
alert . clear_write_flag ( )
time . sleep ( 0.05 )
def setup_onroad_medium_alert ( click , pm : PubMaster ) :
setup_onroad ( click , pm )
alert = messaging . new_message ( ' selfdriveState ' )
alert . selfdriveState . alertSize = AlertSize . mid
alert . selfdriveState . alertText1 = " Medium Alert "
alert . selfdriveState . alertText2 = " This is a medium alert "
alert . selfdriveState . alertStatus = AlertStatus . userPrompt
for _ in range ( 5 ) :
pm . send ( ' selfdriveState ' , alert )
alert . clear_write_flag ( )
time . sleep ( 0.05 )
def setup_onroad_full_alert ( click , pm : PubMaster ) :
setup_onroad ( click , pm )
alert = messaging . new_message ( ' selfdriveState ' )
alert . selfdriveState . alertSize = AlertSize . full
alert . selfdriveState . alertText1 = " DISENGAGE IMMEDIATELY "
alert . selfdriveState . alertText2 = " Driver Distracted "
alert . selfdriveState . alertStatus = AlertStatus . critical
for _ in range ( 5 ) :
pm . send ( ' selfdriveState ' , alert )
alert . clear_write_flag ( )
time . sleep ( 0.05 )
def setup_onroad_full_alert_multiline ( click , pm : PubMaster ) :
setup_onroad ( click , pm )
alert = messaging . new_message ( ' selfdriveState ' )
alert . selfdriveState . alertSize = AlertSize . full
alert . selfdriveState . alertText1 = " Reverse \n Gear "
alert . selfdriveState . alertStatus = AlertStatus . normal
for _ in range ( 5 ) :
pm . send ( ' selfdriveState ' , alert )
alert . clear_write_flag ( )
time . sleep ( 0.05 )
def setup_onroad_full_alert_long_text ( click , pm : PubMaster ) :
setup_onroad ( click , pm )
alert = messaging . new_message ( ' selfdriveState ' )
alert . selfdriveState . alertSize = AlertSize . full
alert . selfdriveState . alertText1 = " TAKE CONTROL IMMEDIATELY "
alert . selfdriveState . alertText2 = " Calibration Invalid: Remount Device & Recalibrate "
alert . selfdriveState . alertStatus = AlertStatus . userPrompt
for _ in range ( 5 ) :
pm . send ( ' selfdriveState ' , alert )
alert . clear_write_flag ( )
time . sleep ( 0.05 )
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 ,
}
class TestUI :
def __init__ ( self ) :
os . environ [ " SCALE " ] = os . getenv ( " SCALE " , " 1 " )
sys . modules [ " mouseinfo " ] = False
def setup ( self ) :
# Seed minimal offroad state
self . pm = PubMaster ( [ " deviceState " , " pandaStates " , " driverStateV2 " , " selfdriveState " ] )
ds = messaging . new_message ( ' deviceState ' )
ds . deviceState . networkType = log . DeviceState . NetworkType . wifi
for _ in range ( 5 ) :
self . pm . send ( ' deviceState ' , ds )
ds . clear_write_flag ( )
time . sleep ( 0.05 )
time . sleep ( 0.5 )
try :
self . ui = pywinctl . getWindowsWithTitle ( " UI " ) [ 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 )
def screenshot ( self , name : str ) :
full_screenshot = pyautogui . screenshot ( )
cropped = full_screenshot . crop ( ( self . ui . left , self . ui . top , self . ui . left + self . ui . width , self . ui . top + self . ui . height ) )
cropped . save ( SCREENSHOTS_DIR / f " { name } .png " )
def click ( self , x : int , y : int , * args , * * kwargs ) :
pyautogui . mouseDown ( self . ui . left + x , self . ui . top + y , * args , * * kwargs )
time . sleep ( 0.01 )
pyautogui . mouseUp ( self . ui . left + x , self . ui . top + y , * args , * * kwargs )
@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 )
def create_screenshots ( ) :
if TEST_OUTPUT_DIR . exists ( ) :
shutil . rmtree ( TEST_OUTPUT_DIR )
SCREENSHOTS_DIR . mkdir ( parents = True )
t = TestUI ( )
for name , setup in CASES . items ( ) :
with OpenpilotPrefix ( ) :
params = Params ( )
params . put ( " DongleId " , " 123456789012345 " )
# Set branch name
description = " 0.10.1 / this-is-a-really-super-mega-long-branch-name / 7864838 / Oct 03 "
params . put ( " UpdaterCurrentDescription " , description )
params . put ( " UpdaterNewDescription " , description )
if name == " homescreen_paired " :
params . put ( " PrimeType " , 0 ) # NONE
elif name == " homescreen_prime " :
params . put ( " PrimeType " , 2 ) # LITE
t . test_ui ( name , setup )
if __name__ == " __main__ " :
create_screenshots ( )