📖 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

PNG golden: ~500 KB per screenshot
⬇️
Lossless WebP golden: ~150 KB (70% smaller, zero quality loss)
⬇️
Lossy WebP quality 50: ~50 KB (90% smaller, minor quality loss)
⬇️
300 tests: PNG = 150 MB, Lossless WebP = 45 MB, Lossy = 15 MB

⚙️ 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).

typescript
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.

typescript
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.

typescript
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)

typescript
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+)

typescript
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.

⚠️ Gotcha

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

  1. Use .webp extension on toHaveScreenshot() for lossless goldens — 70% smaller than PNG.
  2. Use quality: 50–80 for standalone thumbnails and CI artifacts where some loss is acceptable.
  3. Never use lossy WebP for visual regression goldens — compression artifacts cause false positives.
  4. Add *.webp binary to .gitattributes if storing goldens in Git.
  5. 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

Beginner
How do you enable lossless WebP for a visual regression golden?
Show Answer
Use the .webp extension with toHaveScreenshot(): expect(page).toHaveScreenshot('homepage.webp'). The .webp extension forces lossless storage. The golden is 50-80% smaller than PNG with zero quality loss.
Intermediate
What is the difference between toHaveScreenshot('name.webp') and page.screenshot({ path: 'name.webp', quality: 50 })?
Show Answer
toHaveScreenshot('name.webp') stores a lossless golden (zero quality loss, used for visual regression). page.screenshot({ quality: 50 }) uses lossy compression (10x smaller, but has compression artifacts). Use the former for goldens, the latter for thumbnails and CI artifacts.
Advanced
Why should you never use lossy WebP for visual regression goldens?
Show Answer
Lossy compression changes pixel values, which causes false-positive visual diffs on every run. The golden would never match the actual screenshot because both are recompressed with slight variations. Always use lossless WebP (.webp extension on toHaveScreenshot) for goldens.
Scenario
Your Git repo has 1.5 GB of PNG visual regression goldens. How do you reduce it?
Show Answer
Migrate all toHaveScreenshot('name.png') calls to toHaveScreenshot('name.webp'). The .webp extension stores lossless goldens that are 50-80% smaller. Delete the old .png goldens. Add *.webp binary to .gitattributes. The repo should drop to ~400 MB with zero visual quality loss.

🏋️ Practical Exercise

🎯 Hands-On Practice easy

Migrate a Golden to WebP

  1. Find a test with toHaveScreenshot('name.png')
  2. Change it to toHaveScreenshot('name.webp')
  3. Delete the old name.png golden file
  4. Run the test with --update-snapshots to generate the new .webp golden
  5. Compare the file sizes: ls -la name.png name.webp
  6. Verify the WebP is 50–80% smaller

🚀 Mini Project

🏗️ The Repo Slim-Down

  1. Audit all toHaveScreenshot() calls in your suite
  2. Migrate every .png golden to .webp
  3. Run npx playwright test --update-snapshots to regenerate all goldens
  4. Delete all old .png files: find tests -name '*.png' -delete
  5. Add *.webp binary to .gitattributes
  6. Commit and measure the repo size reduction — expect 50–80%

📝 Quiz

Test your understanding. Click an option to check your answer.

1. How do you enable lossless WebP for a visual regression golden?
A) page.screenshot({ webp: true })
B) Use .webp extension with toHaveScreenshot()
C) Set quality: 100 in toHaveScreenshot()
D) It's the default
Answer: B — toHaveScreenshot('name.webp') forces lossless WebP storage via the .webp extension.
2. What quality value is lossless for page.screenshot()?
A) 50
B) 75
C) 100
D) Any value is lossless
Answer: C — quality: 100 is lossless. Values below 100 use lossy compression. The default is 100 (lossless).
3. How much smaller is lossless WebP compared to PNG?
A) 10%
B) 26% on average
C) 90%
D) No difference
Answer: B — Lossless WebP is ~26% smaller than PNG on average, with zero quality loss. Some images see 50-80% reduction.
4. Should you use lossy WebP for visual regression goldens?
A) Yes, it's smaller
B) No — compression artifacts cause false-positive diffs
C) Only for full-page screenshots
D) Only on Chromium
Answer: B — Lossy compression changes pixel values, causing false-positive visual diffs on every run. Always use lossless WebP for goldens.
5. What should you add to .gitattributes when storing WebP goldens in Git?
A) *.webp text
B) *.webp binary
C) *.webp merge=union
D) Nothing
Answer: B — Add *.webp binary to .gitattributes to prevent Git from trying to merge WebP files as text, which can corrupt them.
6. Which Playwright version introduced WebP screenshot support?
A) 1.60
B) 1.61
C) 1.62
D) 1.63
Answer: C — WebP screenshot support shipped in Playwright 1.62 (July 24, 2026).
7. What is a good quality value for CI artifact thumbnails?
A) 1
B) 50
C) 100
D) It doesn't matter
Answer: B — quality: 50 produces a file 10x smaller than PNG with acceptable quality for human review of thumbnails and overview shots.
8. Can locator.screenshot() use WebP?
A) No, only page.screenshot()
B) Yes — it accepts the same path and quality options
C) Only with a plugin
D) Only on Firefox
Answer: B — locator.screenshot() accepts the same { path, quality } options as page.screenshot(). Use .webp extension in the path and quality for lossy/lossless control.
9. Is WebP encoding faster or slower than PNG?
A) Faster
B) Slower (~20%)
C) Same speed
D) 10x slower
Answer: B — WebP encoding is ~20% slower than PNG, but this is negligible compared to the storage and Git operation time saved. The smaller files more than make up for the encoding overhead.
10. What happens if you mix .png and .webp goldens in the same test?
A) They merge automatically
B) Playwright treats them as separate goldens
C) The test fails
D) Only the .webp is used
Answer: B — Playwright treats 'name.png' and 'name.webp' as separate goldens. Pick one format per test and migrate all goldens in one PR to avoid confusion.

❓ 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 .webp extension on toHaveScreenshot() for lossless goldens — 50–80% smaller than PNG.
  • Use quality: 50–80 for lossy standalone screenshots (thumbnails, CI artifacts).
  • Never use lossy WebP for goldens — compression artifacts cause false-positive diffs.
  • Add *.webp binary to .gitattributes when storing goldens in Git.
  • Migrate all PNG goldens in one PR — don’t mix formats in the same test.