[WIP] Add new automated test framework (#1)
Co-authored-by: Daniel <d.brummerloh@gmail.com> Co-committed-by: Daniel <d.brummerloh@gmail.com>
This commit is contained in:
parent
859bd57006
commit
97ed87c79f
31 changed files with 769 additions and 6 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,3 +2,6 @@ __pycache__/
|
||||||
*.json
|
*.json
|
||||||
*.zip
|
*.zip
|
||||||
TestResults/
|
TestResults/
|
||||||
|
.vscode/
|
||||||
|
*.pyc
|
||||||
|
credentials*
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "envfiles"]
|
||||||
|
path = envfiles
|
||||||
|
url = ssh://git@git.local-it.org:2222/local-it-infrastructure/dev.local-it.cloud.git
|
||||||
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir pytest-playwright
|
||||||
|
|
||||||
|
RUN playwright install
|
||||||
|
|
||||||
|
RUN playwright install-deps
|
||||||
|
|
||||||
|
COPY ./requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
WORKDIR /code/src
|
||||||
21
README.md
21
README.md
|
|
@ -1,11 +1,20 @@
|
||||||
|
# Readme
|
||||||
|
|
||||||
# Installation
|
```bash
|
||||||
|
docker compose build
|
||||||
|
docker compose run --rm app python ./main.py
|
||||||
|
docker compose run --rm app pytest
|
||||||
|
# docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
pip install pytest-playwright
|
Force rebuild with cache
|
||||||
playwright install
|
|
||||||
|
|
||||||
# Run Tests:
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
pytest -k nextcloud
|
Force rebuild wtihtout cache
|
||||||
|
|
||||||
playwright show-trace trace.zip
|
```bash
|
||||||
|
docker-compose build --no-cache
|
||||||
|
```
|
||||||
|
|
|
||||||
6
docker-compose.yml
Normal file
6
docker-compose.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: python-env
|
||||||
|
volumes:
|
||||||
|
- .:/code
|
||||||
1
envfiles
Submodule
1
envfiles
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit a8375f6fc7a285a1000b5553be47eaf19b0be0a6
|
||||||
11
previous-work/README.md
Normal file
11
previous-work/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
pip install pytest-playwright
|
||||||
|
playwright install
|
||||||
|
|
||||||
|
# Run Tests:
|
||||||
|
|
||||||
|
pytest -k nextcloud
|
||||||
|
|
||||||
|
playwright show-trace trace.zip
|
||||||
19
pyproject.toml
Normal file
19
pyproject.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[project]
|
||||||
|
name = "locit-testing"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = "~=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"pytest == 7.4.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"ruff >= 0.1.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
package-dir = {"" = "src"}
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
target-version = "py311"
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
pytest
|
||||||
|
pytest-playwright
|
||||||
|
python-dotenv
|
||||||
|
icecream
|
||||||
71
src/blog.dev.local-it.cloud.env
Normal file
71
src/blog.dev.local-it.cloud.env
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
################################################################################
|
||||||
|
# DO NOT EDIT THIS FILE, IT IS AUTOMATICALLY GENERATED AND WILL BE OVERWRITTEN #
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
TYPE=wordpress
|
||||||
|
TIMEOUT=300
|
||||||
|
ENABLE_AUTO_UPDATE=true
|
||||||
|
COMPOSE_FILE="compose.yml"
|
||||||
|
|
||||||
|
# Setup Wordpress settings on each deploy:
|
||||||
|
#POST_DEPLOY_CMDS="app core_install"
|
||||||
|
|
||||||
|
DOMAIN=blog.dev.local-it.cloud
|
||||||
|
## Domain aliases
|
||||||
|
#EXTRA_DOMAINS=', `www.blog.dev.local-it.cloud`'
|
||||||
|
LETS_ENCRYPT_ENV=production
|
||||||
|
|
||||||
|
TITLE="My Example Blog"
|
||||||
|
LOCALE=de_DE
|
||||||
|
ADMIN_EMAIL=admin@example.com
|
||||||
|
|
||||||
|
# Every new user is per default subscriber, uncomment to change it
|
||||||
|
DEFAULT_USER_ROLE=administrator
|
||||||
|
|
||||||
|
#WORDPRESS_DEBUG=true
|
||||||
|
|
||||||
|
## Additional extensions
|
||||||
|
#PHP_EXTENSIONS="calendar"
|
||||||
|
|
||||||
|
SECRET_DB_ROOT_PASSWORD_VERSION=v1
|
||||||
|
SECRET_DB_PASSWORD_VERSION=v1
|
||||||
|
|
||||||
|
# Mostly for compatibility with existing database dumps...
|
||||||
|
#WORDPRESS_TABLE_PREFIX=wp_
|
||||||
|
|
||||||
|
# Multisite
|
||||||
|
#WORDPRESS_CONFIG_EXTRA="\
|
||||||
|
#define('WP_CACHE', false);\
|
||||||
|
#define('WP_ALLOW_MULTISITE', true );"
|
||||||
|
|
||||||
|
# Multisite phase 2 (see README)
|
||||||
|
#WORDPRESS_CONFIG_EXTRA="define('MULTISITE', true); define('SUBDOMAIN_INSTALL', true); define('DOMAIN_CURRENT_SITE', '${DOMAIN}'); define('PATH_CURRENT_SITE', '/'); define('SITE_ID_CURRENT_SITE', 1); define('BLOG_ID_CURRENT_SITE', 1); define('FORCE_SSL_ADMIN', true ); define('COOKIE_DOMAIN', \$_SERVER['HTTP_HOST']);"
|
||||||
|
|
||||||
|
# Local SMTP relay
|
||||||
|
#COMPOSE_FILE="$COMPOSE_FILE:compose.mailrelay.yml"
|
||||||
|
SMTP_HOST=mail.local-it.org
|
||||||
|
MAIL_FROM=noreply@local-it.org
|
||||||
|
|
||||||
|
# Remote SMTP relay
|
||||||
|
COMPOSE_FILE="$COMPOSE_FILE:compose.smtp.yml"
|
||||||
|
SMTP_HOST=mail.local-it.org
|
||||||
|
MAIL_FROM=noreply@local-it.org
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_AUTH=on
|
||||||
|
SMTP_TLS=on
|
||||||
|
SECRET_SMTP_PASSWORD_VERSION=v1
|
||||||
|
|
||||||
|
# Authentik SSO
|
||||||
|
COMPOSE_FILE="$COMPOSE_FILE:compose.authentik.yml"
|
||||||
|
AUTHENTIK_DOMAIN=dev.local-it.cloud
|
||||||
|
SECRET_AUTHENTIK_SECRET_VERSION=v1
|
||||||
|
SECRET_AUTHENTIK_ID_VERSION=v1
|
||||||
|
LOGIN_TYPE='auto'
|
||||||
|
|
||||||
|
# 🚩🚩 dangerous, use only for development sites!
|
||||||
|
|
||||||
|
# Allow remote connections to db
|
||||||
|
#COMPOSE_FILE="$COMPOSE_FILE:compose.public-db.yml
|
||||||
|
|
||||||
|
# Wide-open CORS
|
||||||
|
#CORS_ALLOW_ALL=1
|
||||||
64
src/conftest.py
Normal file
64
src/conftest.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# regarding conftest:
|
||||||
|
# If you have conftest.py files which do not reside in a python package directory
|
||||||
|
# (i.e. one containing an __init__.py) then “import conftest” can be ambiguous
|
||||||
|
# because there might be other conftest.py files as well on your PYTHONPATH or
|
||||||
|
# sys.path. It is thus good practise for projects to either put conftest.py under
|
||||||
|
# a package scope or to never import anything from a conftest.py file.
|
||||||
|
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from dirmanager import DirManager
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
|
||||||
|
pytest_plugins = [
|
||||||
|
"setup.setup_authentik",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addoption(
|
||||||
|
"--env_file",
|
||||||
|
action="store",
|
||||||
|
)
|
||||||
|
parser.addoption(
|
||||||
|
"--tests_dir",
|
||||||
|
action="store",
|
||||||
|
)
|
||||||
|
parser.addoption(
|
||||||
|
"--session_id",
|
||||||
|
action="store",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def dirmanager(request) -> DirManager:
|
||||||
|
tests_dir = request.config.getoption("--tests_dir")
|
||||||
|
tests_dir = Path(tests_dir)
|
||||||
|
session_id = request.config.getoption("--session_id")
|
||||||
|
return DirManager(tests_dir=tests_dir, session_id=session_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def dotenv_config(request) -> dict[str, str]:
|
||||||
|
dotenv_path = request.config.getoption("--env_file")
|
||||||
|
dotenv_path = Path(dotenv_path)
|
||||||
|
assert dotenv_path.is_file()
|
||||||
|
return dotenv_values(dotenv_path)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def RECORDS(dirmanager) -> Path:
|
||||||
|
assert isinstance(dirmanager, DirManager)
|
||||||
|
return dirmanager.dirs["records"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def STATES(dirmanager) -> Path:
|
||||||
|
return dirmanager.dirs["states"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def RESULTS(dirmanager) -> Path:
|
||||||
|
return dirmanager.dirs["results"]
|
||||||
53
src/dirmanager.py
Normal file
53
src/dirmanager.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class DirManager:
|
||||||
|
"""Manages directories for the tests and should be used to create and find
|
||||||
|
and use the correct directories.
|
||||||
|
|
||||||
|
The structures is as follows:
|
||||||
|
tests dir/
|
||||||
|
session_dir-1/
|
||||||
|
records
|
||||||
|
states
|
||||||
|
results
|
||||||
|
session_dir-2/
|
||||||
|
records
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, tests_dir: Path, session_id: str):
|
||||||
|
# root test dir
|
||||||
|
self.tests_dir = tests_dir
|
||||||
|
self.session_id = session_id
|
||||||
|
|
||||||
|
self.dirs = self._get_all_dirs()
|
||||||
|
|
||||||
|
def create_all_dirs(self):
|
||||||
|
self.create_dirs(self.tests_dir, exist_ok=True)
|
||||||
|
self.create_dirs(self.dirs)
|
||||||
|
|
||||||
|
def _get_all_dirs(self):
|
||||||
|
dirs = {}
|
||||||
|
dirs["session"] = self.tests_dir / f"test-{self.session_id}"
|
||||||
|
dirs.update(self._get_subdirs(session_dir=dirs["session"]))
|
||||||
|
return dirs
|
||||||
|
|
||||||
|
def _get_subdirs(self, session_dir: Path):
|
||||||
|
return {
|
||||||
|
"records": session_dir / Path("records"),
|
||||||
|
"states": session_dir / Path("states"),
|
||||||
|
"results": session_dir / Path("results"),
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_dirs(dirs: Path | list[Path] | dict[str, Path], exist_ok=False):
|
||||||
|
match dirs:
|
||||||
|
case Path():
|
||||||
|
dirs.mkdir(exist_ok=exist_ok)
|
||||||
|
case list():
|
||||||
|
for d in dirs:
|
||||||
|
d.mkdir(exist_ok=exist_ok)
|
||||||
|
case dict():
|
||||||
|
for d in dirs.values():
|
||||||
|
d.mkdir(exist_ok=exist_ok)
|
||||||
76
src/main.py
Normal file
76
src/main.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
from dirmanager import DirManager
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
from icecream import ic
|
||||||
|
from tests_authentik.runner_authentik import RunnerAuthentik
|
||||||
|
from tests_wordpress.runner_wordpress import RunnerWordpress
|
||||||
|
|
||||||
|
TESTS_DIR = Path("../tests")
|
||||||
|
|
||||||
|
ENV_FILES = [
|
||||||
|
Path("../envfiles/login.test.dev.local-it.cloud.env"), # authentik
|
||||||
|
# Path("../envfiles/blog.test.dev.local-it.cloud.env"), # wordpress
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TestRunner(Protocol):
|
||||||
|
def __init__(self, dotenv_path: Path, tests_dir: Path, session_id: str):
|
||||||
|
...
|
||||||
|
|
||||||
|
def run_tests(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
# Register all runners here. A .env file with TYPE=authentik will be ran with RunnerAuthentik
|
||||||
|
RUNNER_DICT: dict[str, type[TestRunner]] = {
|
||||||
|
"authentik": RunnerAuthentik,
|
||||||
|
"wordpress": RunnerWordpress,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Wrapper:
|
||||||
|
def __init__(self, env_files: list[Path], session_id: str):
|
||||||
|
self.env_files = env_files
|
||||||
|
self.check_env_files(self.env_files)
|
||||||
|
self.session_id = session_id
|
||||||
|
|
||||||
|
def setup_test(self):
|
||||||
|
self.dir_manager = DirManager(tests_dir=TESTS_DIR, session_id=self.session_id)
|
||||||
|
self.dir_manager.create_all_dirs()
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
self.runners: list[TestRunner] = self.load_runners(self.env_files)
|
||||||
|
self.run_tests(self.runners)
|
||||||
|
|
||||||
|
def load_runners(self, env_files: list[Path]) -> list[TestRunner]:
|
||||||
|
runners = []
|
||||||
|
for env_file in env_files:
|
||||||
|
config: dict[str, str] = dotenv_values(env_file)
|
||||||
|
RunnerClass = RUNNER_DICT[config["TYPE"]]
|
||||||
|
runners.append(RunnerClass(dotenv_path=env_file, tests_dir=TESTS_DIR, session_id=self.session_id))
|
||||||
|
return runners
|
||||||
|
|
||||||
|
def run_tests(self, runners: list[TestRunner]):
|
||||||
|
for runner in runners:
|
||||||
|
runner.run_tests()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check_env_files(env_files: list[Path]):
|
||||||
|
"""checks if file exist for every file in list"""
|
||||||
|
for env_file in env_files:
|
||||||
|
assert env_file.is_file()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_session_id() -> str:
|
||||||
|
current_datetime = datetime.now()
|
||||||
|
return current_datetime.strftime("%Y-%m-%d-%H-%M-%S")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
session_id = Wrapper.get_session_id()
|
||||||
|
wrapper = Wrapper(ENV_FILES, session_id=session_id)
|
||||||
|
wrapper.setup_test()
|
||||||
|
wrapper.run_test()
|
||||||
62
src/runner.py
Normal file
62
src/runner.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable, Optional, TypedDict
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from icecream import ic
|
||||||
|
|
||||||
|
|
||||||
|
class SubTest(TypedDict):
|
||||||
|
condition: Callable[[Path], bool]
|
||||||
|
test_file: str
|
||||||
|
|
||||||
|
|
||||||
|
class Runner:
|
||||||
|
test_dir_name: Optional[Path] = None
|
||||||
|
main_test_name: Optional[str] = None
|
||||||
|
sub_tests: list[SubTest] = []
|
||||||
|
|
||||||
|
def __init__(self, dotenv_path: Path, tests_dir: Path, session_id: str):
|
||||||
|
self.dotenv_path = dotenv_path
|
||||||
|
self.tests_dir = tests_dir
|
||||||
|
self.session_id = session_id
|
||||||
|
|
||||||
|
ic(f"creating instance of {self.__class__.__name__}")
|
||||||
|
assert self.test_dir_name is not None
|
||||||
|
self.root_dir = Path(__file__).parent
|
||||||
|
|
||||||
|
def _run_main_test(self):
|
||||||
|
if isinstance(self.main_test_name, str):
|
||||||
|
full_test_path = self.root_dir / self.test_dir_name / self.main_test_name
|
||||||
|
self._run_pytest(full_test_path)
|
||||||
|
|
||||||
|
def _run_pytest(self, full_test_path: Path):
|
||||||
|
"""runs pytest programmatically
|
||||||
|
|
||||||
|
will run all tests in the file at full_test_path with some command line arguments"""
|
||||||
|
|
||||||
|
ic(f"running test: {full_test_path}")
|
||||||
|
pytest.main(
|
||||||
|
[
|
||||||
|
"-v",
|
||||||
|
"-rp",
|
||||||
|
str(full_test_path),
|
||||||
|
"--env_file",
|
||||||
|
str(self.dotenv_path),
|
||||||
|
"--tests_dir",
|
||||||
|
str(self.tests_dir),
|
||||||
|
"--session_id",
|
||||||
|
self.session_id,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_files(self):
|
||||||
|
ic(list(self.root_dir.glob("*")))
|
||||||
|
|
||||||
|
def run_tests(self):
|
||||||
|
self._run_main_test()
|
||||||
|
for sub_test in self.sub_tests:
|
||||||
|
condition_function = sub_test["condition"]
|
||||||
|
if condition_function(self.dotenv_path):
|
||||||
|
test_name = sub_test["test_file"]
|
||||||
|
full_test_path = self.root_dir / self.test_dir_name / test_name
|
||||||
|
self._run_pytest(full_test_path)
|
||||||
47
src/setup/setup_authentik.py
Normal file
47
src/setup/setup_authentik.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from icecream import ic
|
||||||
|
from playwright.sync_api import Browser, Locator, expect
|
||||||
|
|
||||||
|
cred_file = Path("../credentials.json")
|
||||||
|
with open(cred_file, "r") as f:
|
||||||
|
CREDENTIALS = json.load(f)
|
||||||
|
|
||||||
|
print(CREDENTIALS)
|
||||||
|
|
||||||
|
TESTUSER = {"username": "testuser", "name": "Test User", "password": "test123", "email": "test@example.com"}
|
||||||
|
TIMEOUT = 5000
|
||||||
|
|
||||||
|
|
||||||
|
def check_for(locator: Locator):
|
||||||
|
expect(locator).to_be_visible(timeout=TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def admin_login(browser: Browser, dotenv_config, STATES):
|
||||||
|
# ic(dotenv_config)
|
||||||
|
|
||||||
|
# go to page
|
||||||
|
context = browser.new_context()
|
||||||
|
context.set_default_timeout(TIMEOUT)
|
||||||
|
page = context.new_page()
|
||||||
|
url = "https://" + dotenv_config["DOMAIN"]
|
||||||
|
page.goto(url)
|
||||||
|
|
||||||
|
# check welcome message
|
||||||
|
welcome_message = dotenv_config.get("welcome_message")
|
||||||
|
if welcome_message:
|
||||||
|
check_for(page.get_by_text(welcome_message))
|
||||||
|
|
||||||
|
# login
|
||||||
|
page.locator('input[name="uidField"]').fill(CREDENTIALS["admin"])
|
||||||
|
page.locator('ak-stage-identification input[name="password"]').fill(CREDENTIALS["admin_pw"])
|
||||||
|
page.get_by_role("button", name="Log In").click()
|
||||||
|
check_for(page.locator("ak-library"))
|
||||||
|
|
||||||
|
# save state
|
||||||
|
context.storage_state(path=f"{STATES}/admin_state.json")
|
||||||
|
page.close()
|
||||||
|
context.close()
|
||||||
16
src/tests_authentik/runner_authentik.py
Normal file
16
src/tests_authentik/runner_authentik.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from runner import Runner, SubTest
|
||||||
|
|
||||||
|
|
||||||
|
def condition_always_true(dotenv_path: Path) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def condition_always_false(dotenv_path: Path) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class RunnerAuthentik(Runner):
|
||||||
|
test_dir_name = "tests_authentik"
|
||||||
|
main_test_name = "test_authentik_dummy.py"
|
||||||
2
src/tests_authentik/test_authentik_dummy.py
Normal file
2
src/tests_authentik/test_authentik_dummy.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
def test_true():
|
||||||
|
assert 1 + 1 == 2
|
||||||
179
src/tests_authentik/test_authentik_setup.py
Normal file
179
src/tests_authentik/test_authentik_setup.py
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
## this file will not be used later
|
||||||
|
## split into
|
||||||
|
# -> setup.setup_authentic.py
|
||||||
|
# and
|
||||||
|
# -> tests
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from icecream import ic
|
||||||
|
from playwright.sync_api import Browser, Locator, expect
|
||||||
|
|
||||||
|
# playwright = sync_playwright().start()
|
||||||
|
# browser = playwright.chromium.launch(headless=False)
|
||||||
|
|
||||||
|
|
||||||
|
testuser = {"username": "testuser", "name": "Test User", "password": "test123", "email": "test@example.com"}
|
||||||
|
|
||||||
|
|
||||||
|
TIMEOUT = 5000
|
||||||
|
|
||||||
|
|
||||||
|
def check_for(locator: Locator):
|
||||||
|
expect(locator).to_be_visible(timeout=TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_context(browser, state_file=None):
|
||||||
|
if state_file:
|
||||||
|
context = browser.new_context(storage_state=state_file)
|
||||||
|
else:
|
||||||
|
context = browser.new_context()
|
||||||
|
context.set_default_timeout(TIMEOUT)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
""" Test Authentik Login and DE Locale """
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def admin_login(browser: Browser, dotenv_config, STATES):
|
||||||
|
# ic(dotenv_config)
|
||||||
|
CONFIG = dotenv_config
|
||||||
|
context = setup_context(browser)
|
||||||
|
page = context.new_page()
|
||||||
|
url = "https://" + CONFIG["DOMAIN"]
|
||||||
|
ic(url)
|
||||||
|
page.goto(url)
|
||||||
|
welcome_message = CONFIG.get("welcome_message")
|
||||||
|
if welcome_message:
|
||||||
|
check_for(page.get_by_text(welcome_message))
|
||||||
|
if CONFIG["locale"] == "de":
|
||||||
|
check_for(page.get_by_text("Benutzername oder Passwort vergessen?"))
|
||||||
|
check_for(page.get_by_text("E-Mail or Anmeldename"))
|
||||||
|
check_for(page.get_by_text("Passwort", exact=True))
|
||||||
|
page.locator('input[name="uidField"]').fill(CONFIG["admin"])
|
||||||
|
page.locator('ak-stage-identification input[name="password"]').fill(CONFIG["admin_pw"])
|
||||||
|
page.get_by_role("button", name="Log In").click()
|
||||||
|
check_for(page.locator("ak-library"))
|
||||||
|
if CONFIG["locale"] == "de":
|
||||||
|
check_for(page.get_by_text("Meine Anwendungen"))
|
||||||
|
context.storage_state(path=f"{STATES}/admin_state.json")
|
||||||
|
page.close()
|
||||||
|
context.close()
|
||||||
|
|
||||||
|
|
||||||
|
""" Create User """
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def init_create_user(browser: Browser, dotenv_config, STATES):
|
||||||
|
admin_context = setup_context(browser, f"{STATES}/admin_state.json")
|
||||||
|
admin_page = admin_context.new_page()
|
||||||
|
invitelink = create_invite_link(admin_page, dotenv_config)
|
||||||
|
admin_context.close()
|
||||||
|
user_context = setup_context(browser)
|
||||||
|
create_user(user_context, invitelink)
|
||||||
|
user_context.close()
|
||||||
|
|
||||||
|
|
||||||
|
""" Delete User """
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def post_delete_user(browser: Browser, dotenv_config, RECORDS, STATES):
|
||||||
|
yield
|
||||||
|
context = browser.new_context(storage_state=f"{STATES}/admin_state.json")
|
||||||
|
context.tracing.start(screenshots=True, snapshots=True, sources=True)
|
||||||
|
context.set_default_timeout(TIMEOUT)
|
||||||
|
page = context.new_page()
|
||||||
|
# delete_nextcloud_user(page)
|
||||||
|
delete_authentik_user(page, dotenv_config)
|
||||||
|
context.tracing.stop(path=f"{RECORDS}/delete_user.zip")
|
||||||
|
|
||||||
|
|
||||||
|
""" Create Invite Link """
|
||||||
|
|
||||||
|
|
||||||
|
def create_invite_link(page, dotenv_config):
|
||||||
|
CONFIG = dotenv_config
|
||||||
|
page.goto(CONFIG["domain"])
|
||||||
|
page.get_by_role("link", name="Admin Interface").click()
|
||||||
|
page.get_by_text("Verzeichnis").click()
|
||||||
|
page.get_by_text("Benutzer").nth(2).click()
|
||||||
|
page.get_by_text("Einladungen").click()
|
||||||
|
page.get_by_role("button", name="Erstellen").first.click()
|
||||||
|
page.locator('input[name="name"]').click()
|
||||||
|
linkname = "testlink9433"
|
||||||
|
page.locator('input[name="name"]').fill(linkname)
|
||||||
|
page.get_by_placeholder("Wählen Sie ein Objekt aus.").click()
|
||||||
|
page.get_by_role("option", name="invitation-enrollment-flow invitation-enrollment-flow").click()
|
||||||
|
page.get_by_text("Erstellen", exact=True).first.click()
|
||||||
|
linklocator = page.get_by_role("rowgroup").filter(has=page.get_by_text(linkname))
|
||||||
|
linklocator.locator(".fa-angle-down").click()
|
||||||
|
invitelink = linklocator.get_by_role("textbox").get_attribute(name="value")
|
||||||
|
return invitelink
|
||||||
|
|
||||||
|
|
||||||
|
""" Create User from invitelink """
|
||||||
|
|
||||||
|
|
||||||
|
def create_user(context, invitelink, STATES):
|
||||||
|
page = context.new_page()
|
||||||
|
page.goto(invitelink)
|
||||||
|
page.get_by_placeholder("Benutzername").click()
|
||||||
|
page.get_by_placeholder("Benutzername").fill(testuser["username"])
|
||||||
|
page.locator('input[name="name"]').click()
|
||||||
|
page.locator('input[name="name"]').fill(testuser["name"])
|
||||||
|
page.locator('input[name="email"]').click()
|
||||||
|
page.locator('input[name="email"]').fill(testuser["email"])
|
||||||
|
page.get_by_placeholder("Passwort", exact=True).click()
|
||||||
|
page.get_by_placeholder("Passwort", exact=True).fill(testuser["password"])
|
||||||
|
page.get_by_placeholder("Passwort (wiederholen)").click()
|
||||||
|
page.get_by_placeholder("Passwort (wiederholen)").fill(testuser["password"])
|
||||||
|
page.get_by_role("button", name="Weiter").click()
|
||||||
|
check_for(page.locator("ak-library"))
|
||||||
|
context.storage_state(path=f"{STATES}/user_state.json")
|
||||||
|
|
||||||
|
|
||||||
|
""" Delete Authentik Account """
|
||||||
|
|
||||||
|
|
||||||
|
def delete_authentik_user(page, dotenv_config):
|
||||||
|
CONFIG = dotenv_config
|
||||||
|
page.goto(CONFIG["domain"])
|
||||||
|
page.get_by_role("link", name="Admin Interface").click()
|
||||||
|
page.get_by_text("Verzeichnis").click()
|
||||||
|
page.get_by_text("Benutzer").nth(2).click()
|
||||||
|
page.get_by_role("row").filter(has=page.get_by_text(testuser["username"])).get_by_role("checkbox").click()
|
||||||
|
page.get_by_role("button", name="Löschen").click()
|
||||||
|
page.get_by_role("dialog").get_by_role("button", name="Löschen").click()
|
||||||
|
check_for(page.get_by_text("1 Benutzer erfolgreich gelöscht"))
|
||||||
|
|
||||||
|
|
||||||
|
""" Reuse Authentik Admin Session """
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def admin_session(browser: Browser, dotenv_config, STATES):
|
||||||
|
CONFIG = dotenv_config
|
||||||
|
context = setup_context(browser, f"{STATES}/admin_state.json")
|
||||||
|
page = context.new_page()
|
||||||
|
page.goto(CONFIG["domain"])
|
||||||
|
yield context, page
|
||||||
|
context.close()
|
||||||
|
|
||||||
|
|
||||||
|
""" Reuse Authentik User Session """
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def user_session(browser: Browser, dotenv_config, STATES):
|
||||||
|
CONFIG = dotenv_config
|
||||||
|
context = setup_context(browser, f"{STATES}/user_state.json")
|
||||||
|
page = context.new_page()
|
||||||
|
page.goto(CONFIG["domain"])
|
||||||
|
yield context, page
|
||||||
|
context.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_true():
|
||||||
|
assert 1 + 1 == 2
|
||||||
28
src/tests_wordpress/conftest.py
Normal file
28
src/tests_wordpress/conftest.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# this conftest cannot be executed directly if there is a second conftest
|
||||||
|
# on a higher level. might work if other tests are executed though
|
||||||
|
# -> at least bad for debugging
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import Browser, Locator, expect, sync_playwright
|
||||||
|
|
||||||
|
# playwright = sync_playwright().start()
|
||||||
|
# browser = playwright.chromium.launch(headless=False)
|
||||||
|
|
||||||
|
|
||||||
|
cred_file = Path("../credentials.json")
|
||||||
|
with open(cred_file, "r") as f:
|
||||||
|
CREDENTIALS = json.load(f)
|
||||||
|
|
||||||
|
print(CREDENTIALS)
|
||||||
|
|
||||||
|
RECORDS = Path("records")
|
||||||
|
RECORDS.mkdir(exist_ok=True)
|
||||||
|
STATES = Path("states")
|
||||||
|
STATES.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dummy():
|
||||||
|
assert 1 + 1 == 2
|
||||||
20
src/tests_wordpress/runner_wordpress.py
Normal file
20
src/tests_wordpress/runner_wordpress.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from runner import Runner, SubTest
|
||||||
|
|
||||||
|
|
||||||
|
def condition_always_true(dotenv_path: Path) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def condition_always_false(dotenv_path: Path) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class RunnerWordpress(Runner):
|
||||||
|
test_dir_name = "tests_wordpress"
|
||||||
|
# main_test_name = "test_wordpress.py"
|
||||||
|
sub_tests = [
|
||||||
|
SubTest(condition=condition_always_false, test_file="test_wordpress_feature1.py"),
|
||||||
|
SubTest(condition=condition_always_true, test_file="conftest.py"),
|
||||||
|
]
|
||||||
29
src/tests_wordpress/test_wordpress.py
Normal file
29
src/tests_wordpress/test_wordpress.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
|
||||||
|
def test_one():
|
||||||
|
assert 1 + 1 == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_two():
|
||||||
|
assert 2 + 1 == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_title(page: Page):
|
||||||
|
page.goto("https://playwright.dev/")
|
||||||
|
|
||||||
|
# Expect a title "to contain" a substring.
|
||||||
|
expect(page).to_have_title(re.compile("Playwright"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_started_link(page: Page):
|
||||||
|
page.goto("https://playwright.dev/")
|
||||||
|
|
||||||
|
# Click the get started link.
|
||||||
|
page.get_by_role("link", name="Get started").click()
|
||||||
|
|
||||||
|
# Expects page to have a heading with the name of Installation.
|
||||||
|
expect(page.get_by_role("heading", name="Installation")).to_be_visible()
|
||||||
26
src/tests_wordpress/test_wordpress_feature1.py
Normal file
26
src/tests_wordpress/test_wordpress_feature1.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
from icecream import ic
|
||||||
|
from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
|
||||||
|
def test_one(config):
|
||||||
|
ic(config)
|
||||||
|
assert 1 + 1 == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_title(page: Page):
|
||||||
|
page.goto("https://playwright.dev/")
|
||||||
|
|
||||||
|
# Expect a title "to contain" a substring.
|
||||||
|
expect(page).to_have_title(re.compile("Playwright"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_started_link(page: Page):
|
||||||
|
page.goto("https://playwright.dev/")
|
||||||
|
|
||||||
|
# Click the get started link.
|
||||||
|
page.get_by_role("link", name="Get started").click()
|
||||||
|
|
||||||
|
# Expects page to have a heading with the name of Installation.
|
||||||
|
expect(page.get_by_role("heading", name="Installation")).to_be_visible()
|
||||||
22
src/tests_wordpress/test_wordpress_localization.py
Normal file
22
src/tests_wordpress/test_wordpress_localization.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# WIP localization
|
||||||
|
|
||||||
|
from playwright.sync_api import Page, expect
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_title(page: Page):
|
||||||
|
page.goto("https://playwright.dev/")
|
||||||
|
|
||||||
|
# Expect a title "to contain" a substring.
|
||||||
|
expect(page).to_have_title(re.compile("Playwright"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_wordpress(admin_session):
|
||||||
|
context, page = admin_session
|
||||||
|
with page.expect_popup() as info:
|
||||||
|
page.get_by_role("link", name="Wordpress").click()
|
||||||
|
|
||||||
|
wordpress = info.value
|
||||||
|
check_for(wordpress.locator("#wpcontent"))
|
||||||
|
if CONFIG["locale"] == "de":
|
||||||
|
check_for(wordpress.get_by_role("heading", name="Willkommen bei WordPress!"))
|
||||||
|
context.tracing.stop(path=f"{RECORDS}/wordpress.zip")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue