feat(ui): replace configuration editors with graphical forms
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""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"]}))
|
||||
Reference in New Issue
Block a user