Большинство команд тестируют примерно так: разработчик что-то поправил, QA вручную прошёлся по основным сценариям, выглядит нормально — деплоим. Проблема в том, что это не масштабируется. Чем больше проект, тем больше времени уходит на ручную проверку, и тем выше шанс что-то пропустить.

Решение — встроить тестирование прямо в процесс деплоя. Сделать так, чтобы тесты запускались сами, без участия человека, при каждом изменении кода. И чтобы без зелёных тестов деплой вообще не происходил.

Покажу как это настроить на GitHub Actions — от нуля до рабочего пайплайна.


Как это работает в целом

Представьте конвейер. Каждый шаг — это проверка. Если шаг упал — конвейер останавливается, дальше ничего не идёт.

Пуш кода → Линтинг → Unit тесты → Интеграционные тесты → E2E тесты → Деплой

Логика простая: быстрые проверки идут первыми. Если сломан синтаксис — зачем запускать тесты? Если упали unit тесты — зачем поднимать базу данных? Экономим время и ресурсы.


Шаг 1: Линтинг — самая быстрая проверка

Линтер проверяет код на очевидные ошибки и нарушения стиля. Работает за 15-30 секунд, ловит кучу глупых вещей до запуска чего-либо.

Создайте файл .github/workflows/ci.yml:

name: CI

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Установка Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck

cache: 'npm' — это кэширование зависимостей. Без него каждый запуск заново скачивает все пакеты. С ним — использует сохранённый кэш, если package-lock.json не изменился. Экономит 1-3 минуты на каждом запуске.


Шаг 2: Unit тесты

Unit тесты проверяют отдельные функции изолированно — без базы данных, без внешних запросов. Работают быстро, должны укладываться в минуту.

  unit-tests:
    runs-on: ubuntu-latest
    needs: lint        # запустится только если lint прошёл
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Запуск тестов
        run: npm run test:unit -- --coverage

      - name: Сохранить отчёт о покрытии
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

--coverage генерирует отчёт: какой процент кода покрыт тестами, какие строки не покрыты вообще. Артефакт сохраняется и его можно скачать из интерфейса GitHub Actions.


Шаг 3: Интеграционные тесты с базой данных

Вот здесь начинается интересное. Нужна реальная база данных — и GitHub Actions умеет поднимать её прямо внутри пайплайна через services. Не нужно никуда подключаться, не нужно платить за тестовый сервер.

  integration-tests:
    runs-on: ubuntu-latest
    needs: unit-tests

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Применить миграции
        run: npm run db:migrate
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb

      - name: Запуск тестов
        run: npm run test:integration
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb

options с health-cmd — это ожидание пока база реально поднялась и готова принимать подключения. Без этого тесты стартуют раньше чем Postgres готов, и всё падает с непонятными ошибками.


Шаг 4: E2E тесты на Playwright

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

  e2e-tests:
    runs-on: ubuntu-latest
    needs: integration-tests

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Установить браузеры Playwright
        run: npx playwright install --with-deps chromium

      - name: Собрать приложение
        run: npm run build

      - name: Запустить E2E тесты
        run: npx playwright test

      - name: Сохранить отчёт Playwright
        uses: actions/upload-artifact@v4
        if: always()        # сохраняем даже если тесты упали
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

if: always() — важная деталь. По умолчанию артефакты не сохраняются если джоба упала. А нам как раз нужен отчёт когда что-то сломалось — чтобы понять что именно и почему.


Шаг 5: Деплой только после всех проверок

  deploy:
    runs-on: ubuntu-latest
    needs: [unit-tests, integration-tests, e2e-tests]
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4

      - name: Деплой
        run: |
          # ваша команда деплоя
          echo "Deploying..."

needs со списком — деплой запустится только если все три джобы прошли успешно. if: github.ref == 'refs/heads/main' — деплоим только из main, из feature-веток деплоя нет.


Уведомления когда что-то сломалось

Смысл пайплайна теряется если вы не узнаёте о падениях. Добавьте уведомление в Telegram — это проще всего:

      - name: Уведомление о падении
        if: failure()
        uses: appleboy/telegram-action@master
        with:
          to: ${{ secrets.TELEGRAM_CHAT_ID }}
          token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          message: |
            ❌ Пайплайн упал
            Ветка: ${{ github.ref_name }}
            Автор: ${{ github.actor }}
            Подробности: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

Секреты добавляются в Settings → Secrets and variables → Actions в вашем репозитории.


Итоговый чеклист

Пайплайн настроен правильно если:

  • [ ] Линтинг запускается первым на каждый пуш

  • [ ] Unit тесты не запускаются если упал линтинг

  • [ ] Интеграционные тесты поднимают базу через services

  • [ ] E2E отчёт сохраняется даже при падении

  • [ ] Деплой происходит только из main и только после зелёных тестов

  • [ ] Уведомления о падениях приходят в мессенджер


Итого

Пайплайн выше — рабочая основа, можно брать и адаптировать. Главное что вы получаете: тесты запускаются сами, деплой без зелёных тестов невозможен, и вы всегда знаете если что-то сломалось.


Если что-то непонятно или хотите разобрать конкретный кейс — пишите в комментарии.


💼 Занимаюсь QA и настройкой CI/CD на фрилансе. Kwork