Браузерные бинарники Playwright зависят от примерно двух десятков системных библиотек, и несоответствие между версиями этих библиотек на ноутбуке и в CI вызывает падения которые не имеют никакого отношения к тестам. Официальный образ Playwright по адресу mcr.microsoft.com/playwright упаковывает все три браузера и их зависимости в единый образ который запускается идентично где угодно, при условии что тег образа совпадает с версией @playwright/test в package.json. Здесь разобрано написание Dockerfile, монтирование отчётов о тестах на хост, Docker Compose для тестирования против локального стека приложения, и интеграция контейнера в GitHub Actions.

Почему Docker важен для QA

Тест-сьюты для браузеров необычно чувствительны к окружению. Playwright устанавливает бинарники Chromium, Firefox и WebKit которые зависят от конкретных системных библиотек: libglib, libnss, libatk и ещё около двух десятков других. На ноутбуке эти библиотеки скорее всего правильной версии, потому что Playwright устанавливался недавно. На машине коллеги из 2022 года их может не быть. На CI-раннере настроенном полгода назад с другим образом Ubuntu несоответствие превращается в падение сборки в 2 часа ночи.

Традиционное решение: вики-страница «Настройка окружения разработки» которая устаревает в момент написания. Решение Docker: файл Dockerfile в репозитории описывающий точное окружение которое нужно тестам. Любой кто запустит docker build получит то же окружение, независимо от ОС и того что было установлено на прошлой неделе.

Для QA конкретно Docker даёт три преимущества. Первое: паритет окружений. Тесты запускаются в одном контейнере локально и в CI, поэтому прошедший локально тест что-то значит. Второе: никакой настройки. Новый член команды запускает весь тест-сьют двумя командами без установки Node, Playwright или зависимостей браузеров. Третье: изоляция. Прогоны тестов не мешают друг другу и тому что установлено на хост-машине.

Docker не заменяет понимание падений тестов. Он устраняет категорию падений вызванных различиями окружений. Оставшиеся падения: реальные баги. Именно этого и хочется.

Основы Docker для тестировщиков

Ментальная модель проста. Образ (image): снимок файловой системы: операционная система, установленные пакеты, код, переменные окружения, всё зафиксированное в определённый момент времени. Контейнер (container): запущенный экземпляр образа. Образ как класс, контейнер как объект.

Образ строится из Dockerfile, текстового файла перечисляющего шаги для его создания. Контейнер запускается из образа командой docker run. Когда контейнер завершается, он останавливается. Всё что он делал не изменяет образ.

# Скачать существующий образ из реестра
docker pull node:20-slim

# Запустить контейнер из образа и выполнить команду
docker run node:20-slim node --version

# Собрать образ из Dockerfile в текущей директории
docker build -t my-playwright-tests .

# Запустить контейнер из только что собранного образа
docker run my-playwright-tests

Ключевая концепция для тестовых воркфлоу: контейнеры эфемерны по умолчанию. Файлы созданные внутри контейнера исчезают при его остановке, включая отчёты о тестах. Решается монтированием томов, о чём пойдёт речь в разделе о запуске тестов.

Образы состоят из слоёв. Каждая инструкция в Dockerfile создаёт новый слой, и Docker кеширует слои которые не изменились. Именно это делает пересборку быстрой: если изменить тестовый код но не зависимости, Docker переиспользует кешированный слой с установленными зависимостями и пересобирает только изменившиеся слои.

Официальный Docker-образ Playwright

Команда Playwright публикует и поддерживает официальный образ по адресу mcr.microsoft.com/playwright. Образ включает всё что нужно для браузерных тестов: Node.js, сам пакет Playwright и все три браузерных бинарника (Chromium, Firefox, WebKit) с предустановленными системными зависимостями.

# Скачать образ совпадающий с версией Playwright
docker pull mcr.microsoft.com/playwright:v1.52.0-jammy

# Посмотреть что внутри
docker run --rm mcr.microsoft.com/playwright:v1.52.0-jammy node --version
docker run --rm mcr.microsoft.com/playwright:v1.52.0-jammy npx playwright --version

Формат тега: v{версия-playwright}-{кодовое-имя-ubuntu}. jammy означает Ubuntu 22.04, наиболее стабильный и широко используемый вариант. Есть также вариант noble для Ubuntu 24.04.

Образ большой (около 1.8GB) потому что содержит три полные браузерные установки. Это цена за предустановленное всё и отсутствие необходимости запускать playwright install --with-deps при старте контейнера. Для CI-окружений где образ скачивается многократно это хорошее соотношение: кеширование слоёв Docker означает что образ скачивается только один раз на раннер пока тег не изменится.

Критическое правило: тег образа должен совпадать с версией @playwright/test в package.json. Если используешь v1.52.0-jammy но в проекте "@playwright/test": "1.50.0", несоответствия API вызовут непонятные падения. Фиксируй оба.

Написание Dockerfile для проекта на Playwright

Используй официальный образ Playwright как базовый. Полный production-готовый Dockerfile:

# Официальный образ Playwright; версия должна совпадать с package.json
FROM mcr.microsoft.com/playwright:v1.52.0-jammy

# Рабочая директория внутри контейнера
WORKDIR /app

# Сначала копируем файлы пакетов для лучшего кеширования слоёв
# Этот слой пересобирается только при изменении зависимостей
COPY package.json package-lock.json ./

# Устанавливаем зависимости проекта (браузеры уже в базовом образе)
RUN npm ci

# Копируем остальную часть проекта
COPY . .

# Команда по умолчанию: запуск всех тестов
CMD ["npx", "playwright", "test"]

Порядок инструкций COPY и RUN важен для кеширования. Копируя сначала package.json и package-lock.json и запуская npm ci до копирования остального кода, обеспечиваешь кеширование и переиспользование слоя установки зависимостей при изменении тестовых файлов. Если скопировать всё сначала а потом запустить npm ci, любое изменение любого файла инвалидирует кеш зависимостей.

Шага npx playwright install нет. Браузеры уже в базовом образе. Это делает сборку значительно быстрее по сравнению с вариантом начинающим с чистого образа Node.

Добавь файл .dockerignore рядом с Dockerfile чтобы исключить ненужные файлы:

# .dockerignore
node_modules
playwright-report
test-results
.git
.github
*.md

Исключение node_modules особенно важно. Без него Docker скопирует локальные node_modules в образ до запуска npm ci, что тратит время и может принести платформо-зависимые бинарники которые не работают внутри Linux-контейнера.

Собрать образ:

docker build -t playwright-tests:local .

Флаг -t помечает образ именем. Тег playwright-tests:local помогает отличать локально собранные образы от образов реестра.

Запуск тестов в контейнере

После сборки образа запуск тестов занимает одну команду:

docker run --rm playwright-tests:local

--rm автоматически удаляет контейнер при завершении. Без него остановленные контейнеры накапливаются и занимают место на диске.

Проблема: отчёты о тестах пишутся внутри контейнера и исчезают при его остановке. Решение: монтирование тома через -v:

docker run --rm \
  -v $(pwd)/playwright-report:/app/playwright-report \
  -v $(pwd)/test-results:/app/test-results \
  playwright-tests:local

Флаг -v отображает директорию хоста на директорию контейнера. Когда Playwright пишет HTML-отчёт в /app/playwright-report внутри контейнера, он фактически пишет в $(pwd)/playwright-report на хост-машине. После остановки контейнера отчёт там и готов к открытию.

На Windows замени $(pwd) на %cd% в Command Prompt или ${PWD} в PowerShell:

docker run --rm `
  -v ${PWD}/playwright-report:/app/playwright-report `
  -v ${PWD}/test-results:/app/test-results `
  playwright-tests:local

Для запуска подмножества тестов переопределяй CMD добавив аргументы:

# Запустить только тесты с тегом
docker run --rm playwright-tests:local npx playwright test --grep @smoke

# Запустить только Chromium
docker run --rm playwright-tests:local npx playwright test --project=chromium

# Запустить конкретный тестовый файл
docker run --rm playwright-tests:local npx playwright test tests/login.spec.ts

Передавать переменные окружения через -e:

docker run --rm \
  -e BASE_URL=https://staging.example.com \
  -e TEST_USER=testuser \
  -e TEST_PASSWORD=secret \
  -v $(pwd)/playwright-report:/app/playwright-report \
  playwright-tests:local

Добавь команду в Makefile или в скрипты package.json чтобы команда с флагами не пишется каждый раз вручную. npm run test:docker проще документировать и сложнее опечататься чем полная команда.

docker-compose для локальных прогонов

Когда тесты запускаются против приложения, нужно чтобы и приложение и контейнер с тестами работали одновременно с сетевым доступом между ними. docker-compose управляет многоконтейнерными конфигурациями через один файл.

docker-compose.yml для типичной конфигурации где Playwright-тесты запускаются против локально работающего веб-приложения:

# docker-compose.yml
version: '3.8'

services:
  # Тестируемое приложение
  app:
    image: your-app:latest
    ports:
      - '3000:3000'
    environment:
      NODE_ENV: test
      DATABASE_URL: postgres://user:pass@db:5432/testdb
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
      interval: 5s
      timeout: 5s
      retries: 10

  # База данных (если приложение её требует)
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: testdb
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U user -d testdb']
      interval: 3s
      timeout: 3s
      retries: 15

  # Контейнер с Playwright-тестами
  tests:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      BASE_URL: http://app:3000
    depends_on:
      app:
        condition: service_healthy
    volumes:
      - ./playwright-report:/app/playwright-report
      - ./test-results:/app/test-results
    # Не запускать тесты автоматически, только по требованию
    profiles:
      - testing

profiles: [testing] у сервиса тестов означает что он не стартует при простом docker-compose up (который запустит приложение и базу). Запуск тестов:

# Запустить приложение и базу данных
docker-compose up -d app db

# Запустить тесты
docker-compose run --rm tests

# Или запустить всё и прогнать тесты одной командой
docker-compose --profile testing run --rm tests

# Остановить всё
docker-compose down

Сервисы общаются используя имена сервисов как хостнеймы. Контейнер тестов устанавливает BASE_URL: http://app:3000, и внутренний DNS Docker разрешает app в IP-адрес контейнера приложения. playwright.config.ts читает process.env.BASE_URL, поэтому локально обращается к localhost:3000 а в контейнере к http://app:3000.

depends_on с condition: service_healthy важен. Без него Playwright стартует до того как приложение готово принимать соединения, и каждый тест упадёт с ошибкой соединения. Healthcheck опрашивает пока приложение не ответит, затем Docker запускает зависимый контейнер.

Интеграция Docker в GitHub Actions

GitHub Actions может использовать Docker-контейнер как окружение выполнения для всего задания, или запускать docker build и docker run как шаги. Для Playwright использование контейнера непосредственно как окружения задания: самый чистый вариант.

# .github/workflows/playwright.yml
name: Playwright Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 60

    # Используем образ Playwright как контейнер задания
    container:
      image: mcr.microsoft.com/playwright:v1.52.0-jammy
      options: --user 1001

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run Playwright tests
        run: npx playwright test
        env:
          BASE_URL: ${{ vars.BASE_URL }}
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

      - name: Upload test report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14

Блок container: на уровне задания говорит GitHub Actions запускать каждый шаг внутри указанного Docker-контейнера вместо Ubuntu-раннера напрямую. Браузеры уже установлены в образе, поэтому шаг npx playwright install полностью пропускается.

--user 1001 устанавливает пользователя контейнера совпадающего с UID раннера GitHub Actions. Без этого могут возникать ошибки прав при попытке Actions записать артефакты.

Для команд которые хотят собирать и пушить собственный образ тестов в реестр:

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build test image
        run: docker build -t playwright-tests:${{ github.sha }} .

      - name: Run tests
        run: |
          docker run --rm \
            -e BASE_URL=${{ vars.BASE_URL }} \
            -e TEST_USER=${{ secrets.TEST_USER }} \
            -e TEST_PASSWORD=${{ secrets.TEST_PASSWORD }} \
            -v ${{ github.workspace }}/playwright-report:/app/playwright-report \
            playwright-tests:${{ github.sha }}

      - name: Upload test report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14

Использование ${{ github.sha }} как тега образа привязывает образ к конкретному коммиту, что упрощает сопоставление прогонов тестов с версиями кода при пуше в реестр.

Частые проблемы и их решения

Ошибки прав на файлы отчётов. Когда Playwright пишет в примонтированную директорию, файлы принадлежат пользователю под которым запускается контейнер (часто root). Пользователь хоста не может их изменить или удалить без sudo. Решение: задать пользователя контейнера при запуске:

docker run --rm \
  --user $(id -u):$(id -g) \
  -v $(pwd)/playwright-report:/app/playwright-report \
  playwright-tests:local

Контейнер запускается от имени текущего пользователя хоста, файлы отчётов принадлежат тебе. В CI используй --user 1001 как показано в воркфлоу выше.

Headless-режим и ошибки дисплея. Playwright по умолчанию запускается в headless-режиме, что работает внутри контейнеров без изменений. Для запуска в headed-режиме внутри контейнера понадобится виртуальный дисплей. Не делай так. Используй headless в контейнерах и headed локально. Если тест ведёт себя по-разному в headed и headless: это законный баг для расследования, а не проблема Docker. Медленная сборка из-за отсутствия кеша слоёв. Если пересборка образа занимает 3+ минуты каждый раз, кеш не работает. Самая частая причина: копирование всего проекта до установки зависимостей:

# Медленно: любое изменение файла инвалидирует кеш npm ci
COPY . .
RUN npm ci

# Быстро: только изменения файлов пакетов инвалидируют кеш npm ci
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

В CI кеширование слоёв Docker требует явной настройки. Добавь в воркфлоу:

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build test image
        uses: docker/build-push-action@v6
        with:
          context: .
          load: true
          tags: playwright-tests:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Строки cache-from: type=gha и cache-to: type=gha,mode=max используют хранилище кеша GitHub Actions для кеширования слоёв Docker. Без этого каждый CI-прогон собирает образ с нуля.

Официальный образ Playwright включает все три браузера, поэтому весит около 1.8GB. На медленном CI-раннере первое скачивание может занять 3-4 минуты. После первого скачивания кеш слоёв Docker делает последующие запуски быстрыми. Если в реестре настроено локальное зеркало: используй его.
Тесты не могут достучаться до приложения. Внутри Docker-контейнера localhost ссылается на сам контейнер, а не на хост-машину. Если приложение работает на хосте по адресу localhost:3000, контейнер не может достичь его по этому адресу. Используй IP Docker-бриджа хост-машины (172.17.0.1 на Linux, host.docker.internal на Mac и Windows):

docker run --rm \
  -e BASE_URL=http://host.docker.internal:3000 \
  -v $(pwd)/playwright-report:/app/playwright-report \
  playwright-tests:local

При использовании docker-compose эта проблема исчезает: все сервисы находятся в одной сети и общаются по именам сервисов.

FAQ

Нужен ли Docker если уже используется GitHub Actions

Необязательно. Раннеры GitHub Actions хорошо работают с Playwright без Docker: установил Node, запустил playwright install --with-deps, и готово. Docker становится ценным когда нужно одно окружение локально и в CI, когда тесты работают против многосервисного стека (приложение + база данных), или при онбординге новых членов команды которым нужен путь к запуску тестов без настройки.

Собирать собственный образ или использовать официальный Playwright

Начни с официального образа как основы FROM. Версии браузеров обновляются бесплатно при смене тега, образ поддерживается людьми знающими зависимости Playwright. Строй поверх только когда нужны специфические дополнения: кастомные шрифты, конкретная версия Node, предзагруженные тестовые данные.

Как отлаживать тест который падает только в контейнере

Примонтируй директорию test-results и включи запись видео в конфиге Playwright для упавших тестов. После прогона папка test-results/ на хосте будет содержать видео и трассировки. Открой трассировку командой npx playwright show-trace test-results/.../trace.zip чтобы увидеть точно что произошло.

Можно ли запускать тесты параллельно внутри контейнера

Да. Настройка workers в Playwright управляет параллелизмом и работает одинаково внутри контейнера. Практический предел: количество CPU контейнера. Для крупных сьютов запускай несколько контейнеров параллельно (через шардирование в GitHub Actions matrix) вместо максимизации воркеров внутри одного контейнера. Это проще понимать и масштабировать.

Контейнер собирается нормально но тесты падают с "executable doesn't exist"

Скорее всего несоответствие версий между тегом образа и @playwright/test. Проверь что mcr.microsoft.com/playwright:v1.52.0-jammy совпадает с "@playwright/test": "1.52.0" в package.json. При несовпадении версий Playwright ищет браузеры по пути которого нет в образе.

Как передать файл .env в контейнер

Используй --env-file с docker run:

docker run --rm \
  --env-file .env.test \
  -v $(pwd)/playwright-report:/app/playwright-report \
  playwright-tests:local

Никогда не коммить .env-файлы в репозиторий. Добавь .env* в .gitignore и используй секреты CI для чувствительных значений.

→ See also: Docker для QA инженеров: запуск тестов в контейнерах | GitHub Actions для тестов Playwright: полная настройка (2026) | Параллельное выполнение в Playwright: workers, шарды и шардирование для ускорения