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
29
README.md
29
README.md
|
|
@ -2,19 +2,19 @@
|
|||
|
||||
Pytest-Abra is an installable python package design to test instances created with [abra](https://docs.coopcloud.tech/abra/). After installation, you will have two things:
|
||||
|
||||
- `abratest` CLI command
|
||||
- `abratest` CLI command. *Used to initialize the testing.*
|
||||
|
||||
- `pytest-abra` Pytest plugin
|
||||
- `pytest-abra` Pytest plugin. *Automatically loads custom fixtures in any pytest (see `pytest_abra/custom_fixtures.py`)*
|
||||
|
||||
## CLI (abratest)
|
||||
## CLI (`abratest`)
|
||||
|
||||
The easiest way to call abratest is via the helper script in `main.py`. You can also directly call abratest via terminal, but you will have to make sure that the requirements below are met. To do that, you can call `abratest` with:
|
||||
`abratest` can be called via terminal:
|
||||
|
||||
```bash
|
||||
abratest [arguments]
|
||||
```
|
||||
|
||||
The cli command abratest has 3 **required arguments**:
|
||||
To run successfully, very specific arguments are required. The easiest way to use abratest is with the helper script in `main.py`. Of yourse you can implement a similar helper script in the language of your liking. The cli command `abratest` has 3 **required arguments**:
|
||||
|
||||
- `--env_paths`: list of the .env files used in the test
|
||||
- `--recipes_dir`: directory of all available abra recipes
|
||||
|
|
@ -66,12 +66,20 @@ DIR recipes_dir [contains abra recipes]
|
|||
└── [pytest_files]
|
||||
```
|
||||
|
||||
The class `RunnerWordpress` will be automatically imported by `importlib`, which is equivalent to
|
||||
The class `RunnerWordpress` will be automatically imported using `importlib` library, which is equivalent to the code below. Note that `recipes_dir` will be added to sys.path automatically for the import to work and that every `Runner` class matching `recipes_dir.rglob("*/runner*.py")` will be imported.
|
||||
|
||||
```python
|
||||
from wordpress.tests_wordpress.runner_wordpress import RunnerWordpress
|
||||
```
|
||||
|
||||
### output_dir [string]
|
||||
|
||||
Path to the directory where all test outputs are stored (test report, tracebacks, playwright traces etc.)
|
||||
|
||||
```
|
||||
abratest --output_dir /path/to/output
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
To use pytest-abra, follow these steps:
|
||||
|
|
@ -108,7 +116,7 @@ Run the script with
|
|||
python main.py
|
||||
```
|
||||
|
||||
# 2.2 Run with Docker
|
||||
## 2.2 Run with Docker
|
||||
|
||||
```bash
|
||||
docker compose build # build the image
|
||||
|
|
@ -128,12 +136,13 @@ Force rebuild without cache
|
|||
docker-compose build --no-cache
|
||||
```
|
||||
|
||||
## Codegen
|
||||
## Playwright Debug & Codegen
|
||||
|
||||
Use playwright codegen to create code for new testes easily https://playwright.dev/python/docs/codegen
|
||||
Use playwright debug mode or codegen to create testing code easily by recording browser actions https://playwright.dev/python/docs/codegen
|
||||
|
||||
```bash
|
||||
playwright codegen demo.playwright.dev/todomvc
|
||||
abratest --debug # launch your tests in debug mode
|
||||
playwright codegen demo.playwright.dev/todomvc # visit given url in codegen mode
|
||||
```
|
||||
|
||||
## Development
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ from playwright.sync_api import BrowserContext, expect
|
|||
from pytest_abra.dir_manager import DirManager
|
||||
|
||||
|
||||
def test_wordpress(admin_session: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager):
|
||||
def test_wordpress(admin_session: BrowserContext, env_config: dict[str, str], DIR: DirManager):
|
||||
page_authentik = admin_session.new_page()
|
||||
with page_authentik.expect_popup() as event_context:
|
||||
page_authentik.get_by_role("link", name="Wordpress").click()
|
||||
page_wordpress = event_context.value
|
||||
|
||||
expect(page_wordpress.locator("#wpcontent")).to_be_visible()
|
||||
if "locale" in dotenv_config and "de" in dotenv_config["locale"]:
|
||||
if "locale" in env_config and "de" in env_config["locale"]:
|
||||
expect(page_wordpress.get_by_role("heading")).to_have_text("Willkommen bei WordPress!")
|
||||
|
|
|
|||
19
prototyping/dependency_injection.py
Normal file
19
prototyping/dependency_injection.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import inspect
|
||||
|
||||
a = 2
|
||||
b = 3
|
||||
c = 4
|
||||
|
||||
|
||||
def func(a: int, c: int) -> int:
|
||||
return a + c
|
||||
|
||||
|
||||
arg_names = inspect.getfullargspec(func).args
|
||||
print(arg_names) # ['a', 'c']
|
||||
|
||||
arguments = {arg: globals()[arg] for arg in arg_names if arg in globals()}
|
||||
print(arguments) # {'a': 2, 'c': 4}
|
||||
|
||||
result = func(**arguments)
|
||||
print(result) # 6
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# %%
|
||||
import email
|
||||
import json
|
||||
import os
|
||||
from email.header import decode_header
|
||||
from imaplib import IMAP4, IMAP4_SSL
|
||||
from pathlib import Path
|
||||
|
||||
# -------------------------------- credentials ------------------------------- #
|
||||
|
||||
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"]
|
||||
|
||||
|
||||
# ----------------------------------- imap ----------------------------------- #
|
||||
|
||||
|
||||
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
|
||||
status, email_ids = imap_server.search(None, "ALL")
|
||||
email_ids = email_ids[0].split()
|
||||
|
||||
# Fetch email details using the retrieved IDs
|
||||
for email_id in email_ids:
|
||||
result, data = imap_server.fetch(email_id, "(RFC822)")
|
||||
raw_email = data[0][1] # Raw content of the email
|
||||
email_message = email.message_from_bytes(raw_email)
|
||||
|
||||
# Extract the subject
|
||||
subject_encoded = email_message.get("Subject")
|
||||
decoded_subject = decode_header(subject_encoded)[0][0]
|
||||
|
||||
if isinstance(decoded_subject, bytes):
|
||||
decoded_subject = decoded_subject.decode()
|
||||
|
||||
# Print or use the subject as needed
|
||||
print("Subject:", decoded_subject)
|
||||
80
prototyping/structure.md
Normal file
80
prototyping/structure.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
|
||||
Abratest has 3 required inputs, but most importantly the test configuration is done through the .env files given with the --env_paths argument. So let's say we want to run abratest with these 3 .env files:
|
||||
|
||||
- config1.env [of TYPE authentik]
|
||||
|
||||
- config2.env [of TYPE wordpress]
|
||||
|
||||
- config3.env [of TYPE wordpress]
|
||||
|
||||
Now we run
|
||||
|
||||
```bash
|
||||
abratest --env_paths path/config1.env;path/config2.env;path/config3.env [...other args]
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
abratest -> create Coordinator() instance
|
||||
└── Coordinator() -> create Runner() subclass instances
|
||||
├── RunnerAuthentik() [based on config1.env, loaded
|
||||
│ │ from abra/recipes/authentik]
|
||||
│ │ # RunnerAuthentik with 3 test files:
|
||||
│ ├── RUN pytest path/setup_authentik.py
|
||||
│ ├── RUN pytest path/test_authentik_1.py
|
||||
│ └── RUN pytest path/test_authentik_2.py
|
||||
├── RunnerWordpress() [based on config2.env, loaded
|
||||
│ │ from abra/recipes/wordpress]
|
||||
│ │ # RunnerWordpress with 1 test file
|
||||
│ ├── RUN pytest path/setup_authentik.py
|
||||
│ ├── RUN pytest path/test_authentik_1.py
|
||||
│ └── RUN pytest path/test_authentik_2.py
|
||||
└── RunnerWordpress() [based on config3.env, loaded
|
||||
│ from abra/recipes/wordpress]
|
||||
│ # RunnerWordpress with 1 test file
|
||||
├── RUN pytest path/setup_authentik.py
|
||||
├── RUN pytest path/test_authentik_1.py
|
||||
└── RUN pytest path/test_authentik_2.py
|
||||
|
||||
|
||||
```
|
||||
|
||||
Coordinator will take care of the correct order of the tests. In general, tests are placed in one of 3 categories: `setups`, `tests` and `cleanups`. To associate a test with one of these categories, place the Test in the corresponding list of the Runner class, i.e. Runner.setups = [test] or Runner.tests = [test]. The execution order will be.
|
||||
|
||||
> [setups] ➔ [tests] ➔ [cleanups]
|
||||
|
||||
|
||||
Furthermore, some `Runner` classes can depend on others. For example, `RunnerWordpress` depends on `RunnerAuthentik`. Therefore, `Coordinator` will make sure that `RunnerAuthentik` runs before `RunnerWordpress`. We will end up with with this order:
|
||||
|
||||
| # | Runner | Type |
|
||||
| --- | -------------- | -------- |
|
||||
| 1. | Authentik | setups |
|
||||
| 2. | Wordpress-1 | setups |
|
||||
| 3. | Wordpress-2 | setups |
|
||||
| 4. | Authentik | tests |
|
||||
| 5. | Wordpress-1 | tests |
|
||||
| 6. | Wordpress-2 | tests |
|
||||
| 7. | Authentik | cleanups |
|
||||
| 8. | Wordpress-1 | cleanups |
|
||||
| 9. | Wordpress-2 | cleanups |
|
||||
|
||||
|
||||
|
||||
To comprehend this process, let's examine a simplified rendition of the `RunnerWordpress` class. Within it, there exist two setup scripts and two test scripts, one of which operates conditionally.
|
||||
|
||||
|
||||
```python
|
||||
class RunnerWordpress(Runner):
|
||||
env_type = "wordpress"
|
||||
dependencies = ["authentik"]
|
||||
setups = [
|
||||
Test(test_file="setup_wordpress_1.py"),
|
||||
Test(test_file="setup_wordpress_2.py"),
|
||||
]
|
||||
tests = [
|
||||
Test(test_file="test_wordpress.py"),
|
||||
Test(condition=condition_function, test_file="test_wordpress_conditional.py"),
|
||||
]
|
||||
cleanups = []
|
||||
```
|
||||
|
|
@ -26,7 +26,7 @@ dependencies = [
|
|||
]
|
||||
|
||||
[project.entry-points.pytest11]
|
||||
pytest_abra = "pytest_abra.pytest_abra"
|
||||
pytest_abra = "pytest_abra.custom_fixtures"
|
||||
|
||||
[project.scripts]
|
||||
abratest = "pytest_abra.cli:run"
|
||||
|
|
|
|||
|
|
@ -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,7 +83,9 @@ class Runner:
|
|||
logger.info(f"skipping {identifier_string}, test has passed")
|
||||
return
|
||||
|
||||
if test.condition and not test.condition(self.config):
|
||||
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
|
||||
|
|
@ -91,6 +95,16 @@ class Runner:
|
|||
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
|
||||
|
|
|
|||
|
|
@ -1,12 +1,4 @@
|
|||
from pytest_abra.runner import Runner, Test
|
||||
|
||||
|
||||
def condition_always_true(dotenv_config: dict[str, str]) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def condition_always_false(dotenv_config: dict[str, str]) -> bool:
|
||||
return False
|
||||
from pytest_abra import Runner, Test
|
||||
|
||||
|
||||
class RunnerAuthentik(Runner):
|
||||
|
|
|
|||
|
|
@ -14,20 +14,20 @@ ADMIN_PASS = os.environ["ADMIN_PASS"]
|
|||
TESTUSER = {"username": "testuser", "name": "Test User", "password": "test123", "email": "test@example.com"}
|
||||
|
||||
|
||||
def setup_admin_state(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager):
|
||||
def setup_admin_state(context: BrowserContext, env_config: dict[str, str], DIR: DirManager):
|
||||
# go to page
|
||||
page = context.new_page()
|
||||
url = "https://" + dotenv_config["DOMAIN"]
|
||||
url = "https://" + env_config["DOMAIN"]
|
||||
page.goto(url)
|
||||
|
||||
# check welcome message
|
||||
welcome_message = dotenv_config.get("welcome_message")
|
||||
welcome_message = env_config.get("welcome_message")
|
||||
if welcome_message:
|
||||
expect(page.get_by_text(welcome_message)).to_be_visible()
|
||||
|
||||
# login
|
||||
page.locator('input[name="uidField"]').fill(ADMIN_USER)
|
||||
page.locator('ak-stage-identification input[name="password"]').fill(ADMIN_PASS)
|
||||
page.locator("input[name='uidField']").fill(ADMIN_USER)
|
||||
page.locator("ak-stage-identification input[name='password']").fill(ADMIN_PASS)
|
||||
page.get_by_role("button", name="Log In").click()
|
||||
expect(page.locator("ak-library")).to_be_visible()
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ def setup_admin_state(context: BrowserContext, dotenv_config: dict[str, str], DI
|
|||
context.storage_state(path=DIR.STATES / "authentik_admin_state.json")
|
||||
|
||||
|
||||
def check_if_user_exists(admin_context: BrowserContext, dotenv_config: dict[str, str], URL: BaseUrl):
|
||||
def check_if_user_exists(admin_context: BrowserContext, env_config: dict[str, str], URL: BaseUrl):
|
||||
# go to admin page
|
||||
page = admin_context.new_page()
|
||||
page.goto(URL.get())
|
||||
|
|
@ -49,7 +49,7 @@ 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], URL: BaseUrl):
|
||||
def create_invite_link(admin_context: BrowserContext, env_config: dict[str, str], URL: BaseUrl):
|
||||
# go to admin page
|
||||
page = admin_context.new_page()
|
||||
page.goto(URL.get())
|
||||
|
|
@ -98,19 +98,19 @@ 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, URL: BaseUrl):
|
||||
def setup_user_state(context: BrowserContext, env_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, URL):
|
||||
if check_if_user_exists(context, env_config, URL):
|
||||
# just login with user
|
||||
pass
|
||||
context.clear_cookies()
|
||||
else:
|
||||
# get invite_link
|
||||
invite_link = create_invite_link(context, dotenv_config, URL)
|
||||
invite_link = create_invite_link(context, env_config, URL)
|
||||
# create user
|
||||
context.clear_cookies()
|
||||
create_user(context, invite_link)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
from pytest_abra.runner import Runner, Test
|
||||
|
||||
|
||||
def condition_always_false(dotenv_config: dict[str, str]) -> bool:
|
||||
return False
|
||||
from pytest_abra import Runner, Test
|
||||
|
||||
|
||||
class RunnerNextcloud(Runner):
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ 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"):
|
||||
def test_nextcloud_quota(nextcloud_admin_page: Page, env_config: dict[str, str]):
|
||||
"""Tests if the quota set in .env file matches the actual quota shown on the page within 10%"""
|
||||
if env_config.get("DEFAULT_QUOTA"):
|
||||
# get quota from website
|
||||
quota_string = nextcloud_admin_page.get_by_text(
|
||||
re.compile(r"\d*,\d .* \d*,\d")
|
||||
|
|
@ -17,7 +17,7 @@ def test_nextcloud_quota(nextcloud_admin_page: Page, dotenv_config: dict[str, st
|
|||
quota_website = float(out_number)
|
||||
|
||||
# get quota from env
|
||||
quota_config_string = dotenv_config["DEFAULT_QUOTA"] # "100 MB"
|
||||
quota_config_string = env_config["DEFAULT_QUOTA"] # "100 MB"
|
||||
assert "MB" in quota_config_string
|
||||
quota_config = float(quota_config_string.strip("MB"))
|
||||
|
||||
|
|
@ -27,6 +27,6 @@ def test_nextcloud_quota(nextcloud_admin_page: Page, dotenv_config: dict[str, st
|
|||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_nextcloud_apps(nextcloud_admin_page: Page, dotenv_config: dict[str, str]):
|
||||
for app in dotenv_config["nc_apps"]:
|
||||
def test_nextcloud_apps(nextcloud_admin_page: Page, env_config: dict[str, str]):
|
||||
for app in env_config["nc_apps"]:
|
||||
expect(nextcloud_admin_page.get_by_role("link", name=app)).to_be_visible()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
from dotenv import dotenv_values
|
||||
from playwright.sync_api import BrowserContext, Page
|
||||
|
||||
from pytest_abra.dir_manager import DirManager
|
||||
from pytest_abra import BaseUrl, DirManager
|
||||
|
||||
pytest_plugins = "authentik.tests_authentik.fixtures_authentik"
|
||||
|
||||
|
|
@ -18,10 +17,7 @@ def wordpress_admin_context(context: BrowserContext, DIR: DirManager) -> Browser
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def wordpress_admin_page(wordpress_admin_context: BrowserContext, DIR: DirManager) -> Page:
|
||||
def wordpress_admin_page(wordpress_admin_context: BrowserContext, URL: BaseUrl) -> Page:
|
||||
page = wordpress_admin_context.new_page()
|
||||
env_file = DIR.ENV_FILES / "wordpress"
|
||||
config: dict[str, str] = dotenv_values(env_file) # type: ignore
|
||||
url = "https://" + config["DOMAIN"]
|
||||
page.goto(url)
|
||||
page.goto(URL.get())
|
||||
return page
|
||||
|
|
|
|||
|
|
@ -1,17 +1,9 @@
|
|||
from pytest_abra.runner import Runner, Test
|
||||
from pytest_abra import ConditionArgs, Runner, Test
|
||||
|
||||
|
||||
def condition_always_true(dotenv_config: dict[str, str]) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def condition_always_false(dotenv_config: dict[str, str]) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def condition_has_locale(dotenv_config: dict[str, str]) -> bool:
|
||||
if "LOCALE" in dotenv_config:
|
||||
if "de" in dotenv_config["LOCALE"]:
|
||||
def condition_has_locale(args: ConditionArgs) -> bool:
|
||||
env_config = args.env_config
|
||||
if "de" in env_config.get("LOCALE", ""):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -19,8 +11,11 @@ def condition_has_locale(dotenv_config: dict[str, str]) -> bool:
|
|||
class RunnerWordpress(Runner):
|
||||
env_type = "wordpress"
|
||||
dependencies = ["authentik"]
|
||||
setups = [Test(test_file="setup_wordpress.py")]
|
||||
tests = [
|
||||
Test(test_file="test_wordpress.py"),
|
||||
Test(condition=condition_has_locale, test_file="test_wordpress_localization.py"),
|
||||
setups = [
|
||||
Test(test_file="setup_wordpress.py"),
|
||||
Test(test_file="setup_wordpress_trigger_email.py"),
|
||||
]
|
||||
tests = [
|
||||
Test(test_file="test_wordpress_receive_email.py", prevent_skip=True),
|
||||
# Test(condition=condition_has_locale, test_file="test_wordpress_localization.py"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ from playwright.sync_api import BrowserContext, Page, expect
|
|||
from pytest_abra.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, env_config: dict[str, str]):
|
||||
"""visit wordpress directly with admin_session, expect not to be logged in"""
|
||||
page = authentik_admin_context.new_page()
|
||||
url = "https://" + dotenv_config["DOMAIN"]
|
||||
url = "https://" + env_config["DOMAIN"]
|
||||
page.goto(url)
|
||||
with pytest.raises(AssertionError):
|
||||
# look for admin bar
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import os
|
||||
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
from pytest_abra import BaseUrl
|
||||
|
||||
|
||||
def setup_trigger_email(wordpress_admin_page: Page, URL: BaseUrl):
|
||||
"""change profile email to EMAIL to trigger email"""
|
||||
page = wordpress_admin_page
|
||||
page.goto(URL.get("wp-admin/profile.php"))
|
||||
EMAIL = os.environ["IMAP_EMAIL"]
|
||||
print(EMAIL)
|
||||
# breakpoint()
|
||||
page.pause()
|
||||
page.locator("input[id='email']").fill(EMAIL)
|
||||
page.locator("input[id='submit']").click()
|
||||
|
||||
expect(page.locator("div.notice").get_by_text(EMAIL)).to_be_visible()
|
||||
|
|
@ -5,11 +5,11 @@ from playwright.sync_api import BrowserContext, expect
|
|||
from pytest_abra.dir_manager import DirManager
|
||||
|
||||
|
||||
def test_welcome_message(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager):
|
||||
def test_welcome_message(context: BrowserContext, env_config: dict[str, str], DIR: DirManager):
|
||||
page = context.new_page()
|
||||
url = "https://" + dotenv_config["DOMAIN"]
|
||||
url = "https://" + env_config["DOMAIN"]
|
||||
page.goto(url)
|
||||
|
||||
expect(page.locator(".wp-block-heading")).to_be_visible()
|
||||
if "locale" in dotenv_config and "de" in dotenv_config["locale"]:
|
||||
if "locale" in env_config and "de" in env_config["locale"]:
|
||||
expect(page.get_by_role("heading")).to_have_text("Willkommen bei WordPress!")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
from icecream import ic
|
||||
|
||||
from pytest_abra.custom_fixtures import Message
|
||||
|
||||
|
||||
def test_demo(imap_recent_messages: list[Message]):
|
||||
for message in imap_recent_messages:
|
||||
print(dir(message))
|
||||
ic(message.subject)
|
||||
ic(message.body["plain"])
|
||||
|
||||
exit()
|
||||
assert False
|
||||
|
|
@ -20,7 +20,7 @@ def test_complex_sorting() -> None:
|
|||
]
|
||||
|
||||
demo_types = ["a", "b", "c", "d", "e", "f", "g"]
|
||||
env_files = [EnvFile(env_type=t, env_path=Path(), config=dict()) for t in demo_types]
|
||||
env_files = [EnvFile(env_type=t, env_path=Path(), env_config=dict()) for t in demo_types]
|
||||
EnvManager.sort_env_files_by_rule
|
||||
sorted_env_files = EnvManager.sort_env_files_by_rule(env_files, demo_rules)
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ def test_circular_import() -> None:
|
|||
]
|
||||
|
||||
demo_types = ["a", "b", "c"]
|
||||
env_files = [EnvFile(env_type=t, env_path=Path(), config=dict()) for t in demo_types]
|
||||
env_files = [EnvFile(env_type=t, env_path=Path(), env_config=dict()) for t in demo_types]
|
||||
with pytest.raises(ValueError):
|
||||
EnvManager.sort_env_files_by_rule(env_files, demo_rules)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue