"""One launch of Wonder Ink, its recent tabs restored or not, and a few steps. usage: wonderink-workspace-rig.py WONDERINK_COMMAND SITE STEP... Run by tools/lab/wonderink-workspace-check inside the lab, against the command the package installed, started as main() starts it: the real profile, downloads connected, then open_workspace() — which restores the recent tabs or opens the start page. The start page is the lab's stand-in for WonderInk's sign-in (tools/lab/wonderink-lesson-site.py, /app) rather than the real site. Steps, in order: open:PATH open SITE+PATH (or a full http address) in a new tab, in front worksheet:NAME a worksheet PDF downloaded and opened in a tab signin click the stand-in's Sign In (a real click) touch one real click on the page in front front:N bring tab N to the front, as clicking it does (a real click) close:N close tab N, as its × does move:A:B drag tab A to where B is preview:N the print preview open N seconds, nobody touching it print Print, and the preview's Print button pressed by a real click wait:N N seconds, nobody touching anything state what the tabs are, and what the kept file says quit close the window crash SIGKILL, no chance to write anything """ import importlib.machinery import importlib.util import json import os import signal import sys import time from pathlib import Path loader = importlib.machinery.SourceFileLoader("wonderink", sys.argv[1]) spec = importlib.util.spec_from_loader("wonderink", loader) wi = importlib.util.module_from_spec(spec) loader.exec_module(wi) from PyQt6.QtCore import QEvent, QEventLoop, QPointF, Qt, QTimer # noqa: E402 from PyQt6.QtGui import QMouseEvent, QPageSize, QPainter, QPdfWriter # noqa: E402 from PyQt6.QtWidgets import QApplication # noqa: E402 site, steps = sys.argv[2].rstrip("/"), sys.argv[3:] wi.HOME_PAGE = f"{site}/app" wi.notify = lambda summary, body: print("NOTIFY", summary, "|", body, flush=True) app = QApplication(sys.argv[:1]) t0 = time.monotonic() def stamp(message): print(f"[{time.monotonic() - t0:6.2f}s] {message}", flush=True) profile = wi.build_profile(app, "workspace-rig") wi.DOWNLOADS.mkdir(parents=True, exist_ok=True) window = wi.WonderInk(profile) profile.downloadRequested.connect(window.on_download) keeper = wi.open_workspace(window) window.resize(1100, 700) window.show() stamp(f"START tabs={window._tabs.count()} attempts={keeper._attempts}") def spin(seconds, until=None): end = time.monotonic() + seconds while time.monotonic() < end: app.processEvents(QEventLoop.ProcessEventsFlag.AllEvents, 50) if until is not None and until(): return True time.sleep(0.01) return False def js(code, view=None): box = [] (view or window.current()).page().runJavaScript(code, lambda result: box.append(result)) spin(5, lambda: bool(box)) return box[0] if box else None def press(widget, point, button=Qt.MouseButton.LeftButton): for kind, buttons in ((QEvent.Type.MouseButtonPress, button), (QEvent.Type.MouseButtonRelease, Qt.MouseButton.NoButton)): QApplication.sendEvent(widget, QMouseEvent(kind, point, QPointF(widget.mapToGlobal(point)), button, buttons, Qt.KeyboardModifier.NoModifier)) def click_page(selector="body"): view = window.current() where = js("(function(){var e=document.querySelector(%s); if(!e) return [20, 20]; var r=e.getBoundingClientRect();" "return [r.x+Math.min(r.width/2, 40), r.y+Math.min(r.height/2, 10)]})()" % json.dumps(selector)) or [20, 20] zoom = view.zoomFactor() press(view.focusProxy(), QPointF(where[0] * zoom, where[1] * zoom)) def where_of(view): # A restored tab not yet shown is the tab it will load when it is. if view in window._pending: kept = keeper._pending_tabs[view] if kept["kind"] == "worksheet": return "worksheet:" + Path(kept["where"]).name return kept["where"].replace(site, "") if view in window._pdf_views: return "worksheet:" + window._pdf_views[view].name return view.url().toString().replace(site, "") def state(label="STATE"): tabs = [where_of(window._tabs.widget(i)) for i in range(window._tabs.count())] # What the stand-in's start page says in each tab that shows it. pages = [js("document.getElementById('state') ? document.getElementById('state').textContent : ''", window._tabs.widget(i)) for i in range(window._tabs.count())] kept = "none" if wi.WORKSPACE_FILE.exists(): data = json.loads(wi.WORKSPACE_FILE.read_text()) kept = (f"tabs={len(data['tabs'])} active={data['active']} attempts={data['attempts']} " f"age={time.time() - data['last_activity']:.0f}") stamp(f"{label} tabs={tabs} active={window._tabs.currentIndex()} kept[{kept}] pages={[p for p in pages if p]} " f"unshown={len(window._pending)}") def make_worksheet(name): path = wi.DOWNLOADS / f"{name}.pdf" writer = QPdfWriter(str(path)) writer.setPageSize(QPageSize(QPageSize.PageSizeId.Letter)) painter = QPainter(writer) painter.drawText(200, 400, f"WORKSHEET {name}") painter.end() return path def run(): spin(3) for step in steps: name, _, rest = step.partition(":") stamp(f"STEP {step}") if name == "open": window.open_tab(rest if rest.startswith("http") else site + rest) spin(2.5) elif name == "worksheet": window.open_pdf(make_worksheet(rest)) spin(2.5) elif name == "signin": click_page("#signin") spin(1.5) elif name == "touch": click_page() spin(1.5) elif name == "front": bar = window._tabs.tabBar() press(bar, QPointF(bar.tabRect(int(rest)).center())) spin(1.5) elif name == "close": window.close_tab(int(rest)) spin(1.5) elif name == "move": a, b = (int(n) for n in rest.split(":")) window._tabs.tabBar().moveTab(a, b) spin(1.5) elif name == "preview": QTimer.singleShot(int(float(rest) * 1000), lambda: window._preview.reject() if window._preview else None) window.print_view(window.current()) spin(1.5) elif name == "print": def pressed(): preview = window._preview if preview is None or preview._pdf is None: return QTimer.singleShot(200, pressed) button = preview._print press(button, QPointF(button.rect().center())) QTimer.singleShot(500, pressed) window.print_view(window.current()) spin(2) stamp(f"PRINTED preview_open={window._preview is not None}") elif name == "wait": spin(float(rest)) elif name == "state": state() elif name == "quit": window.close() spin(1) state("QUIT") app.quit() return elif name == "crash": stamp("CRASH") os.kill(os.getpid(), signal.SIGKILL) app.quit() QTimer.singleShot(0, run) QTimer.singleShot(240_000, lambda: (stamp("TIMEOUT"), app.exit(3))) sys.exit(app.exec())