---
title: "Snapshot testing"
url: https://develop.sentry.dev/frontend/testing/snapshot-testing/
---

# Snapshot testing

Snapshot tests catch unintended visual regressions in UI components. Instead of asserting on DOM structure, they render a component to a real PNG screenshot using Playwright and compare it over time.

Snapshot tests leverage Sentry's upcoming snapshot testing product which hosts and diffs images using Git data as the source of truth. Snapshot tests will appear as a status check on your PRs.

## [Working with snapshot tests](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#working-with-snapshot-tests)

Snapshot tests run on all PRs against the `master` branch. Snapshot tests uses Git as the source of truth, meaning it will always diff the HEAD of your branch against the base of your branch on `master`. This means any changes you see in your PR are caused by your changes only.

### [Snapshot results](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#snapshot-results)

Any change in snapshot tests (removed, changed, renamed) will result in a failing status check on your PR. Only added and unchanged snapshots will resunt in a green check. This is intentional to get you to review the changes, as often times these tests will catch real regressions.

Any failing check can be approved in the Sentry UI in the top right of the page or directly in the GitHub status check UI by clicking the approve button.

| GitHub Approve Button                                                                            | Sentry Approve Button                                                                                        |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| ![GitHub approval UI](https://develop.sentry.dev/product/snapshots/github-ui-approve-button.png) | ![Sentry approval UI](https://develop.sentry.dev/product/snapshots/sentry-ui-snapshot-approval-required.png) |

Once approved, your status check will show a green check and the Sentry UI will show approved.

| GitHub Approved UI                                                                           | Sentry Approved UI                                                                                    |
| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| ![GitHub approved](https://develop.sentry.dev/product/snapshots/github-ui-success-check.png) | ![Approved UI Snapshot](https://develop.sentry.dev/product/snapshots/sentry-ui-snapshot-approved.png) |

## [Writing Snapshot Tests](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#writing-snapshot-tests)

### [What makes a good snapshot test?](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#what-makes-a-good-snapshot-test)

We want to avoid flakes - stability is key for a good snapshot test. Flakes are almost always a result of rendering noise or indeterminism.

A few key tenets to focus on when choosing to create a snapshot test:

* Stability: avoid snapshotting components with heavy animations or dynamic data (like timestamps). You can always mock or pass data as props to fake this.
* UI-only: avoid snapshotting components that have heavy business logic. Snapshots are not meant to be a full integration test (but can be used as a light test to ensure rendering works).

### [Creating a new test](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#creating-a-new-test)

#### [Agents](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#agents)

Have claude (or your favorite agent) create a snapshot test for you! Use the `/generate-snapshot-tests` skill to create a new snapshot tests.

#### [Manually create a snapshot test](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#manually-create-a-snapshot-test)

Create a file named `<component>.snapshots.tsx` alongside your component.

Use the `it.snapshot` API (extended onto Jest's `test` by the snapshot framework):

`button.snapshots.tsx`

```tsx
import { ThemeProvider } from "@emotion/react";
import { Button, type ButtonProps } from "@sentry/scraps/button";

// eslint-disable-next-line no-restricted-imports -- SSR snapshot rendering needs direct theme access
import { darkTheme, lightTheme } from "sentry/utils/theme/theme";

const themes = { light: lightTheme, dark: darkTheme };

describe("Button", () => {
  describe.each(["light", "dark"] as const)("%s", (themeName) => {
    it.snapshot.each<ButtonProps["priority"]>([
      "default",
      "primary",
      "danger",
      "warning",
      "link",
      "transparent",
    ])(
      "%s",
      (priority) => (
        <ThemeProvider theme={themes[themeName]}>
          {/* Add padding to avoid clipping box-shadows/focus rings outside #root */}
          <div style={{ padding: 8 }}>
            <Button priority={priority}>{priority}</Button>
          </div>
        </ThemeProvider>
      ),
      (priority) => ({ theme: themeName, priority: String(priority) }),
    );
  });
});
```

### [API](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#api)

**`it.snapshot(name, renderFn, metadata?)`**

Renders `renderFn()` and saves a screenshot named `name`.

* `name`: display name for the snapshot
* `renderFn`: returns the JSX element to render
* `metadata`: optional `Record<string, string>` saved to the accompanying `.json` file. These will be used in Sentry's hosted Snapshots UI in the future.

**`it.snapshot.each(values)(name, renderFn, metadataFn?)`**

Iterates over `values`, substituting `%s` in `name` with each value. `metadataFn` receives each value and returns its metadata.

### [Theming](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#theming)

Always wrap snapshot renders in `ThemeProvider`. Test both `light` and `dark` themes using `describe.each`.

Import themes directly (not via the `useTheme` hook) since there is no React context in SSR:

```tsx
// eslint-disable-next-line no-restricted-imports -- SSR snapshot rendering needs direct theme access
import { darkTheme, lightTheme } from "sentry/utils/theme/theme";
```

### [Clipping](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#clipping)

The screenshot clips to the `#root` element's CSS border-box. If your component renders box-shadows, outlines, or focus rings that extend outside the element, wrap it in a padded `<div>` to prevent clipping.

## [Running Snapshot Tests](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#running-snapshot-tests)

```bash
pnpm snapshots
```

This runs `jest --config jest.config.snapshots.ts`, which only matches `*.snapshots.tsx` files.

## [Output](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#output)

Snapshots are written to `.artifacts/snapshots/`. Each snapshot produces two files:

* `<slug>.png`: 2x-resolution screenshot with a transparent background
* `<slug>.json`: metadata including `display_name`, `group`, and any custom fields

The file slug is derived from the describe block ancestry and test name. For example, a test in `describe('Button') > describe('dark')` named `primary` produces `button/dark/primary.png`.

## [How Sentry's frontend snapshotting works](https://develop.sentry.dev/frontend/testing/snapshot-testing.md#how-sentrys-frontend-snapshotting-works)

Snapshot tests run in a Node SSR environment (not jsdom). Each test:

1. Renders the component to HTML using Emotion SSR
2. Loads the HTML in a headless Chromium browser via Playwright
3. Waits for fonts to load, then captures a PNG
4. Saves the image and a metadata JSON file to `.artifacts/snapshots/`

The test runner uses a separate Jest config (`jest.config.snapshots.ts`) and only picks up files named `*.snapshots.tsx`.
