Files
whynot-design/tests/visual/ui-kit.spec.mjs
tegwick a89bb563a0
Some checks failed
ci / check (push) Has been cancelled
ci / release (push) Has been cancelled
fix(showcase): break wn-breadcrumb slotchange infinite loop (WHYNOT-WP-0002 T11)
WnBreadcrumb._onSlot inserted separator <span>s into its own light DOM on
slotchange but cleaned up in the shadow DOM, so they were never removed — each
insertion re-fired slotchange, looping the main thread and wedging the showcase
page. Made _onSlot idempotent: exclude own separators when reading items, and
mutate only when separators are not already correct.

- Un-fixme the showcase visual test; add a warm-up full-page capture so
  deviceScaleFactor-2 sub-pixel snapping settles before the assertion. All 5
  visual tests pass.
- Remove the dead Google-Fonts @import from colors_and_type.css (token stacks are
  system-ui; webfont unused + a CI-flake source; no visual change).
- Unblocks WHYNOT-WP-0003 T08 (showcase = per-version visual catalog); both T11
  and T08 marked done.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:10:41 +02:00

71 lines
3.2 KiB
JavaScript

import { test, expect } from "@playwright/test";
// Visual-regression baselines for @whynot/design.
//
// Two example pages are covered:
// 1. examples/showcase/index.html — every component, one page
// 2. examples/whynot-control/index.html — full app composition
//
// To update intentionally: pnpm test:visual:update
// Defensive: no webfont is loaded anymore (token font stacks are system-ui
// based), but abort the font CDNs anyway so a re-introduced webfont can never
// make a baseline non-deterministic or network-dependent.
test.beforeEach(async ({ page }) => {
await page.route(/fonts\.(googleapis|gstatic)\.com/, (route) => route.abort());
});
test.describe("showcase — every component", () => {
// Previously KNOWN BROKEN (WHYNOT-WP-0002 T11): the page wedged the renderer
// main thread because <wn-breadcrumb> inserted separators into its own light
// DOM on slotchange, re-firing slotchange in an infinite loop. Fixed by making
// WnBreadcrumb._onSlot idempotent (src/elements/layout.js).
test("renders", async ({ page }) => {
await page.goto("/examples/showcase/index.html");
// Wait for custom elements to register + Lit to render.
await page.waitForFunction(() => !!customElements.get("wn-button"));
await page.waitForTimeout(800);
// Warm-up capture: the first full-page screenshot resizes the viewport to
// the full content height, and at deviceScaleFactor 2 that first layout pass
// snaps several sections by ≤4px. Discard it so the page is settled before
// the real assertion — otherwise toHaveScreenshot fails its "two consecutive
// stable screenshots" check on this long page.
await page.screenshot({ fullPage: true });
await page.waitForTimeout(200);
await expect(page).toHaveScreenshot("showcase.png", { fullPage: true });
});
});
test.describe("whynot-control UI kit", () => {
test("prototypes index", async ({ page }) => {
await page.goto("/examples/whynot-control/index.html");
await page.waitForFunction(() => !!document.querySelector("aside"));
await page.waitForTimeout(800);
await expect(page).toHaveScreenshot("01-prototypes.png", { fullPage: true });
});
test("inbox", async ({ page }) => {
await page.goto("/examples/whynot-control/index.html");
await page.waitForFunction(() => !!document.querySelector("aside a"));
await page.click("aside a:has-text('Inbox')");
await page.waitForTimeout(500);
await expect(page).toHaveScreenshot("02-inbox.png", { fullPage: true });
});
test("signals", async ({ page }) => {
await page.goto("/examples/whynot-control/index.html");
await page.waitForFunction(() => !!document.querySelector("aside a"));
await page.click("aside a:has-text('Signals')");
await page.waitForTimeout(500);
await expect(page).toHaveScreenshot("03-signals.png", { fullPage: true });
});
test("control doc — INTENT.md", async ({ page }) => {
await page.goto("/examples/whynot-control/index.html");
await page.waitForFunction(() => !!document.querySelector("aside a"));
await page.click("aside a:has-text('INTENT.md')");
await page.waitForTimeout(500);
await expect(page).toHaveScreenshot("04-doc-intent.png", { fullPage: true });
});
});