refactor for independent test dirs #7

Merged
dan merged 49 commits from independent-test-dirs into dev 2023-12-05 21:41:46 +01:00
45 changed files with 373 additions and 228 deletions

View file

@ -1,4 +1,11 @@
# GIT Clone # AbraTest
...description...
# Usage
To use AbraTest, follow these steps:
## 1. GIT Clone
To clone with submodules, use these git commands: To clone with submodules, use these git commands:
@ -8,7 +15,11 @@ git submodule update --init // add submodule after normal cloning
git submodule update --remote // update submodules git submodule update --remote // update submodules
``` ```
# Run without Docker ## Run
You can run AbraTest with and without Docker. Choose now and follow the steps accordingly:
## 2.1 Run without Docker
### Installation ### Installation
@ -22,16 +33,16 @@ playwright install
Run the script with Run the script with
```bash ```bash
python main.py python main.py # run abratest
pytest # test abratest
``` ```
# Run with Docker # 2.2 Run with Docker
```bash ```bash
docker compose build docker compose build # build the image
docker compose run --rm app python ./main.py docker compose run --rm app python ./main.py # run AbraTest
docker compose run --rm app pytest docker compose run --rm app pytest # test AbraTest
# docker-compose up
``` ```
Force rebuild with cache Force rebuild with cache
@ -46,7 +57,7 @@ Force rebuild wtihtout cache
docker-compose build --no-cache docker-compose build --no-cache
``` ```
# Codegen ## Codegen
Use playwright codegen to create code for new testes easily https://playwright.dev/python/docs/codegen Use playwright codegen to create code for new testes easily https://playwright.dev/python/docs/codegen

View file

@ -1,25 +1,27 @@
import importlib
import re
from pathlib import Path from pathlib import Path
from loguru import logger from loguru import logger
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
from src.env_manager import EnvFile, EnvManager from abratest.env_manager import EnvFile, EnvManager
from src.html_helper import merge_html_files from abratest.html_helper import merge_html_files
from src.runner import Runner from abratest.runner import Runner
from src.runner_dict import RUNNER_DICT from abratest.utils import rmtree
from src.utils import rmtree
class Coordinator: class Coordinator:
def __init__(self, env_paths_list: list[Path], output_dir: Path, session_id: str) -> None: def __init__(self, env_paths_list: list[Path], output_dir: Path, session_id: str, recipes_dir: Path) -> None:
# logging # logging
out_string = "".join([e.name + "\n" for e in env_paths_list]) out_string = "".join([e.name + "\n" for e in env_paths_list])
out_string += f"output_dir = {output_dir}\n" out_string += f"output_dir = {output_dir}\n"
out_string += f"session_id = {session_id}" out_string += f"session_id = {session_id}"
logger.info(f"initialize Coordinator instance with\nenv_paths_list =\n{out_string}") logger.info(f"initialize Coordinator instance with\nenv_paths_list =\n{out_string}")
self.DIR = DirManager(output_dir=output_dir, session_id=session_id) self.RUNNER_DICT = self.create_runner_dict(recipes_dir)
self.ENV = EnvManager(env_paths_list) self.DIR = DirManager(output_dir=output_dir, session_id=session_id, recipes_dir=recipes_dir)
self.ENV = EnvManager(env_paths_list, self.RUNNER_DICT)
def setup_test(self) -> None: def setup_test(self) -> None:
logger.info("calling setup_test()") logger.info("calling setup_test()")
@ -39,14 +41,15 @@ class Coordinator:
def _load_runners(self, env_files: list[EnvFile]) -> list[Runner]: def _load_runners(self, env_files: list[EnvFile]) -> list[Runner]:
"""Creates an instance of the correct Runner class for each given env file""" """Creates an instance of the correct Runner class for each given env file"""
runners = [] runners: list[Runner] = []
for env_file in env_files: for env_file in env_files:
RunnerClass = RUNNER_DICT[env_file.config["TYPE"]] RunnerClass = self.RUNNER_DICT[env_file.config["TYPE"]]
runners.append( dependency_classes: list[type[Runner]] = []
RunnerClass( for dependency in RunnerClass.dependencies:
dotenv_path=env_file.env_path, output_dir=self.DIR.output_dir, session_id=self.DIR.session_id dependency_classes.append(self.RUNNER_DICT[dependency])
) runner_instance = RunnerClass(dotenv_path=env_file.env_path, DIR=self.DIR)
) runner_instance._dependency_runners = dependency_classes
runners.append(runner_instance)
return runners return runners
def combine_html(self) -> None: def combine_html(self) -> None:
@ -78,3 +81,28 @@ class Coordinator:
new_path = get_new_path(self.DIR.RECORDS, f.parent.name) new_path = get_new_path(self.DIR.RECORDS, f.parent.name)
f.parent.rename(new_path) f.parent.rename(new_path)
rmtree(trace_root_dir) rmtree(trace_root_dir)
@staticmethod
def create_runner_dict(recipes_dir: Path) -> dict[str, type["Runner"]]:
"""Creates a dictionary holding all the RunnerClasses that can be discovered in recipes_dir
example:
RUNNER_DICT: dict[str, type["Runner"]] = {
"authentik": RunnerAuthentik,
"wordpress": RunnerWordpress,
"nextcloud": RunnerNextcloud,
}
"""
RUNNER_DICT: dict[str, type["Runner"]] = dict()
runner_discovery_pattern = re.compile("Runner.+")
for module_path in recipes_dir.rglob("*/runner*.py"):
rel_path = module_path.relative_to(recipes_dir).as_posix().replace("/", ".").replace(".py", "")
module = importlib.import_module(rel_path)
runner_class_names = [name for name in dir(module) if runner_discovery_pattern.match(name)]
assert len(runner_class_names) == 1
runner_class_name = runner_class_names[0]
RunnerClass: type[Runner] = getattr(module, runner_class_name)
RUNNER_DICT[RunnerClass.name] = RunnerClass
return RUNNER_DICT

View file

@ -16,12 +16,14 @@ class DirManager:
... ...
""" """
def __init__(self, output_dir: Path | str, session_id: str): def __init__(self, output_dir: Path | str, session_id: str, recipes_dir: Path | str = ""):
# root test dir
if isinstance(output_dir, str): if isinstance(output_dir, str):
output_dir = Path(output_dir) output_dir = Path(output_dir)
self.output_dir = output_dir.resolve() self.output_dir = output_dir.resolve()
self.session_id = session_id self.session_id = session_id
if isinstance(recipes_dir, str):
recipes_dir = Path(recipes_dir)
self.recipes_dir = recipes_dir
def create_all_dirs(self) -> None: def create_all_dirs(self) -> None:
dirs: list[Path] = [ dirs: list[Path] = [
@ -63,3 +65,7 @@ class DirManager:
@property @property
def RESULTS(self): def RESULTS(self):
return self.SESSION / "results" return self.SESSION / "results"
@property
def RECIPES(self):
return self.recipes_dir

View file

@ -4,8 +4,8 @@ from typing import NamedTuple
from dotenv import dotenv_values from dotenv import dotenv_values
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
from src.runner_dict import RUNNER_DICT from abratest.runner import Runner
class EnvFile(NamedTuple): class EnvFile(NamedTuple):
@ -23,9 +23,9 @@ class DependencyRule(NamedTuple):
class EnvManager: class EnvManager:
def __init__(self, env_paths_list: list[Path]): def __init__(self, env_paths_list: list[Path], RUNNER_DICT: dict[str, type["Runner"]]):
self.env_files: list[EnvFile] = self._get_env_files(env_paths_list) self.env_files: list[EnvFile] = self._get_env_files(env_paths_list)
self.dependency_rules: list[DependencyRule] = self._get_dependency_rules(self.env_files) self.dependency_rules: list[DependencyRule] = self._get_dependency_rules(self.env_files, RUNNER_DICT)
self.env_files = self.sort_env_files_by_rule(self.env_files, self.dependency_rules) self.env_files = self.sort_env_files_by_rule(self.env_files, self.dependency_rules)
@staticmethod @staticmethod
@ -41,12 +41,12 @@ class EnvManager:
return env_files return env_files
@staticmethod @staticmethod
def _get_dependency_rules(env_files: list[EnvFile]) -> list[DependencyRule]: def _get_dependency_rules(env_files: list[EnvFile], RUNNER_DICT: dict[str, type["Runner"]]) -> list[DependencyRule]:
dependency_rules: list[DependencyRule] = [] dependency_rules: list[DependencyRule] = []
for env_file in env_files: for env_file in env_files:
child_runner_class = RUNNER_DICT[env_file.env_type] child_runner_class = RUNNER_DICT[env_file.env_type]
for dependency in child_runner_class.dependencies: for dependency in child_runner_class.dependencies:
dependency_rule = DependencyRule(child=child_runner_class.name, dependency=dependency.name) dependency_rule = DependencyRule(child=child_runner_class.name, dependency=dependency)
dependency_rules.append(dependency_rule) dependency_rules.append(dependency_rule)
return dependency_rules return dependency_rules

View file

@ -14,11 +14,12 @@ from dotenv import dotenv_values
from playwright.sync_api import BrowserContext, expect from playwright.sync_api import BrowserContext, expect
from pytest import Parser from pytest import Parser
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
from abratest.utils import BaseUrl
# global timeout and LOCALE # global timeout and LOCALE
LOCALE = {"Accept-Language": "de_DE"} LOCALE = {"Accept-Language": "de_DE"}
TIMEOUT = 7_000 TIMEOUT = 20_000
expect.set_options(timeout=TIMEOUT) expect.set_options(timeout=TIMEOUT)
@ -73,6 +74,11 @@ def dotenv_config(request) -> dict[str, str]:
return dotenv_values(dotenv_path) # type: ignore return dotenv_values(dotenv_path) # type: ignore
@pytest.fixture(scope="session", autouse=True)
def URL(dotenv_config: dict[str, str]) -> BaseUrl:
return BaseUrl(netloc=dotenv_config["DOMAIN"])
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def imap_ssl_email_client() -> None: def imap_ssl_email_client() -> None:
assert os.environ["IMAP_HOST"] assert os.environ["IMAP_HOST"]

View file

@ -6,7 +6,7 @@ import pytest
from dotenv import dotenv_values from dotenv import dotenv_values
from loguru import logger from loguru import logger
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
@dataclass @dataclass
@ -22,15 +22,13 @@ class Runner:
setups: list[Test] = [] setups: list[Test] = []
tests: list[Test] = [] tests: list[Test] = []
cleanups: list[Test] = [] cleanups: list[Test] = []
dependencies: list[type["Runner"]] = [] dependencies: list[str] = []
prevent_skip = False _dependency_runners: list[type["Runner"]] = []
def __init__(self, dotenv_path: Path, output_dir: Path, session_id: str): def __init__(self, dotenv_path: Path, DIR: DirManager):
self.dotenv_path = dotenv_path self.dotenv_path = dotenv_path
self.config: dict[str, str] = dotenv_values(dotenv_path) # type: ignore self.config: dict[str, str] = dotenv_values(dotenv_path) # type: ignore
self.output_dir = output_dir self.DIR = DIR
self.session_id = session_id
self.DIRS = DirManager(output_dir, session_id)
logger.info(f"creating instance of {self.__class__.__name__}") logger.info(f"creating instance of {self.__class__.__name__}")
assert self.test_dir_name assert self.test_dir_name
@ -66,7 +64,7 @@ class Runner:
# condition_met: true / false # condition_met: true / false
identifier_string = self.combine_names(self.name, test.test_file) identifier_string = self.combine_names(self.name, test.test_file)
test_path = self.root_dir / self.test_dir_name / test.test_file full_test_path = self.DIR.RECIPES / self.name / self.test_dir_name / test.test_file
# check if test aleady passed # check if test aleady passed
if self._is_test_passed(identifier_string, remove_existing=True): if self._is_test_passed(identifier_string, remove_existing=True):
@ -83,7 +81,7 @@ class Runner:
# test condition is undefined or not met # test condition is undefined or not met
logger.info(f"running {identifier_string}") logger.info(f"running {identifier_string}")
result = self._call_pytest(test_path) result = self._call_pytest(full_test_path)
self._create_result_file(result=result, identifier_string=identifier_string) self._create_result_file(result=result, identifier_string=identifier_string)
def _is_test_passed(self, identifier_string: str, remove_existing: bool = False) -> bool: def _is_test_passed(self, identifier_string: str, remove_existing: bool = False) -> bool:
@ -96,7 +94,7 @@ class Runner:
other than 'passed' will be deleted""" other than 'passed' will be deleted"""
already_passed = False already_passed = False
for result in self.DIRS.RESULTS.glob("*"): for result in self.DIR.RESULTS.glob("*"):
if identifier_string in result.name: if identifier_string in result.name:
# process any result file (passed / failed / skipped) if it exists # process any result file (passed / failed / skipped) if it exists
if "passed" in result.name: if "passed" in result.name:
@ -112,6 +110,8 @@ class Runner:
command_arguments = [] command_arguments = []
# command_arguments.append("--traceconfig")
command_arguments.append("-v") command_arguments.append("-v")
# command_arguments.append("-rx") # command_arguments.append("-rx")
command_arguments.append(str(full_test_path)) command_arguments.append(str(full_test_path))
@ -121,17 +121,17 @@ class Runner:
# set root dir for tests output (used in DirManager). this is our custom argument # set root dir for tests output (used in DirManager). this is our custom argument
command_arguments.append("--output_dir") command_arguments.append("--output_dir")
command_arguments.append(str(self.DIRS.OUTPUT_DIR)) command_arguments.append(str(self.DIR.OUTPUT_DIR))
command_arguments.append("--session_id") command_arguments.append("--session_id")
command_arguments.append(self.session_id) command_arguments.append(self.DIR.session_id)
# artifacts dir from pytest # artifacts dir from pytest
# warning: https://github.com/microsoft/playwright-pytest/issues/111 # warning: https://github.com/microsoft/playwright-pytest/issues/111
# --output only works with the given context and page fixture # --output only works with the given context and page fixture
# folder needs to be unique! traces will not appear, if every pytest run has same output dir # folder needs to be unique! traces will not appear, if every pytest run has same output dir
command_arguments.append("--output") command_arguments.append("--output")
command_arguments.append(str(self.DIRS.RECORDS / "traces" / full_test_path.stem)) command_arguments.append(str(self.DIR.RECORDS / "traces" / full_test_path.stem))
# tracing # tracing
command_arguments.append("--tracing") command_arguments.append("--tracing")
@ -145,7 +145,7 @@ class Runner:
# command_arguments.append("--headed") # command_arguments.append("--headed")
# html report. Will be combined into one file later. # html report. Will be combined into one file later.
command_arguments.append(f"--html={self.DIRS.RECORDS / 'html' / full_test_path.with_suffix('.html').name}") command_arguments.append(f"--html={self.DIR.RECORDS / 'html' / full_test_path.with_suffix('.html').name}")
return pytest.main(command_arguments) return pytest.main(command_arguments)
@ -157,7 +157,7 @@ class Runner:
"""create result file to indicated passed/failed or skipped test""" """create result file to indicated passed/failed or skipped test"""
full_name = self.combine_names(self.result_int_to_str(result), identifier_string) full_name = self.combine_names(self.result_int_to_str(result), identifier_string)
file_path = self.DIRS.RESULTS / full_name file_path = self.DIR.RESULTS / full_name
with open(file_path, "w") as _: with open(file_path, "w") as _:
pass # create empty file pass # create empty file
@ -166,11 +166,11 @@ class Runner:
# todo: what about conditional setups? # todo: what about conditional setups?
passed_tests = [r.name for r in self.DIRS.RESULTS.glob("*") if "passed" in r.name] passed_tests = [r.name for r in self.DIR.RESULTS.glob("*") if "passed" in r.name]
results = [] results = []
for dependencie_runner in self.dependencies: for dependency_runner in self._dependency_runners:
for setup_name in dependencie_runner.setups: for setup_name in dependency_runner.setups:
dependencie_identifier = self.combine_names(dependencie_runner.name, setup_name.test_file) dependencie_identifier = self.combine_names(dependency_runner.name, setup_name.test_file)
results.append(any(dependencie_identifier in f for f in passed_tests)) results.append(any(dependencie_identifier in f for f in passed_tests))
return all(results) return all(results)

View file

@ -1,5 +1,20 @@
from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from urllib.parse import urlunparse
@dataclass
class BaseUrl:
netloc: str
scheme: str = "https"
path: str = ""
params: str = ""
query: str = ""
fragment: str = ""
def get(self, path: str = ""):
return urlunparse((self.scheme, self.netloc, path, self.params, self.query, self.fragment))
def get_session_id() -> str: def get_session_id() -> str:

27
main.py
View file

@ -4,9 +4,9 @@ from pathlib import Path
from loguru import logger from loguru import logger
from src.coordinator import Coordinator from abratest.coordinator import Coordinator
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
from src.utils import get_session_id from abratest.utils import get_session_id
# ----------------------------- lookup env files ----------------------------- # # ----------------------------- lookup env files ----------------------------- #
@ -23,6 +23,7 @@ from src.utils import get_session_id
ENV_FILES = [ ENV_FILES = [
Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik
Path("envfiles/blog.test.dev.local-it.cloud.env"), # wordpress Path("envfiles/blog.test.dev.local-it.cloud.env"), # wordpress
# Path("envfiles/files.test.dev.local-it.cloud.env"), # nextcloud
] ]
@ -30,12 +31,7 @@ ENV_FILES = [
OUTPUT_DIR = Path("./test-output").resolve() OUTPUT_DIR = Path("./test-output").resolve()
RECIPES_DIR = Path("./recipes").resolve()
# -------------------------- enable playwright debug ------------------------- #
# os.environ["PWDEBUG"] = "1"
# --------------------- load credentials to env variables -------------------- # # --------------------- load credentials to env variables -------------------- #
@ -49,6 +45,14 @@ for key, value in CREDENTIALS.items():
os.environ[key] = value os.environ[key] = value
# -------------------------- enable playwright debug ------------------------- #
# add abra-testing dir
os.environ["PYTEST_PLUGINS"] = "abratest.plugin-abra" # "abratest.plugin,abratest.other"
# os.environ["PWDEBUG"] = "1"
# ----------------------------- define session_id ---------------------------- # # ----------------------------- define session_id ---------------------------- #
@ -58,6 +62,7 @@ session_id = get_session_id()
# ------------------------------- setup logging ------------------------------ # # ------------------------------- setup logging ------------------------------ #
DIR = DirManager(output_dir=OUTPUT_DIR, session_id=session_id) DIR = DirManager(output_dir=OUTPUT_DIR, session_id=session_id)
log_file = DIR.RECORDS / "coordinator.log" log_file = DIR.RECORDS / "coordinator.log"
logger.add(log_file) logger.add(log_file)
@ -66,7 +71,9 @@ logger.add(log_file)
# ---------------------------- initialize and run ---------------------------- # # ---------------------------- initialize and run ---------------------------- #
coordinator = Coordinator(ENV_FILES, output_dir=OUTPUT_DIR, session_id=session_id) coordinator = Coordinator(
env_paths_list=ENV_FILES, output_dir=OUTPUT_DIR, session_id=session_id, recipes_dir=RECIPES_DIR
)
coordinator.setup_test() coordinator.setup_test()
coordinator.run_test() coordinator.run_test()
coordinator.combine_html() coordinator.combine_html()

View file

@ -1,6 +1,6 @@
from playwright.sync_api import BrowserContext, expect from playwright.sync_api import BrowserContext, expect
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
def test_wordpress(admin_session: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): def test_wordpress(admin_session: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager):

View file

@ -1,6 +1,7 @@
# %% # %%
import email import email
import json import json
import os
from email.header import decode_header from email.header import decode_header
from imaplib import IMAP4, IMAP4_SSL from imaplib import IMAP4, IMAP4_SSL
from pathlib import Path from pathlib import Path
@ -11,19 +12,20 @@ cred_file = Path("../credentials.json")
with open(cred_file, "r") as f: with open(cred_file, "r") as f:
CREDENTIALS = json.load(f) CREDENTIALS = json.load(f)
username = CREDENTIALS["imap_user"] for key, value in CREDENTIALS.items():
password = CREDENTIALS["imap_pass"] os.environ[key] = value
IMAP_HOST = os.environ["IMAP_HOST"]
IMAP_PORT = os.environ["IMAP_PORT"]
IMAP_USER = os.environ["IMAP_USER"]
IMAP_PASS = os.environ["IMAP_PASS"]
# ----------------------------------- imap ----------------------------------- # # ----------------------------------- imap ----------------------------------- #
host = "mail.local-it.org"
imap_port = 143
imap_ssl_port = 993
with IMAP4_SSL(host=IMAP_HOST) as imap_server:
with IMAP4_SSL(host=host) as imap_server: imap_server.login(IMAP_USER, IMAP_PASS)
imap_server.login(username, password)
imap_server.select("INBOX") imap_server.select("INBOX")
# Search for all emails in the folder # Search for all emails in the folder

View file

@ -0,0 +1,53 @@
# %%
import datetime
import json
import os
from pathlib import Path
from imbox import Imbox
cred_file = Path("../credentials.json")
with open(cred_file, "r") as f:
CREDENTIALS = json.load(f)
for key, value in CREDENTIALS.items():
os.environ[key] = value
IMAP_HOST = os.environ["IMAP_HOST"]
IMAP_PORT = os.environ["IMAP_PORT"]
IMAP_USER = os.environ["IMAP_USER"]
IMAP_PASS = os.environ["IMAP_PASS"]
with Imbox(
hostname=os.environ["IMAP_HOST"],
port=os.environ["IMAP_PORT"],
username=os.environ["IMAP_USER"],
password=os.environ["IMAP_PASS"],
ssl=True,
ssl_context=None,
starttls=False,
) as imbox:
# Get all folders
status, folders_with_additional_info = imbox.folders()
# Gets all messages from the inbox
all_inbox_messages = imbox.messages()
# Messages received after specific date
inbox_messages_received_after = imbox.messages(date__gt=datetime.date(2018, 7, 30))
# Messages whose subjects contain a string
inbox_messages_subject_christmas = imbox.messages(subject="Christmas")
for uid, message in all_inbox_messages:
print(uid, message.subject, message.date)
# # Every message is an object with the following keys
# message.sent_from
# message.sent_to
# message.subject
# message.headers
# message.message_id
# message.date
# message.body.plain

View file

@ -1,10 +0,0 @@
# %%
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
string = "blog.dev.local-it.cloud"
parsed_url = urlparse(string, scheme="https")
print(parsed_url)
print(urlunparse(parsed_url))

View file

@ -1,6 +1,6 @@
[project] [project]
name = "locit-testing" name = "abratest"
version = "0.1.0" version = "0.2.0"
requires-python = "~=3.11" requires-python = "~=3.11"
dependencies = [ dependencies = [
"pytest == 7.4.3", "pytest == 7.4.3",
@ -8,11 +8,11 @@ dependencies = [
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"ruff >= 0.1.3", "ruff >= 0.1.7",
] ]
[tool.setuptools] [tool.setuptools]
package-dir = {"" = "src"} package-dir = {"" = "abratest"}
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
@ -20,4 +20,4 @@ target-version = "py311"
[tool.pytest.ini_options] [tool.pytest.ini_options]
python_functions = "test_* setup_*" python_functions = "test_* setup_*"
norecursedirs = "previous-work src" norecursedirs = "previous-work recipes"

View file

@ -4,7 +4,7 @@ import pytest
from dotenv import dotenv_values from dotenv import dotenv_values
from playwright.sync_api import BrowserContext, Page from playwright.sync_api import BrowserContext, Page
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
@pytest.fixture @pytest.fixture

View file

@ -1,4 +1,4 @@
from src.runner import Runner, Test from abratest.runner import Runner, Test
def condition_always_true(dotenv_config: dict[str, str]) -> bool: def condition_always_true(dotenv_config: dict[str, str]) -> bool:

View file

@ -4,7 +4,8 @@ import re
from playwright.sync_api import BrowserContext, expect from playwright.sync_api import BrowserContext, expect
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
from abratest.utils import BaseUrl
ADMIN_USER = os.environ["ADMIN_USER"] ADMIN_USER = os.environ["ADMIN_USER"]
ADMIN_PASS = os.environ["ADMIN_PASS"] ADMIN_PASS = os.environ["ADMIN_PASS"]
@ -31,14 +32,13 @@ def setup_admin_state(context: BrowserContext, dotenv_config: dict[str, str], DI
expect(page.locator("ak-library")).to_be_visible() expect(page.locator("ak-library")).to_be_visible()
# save state # save state
context.storage_state(path=f"{DIR.STATES}/authentik_admin_state.json") context.storage_state(path=DIR.STATES / "authentik_admin_state.json")
def check_if_user_exists(admin_context: BrowserContext, dotenv_config: dict[str, str]): def check_if_user_exists(admin_context: BrowserContext, dotenv_config: dict[str, str], URL: BaseUrl):
# go to admin page # go to admin page
page = admin_context.new_page() page = admin_context.new_page()
url = "https://" + dotenv_config["DOMAIN"] page.goto(URL.get())
page.goto(url)
page.get_by_role("link", name="Admin Interface").click() page.get_by_role("link", name="Admin Interface").click()
nav = page.locator("ak-sidebar-item", has_text=re.compile(r"Directory|Verzeichnis")) nav = page.locator("ak-sidebar-item", has_text=re.compile(r"Directory|Verzeichnis"))
nav.click() nav.click()
@ -49,11 +49,10 @@ def check_if_user_exists(admin_context: BrowserContext, dotenv_config: dict[str,
return user.is_visible() return user.is_visible()
def create_invite_link(admin_context: BrowserContext, dotenv_config: dict[str, str]): def create_invite_link(admin_context: BrowserContext, dotenv_config: dict[str, str], URL: BaseUrl):
# go to admin page # go to admin page
page = admin_context.new_page() page = admin_context.new_page()
url = "https://" + dotenv_config["DOMAIN"] page.goto(URL.get())
page.goto(url)
page.get_by_role("link", name="Admin Interface").click() page.get_by_role("link", name="Admin Interface").click()
nav = page.locator("ak-sidebar-item", has_text=re.compile(r"Directory|Verzeichnis")) nav = page.locator("ak-sidebar-item", has_text=re.compile(r"Directory|Verzeichnis"))
@ -99,21 +98,21 @@ def create_user(user_context: BrowserContext, invitelink):
expect(page.locator("ak-library")).to_be_visible() expect(page.locator("ak-library")).to_be_visible()
def setup_user_state(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): def setup_user_state(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager, URL: BaseUrl):
# load admin cookies # load admin cookies to context
state_file = DIR.STATES / "authentik_admin_state.json" state_file = DIR.STATES / "authentik_admin_state.json"
storage_state = json.loads(state_file.read_bytes()) storage_state = json.loads(state_file.read_bytes())
context.add_cookies(storage_state["cookies"]) context.add_cookies(storage_state["cookies"])
if check_if_user_exists(context, dotenv_config): if check_if_user_exists(context, dotenv_config, URL):
# just login with user # just login with user
pass pass
context.clear_cookies() context.clear_cookies()
else: else:
# get invite_link # get invite_link
invite_link = create_invite_link(context, dotenv_config) invite_link = create_invite_link(context, dotenv_config, URL)
# create user # create user
context.clear_cookies() context.clear_cookies()
create_user(context, invite_link) create_user(context, invite_link)
context.storage_state(path=f"{DIR.STATES}/authentik_user_state.json") context.storage_state(path=DIR.STATES / "authentik_user_state.json")

View file

View file

@ -5,7 +5,7 @@ depend on [demo]. For this to work
1. the Runner class of the other test needs to define the depencency as seen 1. the Runner class of the other test needs to define the depencency as seen
by referencing RunnerDemo in the dependencies list: by referencing RunnerDemo in the dependencies list:
from src.tests_demo.runner_demo import RunnerDemo from abratest.tests_demo.runner_demo import RunnerDemo
class RunnerOther(Runner): class RunnerOther(Runner):
dependencies = [RunnerDemo] dependencies = [RunnerDemo]
@ -15,7 +15,7 @@ class RunnerOther(Runner):
To globally import for all tests in 'other', the import should be done in conftest: To globally import for all tests in 'other', the import should be done in conftest:
in 'conftest.py' in 'test_other' dir: in 'conftest.py' in 'test_other' dir:
from src.tests_demo.fixtures_demo import demo_fixture from abratest.tests_demo.fixtures_demo import demo_fixture
""" """
import pytest import pytest

View file

@ -1,5 +1,4 @@
from src.runner import Runner, Test from abratest.runner import Runner, Test
from src.tests_authentik.runner_authentik import RunnerAuthentik
class RunnerDemo(Runner): class RunnerDemo(Runner):
@ -12,7 +11,7 @@ class RunnerDemo(Runner):
# RunnerDemo will only execute, when setup_authentik.py has finished successfully. # RunnerDemo will only execute, when setup_authentik.py has finished successfully.
# For example, setup_authentik.py generates session states, that can be used as fixtures # For example, setup_authentik.py generates session states, that can be used as fixtures
# that can be loaded from fixtures_authentik.py # that can be loaded from fixtures_authentik.py
dependencies: list[type["Runner"]] = [RunnerAuthentik] dependencies: list[str] = ["authentik"]
# todo: update these comments # todo: update these comments
# Filename of Demo setup. If defined, it will run 1st by executing pytest # Filename of Demo setup. If defined, it will run 1st by executing pytest

View file

@ -0,0 +1,33 @@
import json
import os
import pytest
from playwright.sync_api import BrowserContext, Page
from abratest.dir_manager import DirManager
from abratest.utils import BaseUrl
pytest_plugins = "tests_authentik.fixtures_authentik"
NEXTCLOUD_DEMO_USER = {
"NEXTCLOUD_USER": "next_demo_user",
"NEXTCLOUD_PASS": "P@ss.123",
}
for key, value in NEXTCLOUD_DEMO_USER.items():
os.environ[key] = value
@pytest.fixture
def nextcloud_admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext:
state_file = DIR.STATES / "nextcloud_admin_state.json"
storage_state = json.loads(state_file.read_bytes())
context.add_cookies(storage_state["cookies"])
return context
@pytest.fixture
def nextcloud_admin_page(nextcloud_admin_context: BrowserContext, DIR: DirManager, URL: BaseUrl) -> Page:
page = nextcloud_admin_context.new_page()
page.goto(URL.get("/apps/files"))
return page

View file

@ -0,0 +1,17 @@
from abratest.runner import Runner, Test
def condition_always_false(dotenv_config: dict[str, str]) -> bool:
return False
class RunnerNextcloud(Runner):
name: str = "nextcloud"
test_dir_name: str = "tests_nextcloud"
dependencies = ["authentik"]
setups = [Test(test_file="setup_nextcloud.py", prevent_skip=False)]
tests = [
Test(test_file="tests_nextcloud.py", prevent_skip=True),
# Test(condition=condition_always_false, test_file="tests_nextcloud_onlyoffice.py"),
]
# cleanups = [Test(test_file="cleanup_nextcloud.py")]

View file

@ -0,0 +1,21 @@
from playwright.sync_api import Page, expect
from abratest.dir_manager import DirManager
from abratest.utils import BaseUrl
# url dashboard
# https://files.test.dev.local-it.cloud/apps/dashboard/
# url files
# https://files.test.dev.local-it.cloud/apps/files/
def setup_nextcloud_admin_session(authentik_admin_page: Page, DIR: DirManager, URL: BaseUrl):
"""visit nextcloud from authentik with admin_session to create wordpress_admin_session"""
with authentik_admin_page.expect_popup() as event_context:
authentik_admin_page.get_by_role("link", name="Nextcloud").click()
page_nextcloud = event_context.value
context = page_nextcloud.context
page_nextcloud.goto(URL.get("/apps/files"))
expect(page_nextcloud.get_by_role("link", name="Name")).to_be_visible()
context.storage_state(path=DIR.STATES / "nextcloud_admin_state.json")

View file

@ -0,0 +1,32 @@
import re
import pytest
from playwright.sync_api import Page, expect
def test_nextcloud_quota(nextcloud_admin_page: Page, dotenv_config: dict[str, str]):
"""Test Nextcloud"""
if dotenv_config.get("DEFAULT_QUOTA"):
# get quota from website
quota_string = nextcloud_admin_page.get_by_text(
re.compile(r"\d*,\d .* \d*,\d")
).inner_text() # "37,7 MB von 104,9 MB verwendet"
out = re.search(r"\d*,\d .* (\d*,\d).", quota_string)
out_number = out[1] # 104,9
out_number = out_number.replace(",", ".")
quota_website = float(out_number)
# get quota from env
quota_config_string = dotenv_config["DEFAULT_QUOTA"] # "100 MB"
assert "MB" in quota_config_string
quota_config = float(quota_config_string.strip("MB"))
assert quota_website == pytest.approx(quota_config, rel=0.1) # within 10%
else:
pytest.skip("DEFAULT_QUOTA not defined in env file")
@pytest.mark.skip
def test_nextcloud_apps(nextcloud_admin_page: Page, dotenv_config: dict[str, str]):
for app in dotenv_config["nc_apps"]:
expect(nextcloud_admin_page.get_by_role("link", name=app)).to_be_visible()

View file

@ -4,16 +4,9 @@ import pytest
from dotenv import dotenv_values from dotenv import dotenv_values
from playwright.sync_api import BrowserContext, Page from playwright.sync_api import BrowserContext, Page
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
# from src.tests_authentik.fixtures_authentik import ( pytest_plugins = "tests_authentik.fixtures_authentik"
# authentik_admin_context,
# authentik_admin_page,
# authentik_user_context,
# authentik_user_page,
# )
pytest_plugins = "src.tests_authentik.fixtures_authentik"
@pytest.fixture @pytest.fixture

View file

@ -1,5 +1,4 @@
from src.runner import Runner, Test from abratest.runner import Runner, Test
from src.tests_authentik.runner_authentik import RunnerAuthentik
def condition_always_true(dotenv_config: dict[str, str]) -> bool: def condition_always_true(dotenv_config: dict[str, str]) -> bool:
@ -20,7 +19,7 @@ def condition_has_locale(dotenv_config: dict[str, str]) -> bool:
class RunnerWordpress(Runner): class RunnerWordpress(Runner):
name = "wordpress" name = "wordpress"
test_dir_name = "tests_wordpress" test_dir_name = "tests_wordpress"
dependencies: list[type[Runner]] = [RunnerAuthentik] dependencies = ["authentik"]
setups = [Test(test_file="setup_wordpress.py")] setups = [Test(test_file="setup_wordpress.py")]
tests = [ tests = [
Test(test_file="test_wordpress.py"), Test(test_file="test_wordpress.py"),

View file

@ -1,7 +1,7 @@
import pytest import pytest
from playwright.sync_api import BrowserContext, Page, expect from playwright.sync_api import BrowserContext, Page, expect
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
def test_visit_from_domain(authentik_admin_context: BrowserContext, dotenv_config: dict[str, str]): def test_visit_from_domain(authentik_admin_context: BrowserContext, dotenv_config: dict[str, str]):
@ -25,4 +25,4 @@ def setup_wordpress_admin_session(authentik_admin_page: Page, DIR: DirManager):
expect(page_wordpress.locator("#wpadminbar")).to_be_visible() expect(page_wordpress.locator("#wpadminbar")).to_be_visible()
# save session # save session
context = page_wordpress.context context = page_wordpress.context
context.storage_state(path=f"{DIR.STATES}/wordpress_admin_state.json") context.storage_state(path=DIR.STATES / "wordpress_admin_state.json")

View file

@ -2,7 +2,7 @@
from playwright.sync_api import BrowserContext, expect from playwright.sync_api import BrowserContext, expect
from src.dir_manager import DirManager from abratest.dir_manager import DirManager
def test_welcome_message(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): def test_welcome_message(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager):

View file

@ -4,4 +4,5 @@ pytest-playwright
python-dotenv python-dotenv
icecream icecream
loguru loguru
beautifulsoup4 beautifulsoup4
imbox

View file

@ -1,16 +0,0 @@
from typing import TYPE_CHECKING
from src.tests_authentik.runner_authentik import RunnerAuthentik
from src.tests_nextcloud.runner_nextcloud import RunnerNextcloud
from src.tests_wordpress.runner_wordpress import RunnerWordpress
if TYPE_CHECKING:
from src.runner import Runner
# Register all runners here. Each .env file with TYPE=authentik will be run with RunnerAuthentik
RUNNER_DICT: dict[str, type["Runner"]] = {
"authentik": RunnerAuthentik,
"wordpress": RunnerWordpress,
"nextcloud": RunnerNextcloud,
}

View file

@ -1,16 +0,0 @@
import os
from src.tests_authentik.fixtures_authentik import (
authentik_admin_context,
authentik_admin_page,
authentik_user_context,
authentik_user_page,
)
NEXTCLOUD_DEMO_USER = {
"NEXTCLOUD_USER": "next_demo_user",
"NEXTCLOUD_PASS": "P@ss.123",
}
for key, value in NEXTCLOUD_DEMO_USER.items():
os.environ[key] = value

View file

@ -1,18 +0,0 @@
from src.runner import Runner, Test
from src.tests_authentik.runner_authentik import RunnerAuthentik
def condition_always_false(dotenv_config: dict[str, str]) -> bool:
return False
class RunnerNextcloud(Runner):
name: str = "nextcloud"
test_dir_name: str = "tests_nextcloud"
dependencies = [RunnerAuthentik]
setups = [Test(test_file="setup_nextcloud.py")]
tests = [
Test(test_file="tests_nextcloud.py"),
Test(condition=condition_always_false, test_file="tests_nextcloud_onlyoffice.py"),
]
# cleanups = [Test(test_file="cleanup_nextcloud.py")]

View file

@ -1,35 +0,0 @@
import pytest
@pytest.fixture(scope="session", autouse=True)
def nc_login(browser: Browser):
"""Nextcloud Login"""
context = setup_context(browser, f"{STATES}/user_state.json")
page = context.new_page()
page.goto(CONFIG["domain"])
with page.expect_popup() as nextcloud_info:
link = page.get_by_role("link", name="Nextcloud")
CONFIG["nc_domain"] = link.get_attribute("href")
link.click()
nextcloud = nextcloud_info.value
check_for(nextcloud.get_by_role("link", name="Name"))
if nextcloud.query_selector(".close-icon"):
close_button = nextcloud.get_by_role("button", name="Close modal")
close_button.click()
expect(close_button).to_be_hidden()
nextcloud.wait_for_timeout(2000)
context.storage_state(path=f"{STATES}/nc_user_state.json")
context.tracing.stop(path=f"{RECORDS}/nextcloud_login_user.zip")
context.close()
@pytest.fixture
def nc_session(browser: Browser):
"""Reuse Nextcloud User Session"""
context = setup_context(browser, f"{STATES}/nc_user_state.json")
page = context.new_page()
page.goto(CONFIG["nc_domain"])
if page.query_selector(".close-icon"):
page.get_by_role("button", name="Close modal").click()
yield context, page
context.close()

View file

@ -1,13 +0,0 @@
def test_nextcloud(nc_session):
"""Test Nextcloud"""
context, page = nc_session
# if page.query_selector('.close-icon'):
# page.get_by_role("button", name="Close modal").click()
if CONFIG.get("default_quota"):
quota = int(
page.get_by_role("listitem", name="Storage informations").get_by_role("link").inner_text().split()[3]
)
assert quota == CONFIG["default_quota"]
for app in CONFIG["nc_apps"]:
check_for(page.get_by_role("link", name=app))
context.tracing.stop(path=f"{RECORDS}/nextcloud.zip")

View file

@ -2,8 +2,11 @@ from pathlib import Path
import pytest import pytest
# from src.env_file_helper import DependencyRule, EnvFile, sort_env_files_by_rule from abratest.coordinator import Coordinator
from src.env_manager import DependencyRule, EnvFile, EnvManager from abratest.env_manager import DependencyRule, EnvFile, EnvManager
RECIPES_DIR = Path("./recipes").resolve()
RUNNER_DICT = Coordinator.create_runner_dict(RECIPES_DIR)
def test_complex_sorting() -> None: def test_complex_sorting() -> None:
@ -46,7 +49,7 @@ def test_real_env_files() -> None:
Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik
] ]
env_files: list[EnvFile] = EnvManager._get_env_files(ENV_FILES) env_files: list[EnvFile] = EnvManager._get_env_files(ENV_FILES)
dependency_rules: list[DependencyRule] = EnvManager._get_dependency_rules(env_files) dependency_rules: list[DependencyRule] = EnvManager._get_dependency_rules(env_files, RUNNER_DICT)
sorted_env_files = EnvManager.sort_env_files_by_rule(env_files, dependency_rules) sorted_env_files = EnvManager.sort_env_files_by_rule(env_files, dependency_rules)
assert sorted_env_files[0].env_type == "authentik" assert sorted_env_files[0].env_type == "authentik"
@ -60,7 +63,7 @@ def test_real_env_files_duplicate() -> None:
Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik
] ]
env_files: list[EnvFile] = EnvManager._get_env_files(ENV_FILES) env_files: list[EnvFile] = EnvManager._get_env_files(ENV_FILES)
dependency_rules: list[DependencyRule] = EnvManager._get_dependency_rules(env_files) dependency_rules: list[DependencyRule] = EnvManager._get_dependency_rules(env_files, RUNNER_DICT)
sorted_env_files = EnvManager.sort_env_files_by_rule(env_files, dependency_rules) sorted_env_files = EnvManager.sort_env_files_by_rule(env_files, dependency_rules)
assert sorted_env_files[0].env_type == "authentik" assert sorted_env_files[0].env_type == "authentik"
assert sorted_env_files[1].env_type == "authentik" assert sorted_env_files[1].env_type == "authentik"
@ -79,7 +82,7 @@ def test_real_env_files_duplicate_six() -> None:
Path("envfiles/blog.test.dev.local-it.cloud.env"), # wordpress Path("envfiles/blog.test.dev.local-it.cloud.env"), # wordpress
] ]
env_files: list[EnvFile] = EnvManager._get_env_files(ENV_FILES) env_files: list[EnvFile] = EnvManager._get_env_files(ENV_FILES)
dependency_rules: list[DependencyRule] = EnvManager._get_dependency_rules(env_files) dependency_rules: list[DependencyRule] = EnvManager._get_dependency_rules(env_files, RUNNER_DICT)
sorted_env_files = EnvManager.sort_env_files_by_rule(env_files, dependency_rules) sorted_env_files = EnvManager.sort_env_files_by_rule(env_files, dependency_rules)
assert sorted_env_files[0].env_type == "authentik" assert sorted_env_files[0].env_type == "authentik"
assert sorted_env_files[1].env_type == "authentik" assert sorted_env_files[1].env_type == "authentik"
@ -95,7 +98,7 @@ def test_env_manager() -> None:
Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik
Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik
] ]
ENV = EnvManager(env_paths_list) ENV = EnvManager(env_paths_list, RUNNER_DICT)
assert ENV.env_files[0].env_type == "authentik" assert ENV.env_files[0].env_type == "authentik"
assert ENV.env_files[1].env_type == "authentik" assert ENV.env_files[1].env_type == "authentik"
assert ENV.env_files[2].env_type == "wordpress" assert ENV.env_files[2].env_type == "wordpress"

28
tests/test_url.py Normal file
View file

@ -0,0 +1,28 @@
from abratest.utils import BaseUrl
url_input = {
"netloc": "blog.dev.local-it.cloud",
"scheme": "https",
}
url_obj = BaseUrl(**url_input)
def test_urllib_domain_only():
assert url_obj.get() == "https://blog.dev.local-it.cloud"
def test_urllib_path_single():
assert url_obj.get(path="something") == "https://blog.dev.local-it.cloud/something"
def test_urllib_path_double():
assert url_obj.get(path="something/else") == "https://blog.dev.local-it.cloud/something/else"
def test_urllib_path_signle_suc_slash():
assert url_obj.get(path="something/else/") == "https://blog.dev.local-it.cloud/something/else/"
def test_urllib_path_signle_pre_slash():
assert url_obj.get(path="/something/else") == "https://blog.dev.local-it.cloud/something/else"