diff --git a/tests/ui/Dockerfile b/tests/ui/Dockerfile new file mode 100644 index 000000000..61628534d --- /dev/null +++ b/tests/ui/Dockerfile @@ -0,0 +1,41 @@ +FROM ubuntu:focal + +ARG DEBIAN_FRONTEND=noninteractive +RUN echo "===> Installing system dependencies..." && \ + BUILD_DEPS="curl unzip" && \ + apt-get update && apt-get install --no-install-recommends -y \ + python3 python3-pip wget git zip unzip \ + libappindicator3-1 libasound2 libdbus-glib-1-2 libxtst6 libxt6 firefox firefox-geckodriver \ + $BUILD_DEPS + +RUN echo "===> Installing geckodriver for firefox..." && \ + GECKODRIVER_VERSION=`curl -i https://github.com/mozilla/geckodriver/releases/latest | grep -Po 'v[0-9]+.[0-9]+.[0-9]+'` && \ + wget https://github.com/mozilla/geckodriver/releases/download/$GECKODRIVER_VERSION/geckodriver-$GECKODRIVER_VERSION-linux64.tar.gz && \ + tar -zxf geckodriver-$GECKODRIVER_VERSION-linux64.tar.gz -C /usr/local/bin && \ + chmod +x /usr/local/bin/geckodriver && \ + rm geckodriver-$GECKODRIVER_VERSION-linux64.tar.gz + +RUN echo "===> Remove build dependencies..." && \ + apt-get remove -y $BUILD_DEPS && rm -rf /var/lib/apt/lists/* /var/cache/apt/* + +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 +ENV PYTHONUNBUFFERED=1 + +ENV APP_HOME /usr/src/app +WORKDIR /$APP_HOME + +COPY requirements.txt $APP_HOME/requirements.txt + +RUN pip install -r requirements.txt + +COPY main.py $APP_HOME/main.py +COPY test.zip $APP_HOME/ + +RUN wget https://github.com/bunkerity/bunkerweb-plugins/archive/refs/heads/main.zip && \ + unzip main.zip && \ + mv bunkerweb-plugins-main/discord . && \ + zip -r discord.zip discord && \ + rm -rf bunkerweb-plugins-main main.zip + +CMD ["python3", "main.py"] diff --git a/tests/ui/docker-compose.yml b/tests/ui/docker-compose.yml new file mode 100644 index 000000000..8cc83b14d --- /dev/null +++ b/tests/ui/docker-compose.yml @@ -0,0 +1,117 @@ +version: "3.5" + +services: + mybunker: + # image: bunkerity/bunkerweb:1.4.3 + build: + context: ../.. + dockerfile: src/bw/Dockerfile + ports: + - 80:8080 + - 443:8443 + environment: + SERVER_NAME: "www.example.com" + MULTISITE: "yes" + API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24" + USE_BUNKERNET: "no" + USE_BLACKLIST: "no" + DISABLE_DEFAULT_SERVER: "yes" + USE_CLIENT_CACHE: "yes" + USE_GZIP: "yes" + DATASTORE_MEMORY_SIZE: "384m" + www.example.com_USE_UI: "yes" + www.example.com_SERVE_FILES: "no" + www.example.com_USE_REVERSE_PROXY: "yes" + www.example.com_REVERSE_PROXY_URL: "/admin/" + www.example.com_REVERSE_PROXY_HOST: "http://bw-ui:7000" + www.example.com_REVERSE_PROXY_HEADERS: "X-Script-Name /admin" + www.example.com_REVERSE_PROXY_INTERCEPT_ERRORS: "no" + www.example.com_CUSTOM_CONF_MODSEC_CRS_config: 'SecRule REQUEST_FILENAME "@rx /global_config$$" "id:999,ctl:ruleRemoveByTag=platform-pgsql,nolog"' + CUSTOM_CONF_SERVER_HTTP_port-redirect: "port_in_redirect on;" + labels: + - "bunkerweb.INSTANCE" + networks: + bw-universe: + bw-services: + ipv4_address: 192.168.0.2 + + bw-scheduler: + build: + context: ../.. + dockerfile: src/scheduler/Dockerfile + depends_on: + - mybunker + environment: + DOCKER_HOST: "tcp://docker-proxy:2375" + volumes: + - bw-data:/data + networks: + - bw-universe + - net-docker + + bw-ui: + # image: bunkerity/bunkerweb-ui:1.4.3 + build: + context: ../.. + dockerfile: src/ui/Dockerfile + depends_on: + - mybunker + - docker-proxy + environment: + ABSOLUTE_URI: "http://www.example.com:8080/admin/" + ADMIN_USERNAME: "admin" + ADMIN_PASSWORD: "admin" + DOCKER_HOST: "tcp://docker-proxy:2375" + volumes: + - bw-data:/data + networks: + - net-docker + - bw-universe + + docker-proxy: + image: tecnativa/docker-socket-proxy + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + - CONTAINERS=1 + networks: + - net-docker + + app1: + image: tutum/hello-world + networks: + bw-services: + ipv4_address: 192.168.0.4 + + ui-tests: + build: + context: . + dockerfile: Dockerfile + environment: + - PYTHONUNBUFFERED=1 + extra_hosts: + - "www.example.com:192.168.0.2" + - "app1.example.com:192.168.0.2" + networks: + bw-services: + ipv4_address: 192.168.0.3 + +volumes: + bw-data: + mariadb: + + +networks: + bw-universe: + name: bw-universe + ipam: + driver: default + config: + - subnet: 10.20.30.0/24 + bw-services: + name: bw-services + ipam: + driver: default + config: + - subnet: 192.168.0.0/24 + net-docker: diff --git a/tests/ui/geckodriver b/tests/ui/geckodriver new file mode 100755 index 000000000..01165dbc9 Binary files /dev/null and b/tests/ui/geckodriver differ diff --git a/tests/ui/main.py b/tests/ui/main.py new file mode 100644 index 000000000..66804b8f2 --- /dev/null +++ b/tests/ui/main.py @@ -0,0 +1,1274 @@ +from contextlib import suppress +from datetime import datetime, timedelta +from os import getcwd, listdir +from os.path import join +from time import sleep +from traceback import format_exc +from typing import List, Union +from requests import get +from requests.exceptions import RequestException +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.firefox.options import Options +from selenium.webdriver.firefox.service import Service +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import ( + ElementClickInterceptedException, + TimeoutException, +) + +try: + ready = False + retries = 0 + while ready is False: + with suppress(RequestException): + status_code = get("http://www.example.com:8080/admin").status_code + + if status_code > 500: + print("An error occurred with the server, exiting ...", flush=True) + exit(1) + + ready = status_code < 400 + + if retries > 10: + print("UI took too long to be ready, exiting ...", flush=True) + exit(1) + elif ready is False: + retries += 1 + print("Waiting for UI to be ready, retrying in 5s ...", flush=True) + sleep(5) + + print("UI is ready, starting tests ...", flush=True) + + firefox_options = Options() + firefox_options.add_argument("--headless") + + print("Starting Firefox ...", flush=True) + + def safe_get_element( + driver, by: By, _id: str, *, multiple: bool = False, error: bool = False + ) -> Union[WebElement, List[WebElement]]: + try: + return WebDriverWait(driver, 4).until( + EC.presence_of_element_located((by, _id)) + if multiple is False + else EC.presence_of_all_elements_located((by, _id)) + ) + except TimeoutException as e: + if error is True: + raise e + + print( + f'Element searched by {by}: "{_id}" not found, exiting ...', flush=True + ) + exit(1) + + def assert_button_click(driver, button: Union[str, WebElement]): + clicked = False + while clicked is False: + with suppress(ElementClickInterceptedException): + if isinstance(button, str): + button = safe_get_element(driver, By.XPATH, button) + + sleep(0.5) + + button.click() + clicked = True + + def assert_alert_message(driver, message: str): + safe_get_element(driver, By.XPATH, "//button[@flash-sidebar-open='']") + + sleep(0.3) + + assert_button_click(driver, "//button[@flash-sidebar-open='']") + + error = False + while True: + try: + alerts = safe_get_element( + driver, + By.XPATH, + "//aside[@flash-sidebar='']/div[2]/div", + multiple=True, + error=True, + ) + break + except TimeoutException: + if error is True: + print("Messages list not found, exiting ...", flush=True) + exit(1) + error = True + driver.refresh() + + is_in = False + for alert in alerts: + if message in alert.text: + is_in = True + break + + if is_in is False: + print( + f'Message "{message}" not found in one of the messages in the list, exiting ...', + flush=True, + ) + exit(1) + + print( + f'Message "{message}" found in one of the messages in the list', flush=True + ) + + assert_button_click( + driver, "//aside[@flash-sidebar='']/*[local-name() = 'svg']" + ) + + def access_page( + driver, + driver_wait: WebDriverWait, + button: Union[str, WebElement], + name: str, + message: bool = True, + ): + assert_button_click(driver, button) + + try: + title = driver_wait.until( + EC.presence_of_element_located( + (By.XPATH, "/html/body/div/header/div/nav/h6") + ) + ) + + if title.text != name.replace(" ", "_").title(): + print(f"Didn't get redirected to {name} page, exiting ...", flush=True) + exit(1) + except TimeoutException: + print(f"{name.title()} page didn't load in time, exiting ...", flush=True) + exit(1) + + if message is True: + print( + f"{name.title()} page loaded successfully", + flush=True, + ) + + with webdriver.Firefox( + service=Service("./geckodriver") + if "geckodriver" in listdir(getcwd()) + else None, + options=firefox_options, + ) as driver: + driver.delete_all_cookies() + driver.maximize_window() + driver_wait = WebDriverWait(driver, 15) + + print("Navigating to http://www.example.com:8080/admin ...", flush=True) + + driver.get("http://www.example.com:8080/admin") + + ### LOGIN PAGE + + if not driver.current_url.endswith("/login"): + print("Didn't get redirected to login page, exiting ...", flush=True) + exit(1) + + print("Redirected to login page, waiting for login form ...", flush=True) + + safe_get_element(driver, By.TAG_NAME, "form") + + print( + "Form found, trying to access another page without being logged in ...", + flush=True, + ) + + driver.get("http://www.example.com:8080/admin/home") + + print("Waiting for toast ...", flush=True) + + toast = safe_get_element(driver, By.XPATH, "//div[@flash-message='']") + + print("Toast found", flush=True) + + if "Please log in to access this page." not in toast.text: + print("Toast doesn't contain the expected message, exiting ...", flush=True) + exit(1) + + print( + "Toast contains the expected message, filling login form with wrong credentials ...", + flush=True, + ) + + sleep(1) + + safe_get_element(driver, By.TAG_NAME, "form") + + username_input = safe_get_element(driver, By.ID, "username") + password_input = safe_get_element(driver, By.ID, "password") + username_input.send_keys("hackerman") + password_input.send_keys("password") + password_input.send_keys(Keys.RETURN) + + sleep(0.3) + + try: + title = driver_wait.until( + EC.presence_of_element_located( + (By.XPATH, "/html/body/main/div[1]/div/h1") + ) + ) + + if title.text != "Log in": + print("Didn't get redirected to login page, exiting ...", flush=True) + exit(1) + except TimeoutException: + print("Login page didn't load in time, exiting ...", flush=True) + exit(1) + + print( + "Got redirected to login page successfully, filling login form with good credentials ...", + flush=True, + ) + + username_input = safe_get_element(driver, By.ID, "username") + password_input = safe_get_element(driver, By.ID, "password") + username_input.send_keys("admin") + password_input.send_keys("admin") + + access_page( + driver, + driver_wait, + "//button[@value='login']", + "home", + ) + + ### HOME PAGE + + print("Trying instances page ...", flush=True) + + access_page( + driver, + driver_wait, + "/html/body/aside[1]/div[1]/div[2]/ul/li[2]/a", + "instances", + ) + + ### INSTANCES PAGE + + print("Trying to reload BunkerWeb instance ...", flush=True) + + try: + form = WebDriverWait(driver, 2).until( + EC.presence_of_element_located( + (By.XPATH, "//form[starts-with(@id, 'form-instance-')]") + ) + ) + except TimeoutException: + print("No instance form found, exiting ...", flush=True) + exit(1) + + access_page( + driver, + driver_wait, + "//form[starts-with(@id, 'form-instance-')]//button[@value='reload']", + "instances", + False, + ) + + print( + "Instance was reloaded successfully, checking the message ...", flush=True + ) + + assert_alert_message(driver, "has been reloaded") + + print("Trying global config page ...") + + access_page( + driver, + driver_wait, + "/html/body/aside[1]/div[1]/div[2]/ul/li[3]/a", + "global config", + ) + + ### GLOBAL CONFIG PAGE + + print( + "Trying to save the global config without changing anything ...", flush=True + ) + + safe_get_element(driver, By.ID, "form-edit-global-configs") + + access_page( + driver, + driver_wait, + "//form[@id='form-edit-global-configs']//button[@type='submit']", + "global config", + False, + ) + + print("The page reloaded successfully, checking the message ...", flush=True) + + assert_alert_message( + driver, + "The global configuration was not edited because no values were changed.", + ) + + print( + 'Checking if the "DATASTORE_MEMORY_SIZE" input have the overridden value ...', + flush=True, + ) + + input_datastore = safe_get_element(driver, By.ID, "DATASTORE_MEMORY_SIZE") + + if not input_datastore.get_attribute("disabled"): + print( + 'The input "DATASTORE_MEMORY_SIZE" is not disabled, even though it should be, exiting ...', + flush=True, + ) + exit(1) + elif input_datastore.get_attribute("value") != "384m": + print("The value is not the expected one, exiting ...", flush=True) + exit(1) + + print( + "The value is the expected one and the input is disabled, trying to edit the global config with wrong values ...", + flush=True, + ) + + input_worker = safe_get_element(driver, By.ID, "WORKER_RLIMIT_NOFILE") + + input_worker.clear() + input_worker.send_keys("ZZZ") + + assert_button_click( + driver, "//form[@id='form-edit-global-configs']//button[@type='submit']" + ) + + assert_alert_message( + driver, + "The global configuration was not edited because no values were changed.", + ) + + print( + "The form was not submitted, trying to edit the global config with good values ...", + flush=True, + ) + + input_worker.clear() + input_worker.send_keys("4096") + + access_page( + driver, + driver_wait, + "//form[@id='form-edit-global-configs']//button[@type='submit']", + "global config", + False, + ) + + input_worker = safe_get_element(driver, By.ID, "WORKER_RLIMIT_NOFILE") + + if input_worker.get_attribute("value") != "4096": + print("The value was not updated, exiting ...", flush=True) + exit(1) + + print( + "The value was updated successfully, checking the message ...", flush=True + ) + + assert_alert_message(driver, "The global configuration has been edited.") + + print("Trying to navigate through the global config tabs ...", flush=True) + + buttons = safe_get_element( + driver, + By.XPATH, + "//div[@global-config-tabs-desktop='']/button", + multiple=True, + ) + buttons.reverse() + for button in buttons: + assert_button_click(driver, button) + + print("Trying to filter the global config ...", flush=True) + + safe_get_element(driver, By.ID, "settings-filter").send_keys("Datastore") + + if ( + len( + safe_get_element( + driver, + By.XPATH, + "//form[@id='form-edit-global-configs']//div[@setting-container='' and not(contains(@class, 'hidden'))]", + multiple=True, + ) + ) + != 1 + ): + print("The filter didn't work, exiting ...", flush=True) + exit(1) + + print("Trying services page ...") + + access_page( + driver, + driver_wait, + "/html/body/aside[1]/div[1]/div[2]/ul/li[4]/a", + "services", + ) + + ### SERVICES PAGE + + print("Checking the services page ...", flush=True) + + try: + service = safe_get_element( + driver, By.XPATH, "//div[@services-service='']", error=True + ) + except TimeoutException: + print("Services not found, exiting ...", flush=True) + exit(1) + + if service.find_element(By.TAG_NAME, "h5").text.strip() != "www.example.com": + print("The service is not present, exiting ...", flush=True) + exit(1) + + if service.find_element(By.TAG_NAME, "h6").text.strip() != "scheduler": + print( + "The service should have been created by the scheduler, exiting ...", + flush=True, + ) + exit(1) + + print("Service www.example.com is present, trying to delete it ...", flush=True) + + delete_button = None + with suppress(TimeoutException): + delete_button = safe_get_element( + driver, + By.XPATH, + "//button[@services-action='delete' and @services-name='www.example.com']", + error=True, + ) + + if delete_button is not None: + print( + "Delete button has been found, even though it shouldn't be, exiting ...", + flush=True, + ) + exit(1) + + print( + "Delete button is not present, as expected, trying to edit it ...", + flush=True, + ) + + assert_button_click( + driver, service.find_element(By.XPATH, ".//button[@services-action='edit']") + ) + + try: + modal = safe_get_element( + driver, By.XPATH, "//div[@services-modal='']", error=True + ) + except TimeoutException: + print("Modal not found, exiting ...", flush=True) + exit(1) + + if "hidden" in modal.get_attribute("class"): + print( + "Modal is hidden even though it shouldn't be, exiting ...", flush=True + ) + exit(1) + + input_server_name = safe_get_element(driver, By.ID, "SERVER_NAME") + + if input_server_name.get_attribute("value") != "www.example.com": + print("The value is not the expected one, exiting ...", flush=True) + exit(1) + + print( + 'The value for the "SERVER_NAME" input is the expected one, trying to edit the config ...', + flush=True, + ) + + assert_button_click( + driver, + driver.find_element(By.XPATH, "//button[@services-item-handler='gzip']"), + ) + + gzip_select = safe_get_element( + driver, By.XPATH, "//button[@services-setting-select='gzip-comp-level']" + ) + + assert_button_click(driver, gzip_select) + + assert_button_click( + driver, + safe_get_element( + driver, + By.XPATH, + "//button[@services-setting-select-dropdown-btn='gzip-comp-level' and @value='6']", + ), + ) + + access_page( + driver, + driver_wait, + "//button[@services-modal-submit='']", + "services", + False, + ) + + print("The page reloaded successfully, checking the message ...", flush=True) + + assert_alert_message( + driver, "Configuration for www.example.com has been edited." + ) + + print("Checking if the setting has been updated ...", flush=True) + + try: + service = safe_get_element( + driver, By.XPATH, "//div[@services-service='']", error=True + ) + except TimeoutException: + print("Services not found, exiting ...", flush=True) + exit(1) + + assert_button_click( + driver, service.find_element(By.XPATH, ".//button[@services-action='edit']") + ) + + modal = safe_get_element(driver, By.XPATH, "//div[@services-modal='']") + + if "hidden" in modal.get_attribute("class"): + print( + "Modal is hidden even though it shouldn't be, exiting ...", flush=True + ) + exit(1) + + assert_button_click( + driver, + driver.find_element(By.XPATH, "//button[@services-item-handler='gzip']"), + ) + + gzip_true_select = safe_get_element(driver, By.ID, "GZIP_COMP_LEVEL") + + if ( + safe_get_element( + driver, By.XPATH, "//select[@id='GZIP_COMP_LEVEL']/option[@selected='']" + ).get_attribute("value") + != "6" + ): + print("The value is not the expected one, exiting ...", flush=True) + exit(1) + + print( + "The value is the expected one, trying to save without changes ...", + flush=True, + ) + + access_page( + driver, + driver_wait, + "//button[@services-modal-submit='']", + "services", + False, + ) + + print("The page reloaded successfully, checking the message ...", flush=True) + + assert_alert_message(driver, "was not edited because no values were changed.") + + print("Creating a new service ...", flush=True) + + assert_button_click(driver, "//button[@services-action='new']") + + safe_get_element(driver, By.ID, "SERVER_NAME").send_keys("app1.example.com") + + assert_button_click( + driver, + driver.find_element( + By.XPATH, "//button[@services-item-handler='reverseproxy']" + ), + ) + + assert_button_click( + driver, safe_get_element(driver, By.ID, "USE_REVERSE_PROXY") + ) + + assert_button_click(driver, "//button[@services-multiple-add='reverse-proxy']") + + safe_get_element(driver, By.ID, "REVERSE_PROXY_HOST_SCHEMA_0").send_keys( + "http://app1" + ) + safe_get_element(driver, By.ID, "REVERSE_PROXY_URL_SCHEMA_0").send_keys("/") + + access_page( + driver, + driver_wait, + "//button[@services-modal-submit='']", + "services", + False, + ) + + assert_alert_message(driver, "has been generated.") + + try: + services = safe_get_element( + driver, + By.XPATH, + "//div[@services-service='']", + multiple=True, + error=True, + ) + except TimeoutException: + print("Services not found, exiting ...", flush=True) + exit(1) + + if len(services) < 2: + print("The service hasn't been created, exiting ...", flush=True) + exit(1) + + service = services[0] + + if service.find_element(By.TAG_NAME, "h5").text.strip() != "app1.example.com": + print( + 'The service "app1.example.com" is not present, exiting ...', flush=True + ) + exit(1) + + if service.find_element(By.TAG_NAME, "h6").text.strip() != "ui": + print( + "The service should have been created by the ui, exiting ...", + flush=True, + ) + exit(1) + + print("Service app1.example.com is present, trying it ...", flush=True) + + try: + safe_get_element( + driver, + By.XPATH, + "//button[@services-action='edit' and @services-name='www.example.com']//ancestor::div//a", + error=True, + ) + except TimeoutException: + print( + "Delete button hasn't been found, even though it should be, exiting ...", + flush=True, + ) + exit(1) + + ready = False + retries = 0 + while ready is False: + with suppress(RequestException): + status_code = get("http://app1.example.com:8080/").status_code + + if status_code > 500: + print("The service is not working, exiting ...", flush=True) + exit(1) + + ready = status_code < 400 + + if retries > 10: + print("The service took too long to be ready, exiting ...", flush=True) + exit(1) + elif ready is False: + retries += 1 + print( + "Waiting for the service to be ready, retrying in 5s ...", + flush=True, + ) + sleep(5) + + print("The service is working, trying to delete it ...", flush=True) + + try: + delete_button = safe_get_element( + driver, + By.XPATH, + "//button[@services-action='delete' and @services-name='app1.example.com']", + error=True, + ) + except TimeoutException: + print( + "Delete button hasn't been found, even though it should be, exiting ...", + flush=True, + ) + exit(1) + + print( + "Delete button is present, as expected, deleting the service ...", + flush=True, + ) + + assert_button_click(driver, delete_button) + + access_page( + driver, + driver_wait, + "//form[@services-modal-form-delete='']//button[@type='submit']", + "services", + False, + ) + + assert_alert_message(driver, "has been deleted.") + + print( + "Service app1.example.com has been deleted, checking if it's still present ...", + flush=True, + ) + + try: + services = safe_get_element( + driver, + By.XPATH, + "//div[@services-service='']", + multiple=True, + error=True, + ) + except TimeoutException: + print("Services not found, exiting ...", flush=True) + exit(1) + + if len(services) > 1: + print("The service hasn't been deleted, exiting ...", flush=True) + exit(1) + + print( + "The service has been deleted, successfully, trying configs page ...", + flush=True, + ) + + access_page( + driver, + driver_wait, + "/html/body/aside[1]/div[1]/div[2]/ul/li[5]/a", + "configs", + ) + + ### CONFIGS PAGE + + print("Trying to create a new config ...", flush=True) + + assert_button_click( + driver, "//div[@configs-element='server-http' and @_type='folder']" + ) + assert_button_click(driver, "//li[@configs-add-file='']/button") + + safe_get_element( + driver, By.XPATH, "//div[@configs-modal-path='']/input" + ).send_keys("hello") + safe_get_element( + driver, By.XPATH, "//div[@configs-modal-editor='']/textarea" + ).send_keys( + """ + location /hello { + default_type 'text/plain'; + content_by_lua_block { + ngx.say('hello app1') + } + } + """ + ) + + access_page( + driver, driver_wait, "//button[@configs-modal-submit='']", "configs", False + ) + + assert_alert_message(driver, "was successfully created") + + sleep(5) + + driver.execute_script( + f"window.open('http://www.example.com:8080/hello','_blank');" + ) + driver.switch_to.window(driver.window_handles[1]) + driver.switch_to.default_content() + + try: + if ( + safe_get_element(driver, By.XPATH, "//pre", error=True).text.strip() + != "hello app1" + ): + print( + "The config hasn't been created correctly, exiting ...", flush=True + ) + exit(1) + except TimeoutException: + print("The config hasn't been created, exiting ...", flush=True) + exit(1) + + print( + "The config has been created and is working, trying to edit it ...", + flush=True, + ) + + driver.close() + driver.switch_to.window(driver.window_handles[0]) + + assert_button_click( + driver, "//div[@configs-element='server-http' and @_type='folder']" + ) + assert_button_click(driver, "//div[@configs-action-button='hello.conf']") + assert_button_click( + driver, + "//div[@configs-action-dropdown='hello.conf']/button[@value='delete' and @configs-action-dropdown-btn='hello.conf']", + ) + + access_page( + driver, driver_wait, "//button[@configs-modal-submit='']", "configs", False + ) + + assert_alert_message(driver, "was successfully deleted") + + print("The config has been deleted, trying plugins page ...", flush=True) + + access_page( + driver, + driver_wait, + "/html/body/aside[1]/div[1]/div[2]/ul/li[6]/a", + "plugins", + ) + + ### PLUGINS PAGE + + print("Trying to reload the plugins without adding any ...", flush=True) + + access_page( + driver, + driver_wait, + "//div[@plugins-upload='']//button[@type='submit']", + "plugins", + False, + ) + + assert_alert_message(driver, "Please upload new plugins to reload plugins") + + print("Trying to filter the plugins ...", flush=True) + + safe_get_element( + driver, By.XPATH, "//input[@placeholder='key words']" + ).send_keys("Anti") + + plugins = safe_get_element( + driver, By.XPATH, "//div[@plugins-list='']", multiple=True + ) + + if len(plugins) != 1: + print("The filter is not working, exiting ...", flush=True) + exit(1) + + print("The filter is working, trying to add a bad plugin ...", flush=True) + + safe_get_element( + driver, By.XPATH, "//input[@type='file' and @name='file']" + ).send_keys(join(getcwd(), "test.zip")) + + access_page( + driver, + driver_wait, + "//div[@plugins-upload='']//button[@type='submit']", + "plugins", + False, + ) + + assert_alert_message(driver, "is not a valid plugin") + + print( + "The bad plugin has been rejected, trying to add a good plugin ...", + flush=True, + ) + + safe_get_element( + driver, By.XPATH, "//input[@type='file' and @name='file']" + ).send_keys(join(getcwd(), "discord.zip")) + + access_page( + driver, + driver_wait, + "//div[@plugins-upload='']//button[@type='submit']", + "plugins", + False, + ) + + assert_alert_message(driver, "Successfully created plugin") + + external_plugins = safe_get_element( + driver, By.XPATH, "//div[@plugins-external=' external ']", multiple=True + ) + + if len(external_plugins) != 1: + print("The plugin hasn't been added, exiting ...", flush=True) + exit(1) + + print("The plugin has been added, trying delete it ...", flush=True) + + assert_button_click( + driver, + "//button[@plugins-action='delete' and @name='discord']", + ) + + access_page( + driver, + driver_wait, + "//form[@plugins-modal-form-delete='']//button[@type='submit']", + "plugins", + False, + ) + + assert_alert_message(driver, "was successfully deleted") + + print("The plugin has been deleted, trying cache page ...", flush=True) + + access_page( + driver, driver_wait, "/html/body/aside[1]/div[1]/div[2]/ul/li[7]/a", "cache" + ) + + ### CACHE PAGE + + print("Trying to open a cache file ...", flush=True) + + assert_button_click(driver, "//div[@cache-element='asn.mmdb']") + + if ( + safe_get_element( + driver, + By.XPATH, + "//div[@cache-modal-editor='']/div[@class='ace_scroller']//div[@class='ace_line']", + ).text.strip() + != "Download file to view content" + ): + print("The cache file content is not correct, exiting ...", flush=True) + exit(1) + + assert_button_click(driver, "//button[@cache-modal-submit='']") + + print( + "The cache file content is correct, trying to download it ...", flush=True + ) + + assert_button_click(driver, "//div[@cache-action-button='asn.mmdb']") + + assert_button_click( + driver, "//div[@cache-action-dropdown='asn.mmdb']/button[@value='download']" + ) + + sleep(0.3) + + elem = None + with suppress(TimeoutException): + elem = safe_get_element( + driver, + By.XPATH, + "//div[@cache-action-dropdown='asn.mmdb' and contains(@class, 'hidden')]", + error=True, + ) + + if elem is None: + print("The cache file hasn't been downloaded, exiting ...", flush=True) + exit(1) + + print("The cache file has been downloaded, trying logs page ...", flush=True) + + access_page( + driver, driver_wait, "/html/body/aside[1]/div[1]/div[2]/ul/li[8]/a", "logs" + ) + + ### LOGS PAGE + + print("Selecting correct instance ...", flush=True) + + assert_button_click(driver, "//button[@logs-setting-select='instances']") + + instances = safe_get_element( + driver, + By.XPATH, + "//div[@logs-setting-select-dropdown='instances']/button", + multiple=True, + ) + + if len(instances) == 0: + print("No instances found, exiting ...", flush=True) + exit(1) + + assert_button_click(driver, instances[0]) + assert_button_click(driver, safe_get_element(driver, By.ID, "submit-settings")) + + sleep(3) + + logs_list = safe_get_element( + driver, By.XPATH, "//ul[@logs-list='']/li", multiple=True + ) + + if len(logs_list) == 0: + print("No logs found, exiting ...", flush=True) + exit(1) + + print("Logs found, trying auto refresh ...", flush=True) + + assert_button_click(driver, safe_get_element(driver, By.ID, "live-update")) + assert_button_click(driver, safe_get_element(driver, By.ID, "submit-settings")) + + sleep(3) + + if len(logs_list) == len( + safe_get_element( + driver, + By.XPATH, + "//ul[@logs-list='']/li[not(contains(@class, 'hidden'))]", + multiple=True, + ) + ): + print("Auto refresh is not working, exiting ...", flush=True) + exit(1) + + print("Auto refresh is working, deactivating it ...", flush=True) + + assert_button_click( + driver, + "//div[@checkbox-handler='live-update']/*[local-name() = 'svg']", + ) + assert_button_click(driver, safe_get_element(driver, By.ID, "submit-settings")) + + sleep(3) + + logs_list = safe_get_element( + driver, By.XPATH, "//ul[@logs-list='']/li", multiple=True + ) + + print("Trying filters ...", flush=True) + + filter_input = safe_get_element(driver, By.ID, "keyword") + + filter_input.send_keys("gen") + + sleep(3) + + if len(logs_list) == len( + safe_get_element( + driver, + By.XPATH, + "//ul[@logs-list='']/li[not(contains(@class, 'hidden'))]", + multiple=True, + ) + ): + print("The keyword filter is not working, exiting ...", flush=True) + exit(1) + + filter_input.clear() + + print("Keyword filter is working, trying type filter ...", flush=True) + + assert_button_click(driver, "//button[@logs-setting-select='types']") + + assert_button_click( + driver, + "//div[@logs-setting-select-dropdown='types']/button[@value='warn']", + ) + + if len(logs_list) == len( + safe_get_element( + driver, + By.XPATH, + "//ul[@logs-list='']/li[not(contains(@class, 'hidden'))]", + multiple=True, + ) + ): + print("The keyword filter is not working, exiting ...", flush=True) + exit(1) + + assert_button_click(driver, "//button[@logs-setting-select='types']") + + assert_button_click( + driver, + "//div[@logs-setting-select-dropdown='types']/button[@value='all']", + ) + + print("Type filter is working, trying to filter by date ...", flush=True) + + safe_get_element(driver, By.ID, "to-date").send_keys( + (datetime.now() - timedelta(1)).strftime("%m/%d/%Y") + ) + assert_button_click(driver, safe_get_element(driver, By.ID, "submit-settings")) + + sleep(1) + + logs_list = [] + with suppress(TimeoutException): + logs_list = safe_get_element( + driver, By.XPATH, "//ul[@logs-list='']/li", multiple=True, error=True + ) + + if len(logs_list) != 0: + print("The date filter is not working, exiting ...", flush=True) + exit(1) + + print("Date filter is working, trying jobs page ...", flush=True) + + access_page( + driver, driver_wait, "/html/body/aside[1]/div[1]/div[2]/ul/li[9]/a", "jobs" + ) + + ### JOBS PAGE + + print("Trying to filter jobs ...", flush=True) + + jobs_list = safe_get_element( + driver, By.XPATH, "//ul[@jobs-list='']/li", multiple=True + ) + + if len(jobs_list) == 0: + print("No jobs found, exiting ...", flush=True) + exit(1) + + filter_input = safe_get_element(driver, By.ID, "keyword") + + filter_input.send_keys("abcde") + + with suppress(TimeoutException): + if len(jobs_list) == len( + safe_get_element( + driver, + By.XPATH, + "//ul[@jobs-list='']/li[not(contains(@class, 'hidden'))]", + multiple=True, + error=True, + ) + ): + print("The keyword filter is not working, exiting ...", flush=True) + exit(1) + + filter_input.clear() + + print( + "Keyword filter is working, trying to filter by success state ...", + flush=True, + ) + + sleep(0.3) + + assert_button_click(driver, "//button[@jobs-setting-select='success']") + + assert_button_click( + driver, + "//div[@jobs-setting-select-dropdown='success']/button[@value='false']", + ) + + with suppress(TimeoutException): + if ( + len(jobs_list) + == len( + safe_get_element( + driver, + By.XPATH, + "//ul[@jobs-list='']/li[not(contains(@class, 'hidden'))]", + multiple=True, + error=True, + ) + ) + ) and len(jobs_list) != len( + safe_get_element( + driver, + By.XPATH, + "//ul[@jobs-list='']//p[@jobs-success='false']", + multiple=True, + error=True, + ) + ): + print("The success filter is not working, exiting ...", flush=True) + exit(1) + + assert_button_click(driver, "//button[@jobs-setting-select='success']") + + assert_button_click( + driver, + "//div[@jobs-setting-select-dropdown='success']/button[@value='all']", + ) + + print( + "Success filter is working, trying to filter by reload state ...", + flush=True, + ) + + sleep(0.3) + + assert_button_click(driver, "//button[@jobs-setting-select='reload']") + + assert_button_click( + driver, + "//div[@jobs-setting-select-dropdown='reload']/button[@value='true']", + ) + + with suppress(TimeoutException): + if ( + len(jobs_list) + == len( + safe_get_element( + driver, + By.XPATH, + "//ul[@jobs-list='']/li[not(contains(@class, 'hidden'))]", + multiple=True, + error=True, + ) + ) + ) and len(jobs_list) != len( + safe_get_element( + driver, + By.XPATH, + "//ul[@jobs-list='']//p[@jobs-reload='true']", + multiple=True, + error=True, + ) + ): + print("The reload filter is not working, exiting ...", flush=True) + exit(1) + + assert_button_click(driver, "//button[@jobs-setting-select='reload']") + + assert_button_click( + driver, + "//div[@jobs-setting-select-dropdown='reload']/button[@value='all']", + ) + + print("Reload filter is working, trying jobs cache ...", flush=True) + + sleep(0.3) + + resp = get( + "http://www.example.com:8080/admin/jobs/download?job_name=mmdb-country&file_name=country.mmdb" + ) + + if resp.status_code != 200: + print("The cache download is not working, exiting ...", flush=True) + exit(1) + + print("Cache download is working, trying to log out ...", flush=True) + + assert_button_click(driver, "//a[@href='logout']") + + try: + title = driver_wait.until( + EC.presence_of_element_located( + (By.XPATH, "/html/body/main/div[1]/div/h1") + ) + ) + + if title.text != "Log in": + print("Didn't get redirected to login page, exiting ...", flush=True) + exit(1) + except TimeoutException: + print("Login page didn't load in time, exiting ...", flush=True) + exit(1) + + print("Successfully logged out, tests are done", flush=True) +except SystemExit: + exit(1) +except: + print(f"Something went wrong, exiting ...\n{format_exc()}", flush=True) + exit(1) + +exit(0) diff --git a/tests/ui/requirements.txt b/tests/ui/requirements.txt new file mode 100644 index 000000000..46d3454fc --- /dev/null +++ b/tests/ui/requirements.txt @@ -0,0 +1,2 @@ +selenium==4.7.2 +requests==2.28.1 diff --git a/tests/ui/test.zip b/tests/ui/test.zip new file mode 100644 index 000000000..3f6a0e205 Binary files /dev/null and b/tests/ui/test.zip differ