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.
45 lines
2 KiB
JavaScript
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();
|