Files
BartelLuis 7b979e6243
CI / javascript-check (push) Successful in 14s
CI / container-policy (push) Successful in 4s
CI / python-tests (push) Successful in 1m48s
CI / container-verify (push) Skipped
CI / container-publish (push) Successful in 49s
feat(ui): replace configuration editors with graphical forms
2026-09-14 21:52:39 +02:00

99 lines
3.8 KiB
Python

"""Isolated localhost application and Chromium support for browser_forms.py.
Only generated test credentials/data are used. Fixture databases, server logs,
and screenshots are written below the repository's ignored .cache/ui-browser.
The server process and browser are always terminated after each scenario group.
"""
from contextlib import contextmanager
import json
import os
from pathlib import Path
import socket
import subprocess
import sys
import tempfile
import time
import httpx
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[1]
TEST_PASSWORD = "Browser-fixture-admin-2026!"
@contextmanager
def local_browser(viewport=None):
with socket.socket() as listener:
listener.bind(("127.0.0.1", 0))
port = listener.getsockname()[1]
base = f"http://127.0.0.1:{port}"
artifacts = ROOT / ".cache" / "ui-browser"
artifacts.mkdir(parents=True, exist_ok=True)
tempdir = Path(tempfile.mkdtemp(prefix="fixture-", dir=artifacts))
env = {
**os.environ,
"PYTHONPATH": str(ROOT),
"DATA_DIR": str(tempdir / "data"),
"MASTER_KEY_FILE": str(tempdir / "keys" / "master.key"),
"PUBLIC_URL": base,
"SECURE_COOKIES": "false",
"BOOTSTRAP_USERNAME": "admin",
"BOOTSTRAP_PASSWORD": TEST_PASSWORD,
"TESTING": "true",
"FOUR_EYES": "false",
}
log = (tempdir / "server.log").open("w", encoding="utf-8")
server = subprocess.Popen(
[sys.executable, "-m", "provisioner.cli", "serve", "--host", "127.0.0.1", "--port", str(port)],
cwd=ROOT, env=env, stdout=log, stderr=subprocess.STDOUT,
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
)
try:
for _ in range(80):
if server.poll() is not None:
raise RuntimeError((tempdir / "server.log").read_text(encoding="utf-8"))
try:
if httpx.get(base + "/health/ready", timeout=1).status_code == 200:
break
except httpx.HTTPError:
pass
time.sleep(0.15)
else:
raise TimeoutError("Local application did not become ready")
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
context = browser.new_context(viewport=viewport or {"width": 1440, "height": 1000})
page = context.new_page()
errors = []
page.on("pageerror", lambda error: errors.append(str(error)))
page.goto(base + "/")
page.get_by_label("Benutzername").fill("admin")
page.get_by_label("Passwort", exact=True).fill(TEST_PASSWORD)
page.get_by_role("button", name="Anmelden").click()
page.wait_for_selector("#main .page-header")
csrf = context.request.get(base + "/api/v1/me").json()["csrf_token"]
def api(path, body=None):
response = context.request.get(base + "/api/v1" + path) if body is None else context.request.post(
base + "/api/v1" + path, data=body, headers={"X-CSRF-Token": csrf})
assert response.ok, f"{path}: {response.status} {response.text()}"
return response.json()
yield page, api, {"base": base, "artifacts": artifacts, "fixture": tempdir, "errors": errors}
assert not errors, errors
context.close()
browser.close()
finally:
server.terminate()
try:
server.wait(timeout=5)
except subprocess.TimeoutExpired:
server.kill()
server.wait(timeout=5)
log.close()
if __name__ == "__main__":
with local_browser() as (page, api, context):
print(json.dumps({"title": page.title(), "url": page.url, "me": api("/me")["username"], "errors": context["errors"]}))