Пропустить --with-deps при установке Playwright в GitHub Actions: самая частая причина, по которой тесты проходят локально и падают в CI. Системные библиотеки вроде libgtk и libnss отсутствуют на чистых Ubuntu-раннерах. Вторая по частоте ошибка: не добавить if: always() к шагу загрузки артефактов. В результате теряешь HTML-отчёт именно тогда, когда тесты упали и он нужен больше всего. В этом гайде: минимальный рабочий воркфлоу, секреты, кэширование браузеров, фильтры веток, матричные кросс-браузерные запуски, шардирование на параллельных джобах и комментарии к PR с результатами тестов.

Почему GitHub Actions

GitHub Actions стал доминирующим CI-инструментом для новых проектов. По данным JetBrains State of CI/CD 2025, его используют 62% разработчиков для личных проектов и 41% в организациях, оба показателя выше чем у любого другого инструмента.

Практические причины просты: бесплатно для публичных репозиториев, 2 000 минут в месяц для приватных, и всё это встроено прямо в GitHub. Не нужно поднимать отдельный сервер, подключать сторонние аккаунты или настраивать вебхуки. Пушишь YAML-файл и тесты запускаются.

Для Playwright GitHub Actions подходит особенно хорошо: Ubuntu-раннеры имеют всё необходимое. Зависимости браузеров, шрифты и системные библиотеки устанавливаются через --with-deps. Команда Playwright тестирует именно на этих раннерах: ты в поддерживаемом окружении.

Минимальный рабочий воркфлоу

Создаёшь файл .github/workflows/playwright.yml в корне проекта. Именно здесь GitHub ищет определения воркфлоу.

mkdir -p .github/workflows

Полный минимальный воркфлоу:

# .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

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

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        run: npx playwright test

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

Коммитишь этот файл, пушишь, открываешь вкладку Actions в репозитории на GitHub. Воркфлоу появится и запустится в течение нескольких секунд.

Несколько деталей стоит понять. npm ci вместо npm install устанавливает ровно то что записано в package-lock.json, не изменяя его. Это правильная команда для CI: даёт воспроизводимые сборки. npx playwright install --with-deps устанавливает браузеры вместе с системными зависимостями (libgtk, libnss и прочими) которых нет на чистом Ubuntu-раннере. Именно из-за пропущенного --with-deps Playwright чаще всего падает в CI при успешном прохождении локально.

if: always() на шаге загрузки артефактов критичен. Без него артефакт загружается только при успешном воркфлоу, и ты теряешь отчёт именно тогда, когда тесты упали. timeout-minutes: 60 на джобе: подстраховка. Зависший тест может израсходовать весь месячный лимит минут прежде чем ты заметишь.

Сохранение и скачивание отчётов

Шаг upload-artifact сохраняет директорию playwright-report/ как скачиваемый zip. После завершения прогона: страница запуска воркфлоу → Summary → Artifacts → playwright-report. Скачиваешь, разархивируешь, открываешь index.html в браузере.

HTML-отчёт показывает какие тесты прошли, какие упали, скриншоты при падениях, трассировки и видео если запись включена. Так исследуешь падения без прямого доступа к CI-раннеру.

Если генерируешь несколько отчётов (по одному на браузер в матричном запуске), давай каждому артефакту уникальное имя:

      - name: Upload test report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ matrix.browser }}
          path: playwright-report/
          retention-days: 14

retention-days: 14 хранит отчёты две недели: достаточно для большинства сценариев отладки без накопления лишних расходов на хранение. GitHub допускает до 90 дней.

Переменные окружения и секреты

Никогда не помещай учётные данные или URL окружений прямо в YAML-файл воркфлоу. GitHub Secrets: правильное место для всего чувствительного.

Репозиторий → Settings → Secrets and variables → Actions → New repository secret. Добавляешь секреты которые нужны тестам: TEST_USER, TEST_PASSWORD, API-ключи.

Ссылаешься на них в воркфлоу через env: на шаге с тестами:

      - name: Run Playwright tests
        run: npx playwright test
        env:
          BASE_URL: https://staging.example.com
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

В тестах читаешь через process.env:

const baseURL = process.env.BASE_URL ?? 'http://localhost:3000';
const user = process.env.TEST_USER ?? '';
const password = process.env.TEST_PASSWORD ?? '';

GitHub скрывает значения секретов в логах. Если секрет попадает в вывод, он заменяется на *. Нечувствительные значения вроде BASE_URL можно хранить как переменные репозитория (не секреты): они видны в UI. Переменные для URL и флагов фич, секреты для учётных данных.

Секреты недоступны в воркфлоу запускаемых пул реквестами из форков. Это мера безопасности: иначе форк-PR мог бы читать твои секреты. Если нужно тестировать форк-PR с секретами, изучи событие pull_request_target, но сначала разберись с последствиями для безопасности.

Кэширование для ускорения запусков

Каждый новый запуск воркфлоу устанавливает Node-модули и браузеры Playwright с нуля. На холодном раннере это 2–3 минуты до запуска первого теста. Кэширование существенно сокращает это время.

Экшен setup-node с cache: 'npm' уже обрабатывает кэш node_modules автоматически. Для браузеров Playwright добавляешь отдельный шаг кэширования:

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

      - name: Install Playwright browsers
        run: npx playwright install --with-deps
        if: steps.playwright-cache.outputs.cache-hit != 'true'

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

На больших сьютах это ощутимо. Сьют из 200 тестов с включённым кэшированием может пройти за 4 минуты вместо 8 только за счёт пропуска повторных загрузок браузеров.

Запуск только на определённых ветках

Секция on: управляет когда воркфлоу запускается. Минимальный пример уже фильтрует по main и develop. Можно конкретизировать:

on:
  push:
    branches:
      - main
      - develop
      - 'release/**'
  pull_request:
    branches:
      - main
      - develop
  workflow_dispatch:

workflow_dispatch: добавляет кнопку «Run workflow» во вкладке Actions, которая позволяет запускать воркфлоу вручную для конкретной ветки без пуша коммита. Удобно для запуска тестов на конкретной ветке по запросу.

Можно также запускать тесты по расписанию. Ночные smoke-запуски ловят проблемы которые проявляются только в определённые временные окна или после накопления данных:

on:
  schedule:
    - cron: '0 3 * * *'   # 3:00 UTC каждую ночь
  push:
    branches: [main]
  pull_request:
    branches: [main]

Внутри джобы можно добавлять условия по ветке на конкретные шаги через if::

      - name: Run full suite
        run: npx playwright test
        if: github.ref == 'refs/heads/main'

      - name: Run smoke tests only
        run: npx playwright test --grep @smoke
        if: github.event_name == 'pull_request'

Этот паттерн даёт PR-ам быструю обратную связь от smoke-сьюта, а ветка main получает полный прогон.

Матричная стратегия для кросс-браузерного тестирования

Playwright поддерживает Chromium, Firefox и WebKit. Запускать все три на каждом PR дорого по времени и минутам, но кросс-браузерное покрытие нужно. Матричная стратегия запускает параллельные джобы с разными конфигурациями:

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

    strategy:
      fail-fast: false
      matrix:
        browser: [chromium, firefox, webkit]

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

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ matrix.browser }}-${{ hashFiles('package-lock.json') }}

      - name: Install Playwright browsers
        run: npx playwright install ${{ matrix.browser }} --with-deps
        if: steps.playwright-cache.outputs.cache-hit != 'true'

      - name: Run tests
        run: npx playwright test --project=${{ matrix.browser }}
        env:
          BASE_URL: ${{ vars.BASE_URL }}
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

      - name: Upload report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ matrix.browser }}
          path: playwright-report/
          retention-days: 14

fail-fast: false означает: если Firefox упал, джобы Chromium и WebKit продолжают выполняться. При fail-fast: true (дефолт) падение любой матричной джобы отменяет остальные, обычно не то что нужно при отладке кросс-браузерных проблем. npx playwright install ${{ matrix.browser }} --with-deps устанавливает только браузер для этой джобы, а не все три. Быстрее чем устанавливать всё в каждой джобе.
Запускай матрицу только на пушах в main, а не на каждом PR. Добавь if: github.ref == 'refs/heads/main' к джобе или используй фильтр ветки в триггере on.push. PR-ы остаются быстрыми; ветка main получает полное покрытие.

Шардирование: разбивка тестов по джобам

Шардирование разбивает тест-сьют на несколько параллельных джоб. Матричная стратегия запускает разные конфигурации, шардирование запускает те же тесты быстрее, делением их между джобами:

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

    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]

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

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install chromium --with-deps

      - name: Run shard
        run: npx playwright test --shard=${{ matrix.shard }}/4
        env:
          BASE_URL: ${{ vars.BASE_URL }}
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

      - name: Upload shard report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-shard-${{ matrix.shard }}
          path: playwright-report/
          retention-days: 14

Playwright распределяет тесты равномерно между шардами на основе данных о предыдущих запусках (если предоставлен blob-отчёт) или по порядку файлов. С четырьмя шардами сьют на 20 минут выполняется примерно за 5 минут.

Можно комбинировать шардирование с матричной стратегией. Для больших сьютов распространён паттерн «четыре шарда на браузер»:

    strategy:
      matrix:
        browser: [chromium, firefox]
        shard: [1, 2, 3, 4]

Это создаёт 8 параллельных джоб. Важно понимать: каждая джоба расходует минуты раннера. 8 джоб по 5 минут стоят 40 минут квоты, столько же сколько последовательный запуск. Выигрыш в реальном времени ожидания, а не в квоте.

Объединение отчётов шардов в один HTML-отчёт требует blob-репортера. Устанавливаешь reporter: 'blob' в конфиге Playwright для CI-запусков, затем добавляешь джобу слияния:

  merge-reports:
    if: always()
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - name: Download blob reports
        uses: actions/download-artifact@v4
        with:
          path: all-blob-reports
          pattern: blob-report-*
          merge-multiple: true
      - name: Merge into HTML report
        run: npx playwright merge-reports --reporter html ./all-blob-reports
      - name: Upload merged report
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14

Публикация результатов в комментариях к PR

Если результаты тестов появляются прямо в пул реквесте, падения становятся намного заметнее. Для этого нужен отдельный экшен после завершения прогона.

Самый простой подход использует dawidd6/action-junit-report, который читает JUnit XML вывод и публикует комментарий. Сначала добавляешь JUnit-репортер в конфиг Playwright:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['html'],
    ['junit', { outputFile: 'results.xml' }],
  ],
});

Затем добавляешь шаг отчётности в воркфлоу:

      - 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: Post test results to PR
        uses: dawidd6/action-junit-report@v4
        if: always()
        with:
          report_paths: results.xml
          github_token: ${{ secrets.GITHUB_TOKEN }}
          check_name: Playwright Test Results

GITHUB_TOKEN автоматически доступен в каждом воркфлоу. Ручная настройка секретов не нужна.

Комментарий показывает количество прошедших/упавших тестов, их имена и сообщения об ошибках прямо в ленте PR. Ревьюеры не должны идти во вкладку Actions чтобы узнать результат.

Для более визуального сводного отчёта экшены вроде ctrf-io/github-actions-ctrf-summary генерируют таблицу в сводке джобы GitHub Actions. Требует CTRF-формата отчёта (поддерживается через пакет playwright-ctrf-json-reporter): несколько дополнительных строк, но результат чистый.

FAQ

Тесты проходят локально, падают в GitHub Actions. С чего начать?

Проверяй по порядку: переменные окружения (секрет не заданный в Actions будет undefined, не ошибкой), BASE_URL (если захардкожен на localhost, в CI не сработает), тайминги. CI-раннеры медленнее машин разработчиков. Увеличь таймауты в playwright.config.ts или добавь retries: 1 специально для CI через process.env.CI ? 1 : 0.

Как посмотреть HTML-отчёт после падения?

Страница упавшего воркфлоу → Summary → прокручиваешь до Artifacts → скачиваешь playwright-report. Разархивируешь, открываешь index.html в браузере. Отчёт включает скриншоты и трассировки для упавших тестов.

Нужно ли коммитить браузеры Playwright в репозиторий?

Нет. Воркфлоу устанавливает их во время выполнения через npx playwright install. С настроенным кэшированием последующие запуски пропускают загрузку. Коммит бинарников браузеров раздует репозиторий на сотни мегабайт.

Воркфлоу медленный. Что даёт наибольший эффект?

Кэширование: сохранение браузеров Playwright экономит 1–2 минуты каждого запуска. Шардирование: разбивка 15-минутного сьюта на 3 шарда сокращает реальное время до 5 минут. Только Chromium на PR-ах, полная матрица на main: утраивает скорость каждого PR-запуска.

Можно ли использовать Docker-образ Playwright вместо установки браузеров?

Да. Укажи container: mcr.microsoft.com/playwright:v1.52.0-jammy для джобы и пропусти шаг установки. Компромисс: Docker-контейнеры запускаются медленнее голых Ubuntu-раннеров, и ты привязан к конкретной версии Playwright из тега образа. Разумный выбор для команд которые хотят полностью избавиться от шага установки.

Как запускать только тесты связанные с изменёнными файлами?

Playwright не поддерживает это нативно. Некоторые команды используют фильтры по пути в воркфлоу, запуская тестовую джобу только когда меняются файлы в определённых директориях. Добавь paths: в on.pull_request для ограничения триггеров воркфлоу. Полный выбор тестов по изменениям кода требует внешнего инструментария и редко оправдывает сложность пока сьют не превышает 10 минут.

→ See also: CI/CD для QA: сравнение GitHub Actions, Jenkins и GitLab | Отладка нестабильных тестов: практическое руководство | Параллельное выполнение в Playwright: workers, шарды и шардирование для ускорения | Отчёты тестов Playwright: встроенный HTML против Allure | GitLab CI + Playwright: полное руководство по настройке