Running Playwright tests locally feels fast and reliable, but the moment your CI pipeline kicks in, screenshots mismatch, fonts render differently, and flaky tests multiply. Testing in Docker: Playwright best practices explained is the antidote—it ensures that the environment where you develop, test, and deploy stays perfectly synchronized. In this guide, we'll walk through the practical setup that keeps your end-to-end test suite predictable across every machine, CI runner, and developer's laptop.
Why Docker matters for Playwright tests
Playwright is a powerful browser automation framework, but it's also environment-sensitive. Fonts, system libraries, browser versions, and OS configuration all affect test behavior. When your colleague runs tests on macOS and your CI uses Ubuntu, visual regression tests fail even though the code is identical. Docker eliminates this friction by packaging the exact same OS, system libraries, and browser versions into a reproducible container.
The most common scenario we see: a developer writes a visual regression test that passes locally. They push to main, CI runs it, and it fails on font rendering or button spacing. Six commits later, someone realizes the local machine has a different OS than the CI runner. With Docker, this class of bug vanishes entirely.
Official image — mcr.microsoft.com/playwright with browsers installed
Microsoft maintains an official Playwright Docker image at mcr.microsoft.com/playwright that comes with all major browsers (Chromium, Firefox, WebKit) already installed, along with all system dependencies. This is your starting point—never build Playwright's dependencies from scratch.
Here's a basic Dockerfile for a Node.js project using Playwright:
FROM mcr.microsoft.com/playwright:v1.40.0-focal
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "test:e2e"]This approach gives you:
- Pre-installed browsers — Chromium, Firefox, and WebKit are ready to use immediately.
- System dependencies — Libraries like libxkbcommon, libpango, and font rendering tools are already there.
- No version mismatch surprises — The image version locks everything together predictably.
The key insight: use the versioned tag (like v1.40.0-focal), not latest. This prevents unexpected browser updates that could break your tests or change visual output silently.
Match versions — Image version must match the Playwright package
Here's where many teams stumble. The Playwright Docker image version and your npm package version must align. If your package.json specifies Playwright 1.40.0 but your Docker image is v1.39.0, browser binaries will mismatch and tests may fail or behave unexpectedly.
Set up a consistent versioning strategy:
{
"devDependencies": {
"@playwright/test": "1.40.0"
}
}
Then in your Dockerfile, use the exact same version:
FROM mcr.microsoft.com/playwright:v1.40.0-focalA real-world example: we had a team where the Docker image was pinned to v1.38.0 but developers locally had 1.40.0 installed via npm install. Local tests passed; CI tests failed intermittently. A 20-minute investigation revealed the version gap. After aligning both to 1.40.0, all failures disappeared.
To manage this systematically, automate the check in your CI:
#!/bin/bash
PACKAGE_VERSION=$(grep '"@playwright/test"' package.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
DOCKER_VERSION=$(grep 'playwright:v' Dockerfile | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if [ "$PACKAGE_VERSION" != "$DOCKER_VERSION" ]; then
echo "Playwright version mismatch: package=$PACKAGE_VERSION, docker=$DOCKER_VERSION"
exit 1
fi
Run your app too — Docker Compose for app, database and tests
Unit tests can run in isolation, but Playwright tests need your actual application running. The cleanest approach is Docker Compose, which orchestrates your app, database, and test container as a coordinated system.
Here's a practical docker-compose.yml:
version: '3.9'
services:
app:
build:
context: .
dockerfile: Dockerfile.app
environment:
DATABASE_URL: postgresql://postgres:password@db:5432/test_db
NODE_ENV: test
ports:
- "3000:3000"
depends_on:
- db
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 5s
timeout: 3s
retries: 10
db:
image: postgres:15
environment:
POSTGRES_DB: test_db
POSTGRES_PASSWORD: password
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
tests:
image: mcr.microsoft.com/playwright:v1.40.0-focal
working_dir: /app
volumes:
- .:/app
environment:
BASE_URL: http://app:3000
command: npm run test:e2e
depends_on:
app:
condition: service_healthy
db:
condition: service_healthy
volumes:
db_data:
Key benefits of this setup:
- Service dependencies — The
depends_onwith healthchecks ensures the app is truly ready before tests run. - Isolated database — Each test run gets a fresh database state, eliminating test pollution.
- Network isolation — Services communicate via internal Docker networks, mirroring production topology.
- Local development parity — Developers run
docker-compose upand get the exact same stack as CI.
In your playwright.config.ts, use the Docker network URL:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: false,
},
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
When running in Docker Compose, BASE_URL becomes http://app:3000, and tests communicate with the app container seamlessly.
Same screenshots — Identical fonts and rendering
Visual regression tests are powerful but fragile if rendering isn't identical everywhere. The official Playwright image includes common fonts and font rendering libraries, but you may need additional fonts for your application.
If your app uses custom fonts or relies on system fonts, extend the base image:
FROM mcr.microsoft.com/playwright:v1.40.0-focal
RUN apt-get update && apt-get install -y \
fonts-dejavu \
fonts-liberation \
fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "test:e2e"]
A real scenario: we were testing a SaaS dashboard with charts and data tables. Screenshots matched locally but failed in CI because the CI image lacked the Noto Sans font family. Adding fonts-noto-cjk to the Docker image resolved all failures instantly.
Beyond fonts, ensure consistent rendering with these Playwright config settings:
export default defineConfig({
use: {
// Disable locale-specific rendering quirks
locale: 'en-US',
// Use a fixed timezone to avoid date-related differences
timezoneId: 'America/New_York',
// Disable GPU acceleration to ensure deterministic rendering
launchArgs: ['--disable-gpu'],
},
snapshotConfig: {
// Allow pixel-level variance for cross-platform tolerance
maxDiffPixels: 100,
},
});
Screenshots taken in Docker will now match across all machines running the same image version.
Common mistakes
- Using
latesttags — Browsers update unexpectedly, breaking your tests silently. Always pin to a specific version. - Ignoring healthchecks in Docker Compose — Tests may start before the app is truly ready, causing flaky failures. Use healthchecks to guarantee readiness.
- Running tests without the app in Docker — Testing a local app while running tests in Docker creates network connectivity issues and mismatched environments. Keep everything in containers.
- Forgetting to install fonts — Visual regression tests fail mysteriously because the Docker image lacks your application's custom fonts. Always list font dependencies explicitly.
- Not versioning the Playwright image with package.json — This creates a version gap where local and CI tests use different browsers, causing intermittent failures that are hard to debug.
- Mounting the entire host filesystem without .dockerignore — This can bloat the Docker context and cause slow builds. Use a proper .dockerignore to exclude node_modules and test artifacts.
Key takeaways
- Use the official
mcr.microsoft.com/playwrightimage as your foundation—it includes all browsers and system dependencies, saving build time and complexity. - Pin the Playwright image version to match your npm package version exactly, and automate this check in CI to catch mismatches early.
- Orchestrate your app, database, and tests with Docker Compose, using healthchecks to ensure services are ready before tests run.
- Install required fonts in the Docker image and configure Playwright to use fixed locale and timezone settings for pixel-perfect visual regression tests.
- Test the entire stack in Docker, not just the test runner—this eliminates environment-specific failures and makes CI behavior predictable from day one.






Comments
Loading comments…