
Security News
GPT-6 Astra Attempts Supply Chain Attacks Against Open Source Maintainers in Testing
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.
@corralimited/snapdiff-playwright
Advanced tools
SnapDiff visual regression reporter for Playwright. Captures PNGs during your tests, ships them to SnapDiff, gates merges on visual changes.
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.
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:
storageState, OAuth, or a login flowThe 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.
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.
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
},
],
],
});
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.
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.
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');
}
});
Before each snapshot the reporter runs a stabilization pass that fixes the most common sources of flaky captures:
document.fonts.ready so web fonts finish loading before capturefullPage: true captures, scrolls to the bottom and back to trigger IntersectionObserver-based lazy-load<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 settleignoreSelectors (per-page) — exclude regions that legitimately differ every run (timestamps, ad slots)Date.now() if your page renders relative timestamps and you don't want to ignoreSelector themawait 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)
});
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.
// 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.
use: {
extraHTTPHeaders: {
'CF-Access-Client-Id': process.env.CF_ACCESS_CLIENT_ID!,
'CF-Access-Client-Secret': process.env.CF_ACCESS_CLIENT_SECRET!,
},
},
Same pattern — drop any header into extraHTTPHeaders.
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.
| 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.dev | 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 fail the run on visual changes |
waitTimeoutMinutes | number | 5 | Maximum polling duration |
disabled | boolean | false | Disable the reporter, for example during local debugging |
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:
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 under Integrations in the SnapDiff dashboard. The token requires the repo:status scope.
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/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.
await snapshot(page, name) calls page.screenshot() and attaches the PNG to the test result.POST /v1/screenshot/upload. SnapDiff returns a screenshot_id.POST /v1/projects/:project/builds, referencing the returned screenshot_id values.status: 'failed' to Playwright, which exits non-zero.Pixels are captured on the CI machine and diffed by SnapDiff. There is no DOM serialization or cloud rendering. The reasoning behind this is documented in POSITIONING.md.
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.
MIT
FAQs
SnapDiff visual regression reporter for Playwright. Captures PNGs during your tests, ships them to SnapDiff, gates merges on visual changes.
The npm package @corralimited/snapdiff-playwright receives a total of 4 weekly downloads. As such, @corralimited/snapdiff-playwright popularity was classified as not popular.
We found that @corralimited/snapdiff-playwright demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
GPT-6 Astra hits 100% on ExploitBench and finds zero-days autonomously, while independent tests reveal scope violations and monitoring gaps.

Product
Socket can now send alerts and supply chain attack notifications to Microsoft Teams, with filters that route the right updates to each channel.

Security News
pnpm 12 rewrites the package manager in Rust, cutting install times by up to 90% while preserving pnpm 11 workflows and lockfiles.