npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@corralimited/snapdiff-playwright

v0.1.6

Published

SnapDiff visual regression reporter for Playwright. Captures PNGs during your tests, ships them to SnapDiff, gates merges on visual changes.

Downloads

276

Readme

@corralimited/snapdiff-playwright

npm

SnapDiff visual regression for Playwright. Captures PNGs during your tests, uploads them to SnapDiff, and gates merges on visual changes.

The reporter is a thin layer over Playwright. It does not replace your test runner, your assertions, or your authentication setup. Add await snapshot(page, 'name') wherever you want a visual check, and the reporter handles upload, build creation, polling, and gating.

When to use this package

The URL-based SnapDiff GitHub Action covers public routes such as marketing sites, documentation, and any unauthenticated page. It cannot reach routes behind a login.

The Playwright reporter covers authenticated routes by reusing your existing end-to-end tests:

  • Your tests already log in, whether through storageState, OAuth, or a login flow
  • You add one line per page you want a visual snapshot of
  • The reporter captures the page in its authenticated state and uploads it

The two packages can be used together. Keep the GitHub Action for routes such as / and /pricing, and use the reporter for /dashboard, /account, and similar authenticated pages. Both write to the same SnapDiff project.

Install

npm install -D @corralimited/snapdiff-playwright
# or
pnpm add -D @corralimited/snapdiff-playwright
# or
yarn add -D @corralimited/snapdiff-playwright

Requires @playwright/test 1.40 or later.

Configure the reporter

Add it to playwright.config.ts:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['list'],
    [
      '@corralimited/snapdiff-playwright/reporter',
      {
        project: 'my-app',                    // your SnapDiff project slug
        // apiKey: process.env.SNAPDIFF_API_KEY,   // defaults to SNAPDIFF_API_KEY
        // apiUrl: 'https://snapdiff.example.com', // self-hosted? set this
      },
    ],
  ],
});

Capturing snapshots

The recommended API is the fixture extension:

import { test, expect } from '@corralimited/snapdiff-playwright';

test('account page renders correctly', async ({ page, snapshot }) => {
  await page.goto('/account');
  await expect(page.locator('h1')).toBeVisible();
  await snapshot('account');
});

test('billing settings render correctly', async ({ page, snapshot }) => {
  await page.goto('/account/billing');
  await snapshot('account-billing');
});

The test import extends @playwright/test with a snapshot fixture. Existing fixtures, hooks, and configuration continue to work.

Standalone helper

If you would rather not change the test import, the package also exports a standalone snapshot(page, name):

import { test } from '@playwright/test';
import { snapshot } from '@corralimited/snapdiff-playwright';

test('account page', async ({ page }) => {
  await page.goto('/account');
  await snapshot(page, 'account');
});

In some peer-dependency configurations, the standalone helper can miss tests when @playwright/test is duplicated under node_modules. The fixture API is unaffected and is the preferred approach.

Snapshot names must be unique across the entire test run. They map directly to baselines in SnapDiff. Use prefixes to organize related snapshots, for example account-overview, account-billing, and settings-profile.

Keep functional assertions content-agnostic

Snapshots are taken only when the test passes. If a functional assertion fails, for example toContainText('Ship') after a heading was renamed to 'Catch', the test fails before snapshot() is reached and SnapDiff never sees the page. The visual change that caused the assertion failure is then never captured for review.

The recommendation is to assert on structure rather than copy:

// Brittle: breaks whenever copy changes
await expect(page.locator('h1')).toContainText('Welcome to Acme');

// Resilient: verifies rendering and lets SnapDiff catch copy changes
await expect(page.locator('h1')).toBeVisible();

To capture a snapshot regardless of test outcome, for example to inspect the page when an assertion fails, wrap the assertion in try / finally:

test('account page', async ({ page, snapshot }) => {
  await page.goto('/account');
  try {
    await expect(page.locator('[data-testid=balance]')).toBeVisible();
  } finally {
    await snapshot('account');
  }
});

What the reporter stabilizes for you

Before each snapshot the reporter runs a stabilization pass that fixes the most common sources of flaky captures:

  • Waits for document.fonts.ready so web fonts finish loading before capture
  • Forces all CSS animations and transitions to complete instantly
  • Hides the text caret and any visible scrollbars
  • Blurs the focused element to remove focus rings
  • Moves the mouse off the page to clear hover/active states
  • For fullPage: true captures, scrolls to the bottom and back to trigger IntersectionObserver-based lazy-load
  • Waits for every <img> to finish decoding (with a 3 s safety cap)

This is automatic and not configurable — these fixes are universally desired. You still need to handle anything specific to your app:

  • await page.waitForSelector('[data-loaded]') — wait for a data-driven UI to settle
  • ignoreSelectors (per-page) — exclude regions that legitimately differ every run (timestamps, ad slots)
  • Time/locale freezing — pin Date.now() if your page renders relative timestamps and you don't want to ignoreSelector them

Snapshot options

await snapshot(page, 'dashboard', {
  fullPage: true,                                 // capture entire scrollable page
  selector: '[data-testid="main-content"]',       // capture only this element
  clip: { x: 0, y: 0, width: 1280, height: 720 }, // explicit region
  delayMs: 500,                                   // wait before capture (animations)
});

Authentication on protected previews

If your preview deployments are behind Vercel Authentication, Cloudflare Access, basic auth, or any header-based bypass, configure it in your Playwright config — the reporter inherits whatever your tests already do. There is no SnapDiff-specific input.

Vercel Deployment Protection

// playwright.config.ts
export default defineConfig({
  use: {
    extraHTTPHeaders: {
      'x-vercel-protection-bypass': process.env.VERCEL_AUTOMATION_BYPASS_SECRET!,
      'x-vercel-set-bypass-cookie': 'true',
    },
  },
});

Generate the secret in Vercel → Settings → Deployment Protection → Protection Bypass for Automation. Reference: Vercel docs.

Cloudflare Access

use: {
  extraHTTPHeaders: {
    'CF-Access-Client-Id': process.env.CF_ACCESS_CLIENT_ID!,
    'CF-Access-Client-Secret': process.env.CF_ACCESS_CLIENT_SECRET!,
  },
},

Basic auth, custom tokens

Same pattern — drop any header into extraHTTPHeaders.

Application authentication (login behind the page)

The reporter does not handle login. Authentication is performed by your existing Playwright setup:

// playwright.config.ts
export default defineConfig({
  use: {
    storageState: 'auth.json', // already-logged-in state
  },
});
// global setup (one-time login)
test.beforeAll(async ({ browser }) => {
  const ctx = await browser.newContext();
  const page = await ctx.newPage();
  await page.goto('/login');
  await page.fill('[name=email]', process.env.TEST_USER_EMAIL!);
  await page.fill('[name=password]', process.env.TEST_USER_PASSWORD!);
  await page.click('button[type=submit]');
  await page.waitForURL('/dashboard');
  await ctx.storageState({ path: 'auth.json' });
});

Whatever authentication mechanism the tests already use, whether cookies, localStorage tokens, or OAuth, is inherited by the reporter. SnapDiff has no separate authentication concept.

Reporter options

| Option | Type | Default | Description | | --- | --- | --- | --- | | project | string | required | SnapDiff project slug or ID | | apiKey | string | process.env.SNAPDIFF_API_KEY | API key | | apiUrl | string | https://api.snapdiff.ai | Override for self-hosted SnapDiff | | branch | string | auto-detected | Override CI detection | | commitSha | string | auto-detected | Override CI detection | | commitMessage | string | auto-detected | Override CI detection | | pullRequestUrl | string | auto-detected | Override CI detection | | wait | boolean | true | Poll the build to completion and print the per-page result. Visual changes do not fail the run — see Merge gating | | waitTimeoutMinutes | number | 5 | Maximum polling duration | | disabled | boolean | false | Disable the reporter, for example during local debugging |

Merge gating

When the reporter detects visual changes, the workflow does not fail and the Playwright check remains green. SnapDiff posts a separate commit status named snapdiff/visual-test through the GitHub API:

  • pending — the build is processing, or changes are awaiting review
  • success — no changes detected, or all changes have been approved in the dashboard
  • error — the build failed for reasons unrelated to visual differences

Add snapdiff/visual-test as a required check in your branch protection rules. While the status is pending, the merge button is blocked. A reviewer opens the dashboard to approve changes (which become the new baseline) or reject them. Once approved, the status updates to success and the merge is unblocked.

To enable status posting, connect a GitHub repository and personal access token in your project's Settings page in the SnapDiff dashboard (config is per-project). The token requires the repo:status scope.

CI integration

The reporter auto-detects CI metadata for GitHub Actions, CircleCI, GitLab CI, Vercel, and Buildkite. For other systems, supply explicit overrides through reporter options.

GitHub Actions example

# .github/workflows/visual.yml
name: Visual diff

on:
  pull_request:
  push:
    branches: [main]

jobs:
  visual:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test
        env:
          SNAPDIFF_API_KEY: ${{ secrets.SNAPDIFF_API_KEY }}
          # If your tests need credentials to log in:
          TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
          TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}

The reporter creates a build and waits for diffs to complete. The workflow itself remains green regardless of visual changes; merge gating is handled by the snapdiff/visual-test commit status described above.

How it works

  1. Tests run normally. await snapshot(page, name) calls page.screenshot() and attaches the PNG to the test result.
  2. After each passing test, the reporter uploads attached PNGs to POST /v1/screenshot/upload. SnapDiff returns a screenshot id.
  3. After the test run, the reporter creates a build through POST /v1/projects/:project/builds, referencing the returned ids as snapshots[].screenshot_id.
  4. The reporter polls until diffing is complete and prints the result. Visual changes leave the run green and block the merge via the snapdiff/visual-test commit status; only infrastructure failures (upload error, build error, poll timeout) fail the run.

Pixels are captured on the CI machine and diffed by SnapDiff. There is no DOM serialization or cloud rendering — captures come from the same browsers your tests already run.

Troubleshooting

No snapshots captured. The reporter is registered but snapshot() was not called. Add await snapshot(page, 'name') inside a test that passes.

Duplicate snapshot names. Two tests captured a snapshot with the same name. Names are global; rename or add a prefix.

No API key found. Set SNAPDIFF_API_KEY in the environment, or pass apiKey in reporter options.

Build poll failed. Usually transient. Rerun the test job. If the issue persists, contact support.

Authenticated routes show the login form. The storageState file is not being loaded. Confirm playwright.config.ts has use.storageState pointing at a valid auth state file, and that the file is generated before playwright test runs.

License

MIT