#!/usr/bin/env python3
"""
The playlist menus, pressed in a real browser.
`playlists.js` is covered against a stubbed node by `playlist_store_probe.py`,
and the merge and the sealing by their own tests. None of that reaches the part
a person actually touches: whether the toolbar button opens a menu, whether
naming a playlist in a field works (Electron has no `prompt` — it throws), and
whether "add to playlist" on an album cover puts the right tracks in the right
playlist.
So this renders the shipped `GroupPage`, `MusicPlayerBar` and playlist menus,
presses the real controls, and reads the result back out of the store.
playlist_ui_probe.py
Prints JSON: one entry per step.
"""
import http.server
import json
import socketserver
import subprocess
import sys
import tempfile
import threading
import time
from pathlib import Path
STATIC = Path(__file__).resolve().parents[2] / "src" / "meshbay_hub" / "static"
PORT = 8755
RECORDS = []
socketserver.TCPServer.allow_reuse_address = True
FRAME = r"""
"""
PAGE = r"""
"""
class H(http.server.BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def do_POST(self):
length = int(self.headers.get("Content-Length") or 0)
if self.path == "/log":
RECORDS.append(json.loads(self.rfile.read(length).decode()))
else:
self.rfile.read(length)
self.send_response(204)
self.end_headers()
def _send(self, body: bytes, ctype: str) -> None:
self.send_response(200)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
path = self.path.split("?")[0]
if path == "/":
self._send(PAGE.encode(), "text/html; charset=utf-8")
elif path == "/case":
self._send(FRAME.encode(), "text/html; charset=utf-8")
elif path == "/v1/groups/g1/nodes":
self._send(b'{"nodes": [{"node_id": "n1"}]}', "application/json")
else:
asset = (STATIC / path.lstrip("/")).resolve()
if not str(asset).startswith(str(STATIC)) or not asset.is_file():
self.send_response(404)
self.end_headers()
return
self._send(asset.read_bytes(),
"text/css" if asset.suffix == ".css"
else "text/javascript" if asset.suffix == ".js"
else "application/octet-stream")
def main() -> int:
with socketserver.TCPServer(("127.0.0.1", PORT), H) as srv:
threading.Thread(target=srv.serve_forever, daemon=True).start()
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as profile:
proc = subprocess.Popen(
["google-chrome", "--headless=new", "--disable-gpu", "--no-sandbox",
f"--user-data-dir={profile}", "--window-size=1100,900",
f"http://127.0.0.1:{PORT}/"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
for _ in range(400):
if RECORDS:
break
time.sleep(0.1)
proc.terminate()
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
if not RECORDS:
print(json.dumps({"error": "no measurement"}), file=sys.stderr)
return 1
print(json.dumps(RECORDS[0], indent=1))
return 0
if __name__ == "__main__":
raise SystemExit(main())