diff --git a/.gitignore b/.gitignore index 47caf4d..4395fbc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ __pycache__/ *.json *.zip TestResults/ +.vscode/ +*.pyc +credentials* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2cb2d96 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "envfiles"] + path = envfiles + url = ssh://git@git.local-it.org:2222/local-it-infrastructure/dev.local-it.cloud.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..662da0f --- /dev/null +++ b/Dockerfile @@ -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 \ No newline at end of file diff --git a/README.md b/README.md index 4d956f1..acf5183 100644 --- a/README.md +++ b/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 -playwright install +Force rebuild with cache -# 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 +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5da046e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,6 @@ +services: + app: + build: . + container_name: python-env + volumes: + - .:/code diff --git a/envfiles b/envfiles new file mode 160000 index 0000000..a8375f6 --- /dev/null +++ b/envfiles @@ -0,0 +1 @@ +Subproject commit a8375f6fc7a285a1000b5553be47eaf19b0be0a6 diff --git a/previous-work/README.md b/previous-work/README.md new file mode 100644 index 0000000..4d956f1 --- /dev/null +++ b/previous-work/README.md @@ -0,0 +1,11 @@ + +# Installation + +pip install pytest-playwright +playwright install + +# Run Tests: + +pytest -k nextcloud + +playwright show-trace trace.zip diff --git a/authentik_test.py b/previous-work/authentik_test.py similarity index 100% rename from authentik_test.py rename to previous-work/authentik_test.py diff --git a/config.yaml b/previous-work/config.yaml similarity index 100% rename from config.yaml rename to previous-work/config.yaml diff --git a/conftest.py b/previous-work/conftest.py similarity index 100% rename from conftest.py rename to previous-work/conftest.py diff --git a/nextcloud_test.py b/previous-work/nextcloud_test.py similarity index 100% rename from nextcloud_test.py rename to previous-work/nextcloud_test.py diff --git a/pytest.ini b/previous-work/pytest.ini similarity index 100% rename from pytest.ini rename to previous-work/pytest.ini diff --git a/vikunja_test.py b/previous-work/vikunja_test.py similarity index 100% rename from vikunja_test.py rename to previous-work/vikunja_test.py diff --git a/wekan_test.py b/previous-work/wekan_test.py similarity index 100% rename from wekan_test.py rename to previous-work/wekan_test.py diff --git a/wordpress_test.py b/previous-work/wordpress_test.py similarity index 100% rename from wordpress_test.py rename to previous-work/wordpress_test.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4a80e86 --- /dev/null +++ b/pyproject.toml @@ -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" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..66c69c3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +pytest +pytest-playwright +python-dotenv +icecream \ No newline at end of file diff --git a/src/blog.dev.local-it.cloud.env b/src/blog.dev.local-it.cloud.env new file mode 100644 index 0000000..600aaf6 --- /dev/null +++ b/src/blog.dev.local-it.cloud.env @@ -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 diff --git a/src/conftest.py b/src/conftest.py new file mode 100644 index 0000000..5c0bcee --- /dev/null +++ b/src/conftest.py @@ -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"] diff --git a/src/dirmanager.py b/src/dirmanager.py new file mode 100644 index 0000000..dfc149d --- /dev/null +++ b/src/dirmanager.py @@ -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) diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..3100621 --- /dev/null +++ b/src/main.py @@ -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() diff --git a/src/runner.py b/src/runner.py new file mode 100644 index 0000000..9e0a09b --- /dev/null +++ b/src/runner.py @@ -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) diff --git a/src/setup/setup_authentik.py b/src/setup/setup_authentik.py new file mode 100644 index 0000000..0c7307b --- /dev/null +++ b/src/setup/setup_authentik.py @@ -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() diff --git a/src/tests_authentik/runner_authentik.py b/src/tests_authentik/runner_authentik.py new file mode 100644 index 0000000..1ef97b8 --- /dev/null +++ b/src/tests_authentik/runner_authentik.py @@ -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" diff --git a/src/tests_authentik/test_authentik_dummy.py b/src/tests_authentik/test_authentik_dummy.py new file mode 100644 index 0000000..d20e1cf --- /dev/null +++ b/src/tests_authentik/test_authentik_dummy.py @@ -0,0 +1,2 @@ +def test_true(): + assert 1 + 1 == 2 diff --git a/src/tests_authentik/test_authentik_setup.py b/src/tests_authentik/test_authentik_setup.py new file mode 100644 index 0000000..36a22f3 --- /dev/null +++ b/src/tests_authentik/test_authentik_setup.py @@ -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 diff --git a/src/tests_wordpress/conftest.py b/src/tests_wordpress/conftest.py new file mode 100644 index 0000000..d78bb85 --- /dev/null +++ b/src/tests_wordpress/conftest.py @@ -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 diff --git a/src/tests_wordpress/runner_wordpress.py b/src/tests_wordpress/runner_wordpress.py new file mode 100644 index 0000000..3d60aa2 --- /dev/null +++ b/src/tests_wordpress/runner_wordpress.py @@ -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"), + ] diff --git a/src/tests_wordpress/test_wordpress.py b/src/tests_wordpress/test_wordpress.py new file mode 100644 index 0000000..5d5b407 --- /dev/null +++ b/src/tests_wordpress/test_wordpress.py @@ -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() diff --git a/src/tests_wordpress/test_wordpress_feature1.py b/src/tests_wordpress/test_wordpress_feature1.py new file mode 100644 index 0000000..865fd93 --- /dev/null +++ b/src/tests_wordpress/test_wordpress_feature1.py @@ -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() diff --git a/src/tests_wordpress/test_wordpress_localization.py b/src/tests_wordpress/test_wordpress_localization.py new file mode 100644 index 0000000..ca41ffe --- /dev/null +++ b/src/tests_wordpress/test_wordpress_localization.py @@ -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")