ScreenshotNeo

BlogHow-to

Playwright Screenshot Testing with Docker: Browser and Font Setup

Set up Playwright screenshot tests in Docker with matching browsers, explicit fonts, and stable baselines for CI.

By the ScreenshotNeo team4 October 202610 min read

For repeatable Playwright screenshot tests in Docker, pin a Playwright image version and use the same Playwright package version in your project. Make sure the container has the fonts your application needs, wait for web fonts to load before capture, and create and compare baselines in the same browser and operating system environment. Docker reduces some environment differences, but it does not guarantee identical screenshots across hosts: browser version, operating system, settings, hardware, power source, and headless mode can all affect rendering.

This guide covers the official Playwright image and custom images, a runnable JavaScript setup, font handling, baseline workflow, CI, and troubleshooting.

1. Choose an official Playwright image or a custom image

Approach What you get What you must manage
Official Playwright image Playwright browser binaries and browser system dependencies. Install your project’s Playwright package separately and align its version with the image.
Custom Linux base image Control over the operating system image and other installed software. Install Node.js, the matching Playwright package, browser binaries, and browser system dependencies.

The official image does not include the Playwright package for your project. The Docker documentation says a mismatch between the image’s browser binaries and the Playwright package can prevent Playwright from locating browser executables. Pin a published image tag and match its Playwright version to your project dependency. Playwright recommends pinning the image version when possible. Check the current tag before publishing or upgrading because image tags and releases change.

Playwright’s documented custom-image pattern is npx -y playwright@<matching-version> install --with-deps. The documentation example uses version 1.63.0; use the version matching your project rather than copying that version blindly. The command installs browsers and their system dependencies. It does not ensure that application-specific fonts match those on a developer workstation.

Playwright’s Firefox and WebKit builds target glibc-based distributions; Alpine and other musl-based distributions are unsupported for those browsers. The current Docker documentation lists Ubuntu Noble, Jammy, and Resolute variants, but treat available tags as version-sensitive and verify the tag you choose.

2. Run screenshot tests in Docker

Here is a small JavaScript project using Playwright Test. The example uses an official Playwright image; substitute a currently published tag that matches the version in package.json.

Project files

{
  "name": "visual-tests",
  "private": true,
  "scripts": {
    "test": "playwright test"
  },
  "devDependencies": {
    "@playwright/test": "1.63.0"
  }
}

playwright.config.js:

const { defineConfig } = require('@playwright/test');

module.exports = defineConfig({
  testDir: './tests',
  snapshotPathTemplate: '{testDir}/__screenshots__/{projectName}/{arg}{ext}',
  projects: [
    {
      name: 'chromium',
      use: { browserName: 'chromium' }
    }
  ],
  workers: process.env.CI ? 1 : undefined
});

tests/homepage.spec.js:

const { test, expect } = require('@playwright/test');

test('homepage visual baseline', async ({ page }) => {
  await page.goto('http://app:3000/', { waitUntil: 'networkidle' });
  await page.evaluate(() => document.fonts.ready);
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
    animations: 'disabled'
  });
});

Dockerfile for running the test project in the official image:

FROM mcr.microsoft.com/playwright:v1.63.0-noble
WORKDIR /work
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npx", "playwright", "test"]

Use a tag that actually exists and matches the installed Playwright release. If your app runs in another container, put both containers on the same Docker network and use the app container’s service name in the test URL. For a local app, expose it to the test container in a way that works for your Docker environment; localhost inside the test container refers to that container itself.

Build and run with Docker’s recommended initialization and shared IPC settings for Chromium:

docker build -t visual-tests .
docker run --rm --init --ipc=host visual-tests

Playwright recommends --init and advises --ipc=host for Chromium, which may otherwise run out of memory and crash. If Chromium has unusual launch problems in local development, Playwright suggests trying --cap-add=SYS_ADMIN. Treat that as a targeted troubleshooting option, not a default requirement.

Custom base image

For a custom Linux image, install Node.js first, then use the Playwright CLI version that matches the project:

FROM node:22-bookworm
WORKDIR /work
COPY package*.json ./
RUN npm install
RUN npx -y playwright@1.63.0 install --with-deps
COPY . .
CMD ["npx", "playwright", "test"]

Align both the example CLI version and the project dependency with the release you select. The CLI’s browser installation supports installing defaults with no browser argument, or narrowing installation by naming browsers. Choose deliberately if your tests use a browser matrix.

3. Install and verify the fonts your pages need

Fonts change text width, line wrapping, element height, and therefore the whole screenshot. Playwright’s visual comparison guidance identifies fonts as one source of screenshot differences. The reviewed Docker documentation does not provide a definitive inventory of font families bundled in current image tags, so inspect the exact image you use and explicitly install project-required fonts rather than assuming it has the same fonts as your workstation.

  1. List the fonts your application actually uses, including fallback fonts and any non-Latin scripts.
  2. Ensure the chosen image contains those fonts or add them to your custom image using the package mechanism for its Linux distribution.
  3. Verify the computed font family in the browser and ensure the page’s web fonts have loaded before capture. await page.evaluate(() => document.fonts.ready) waits for the document’s font loading set to settle.
  4. Generate baselines in the same image and browser configuration used for comparisons.

Waiting for document.fonts.ready does not make a missing font appear. If the intended face fails to load, the browser can render its fallback and still finish waiting. Check network responses and computed styles when text geometry differs.

4. Create and maintain screenshot baselines

Playwright Test’s toHaveScreenshot() creates an expected screenshot on its first run and compares later runs against that baseline. Playwright waits until two consecutive screenshots match before saving the initial reference. PNG is the default; the visual comparison guide also supports .webp snapshots.

  1. Choose the browser project and Docker image that represent the environment you want to compare.
  2. Run the test once to generate the baseline.
  3. Review the generated image. Commit the snapshot directory with the test code so changes are reviewable.
  4. Run the test in CI with the same image, browser, and relevant settings.
  5. When a visual change is intentional, inspect it and update references explicitly with npx playwright test --update-snapshots. Review and commit those changes.

Snapshot filenames can include test and snapshot identity as well as browser or platform information. A project name in a multi-project configuration can keep browser-specific baselines distinct. Use separate baselines when you intentionally test browsers or platforms that render differently.

For repeatability, control dynamic inputs in the page: use stable test data, freeze clocks or timestamps where relevant, avoid random content, and wait for the particular UI state you need. Disabling animations can reduce transient differences. Avoid broad waits as a substitute for waiting for the actual content and fonts your screenshot depends on.

5. Configure browser coverage and CI

Playwright supports Chromium, Firefox, WebKit, branded Chrome and Edge, and device emulation. Start with the browser or browsers your users rely on. Each browser and platform can produce different rendering, so a multi-browser matrix may need separate baselines. Emulated device settings also affect viewport and device characteristics; keep those settings fixed for a given baseline.

The official CI guide recommends one worker for stability and reproducibility in CI. Stronger self-hosted runners can use parallel execution or sharding when throughput matters. More parallel work can increase resource contention, so assess whether the runner can support it consistently.

A typical CI sequence is:

npm ci
npx playwright install --with-deps
npx playwright test

Alternatively, run the tests inside a matching official Playwright image with your project package installed. Playwright does not recommend caching browser binaries in CI as a general optimization: restoring them can take a similar amount of time to downloading them, and Linux operating system dependencies cannot be cached as browser files. If you do cache binaries, key the cache to the Playwright version.

6. Why screenshots differ in Docker and CI

Source of drift What to check
Playwright or browser version Match the project package to the image/browser release; rebuild after changing versions.
Fonts Confirm required fonts exist, web fonts loaded, and fallback fonts are not being used.
Operating system and libraries Use the same pinned image for baseline generation and comparison.
Viewport, scale, or device settings Keep viewport and device emulation configuration stable across runs.
Headless mode and browser settings Keep launch mode and screenshot options consistent.
Hardware, power source, or resource pressure Reduce concurrent load; use the same class of CI runner when possible.
Dynamic page content Stabilize test data and wait for the target state before capture.

Playwright explicitly identifies host OS, browser version, settings, hardware, power source, and headless mode as sources of variation. A container helps pin the userspace and browser dependencies, but the host and runtime still matter. Use containers to make the comparison environment more controlled, not as a promise of pixel identity across every machine.

7. Troubleshooting

Symptom Likely cause Fix
Playwright cannot find the browser executable Project package and image/browser versions are mismatched, or the selected browser was not installed. Align versions, rebuild the image, and install the required browser using the matching Playwright CLI.
Firefox or WebKit fails to launch on Alpine Those browser builds target glibc; Alpine uses musl. Use a supported glibc-based image.
Chromium crashes or reports memory-related failures Shared memory may be insufficient. Run the container with --ipc=host as Playwright recommends; reduce parallel load if needed.
Chromium launch fails in local development Container runtime capabilities or sandbox conditions may be involved. Try Playwright’s suggested --cap-add=SYS_ADMIN option for diagnosis. Review the security implications for your workload.
Text wraps differently from the baseline A font is missing, failed to load, or a different fallback is used. Install the required font in the image, inspect computed styles and font network responses, then regenerate a baseline only if the intended output changed.
Screenshot captures a loading state The page was captured before the app or its fonts/content reached the intended state. Wait for a specific selector or app-ready condition, and await document.fonts.ready for web fonts.
URL works on the host but not in the test container localhost resolves to the test container. Use a reachable service name on a shared Docker network or configure host access for your platform.
Snapshots change on every CI run Uncontrolled data, different image tags, browser versions, settings, or resource contention. Pin the environment, stabilize inputs, keep settings fixed, and consider one CI worker.
Baseline updates contain unexpected changes The update command regenerated references without review, or the environment drifted. Inspect the image diff, confirm the environment, and commit only understood visual changes.

8. Performance, reliability, and cost notes

Building an image with browser dependencies makes the runtime environment reproducible, but image size and build time depend on the browsers and system dependencies installed. Install only the browsers your tests need when using the CLI’s named browser options. Avoid adding parallel workers until the runner has enough memory and CPU to avoid resource pressure. The Playwright CI guide’s one-worker recommendation favors stability; parallelism and sharding are options for capable runners.

Browser downloads and Linux dependencies are separate concerns: caching browser files does not capture the operating system dependencies. Playwright says browser caching often does not save time because cache restoration can take as long as downloading, and dependencies cannot be cached that way. Pinning versions and rebuilding deliberately makes failures easier to diagnose than silently changing browser binaries.

These tests run in infrastructure you provide, so account for CI minutes, image storage and transfer, and the time spent reviewing visual changes. The research sources provide no benchmark or universal cost figure; measure against your own runner and test suite.

9. Or skip the browser setup

If your goal is to capture a live website rather than maintain a Playwright visual regression suite, ScreenshotNeo provides a screenshot API and MCP server. One GET request returns an image or PDF. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners are accepted and removed before the shot; 60+ known consent platforms, newsletter popups, and chat widgets can be removed, and each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and whether the shot was billed.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots. Every feature is on every plan.

Sign up free for 1,000 screenshots a month, no card required.

10. FAQ

Can Docker guarantee identical screenshots on every developer machine?

No. It controls important parts of the environment, but host OS, hardware, settings, and headless mode can still affect rendering. Generate and compare baselines in the same controlled environment.

Does the official image include my project’s Playwright dependency?

No. It includes browser binaries and browser system dependencies; install the Playwright package in your project.

Should I commit visual baselines?

Yes. Committing reviewed snapshots lets code review show what changed and keeps the expected output with the tests.

Can I use ScreenshotNeo for visual regression baselines?

ScreenshotNeo is a website screenshot API, while Playwright Test provides the baseline generation and comparison workflow described here. Use Playwright when you need that test assertion workflow.