refactor for independent test dirs (#7)

* make it so that the actual tests can be moved anywhere, for example in abra recipe repos
-> major refactoring with pytest test discovery magic

* create RUNNER_DICT dynamically with importlib
-> none of the tests are hardcoded, more tests can be added by placing a folder

* autoload fixtures with pytest plugins

* add URL fixture to navigate on web pages. Includes url parser based on python urllib to generate correct links

* fix nextcloud setups and tests

*  add email groundwork with imbox

Reviewed-on: local-it-infrastructure/e2e_tests#7
Co-authored-by: Daniel <d.brummerloh@gmail.com>
Co-committed-by: Daniel <d.brummerloh@gmail.com>
This commit is contained in:
Daniel 2023-12-05 21:41:43 +01:00 committed by dan
parent 3fa10aaa69
commit f9c21c6e6b
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:
@ -8,7 +15,11 @@ git submodule update --init // add submodule after normal cloning
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
@ -22,16 +33,16 @@ playwright install
Run the script with
```bash
python main.py
python main.py # run abratest
pytest # test abratest
```
# Run with Docker
# 2.2 Run with Docker
```bash
docker compose build
docker compose run --rm app python ./main.py
docker compose run --rm app pytest
# docker-compose up
docker compose build # build the image
docker compose run --rm app python ./main.py # run AbraTest
docker compose run --rm app pytest # test AbraTest
```
Force rebuild with cache
@ -46,7 +57,7 @@ Force rebuild wtihtout cache
docker-compose build --no-cache
```
# Codegen
## 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 loguru import logger
from src.dir_manager import DirManager
from src.env_manager import EnvFile, EnvManager
from src.html_helper import merge_html_files
from src.runner import Runner
from src.runner_dict import RUNNER_DICT
from src.utils import rmtree
from abratest.dir_manager import DirManager
from abratest.env_manager import EnvFile, EnvManager
from abratest.html_helper import merge_html_files
from abratest.runner import Runner
from abratest.utils import rmtree
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
out_string = "".join([e.name + "\n" for e in env_paths_list])
out_string += f"output_dir = {output_dir}\n"
out_string += f"session_id = {session_id}"
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.ENV = EnvManager(env_paths_list)
self.RUNNER_DICT = self.create_runner_dict(recipes_dir)
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:
logger.info("calling setup_test()")
@ -39,14 +41,15 @@ class Coordinator:
def _load_runners(self, env_files: list[EnvFile]) -> list[Runner]:
"""Creates an instance of the correct Runner class for each given env file"""
runners = []
runners: list[Runner] = []
for env_file in env_files:
RunnerClass = RUNNER_DICT[env_file.config["TYPE"]]
runners.append(
RunnerClass(
dotenv_path=env_file.env_path, output_dir=self.DIR.output_dir, session_id=self.DIR.session_id
)
)
RunnerClass = self.RUNNER_DICT[env_file.config["TYPE"]]
dependency_classes: list[type[Runner]] = []
for dependency in RunnerClass.dependencies:
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
def combine_html(self) -> None:
@ -78,3 +81,28 @@ class Coordinator:
new_path = get_new_path(self.DIR.RECORDS, f.parent.name)
f.parent.rename(new_path)
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):
# root test dir
def __init__(self, output_dir: Path | str, session_id: str, recipes_dir: Path | str = ""):
if isinstance(output_dir, str):
output_dir = Path(output_dir)
self.output_dir = output_dir.resolve()
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:
dirs: list[Path] = [
@ -63,3 +65,7 @@ class DirManager:
@property
def RESULTS(self):
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 src.dir_manager import DirManager
from src.runner_dict import RUNNER_DICT
from abratest.dir_manager import DirManager
from abratest.runner import Runner
class EnvFile(NamedTuple):
@ -23,9 +23,9 @@ class DependencyRule(NamedTuple):
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.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)
@staticmethod
@ -41,12 +41,12 @@ class EnvManager:
return env_files
@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] = []
for env_file in env_files:
child_runner_class = RUNNER_DICT[env_file.env_type]
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)
return dependency_rules

View file

@ -14,11 +14,12 @@ from dotenv import dotenv_values
from playwright.sync_api import BrowserContext, expect
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
LOCALE = {"Accept-Language": "de_DE"}
TIMEOUT = 7_000
TIMEOUT = 20_000
expect.set_options(timeout=TIMEOUT)
@ -73,6 +74,11 @@ def dotenv_config(request) -> dict[str, str]:
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")
def imap_ssl_email_client() -> None:
assert os.environ["IMAP_HOST"]

View file

@ -6,7 +6,7 @@ import pytest
from dotenv import dotenv_values
from loguru import logger
from src.dir_manager import DirManager
from abratest.dir_manager import DirManager
@dataclass
@ -22,15 +22,13 @@ class Runner:
setups: list[Test] = []
tests: list[Test] = []
cleanups: list[Test] = []
dependencies: list[type["Runner"]] = []
prevent_skip = False
dependencies: list[str] = []
_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.config: dict[str, str] = dotenv_values(dotenv_path) # type: ignore
self.output_dir = output_dir
self.session_id = session_id
self.DIRS = DirManager(output_dir, session_id)
self.DIR = DIR
logger.info(f"creating instance of {self.__class__.__name__}")
assert self.test_dir_name
@ -66,7 +64,7 @@ class Runner:
# condition_met: true / false
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
if self._is_test_passed(identifier_string, remove_existing=True):
@ -83,7 +81,7 @@ class Runner:
# test condition is undefined or not met
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)
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"""
already_passed = False
for result in self.DIRS.RESULTS.glob("*"):
for result in self.DIR.RESULTS.glob("*"):
if identifier_string in result.name:
# process any result file (passed / failed / skipped) if it exists
if "passed" in result.name:
@ -112,6 +110,8 @@ class Runner:
command_arguments = []
# command_arguments.append("--traceconfig")
command_arguments.append("-v")
# command_arguments.append("-rx")
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
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(self.session_id)
command_arguments.append(self.DIR.session_id)
# artifacts dir from pytest
# warning: https://github.com/microsoft/playwright-pytest/issues/111
# --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
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
command_arguments.append("--tracing")
@ -145,7 +145,7 @@ class Runner:
# command_arguments.append("--headed")
# 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)
@ -157,7 +157,7 @@ class Runner:
"""create result file to indicated passed/failed or skipped test"""
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 _:
pass # create empty file
@ -166,11 +166,11 @@ class Runner:
# 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 = []
for dependencie_runner in self.dependencies:
for setup_name in dependencie_runner.setups:
dependencie_identifier = self.combine_names(dependencie_runner.name, setup_name.test_file)
for dependency_runner in self._dependency_runners:
for setup_name in dependency_runner.setups:
dependencie_identifier = self.combine_names(dependency_runner.name, setup_name.test_file)
results.append(any(dependencie_identifier in f for f in passed_tests))
return all(results)

View file

@ -1,5 +1,20 @@
from dataclasses import dataclass
from datetime import datetime
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:

27
main.py
View file

@ -4,9 +4,9 @@ from pathlib import Path
from loguru import logger
from src.coordinator import Coordinator
from src.dir_manager import DirManager
from src.utils import get_session_id
from abratest.coordinator import Coordinator
from abratest.dir_manager import DirManager
from abratest.utils import get_session_id
# ----------------------------- lookup env files ----------------------------- #
@ -23,6 +23,7 @@ from src.utils import get_session_id
ENV_FILES = [
Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik
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()
# -------------------------- enable playwright debug ------------------------- #
# os.environ["PWDEBUG"] = "1"
RECIPES_DIR = Path("./recipes").resolve()
# --------------------- load credentials to env variables -------------------- #
@ -49,6 +45,14 @@ for key, value in CREDENTIALS.items():
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 ---------------------------- #
@ -58,6 +62,7 @@ session_id = get_session_id()
# ------------------------------- setup logging ------------------------------ #
DIR = DirManager(output_dir=OUTPUT_DIR, session_id=session_id)
log_file = DIR.RECORDS / "coordinator.log"
logger.add(log_file)
@ -66,7 +71,9 @@ logger.add(log_file)
# ---------------------------- 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.run_test()
coordinator.combine_html()

View file

@ -1,6 +1,6 @@
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):

View file

@ -1,6 +1,7 @@
# %%
import email
import json
import os
from email.header import decode_header
from imaplib import IMAP4, IMAP4_SSL
from pathlib import Path
@ -11,19 +12,20 @@ cred_file = Path("../credentials.json")
with open(cred_file, "r") as f:
CREDENTIALS = json.load(f)
username = CREDENTIALS["imap_user"]
password = CREDENTIALS["imap_pass"]
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"]
# ----------------------------------- imap ----------------------------------- #
host = "mail.local-it.org"
imap_port = 143
imap_ssl_port = 993
with IMAP4_SSL(host=host) as imap_server:
imap_server.login(username, password)
with IMAP4_SSL(host=IMAP_HOST) as imap_server:
imap_server.login(IMAP_USER, IMAP_PASS)
imap_server.select("INBOX")
# 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]
name = "locit-testing"
version = "0.1.0"
name = "abratest"
version = "0.2.0"
requires-python = "~=3.11"
dependencies = [
"pytest == 7.4.3",
@ -8,11 +8,11 @@ dependencies = [
[project.optional-dependencies]
dev = [
"ruff >= 0.1.3",
"ruff >= 0.1.7",
]
[tool.setuptools]
package-dir = {"" = "src"}
package-dir = {"" = "abratest"}
[tool.ruff]
line-length = 120
@ -20,4 +20,4 @@ target-version = "py311"
[tool.pytest.ini_options]
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 playwright.sync_api import BrowserContext, Page
from src.dir_manager import DirManager
from abratest.dir_manager import DirManager
@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:

View file

@ -4,7 +4,8 @@ import re
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_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()
# 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
page = admin_context.new_page()
url = "https://" + dotenv_config["DOMAIN"]
page.goto(url)
page.goto(URL.get())
page.get_by_role("link", name="Admin Interface").click()
nav = page.locator("ak-sidebar-item", has_text=re.compile(r"Directory|Verzeichnis"))
nav.click()
@ -49,11 +49,10 @@ def check_if_user_exists(admin_context: BrowserContext, dotenv_config: dict[str,
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
page = admin_context.new_page()
url = "https://" + dotenv_config["DOMAIN"]
page.goto(url)
page.goto(URL.get())
page.get_by_role("link", name="Admin Interface").click()
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()
def setup_user_state(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager):
# load admin cookies
def setup_user_state(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager, URL: BaseUrl):
# load admin cookies to context
state_file = DIR.STATES / "authentik_admin_state.json"
storage_state = json.loads(state_file.read_bytes())
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
pass
context.clear_cookies()
else:
# get invite_link
invite_link = create_invite_link(context, dotenv_config)
invite_link = create_invite_link(context, dotenv_config, URL)
# create user
context.clear_cookies()
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
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):
dependencies = [RunnerDemo]
@ -15,7 +15,7 @@ class RunnerOther(Runner):
To globally import for all tests in 'other', the import should be done in conftest:
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

View file

@ -1,5 +1,4 @@
from src.runner import Runner, Test
from src.tests_authentik.runner_authentik import RunnerAuthentik
from abratest.runner import Runner, Test
class RunnerDemo(Runner):
@ -12,7 +11,7 @@ class RunnerDemo(Runner):
# 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
# that can be loaded from fixtures_authentik.py
dependencies: list[type["Runner"]] = [RunnerAuthentik]
dependencies: list[str] = ["authentik"]
# todo: update these comments
# 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 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 (
# authentik_admin_context,
# authentik_admin_page,
# authentik_user_context,
# authentik_user_page,
# )
pytest_plugins = "src.tests_authentik.fixtures_authentik"
pytest_plugins = "tests_authentik.fixtures_authentik"
@pytest.fixture

View file

@ -1,5 +1,4 @@
from src.runner import Runner, Test
from src.tests_authentik.runner_authentik import RunnerAuthentik
from abratest.runner import Runner, Test
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):
name = "wordpress"
test_dir_name = "tests_wordpress"
dependencies: list[type[Runner]] = [RunnerAuthentik]
dependencies = ["authentik"]
setups = [Test(test_file="setup_wordpress.py")]
tests = [
Test(test_file="test_wordpress.py"),

View file

@ -1,7 +1,7 @@
import pytest
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]):
@ -25,4 +25,4 @@ def setup_wordpress_admin_session(authentik_admin_page: Page, DIR: DirManager):
expect(page_wordpress.locator("#wpadminbar")).to_be_visible()
# save session
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 src.dir_manager import DirManager
from abratest.dir_manager import DirManager
def test_welcome_message(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager):

View file

@ -4,4 +4,5 @@ pytest-playwright
python-dotenv
icecream
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
# from src.env_file_helper import DependencyRule, EnvFile, sort_env_files_by_rule
from src.env_manager import DependencyRule, EnvFile, EnvManager
from abratest.coordinator import Coordinator
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:
@ -46,7 +49,7 @@ def test_real_env_files() -> None:
Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik
]
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)
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
]
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)
assert sorted_env_files[0].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
]
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)
assert sorted_env_files[0].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
]
ENV = EnvManager(env_paths_list)
ENV = EnvManager(env_paths_list, RUNNER_DICT)
assert ENV.env_files[0].env_type == "authentik"
assert ENV.env_files[1].env_type == "authentik"
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"