Браузерные бинарники 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 для тестировщиков
Ментальная модель проста. Образ (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:localMakefile или в скрипты 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:
- testingprofiles: [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 как показано в воркфлоу выше.
# Медленно: любое изменение файла инвалидирует кеш 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-прогон собирает образ с нуля.
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 для чувствительных значений.