Files
Classroom_Desktop/tools/lab/wonderink-workspace-rig.py
T
ekklesiaandClaude Opus 5.5 78a844968c test: the lab restores real tabs across real launches, and follows real links
tools/lab/wonderink-workspace-check launches the installed command again
and again against a stand-in for WonderInk's start page whose sign-in is
Basic auth, as WonderInk's is: restored after 5 and 90 minutes; fresh
after two hours and a minute, a day and a week; reopened untouched until
it lapses; a click after a restore counts and the restore does not; a
busy page and an untouched print preview do not; Print does; closed,
reordered, worksheet and missing-worksheet tabs; killed outright; a
restore that crashes twice; corrupt, future-format and half-written
files; the file holding no credential; restored tabs printing. 59 checks.

The print check follows every kind of link on a signed-in page with one
click — new tab in front for links and window.open, nothing for buttons
and anchors, same-tab for no target, Back, closing — and prints a PDF and
a picture reached by a link, and a page after several tabs. 0.2.6.3's
scenarios are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 21:12:17 -05:00

202 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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())