mirror of
				https://github.com/thunderbrewhq/thunderbrew
				synced 2025-11-01 00:36:04 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			185 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| import argparse
 | |
| import contextlib
 | |
| import logging
 | |
| import os
 | |
| import pathlib
 | |
| import shlex
 | |
| import sys
 | |
| import time
 | |
| from typing import Optional
 | |
| import urllib.parse
 | |
| 
 | |
| from selenium import webdriver
 | |
| import selenium.common.exceptions
 | |
| from selenium.webdriver.common.by import By
 | |
| from selenium.webdriver.support.ui import WebDriverWait
 | |
| 
 | |
| 
 | |
| logger = logging.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| class SDLSeleniumTestDriver:
 | |
|     def __init__(self, server: str, test: str, arguments: list[str], browser: str, firefox_binary: Optional[str]=None, chrome_binary: Optional[str]=None):
 | |
|         self. server = server
 | |
|         self.test = test
 | |
|         self.arguments = arguments
 | |
|         self.chrome_binary = chrome_binary
 | |
|         self.firefox_binary = firefox_binary
 | |
|         self.driver = None
 | |
|         self.stdout_printed = False
 | |
|         self.failed_messages: list[str] = []
 | |
|         self.return_code = None
 | |
| 
 | |
|         options = [
 | |
|             "--headless",
 | |
|         ]
 | |
| 
 | |
|         driver_contructor = None
 | |
|         match browser:
 | |
|             case "firefox":
 | |
|                 driver_contructor = webdriver.Firefox
 | |
|                 driver_options = webdriver.FirefoxOptions()
 | |
|                 if self.firefox_binary:
 | |
|                     driver_options.binary_location = self.firefox_binary
 | |
|             case "chrome":
 | |
|                 driver_contructor = webdriver.Chrome
 | |
|                 driver_options = webdriver.ChromeOptions()
 | |
|                 if self.chrome_binary:
 | |
|                     driver_options.binary_location = self.chrome_binary
 | |
|                 options.append("--no-sandbox")
 | |
|         if driver_contructor is None:
 | |
|             raise ValueError(f"Invalid {browser=}")
 | |
|         for o in options:
 | |
|             driver_options.add_argument(o)
 | |
|         logger.debug("About to create driver")
 | |
|         self.driver = driver_contructor(options=driver_options)
 | |
| 
 | |
|     @property
 | |
|     def finished(self):
 | |
|         return len(self.failed_messages) > 0 or self.return_code is not None
 | |
| 
 | |
|     def __del__(self):
 | |
|         if self.driver:
 | |
|             self.driver.quit()
 | |
| 
 | |
|     @property
 | |
|     def url(self):
 | |
|         req = {
 | |
|             "loghtml": "1",
 | |
|             "SDL_ASSERT": "abort",
 | |
|         }
 | |
|         for key, value in os.environ.items():
 | |
|             if key.startswith("SDL_"):
 | |
|                 req[key] = value
 | |
|         req.update({f"arg_{i}": a for i, a in enumerate(self.arguments, 1) })
 | |
|         req_str = urllib.parse.urlencode(req)
 | |
|         return f"{self.server}/{self.test}.html?{req_str}"
 | |
| 
 | |
|     @contextlib.contextmanager
 | |
|     def _selenium_catcher(self):
 | |
|         try:
 | |
|             yield
 | |
|             success = True
 | |
|         except selenium.common.exceptions.UnexpectedAlertPresentException as e:
 | |
|             # FIXME: switch context, verify text of dialog and answer "a" for abort
 | |
|             wait = WebDriverWait(self.driver, timeout=2)
 | |
|             try:
 | |
|                 alert = wait.until(lambda d: d.switch_to.alert)
 | |
|             except selenium.common.exceptions.NoAlertPresentException:
 | |
|                 self.failed_messages.append(e.msg)
 | |
|                 return False
 | |
|             self.failed_messages.append(alert)
 | |
|             if "Assertion failure" in e.msg and "[ariA]" in e.msg:
 | |
|                 alert.send_keys("a")
 | |
|                 alert.accept()
 | |
|             else:
 | |
|                 self.failed_messages.append(e.msg)
 | |
|             success = False
 | |
|         return success
 | |
| 
 | |
|     def get_stdout_and_print(self):
 | |
|         if self.stdout_printed:
 | |
|             return
 | |
|         with self._selenium_catcher():
 | |
|             div_terminal = self.driver.find_element(by=By.ID, value="terminal")
 | |
|             assert div_terminal
 | |
|             text = div_terminal.text
 | |
|             print(text)
 | |
|             self.stdout_printed = True
 | |
| 
 | |
|     def update_return_code(self):
 | |
|         with self._selenium_catcher():
 | |
|             div_process_quit = self.driver.find_element(by=By.ID, value="process-quit")
 | |
|             if not div_process_quit:
 | |
|                 return
 | |
|             if div_process_quit.text != "":
 | |
|                 try:
 | |
|                     self.return_code = int(div_process_quit.text)
 | |
|                 except ValueError:
 | |
|                     raise ValueError(f"process-quit element contains invalid data: {div_process_quit.text:r}")
 | |
| 
 | |
|     def loop(self):
 | |
|         print(f"Connecting to \"{self.url}\"", file=sys.stderr)
 | |
|         self.driver.get(url=self.url)
 | |
|         self.driver.implicitly_wait(0.2)
 | |
| 
 | |
|         while True:
 | |
|             self.update_return_code()
 | |
|             if self.finished:
 | |
|                 break
 | |
|             time.sleep(0.1)
 | |
| 
 | |
|         self.get_stdout_and_print()
 | |
|         if not self.stdout_printed:
 | |
|             self.failed_messages.append("Failed to get stdout/stderr")
 | |
| 
 | |
| 
 | |
| 
 | |
| def main() -> int:
 | |
|     parser = argparse.ArgumentParser(allow_abbrev=False, description="Selenium SDL test driver")
 | |
|     parser.add_argument("--browser", default="firefox", choices=["firefox", "chrome"], help="browser")
 | |
|     parser.add_argument("--server", default="http://localhost:8080", help="Server where SDL tests live")
 | |
|     parser.add_argument("--verbose", action="store_true", help="Verbose logging")
 | |
|     parser.add_argument("--chrome-binary", help="Chrome binary")
 | |
|     parser.add_argument("--firefox-binary", help="Firefox binary")
 | |
| 
 | |
|     index_double_dash = sys.argv.index("--")
 | |
|     if index_double_dash < 0:
 | |
|         parser.error("Missing test arguments. Need -- <FILENAME> <ARGUMENTS>")
 | |
|     driver_arguments = sys.argv[1:index_double_dash]
 | |
|     test = pathlib.Path(sys.argv[index_double_dash+1]).name
 | |
|     test_arguments = sys.argv[index_double_dash+2:]
 | |
| 
 | |
|     args = parser.parse_args(args=driver_arguments)
 | |
| 
 | |
|     logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
 | |
| 
 | |
|     logger.debug("driver_arguments=%r test=%r test_arguments=%r", driver_arguments, test, test_arguments)
 | |
| 
 | |
|     sdl_test_driver = SDLSeleniumTestDriver(
 | |
|         server=args.server,
 | |
|         test=test,
 | |
|         arguments=test_arguments,
 | |
|         browser=args.browser,
 | |
|         chrome_binary=args.chrome_binary,
 | |
|         firefox_binary=args.firefox_binary,
 | |
|     )
 | |
|     sdl_test_driver.loop()
 | |
| 
 | |
|     rc = sdl_test_driver.return_code
 | |
|     if sdl_test_driver.failed_messages:
 | |
|         for msg in sdl_test_driver.failed_messages:
 | |
|             print(f"FAILURE MESSAGE: {msg}", file=sys.stderr)
 | |
|         if rc == 0:
 | |
|             print(f"Test signaled success (rc=0) but a failure happened", file=sys.stderr)
 | |
|             rc = 1
 | |
|     sys.stdout.flush()
 | |
|     logger.info("Exit code = %d", rc)
 | |
|     return rc
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     raise SystemExit(main())
 | 
