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
| Playwright | Snapvisor 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-snapshots | Approve the change in the review UI. See Review workflow. |
maxDiffPixels, maxDiffPixelRatio, threshold | threshold. 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 -darwin | One screenshot name. See Per-platform snapshot names. |
| A failed assertion in the test report | A 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:
- Delete the
*-snapshots/directories (or the folder yoursnapshotPathTemplatepoints to) from the repository, and from Git LFS if you stored them there. - Remove any
.gitignorerules you added for Playwright's-actual,-expectedand-diffoutput that only existed for snapshots. Keep thetest-results/rule. - Remove
--update-snapshotsfrom 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. - Remove
snapshotPathTemplateand theexpect.toHaveScreenshotblock fromplaywright.config.ts. - 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 ./screenshotsSee 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 testAfter, 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:
- Add the Snapvisor calls and reporter, and merge them to your base branch so the first Snapvisor build becomes the baseline.
- Keep the Playwright assertions as your gate, and the Snapvisor check optional, while you compare results.
- 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.
First-build checklist
After the first Snapvisor build on your base branch:
- Open the build and confirm it has a screenshot for each
toHaveScreenshotcall you replaced. - Look through the screenshots for fonts, images and animations that did not settle. Fix those first, before any threshold change.
- Run the same build twice on one commit and check that nothing shows as changed.
- Open a pull request with one small visual change. Confirm the Snapvisor status check appears, asks for review, and clears after you approve.
- 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.
Migrate from Applitools
Move an Applitools Eyes Playwright suite to Snapvisor - how eyes.check, APPLITOOLS_API_KEY, batches, baselines, Ultrafast Grid browsers and match levels translate, and where the two comparison models differ.
Migrate from BackstopJS
Move a BackstopJS suite to Snapvisor - how backstop.json scenarios, viewports, selectors, hidden elements, mismatch thresholds, reference images and backstop approve translate.