How to run Playwright screenshot tests in a Docker container
Run Playwright screenshot tests in Docker with matching versions, stable visual baselines, and CI-ready commands. Includes troubleshooting and snapshot updates.
Run Playwright screenshot tests in a Docker container by pinning the container image and the project’s Playwright package to matching versions, installing the project dependencies, and comparing snapshots in the same container environment every time. Start with the official Playwright image, use --init and --ipc=host for Chromium, and begin CI with one worker.
The official Playwright image provides browser binaries and operating-system dependencies, but it does not install your project’s Playwright package. Your project still needs its own dependency installation, normally npm ci. A mismatch between the image and package versions can prevent Playwright from finding the expected browser executable. See the official Docker guide, visual comparison guide, and CI guide.
1. Pin matching Playwright versions
Use the same Playwright release for the project package and the container image. Check the official Docker documentation for the release tag that matches your project. The tag v1.63.0-noble is an example from the research materials, not a permanent default; use the version you have pinned in your project.
For an npm project, keep the Playwright Test dependency exact or lockfile-controlled. A minimal package.json might look like this (substitute your selected version consistently):
{
"scripts": {
"test:e2e": "playwright test"
},
"devDependencies": {
"@playwright/test": "1.63.0"
}
}
Commit the lockfile and use npm ci in the container so CI installs the locked dependency tree. If you use a package version other than the example, use its matching official image tag or install browsers with that same Playwright version.
2. Add a screenshot assertion
Playwright Test’s toHaveScreenshot() assertion captures a reference image the first time it runs, then compares later screenshots to that baseline. Here is a runnable starter test for a site your container can reach:
// tests/homepage.spec.ts
import { test, expect } from '@playwright/test';
test('homepage visual baseline', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveScreenshot('homepage.png');
});
The first run creates a test-specific snapshot. Review the generated file and commit it with the test. Do not accept regenerated images automatically: inspect the image difference and update the baseline only when the visual change is intentional.
For an application served by your test setup, navigate to the app’s actual URL and wait for meaningful readiness, such as a heading or loaded application state, before capturing. Avoid arbitrary short sleeps when an explicit readiness condition is available.
3. Run locally in the official container
From the repository root, run this command after replacing the image placeholder with the tag matching your project’s Playwright version:
docker run --rm --init --ipc=host \
-v "$PWD:/work" -w /work \
mcr.microsoft.com/playwright:<matching-version>-noble \
sh -lc 'npm ci && npx playwright test'
--init provides an init process to handle child processes cleanly. The Playwright Docker guide recommends --ipc=host for Chromium because insufficient shared memory can cause Chromium to run out of memory and crash. Keep generated snapshots, reports, and traces in the mounted repository or another CI-persisted location so you can review them.
Build dependencies into an application image
Installing dependencies on every run is convenient for a first setup. For repeated CI runs, a project image can install dependencies during the build instead:
# Dockerfile
FROM mcr.microsoft.com/playwright:<matching-version>-noble
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npx", "playwright", "test"]
Build and run it with:
docker build -t app-playwright-tests .
docker run --rm --init --ipc=host app-playwright-tests
When the test source changes, rebuild the image or mount the working tree. The official image already has the browser binaries and browser system dependencies; the project image adds your package dependencies and test code.
Build a custom browser image
If you need a different Node or operating-system base, install browser binaries and system dependencies using the same Playwright release as the package. This is an illustrative starting point; align versions and base OS choices with your project:
FROM node:20-bookworm
RUN npx -y playwright@1.63.0 install --with-deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npx", "playwright", "test"]
For Firefox and WebKit, avoid Alpine/musl images: Playwright documents those browser builds for glibc and says Alpine is unsupported. Install only the browser engines your test suite needs. See the official browser installation documentation.
4. Keep screenshot comparisons stable
A screenshot baseline is meaningful only when its environment is controlled. Playwright documents that rendering can vary with the host operating system, browser version, settings, hardware, power source, and headless mode. Create and compare baselines using the same pinned container image and project configuration. If you intentionally test multiple browsers or environments, use separate projects and project-specific snapshot names rather than comparing unlike renderings against one image.
Wait for stable page content
- Wait for a meaningful selector or application-ready condition before capture.
- Stabilize animations, clocks, random content, rotating banners, and other changing data when those details are irrelevant to the visual assertion.
- Use Playwright screenshot assertion options such as
stylePathto apply a screenshot stylesheet for known volatile elements when appropriate. - Choose
maxDiffPixelsor other comparison thresholds based on reviewed expected variation. A tolerance should not conceal real regressions.
The screenshot assertion takes repeated screenshots until two consecutive captures match before saving an initial reference. That helps with page stabilization, but it does not replace controlling application data or waiting for the correct page state.
Update a baseline intentionally
- Make the intended UI change.
- Run the test in the pinned container environment with
npx playwright test --update-snapshots. - Review each changed snapshot alongside the code change.
- Commit the updated baseline only after confirming that the difference is expected.
Do not run snapshot updates as a routine response to unexplained CI failures. First confirm that the test environment and page state are the same as the baseline run.
5. Configure CI for repeatable results
The general CI sequence is: check out the repository, make the matching browsers and OS dependencies available (often by using the Playwright image), install locked Node dependencies, run tests, and preserve the report and any failure artifacts. Playwright’s CI guide recommends one worker by default for stability and reproducibility. Increase concurrency only after observing the capacity of your runner; sharding can distribute a larger suite across jobs.
npm ci
npx playwright test --workers=1
Use your CI system’s container job configuration to select the matching Playwright image. Preserve the Playwright report and screenshot artifacts using that provider’s artifact mechanism so reviewers can inspect failures. Follow the official CI documentation for provider-specific examples.
Test a host service from inside Docker
Inside a container, localhost means the container itself. If your application server runs on the host, configure a host-gateway hostname using the networking approach for your platform, then navigate to that hostname from the test. Playwright’s Docker guide demonstrates a host-gateway mapping; do not assume a host’s localhost is reachable unchanged from a container.
6. Docker and Playwright options that matter
| Choice | Use it when | Tradeoff or note |
|---|---|---|
| Official Playwright image | You want browser binaries and OS dependencies assembled for Playwright. | Your project must still install its Playwright package; versions must match. |
| Custom image | You need a project-specific base or dependency layout. | You are responsible for matching browser installation and system dependencies to the package version. |
| One browser engine | The suite only needs one rendering target. | Install and run only the engines needed, reducing unnecessary setup. |
| Multiple browser projects | You need to verify cross-browser rendering. | Maintain suitable project-specific baselines because rendering may differ. |
| One CI worker | You need the stable starting configuration. | It may take longer; tune parallel workers or shard after checking runner capacity. |
| Root container user | You use the image defaults for trusted end-to-end test code. | The image runs as root by default, which disables Chromium’s sandbox. |
| Non-root user | Your CI threat model or environment calls for it. | Configure and validate the user and permissions for your image and tests. |
The Docker guide mentions --cap-add=SYS_ADMIN as a development troubleshooting option for unusual Chromium launch errors. Add it only when a concrete launch problem calls for it; it is not a default runtime flag.
Or skip the browser setup
If you need screenshots of public pages without maintaining a browser container, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF, and its parameters include the names used by other screenshot APIs to make switching easier. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners, popups, and chat widgets are removed before the shot.
- Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing result.
- An MCP server lets AI agents use screenshot tools through Claude, Cursor, or another MCP client.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Playwright cannot find a browser executable | The package and image or installed browser version do not match. | Use the same Playwright release for the package, image, or playwright install command. |
| Chromium crashes or reports memory-related launch errors | Chromium may be short of shared memory in the container. | Run with --ipc=host as recommended by the Docker guide. |
| Browser child processes hang or the job does not exit cleanly | The container lacks an init process to handle child processes. | Add Docker’s --init option. |
| Test navigation cannot reach a host-run app | localhost points at the container. |
Use the platform’s host-gateway mapping and navigate to its configured hostname. |
| Screenshot differs only in CI | OS, browser, headless mode, hardware, settings, or page state differs from baseline generation. | Generate and compare in the same pinned image, then stabilize page readiness and dynamic content. |
| Firefox or WebKit fails in an Alpine container | Those Playwright browser builds target glibc; Alpine uses musl. | Use a supported glibc-based image such as the matching official image or a compatible Debian base. |
| Browser launch fails for an unclear reason | Launch diagnostics are not visible in normal test output. | Set DEBUG=pw:browser to inspect browser launch details. |
| Headed Linux debugging fails due to missing display | Headed Linux runs need an X server. | Use Xvfb, for example xvfb-run npx playwright test; the official image includes Xvfb. |
| Snapshot update creates many unexpected changes | The environment or app state differs, or volatile content is being captured. | Do not commit blindly. Match the pinned environment, control dynamic state, and review each image. |
The CI guide does not generally recommend caching browser binaries: restoring them can take about as long as downloading them, and Linux OS dependencies cannot be cached. If you do cache browsers, key the cache to the Playwright version so it cannot silently pair with a different release.
Performance, reliability, and cost
- Build once for repeated jobs: baking npm dependencies into your project image avoids installing them on every invocation. Keep the image tied to the same Playwright release.
- Install only needed browsers: if the test suite targets one engine, avoid installing engines it never launches. Cross-browser coverage requires separate browser runs and may require separate snapshots.
- Start conservatively in CI: one worker is Playwright’s documented stability default. Add workers or shards only when runner capacity and test behavior support them.
- Preserve diagnostic output: keep reports and failure artifacts accessible after a CI job, so snapshot differences can be reviewed instead of guessed at.
- Account for compute, not API charges: Docker-based Playwright runs use your local or CI compute and browser setup. If you instead call ScreenshotNeo for public-page captures, its stated free tier is 1,000 shots per month, and paid plans begin at $5 for 3,000; only clean shots are billed.
FAQ
Does the Playwright Docker image include the npm package?
No. It includes browsers and browser OS dependencies. Install the package from your project, for example with npm ci.
Should I generate snapshots on my laptop and compare them in Linux CI?
For dependable comparisons, generate and compare baselines in the same container image and configuration. Platform and rendering differences can affect pixels.
Can I update every snapshot in CI automatically?
Use --update-snapshots for an intentional baseline change, then review the diffs and commit them. Routine automatic updates remove the review signal visual tests are meant to provide.
Do I need headed mode for screenshot assertions?
No. Playwright screenshot assertions can run headless. For headed Linux debugging, use Xvfb as described in the CI documentation.
When is ScreenshotNeo a better fit?
Use it when you want a screenshot or PDF of a reachable website without building and maintaining the browser runtime yourself. Playwright remains appropriate when the test needs direct control over your application and its browser session.


