freezmo/tests/freeze-loading-test.mjs
Martin Dvorak dbd5dfbc58 Move the e2e suites into the repo (tests/), out of /tmp
The browser harness lived in /tmp/opencode and an overnight reboot
wiped it. It now lives in tests/ and survives reboots, is versioned
and runs with one command.

- tests/lib.mjs: shared helpers (Chromium auto-detect, seeding,
  IndexedDB fixtures, stub/extra instance spawn with process-group
  cleanup, check/finish).
- tests/run-all.mjs + npm run test:e2e [filter]: runs the ten suites
  in order with a summary; suites that need a stub Ollama or VAPID
  keys start their own next start instance on 3208/3209, verify the
  port is free and kill the group on exit. Test scratch in
  tests/.tmp/ (gitignored).
- Suites rebuilt and green against the current build: regression 38,
  mobile 94 (tab matrix, ... sheet, settings sheet, axe), wizard 83,
  native 31 (PWA/offline/share/back/undo/haptics/draft), i18n 13,
  freeze-loading 6, review 15, phase1 36 (backup/sync/usage limits),
  phase2 42 (learning engine/reminders), phase3 21 (homework check).
  379 checks, 10/10 suites in one full run.
- playwright-core + axe-core added as devDependencies; CHROME env or
  the newest ~/.cache/ms-playwright chromium is used.
- README: new decision 45 documenting the harness; decision 44's
  verification note updated.

The older suites' exact sources were lost with the wipe, so the
rebuilt versions reconstruct the same feature areas rather than
being byte-identical.
2026-10-11 15:11:06 +00:00

45 lines
2 KiB
JavaScript

/**
* Freeze loading screen: it must appear the moment generation starts, keep
* the wizard's action bar out of the way, and Cancel must return to the
* wizard with the cancelled notice (not a stuck spinner).
*/
import { BASE, launchBrowser, makeChecker, makePage, seed } from "./lib.mjs";
const { check, finish } = makeChecker();
const browser = await launchBrowser();
const { context, page } = await makePage(browser);
await seed(page, { decks: [] });
// Hold the generation so the loading screen stays up.
await page.route("**/api/generate", () => new Promise(() => {}));
await page.goto(`${BASE}/create`, { waitUntil: "networkidle" });
await page.waitForTimeout(800);
await page.getByRole("button", { name: "Paste text instead" }).click();
await page.locator("textarea").first().fill("Photosynthesis needs light and water.");
await page.getByRole("button", { name: "Next" }).click();
await page.waitForTimeout(300);
await page.getByRole("button", { name: "Next" }).click();
await page.waitForTimeout(300);
const tClick = Date.now();
await page.getByRole("button", { name: "Freeze my deck" }).click();
let appeared = true;
await page
.locator('img[src="/icons/icon-512.png"]')
.waitFor({ state: "visible", timeout: 2000 })
.catch(() => {
appeared = false;
});
check("loading screen appears immediately after Freeze", appeared, `${Date.now() - tClick} ms`);
check("stage message visible", await page.getByText(/Reading your notes|Finding the key ideas/).first().isVisible().catch(() => false));
check("cancel available", await page.getByRole("button", { name: "Cancel" }).isVisible().catch(() => false));
check("wizard bar is gone", (await page.locator("[data-wizard-bar]").count()) === 0);
await page.getByRole("button", { name: "Cancel" }).click();
await page.waitForTimeout(700);
check("cancel returns to the wizard", (await page.locator("[data-wizard-bar]").count()) === 1);
check("cancel shows the notice + retry", await page.getByRole("button", { name: "Retry" }).isVisible());
await context.close();
await browser.close();
finish();