#!/usr/bin/env python3 """ Measure a piece of the SPA at a phone width, in a real browser. The responsive tests up to now pinned numbers out of the stylesheet, with a docstring admitting that a layout cannot be measured because there is no browser in the suite. There is one: Chrome is what the video work has been verified against. Reading `width: 330px` out of a rule says nothing about whether the thing lands on the screen — that depends on where its anchor sits, which depends on everything to its right. Renders the real style.css with a fragment of markup, at a given viewport, and reports the bounding box of each selector asked for. layout_probe.py [...] One browser for all the widths asked for: an iframe apiece, measured in a single pass. Launching Chrome per width put three minutes on the test suite. """ import http.server import json import shutil 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 = 8734 PAGE = """
""" RECORDS = [] def main() -> int: widths = [int(w) for w in sys.argv[1].split(",")] fragment = Path(sys.argv[2]).read_text() selectors = sys.argv[3:] class H(http.server.BaseHTTPRequestHandler): def log_message(self, *a): pass def do_POST(self): RECORDS.append(json.loads( self.rfile.read(int(self.headers["Content-Length"])).decode())) self.send_response(204) self.end_headers() def do_GET(self): if self.path == "/": body = (PAGE % {"fragment": json.dumps(fragment), "widths": json.dumps(widths), "selectors": json.dumps(selectors)}).encode() ctype = "text/html; charset=utf-8" elif self.path == "/style.css": body = (STATIC / "style.css").read_bytes() ctype = "text/css" else: self.send_response(404) self.end_headers() return 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) class S(socketserver.ThreadingTCPServer): allow_reuse_address = True daemon_threads = True srv = S(("127.0.0.1", PORT), H) threading.Thread(target=srv.serve_forever, daemon=True).start() # mkdtemp left a Chrome profile in /tmp on every run, for ever, and nothing # waited for Chrome to exit. Same cleanup rule as the other probes. profile = tempfile.mkdtemp(prefix="chrome-layout-") chrome = subprocess.Popen([ "google-chrome", "--headless=new", "--no-sandbox", "--window-size=1000,900", "--user-data-dir=" + profile, f"http://127.0.0.1:{PORT}/", ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) deadline = time.time() + 45 while time.time() < deadline and not RECORDS: time.sleep(0.2) chrome.terminate() try: chrome.wait(timeout=10) except subprocess.TimeoutExpired: chrome.kill() chrome.wait() shutil.rmtree(profile, ignore_errors=True) srv.shutdown() if not RECORDS: print(json.dumps({"error": "no measurement"})) return 1 print(json.dumps(RECORDS[0])) return 0 if __name__ == "__main__": sys.exit(main())