refactoring (#13)
* general project refactoring * various small improvements * improve imap fixture with helper functions and typing * add wordpress send email setup * add wordpress receive email test * add various documentation Reviewed-on: local-it-infrastructure/e2e_tests#13 Co-authored-by: Daniel <d.brummerloh@gmail.com> Co-committed-by: Daniel <d.brummerloh@gmail.com>
This commit is contained in:
parent
41a042f07d
commit
d1ff1183a5
29 changed files with 323 additions and 175 deletions
|
|
@ -0,0 +1,13 @@
|
|||
from pytest_abra.coordinator import Coordinator
|
||||
from pytest_abra.dir_manager import DirManager
|
||||
from pytest_abra.runner import ConditionArgs, Runner, Test
|
||||
from pytest_abra.utils import BaseUrl
|
||||
|
||||
__all__ = [
|
||||
"Coordinator",
|
||||
"ConditionArgs",
|
||||
"Runner",
|
||||
"Test",
|
||||
"DirManager",
|
||||
"BaseUrl",
|
||||
]
|
||||
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||
|
||||
from loguru import logger
|
||||
|
||||
from pytest_abra.coordinator import Coordinator
|
||||
from pytest_abra import Coordinator
|
||||
from pytest_abra.dir_manager import DirManager
|
||||
from pytest_abra.utils import get_datetime_string
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ def run():
|
|||
parser.add_argument("--debug", action="store_true", help="Enable Playwright debug mode")
|
||||
parser.add_argument("--resume", action="store_true", help="Re-run the most recent test, skipping passed tests")
|
||||
args = parser.parse_args()
|
||||
ENV_FILES = [Path(s) for s in args.env_paths.split(";")]
|
||||
env_paths = [Path(s) for s in args.env_paths.split(";")]
|
||||
|
||||
# -------------------------- enable playwright debug ------------------------- #
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ def run():
|
|||
# ---------------------------- initialize and run ---------------------------- #
|
||||
|
||||
coordinator = Coordinator(
|
||||
env_paths_list=ENV_FILES,
|
||||
env_paths=env_paths,
|
||||
output_dir=args.output_dir,
|
||||
session_id=session_id,
|
||||
recipes_dir=args.recipes_dir,
|
||||
|
|
|
|||
|
|
@ -15,21 +15,21 @@ from pytest_abra.utils import rmtree
|
|||
class Coordinator:
|
||||
def __init__(
|
||||
self,
|
||||
env_paths_list: list[Path],
|
||||
env_paths: list[Path],
|
||||
output_dir: Path,
|
||||
session_id: str,
|
||||
recipes_dir: Path,
|
||||
timeout: int,
|
||||
) -> None:
|
||||
# logging
|
||||
out_string = "".join([e.name + "\n" for e in env_paths_list])
|
||||
out_string = "".join([e.name + "\n" for e in env_paths])
|
||||
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.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)
|
||||
self.ENV = EnvManager(env_paths=env_paths, RUNNER_DICT=self.RUNNER_DICT)
|
||||
self.TIMEOUT = timeout
|
||||
|
||||
def setup_test(self) -> None:
|
||||
|
|
@ -52,7 +52,7 @@ class Coordinator:
|
|||
"""Creates an instance of the correct Runner class for each given env file"""
|
||||
runners: list[Runner] = []
|
||||
for index, env_file in enumerate(env_files):
|
||||
RunnerClass = self.RUNNER_DICT[env_file.config["TYPE"]]
|
||||
RunnerClass = self.RUNNER_DICT[env_file.env_config["TYPE"]]
|
||||
runners.append(RunnerClass(coordinator=self, runner_index=index))
|
||||
return runners
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@
|
|||
|
||||
import os
|
||||
import re
|
||||
from imaplib import IMAP4_SSL
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Protocol, TypedDict
|
||||
|
||||
import pytest
|
||||
from dotenv import dotenv_values
|
||||
from icecream import ic
|
||||
from imbox import Imbox # type: ignore
|
||||
from playwright.sync_api import BrowserContext, expect
|
||||
from pytest import Parser
|
||||
|
||||
|
|
@ -62,39 +65,90 @@ def DIR(request) -> DirManager:
|
|||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ENV_FILES(DIR: DirManager) -> dict[int, EnvFile]:
|
||||
out: dict[int, EnvFile] = dict()
|
||||
def env_files(DIR: DirManager) -> list[EnvFile]:
|
||||
"""list of EnvFile objects created from the given env files"""
|
||||
|
||||
env_files_dict: dict[int, EnvFile] = dict()
|
||||
for env_path in DIR.ENV_FILES.glob("*.env"):
|
||||
config: dict[str, str] = dotenv_values(env_path) # type: ignore
|
||||
env_type = config["TYPE"]
|
||||
result = re.search(r"(\d+)-*", env_path.name)
|
||||
assert result
|
||||
runner_index = int(result[1])
|
||||
out[runner_index] = EnvFile(env_path=env_path, config=config, env_type=env_type)
|
||||
return out
|
||||
env_files_dict[runner_index] = EnvFile(env_path=env_path, env_config=config, env_type=env_type)
|
||||
keys = list(env_files_dict.keys())
|
||||
keys.sort()
|
||||
return [env_files_dict[key] for key in keys]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def dotenv_config(request, ENV_FILES: dict[int, EnvFile]) -> dict[str, str]:
|
||||
def env_config(request, env_files: list[EnvFile]) -> dict[str, str]:
|
||||
"""Current env_config"""
|
||||
runner_index = request.config.getoption("--runner_index")
|
||||
return ENV_FILES[runner_index].config
|
||||
return env_files[runner_index].env_config
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def URL(dotenv_config: dict[str, str]) -> BaseUrl:
|
||||
return BaseUrl(netloc=dotenv_config["DOMAIN"])
|
||||
def URL(env_config: dict[str, str]) -> BaseUrl:
|
||||
"""BaseUrl object based on current DOMAIN"""
|
||||
return BaseUrl(netloc=env_config["DOMAIN"])
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def imap_ssl_email_client() -> None:
|
||||
def imap_client() -> None:
|
||||
"""imap email client using credentials from environment variables"""
|
||||
|
||||
assert os.environ["IMAP_HOST"]
|
||||
assert os.environ["IMAP_PORT"]
|
||||
assert os.environ["IMAP_USER"]
|
||||
assert os.environ["IMAP_PASS"]
|
||||
port = int(os.environ["IMAP_PORT"])
|
||||
imap_client = IMAP4_SSL(host=os.environ["IMAP_HOST"], port=port)
|
||||
imap_client.login(os.environ["IMAP_USER"], os.environ["IMAP_PASS"])
|
||||
imap_client.select("INBOX")
|
||||
yield imap_client
|
||||
imap_client.close()
|
||||
imap_client.logout()
|
||||
|
||||
imbox = 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,
|
||||
)
|
||||
|
||||
yield imbox
|
||||
|
||||
imbox.logout()
|
||||
|
||||
|
||||
class Body(TypedDict):
|
||||
plain: list
|
||||
html: list
|
||||
|
||||
|
||||
class Message(Protocol):
|
||||
sent_from: list
|
||||
sent_to: list
|
||||
subject: str
|
||||
headers: list
|
||||
date: str
|
||||
body: Body
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def imap_recent_messages(imap_client: Imbox) -> list[Message]:
|
||||
"""Get all messages from [n_minutes] ago till now.
|
||||
|
||||
# iterate with
|
||||
for uid, message in messages:
|
||||
print(uid, message.subject, message.date)"""
|
||||
|
||||
N_MINUTES = 30
|
||||
|
||||
n_minutes_ago = datetime.now() - timedelta(minutes=N_MINUTES)
|
||||
uids: list[bytes] = []
|
||||
messages: list[Message] = []
|
||||
# for uid, message in imap_client.messages(date__gt=n_minutes_ago):
|
||||
for uid, message in imap_client.messages():
|
||||
ic("one time")
|
||||
uids.append(uid)
|
||||
messages.append(message)
|
||||
|
||||
return messages
|
||||
|
|
@ -1 +0,0 @@
|
|||
print("wooooorking")
|
||||
|
|
@ -10,11 +10,11 @@ class DirManager:
|
|||
|
||||
The structures is as follows:
|
||||
tests dir/
|
||||
session_dir-1/
|
||||
session_id-1/
|
||||
records
|
||||
results
|
||||
states
|
||||
session_dir-2/
|
||||
session_id-2/
|
||||
records
|
||||
...
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
from typing import TYPE_CHECKING, NamedTuple
|
||||
|
||||
from dotenv import dotenv_values
|
||||
|
||||
from pytest_abra.dir_manager import DirManager
|
||||
from pytest_abra.runner import Runner
|
||||
if TYPE_CHECKING:
|
||||
from pytest_abra.dir_manager import DirManager
|
||||
from pytest_abra.runner import Runner
|
||||
|
||||
|
||||
class EnvFile(NamedTuple):
|
||||
env_path: Path
|
||||
config: dict[str, str]
|
||||
env_config: dict[str, str]
|
||||
env_type: str
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
@ -23,8 +24,8 @@ class DependencyRule(NamedTuple):
|
|||
|
||||
|
||||
class EnvManager:
|
||||
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)
|
||||
def __init__(self, env_paths: list[Path], RUNNER_DICT: dict[str, type["Runner"]]):
|
||||
self.env_files: list[EnvFile] = self._get_env_files(env_paths)
|
||||
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)
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ class EnvManager:
|
|||
config: dict[str, str] = dotenv_values(env_path) # type: ignore
|
||||
assert "TYPE" in config, f"the env file {env_path} does not specify the required TYPE key."
|
||||
env_type = config["TYPE"]
|
||||
env_files.append(EnvFile(env_path=env_path, config=config, env_type=env_type))
|
||||
env_files.append(EnvFile(env_path=env_path, env_config=config, env_type=env_type))
|
||||
return env_files
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -92,7 +93,7 @@ class EnvManager:
|
|||
"Could not resolve test order. This is possibly due to a circular dependency (a on b, b on c, c on a)"
|
||||
)
|
||||
|
||||
def copy_env_files(self, DIR: DirManager) -> None:
|
||||
def copy_env_files(self, DIR: "DirManager") -> None:
|
||||
"""Copies all env files to STATES/env_files. Files will be renamed to
|
||||
<index>-<env_type>-<original_name>
|
||||
00-authentik-login.test.dev.local-it.cloud.env"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
from typing import TYPE_CHECKING, Callable, NamedTuple
|
||||
|
||||
import pytest
|
||||
from loguru import logger
|
||||
|
|
@ -10,10 +10,16 @@ if TYPE_CHECKING:
|
|||
from pytest_abra.env_manager import EnvFile
|
||||
|
||||
|
||||
class ConditionArgs(NamedTuple):
|
||||
env_config: dict[str, str]
|
||||
runner_index: int
|
||||
env_files: list["EnvFile"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Test:
|
||||
test_file: str
|
||||
condition: Callable[[dict[str, str]], bool] | None = None
|
||||
condition: Callable[[ConditionArgs], bool] | None = None
|
||||
prevent_skip: bool = False
|
||||
|
||||
|
||||
|
|
@ -25,17 +31,13 @@ class Runner:
|
|||
dependencies: list[str] = []
|
||||
|
||||
def __init__(self, coordinator: "Coordinator", runner_index: int):
|
||||
self.coordinator = coordinator # needed?
|
||||
self.runner_index = runner_index # needed?
|
||||
self.coordinator = coordinator
|
||||
self.runner_index = runner_index
|
||||
|
||||
self.DIR = coordinator.DIR
|
||||
self.ENV = coordinator.ENV
|
||||
self.RUNNER_DICT = coordinator.RUNNER_DICT
|
||||
|
||||
self.env_file: EnvFile = self.ENV.env_files[self.runner_index]
|
||||
self.dotenv_path = self.env_file.env_path
|
||||
self.config = self.env_file.config
|
||||
|
||||
logger.info(f"creating instance of {self.__class__.__name__}")
|
||||
|
||||
def run_setups(self):
|
||||
|
|
@ -81,16 +83,28 @@ class Runner:
|
|||
logger.info(f"skipping {identifier_string}, test has passed")
|
||||
return
|
||||
|
||||
if test.condition and not test.condition(self.config):
|
||||
# test condition is defined but not met
|
||||
logger.info(f"skipping {identifier_string}, test condition is not met")
|
||||
return
|
||||
if test.condition:
|
||||
condition_result = self._run_condition(test.condition)
|
||||
if not condition_result:
|
||||
# test condition is defined but not met
|
||||
logger.info(f"skipping {identifier_string}, test condition is not met")
|
||||
return
|
||||
|
||||
# test condition is undefined or not met
|
||||
logger.info(f"running {identifier_string}")
|
||||
result = self._call_pytest(full_test_path)
|
||||
self._create_result_file(result=result, identifier_string=identifier_string)
|
||||
|
||||
def _run_condition(self, condition_function: Callable[[ConditionArgs], bool]):
|
||||
"""run the test condition function with multiple arguments"""
|
||||
# more arguments can be added later without changing the function signature
|
||||
conditon_args = ConditionArgs(
|
||||
env_files=self.ENV.env_files,
|
||||
runner_index=self.runner_index,
|
||||
env_config=self.ENV.env_files[self.runner_index].env_config,
|
||||
)
|
||||
return condition_function(conditon_args)
|
||||
|
||||
def _is_test_passed(self, identifier_string: str, remove_existing: bool = False) -> bool:
|
||||
"""returns True if the selected test matching identifier_string already passed
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from urllib.parse import urlunparse
|
|||
|
||||
@dataclass
|
||||
class BaseUrl:
|
||||
"""utility class to create a url string with urllib"""
|
||||
|
||||
netloc: str
|
||||
scheme: str = "https"
|
||||
path: str = ""
|
||||
|
|
@ -33,8 +35,3 @@ def rmtree(root_dir: Path):
|
|||
child.unlink()
|
||||
|
||||
root_dir.rmdir()
|
||||
|
||||
|
||||
def make_url(domain: str) -> str:
|
||||
"""adds 'http://' at the beginning of a string"""
|
||||
return "https://" + domain
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue