Snapvisor Docs

Migrate from Playwright toHaveScreenshot

Move a Playwright suite from expect(page).toHaveScreenshot and committed snapshot PNGs to Snapvisor - how assertions, baselines, tolerances, masks and per-platform snapshot names translate.

This guide moves a Playwright suite that uses expect(page).toHaveScreenshot() or expect(value).toMatchSnapshot() to Snapvisor. You keep your tests and your CI. What changes is the capture call, and where baselines and review live. For a side-by-side of the two approaches, see Snapvisor vs Playwright.

The one change that matters

toHaveScreenshot compares a screenshot with a golden file that Playwright writes next to your tests. The golden files are PNGs in your repository, and a failed comparison fails the test.

Snapvisor stores baselines server-side, per reference branch. argosScreenshot captures the screenshot, your pipeline uploads it, and Snapvisor compares it with the approved screenshot for the branch. A changed screenshot does not fail the test. You approve it in the review UI, and it becomes the new baseline for that branch. See Baseline builds.

Your committed snapshots do not carry over, and there is no import path. Your first Snapvisor build on your base branch becomes the baseline. Plan the switch for a moment when your base branch is visually correct.

Concept mapping

PlaywrightSnapvisor equivalent
await expect(page).toHaveScreenshot("home.png")await argosScreenshot(page, "home", { fullPage: false }) from @snapvisor/playwright. Page captures are full page by default, so fullPage: false keeps the viewport-sized capture toHaveScreenshot takes.
expect(locator).toHaveScreenshot()argosScreenshot(page, name, { element: locator })
expect(buffer).toMatchSnapshot("name.png")Write the image to a folder and upload it with npx @snapvisor/cli upload. See Other images.
Golden files in *-snapshots/ (or your snapshotPathTemplate)Nothing in your repository. Baselines live on the server.
npx playwright test --update-snapshotsApprove the change in the review UI. See Review workflow.
maxDiffPixels, maxDiffPixelRatio, thresholdthreshold. See Tolerances.
mask: [locator]mask: [locator]. Your own mask replaces the default one; see Masks, full page and animations.
fullPage (default false)fullPage (default true for page captures). Pass fullPage: false for a viewport-sized capture.
animations: "disabled" (the default)Disabled by default, as in toHaveScreenshot. Pass animations: "allow" to opt out.
Snapshot names with -chromium-linux or -darwinOne screenshot name. See Per-platform snapshot names.
A failed assertion in the test reportA change flagged in the Snapvisor review and on the pull request status check.

Swap the assertion

Install the SDK and register the reporter, which uploads screenshots when the run finishes:

npm install --save-dev @snapvisor/playwright
// playwright.config.ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@snapvisor/playwright/reporter";

export default defineConfig({
  reporter: [
    process.env.CI ? ["dot"] : ["list"],
    [
      "@snapvisor/playwright/reporter",
      createArgosReporterOptions({ uploadToArgos: !!process.env.CI }),
    ],
  ],
});

Then change the assertion to a capture call:

 // tests/visual.spec.ts
-import { test, expect } from "@playwright/test";
+import { test } from "@playwright/test";
+import { argosScreenshot } from "@snapvisor/playwright";

 test("home", async ({ page }) => {
   await page.goto("http://localhost:3000/");
-  await expect(page).toHaveScreenshot("home.png", {
-    fullPage: true,
-    mask: [page.locator(".timestamp")],
-  });
+  await argosScreenshot(page, "home", {
+    fullPage: true,
+    mask: [page.locator(".timestamp")],
+  });
 });

argosScreenshot takes Playwright's own page and locator screenshot options, so mask, clip and fullPage work as they do in Playwright. One default differs: a page capture is full page unless you pass fullPage: false. Keep the other expect assertions in your tests; only the screenshot assertion changes.

argosScreenshot also stabilizes the page before capturing. It waits for web fonts, images and aria-busy regions to settle, hides carets and scrollbars and normalizes font anti-aliasing. It is on by default; beforeScreenshot runs your own code at the right moment if a page needs more. The screenshot name must be unique within a test run. See Flaky test detection for how recurring changes are handled.

To capture several sizes in one call, pass viewports:

await argosScreenshot(page, "home", {
  viewports: ["iphone-6", { width: 1280, height: 800 }],
});

Delete the snapshot files

Once builds are landing in Snapvisor:

  1. Delete the *-snapshots/ directories (or the folder your snapshotPathTemplate points to) from the repository, and from Git LFS if you stored them there.
  2. Remove any .gitignore rules you added for Playwright's -actual, -expected and -diff output that only existed for snapshots. Keep the test-results/ rule.
  3. Remove --update-snapshots from scripts, docs and CI jobs. Accepting a change now happens in the review UI, and there is no step that rewrites files and commits them.
  4. Remove snapshotPathTemplate and the expect.toHaveScreenshot block from playwright.config.ts.
  5. Remove any CI job that only existed to regenerate snapshots on a reference machine or container.

Your pull requests stop carrying binary PNG changes. Reviewers see the before and after in the review UI instead.

Tolerances

toHaveScreenshot has three tolerance options. maxDiffPixels and maxDiffPixelRatio set how many pixels may differ, and threshold sets how different a single pixel's color may be (0 is strict, 1 is lax, and the default is 0.2).

Snapvisor has one setting, threshold, a sensitivity between 0 and 1 that defaults to 0.5. Higher means less sensitive. It does not mean the same thing as Playwright's threshold, so do not copy the number over:

await argosScreenshot(page, "chart", { threshold: 0.7 });

Start from the default and adjust only when a specific screenshot is noisy. If you raised maxDiffPixels to absorb font or anti-aliasing differences between machines, remove it and try the default first. Snapvisor's stabilization normalizes font anti-aliasing before the capture. The threshold is stored with each screenshot and applied by the same engine that produces the diff you review.

Masks, full page and animations

Full page. toHaveScreenshot captures the viewport unless you pass fullPage: true. argosScreenshot captures the full page by default when you pass a page. To keep viewport-sized captures, pass fullPage: false:

await argosScreenshot(page, "home", { fullPage: false });

Animations. Animations are disabled by default, as in toHaveScreenshot. Pass animations: "allow" to opt out.

Mask. mask is Playwright's own option:

await argosScreenshot(page, "dashboard", {
  mask: [page.locator(".clock"), page.locator("[data-testid=avatar]")],
});

A mask hides the area in the screenshot, so changes inside it are not compared. Use it for clocks, ads and random data rather than raising the threshold. Passing your own mask replaces Snapvisor's default mask, [data-visual-test="blackout"], so include that selector too if you rely on it.

Per-platform snapshot names

Playwright puts the browser and platform in each snapshot name, for example home-chromium-linux.png or home-chromium-darwin.png, because screenshots differ between them. That is why teams generate snapshots in one fixed environment, usually a container.

With Snapvisor you pass one name, home, and the baseline lives on the server. There are no -linux or -darwin files to keep in sync, and no need to generate baselines on a particular machine. Screenshots still differ between browsers and operating systems, so run CI in a consistent environment. To cover more than one browser, add Playwright projects:

// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
  ],
});

Each project runs the whole suite, so more projects means more screenshots and a longer run.

Other images

toMatchSnapshot compares any buffer, not only screenshots. If you compare images that do not come from a page, write them to a folder and upload the folder:

export ARGOS_TOKEN="<your project token>"
npx @snapvisor/cli upload ./screenshots

See SDKs for the CLI options.

The CI change

Before, the job ran the tests, and a changed screenshot failed the job:

- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test

After, the job also has the project token. A changed screenshot no longer fails the job:

- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test # captures via argosScreenshot, uploads via the reporter
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}

Copy the project token from your project's settings in the app, and store it as a CI secret, never in the repository. See Access tokens. On GitHub Actions you can drop the secret with tokenless authentication. More workflows, including merge queues and fork pull requests, are in GitHub Actions recipes.

Run both side by side

You do not have to switch in one pull request. Keep the toHaveScreenshot assertions and add argosScreenshot next to them:

await expect(page).toHaveScreenshot("home.png");
await argosScreenshot(page, "home", { fullPage: false });

A way to run the overlap:

  1. Add the Snapvisor calls and reporter, and merge them to your base branch so the first Snapvisor build becomes the baseline.
  2. Keep the Playwright assertions as your gate, and the Snapvisor check optional, while you compare results.
  3. When the Snapvisor builds match your expectations on a few pull requests, make the Snapvisor check required, then remove the assertions and snapshot files.

Make the Snapvisor check the gate

upload exits 0 once the build has been created, whether or not it contains changes. A non-zero exit means the upload itself failed (bad token, unresolvable commit, network, missing parallel arguments). The review gate is the commit status check Snapvisor posts to the pull request, which asks for review until someone approves or rejects the changes.

If the check is optional, nothing blocks a merge. A green test job means the screenshots arrived, not that they are unchanged. Require the check in branch protection — see GitHub or GitLab.

First-build checklist

After the first Snapvisor build on your base branch:

  1. Open the build and confirm it has a screenshot for each toHaveScreenshot call you replaced.
  2. Look through the screenshots for fonts, images and animations that did not settle. Fix those first, before any threshold change.
  3. Run the same build twice on one commit and check that nothing shows as changed.
  4. Open a pull request with one small visual change. Confirm the Snapvisor status check appears, asks for review, and clears after you approve.
  5. Make the Snapvisor check required in branch protection.

Frequently asked questions

Do I have to stop using Playwright?
No. Snapvisor captures through your Playwright tests. Playwright stays your test runner, and you keep your specs, fixtures and projects.
Can I import my committed snapshot PNGs as baselines?
No. There is no import path. Your first Snapvisor build on your base branch becomes the new baseline: it has nothing to compare against, so it is approved automatically. Run it when your base branch is visually correct.
Does argosScreenshot fail the test when the screenshot changes?
No. The test passes once the screenshot is captured. The review gate is the commit status check Snapvisor posts to the pull request, which asks for review until someone approves or rejects the changes. Make that check required in branch protection.
Can I run toHaveScreenshot and Snapvisor side by side while I switch?
Yes. They are independent. Keep your existing assertions, add argosScreenshot calls next to them, and remove the assertions once the Snapvisor check is your gate.

On this page