QA Interview Hub · Knowledge base · Quizzes

CI/CD

Docker for automated tests

Playwright in the official Docker image

The official mcr.microsoft.com/playwright image contains Node, browsers of the right revision and system libraries. The tag must match the @playwright/test version in package-lock.json: each Playwright version expects its own browser builds, and on a mismatch tests fail with a "browser not found" error.

docker run --rm --init --ipc=host \
  -v "$PWD":/work -w /work \
  mcr.microsoft.com/playwright:v1.64.0-noble \
  sh -c "npm ci && npx playwright test"

(Replace v1.64.0 with your version; noble is the Ubuntu 24.04 base.) Why the flags: --ipc=host gives Chromium proper shared memory (otherwise it crashes on heavy pages), --init reaps zombie processes.

Why this fixes "screenshots differ locally and in CI". Text rendering depends on the OS, fonts and libraries. If the baseline was made on macOS or Windows and the comparison runs on a Linux runner, pixel differences are unavoidable. The image gives an identical environment: baselines are created and updated in that same image (npx playwright test --update-snapshots inside the container), and CI runs the same image.

In GitHub Actions the image is set at job level: container: { image: mcr.microsoft.com/playwright:v1.64.0-noble }. Then npx playwright install is not needed: the browsers are already inside. The tag is bumped together with the @playwright/test upgrade in one PR.

docker compose for e2e: app + db + tests

Compose starts the application, the database and the test runner itself with one command, and then cleans everything up. The idea: an ephemeral environment for every run: an empty DB, a known state, no shared staging data, so parallel PRs do not interfere with each other.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: test
      POSTGRES_DB: app_test
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U postgres -d app_test']
      interval: 2s
      timeout: 3s
      retries: 15

  app:
    build: .
    environment:
      DATABASE_URL: postgres://postgres:test@db:5432/app_test
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ['CMD-SHELL', 'curl -fs http://localhost:3000/health || exit 1']
      interval: 2s
      timeout: 3s
      retries: 30

  e2e:
    image: mcr.microsoft.com/playwright:v1.64.0-noble
    working_dir: /work
    volumes:
      - ./:/work
    ipc: host
    environment:
      BASE_URL: http://app:3000
    depends_on:
      app:
        condition: service_healthy
    command: sh -c "npm ci && npx playwright test"
docker compose up --build --abort-on-container-exit --exit-code-from e2e
docker compose down -v

Key points:

  • Services see each other by service name (db, app), not via localhost;
  • depends_on without condition guarantees only the start order of containers. Readiness is provided by healthcheck + condition: service_healthy;
  • --exit-code-from e2e returns the test exit code to CI; without it the pipeline can be green with failed tests;
  • Secrets and config are passed via environment / env_file (in CI from secrets), not baked into the image;
  • Reports reach the host thanks to a volume (./:/work): the playwright-report/ folder is available to the next CI step for upload-artifact;
  • down -v removes volumes so the next run starts with a clean DB.