Sparround

Running tests in Docker

Docker solves one problem for automated tests: "it works on my machine". Browser version, system libraries, fonts, locale, timezone — all pinned in the image, so the local machine, every team member and CI run in the same environment.

The official image: mcr.microsoft.com/playwright:v1.49.0-noble — it ships with the browsers (Chromium, Firefox, WebKit) and all system dependencies. That removes the npx playwright install --with-deps step and the 2-3 minutes it costs.

The most important rule: the image tag must match the @playwright/test version in package.json. A mismatch gives you either an "Executable doesn't exist" error or, worse, tests behaving unexpectedly. This is the number one trap in the topic and must be remembered on every dependency bump.

When I reach for Docker:

  • In CI — for environment stability and a fast start
  • To generate visual-test baselines — identical font rendering to CI matters
  • While investigating "passes locally, fails in CI" — reproducing CI conditions locally
  • To bring up the whole stack (app + DB + tests) — with docker compose

A full environment with docker compose — tests, the application and the database come up together. This is especially valuable because it gives every PR a clean, isolated environment: the shared-stage problem of "someone changed the data" disappears.

There are several classic traps here, and these are exactly what interviews ask about:

  • `localhost` doesn't work. A container has its own network namespace; http://localhost:3000 points at the container itself. The correct address is the service name: http://app:3000.
  • `/dev/shm` exhaustion. Docker gives 64MB of shared memory by default; Chromium burns through it and crashes with cryptic errors like Target closed or Browser crashed. Fix: --shm-size=1gb (or --ipc=host). This is a very common but rarely recognised cause of CI flakiness.
  • The service isn't ready. A container starting ≠ the app responding. depends_on alone isn't enough — you need a healthcheck + condition: service_healthy, otherwise tests start while the app is still booting.
  • File ownership (on Linux). If the container runs as root, the artifacts it creates (playwright-report/, test-results/) end up root-owned and undeletable on the host. Fix: -u $(id -u):$(id -g).
  • Volume mounts and `node_modules`. Mounting the host's node_modules into the container breaks on platform-specific binaries — either run npm ci inside the image or exclude node_modules with an anonymous volume.
  • Layer caching. The order COPY package*.jsonRUN npm ciCOPY . . matters; otherwise every code change reinstalls all dependencies.
SymptomCauseFix
"Executable doesn't exist"The image tag doesn't match the @playwright/test versionAlign the tag with the package.json version
Chromium crashes randomly, "Target closed"/dev/shm fills at the 64MB default--shm-size=1gb or --ipc=host
ECONNREFUSED localhost:3000The container is looking at its own namespaceUse the service name in BASE_URL: http://app:3000
The first tests fail, later ones passThe app hasn't finished bootinghealthcheck + condition: service_healthy
Artifacts can't be deleted on the hostThe container ran as root-u $(id -u):$(id -g)
npm ci reruns on every buildCOPY . . comes BEFORE the dependency installCopy package*.json first, then npm ci, then the rest

A strong concrete detail for interviews: `--shm-size=1gb`. The sentence "Chromium kept crashing randomly in Docker; the cause was the default 64MB /dev/shm" is one of the most convincing signals of real experience — only someone who hit it knows it. Likewise the service name instead of localhost, and matching the image version, separate "read about it" from "built it".

📚 Sources and documentation