fix: eliminate network dependency in avatar visual regression test

The WithImage avatar story fetched an external GitHub avatar URL, creating
a race condition: if the image hadn't loaded before the screenshot was
taken, the fallback text was captured instead, causing a spurious baseline
diff (809 → 159 bytes).

Two fixes:
- Replace the external URL with an inline data URI so the story never
  depends on network availability or latency.
- Add a waitForImages() helper to the visual regression test that waits
  for all <img> elements inside #storybook-root to finish loading before
  taking the screenshot. This guards against future stories that may
  reference slow-loading images.
This commit is contained in:
kiloconnect[bot]
2026-03-12 10:57:08 +00:00
parent ff1b422c55
commit 8aa2b0b1e8
3 changed files with 25 additions and 4 deletions
@@ -33,10 +33,15 @@ export const WithCustomColors: Story = {
args: { fallback: "KL", background: "#1a4d8f", foreground: "#ffffff" },
}
// Inline data URI so the visual regression test never depends on network.
// 1×1 teal PNG — small enough to embed, large enough to prove <img> rendering.
const AVATAR_DATA_URI =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAADZJREFUWIW2xzERACAQBLA7/pWACxrsgfFhJpmtsrMn0N2S2RNIZk+gu2X2BLpbZk8gmT0B4AWJGwMhBjARKwAAAABJRU5ErkJggg=="
export const WithImage: Story = {
args: {
fallback: "OC",
src: "https://avatars.githubusercontent.com/u/154330673?s=48",
src: AVATAR_DATA_URI,
},
}
@@ -45,6 +45,24 @@ async function disableAnimations(page: Page) {
})
}
// Wait for every <img> inside the story root to finish loading so screenshots
// never capture an intermediate state with missing / half-loaded images.
async function waitForImages(page: Page) {
await page.evaluate(() => {
const imgs = document.querySelectorAll<HTMLImageElement>("#storybook-root img")
return Promise.all(
Array.from(imgs).map(
(img) =>
img.complete ||
new Promise<void>((resolve) => {
img.addEventListener("load", () => resolve(), { once: true })
img.addEventListener("error", () => resolve(), { once: true })
}),
),
)
})
}
// Stories to skip from visual regression:
// - Font/Favicon: inject into <head>, no visible content in #storybook-root
// - Typewriter: uses JS setTimeout + Math.random(), inherently non-deterministic
@@ -72,6 +90,7 @@ for (const story of stories) {
await disableAnimations(page)
// Wait for Kobalte/SolidJS to finish hydrating interactive components
await page.waitForSelector("#storybook-root *", { state: "attached" })
await waitForImages(page)
// Screenshot just the story content, not the full 1280x720 canvas.
// Use [component, variant] path so snapshots are grouped per component dir.
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79b80c13a1fa198f1602c85728a48467ac9b47c6430dadd6ff698f9c4d39980e
size 809