Added test settings & improved test architecture #139

This commit is contained in:
Sven Heidemann 2022-11-26 23:09:15 +01:00
parent 9de66d4fd4
commit de78cec96c
17 changed files with 185 additions and 72 deletions

View File

@ -1,2 +1,14 @@
# kd_discord_bot
## Test Bot
To test the bot run unittests or call ```cpl test```.
Configure test instance by creating the file ./test/ui_tests/.env and set following environment variables:
```sh
KDB_TEST_DB_PASSWORD=
KDB_TEST_NAME=
KDB_TEST_TOKEN=
KDB_TEST_DISCORD_MAIL=
KDB_TEST_DISCORD_PASSWORD=
```

View File

@ -15,14 +15,14 @@
"stats": "src/modules/stats/stats.json",
"technician": "src/modules/technician/technician.json",
"ui-tests": "test/ui_tests/ui-tests.json",
"ui-tests-shared": "src/../test/ui_tests_shared/ui-tests-shared.json",
"ui-tests-shared": "test/ui_tests_shared/ui-tests-shared.json",
"ui-tests-tests": "test/ui_tests_tests/ui-tests-tests.json",
"get-version": "tools/get_version/get-version.json",
"post-build": "tools/post_build/post-build.json",
"set-version": "tools/set_version/set-version.json"
},
"Scripts": {
"test": "cpl run ui-tests",
"test": "export $(cat test/ui_tests/.env); export PYTHONPATH=$PWD/src:$PYTHONPATH; cpl run ui-tests",
"sv": "cpl set-version $ARGS",
"set-version": "cpl run set-version $ARGS; echo '';",
"gv": "cpl get-version",

View File

@ -16,5 +16,10 @@
"Languages": [
"de"
]
},
"TestSettings": {
"LoginUrl": "https://discord.com/login",
"MePageUrl": "https://discord.com/channels/@me",
"CmdURL": "https://discord.com/channels/910199451145076828/911578636899987526"
}
}

View File

@ -3,7 +3,7 @@ import asyncio
from cpl_core.application import ApplicationABC
from cpl_core.application import ApplicationBuilder
from bot.startup_test_extension import StartupTestExtension
from startup_test_extension import StartupTestExtension
from ui_tests.startup import Startup
from ui_tests_shared.declarations import Declarations

View File

@ -21,7 +21,7 @@ class Startup(StartupABC):
self._config: Optional[ConfigurationABC] = None
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC:
configuration.add_environment_variables('KDB-TEST_')
configuration.add_environment_variables('KDB_TEST_')
configuration.add_environment_variables('DISCORD_')
cwd = os.path.dirname(os.path.realpath(__file__))

View File

@ -1,19 +1,10 @@
import os
from datetime import datetime
from typing import Callable, Type, Optional
from cpl_core.application import StartupExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from bot_core.configuration.bot_logging_settings import BotLoggingSettings
from bot_core.configuration.bot_settings import BotSettings
from modules.base.configuration.base_settings import BaseSettings
from modules.boot_log.configuration.boot_log_settings import BootLogSettings
from modules.level.configuration.level_settings import LevelSettings
from modules.permission.configuration.permission_settings import PermissionSettings
class StartupTestExtension(StartupExtensionABC):
@ -23,6 +14,7 @@ class StartupTestExtension(StartupExtensionABC):
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC):
# this shit has to be done here because we need settings in subsequent startup extensions
environment.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
environment.set_working_directory('../../src/bot/')
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
pass

View File

@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "ui-tests",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.10.0.post9"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "console",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "ui_tests_shared.main",
"EntryPoint": "ui-tests-shared",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -1,8 +1,8 @@
import time
from typing import Optional
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from selenium import webdriver
from selenium.common import NoSuchElementException, StaleElementReferenceException
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
@ -11,13 +11,15 @@ from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from ui_tests_shared.test_case_with_app import TestCaseWithApp
from ui_tests_shared.ui import UI
class CommandTestCaseWithApp(TestCaseWithApp):
_cmd: WebElement
_bot = None
_t = None
_bot: Optional[DiscordBotServiceABC] = None
_t: Optional[TranslatePipe] = None
@classmethod
def setUpClass(cls):
@ -25,22 +27,22 @@ class CommandTestCaseWithApp(TestCaseWithApp):
cls._bot = cls._services.get_service(DiscordBotServiceABC)
cls._t = cls._services.get_service(TranslatePipe)
def send_command(self, cmd: str):
self._driver.get('https://discord.com/channels/910199451145076828/911578636899987526')
@classmethod
def send_command(cls, cmd: str):
UI.driver.get(cls._test_settings.cmd_url)
time.sleep(2)
cmd_element_ident = (By.XPATH, '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div[2]/main/form/div/div[1]/div/div[3]/div/div[2]/div')
cmd_element = self._driver.find_element(*cmd_element_ident)
cmd_element = UI.driver.find_element(*cmd_element_ident)
cmd_element.send_keys(f'/{cmd}')
time.sleep(2)
ignored_exceptions = (NoSuchElementException, StaleElementReferenceException,)
WebDriverWait(self._driver, 20, ignored_exceptions=ignored_exceptions).until(
WebDriverWait(UI.driver, 20, ignored_exceptions=ignored_exceptions).until(
expected_conditions.presence_of_element_located((
By.XPATH,
'/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div[2]/main/form/div/div[2]/div/div/div[5]'
))
).click()
time.sleep(2)
self._driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div/main/form/div/div[2]/div/div[2]/div/div').send_keys(
Keys.ENTER)
UI.driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div/main/form/div/div[2]/div/div[2]/div/div').send_keys(Keys.ENTER)

View File

@ -0,0 +1 @@
# imports

View File

@ -0,0 +1,35 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class TestSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._login_url = ''
self._me_page_url = ''
self._cmd_url = ''
@property
def login_url(self) -> str:
return self._login_url
@property
def me_page_url(self) -> str:
return self._me_page_url
@property
def cmd_url(self) -> str:
return self._cmd_url
def from_dict(self, settings: dict):
try:
self._login_url = settings['LoginUrl']
self._me_page_url = settings['MePageUrl']
self._cmd_url = settings['CmdURL']
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@ -1,7 +1,12 @@
from typing import Optional
from cpl_core.environment import ApplicationEnvironmentABC
from ui_tests.test_application import TestApplication
from ui_tests_shared.configuration.test_settings import TestSettings
class Declarations:
app: Optional[TestApplication] = None
app: Optional[TestApplication]
env: Optional[ApplicationEnvironmentABC]
test_settings: Optional[TestSettings]

View File

@ -1,29 +1,19 @@
import time
import unittest
from typing import Optional
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from ui_tests.main import get_app
from ui_tests_shared.configuration.test_settings import TestSettings
from ui_tests_shared.declarations import Declarations
from ui_tests_shared.ui import UI
class TestCaseWithApp(unittest.TestCase):
options = webdriver.ChromeOptions()
options.add_experimental_option('useAutomationExtension', False)
options.add_experimental_option("excludeSwitches", ["enable-automation"])
_config: Optional[ConfigurationABC] = None
_services: Optional[ServiceProviderABC] = None
_driver = webdriver.Chrome(options=options)
_is_logged_in = False
_test_settings: Optional[TestSettings] = None
@classmethod
def setUpClass(cls):
@ -32,30 +22,6 @@ class TestCaseWithApp(unittest.TestCase):
cls._config = Declarations.app.config
cls._services = Declarations.app.services
cls._login()
@classmethod
def _login(cls):
if cls._is_logged_in:
return
# use full xpath: https://stackoverflow.com/questions/71179006/how-can-selenium-python-chrome-find-web-elements-visible-in-dev-tools-but-no
cls._driver.get('https://discord.com/login')
WebDriverWait(cls._driver, 20).until(expected_conditions.presence_of_element_located((By.NAME, 'email')))
mail = cls._driver.find_element(By.NAME, 'email')
mail.clear()
mail.send_keys("dev.sven.heidemann@sh-edraft.de")
mail.send_keys(Keys.RETURN)
pw = cls._driver.find_element(By.NAME, 'password')
pw.clear()
pw.send_keys("Heidemann1410")
pw.send_keys(Keys.RETURN)
time.sleep(1)
pw.send_keys(Keys.RETURN)
WebDriverWait(cls._driver, 20).until(expected_conditions.url_matches('https://discord.com/channels/@me'))
time.sleep(4)
cls._test_settings: TestSettings = cls._config.get_configuration(TestSettings)
UI.set_settings(cls._test_settings)
UI.login(cls._config.get_configuration('DISCORD_MAIL'), cls._config.get_configuration('DISCORD_PASSWORD'))

View File

@ -22,9 +22,7 @@
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {

View File

@ -0,0 +1,56 @@
import time
import unittest
from typing import Optional
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from ui_tests.main import get_app
from ui_tests_shared.configuration.test_settings import TestSettings
from ui_tests_shared.declarations import Declarations
class UI:
_test_settings: Optional[TestSettings] = None
options = webdriver.ChromeOptions()
options.add_experimental_option('useAutomationExtension', False)
options.add_experimental_option("excludeSwitches", ["enable-automation"])
driver = webdriver.Chrome(options=options)
_is_logged_in = False
@classmethod
def set_settings(cls, test_settings: TestSettings):
cls._test_settings = test_settings
@classmethod
def login(cls, mail: str, password: str):
if cls._is_logged_in:
return
# use full xpath: https://stackoverflow.com/questions/71179006/how-can-selenium-python-chrome-find-web-elements-visible-in-dev-tools-but-no
cls.driver.get(cls._test_settings.login_url)
WebDriverWait(cls.driver, 20).until(expected_conditions.presence_of_element_located((By.NAME, 'email')))
mail_element = cls.driver.find_element(By.NAME, 'email')
mail_element.clear()
mail_element.send_keys(mail)
mail_element.send_keys(Keys.RETURN)
pw_element = cls.driver.find_element(By.NAME, 'password')
pw_element.clear()
pw_element.send_keys(password)
pw_element.send_keys(Keys.RETURN)
time.sleep(1)
pw_element.send_keys(Keys.RETURN)
WebDriverWait(cls.driver, 20).until(expected_conditions.url_matches(cls._test_settings.me_page_url))
time.sleep(4)

View File

@ -1,3 +1,5 @@
import unittest
import discord
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
@ -6,9 +8,6 @@ from ui_tests_shared.decorators import async_test
class PingTestCase(CommandTestCaseWithApp):
def setUp(self):
pass
@async_test
async def test_ping(self):
correct_response = self._t.transform('modules.base.pong')

View File

@ -22,9 +22,7 @@
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {