WebP Screenshots — 10x Smaller Visual Regression
Playwright 1.62 supports lossless WebP for toHaveScreenshot() and lossy WebP for page.screenshot(). Cut artifact storage 10x without losing visual fidelity.
📖 The Story: The 2 GB Git Repo
A team had 300 visual regression tests, each with a golden screenshot stored in the Git repo. The screenshots were PNG, averaging 500 KB each. The repo was 1.5 GB — cloning took 4 minutes, and every PR that updated a screenshot pushed 5–10 MB.
The team tried Git LFS, but it added complexity and the LFS quota ran out every quarter. They tried reducing screenshot resolution, but the goldens became too low-quality to catch subtle CSS regressions.
They considered JPEG, but JPEG is lossy — the goldens had compression artifacts that caused false-positive visual diffs on every run.
WebP in 1.62 solved it. Lossless WebP goldens are 50–80% smaller than PNG, with zero quality loss. The repo dropped from 1.5 GB to 400 MB overnight.
🎯 Why It Matters
10x Smaller
Lossless WebP is 50-80% smaller than PNG. Lossy WebP at quality 50 is 10x smaller than PNG.
Lossless
toHaveScreenshot('name.webp') stores goldens as lossless WebP — zero quality loss, zero false positives.
Quality Control
page.screenshot({ quality: 50 }) lets you trade size for quality on standalone screenshots.
Repo-Friendly
Smaller goldens mean smaller Git repos, faster clones, and cheaper LFS bills.
🌍 Real World Example
A team with 300 visual regression tests stored PNG goldens in Git. The repo was 1.5 GB. After migrating to toHaveScreenshot('name.webp') (lossless), the repo dropped to 400 MB — a 73% reduction with zero visual quality loss. PRs that updated screenshots went from 10 MB to 2.5 MB.
🧒 Explain Like I'm 10
Imagine you’re saving photos. PNG is like saving every single pixel exactly — the file is huge but perfect. JPEG is like drawing a rough sketch — the file is small but blurry.
WebP is magic: it can be as perfect as PNG (lossless) but much smaller, or it can be a smart sketch (lossy) that’s 10x smaller than JPEG but looks just as good.
For visual regression tests, you want perfect (lossless WebP). For thumbnail overviews, you want small (lossy WebP).
🎓 Professional Explanation
WebP is Google’s image format that supports both lossless and lossy compression. Lossless WebP is typically 26% smaller than PNG; lossy WebP is typically 25–34% smaller than comparable JPEGs. Playwright 1.62 adds WebP support to expect(page).toHaveScreenshot(), page.screenshot(), and locator.screenshot().
For visual regression goldens, use the .webp extension with toHaveScreenshot() — this stores the golden as lossless WebP. For standalone screenshots (CI artifacts, thumbnails), use page.screenshot({ path: 'name.webp', quality: 50 }) — this uses lossy compression with a configurable quality from 1 to 100 (100 is lossless).
📊 The Flow
PNG vs WebP File Size Comparison
⚙️ WebP Screenshots
Use the .webp extension with toHaveScreenshot() for lossless goldens. Pass { path: 'name.webp', quality: N } to page.screenshot() for lossy standalone screenshots (N = 1–100, where 100 is lossless).
import { test, expect } from class="tk-str">'@playwright/test'; test(class="tk-str">'visual regression with lossless WebP', async ({ page }) => { await page.goto(class="tk-str">'https:class="tk-com">//app.example.com'); class=class="tk-str">"tk-com">// .webp extension = lossless golden, ~70% smaller than .png await expect(page).toHaveScreenshot(class="tk-str">'homepage.webp'); });
The .webp extension on toHaveScreenshot() stores the golden as lossless WebP. Zero quality loss, zero false-positive diffs, but 50–80% smaller than PNG.
class=class="tk-str">"tk-com">// Standalone screenshot with lossy WebP test(class="tk-str">'capture a thumbnail', async ({ page }) => { await page.goto(class="tk-str">'https:class="tk-com">//app.example.com'); class=class="tk-str">"tk-com">// quality 1-100 (100 = lossless). 50 is a good default for thumbnails await page.screenshot({ path: class="tk-str">'thumbnail.webp', quality: 50, }); });
page.screenshot({ path, quality }) uses lossy WebP when quality < 100. Quality 50 produces a file ~10x smaller than PNG, with minor quality loss acceptable for thumbnails and overview shots.
class=class="tk-str">"tk-com">// Locator-level screenshots also support WebP test(class="tk-str">'component screenshot', async ({ page }) => { await page.goto(class="tk-str">'https:class="tk-com">//app.example.com'); await page.locator(class="tk-str">'.header').screenshot({ path: class="tk-str">'header.webp', quality: 80, class=class="tk-str">"tk-com">// near-lossless, good for CI artifacts }); }); class=class="tk-str">"tk-com">// Full-page screenshot as lossless WebP test(class="tk-str">'full page golden', async ({ page }) => { await page.goto(class="tk-str">'https:class="tk-com">//app.example.com'); await expect(page).toHaveScreenshot(class="tk-str">'full-page.webp', { fullPage: true, }); });
locator.screenshot() also accepts the quality option. For full-page visual regression, use toHaveScreenshot('name.webp', { fullPage: true }) — lossless, but much smaller than a PNG full-page golden.
🔄 Before vs After
Before: PNG screenshots (1.61 and earlier)
class=class="tk-str">"tk-com">// PNG — large files, especially for full-page screenshots await expect(page).toHaveScreenshot(class="tk-str">'homepage.png'); await page.screenshot({ path: class="tk-str">'homepage.png' }); class=class="tk-str">"tk-com">// 500 KB per screenshot, 150 MB for 300 tests
PNG is lossless but uncompressed — files are large. A single full-page screenshot can be 1–2 MB. 300 visual regression tests = 150 MB of goldens in your Git repo.
After: WebP screenshots (1.62+)
import { test, expect } from class="tk-str">'@playwright/test'; test(class="tk-str">'visual regression with lossless WebP', async ({ page }) => { await page.goto(class="tk-str">'https:class="tk-com">//app.example.com'); await expect(page).toHaveScreenshot(class="tk-str">'homepage.webp'); }); class=class="tk-str">"tk-com">// ~150 KB per screenshot (lossless), 45 MB for 300 tests
Lossless WebP is 50–80% smaller than PNG with zero quality loss. The same 300 tests produce 45 MB of goldens instead of 150 MB. PRs that update screenshots are 3x smaller.
WebP quality 100 is not the same as .webp toHaveScreenshot. For toHaveScreenshot('name.webp'), the .webp extension forces lossless storage of the golden. For page.screenshot({ path: 'name.webp', quality: 100 }), quality 100 is also lossless, but quality 50 introduces lossy compression that can produce visible artifacts on text-heavy pages. Use lossy WebP only for thumbnails; keep lossless for visual regression goldens.
⚠️ Common Mistakes
Mistake 1: Using lossy WebP for visual regression goldens
Lossy WebP (quality < 100) introduces compression artifacts that cause false-positive visual diffs on every run. Always use toHaveScreenshot('name.webp') (lossless via extension) for goldens, not page.screenshot({ quality: 50 }).
Mistake 2: Forgetting to update .gitattributes
If you store WebP goldens in Git, add *.webp binary to .gitattributes to prevent Git from trying to merge them as text. Without this, a WebP golden update can corrupt the file on merge.
Mistake 3: Mixing PNG and WebP goldens in the same test
If a test calls toHaveScreenshot('name.png') and later toHaveScreenshot('name.webp'), Playwright treats them as separate goldens. Pick one format per test and stick with it. Migrate all PNG goldens to WebP in one PR.
✅ Best Practices
- Use
.webpextension ontoHaveScreenshot()for lossless goldens — 70% smaller than PNG. - Use
quality: 50–80for standalone thumbnails and CI artifacts where some loss is acceptable. - Never use lossy WebP for visual regression goldens — compression artifacts cause false positives.
- Add
*.webp binaryto .gitattributes if storing goldens in Git. - Migrate all PNG goldens to WebP in one PR — don’t mix formats in the same test.
🏗️ Senior Engineer Deep Dive
Lossless WebP vs PNG: why WebP wins
Both formats are lossless, but WebP uses a more efficient compression algorithm (predictive coding + entropy coding) that produces 26% smaller files on average for the same pixel-perfect output. For Playwright visual regression, this means the golden is byte-identical in visual quality but significantly smaller on disk. The only downside: WebP encoding is ~20% slower than PNG, but this is negligible compared to the time saved in storage and Git operations.
When to use lossy WebP
Lossy WebP (quality < 100) is for non-golden screenshots: CI artifact thumbnails, bug report images, overview shots in HTML reports. These don’t need pixel-perfect quality — they need to be small enough to attach to a Jira ticket or embed in a Slack message. Quality 50 produces a file 10x smaller than PNG with acceptable quality for human review. Never use lossy for visual regression goldens — the compression artifacts change the pixel values and cause false-positive diffs.
💼 Interview Questions
Show Answer
Show Answer
Show Answer
Show Answer
🏋️ Practical Exercise
Migrate a Golden to WebP
- Find a test with
toHaveScreenshot('name.png') - Change it to
toHaveScreenshot('name.webp') - Delete the old
name.pnggolden file - Run the test with
--update-snapshotsto generate the new.webpgolden - Compare the file sizes:
ls -la name.png name.webp - Verify the WebP is 50–80% smaller
🚀 Mini Project
🏗️ The Repo Slim-Down
- Audit all
toHaveScreenshot()calls in your suite - Migrate every
.pnggolden to.webp - Run
npx playwright test --update-snapshotsto regenerate all goldens - Delete all old
.pngfiles:find tests -name '*.png' -delete - Add
*.webp binaryto.gitattributes - Commit and measure the repo size reduction — expect 50–80%
📝 Quiz
Test your understanding. Click an option to check your answer.
❓ FAQ
Q: Do all browsers render WebP screenshots the same way?
A: WebP screenshots are files on disk — they’re not rendered by the browser. Playwright captures the page via the browser’s screenshot API (which produces raw pixels), then encodes to WebP using a built-in encoder. The browser type doesn’t affect the WebP output.
Q: Can I convert existing PNG goldens to WebP without re-capturing?
A: Yes. Use ImageMagick or sharp: magick name.png name.webp (lossless) or sharp(name.png).webp({ lossless: true }).toFile('name.webp'). Then update the test from .png to .webp. This avoids re-capturing all goldens.
Q: Does WebP work with full-page screenshots?
A: Yes. expect(page).toHaveScreenshot('full.webp', { fullPage: true }) captures a full-page lossless WebP golden. Full-page screenshots see the biggest size reduction — a 2 MB PNG full-page golden becomes ~600 KB as lossless WebP.
📦 Summary
🎯 Key Takeaways
- Use
.webpextension ontoHaveScreenshot()for lossless goldens — 50–80% smaller than PNG. - Use
quality: 50–80for lossy standalone screenshots (thumbnails, CI artifacts). - Never use lossy WebP for goldens — compression artifacts cause false-positive diffs.
- Add
*.webp binaryto .gitattributes when storing goldens in Git. - Migrate all PNG goldens in one PR — don’t mix formats in the same test.
Comments
Comments
Post a Comment