A generated matching card arrived on a phone with three identical definitions (the generation guards now drop those), but there was no way to get past a bad card from inside a session. The feed chrome (deck study, review, mistakes and exam) now has the red "!" control: one tap records the question, skips to the next card and shows an Undo toast. - lib/question-reports.ts: the local list (freezmo.reports.v1, zod on read, capped 200) is the skip state — reported cards leave every session type, count as "not attempted" in the results instead of wrong, and Undo takes a report back. A deck where everything is reported stays studiable so it can be regenerated. - POST /api/reports appends one JSON line per report to REPORTS_DIR/reports.jsonl (shared/freezmo/reports in production, the dev data dir in dev). Deliberately write-only: the question text is readable on the server (that is the point — prompt tuning) but never over HTTP, since the app has no auth. Best effort: offline reports stay local. - The button: a red outlined rounded square with "!", the chrome's control size, in both feed headers (hidden on the results slide). - config: REPORTS_DIR + RATE_LIMIT_REPORTS (20); the deploy creates the shared reports dir; the dev unit and AGENTS.md note the new server data location. - Suites: phase-1 (append + validation + no GET), phase-2 (button, toast, server POST, local copy, skip counter, hidden next session, Undo, exam pool). SW -> freezmo-v38. |
||
|---|---|---|
| .. | ||
| bubble-perf.mjs | ||
| bubbles-test.mjs | ||
| chat-test.mjs | ||
| freeze-loading-test.mjs | ||
| i18n-test.mjs | ||
| lib.mjs | ||
| mobile-test.mjs | ||
| model-compare.mjs | ||
| native-test.mjs | ||
| phase1-test.mjs | ||
| phase2-test.mjs | ||
| phase3-test.mjs | ||
| README.md | ||
| regression.mjs | ||
| review-test.mjs | ||
| run-all.mjs | ||
| wizard-test.mjs | ||
Browser suites
End-to-end checks for Freezmo, driven by Playwright against a running
production build. Everything lives in this repo (nothing in /tmp), so a
reboot cannot wipe the harness.
Requirements
- The app is running on
http://127.0.0.1:3207(systemctl --user start freezmo). - A Chromium build. The suites auto-detect the newest Playwright Chromium in
~/.cache/ms-playwright/; override withCHROME=/path/to/chrome. npm install(dev deps:playwright-core,axe-core).
Running
npm run build && systemctl --user restart freezmo # fresh build first
npm run test:e2e # every suite, in order
npm run test:e2e -- phase1 # only suites whose name contains "phase1"
Each suite prints PASS/FAIL lines and exits non-zero on failure; the runner
prints a per-suite summary at the end.
Suites
| Suite | Covers |
|---|---|
regression.mjs |
Library, deck card + menu, study feed to results, homework entry, desktop shell + settings/health panel. |
bubbles-test.mjs |
Ice-scene bubbles: stacking/layout contract, keyframes only transform+opacity, rise/sway/scale/fade phases, per-breakpoint counts (5/3/2), phone pacing, reduced motion, tab-hidden parking. |
mobile-test.mjs |
Five-tab bar at 360/390 × EN/CS × dark/light × reduced motion (labels, targets, no hamburger/drawer), the library ⋯ sheet (Import JSON / New folder), the settings sheet (touch targets, key flow, drag-to-close, backdrop), axe contrast audits. |
wizard-test.mjs |
Deck 3-step flow and homework flow, flat action bar, in-page mode switch, the loading lock, abort-on-leave. |
native-test.mjs |
Manifest/PWA head, offline banner + disabled generate, share target GET + POST, Back-gesture layers, undo toast, tab re-tap, haptics, draft restore. |
i18n-test.mjs |
EN/CS dictionary parity, locale switch, Czech desktop shell, Czech phone tab labels and plurals. |
freeze-loading-test.mjs |
The generation loading screen appears immediately, hides the action bar, Cancel returns with the notice. |
review-test.mjs |
Smart Review loading feedback under a stalled request, the session end-to-end, mistakes, and the health-probe cache. |
phase1-test.mjs |
Backup export/import, persistence row, nudge, encrypted sync (enable/join/push/delete + API validation), usage-limit handling (client + a stub Ollama). |
phase2-test.mjs |
Answer history + SRS from a real study run, review/mistakes, stats, exam mode, push reminders hidden without VAPID and validated with it. |
phase3-test.mjs |
Check my work (API + phone/desktop UI), follow-ups, guide step explain, turn-into-a-study-deck. |
chat-test.mjs |
Chat: streaming API (validation, allowlist, context travel, history window, thinking separation, usage-limit mapping), attachment extraction, entry points, streaming/stop, markdown + KaTeX, model/scope pickers, edit/regenerate, offline, reduced motion, plus history (IDB persistence, reload, rename/search/delete/undo, scope+model memory), assistant actions, the export payload and a two-device sync round-trip. |
Extra instances
phase1, phase2 and phase3 start their own next start instance with a
stub Ollama (or VAPID keys) on a spare port (3208/3209). Each suite checks the
port is free first and kills the whole process group on exit, so ports do not
leak between suites. Scratch data for the VAPID test lives in tests/.tmp/
(gitignored).
Diagnostics (not part of the run)
node tests/bubble-perf.mjs [label] measures the ice-scene frame cost with a
CDP performance trace (rAF frame timing, long tasks, main-thread busy time,
style recalcs) on a 4× throttled phone profile and a 2× desktop profile. It
always exits 0; the numbers are for tuning the bubble count/speed.