You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sdl/test/emscripten/driver.py

184 lines
6.2 KiB
Python

#!/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
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
if driver_contructor is None:
raise ValueError(f"Invalid {browser=}")
options = [
"--headless",
]
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())