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 # 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", "stats": "src/modules/stats/stats.json",
"technician": "src/modules/technician/technician.json", "technician": "src/modules/technician/technician.json",
"ui-tests": "test/ui_tests/ui-tests.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", "ui-tests-tests": "test/ui_tests_tests/ui-tests-tests.json",
"get-version": "tools/get_version/get-version.json", "get-version": "tools/get_version/get-version.json",
"post-build": "tools/post_build/post-build.json", "post-build": "tools/post_build/post-build.json",
"set-version": "tools/set_version/set-version.json" "set-version": "tools/set_version/set-version.json"
}, },
"Scripts": { "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", "sv": "cpl set-version $ARGS",
"set-version": "cpl run set-version $ARGS; echo '';", "set-version": "cpl run set-version $ARGS; echo '';",
"gv": "cpl get-version", "gv": "cpl get-version",

View File

@ -16,5 +16,10 @@
"Languages": [ "Languages": [
"de" "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 ApplicationABC
from cpl_core.application import ApplicationBuilder 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.startup import Startup
from ui_tests_shared.declarations import Declarations from ui_tests_shared.declarations import Declarations

View File

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

View File

@ -1,19 +1,10 @@
import os import os
from datetime import datetime
from typing import Callable, Type, Optional
from cpl_core.application import StartupExtensionABC from cpl_core.application import StartupExtensionABC
from cpl_core.configuration import ConfigurationABC from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC 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): class StartupTestExtension(StartupExtensionABC):
@ -23,6 +14,7 @@ class StartupTestExtension(StartupExtensionABC):
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC): def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC):
# this shit has to be done here because we need settings in subsequent startup extensions # 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(os.path.dirname(os.path.realpath(__file__)))
environment.set_working_directory('../../src/bot/')
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
pass 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 import time
from typing import Optional
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe from cpl_translation import TranslatePipe
from selenium import webdriver
from selenium.common import NoSuchElementException, StaleElementReferenceException from selenium.common import NoSuchElementException, StaleElementReferenceException
from selenium.webdriver import Keys from selenium.webdriver import Keys
from selenium.webdriver.common.by import By 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 selenium.webdriver.support.wait import WebDriverWait
from ui_tests_shared.test_case_with_app import TestCaseWithApp from ui_tests_shared.test_case_with_app import TestCaseWithApp
from ui_tests_shared.ui import UI
class CommandTestCaseWithApp(TestCaseWithApp): class CommandTestCaseWithApp(TestCaseWithApp):
_cmd: WebElement _cmd: WebElement
_bot = None _bot: Optional[DiscordBotServiceABC] = None
_t = None _t: Optional[TranslatePipe] = None
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -25,22 +27,22 @@ class CommandTestCaseWithApp(TestCaseWithApp):
cls._bot = cls._services.get_service(DiscordBotServiceABC) cls._bot = cls._services.get_service(DiscordBotServiceABC)
cls._t = cls._services.get_service(TranslatePipe) cls._t = cls._services.get_service(TranslatePipe)
def send_command(self, cmd: str): @classmethod
self._driver.get('https://discord.com/channels/910199451145076828/911578636899987526') def send_command(cls, cmd: str):
UI.driver.get(cls._test_settings.cmd_url)
time.sleep(2) 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_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}') cmd_element.send_keys(f'/{cmd}')
time.sleep(2) time.sleep(2)
ignored_exceptions = (NoSuchElementException, StaleElementReferenceException,) 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(( expected_conditions.presence_of_element_located((
By.XPATH, 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]' '/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() ).click()
time.sleep(2) 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( 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)
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 typing import Optional
from cpl_core.environment import ApplicationEnvironmentABC
from ui_tests.test_application import TestApplication from ui_tests.test_application import TestApplication
from ui_tests_shared.configuration.test_settings import TestSettings
class Declarations: 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 import unittest
from typing import Optional from typing import Optional
from cpl_core.configuration import ConfigurationABC from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC 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.main import get_app
from ui_tests_shared.configuration.test_settings import TestSettings
from ui_tests_shared.declarations import Declarations from ui_tests_shared.declarations import Declarations
from ui_tests_shared.ui import UI
class TestCaseWithApp(unittest.TestCase): class TestCaseWithApp(unittest.TestCase):
options = webdriver.ChromeOptions()
options.add_experimental_option('useAutomationExtension', False)
options.add_experimental_option("excludeSwitches", ["enable-automation"])
_config: Optional[ConfigurationABC] = None _config: Optional[ConfigurationABC] = None
_services: Optional[ServiceProviderABC] = None _services: Optional[ServiceProviderABC] = None
_driver = webdriver.Chrome(options=options) _test_settings: Optional[TestSettings] = None
_is_logged_in = False
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -32,30 +22,6 @@ class TestCaseWithApp(unittest.TestCase):
cls._config = Declarations.app.config cls._config = Declarations.app.config
cls._services = Declarations.app.services cls._services = Declarations.app.services
cls._login() cls._test_settings: TestSettings = cls._config.get_configuration(TestSettings)
UI.set_settings(cls._test_settings)
@classmethod UI.login(cls._config.get_configuration('DISCORD_MAIL'), cls._config.get_configuration('DISCORD_PASSWORD'))
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)

View File

@ -22,9 +22,7 @@
"cpl-cli>=2022.10.0" "cpl-cli>=2022.10.0"
], ],
"PythonVersion": ">=3.10.4", "PythonVersion": ">=3.10.4",
"PythonPath": { "PythonPath": {},
"linux": ""
},
"Classifiers": [] "Classifiers": []
}, },
"BuildSettings": { "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 import discord
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp 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): class PingTestCase(CommandTestCaseWithApp):
def setUp(self):
pass
@async_test @async_test
async def test_ping(self): async def test_ping(self):
correct_response = self._t.transform('modules.base.pong') correct_response = self._t.transform('modules.base.pong')

View File

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