#!/usr/bin/env python3 """Is there exactly one analytics implementation, reported from admin? — D5, T7.10. Usage analytics existed twice (creator + wizard), five of the nine colliding globals `creator-frame.md` counted, and the wizard's copy had no caller. One core survives in wp-usage.js; the pages keep thin track() wrappers; the report and its downloads live on the admin console behind the same role gate as the rest of that page. Keys are unchanged, so pre-move data still reads. Self-contained: throwaway SQLite, its own uvicorn, headless Edge or Chrome. Exit 0 all passed, 1 a failure, 2 could not run. """ import json import os import re import sys import tempfile import time sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import cdp # noqa: E402 from browser_check import seed, start_server, chk, _PASS, _FAIL # noqa: E402 from sections_check import set_sop # noqa: E402 from stepper_check import dismiss_dialogs # noqa: E402 ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) HTML = os.path.join(ROOT, "html") def ascii_(v, n=280): return re.sub(r"\s+", " ", str(v)).encode("ascii", "replace").decode()[:n] def settle(seconds=0.6): time.sleep(seconds) def strip_js(src): src = re.sub(r"/\*.*?\*/", "", src, flags=re.S) return "\n".join(re.sub(r"(? { const d = JSON.parse(localStorage.getItem('wp_iwp_analytics_v1')) || {events: []}; d.events.unshift({ts: '2026-07-01T10:00:00Z', session: 's_legacy', event: 'legacy_probe_event', detail: null}); localStorage.setItem('wp_iwp_analytics_v1', JSON.stringify(d)); })()""") n0 = page.eval("WPUsage.load(WPUsage.KEYS.creator).events.length") page.eval("track('probe_event')") chk("the creator's track() still lands events under the old key", page.eval("WPUsage.load(WPUsage.KEYS.creator).events.length") == n0 + 1) page.goto(base + "/work-package-suite.html?tab=sop") dismiss_dialogs(page) settle(2.0) n0 = page.eval("WPUsage.load(WPUsage.KEYS.wizard).events.length") page.eval("track('probe_event_wizard')") chk("the wizard's track() still lands events under its old key", page.eval("WPUsage.load(WPUsage.KEYS.wizard).events.length") == n0 + 1) wiz_errors = [e for e in page.js_errors() if "beforeunload" not in e] chk("...and the wizard page throws no errors without its old globals " "(the blocked-beforeunload console line is BL-020, filtered not hidden)", not wiz_errors, ascii_(wiz_errors[:2])) # ── 3. the report, on admin, behind the admin gate ─────────────────── print("\n3. the admin report") page.goto(base + "/admin.html") dismiss_dialogs(page) settle(2.0) page.eval("loadUsage()") settle(0.5) report = page.eval("(document.getElementById('usage-admin')||{textContent:''}).textContent") chk("usage data is reachable from admin.html, both tools reported", "Work package creator" in report and "SOP wizard" in report, ascii_(report, 160)) chk("the pre-move legacy event is readable in the report", "legacy_probe_event" in report) chk("this session's fresh events are in it too", "probe_event" in report and "probe_event_wizard" in report) chk("each tool offers its download from the report", page.eval("[...document.querySelectorAll('#usage-admin button')].length") >= 2) page.viewport(390, 844, mobile=True) settle(0.6) fits = page.eval("""(() => { const box = document.getElementById('usage-admin'); return box && box.scrollWidth <= box.clientWidth + 2 && document.documentElement.scrollWidth <= 392; })()""") chk("the report is usable at 390px - no sideways scrolling", bool(fits)) page.viewport(1440, 900) settle(0.4) # The same role gate as the rest of the console: a non-admin sees the # denied card and no cards, this one included. page.clear_cookies() page.set_cookie("wp_session", tok["pat"]) page.goto(base + "/admin.html") dismiss_dialogs(page) settle(2.0) chk("a non-administrator gets the denied notice, not the usage report", page.eval("""(() => { const denied = document.getElementById('admin-denied'); const usage = document.getElementById('usage-admin'); const visible = el => !!el && el.offsetParent !== null; return visible(denied) && !visible(usage); })()""")) js_errors = [e for e in page.js_errors() if "beforeunload" not in e] chk("no JavaScript errors anywhere in this run", not js_errors, ascii_(js_errors[:2])) finally: if browser is not None: try: browser.close() except Exception: pass if server is not None: try: server.terminate() except Exception: pass print("\n" + "-" * 54) print("%d/%d checks passed." % (len(_PASS), len(_PASS) + len(_FAIL))) for f in _FAIL: print(" - " + f) return 1 if _FAIL else 0 if __name__ == "__main__": sys.exit(main())