99 lines
3.8 KiB
Python
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"]}))
|