Sparround

Visual and component testing

A functional test answers "does the button work?"; a visual test answers "does the button LOOK right?". CSS regressions (a shifted layout, a missing icon, unreadable contrast) survive functional tests entirely — because in the DOM everything is where it should be.

The mechanism in Playwright is simple: await expect(page).toHaveScreenshot().

  • The first run creates a baseline, stored in the repo (a __screenshots__ folder)
  • Later runs compare the current image with the baseline pixel by pixel
  • On a difference the test fails and produces three files: expected, actual, diff
  • If the change is intentional: npx playwright test --update-snapshots

An important nuance: baselines are platform-dependent. An image created on macOS will fail on Linux CI because of font rendering. So baselines must be generated in the environment CI runs in (usually Docker) — the most common trap in this topic.

The main problem with visual tests is flakiness — and the causes are predictable:

  • Dynamic content: dates/times, user names, order numbers, random banners
  • Animations and transitions — the shot lands mid-animation
  • Image loading — lazy loading makes them appear sometimes and not others
  • Scrollbars, cursors, focus rings

The defences:

  • mask: [page.getByTestId('order-date')] — covers dynamic regions with a solid colour
  • animations: 'disabled' — freezes CSS animations (Playwright does this by default)
  • maxDiffPixelRatio: 0.01 — tolerance for small anti-aliasing differences
  • Component shots instead of full pages: expect(locator).toHaveScreenshot() — the smaller the area, the more stable it is
  • Fixing the data with network mocks — the most reliable route

A strategy note: visually testing every page is a trap — maintenance cost explodes and the team starts hitting --update-snapshots without thinking (which is the death of visual testing). Design-system components plus 5-10 critical pages are enough.

Component testing is a separate layer: rendering and testing a single React/Vue/Svelte component in a real browser without booting the whole app (@playwright/experimental-ct-react).

Its value: far faster than E2E, easy to walk every component variant (disabled, loading, error, long text, empty data), and failures localise precisely. Especially strong on projects with a design system or many reusable components.

Its limit: it doesn't verify real integration, routing or backend communication — it doesn't replace E2E, it widens the middle layer of the pyramid.

The position that suffices for an interview: know what it is, when it pays off, and what it does not replace. Playwright's component testing is still experimental — knowing and saying that is an honesty signal.

An interview trap: "Would you add visual tests to your project?" An enthusiastic "yes, on every page!" signals inexperience. The strong answer is conditional: yes, but on a narrow scope and only with baselines generated in the CI environment (Docker); otherwise flakiness teaches the team to hit --update-snapshots blindly and the tests lose their value.

📚 Sources and documentation