Большинство команд тестируют примерно так: разработчик что-то поправил, 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
