You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							325 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
	
	
							325 lines
						
					
					
						
							10 KiB
						
					
					
				#!/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 car, 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):
 | 
						|
  CP = car.CarParams()
 | 
						|
  CP.alphaLongitudinalAvailable = True  # show alpha long control toggle
 | 
						|
  Params().put("CarParamsPersistent", CP.to_bytes())
 | 
						|
 | 
						|
  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_openpilot_long_confirmation_dialog(click, pm: PubMaster):
 | 
						|
  setup_settings_developer(click, pm)
 | 
						|
  click(2000, 960)  # toggle openpilot longitudinal control
 | 
						|
 | 
						|
 | 
						|
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\nGear"
 | 
						|
  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,
 | 
						|
  "openpilot_long_confirmation_dialog": setup_openpilot_long_confirmation_dialog,
 | 
						|
  "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()
 | 
						|
 |