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 colouranimations: '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
- Visual comparisonsofficialplaywright.dev
- Component testing (experimental)officialplaywright.dev