A Hosted Alternative to Playwright Screenshots — No Per-Browser Baselines to Manage

Playwright’s built-in visual comparison is good. expect(page).toHaveScreenshot() handles masking, animation freezing, full-page capture and configurable pixel thresholds without a third-party plugin, which puts it ahead of BackstopJS and well ahead of Nightwatch VRT on raw capture-time features. This comparison isn’t about whether Playwright can take a decent screenshot — it can. It’s about what happens once you have thousands of them: where they live, whether they compare cleanly across three operating systems, who can review a diff, and whether anything runs without a human typing npx playwright test.

What kind of tool each one is

A Playwright visual assertion is a few lines in an ordinary test file:

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

test('homepage matches baseline', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
    mask: [page.locator('.timestamp')],
  });
});

Playwright Test is a full end-to-end framework — it drives real Chromium, Firefox and WebKit builds that it downloads and version-pins itself, so there’s no separate driver to keep in sync with your installed browser. toHaveScreenshot() is one assertion among many: click, fill, wait, navigate, intercept a network request, then optionally screenshot. The first run writes a baseline; every run after that compares against it and fails the test if the images differ beyond your configured tolerance.

Diffy has no test file. You create a project in a browser tab, paste a URL list, set breakpoints, and Diffy captures and compares on a schedule or on demand — no framework, no assertions, no npx.

Setup: config and assertion options vs a project

Global defaults live in playwright.config.ts:

export default defineConfig({
  expect: {
    toHaveScreenshot: {
      maxDiffPixelRatio: 0.02,
      animations: 'disabled',
    },
  },
});

and any individual assertion can override them. That’s real per-test precision: one page can tolerate more drift than another, one test can clip to a region, another can mask three elements — because it’s all just arguments to a function call in code you already review like any other change.

Diffy’s settings are mostly per-project fields in the UI — sensitivity, masks, delay — editable by whoever needs to change them, without a pull request. The trade-off is the same one in every article on this page: code gives you precision and version history, a UI gives you access to people who don’t write code.

Where baselines live, and the per-OS problem

This is the sharpest practical difference, and it’s worth being specific about it.

Playwright names snapshots by test file, test name, browser and platform: homepage.spec.ts-snapshots/homepage-1-chromium-darwin.png next to homepage-1-chromium-linux.png. That’s not incidental — the docs are direct about why: “Browser rendering can vary based on the host OS, version, settings, hardware, power source… and other factors.” A baseline captured on a developer’s MacBook will not pixel-match a screenshot taken by Linux-based CI, so most teams generate and update baselines inside the same environment CI runs in — typically the official mcr.microsoft.com/playwright Docker image — rather than trusting a screenshot taken locally.

That’s a real chore: every browser you test times every OS you run tests on is its own baseline set, and “update the baseline” means doing it from the right machine, not just anyone’s.

Diffy renders every screenshot server-side, so there’s exactly one baseline per page per breakpoint, generated the same way every time regardless of who triggered it or what laptop they own. The local worker still captures on your machine when you need that, but production comparisons run on Diffy’s infrastructure, not a mix of developer laptops and CI runners.

Capture-time options

Playwright’s toHaveScreenshot() covers most of what a dedicated tool offers at the point of capture:

PlaywrightDiffy
Hide/mask an elementmask — Locators overlaid with a solid box, pink #FF00FF by default, customizable via maskColorMask — a visible rectangle painted over the region
Remove an elementCustom CSS via stylePath (e.g. display: none)Removes the node from the DOM
Freeze animationsanimations: 'disabled' — stops CSS animations, CSS transitions and Web AnimationsNo dedicated freeze; a fixed delay before capture
Full page vs viewportfullPage option, defaults to false (viewport only)Full page is the default and only mode
Crop a regionclip — x/y/width/heightNot available — page or breakpoint level only
Dynamic textNo built-in helper — stub via page.route() or write it out of the DOM yourselfMock content — replace selector contents with placeholder text
Log in firststorageState — save an authenticated session once, reuse it everywherePresets for Drupal, WordPress and Netlify, plus custom
Retina, dark modepage.emulateMedia(), device scale factor in project configCheckboxes
Transparent backgroundomitBackground: trueNot applicable — page screenshots

Two of those are worth calling out as genuinely strong Playwright features rather than table rows.

page.route() intercepts network requests and lets you return canned data instead of hitting a real API. For a page whose “changed content” problem is actually “different API response every run,” this eliminates the dynamic content at the source rather than masking it after rendering — a more thorough fix than anything a screenshot tool bolted on afterward can do.

storageState saves cookies and local storage from an authenticated session to a file, which every subsequent test can load instead of logging in again:

// once, in a setup project
await page.goto('https://example.com/login');
await page.fill('#username', 'user');
await page.fill('#password', 'pass');
await page.click('button[type=submit]');
await page.context().storageState({ path: 'auth.json' });
// playwright.config.ts
use: { storageState: 'auth.json' }

Diffy’s login presets (Drupal, WordPress, Netlify, custom) log in before every capture; storageState logs in once and skips it after. Different trade-off — Playwright avoids repeating the login flow at all, Diffy doesn’t require you to script one.

Diffy project settings showing the Authenticate User Before Screenshot options, with presets for Custom, Drupal, WordPress and Netlify, and username and password fields.
A preset and two fields, run before every capture.

Comparison thresholds

Playwright Test uses pixelmatch for comparison. Three knobs control it: threshold — “an acceptable perceived color difference in the YIQ color space between the same pixel in compared images,” 0 to 1, defaulting to 0.2 — plus maxDiffPixels and maxDiffPixelRatio, which set an absolute or proportional budget for how many pixels are allowed to differ before the assertion fails.

That’s a tunable, well-documented system, and it’s still a positional comparator: pixelmatch checks the same x,y coordinate in both images. It has no concept of “this content moved down 40px because the header grew a line” — it just sees several thousand pixels that no longer match their old position, and if that exceeds your budget, the test fails. A single added line in a shared header can still flood a maxDiffPixelRatio budget on every page that includes it.

Diffy runs a custom algorithm alongside pixel-perfect comparison that recognizes vertical shifts specifically, highlighting the element that actually changed instead of everything beneath it.

Diffy's comparison algorithm switcher, offering Custom highlight and Pixel perfect, with an option to set the choice as the default algorithm for the project.
Switch algorithms per comparison, or set a default for the project.

One more detail worth knowing: toHaveScreenshot() doesn’t just take one screenshot and compare it. Per Playwright’s own description, the assertion “took a bunch of screenshots until two consecutive screenshots matched, and saved the last screenshot to file system” — it retries capture until the page has visibly settled, which cuts down on a specific kind of flakiness (a still-animating element, a font not yet loaded) that a fixed delay handles less precisely.

Reviewing failures

This is Playwright’s other strong point. A failed run produces an HTML report (npx playwright show-report) with expected, actual and diff images for every failed screenshot assertion, and the trace viewer goes further — a full timeline of DOM snapshots, network requests and console output for the test that failed, so you can scrub through exactly what the browser did. For local debugging, this is as good as anything covered on this site.

What it doesn’t do is persist across runs as a hosted history. The report is an artifact from one CI run; if you want last month’s results, your pipeline has to have archived them somewhere. Diffy keeps every screenshot and comparison in the cloud continuously:

  • Thumbnail view, with approval directly from thumbnails
  • The same change grouped across every screenshot it appears in, so you approve it once
  • Before/after slider and keyboard shortcuts for moving through a large set
  • Shareable links that work for people without a Diffy account
  • Six months of history on paid plans; one month on the free plan
Diffy's review dashboard comparing Prod 8 May against Stage 8 May, with tabs for changed pages, unreviewed and with bugs, and page thumbnails each carrying an Approve button.
Reviewing a production-versus-staging run from thumbnails — no CI artifact to publish first.

Environments and continuous monitoring

Playwright has no concept of environments or scheduling built in — it’s a test runner, and it does what its config tells it to do when something invokes it. Comparing production against staging means writing a test that visits both and diffs them yourself; catching a change nobody deployed means putting the whole suite on a cron job and building the alerting around it.

Diffy models environments — production, staging, development, plus custom ones — as first-class, with pages matched by path against a base URL. That enables scheduled monitoring: Diffy can run daily or weekly and compare an environment against itself over time, catching an expired plugin, a third-party script update or a CDN change that nobody shipped, with notifications by email, Slack or webhook and configurable thresholds.

CI/CD and integrations

Playwright’s CI story is mature: official GitHub Actions guidance, first-party Docker images for consistent rendering, test sharding across multiple machines with a merge-reports step to combine sharded HTML reports back into one, and reporters for JUnit, JSON and other formats most CI systems already understand.

What it doesn’t include out of the box is anything screenshot-specific for the pull request itself — no default action that posts a visual diff as a PR comment. Diffy provides:

  • A CLI for CI/CD, with worked examples for Pantheon, Tugboat, Platform.sh, CircleCI, GitLab and GitHub Actions
  • A GitHub Action that posts results back to the pull request
  • Zapier, so a diff can open a ticket in your tracker
  • Figma — compare a built page against the design export
  • Screenshot upload from Playwright itself (or any other functional testing framework), using Diffy purely as the comparison and review layer for images you already captured
Diffy CLI commands for triggering screenshot and comparison jobs from a CI pipeline.
Diffy’s CLI, with a GitHub Action that posts straight back to the PR.

That last point is worth dwelling on: you don’t have to choose. Keep Playwright for functional coverage and interaction-driven screenshots, and pipe the images it already captures into Diffy for the parts a local HTML report doesn’t do well — grouped review, hosted history, notifications, and a link a non-developer can open.

Driving Diffy from Claude

Diffy publishes a Claude plugin that runs visual testing from a conversation rather than a test file or a CI job. It installs in two lines:

/plugin marketplace add diffywebsite/diffy-skills
/plugin install diffy@diffy

That gives you skills under the /diffy: namespace covering project setup, capture (locally through the screenshot-worker container, or remotely on Diffy’s servers), comparison, and end-to-end runs that return a percentage changed, a per-page table and a JUnit report. The plugin is MIT licensed and developed in the open at github.com/DiffyWebsite/diffy-skills.

Where Playwright is the better fit

We would rather you picked the right tool than the one that emails you.

  • You need to test an interaction, not a page load. Click, fill, wait, then screenshot — Playwright drives the exact sequence. Diffy captures pages, not interaction sequences.
  • You want one framework for functional and visual testing. The same test that checks a form submits correctly can also assert the confirmation screen matches its baseline.
  • You need real Firefox alongside Chromium and WebKit. All three, version-pinned and bundled — no separately installed browsers or drivers to keep in sync.
  • You want network-level control over dynamic content. page.route() stubs the API response itself, which is more thorough than masking or mocking after the page renders.
  • You need zero licence cost at any volume, from a project actively maintained by Microsoft with releases every few weeks.
  • Nothing may leave your infrastructure. Playwright runs entirely in your own CI. Diffy’s local worker keeps capture local but still uploads to Diffy’s cloud for comparison.
  • You want component-level or clipped-region screenshots. clip and locator-scoped assertions go narrower than Diffy’s page-level capture.

Project status, as of this writing

@playwright/test is Apache-2.0 licensed and developed directly by Microsoft. The most recent release on npm is 1.62.1, published in July 2026, maintained by a team of Microsoft engineers with releases shipping every few weeks. Of every tool compared on this site, it’s the most actively maintained by a wide margin — there’s no staleness caveat to add here.

Playwright version, release date, licence and API facts verified against the npm registry and the Playwright documentation in August 2026. Diffy prices as published on our pricing page in August 2026.

Frequently asked questions

Does Playwright support masking dynamic elements in screenshots?

Yes. The mask option takes an array of Locators and overlays each one with a solid box, pink (#FF00FF) by default and customizable via maskColor. It’s a genuine, built-in equivalent to what a dedicated visual testing tool offers.

Why do my Playwright visual tests fail when a teammate runs them locally?

Snapshots are named per browser and platform — for example homepage-chromium-darwin.png versus homepage-chromium-linux.png — because rendering differs across operating systems, fonts and hardware. A baseline generated on macOS will not match a screenshot taken in Linux CI. Most teams generate and update baselines inside the same Docker image (mcr.microsoft.com/playwright) their CI uses, rather than on individual laptops.

Is Playwright free?

Yes. It’s Apache-2.0 licensed, developed directly by Microsoft, and there’s no cost at any volume. The most recent release on npm at the time of writing is 1.62.1, published in July 2026.

Can Diffy stub network responses the way Playwright can?

No. Diffy works at the page and DOM level — masking regions, replacing selector text with placeholder content. It doesn’t intercept and rewrite network requests the way Playwright’s page.route() does.

Does Playwright’s threshold option detect layout shifts the way Diffy’s algorithm does?

No. Playwright’s comparison engine is pixelmatch, a positional pixel-by-pixel comparator. Its threshold option sets how much perceived color difference is tolerated at each pixel, and maxDiffPixels/maxDiffPixelRatio set a budget for mismatched pixels — but it has no concept of a vertical shift. Diffy runs a separate algorithm alongside pixel-perfect comparison specifically to recognize vertical shifts and highlight only what actually changed.

Try it

Create an account, paste your URLs, and run your first comparison — the free plan covers 500 screenshots a month and there’s nothing to uninstall if it’s not for you.

If you’re choosing between a hosted platform and a code-first tool more broadly, our BackstopJS and Nightwatch comparisons cover that trade-off from other angles, and what visual regression testing is covers the basics.