Chromium vs Firefox vs WebKit Rendering for Screenshots

Chromium vs Firefox vs WebKit rendering, measured with Playwright: capture times, PNG sizes, native controls, lazy images, and which engine to use when.

By Simon Chiu · Updated

Disclosure: I build RenderScreenshot, a screenshot API that renders with Chromium only. This page compares three engines that you run yourself with Playwright, and it says plainly when our API is the wrong tool (any time you need Firefox or WebKit pixels).

If you need one engine for screenshots, use Chromium: it is what most of your visitors run, it launched fastest in our test, and almost every hosted screenshot API uses it. Use WebKit when you need to see what Safari users see, and Firefox when Firefox users matter to you or when you want a second opinion on a layout bug. The engines agreed on modern CSS in our tests. They disagreed on native form controls, full-page lazy loading and PNG size, and those are the differences that break screenshot pipelines.

This is part of our comparison hub. For Playwright basics (contexts, device scale factor, locators) start with the Playwright screenshot tutorial.

Last tested: October 15, 2026 with Playwright 1.56.1: Chromium 141.0.7390.37 (headless shell), Firefox 142.0.1, WebKit 26.0. Node 22.18.0, macOS 27.0.1, Apple M1 Pro, 32 GB.

The short answer

Use Chromium when Use Firefox when Use WebKit when
You need one engine for social cards, thumbnails, PDFs or archives Your analytics show a real Firefox audience Your audience is on iPhone, iPad or Safari
You want the fastest launch and the smallest PNGs on simple pages You debug a layout bug and want an independent engine You test forms, because Safari-style controls differ most
You want parity with a hosted screenshot API You test standards behaviour, not Chrome quirks You need a Safari-like render without a Mac in CI

WebKit in Playwright is not Safari. It is the same engine with Playwright's patches, running on your OS, so system fonts and some platform features differ from a real iPhone. It is still the closest you can get in a Linux container.

The test corpus

We wrote five small pages to isolate one kind of difference each, and served them from localhost so the network was not part of any measurement:

  • example: a mirror of example.com, as a baseline.
  • typography: serif heading, text-wrap: balance, monospace, small caps, CJK text, emoji (including a ZWJ sequence and a flag), text shadow, outlined text.
  • css-features: CSS Grid, :has(), color-mix() in OKLCH, backdrop-filter, a container query, CSS nesting, conic-gradient, clip-path, all over a gradient background.
  • forms: text input, select, checkbox, radio, range, date, color, progress, button, with no styling.
  • lazy-fullpage: eight loading="lazy" images, each in a 700 px section, captured full page.

Each page was captured 10 times per engine at 1280x800, device scale factor 1, PNG, one browser per engine and a new context per capture, timed from goto (waiting for load) to the returned buffer.

Results

Page Engine p50 p95 PNG bytes
example Chromium 47 ms 56 ms 18,497
example Firefox 59 ms 159 ms 36,386
example WebKit 28 ms 119 ms 35,378
typography Chromium 67 ms 83 ms 76,575
typography Firefox 73 ms 112 ms 100,599
typography WebKit 66 ms 87 ms 97,492
css-features Chromium 96 ms 99 ms 183,829
css-features Firefox 62 ms 217 ms 110,273
css-features WebKit 62 ms 73 ms 857,164
forms Chromium 47 ms 61 ms 19,652
forms Firefox 70 ms 73 ms 39,752
forms WebKit 33 ms 53 ms 37,627
lazy-fullpage Chromium 156 ms 164 ms 58,061
lazy-fullpage Firefox 111 ms 370 ms 170,541
lazy-fullpage WebKit 85 ms 87 ms 151,983

Browser launch (median of 3 launches): Chromium 74 ms, WebKit 197 ms, Firefox 571 ms. Success rate: 150 of 150 captures for every engine.

What the numbers say:

  1. Capture speed is a wash. No engine was fastest on every page, and every median is under 160 ms. On a real site, network and fonts dominate. Do not choose an engine for speed per capture.
  2. Launch time is not a wash. Firefox took about eight times longer than Chromium to start. If your design launches a browser per job (serverless functions often do), that cost lands on every request. Keep the browser warm, or use Chromium.
  3. PNG size varies a lot. WebKit's PNG of the gradient-heavy page was 4.7 times the size of Chromium's for the same 1280x800 pixels, and Firefox and WebKit produced roughly double Chromium's bytes on the simple pages. If you store engine screenshots, convert them to WebP or AVIF before you keep them; our format comparison has the measurements.
  4. First runs are slow. On the very first run on a fresh machine, one Chromium capture of the typography page took about 4 seconds while the OS built its font cache for CJK and emoji. It did not happen again. Warm up new containers with one throwaway capture.

Where the pixels differ

Native form controls

The same unstyled form in Chromium, Firefox and WebKit: Firefox's range thumb and date field are larger, WebKit's select, checkbox and button use Safari-style shapes

Rendered with Playwright 1.56.1, 1280x800, cropped to 420x340, October 15, 2026.

This is the largest difference in the corpus. Every control has its own engine look: Firefox draws a large round range thumb and a taller date field, WebKit uses Safari's rounded select with a blue stepper and a flatter button. Firefox's controls are also taller, so the same form ends about 20 px lower. If you use pixel diffs, that one row of height shifts everything below it, and the whole page shows as changed.

The fix for visual tests is per-engine baselines. The fix for marketing screenshots is to style controls explicitly (appearance: none plus your own CSS), so all engines draw the same thing.

Modern CSS

The CSS features page in three engines, nearly identical: grid cards, a :has() outline, a backdrop-filter panel, a green container-query box, red nested text, conic gradient and diamond clip-path

Same setup, full 1280x480 crop, scaled to 520 px per engine.

Everything rendered in all three: the grid, the :has() outline, color-mix(), backdrop-filter, the container query (green box), nesting (red text), the conic gradient and the clip path. Differences are sub-pixel text positions. In 2026, "this CSS does not work in engine X" is rarely why screenshots differ between current engines. It is a common reason when one side is old, as in our wkhtmltopdf vs Puppeteer comparison.

Typography, emoji and CJK

Typography test in three engines: same balanced headline wrap, monospace line, small caps, Japanese, Chinese and Korean text and colour emoji in each

Same setup, cropped to 900x560.

On macOS, all three engines look nearly identical, because they share the operating system's fonts, including the colour emoji font. That is a property of the test machine, not of the engines. On Linux, the result depends on which fonts are installed in the container, and each engine walks its fallback list differently. Missing CJK or emoji fonts show up as empty boxes. Install them explicitly (for Debian or Ubuntu: fonts-noto-cjk and fonts-noto-color-emoji) and test each engine after the install.

Lazy images in full-page captures

Full-page captures of eight lazy images: Chromium shows six, Firefox shows two plus alt text for the rest, WebKit shows two and blank space for the rest

Same setup, full page (1280x5608), captured right after the load event.

This is the difference most likely to hurt you in production. At the load event, none of the eight lazy images had finished in any engine; what appears in the capture depends on what each engine loads while it builds the full-page image. Chromium showed 6 of 8, Firefox 2 of 8 (with alt text where the others should be), WebKit 2 of 8 (with blank space). None of them is "correct", and a test that passes in Chromium can fail in the other two.

The fix works in all three. Scroll through the page one screen at a time, then wait until every image has decoded:

// Scroll so lazy images start loading, then wait for all of them to decode.
// Works the same in Chromium, Firefox and WebKit with Playwright 1.56.
async function loadLazyImages(page) {
  await page.evaluate(async () => {
    for (let y = 0; y < document.body.scrollHeight; y += window.innerHeight) {
      window.scrollTo(0, y);
      await new Promise((r) => setTimeout(r, 150));
    }
    window.scrollTo(0, 0);
  });
  await page.waitForFunction(() =>
    [...document.images].every((img) => img.complete && img.naturalWidth > 0));
}

With this step before page.screenshot({ fullPage: true }), all three engines rendered 8 of 8 images. Give waitForFunction a timeout in production; a single broken image URL will otherwise hold the capture until the default timeout. Our Puppeteer full-page guide covers the same problem with sticky headers and infinite scroll.

Code: one page, three engines

// three-engines.mjs: one page, three engines, same viewport.
// npm i [email protected] && npx playwright install chromium firefox webkit
import { chromium, firefox, webkit } from 'playwright';

const url = process.argv[2] ?? 'https://example.com/';
for (const [name, engine] of Object.entries({ chromium, firefox, webkit })) {
  const browser = await engine.launch();
  try {
    const context = await browser.newContext({ viewport: { width: 1280, height: 800 }, deviceScaleFactor: 1 });
    const page = await context.newPage();
    await page.goto(url, { waitUntil: 'load' });
    await page.screenshot({ path: `${name}.png`, fullPage: true });
    console.log(`${name} ${browser.version()} -> ${name}.png`);
  } finally {
    await browser.close();
  }
}

Tested on October 15, 2026 with Node 22.18.0 and [email protected] against the local corpus.

Set deviceScaleFactor on the context; page.setViewportSize() has no such option. Note that page.screenshot() in Playwright writes PNG or JPEG only. If you want WebP for storage, convert the buffer afterwards.

The benchmark harness behind the table is the same loop with timing, 10 runs per page, a fresh context per run, and a CSV writer. Run three launches first and discard them if you want warm numbers.

Engines in production

For a screenshot service, the engine choice also decides your operations:

Chromium Firefox WebKit
Puppeteer support Yes (primary) Yes (via WebDriver BiDi) No
Playwright support Yes Yes (patched build) Yes (patched build)
Hosted screenshot APIs Nearly all, including RenderScreenshot Rare Rare
Launch in our test 74 ms 571 ms 197 ms
Biggest screenshot risk Lazy images partly loaded Lazy images, slow launch Lazy images, large PNGs

Running all three engines in one service triples the browser binaries, the font testing and the update work. Most teams that need Firefox or WebKit renders need them for testing, not for production images, and run them in CI rather than in a public service.

Where RenderScreenshot fits

Our API renders with Chromium on Cloudflare Browser Rendering, so it is the right tool when Chromium output is what you want: social cards, link previews, PDFs, archives, and monitoring. It handles the lazy-image problem from this page with the full_page preset, which captures the full page with scroll simulation (capture.scroll.simulate), and you can wait for a selector or network idle with the wait parameters.

If your requirement is "what does this page look like in Safari", it is the wrong tool: use Playwright's WebKit as shown above. If your requirement is "a correct Chromium screenshot without running Chromium", get a free key and compare our render of your hardest page with your local Chromium capture.

Frequently asked questions

Do Chromium, Firefox and WebKit render the same page differently?
Layout and modern CSS matched closely in our tests (grid, :has(), container queries, color-mix, nesting and backdrop-filter rendered in all three). Native form controls, scrollbars, font fallback and lazy-image loading during full-page capture differed. Those differences change pixel diffs and page heights.
Which engine is fastest for screenshots?
With a warm browser, all three captured simple local pages in 28 to 96 ms at the median in our test, and no engine won on every page. Browser launch differed more: about 74 ms for Playwright's Chromium headless shell, 197 ms for WebKit and 571 ms for Firefox.
Which engine should I use for visual regression tests?
Use the engine your users run. Take a separate baseline per engine, because native controls and font rendering differ even when the layout is the same. Running all three on every commit is usually wasteful; Chromium on every commit and WebKit and Firefox nightly is a common split.
What engine does RenderScreenshot use?
Chromium, through Cloudflare Browser Rendering. If you need Firefox or WebKit output, run Playwright yourself.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.