v0.2 Phase 0+1: planning doc + tooling baseline
Phase 0 — docs/release/v0.2-frontend-overhaul.md as single source of truth for the v0.2 frontend coherence pass. Records hard rules, tooling pins, sacred-behaviour contract list, wrapper catalogue, per-page migration order, verification matrix, KI-05 plan, and the explicit "DO NOT add Skeleton" line. Phase 1 — tooling baseline. All exact-pinned per the plan: - @playwright/test@1.60.0 + playwright@1.60.0 + @axe-core/playwright@4.11.3 - rollup-plugin-visualizer@7.0.1 (wired behind ANALYZE=1) - @vitest/browser@4.1.6 + @vitest/browser-playwright@4.1.6 (provider) - vitest-browser-svelte@2.1.1 (runes-aware Svelte 5 bridge) - cargo-nextest installed globally New configs: playwright.config.ts (frontend-only, dev:frontend webServer, 900x700 + 1440x900 projects, visual baselines deferred), vitest.browser.config.js (separate from jsdom suite). New scripts: test:e2e, test:e2e:ui, test:browser, analyze, test:rust:fast, guard:no-skeleton. guard-no-skeleton.mjs walks package.json + package-lock + src/ for any @skeletonlabs reference and exits 1 if found — locks in the no-Skeleton hard rule for any future agent. Smoke baseline (tests/e2e/smoke.spec.ts): 16 tests passing across two viewports — app loads without Tauri runtime, keyboard nav, axe scan (color-contrast deferred to Phase 7 per docs/release/v0.1-contrast-audit.md), light/dark theme cycle, all three sensory zones (cave/energy/reset). 10 screenshots emitted to test-results/ as non-failing artefacts. Surfaced + fixed two browser-preview bugs while landing the baseline: - src/lib/utils/osInfo.ts: FALLBACK_BROWSER_INFO was evaluated at SSR module-load (navigator undefined → os: 'unknown'), and the UA check ran before navigator.platform — so Playwright's Windows-UA Chromium on a Linux runner detected as Windows, useCustomChrome went true, Titlebar mounted and tripped Tauri-only APIs. Now lazy-built per call; platform is the primary signal, UA only a fallback. - src/lib/components/Titlebar.svelte: defensive hasTauriRuntime() guard on every handler and the $effect. Titlebar should not crash if any future code path mounts it without Tauri. .gitignore: reports/, .playwright/, test-results/, playwright-report/. Per-phase gate green: npm run check (0/0), npm test (0/0), npm run test:e2e (16/16), npm run guard:no-skeleton (clean). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
91
tests/e2e/smoke.spec.ts
Normal file
91
tests/e2e/smoke.spec.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
// Phase 1 smoke tests. Frontend-only — no Tauri IPC.
|
||||
// Visual screenshots are saved as artifacts; they do NOT fail the build at
|
||||
// this stage (see playwright.config.ts header). Promotion to failing visual
|
||||
// diffs happens after Phase 7 stabilises the UI.
|
||||
import { test, expect } from "@playwright/test";
|
||||
import AxeBuilder from "@axe-core/playwright";
|
||||
|
||||
const ZONES = ["cave", "energy", "reset"] as const;
|
||||
const THEMES = ["light", "dark"] as const;
|
||||
|
||||
test.describe("Lumotia frontend smoke (no Tauri IPC)", () => {
|
||||
test("app loads without Tauri runtime", async ({ page }) => {
|
||||
// Suppress noisy invoke-rejected promises from the layout's onMount;
|
||||
// they are expected without Tauri. We only fail on unhandled crashes.
|
||||
const fatalErrors: string[] = [];
|
||||
page.on("pageerror", (err) => fatalErrors.push(err.message));
|
||||
|
||||
await page.goto("/");
|
||||
await expect(page.locator("main")).toBeVisible();
|
||||
expect(fatalErrors).toEqual([]);
|
||||
});
|
||||
|
||||
test("keyboard navigation reaches main + sidebar surface", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.locator("main").waitFor({ state: "visible" });
|
||||
// Tab once and confirm focus moves to a focusable element somewhere in
|
||||
// the document, not nothing. We don't assert the specific element to
|
||||
// keep the test robust through chrome migration.
|
||||
await page.keyboard.press("Tab");
|
||||
const focused = await page.evaluate(() =>
|
||||
document.activeElement && document.activeElement !== document.body
|
||||
? document.activeElement.tagName
|
||||
: null,
|
||||
);
|
||||
expect(focused).not.toBeNull();
|
||||
});
|
||||
|
||||
test("axe scan on / has no critical structural violations", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.locator("main").waitFor({ state: "visible" });
|
||||
const results = await new AxeBuilder({ page })
|
||||
.disableRules([
|
||||
// svelte-i18n initialises asynchronously; <html lang> is set after
|
||||
// first paint in browser-preview mode.
|
||||
"html-has-lang",
|
||||
// Colour-contrast is owned by docs/release/v0.1-contrast-audit.md
|
||||
// and gets re-enabled in Phase 7 after the wrapper palette settles.
|
||||
// Phase 1's smoke baseline focuses on structural/semantic a11y.
|
||||
"color-contrast",
|
||||
])
|
||||
.analyze();
|
||||
const criticals = results.violations.filter(
|
||||
(v) => v.impact === "critical" || v.impact === "serious",
|
||||
);
|
||||
expect(criticals).toEqual([]);
|
||||
});
|
||||
|
||||
for (const theme of THEMES) {
|
||||
test(`theme smoke — ${theme}`, async ({ page }, testInfo) => {
|
||||
await page.goto("/");
|
||||
await page.locator("main").waitFor({ state: "visible" });
|
||||
// Apply theme without depending on the preferences store, which
|
||||
// needs Tauri persistence. The data-theme attribute is what the CSS
|
||||
// tokens key off; the preferences store also writes this attribute
|
||||
// in production.
|
||||
await page.evaluate((t) => {
|
||||
document.documentElement.dataset.theme = t;
|
||||
}, theme);
|
||||
await page.waitForTimeout(50);
|
||||
await page.screenshot({
|
||||
path: `test-results/theme-${theme}-${testInfo.project.name}.png`,
|
||||
fullPage: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const zone of ZONES) {
|
||||
test(`sensory zone smoke — ${zone}`, async ({ page }, testInfo) => {
|
||||
await page.goto("/");
|
||||
await page.locator("main").waitFor({ state: "visible" });
|
||||
await page.evaluate((z) => {
|
||||
document.documentElement.dataset.zone = z;
|
||||
}, zone);
|
||||
await page.waitForTimeout(50);
|
||||
await page.screenshot({
|
||||
path: `test-results/zone-${zone}-${testInfo.project.name}.png`,
|
||||
fullPage: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user