mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Start refactoring UI tests
This commit is contained in:
parent
1ee9846762
commit
b8eaa1e5f5
16 changed files with 1162 additions and 11 deletions
23
.github/workflows/dev.yml
vendored
23
.github/workflows/dev.yml
vendored
|
|
@ -74,13 +74,32 @@ jobs:
|
|||
security-events: write
|
||||
|
||||
# UI tests
|
||||
prepare-tests-ui:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- id: set-matrix
|
||||
run: |
|
||||
tests=$(find ./tests/ui/ -name "*_page.py" -type f -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')
|
||||
echo "tests=$tests" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
tests: ${{ steps.set-matrix.outputs.tests }}
|
||||
tests-ui:
|
||||
needs: [build-containers]
|
||||
needs: [prepare-tests-ui, build-containers]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test: ${{ fromJson(needs.prepare-tests-core.outputs.tests) }}
|
||||
uses: ./.github/workflows/tests-ui.yml
|
||||
with:
|
||||
RELEASE: dev
|
||||
tests-ui-linux:
|
||||
needs: [build-packages]
|
||||
needs: [prepare-tests-ui, build-packages]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test: ${{ fromJson(needs.prepare-tests-core.outputs.tests) }}
|
||||
uses: ./.github/workflows/tests-ui-linux.yml
|
||||
with:
|
||||
RELEASE: dev
|
||||
|
|
|
|||
29
.github/workflows/staging.yml
vendored
29
.github/workflows/staging.yml
vendored
|
|
@ -97,18 +97,38 @@ jobs:
|
|||
echo "tests=$tests" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
tests: ${{ steps.set-matrix.outputs.tests }}
|
||||
prepare-tests-ui:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- id: set-matrix
|
||||
run: |
|
||||
tests=$(find ./tests/ui/ -name "*_page.py" -type f -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')
|
||||
echo "tests=$tests" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
tests: ${{ steps.set-matrix.outputs.tests }}
|
||||
|
||||
# Perform tests
|
||||
tests-ui:
|
||||
needs: [codeql, build-containers]
|
||||
needs: [prepare-tests-ui, build-containers]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test: ${{ fromJson(needs.prepare-tests-core.outputs.tests) }}
|
||||
uses: ./.github/workflows/tests-ui.yml
|
||||
with:
|
||||
RELEASE: testing
|
||||
RELEASE: dev
|
||||
tests-ui-linux:
|
||||
needs: [codeql, build-packages]
|
||||
needs: [prepare-tests-ui, build-packages]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test: ${{ fromJson(needs.prepare-tests-core.outputs.tests) }}
|
||||
uses: ./.github/workflows/tests-ui-linux.yml
|
||||
with:
|
||||
RELEASE: testing
|
||||
RELEASE: dev
|
||||
|
||||
staging-tests:
|
||||
needs: [create-infras]
|
||||
strategy:
|
||||
|
|
@ -130,6 +150,7 @@ jobs:
|
|||
TYPE: ${{ matrix.type }}
|
||||
RUNS_ON: ${{ matrix.runs_on }}
|
||||
secrets: inherit
|
||||
|
||||
tests-core:
|
||||
needs: [build-containers, prepare-tests-core]
|
||||
strategy:
|
||||
|
|
|
|||
5
.github/workflows/tests-ui-linux.yml
vendored
5
.github/workflows/tests-ui-linux.yml
vendored
|
|
@ -3,6 +3,9 @@ name: Core test Linux (REUSABLE)
|
|||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
TEST:
|
||||
required: true
|
||||
type: string
|
||||
RELEASE:
|
||||
required: true
|
||||
type: string
|
||||
|
|
@ -113,6 +116,6 @@ jobs:
|
|||
}' | tee plugin.json
|
||||
zip discord.zip plugin.json
|
||||
rm plugin.json
|
||||
./tests.sh "linux"
|
||||
./tests.sh "linux" ${{ inputs.TEST }}
|
||||
env:
|
||||
MODE: ${{ inputs.RELEASE }}
|
||||
|
|
|
|||
5
.github/workflows/tests-ui.yml
vendored
5
.github/workflows/tests-ui.yml
vendored
|
|
@ -3,6 +3,9 @@ name: Perform tests for UI (REUSABLE)
|
|||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
TEST:
|
||||
required: true
|
||||
type: string
|
||||
RELEASE:
|
||||
required: true
|
||||
type: string
|
||||
|
|
@ -29,6 +32,6 @@ jobs:
|
|||
- name: Run tests
|
||||
run: |
|
||||
cd ./tests/ui
|
||||
./tests.sh "docker"
|
||||
./tests.sh "docker" ${{ inputs.TEST }}
|
||||
env:
|
||||
MODE: ${{ inputs.RELEASE }}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,12 @@ RUN echo '{ \
|
|||
RUN apk del .build-deps && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
COPY main.py .
|
||||
ARG TEST_FILE=main.py
|
||||
|
||||
COPY base.py .
|
||||
COPY wizard.py .
|
||||
COPY utils.py .
|
||||
COPY $TEST_FILE main.py
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
|
|
|||
63
tests/ui/base.py
Normal file
63
tests/ui/base.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from contextlib import suppress
|
||||
from functools import partial
|
||||
from logging import DEBUG, INFO, _nameToLevel, basicConfig, info
|
||||
from os import getenv, listdir, sep
|
||||
from pathlib import Path
|
||||
from time import sleep
|
||||
|
||||
from requests import RequestException, get
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.firefox.options import Options
|
||||
from selenium.webdriver.firefox.service import Service
|
||||
|
||||
basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
datefmt="[%Y-%m-%d %H:%M:%S]",
|
||||
level=DEBUG if getenv("ACTIONS_STEP_DEBUG", False) else _nameToLevel.get(getenv("LOG_LEVEL", "INFO").upper(), INFO),
|
||||
)
|
||||
|
||||
os_release_path = Path(sep, "etc", "os-release")
|
||||
DEFAULT_SERVER = "192.168.0.2" if os_release_path.is_file() and "Alpine" in os_release_path.read_text(encoding="utf-8") else "127.0.0.1"
|
||||
|
||||
TEST_TYPE = getenv("TEST_TYPE", "docker")
|
||||
local_geckodriver = "geckodriver" in listdir(Path.cwd())
|
||||
|
||||
FIREFOX_OPTIONS = Options()
|
||||
if not local_geckodriver:
|
||||
FIREFOX_OPTIONS.add_argument("--headless")
|
||||
FIREFOX_OPTIONS.log.level = "trace" # type: ignore
|
||||
|
||||
|
||||
ready = False
|
||||
retries = 0
|
||||
while not ready:
|
||||
with suppress(RequestException):
|
||||
status_code = get(f"http://{DEFAULT_SERVER}/setup").status_code
|
||||
|
||||
if status_code > 500 and status_code != 502:
|
||||
print("An error occurred with the server, exiting ...", flush=True)
|
||||
exit(1)
|
||||
|
||||
ready = status_code < 400
|
||||
|
||||
if retries > 20:
|
||||
print("UI took too long to be ready, exiting ...", flush=True)
|
||||
exit(1)
|
||||
elif not ready:
|
||||
retries += 1
|
||||
print("Waiting for UI to be ready, retrying in 5s ...", flush=True)
|
||||
sleep(5)
|
||||
|
||||
driver_func = partial(webdriver.Firefox, service=Service(log_output="./geckodriver.log"), options=FIREFOX_OPTIONS)
|
||||
if TEST_TYPE == "dev":
|
||||
driver_func = partial(
|
||||
webdriver.Firefox,
|
||||
service=Service(executable_path="./geckodriver" if local_geckodriver else "/usr/local/bin/geckodriver", log_output="./geckodriver.log"),
|
||||
options=FIREFOX_OPTIONS,
|
||||
)
|
||||
|
||||
DRIVER = driver_func()
|
||||
|
||||
info("UI is ready, starting tests ...")
|
||||
|
||||
__all__ = ("DEFAULT_SERVER", "TEST_TYPE", "DRIVER")
|
||||
208
tests/ui/configs_page.py
Normal file
208
tests/ui/configs_page.py
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
from contextlib import suppress
|
||||
from logging import info as log_info, exception as log_exception, error as log_error
|
||||
from time import sleep
|
||||
from requests import get
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
|
||||
from wizard import DRIVER
|
||||
from base import TEST_TYPE
|
||||
from utils import access_page, assert_alert_message, assert_button_click, safe_get_element, wait_for_service
|
||||
|
||||
exit_code = 0
|
||||
|
||||
try:
|
||||
log_info("Navigating to the services page to create a new service ...")
|
||||
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[4]/a", "services")
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-services-action='new']")
|
||||
|
||||
server_name_input = safe_get_element(DRIVER, By.ID, "SERVER_NAME")
|
||||
assert isinstance(server_name_input, WebElement), "Input is not a WebElement"
|
||||
|
||||
server_name_input.clear()
|
||||
server_name_input.send_keys("app1.example.com")
|
||||
|
||||
access_page(DRIVER, "//button[@data-services-modal-submit='']", "services", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service("app1.example.com")
|
||||
|
||||
log_info("Navigating to the configs page ...")
|
||||
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[5]/a", "configs")
|
||||
|
||||
log_info("Trying to create a new config ...")
|
||||
|
||||
assert_button_click(DRIVER, "//div[@data-configs-element='server-http' and @data-_type='folder']")
|
||||
assert_button_click(DRIVER, "//li[@data-configs-add-file='']/button")
|
||||
|
||||
configs_modal_path_input = safe_get_element(DRIVER, By.XPATH, "//div[@data-configs-modal-path='']/input")
|
||||
assert isinstance(configs_modal_path_input, WebElement), "The path input is not an instance of WebElement"
|
||||
configs_modal_path_input.send_keys("hello")
|
||||
|
||||
configs_modal_editor_elem = safe_get_element(DRIVER, By.XPATH, "//div[@data-configs-modal-editor='']/textarea")
|
||||
assert isinstance(configs_modal_editor_elem, WebElement), "The editor element is not an instance of WebElement"
|
||||
configs_modal_editor_elem.send_keys(
|
||||
"""
|
||||
location /hello {
|
||||
default_type 'text/plain';
|
||||
content_by_lua_block {
|
||||
ngx.say('hello app1')
|
||||
}
|
||||
}"""
|
||||
)
|
||||
|
||||
access_page(DRIVER, "//button[@data-configs-modal-submit='']", "configs", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
assert_alert_message(DRIVER, "was successfully created")
|
||||
|
||||
sleep(10)
|
||||
|
||||
DRIVER.execute_script("window.open('http://www.example.com/hello','_blank');")
|
||||
DRIVER.switch_to.window(DRIVER.window_handles[1])
|
||||
DRIVER.switch_to.default_content()
|
||||
|
||||
try:
|
||||
pre_elem = safe_get_element(DRIVER, By.XPATH, "//pre", error=True)
|
||||
assert isinstance(pre_elem, WebElement), "The pre element is not an instance of WebElement"
|
||||
if pre_elem.text.strip() != "hello app1":
|
||||
log_error("The config hasn't been created correctly, exiting ...")
|
||||
exit(1)
|
||||
except TimeoutException:
|
||||
log_info("The config hasn't been created, exiting ...")
|
||||
exit(1)
|
||||
|
||||
DRIVER.execute_script("window.open('http://app1.example.com/hello','_blank');")
|
||||
DRIVER.switch_to.window(DRIVER.window_handles[1])
|
||||
DRIVER.switch_to.default_content()
|
||||
|
||||
try:
|
||||
pre_elem = safe_get_element(DRIVER, By.XPATH, "//pre", error=True)
|
||||
assert isinstance(pre_elem, WebElement), "The pre element is not an instance of WebElement"
|
||||
if pre_elem.text.strip() != "hello app1":
|
||||
log_error("The config hasn't been created correctly, exiting ...")
|
||||
exit(1)
|
||||
except TimeoutException:
|
||||
log_info("The config hasn't been created, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The config has been created and is working with both services, trying to delete it ...")
|
||||
|
||||
for _ in range(2):
|
||||
DRIVER.close()
|
||||
DRIVER.switch_to.window(DRIVER.window_handles[len(DRIVER.window_handles) - 1])
|
||||
|
||||
assert_button_click(DRIVER, "//div[@data-configs-element='server-http' and @data-_type='folder']")
|
||||
assert_button_click(DRIVER, "//div[@data-configs-action-button='hello.conf']")
|
||||
assert_button_click(DRIVER, "//div[@data-configs-action-dropdown='hello.conf']/button[@value='delete' and @data-configs-action-dropdown-btn='hello.conf']")
|
||||
|
||||
access_page(DRIVER, "//button[@data-configs-modal-submit='']", "configs", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
assert_alert_message(DRIVER, "was successfully deleted")
|
||||
|
||||
sleep(10)
|
||||
|
||||
resp = get("http://www.example.com/hello")
|
||||
|
||||
if resp.status_code != 404:
|
||||
log_error("The config hasn't been deleted correctly, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The config has been deleted, trying the same for a specific service ...")
|
||||
|
||||
assert_button_click(DRIVER, "//div[@data-configs-element='server-http' and @data-_type='folder']")
|
||||
assert_button_click(DRIVER, "//div[@data-path='/etc/bunkerweb/configs/server-http/app1.example.com' and @data-_type='folder']")
|
||||
assert_button_click(DRIVER, "//li[@data-configs-add-file='']/button")
|
||||
|
||||
configs_modal_path_input = safe_get_element(DRIVER, By.XPATH, "//div[@data-configs-modal-path='']/input")
|
||||
assert isinstance(configs_modal_path_input, WebElement), "The path input is not an instance of WebElement"
|
||||
configs_modal_path_input.send_keys("hello")
|
||||
|
||||
configs_modal_editor_elem = safe_get_element(DRIVER, By.XPATH, "//div[@data-configs-modal-editor='']/textarea")
|
||||
assert isinstance(configs_modal_editor_elem, WebElement), "The editor element is not an instance of WebElement"
|
||||
configs_modal_editor_elem.send_keys(
|
||||
"""
|
||||
location /hello {
|
||||
default_type 'text/plain';
|
||||
content_by_lua_block {
|
||||
ngx.say('hello app1')
|
||||
}
|
||||
}"""
|
||||
)
|
||||
|
||||
access_page(DRIVER, "//button[@data-configs-modal-submit='']", "configs", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
assert_alert_message(DRIVER, "was successfully created")
|
||||
|
||||
sleep(10)
|
||||
|
||||
DRIVER.execute_script("window.open('http://app1.example.com/hello','_blank');")
|
||||
DRIVER.switch_to.window(DRIVER.window_handles[1])
|
||||
DRIVER.switch_to.default_content()
|
||||
|
||||
try:
|
||||
pre_elem = safe_get_element(DRIVER, By.XPATH, "//pre", error=True)
|
||||
assert isinstance(pre_elem, WebElement), "The pre element is not an instance of WebElement"
|
||||
if pre_elem.text.strip() != "hello app1":
|
||||
log_error("The config hasn't been created correctly, exiting ...")
|
||||
exit(1)
|
||||
except TimeoutException:
|
||||
log_info("The config hasn't been created, exiting ...")
|
||||
exit(1)
|
||||
|
||||
DRIVER.close()
|
||||
DRIVER.switch_to.window(DRIVER.window_handles[0])
|
||||
|
||||
resp = get("http://www.example.com/hello")
|
||||
|
||||
if resp.status_code != 404:
|
||||
log_error("The config didn't get created only for the app1.example.com service, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The config has been created only for the app1.example.com service, trying to delete the service to see if the config gets deleted ...")
|
||||
|
||||
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[4]/a", "services")
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-services-action='delete' and @data-services-name='app1.example.com']")
|
||||
|
||||
access_page(DRIVER, "//form[@data-services-modal-form-delete='']//button[@type='submit']", "services", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
log_info("The service has been deleted, checking if the config has been deleted as well ...")
|
||||
|
||||
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[5]/a", "configs")
|
||||
|
||||
assert_button_click(DRIVER, "//div[@data-configs-element='server-http' and @data-_type='folder']")
|
||||
|
||||
with suppress(TimeoutException):
|
||||
safe_get_element(DRIVER, By.XPATH, "//div[@data-configs-element='app2.example.com' and @data-_type='folder']", error=True)
|
||||
log_error("The config hasn't been deleted, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The config has been deleted")
|
||||
|
||||
log_info("✅ Configs page tests finished successfully")
|
||||
except SystemExit as e:
|
||||
exit_code = e.code
|
||||
except KeyboardInterrupt:
|
||||
exit_code = 1
|
||||
except:
|
||||
log_exception("Something went wrong, exiting ...")
|
||||
DRIVER.save_screenshot("error.png")
|
||||
exit_code = 1
|
||||
finally:
|
||||
DRIVER.quit()
|
||||
exit(exit_code)
|
||||
|
|
@ -5,6 +5,8 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- TEST_FILE=${TEST_FILE}
|
||||
environment:
|
||||
- PYTHONUNBUFFERED=1
|
||||
extra_hosts:
|
||||
|
|
|
|||
112
tests/ui/global_config_page.py
Normal file
112
tests/ui/global_config_page.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
from logging import info as log_info, exception as log_exception, error as log_error, warning as log_warning
|
||||
from random import shuffle
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
from wizard import DRIVER
|
||||
from base import TEST_TYPE
|
||||
from utils import access_page, assert_alert_message, assert_button_click, safe_get_element, wait_for_service
|
||||
|
||||
exit_code = 0
|
||||
|
||||
try:
|
||||
log_info("Navigating to the global config page ...")
|
||||
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[3]/a", "global config")
|
||||
|
||||
no_errors = True
|
||||
retries = 0
|
||||
while no_errors:
|
||||
try:
|
||||
log_info("Trying to save the global config without changing anything ...")
|
||||
access_page(DRIVER, "//form[@id='form-edit-global-configs']//button[@type='submit']", "global config", False)
|
||||
|
||||
log_info("The page reloaded successfully, checking the message ...")
|
||||
assert_alert_message(DRIVER, "The global configuration was not edited because no values were changed.")
|
||||
|
||||
no_errors = False
|
||||
except:
|
||||
if retries >= 3:
|
||||
exit(1)
|
||||
retries += 1
|
||||
log_warning("message list doesn't contain the expected message or is empty, retrying...")
|
||||
|
||||
log_info('Checking if the "DATASTORE_MEMORY_SIZE" input have the overridden value ...')
|
||||
|
||||
input_datastore = safe_get_element(DRIVER, By.ID, "DATASTORE_MEMORY_SIZE")
|
||||
assert isinstance(input_datastore, WebElement), "Input is not a WebElement"
|
||||
|
||||
if not input_datastore.get_attribute("disabled"):
|
||||
log_error('The input "DATASTORE_MEMORY_SIZE" is not disabled, even though it should be, exiting ...')
|
||||
exit(1)
|
||||
elif input_datastore.get_attribute("value") != "384m":
|
||||
log_error(f"The value is not the expected one ({input_datastore.get_attribute('value')} instead of 384m), exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The value is the expected one and the input is disabled, trying to edit the global config with wrong values ...")
|
||||
|
||||
input_worker = safe_get_element(DRIVER, By.ID, "WORKER_RLIMIT_NOFILE")
|
||||
assert isinstance(input_worker, WebElement), "Input is not a WebElement"
|
||||
|
||||
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.")
|
||||
|
||||
log_info("The form was not submitted, trying to edit the global config with good values ...")
|
||||
|
||||
input_worker.clear()
|
||||
input_worker.send_keys("4096")
|
||||
|
||||
access_page(DRIVER, "//form[@id='form-edit-global-configs']//button[@type='submit']", "global config", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
input_worker = safe_get_element(DRIVER, By.ID, "WORKER_RLIMIT_NOFILE")
|
||||
assert isinstance(input_worker, WebElement), "Input is not a WebElement"
|
||||
|
||||
if input_worker.get_attribute("value") != "4096":
|
||||
log_error(f"The value was not updated ({input_worker.get_attribute('value')} instead of 4096), exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The value was updated successfully, trying to navigate through the global config tabs ...")
|
||||
|
||||
buttons = safe_get_element(DRIVER, By.XPATH, "//div[@data-global-config-tabs-desktop='']/button", multiple=True)
|
||||
assert isinstance(buttons, list), "Buttons is not a list of WebElements"
|
||||
|
||||
shuffle(buttons)
|
||||
for button in buttons:
|
||||
assert_button_click(DRIVER, button)
|
||||
|
||||
log_info("Trying to filter the global config ...")
|
||||
|
||||
setting_filter_elem = safe_get_element(DRIVER, By.ID, "settings-filter")
|
||||
assert isinstance(setting_filter_elem, WebElement), "Setting filter input is not a WebElement"
|
||||
setting_filter_elem.send_keys("Datastore")
|
||||
|
||||
settings = safe_get_element(
|
||||
DRIVER,
|
||||
By.XPATH,
|
||||
"//form[@id='form-edit-global-configs']//div[@data-setting-container='' and not(contains(@class, 'hidden'))]",
|
||||
multiple=True,
|
||||
)
|
||||
assert isinstance(settings, list), "Hidden settings is not a list of WebElements"
|
||||
|
||||
if len(settings) != 1:
|
||||
log_error(f"The filter didn't work (found {len(settings)} settings instead of 1), exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("✅ Global config page tests finished successfully")
|
||||
except SystemExit as e:
|
||||
exit_code = e.code
|
||||
except KeyboardInterrupt:
|
||||
exit_code = 1
|
||||
except:
|
||||
log_exception("Something went wrong, exiting ...")
|
||||
DRIVER.save_screenshot("error.png")
|
||||
exit_code = 1
|
||||
finally:
|
||||
DRIVER.quit()
|
||||
exit(exit_code)
|
||||
4
tests/ui/home_page.py
Normal file
4
tests/ui/home_page.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from wizard import UI_URL
|
||||
|
||||
print(f"done, UI_URL: {UI_URL}", flush=True)
|
||||
# TODO
|
||||
54
tests/ui/instances_page.py
Normal file
54
tests/ui/instances_page.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
from logging import info as log_info, exception as log_exception, warning as log_warning
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
|
||||
from wizard import DRIVER
|
||||
from base import TEST_TYPE
|
||||
from utils import access_page, assert_alert_message, safe_get_element, wait_for_service
|
||||
|
||||
exit_code = 0
|
||||
|
||||
try:
|
||||
log_info("Navigating to the instances page ...")
|
||||
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[2]/a", "instances")
|
||||
|
||||
no_errors = True
|
||||
retries = 0
|
||||
action = "reload" if TEST_TYPE == "docker" else "restart"
|
||||
while no_errors:
|
||||
log_info(f"Trying to {action} BunkerWeb instance ...")
|
||||
|
||||
try:
|
||||
form = safe_get_element(DRIVER, By.XPATH, "//form[starts-with(@id, 'form-instance-')]")
|
||||
except TimeoutException:
|
||||
log_exception("No instance form found, exiting ...")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
access_page(DRIVER, f"//form[starts-with(@id, 'form-instance-')]//button[@value='{action}']", "instances", False)
|
||||
|
||||
log_info(f"Instance was {action}ed successfully, checking the message ...")
|
||||
assert_alert_message(DRIVER, f"has been {action}ed")
|
||||
no_errors = False
|
||||
except:
|
||||
if retries >= 3:
|
||||
exit(1)
|
||||
retries += 1
|
||||
log_warning("Message list doesn't contain the expected message or is empty, retrying...")
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
log_info("✅ Instances page tests finished successfully")
|
||||
except SystemExit as e:
|
||||
exit_code = e.code
|
||||
except KeyboardInterrupt:
|
||||
exit_code = 1
|
||||
except:
|
||||
log_exception("Something went wrong, exiting ...")
|
||||
DRIVER.save_screenshot("error.png")
|
||||
exit_code = 1
|
||||
finally:
|
||||
DRIVER.quit()
|
||||
exit(exit_code)
|
||||
95
tests/ui/plugins_page.py
Normal file
95
tests/ui/plugins_page.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
from contextlib import suppress
|
||||
from logging import info as log_info, exception as log_exception, error as log_error
|
||||
from pathlib import Path
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
|
||||
from wizard import DRIVER
|
||||
from base import TEST_TYPE
|
||||
from utils import access_page, assert_button_click, safe_get_element, wait_for_service
|
||||
|
||||
exit_code = 0
|
||||
|
||||
try:
|
||||
log_info("Navigating to the plugins page to create a new service ...")
|
||||
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[6]/a", "plugins")
|
||||
|
||||
log_info("Trying to reload the plugins without adding any ...")
|
||||
|
||||
reload_button = safe_get_element(DRIVER, By.XPATH, "//div[@data-plugins-upload='']//button[@type='submit']")
|
||||
assert isinstance(reload_button, WebElement), "Reload button is not a WebElement"
|
||||
if reload_button.get_attribute("disabled") is None:
|
||||
log_error("The reload button is not disabled, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Trying to filter the plugins ...")
|
||||
|
||||
key_word_filter_input = safe_get_element(DRIVER, By.XPATH, "//input[@placeholder='key words']")
|
||||
assert isinstance(key_word_filter_input, WebElement), "Key word filter input is not a WebElement"
|
||||
key_word_filter_input.send_keys("Anti")
|
||||
|
||||
plugins = safe_get_element(DRIVER, By.XPATH, "//div[@data-plugins-list='']", multiple=True)
|
||||
assert isinstance(plugins, list), "Plugins list is not a list"
|
||||
|
||||
if len(plugins) != 1:
|
||||
log_error("The filter is not working, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The filter is working, trying to add a bad plugin ...")
|
||||
|
||||
file_input = safe_get_element(DRIVER, By.XPATH, "//input[@type='file' and @name='file']")
|
||||
assert isinstance(file_input, WebElement), "File input is not a WebElement"
|
||||
file_input.send_keys(Path.cwd().joinpath("test.zip").as_posix())
|
||||
|
||||
access_page(DRIVER, "//div[@data-plugins-upload='']//button[@type='submit']", "plugins", False)
|
||||
|
||||
log_info("The bad plugin has been rejected, trying to add a good plugin ...")
|
||||
|
||||
file_input = safe_get_element(DRIVER, By.XPATH, "//input[@type='file' and @name='file']")
|
||||
assert isinstance(file_input, WebElement), "File input is not a WebElement"
|
||||
file_input.send_keys(Path.cwd().joinpath("discord.zip").as_posix())
|
||||
|
||||
access_page(DRIVER, "//div[@data-plugins-upload='']//button[@type='submit']", "plugins", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
external_plugins = safe_get_element(DRIVER, By.XPATH, "//div[@data-plugins-external=' external ']", multiple=True)
|
||||
assert isinstance(external_plugins, list), "External plugins list is not a list"
|
||||
|
||||
if len(external_plugins) != 1:
|
||||
log_error("The plugin hasn't been added, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The plugin has been added, trying delete it ...")
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-plugins-action='delete' and @name='discord']")
|
||||
|
||||
access_page(DRIVER, "//form[@data-plugins-modal-form-delete='']//button[@type='submit']", "plugins", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
with suppress(TimeoutException):
|
||||
if safe_get_element(DRIVER, By.XPATH, "//button[@data-plugins-action='delete' and @name='discord']", error=True):
|
||||
log_error("The plugin hasn't been deleted, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The plugin has been deleted")
|
||||
|
||||
# TODO add test for plugin pages
|
||||
|
||||
log_info("✅ Plugins page tests finished successfully")
|
||||
except SystemExit as e:
|
||||
exit_code = e.code
|
||||
except KeyboardInterrupt:
|
||||
exit_code = 1
|
||||
except:
|
||||
log_exception("Something went wrong, exiting ...")
|
||||
DRIVER.save_screenshot("error.png")
|
||||
exit_code = 1
|
||||
finally:
|
||||
DRIVER.quit()
|
||||
exit(exit_code)
|
||||
282
tests/ui/services_page.py
Normal file
282
tests/ui/services_page.py
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
from contextlib import suppress
|
||||
from logging import info as log_info, exception as log_exception, error as log_error
|
||||
from time import sleep
|
||||
from requests import RequestException, get
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
|
||||
from wizard import DRIVER
|
||||
from base import TEST_TYPE
|
||||
from utils import access_page, assert_alert_message, assert_button_click, safe_get_element, wait_for_service
|
||||
|
||||
exit_code = 0
|
||||
|
||||
try:
|
||||
log_info("Navigating to the services page ...")
|
||||
access_page(DRIVER, "/html/body/aside[1]/div[1]/div[3]/ul/li[4]/a", "services")
|
||||
|
||||
service_name_elem = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service='www.example.com']//h5")
|
||||
assert isinstance(service_name_elem, WebElement), "Service name element is not a WebElement"
|
||||
if service_name_elem.text.strip() != "www.example.com":
|
||||
log_error("The service is not present, exiting ...")
|
||||
exit(1)
|
||||
|
||||
service_method_elem = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service='www.example.com']//h6")
|
||||
assert isinstance(service_method_elem, WebElement), "Service method element is not a WebElement"
|
||||
if service_method_elem.text.strip() != "ui":
|
||||
log_error("The service should have been created by the ui, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Service www.example.com is present, trying to edit it ...")
|
||||
|
||||
assert_button_click(DRIVER, "//div[@data-services-service='www.example.com']//button[@data-services-action='edit']")
|
||||
|
||||
modal = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-modal='']")
|
||||
assert isinstance(modal, WebElement), "Modal is not a WebElement"
|
||||
if "hidden" in (modal.get_attribute("class") or ""):
|
||||
log_error("Modal is hidden even though it shouldn't be, exiting ...")
|
||||
exit(1)
|
||||
|
||||
input_server_name = safe_get_element(DRIVER, By.ID, "SERVER_NAME")
|
||||
assert isinstance(input_server_name, WebElement), "Input is not a WebElement"
|
||||
if input_server_name.get_attribute("value") != "www.example.com":
|
||||
log_error("The value is not the expected one, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info('The value for the "SERVER_NAME" input is the expected one, trying to edit the config ...')
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-tab-handler='gzip']")
|
||||
gzip_select = safe_get_element(DRIVER, By.XPATH, "//button[@data-setting-select='gzip-comp-level']")
|
||||
assert isinstance(gzip_select, WebElement), "Gzip select is not a WebElement"
|
||||
assert_button_click(DRIVER, gzip_select)
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-setting-select-dropdown-btn='gzip-comp-level' and @value='6']")
|
||||
|
||||
access_page(DRIVER, "//button[@data-services-modal-submit='']", "services", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
log_info("The page reloaded successfully, checking if the setting has been updated ...")
|
||||
|
||||
assert_button_click(DRIVER, "//div[@data-services-service='www.example.com']//button[@data-services-action='edit']")
|
||||
|
||||
modal = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-modal='']")
|
||||
assert isinstance(modal, WebElement), "Modal is not a WebElement"
|
||||
if "hidden" in (modal.get_attribute("class") or ""):
|
||||
log_error("Modal is hidden even though it shouldn't be, exiting ...")
|
||||
exit(1)
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-tab-handler='gzip']")
|
||||
|
||||
gzip_comp_level_selected_elem = safe_get_element(DRIVER, By.XPATH, "//select[@id='GZIP_COMP_LEVEL']/option[@selected='']")
|
||||
assert isinstance(gzip_comp_level_selected_elem, WebElement), "Gzip comp level selected element is not a WebElement"
|
||||
if gzip_comp_level_selected_elem.get_attribute("value") != "6":
|
||||
log_error("The value is not the expected one, exiting ...")
|
||||
exit(1)
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-services-modal-close='']/*[local-name() = 'svg']")
|
||||
|
||||
log_info("Creating a new service ...")
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-services-action='new']")
|
||||
|
||||
server_name_input = safe_get_element(DRIVER, By.ID, "SERVER_NAME")
|
||||
assert isinstance(server_name_input, WebElement), "Input is not a WebElement"
|
||||
|
||||
server_name_input.clear()
|
||||
server_name_input.send_keys("app1.example.com")
|
||||
|
||||
if TEST_TYPE == "docker":
|
||||
assert_button_click(DRIVER, "//button[@data-tab-handler='reverseproxy']")
|
||||
|
||||
use_reverse_proxy_checkbox = safe_get_element(DRIVER, By.ID, "USE_REVERSE_PROXY")
|
||||
assert isinstance(use_reverse_proxy_checkbox, WebElement), "Use reverse proxy checkbox is not a WebElement"
|
||||
assert_button_click(DRIVER, use_reverse_proxy_checkbox)
|
||||
|
||||
reverse_proxy_host_input = safe_get_element(DRIVER, By.ID, "REVERSE_PROXY_HOST")
|
||||
assert isinstance(reverse_proxy_host_input, WebElement), "Reverse proxy host input is not a WebElement"
|
||||
reverse_proxy_host_input.send_keys("http://app1:8080")
|
||||
|
||||
reverse_proxy_url_input = safe_get_element(DRIVER, By.ID, "REVERSE_PROXY_URL")
|
||||
assert isinstance(reverse_proxy_url_input, WebElement), "Reverse proxy url input is not a WebElement"
|
||||
reverse_proxy_url_input.send_keys("/")
|
||||
|
||||
access_page(DRIVER, "//button[@data-services-modal-submit='']", "services", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service("app1.example.com")
|
||||
|
||||
try:
|
||||
services = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service]", multiple=True, error=True)
|
||||
assert isinstance(services, list), "Services is not a list"
|
||||
except TimeoutException:
|
||||
log_exception("Services not found, exiting ...")
|
||||
exit(1)
|
||||
|
||||
if len(services) < 2:
|
||||
log_error("The service hasn't been created, exiting ...")
|
||||
exit(1)
|
||||
|
||||
server_name_elem = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service='app1.example.com']//h5")
|
||||
assert isinstance(server_name_elem, WebElement), "Server name element is not a WebElement"
|
||||
if server_name_elem.text.strip() != "app1.example.com":
|
||||
log_error('The service "app1.example.com" is not present, exiting ...')
|
||||
exit(1)
|
||||
|
||||
service_method_elem = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service='app1.example.com']//h6")
|
||||
assert isinstance(service_method_elem, WebElement), "Service method element is not a WebElement"
|
||||
if service_method_elem.text.strip() != "ui":
|
||||
log_error("The service should have been created by the ui, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Service app1.example.com is present, trying it ...")
|
||||
|
||||
try:
|
||||
safe_get_element(DRIVER, By.XPATH, "//button[@data-services-action='edit' and @data-services-name='app1.example.com']//ancestor::div//a", error=True)
|
||||
except TimeoutException:
|
||||
log_exception("Delete button hasn't been found, even though it should be, exiting ...")
|
||||
exit(1)
|
||||
|
||||
wait_for_service("app1.example.com")
|
||||
|
||||
log_info("The service is working, trying to clone it ...")
|
||||
|
||||
try:
|
||||
clone_button = safe_get_element(DRIVER, By.XPATH, "//button[@data-services-action='clone' and @data-services-name='app1.example.com']", error=True)
|
||||
assert isinstance(clone_button, WebElement), "Clone button is not a WebElement"
|
||||
except TimeoutException:
|
||||
log_exception("Clone button hasn't been found, even though it should be, exiting ...")
|
||||
exit(1)
|
||||
|
||||
assert_button_click(DRIVER, clone_button)
|
||||
|
||||
server_name_input = safe_get_element(DRIVER, By.ID, "SERVER_NAME")
|
||||
assert isinstance(server_name_input, WebElement), "Input is not a WebElement"
|
||||
|
||||
if server_name_input.get_attribute("value"):
|
||||
log_error("The cloned service input is not empty, exiting ...")
|
||||
exit(1)
|
||||
|
||||
server_name_input.clear()
|
||||
server_name_input.send_keys("app2.example.com")
|
||||
|
||||
access_page(DRIVER, "//button[@data-services-modal-submit='']", "services", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service("app2.example.com")
|
||||
|
||||
try:
|
||||
services = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service]", multiple=True, error=True)
|
||||
assert isinstance(services, list), "Services is not a list"
|
||||
except TimeoutException:
|
||||
log_exception("Services not found, exiting ...")
|
||||
exit(1)
|
||||
|
||||
if len(services) < 3:
|
||||
log_error(f"The service hasn't been created ({len(services)} services found), exiting ...")
|
||||
exit(1)
|
||||
|
||||
server_name_elem = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service='app2.example.com']//h5")
|
||||
assert isinstance(server_name_elem, WebElement), "Server name element is not a WebElement"
|
||||
if server_name_elem.text.strip() != "app2.example.com":
|
||||
log_error('The service "app2.example.com" is not present, exiting ...')
|
||||
exit(1)
|
||||
|
||||
service_method_elem = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service='app2.example.com']//h6")
|
||||
assert isinstance(service_method_elem, WebElement), "Service method element is not a WebElement"
|
||||
if service_method_elem.text.strip() != "ui":
|
||||
log_error("The service should have been created by the ui, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Service app2.example.com is present, trying it ...")
|
||||
|
||||
try:
|
||||
safe_get_element(DRIVER, By.XPATH, "//button[@data-services-action='edit' and @data-services-name='app2.example.com']//ancestor::div//a", error=True)
|
||||
except TimeoutException:
|
||||
log_error("Delete button hasn't been found, even though it should be, exiting ...")
|
||||
exit(1)
|
||||
|
||||
wait_for_service("app2.example.com")
|
||||
|
||||
log_info("The service is working, trying to set it as draft ...")
|
||||
|
||||
assert_button_click(DRIVER, "//div[@data-services-service='app2.example.com']//button[@data-services-action='edit']")
|
||||
|
||||
assert_button_click(DRIVER, "//button[@data-toggle-draft-btn='']")
|
||||
|
||||
access_page(DRIVER, "//button[@data-services-modal-submit='']", "services", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
try:
|
||||
services = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service]", multiple=True, error=True)
|
||||
assert isinstance(services, list), "Services is not a list"
|
||||
except TimeoutException:
|
||||
log_exception("Services not found, exiting ...")
|
||||
exit(1)
|
||||
|
||||
if len(services) < 3:
|
||||
log_error(f"The service has been deleted ({len(services)} services found), exiting ...")
|
||||
exit(1)
|
||||
|
||||
sleep(30)
|
||||
|
||||
log_info("Service app2.example.com has been set as draft, making sure it's not working anymore ...")
|
||||
|
||||
for _ in range(5):
|
||||
with suppress(RequestException):
|
||||
if get("http://app2.example.com").status_code < 400:
|
||||
log_error("The service is still working, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("The service is not working, as expected, trying to delete it ...")
|
||||
|
||||
try:
|
||||
delete_button = safe_get_element(DRIVER, By.XPATH, "//button[@data-services-action='delete' and @data-services-name='app2.example.com']", error=True)
|
||||
assert isinstance(delete_button, WebElement), "Delete button is not a WebElement"
|
||||
except TimeoutException:
|
||||
log_exception("Delete button hasn't been found, even though it should be, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Delete button is present, as expected, deleting the service ...")
|
||||
|
||||
assert_button_click(DRIVER, delete_button)
|
||||
|
||||
access_page(DRIVER, "//form[@data-services-modal-form-delete='']//button[@type='submit']", "services", False)
|
||||
|
||||
if TEST_TYPE == "linux":
|
||||
wait_for_service()
|
||||
|
||||
assert_alert_message(DRIVER, "has been deleted.")
|
||||
|
||||
log_info("Service app2.example.com has been deleted, checking if it's still present ...")
|
||||
|
||||
try:
|
||||
services = safe_get_element(DRIVER, By.XPATH, "//div[@data-services-service='']", multiple=True, error=True)
|
||||
assert isinstance(services, list), "Services is not a list"
|
||||
except TimeoutException:
|
||||
log_exception("Services not found, exiting ...")
|
||||
exit(1)
|
||||
|
||||
if len(services) > 2:
|
||||
log_error(f"The service hasn't been deleted ({len(services)} services found), exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Service app2.example.com has been deleted successfully")
|
||||
|
||||
log_info("✅ Services page tests finished successfully")
|
||||
except SystemExit as e:
|
||||
exit_code = e.code
|
||||
except KeyboardInterrupt:
|
||||
exit_code = 1
|
||||
except:
|
||||
log_exception("Something went wrong, exiting ...")
|
||||
DRIVER.save_screenshot("error.png")
|
||||
exit_code = 1
|
||||
finally:
|
||||
DRIVER.quit()
|
||||
exit(exit_code)
|
||||
|
|
@ -1,16 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
integration=$1
|
||||
test=$2
|
||||
|
||||
if [ -z "$integration" ] ; then
|
||||
echo "Please provide an integration name as argument ❌"
|
||||
exit 1
|
||||
elif [ -z "$test" ] ; then
|
||||
echo "Please provide a test name as argument ❌"
|
||||
exit 1
|
||||
elif [ "$integration" != "docker" ] && [ "$integration" != "linux" ] ; then
|
||||
echo "Integration \"$integration\" is not supported ❌"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🌐 Building UI stack for integration \"$integration\" ..."
|
||||
test=$(basename "$test")
|
||||
|
||||
echo "🌐 Building UI stack for integration \"$integration\", test: $test ..."
|
||||
|
||||
cleanup_stack () {
|
||||
echo "🌐 Cleaning up current stack ..."
|
||||
|
|
@ -141,6 +147,7 @@ fi
|
|||
|
||||
# Start tests
|
||||
if [ "$integration" == "docker" ] ; then
|
||||
echo "TEST_FILE=$test" > .env
|
||||
docker-compose -f docker-compose.test.yml build
|
||||
# shellcheck disable=SC2181
|
||||
if [ $? -ne 0 ] ; then
|
||||
|
|
@ -149,8 +156,9 @@ if [ "$integration" == "docker" ] ; then
|
|||
fi
|
||||
|
||||
docker-compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from ui-tests
|
||||
rm -f .env
|
||||
else
|
||||
python3 main.py
|
||||
python3 "$test"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2181
|
||||
|
|
|
|||
134
tests/ui/utils.py
Normal file
134
tests/ui/utils.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
from contextlib import suppress
|
||||
from logging import error as log_error, exception as log_exception, info as log_info, warning as log_warning
|
||||
from time import sleep
|
||||
from typing import List, Optional, Union
|
||||
from requests import RequestException, get
|
||||
from selenium.webdriver.common.by import By
|
||||
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, WebDriverException
|
||||
|
||||
|
||||
def safe_get_element(driver, by: str, _id: str, *, driver_wait: Optional[WebDriverWait] = None, multiple: bool = False, error: bool = False) -> Union[WebElement, List[WebElement]]:
|
||||
try:
|
||||
return (driver_wait or WebDriverWait(driver, 4)).until(EC.presence_of_element_located((by, _id)) if not multiple else EC.presence_of_all_elements_located((by, _id)))
|
||||
except TimeoutException as e:
|
||||
if error:
|
||||
raise e
|
||||
|
||||
log_exception(f'Element searched by {by}: "{_id}" not found, exiting ...')
|
||||
exit(1)
|
||||
|
||||
|
||||
def assert_button_click(driver, button: Union[str, WebElement]): # type: ignore
|
||||
clicked = False
|
||||
while not clicked:
|
||||
with suppress(ElementClickInterceptedException):
|
||||
if isinstance(button, str):
|
||||
button: Union[WebElement, List[WebElement]] = safe_get_element(driver, By.XPATH, button)
|
||||
assert isinstance(button, WebElement), "Button is not a WebElement"
|
||||
|
||||
sleep(0.5)
|
||||
|
||||
button.click()
|
||||
clicked = True
|
||||
return clicked
|
||||
|
||||
|
||||
def assert_alert_message(driver, message: str):
|
||||
safe_get_element(driver, By.XPATH, "//button[@data-flash-sidebar-open='']")
|
||||
|
||||
sleep(0.3)
|
||||
|
||||
assert_button_click(driver, "//button[@data-flash-sidebar-open='']")
|
||||
|
||||
error = False
|
||||
while True:
|
||||
try:
|
||||
alerts: Union[WebElement, List[WebElement]] = safe_get_element(
|
||||
driver,
|
||||
By.XPATH,
|
||||
"//aside[@data-flash-sidebar='']/div[2]/div",
|
||||
multiple=True,
|
||||
error=True,
|
||||
)
|
||||
assert isinstance(alerts, list), "Alerts is not a list of WebElements"
|
||||
break
|
||||
except TimeoutException:
|
||||
if error:
|
||||
log_exception("Messages list not found, exiting ...")
|
||||
exit(1)
|
||||
error = True
|
||||
driver.refresh()
|
||||
|
||||
is_in = False
|
||||
for alert in alerts:
|
||||
if message in alert.text:
|
||||
is_in = True
|
||||
break
|
||||
|
||||
if not is_in:
|
||||
log_error(f'Message "{message}" not found in one of the messages in the list, exiting ...')
|
||||
exit(1)
|
||||
|
||||
log_info(f'Message "{message}" found in one of the messages in the list')
|
||||
assert_button_click(driver, "//button[@data-flash-sidebar-close='']/*[local-name() = 'svg']")
|
||||
|
||||
|
||||
def access_page(driver, button: Union[str, WebElement], name: str, message: bool = True, *, retries: int = 0, clicked: bool = False):
|
||||
if retries > 5:
|
||||
log_error("Too many retries...")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
if not clicked:
|
||||
clicked = assert_button_click(driver, button)
|
||||
|
||||
title: Union[WebElement, List[WebElement]] = safe_get_element(driver, By.XPATH, "/html/body/div/header/div/nav/h6", driver_wait=WebDriverWait(driver, 45))
|
||||
assert isinstance(title, WebElement), "Title is not a WebElement"
|
||||
|
||||
if title.text != name.title():
|
||||
log_error(f"Didn't get redirected to {name} page, exiting ...")
|
||||
exit(1)
|
||||
except TimeoutException:
|
||||
if "/loading" in driver.current_url:
|
||||
sleep(2)
|
||||
return access_page(driver, button, name, message, retries=retries + 1, clicked=clicked)
|
||||
|
||||
log_error(f"{name.title()} page didn't load in time, exiting ...")
|
||||
exit(1)
|
||||
except WebDriverException as we:
|
||||
if "connectionFailure" in str(we):
|
||||
log_warning("Connection failure, retrying in 5s ...")
|
||||
driver.refresh()
|
||||
sleep(5)
|
||||
return access_page(driver, button, name, message, retries=retries + 1, clicked=clicked)
|
||||
raise we
|
||||
|
||||
if message:
|
||||
log_info(f"{name.title()} page loaded successfully")
|
||||
|
||||
|
||||
def wait_for_service(service: str = "www.example.com"):
|
||||
ready = False
|
||||
retries = 0
|
||||
while not ready:
|
||||
with suppress(RequestException):
|
||||
resp = get(f"http://{service}/ready", headers={"Host": service}, verify=False)
|
||||
status_code = resp.status_code
|
||||
text = resp.text
|
||||
|
||||
if resp.status_code >= 500:
|
||||
log_error(f"An error occurred while trying to reach {service}, exiting ...")
|
||||
exit(1)
|
||||
|
||||
ready = status_code < 400 and "ready" in text
|
||||
|
||||
if retries > 10:
|
||||
log_error(f"Service {service} took too long to be ready, exiting ...")
|
||||
exit(1)
|
||||
elif not ready:
|
||||
retries += 1
|
||||
log_warning(f"Waiting for {service} to be ready, retrying in 5s ...")
|
||||
sleep(5)
|
||||
138
tests/ui/wizard.py
Normal file
138
tests/ui/wizard.py
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
from datetime import datetime, timedelta
|
||||
from logging import error as log_error, exception as log_exception, info as log_info
|
||||
from time import sleep
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
|
||||
from base import DEFAULT_SERVER, DRIVER
|
||||
from utils import access_page, assert_button_click, safe_get_element
|
||||
|
||||
UI_URL = ""
|
||||
exit_code = None
|
||||
|
||||
log_info("Starting the setup wizard ...")
|
||||
|
||||
try:
|
||||
DRIVER.delete_all_cookies()
|
||||
DRIVER.maximize_window()
|
||||
driver_wait = WebDriverWait(DRIVER, 45)
|
||||
|
||||
log_info(f"Navigating to http://{DEFAULT_SERVER}/setup ...")
|
||||
DRIVER.get(f"http://{DEFAULT_SERVER}/setup")
|
||||
|
||||
try:
|
||||
title = safe_get_element(DRIVER, By.XPATH, "/html/body/main/div/div/h1", driver_wait=driver_wait)
|
||||
assert isinstance(title, WebElement), "Title is not a WebElement"
|
||||
|
||||
if title.text != "Setup Wizard":
|
||||
log_error("Didn't get redirected to setup page, exiting ...")
|
||||
exit(1)
|
||||
except TimeoutException:
|
||||
log_exception("Didn't get redirected to setup page, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Setup page loaded successfully, filling the form ...")
|
||||
|
||||
admin_username_input = safe_get_element(DRIVER, By.ID, "admin_username")
|
||||
assert isinstance(admin_username_input, WebElement), "Admin username input is not a WebElement"
|
||||
admin_username_input.send_keys("admin")
|
||||
|
||||
password_input = safe_get_element(DRIVER, By.ID, "admin_password")
|
||||
assert isinstance(password_input, WebElement), "Password input is not a WebElement"
|
||||
password_input.send_keys("S$cr3tP@ssw0rd")
|
||||
|
||||
password_check_input = safe_get_element(DRIVER, By.ID, "admin_password_check")
|
||||
assert isinstance(password_check_input, WebElement), "Password check input is not a WebElement"
|
||||
password_check_input.send_keys("S$cr3tP@ssw0rd")
|
||||
|
||||
ui_url_elem = safe_get_element(DRIVER, By.ID, "ui_url")
|
||||
assert isinstance(ui_url_elem, WebElement), "UI URL input is not a WebElement"
|
||||
UI_URL = ui_url_elem.get_attribute("value")
|
||||
|
||||
assert_button_click(DRIVER, "//button[@id='setup-button']")
|
||||
|
||||
log_info("Submitted the form, waiting for the wizard to finish ...")
|
||||
|
||||
current_time = datetime.now()
|
||||
|
||||
while current_time + timedelta(minutes=5) > datetime.now() and not DRIVER.current_url.endswith("/login"):
|
||||
sleep(1)
|
||||
|
||||
if not DRIVER.current_url.endswith("/login"):
|
||||
log_error("Didn't get redirected to login page, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Redirected to login page, waiting for login form ...")
|
||||
|
||||
safe_get_element(DRIVER, By.TAG_NAME, "form")
|
||||
|
||||
log_info("Form found, trying to access another page without being logged in ...")
|
||||
|
||||
DRIVER.get(f"http://www.example.com{UI_URL}/home")
|
||||
|
||||
log_info("Waiting for toast ...")
|
||||
|
||||
toast = safe_get_element(DRIVER, By.XPATH, "//div[@data-flash-message='']")
|
||||
assert isinstance(toast, WebElement), "Toast is not a WebElement"
|
||||
|
||||
log_info("Toast found")
|
||||
|
||||
if "Please log in to access this page." not in toast.text:
|
||||
log_error("Toast doesn't contain the expected message, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Toast contains the expected message, filling login form with wrong credentials ...")
|
||||
|
||||
sleep(1)
|
||||
|
||||
safe_get_element(DRIVER, By.TAG_NAME, "form")
|
||||
|
||||
username_input = safe_get_element(DRIVER, By.ID, "username")
|
||||
assert isinstance(username_input, WebElement), "Username input is not a WebElement"
|
||||
username_input.send_keys("hackerman")
|
||||
|
||||
password_input = safe_get_element(DRIVER, By.ID, "password")
|
||||
assert isinstance(password_input, WebElement), "Password input is not a WebElement"
|
||||
password_input.send_keys("password")
|
||||
password_input.send_keys(Keys.RETURN)
|
||||
|
||||
sleep(0.3)
|
||||
|
||||
try:
|
||||
title = safe_get_element(DRIVER, By.XPATH, "/html/body/main/div[1]/div/h1", driver_wait=driver_wait)
|
||||
assert isinstance(title, WebElement), "Title is not a WebElement"
|
||||
|
||||
if title.text != "Log in":
|
||||
log_error("Didn't get redirected to login page, exiting ...")
|
||||
exit(1)
|
||||
except TimeoutException:
|
||||
log_exception("Login page didn't load in time, exiting ...")
|
||||
exit(1)
|
||||
|
||||
log_info("Got redirected to login page successfully, filling login form with good credentials ...")
|
||||
|
||||
username_input = safe_get_element(DRIVER, By.ID, "username")
|
||||
assert isinstance(username_input, WebElement), "Username input is not a WebElement"
|
||||
username_input.send_keys("admin")
|
||||
|
||||
password_input = safe_get_element(DRIVER, By.ID, "password")
|
||||
assert isinstance(password_input, WebElement), "Password input is not a WebElement"
|
||||
password_input.send_keys("S$cr3tP@ssw0rd")
|
||||
|
||||
access_page(DRIVER, "//button[@value='login']", "home")
|
||||
except SystemExit as e:
|
||||
exit_code = e.code
|
||||
except KeyboardInterrupt:
|
||||
exit_code = 1
|
||||
except:
|
||||
log_exception("Something went wrong, exiting ...")
|
||||
DRIVER.save_screenshot("error.png")
|
||||
exit_code = 1
|
||||
finally:
|
||||
if exit_code is not None:
|
||||
DRIVER.quit()
|
||||
exit(exit_code)
|
||||
Loading…
Reference in a new issue