Skip to content

Commit

Permalink
wip: refacto of webdriver
Browse files Browse the repository at this point in the history
  • Loading branch information
soonum committed Sep 17, 2024
1 parent e04addc commit 06a2a96
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 120 deletions.
332 changes: 332 additions & 0 deletions ci/webdriver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
# TODO
#
# Adapter les méthodes pour pouvoir runner les tests de web parallel
# Adapter les méthodes pour dumper les résultats des benchs
# Faire très attention sur la logique des timeouts
# Pouvoir indiquer un timeout par test potentiellement
# Probablement prévoir un json de conf par test, avec une conf par défaut
# Auto-discovery des tests ?

# 1. OK(setup CI/get driver)
# 2. Ok(run driver + run server with npm run server in the background (kill on completion))
# 3. auto discovery tests
# 4. load per test config (with default config if not specified)
# 5. run test with the config timeout
# 6. Ok(print logs (if possible as the test run so with a thread, no need for async, just that it prints from time to time))
# 7. If timeout -> fail the test
# 8. If fail fast stop, otherwise run all tests
# 9. Run tests sequentially
# 10. Ok(If success keep going and mark success)
# 11. Ok(On test success find a way if possible to flush logs)
# 12. Ok(Refresh the test page/the website)
# 13. Run the other tests

import argparse
import datetime
import pathlib
import time
import threading
import enum
import os
import signal
import subprocess
import socket
import sys

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException


parser = argparse.ArgumentParser()
parser.add_argument(
"-a",
"--address",
dest="address",
default="localhost",
help="Address to testing Node server",
)
parser.add_argument(
"-p",
"--port",
dest="port",
default=3000,
type=int,
help="Port to testing Node server",
)
parser.add_argument(
"-k",
"--browser-kind",
dest="browser_kind",
choices=["chrome", "firefox"],
required=True,
help="Path to web driver file",
)
parser.add_argument(
"-b",
"--browser-path",
dest="browser_path",
required=True,
help="Path to browser file",
)
parser.add_argument(
"-d",
"--driver-path",
dest="driver_path",
required=True,
help="Path to web driver file",
)
parser.add_argument(
"-f",
"--fail-fast",
dest="fail_fast",
action="store_true",
help="Exit on first failed test",
)
parser.add_argument(
"--server-cmd",
dest="server_cmd",
help="Command to execute to launch web server in the background",
)
parser.add_argument(
"--server-workdir",
dest="server_workdir",
help="Path to working directory to launch web server",
)


class BrowserKind(enum.Enum):
chrome = 1
firefox = 2


class Driver:
def __init__(self, browser_path, driver_path, browser_kind, threaded_logs=False):
self.browser_path = browser_path
self.driver_path = driver_path

self.browser_kind = browser_kind

self.options = Options()
self.options.binary_location = self.browser_path
self.options.add_argument("--headless")

self.driver = None

self.shutting_down = False

self._log_thread = None
if threaded_logs:
self._log_thread = threading.Thread(target=self._threaded_logs)

def set_capability(self, name, value):
self.options.set_capability(name, value)

def get_driver(self):
if self.driver is None:
driver_service = Service(self.driver_path)

match self.browser_kind:
case BrowserKind.chrome:
self.driver = webdriver.Chrome(
service=driver_service, options=self.options
)
# TODO: Add Firefox support
case _:
print(
f"{self.browser_kind.name.capitalize()} browser driver is not supported"
)
sys.exit(1)

if self._log_thread:
self._log_thread.start()

return self.driver

def get_page(self, server_url, timeout=10):
dr = self.get_driver()
dr.get(server_url)
self.wait_for_page_load(self.get_waiter(timeout))

def get_waiter(self, timeout):
return WebDriverWait(self.get_driver(), timeout)

# Function to wait for page to fully load
def wait_for_page_load(self, waiter):
waiter.until(
lambda d: d.execute_script("return document.readyState") == "complete"
)

def wait_for_button(self, waiter, element_id):
return waiter.until(EC.element_to_be_clickable((By.ID, element_id)))

def wait_for_selection(self, waiter, element):
return waiter.until(EC.element_to_be_selected(element))

def find_element(self, element_id):
return self.get_driver().find_element(By.ID, element_id)

def print_log(self, log_type):
logs = self.get_driver().get_log(log_type)
for log in logs:
print(f"Console log: {log}")

def _threaded_logs(self):
while not self.shutting_down:
self.print_log("browser")
time.sleep(0.2)

def refresh(self):
self.get_driver().refresh()

def quit(self):
self.shutting_down = True
if self._log_thread:
self._log_thread.join()

self.get_driver().quit()


def run_test(driver, button_id):
waiter = driver.get_waiter(10)

# Wait for the button to be clickable before interacting with it
button = driver.wait_for_button(waiter, button_id)
button.click() # Click the button

# Wait for the checkbox to be checked while printing logs
checkbox_id = "testSuccess" # Replace with the actual checkbox ID

try:
checkbox = driver.find_element(checkbox_id) # Re-locate the checkbox element

# Use a loop with a sleep to print logs every 2 seconds while waiting for the checkbox
while True:
try:
checkbox = driver.wait_for_selection(waiter, checkbox)
break # Exit loop when the checkbox is checked
except TimeoutException:
pass # Continue waiting if the checkbox is not checked

time.sleep(2) # Pause for 2 seconds before checking the checkbox again

# After the checkbox is confirmed to be checked, refresh the page
driver.refresh()
# Wait for the page to load after refresh
driver.wait_for_page_load(waiter)

except TimeoutException:
print("timed out waiting for result checkbox to be checked")
raise


def start_web_server(
command, working_directory, server_address, server_port, startup_timeout=30
):
try:
sock = socket.create_connection((server_address, server_port), timeout=2)
except (TimeoutError, ConnectionRefusedError):
# Nothing is alive at this URL, ignoring exception
pass
else:
sock.close()
raise ConnectionError(
f"address and port already in use at ({server_address}, {server_port})"
)

proc = subprocess.Popen(
command.split(),
cwd=working_directory,
stdout=subprocess.DEVNULL,
start_new_session=True,
)

print("Starting web server")

timeout_seconds = 0.5
start_date = datetime.datetime.now()
while int((datetime.datetime.now() - start_date).total_seconds()) < startup_timeout:
try:
sock = socket.create_connection(
(server_address, server_port), timeout=timeout_seconds
)
except TimeoutError:
pass
except ConnectionRefusedError:
time.sleep(timeout_seconds)
else:
sock.close()
break
else:
terminate_web_server(proc.pid)
raise TimeoutError(
f"timeout after {startup_timeout} seconds while waiting for web server"
)

return proc


def terminate_web_server(pid):
os.killpg(os.getpgid(pid), signal.SIGTERM)


if __name__ == "__main__":
args = parser.parse_args()
browser_kind = BrowserKind[args.browser_kind]

server_process = None
if args.server_cmd:
try:
server_process = start_web_server(
args.server_cmd, args.server_workdir, args.address, args.port
)
except Exception as err:
print(f"Failed to start web server (error: {err})")
sys.exit(1)

print("Starting web driver")
driver = Driver(
args.browser_path, args.driver_path, browser_kind, threaded_logs=True
)
match browser_kind:
case BrowserKind.chrome:
driver.set_capability("goog:loggingPrefs", {"browser": "ALL"})
case _:
# A no-op for browser that are not supported
pass

driver.get_page(f"http://{args.address}:{args.port}", timeout=10)

failures = []

# TODO faire une boucle for ici avec tous les tests/benchs à exécuter
try:
run_test(driver, "compressedServerKeyBenchMessage2Carry2")
print(f"SUCCESS: <TEST_NAME_TO_DISPLAY>")
except Exception: # TODO Faut-il ratisser plus large que TimeoutException
if args.fail_fast:
print(f"FAIL: <TEST_NAME_TO_DISPLAY>")
print("Fail fast is enabled, exiting")
if server_process:
server_process.terminate()
sys.exit(1)
else:
# TODO stocker ici le nom du test ayant échoué
pass

print("Shutting down web driver")
# Close the browser
driver.quit()

if server_process:
print("Shutting down web server")
terminate_web_server(server_process.pid)

if failures:
for fail in failures:
print(f"FAIL: {fail}")
sys.exit(1)
1 change: 1 addition & 0 deletions ci/webdriver_requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
selenium==4.24.0
Loading

0 comments on commit 06a2a96

Please sign in to comment.