Blog Testing 12 min read

Placeholder Images in Storybook, Playwright, and Visual Regression Tests

A testing workflow guide for deterministic placeholder image URLs in Storybook stories, Playwright screenshots, visual regression tests, fixtures, and CI.

Test placeholder imagesStorybook placeholder imagePlaywright image placeholderVisual regression placeholder
Placeholder Images in Storybook, Playwright, and Visual Regression Tests

Visual tests are only useful when screenshots change because the UI changed, not because image fixtures, random photos, or remote media changed.

Deterministic placeholder image URLs give Storybook, Playwright, and visual regression tests stable image surfaces for cards, avatars, product grids, docs, and fallback states.

Search intent

Why test placeholder images should be deterministic

Storybook stories and visual regression tests often need image content before real assets exist. The problem is not filling the box. The problem is filling it the same way every time the test runs.

A random photo, expired CDN image, or fixture that changes between runs can create screenshot noise. A deterministic placeholder URL keeps the visual fixture stable so diffs point to real UI changes.

Stable screenshots

The same placeholder renders across local runs, CI, branches, and baseline updates.

Layout coverage

Cards, avatars, grids, banners, and media slots still exercise real dimensions and aspect ratios.

Readable state

Labels such as Product Image, User, Article Image, or Preview explain what the slot represents.

Storybook

Use placeholder URLs in Storybook args

Storybook stories should use stable data. Put deterministic placeholder URLs in args, fixtures, or mock data instead of relying on remote production images.

Use dimensions that match the component. A product card story should use a square placeholder if the real product media is square. A blog card should use a 1200x630 placeholder if the component expects an OG-style image.

Implementation tsx
export const ProductCardStory = {
  args: {
    title: 'Everyday Backpack',
    imageUrl: 'https://fallback.pics/api/v1/800x800/F4F4F5/18181B?text=Product+Image',
  },
};

export const ArticleCardStory = {
  args: {
    title: 'Release notes',
    imageUrl: 'https://fallback.pics/api/v1/1200x630/F4F4F5/18181B?text=Article+Image',
  },
};

Fixtures

Standardize image fixtures by surface

Test data gets easier to maintain when each image surface has a standard placeholder URL. Use one product image URL, one avatar URL, one article image URL, and one dashboard preview URL instead of inventing new labels per test.

This reduces screenshot churn and helps reviewers understand what a placeholder means at a glance.

Implementation tsx
export const imageFixtures = {
  product: 'https://fallback.pics/api/v1/800x800/F4F4F5/18181B?text=Product+Image',
  avatar: 'https://fallback.pics/api/v1/avatar/96?text=User',
  article: 'https://fallback.pics/api/v1/1200x630/F4F4F5/18181B?text=Article+Image',
  dashboard: 'https://fallback.pics/api/v1/600x400/F4F4F5/18181B?text=Preview',
};

Playwright

Use deterministic placeholders before taking screenshots

Playwright visual comparisons create or compare screenshots. If image content varies, tests can fail even when layout and component behavior are correct.

Use stable fixture data before calling toHaveScreenshot. For pages that load image URLs from APIs, mock the API response so the browser receives deterministic placeholder URLs.

Implementation tsx
import { test, expect } from '@playwright/test';

test('product grid visual state', async ({ page }) => {
  await page.route('**/api/products', async route => {
    await route.fulfill({
      json: [
        {
          name: 'Everyday Backpack',
          imageUrl: 'https://fallback.pics/api/v1/800x800/F4F4F5/18181B?text=Product+Image',
        },
      ],
    });
  });

  await page.goto('/products');
  await expect(page).toHaveScreenshot('product-grid.png');
});

Network mocking

Mock image requests when the app cannot change fixture URLs

Sometimes a page under test still points to production image URLs. If you cannot change the fixture data, intercept image requests in Playwright and fulfill them with a deterministic SVG body.

This is useful for visual tests where layout matters but the real image content does not.

Implementation tsx
const svgBody =
  '<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 800 800">' +
  '<rect width="100%" height="100%" fill="#F4F4F5"/>' +
  '<text x="50%" y="50%" fill="#18181B" font-size="64" text-anchor="middle" dominant-baseline="middle">Product Image</text>' +
  '</svg>';

await page.route('**/*', async route => {
  if (route.request().resourceType() === 'image') {
    await route.fulfill({
      status: 200,
      contentType: 'image/svg+xml',
      body: svgBody,
    });
    return;
  }

  await route.continue();
});

Visual regression

Reduce screenshot noise before tuning thresholds

Do not start by raising visual diff thresholds. First remove avoidable sources of noise: random images, timestamps, animations, inconsistent fonts, live remote media, ads, and user-specific data.

Deterministic placeholder URLs help with one of the most common noise sources: image content that should not be part of the assertion.

Control inputs

Mock API data, image URLs, dates, locale, theme, and feature flags before taking screenshots.

Freeze media

Use deterministic placeholders for image slots that are not the subject of the test.

Assert the right thing

Use visual snapshots for layout and appearance, not unpredictable remote content.

States

Test loading, missing, and failed-image states separately

A component can look correct with a normal image and still break when the image is missing or fails to load. Add separate stories or tests for each state.

Use skeleton placeholders for loading states, static labeled placeholders for known missing media, and failed-load tests to verify fallback behavior.

Implementation tsx
export const Loading = {
  args: {
    imageUrl: 'https://fallback.pics/api/v1/skeleton/800x800',
  },
};

export const MissingImage = {
  args: {
    imageUrl: 'https://fallback.pics/api/v1/800x800/F4F4F5/18181B?text=Product+Image',
  },
};

export const FailedImage = {
  args: {
    imageUrl: '/broken-product-image.jpg',
    fallbackUrl: 'https://fallback.pics/api/v1/800x800/F4F4F5/18181B?text=Product+Image',
  },
};

CI

Keep CI baselines portable

Visual tests can still vary by operating system, browser version, fonts, antialiasing, and viewport. Deterministic image placeholders do not solve every visual testing problem, but they remove one major source of variance.

Run screenshot tests in a consistent environment and commit baseline updates intentionally. The placeholder URL should not change unless the visual state being tested changes.

Privacy

Keep test placeholder URLs free of private data

Test artifacts are often shared widely: CI logs, HTML reports, trace files, screenshots, pull requests, and bug reports. Treat placeholder URLs in tests as public.

Do not put secrets, tokens, email addresses, customer names, account IDs, order IDs, private product names, regulated data, or internal identifiers in placeholder URL text.

Implementation text
// Good test labels
https://fallback.pics/api/v1/800x800?text=Product+Image
https://fallback.pics/api/v1/avatar/96?text=User
https://fallback.pics/api/v1/1200x630?text=Article+Image

// Keep private values out of URL text

Internal links

Where to go next

Use the placeholder image API guide for URL syntax, then use the layout shift and framework guides to make image states production-safe.

Use the skeleton guide when deciding whether a visual state should represent loading, missing media, or a failed image.

Implementation text
Placeholder image API: https://fallback.pics/placeholder-image-api/
Skeleton placeholder guide: https://fallback.pics/blog/skeleton-placeholder-images-vs-static-fallbacks/
Prevent image layout shift: https://fallback.pics/blog/prevent-layout-shift-missing-images/
React image fallback: https://fallback.pics/guides/react-image-fallback/
Next.js image fallback: https://fallback.pics/guides/nextjs-image-fallback/
SVG placeholder images: https://fallback.pics/blog/svg-placeholder-images-fast-cacheable-scalable/
Cache-Control guide: https://fallback.pics/blog/cache-control-placeholder-images-cdn-browser/

Key takeaways

What to standardize before shipping

  • Use deterministic placeholder URLs in Storybook stories, fixtures, and screenshot tests.
  • Mock API responses or image requests in Playwright when live media would make screenshots noisy.
  • Standardize placeholder URLs by surface: product, avatar, article, dashboard, skeleton, and missing media.
  • Test normal, loading, missing, and failed-image states separately.
  • Keep placeholder URL labels generic because screenshots, traces, and CI reports are often shared.

Production fallback layer

Use fallback.pics anywhere an image URL is accepted.

Start with one deterministic URL and standardize fallback behavior across your design system.

Read the docs