Files
ios-advanced-parental-controls/scripts/browser_smoke.py
T
2026-09-17 17:59:34 +02:00

90 lines
5.0 KiB
Python

"""Real Chromium DOM + real ASGI backend, bridged in-process (no network deployment test).
Requires playwright. Browser cookie/CSRF behavior is covered separately in test_api.py.
The bridge uses TestClient's cookie jar and supplies the configured same-origin header.
"""
import os
from pathlib import Path
import secrets
import sys
import tempfile
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "backend" / "src"))
from fastapi.testclient import TestClient
from playwright.sync_api import sync_playwright
from apc.api import Settings, create_app
def main():
with tempfile.TemporaryDirectory() as directory:
token = secrets.token_urlsafe(32)
app = create_app(Settings(token, str(Path(directory) / "smoke.db")))
with TestClient(app) as client, sync_playwright() as pw:
browser = pw.chromium.launch(executable_path=os.environ.get("CHROMIUM_PATH") or None,
args=["--no-sandbox"])
page = browser.new_page(viewport={"width": 1280, "height": 900})
errors = []
page.on("pageerror", lambda error: errors.append(str(error)))
def request(path, options):
response = client.request(options.get("method", "GET"), path,
content=options.get("body"), headers={"Content-Type": "application/json",
"Origin": "http://localhost:8000"})
return {"status": response.status_code, "body": response.text}
page.expose_function("asgiRequest", request)
html = client.get("/").text.replace('<script defer src="/static/app.js"></script>', "")
html = html.replace('<link rel="stylesheet" href="/static/style.css">', "")
page.set_content(html)
page.add_style_tag(content=client.get("/static/style.css").text)
page.evaluate("""() => { window.fetch = async (path, options = {}) => {
const response = await window.asgiRequest(path, options);
return new Response(response.body, {status: response.status,
headers: {'Content-Type': 'application/json'}});
}; }""")
page.add_script_tag(content=client.get("/static/app.js").text)
page.get_by_label("Administration token").fill(token)
page.get_by_role("button", name="Sign in", exact=True).click()
page.locator("#dashboard").wait_for(state="visible")
for name in ("Example A", "Example B"):
page.get_by_label("Display name", exact=True).fill(name)
page.get_by_role("button", name="Add child", exact=True).click()
page.get_by_role("heading", name=name, exact=True).wait_for()
page.locator("#policy [name=daily_minutes]").fill("40")
page.get_by_role("button", name="Save shared policy").click()
page.get_by_text("Shared policy saved.", exact=False).wait_for()
assert page.locator(".child").count() == 2
first = page.locator(".child").first
first.get_by_text("Individual exceptions", exact=True).click()
first.locator("[name=daily_minutes]").fill("45")
first.get_by_role("button", name="Save exceptions").click()
page.get_by_text("Exceptions saved.", exact=False).wait_for()
first = page.locator(".child").first
assert "45 daily minutes" in first.inner_text()
first.get_by_role("button", name="Pair phone").click()
page.locator("#code-dialog").wait_for(state="visible")
code = page.locator("#issued-code").inner_text()
paired = client.post("/v1/pair", json={"code": code, "name": "Smoke test"}).json()
headers = {"Authorization": f"Bearer {paired['token']}"}
challenge = client.post("/v1/device/challenges", headers=headers).json()
answers = []
for q in challenge["questions"]:
a, op, b = q["prompt"].split()
answers.append({"question_id": q["id"], "answer": int(a) + int(b) if op == "+" else int(a) - int(b)})
result = client.post(f"/v1/device/challenges/{challenge['id']}/submit",
headers=headers, json={"answers": answers})
assert result.json()["awarded_minutes"] == 10
page.get_by_role("button", name="Close", exact=True).click()
page.get_by_role("button", name="Refresh status").click()
page.get_by_text("45 daily minutes + 10 earned today", exact=False).wait_for()
if os.environ.get("APC_SCREENSHOT"):
page.screenshot(path=os.environ["APC_SCREENSHOT"], full_page=True)
page.get_by_role("button", name="Sign out").click()
page.locator("#login-panel").wait_for(state="visible")
assert not errors, errors
browser.close()
print("Browser/ASGI smoke passed: login, two children, policy, override, pairing, reward, logout")
if __name__ == "__main__":
main()