Статья 5: CI/CD для QA Engineers: От Manual Testing к Continuous Testing
CI/CD для QA Engineers: От Manual Testing к Continuous Testing
Полное руководство по Jenkins, GitHub Actions, Docker и интеграции test automation в CI/CD pipeline

📍 Вы здесь:
[✓] Статья 1: Основы QA
[✓] Статья 2: Практика QA
[✓] Статья 3: DSA для QA
[✓] Статья 4: Automation Frameworks
[→] Статья 5: CI/CD ← Сейчас читаете
[ ] Статья 6: Performance Testing
[ ] Статья 7: Работа в Apple
Прогресс: 71% ✨
“У нас есть 500 автоматизированных тестов, но мы запускаем их вручную раз в неделю” — услышав это на собеседовании, я понял, что компания застряла в 2015 году.
В 2026 году continuous testing — это не опция, а необходимость. Apple, Google, Amazon выкатывают изменения десятки раз в день. Как они это делают? CI/CD pipelines с интегрированным тестированием.
Из реальной вакансии Apple:
“Experience with CI/CD pipelines and test automation integration”
В этой статье вы научитесь:
- ✅ Настраивать Jenkins для test automation
- ✅ Создавать GitHub Actions workflows
- ✅ Контейнеризировать тесты с Docker
- ✅ Интегрировать Playwright/Selenium в pipeline
- ✅ Настраивать notifications (Slack, Teams)
- ✅ Создавать test dashboards
К концу статьи у вас будет production-ready CI/CD pipeline для вашего портфолио.
📋 Содержание
- Что такое CI/CD и почему это важно для QA
- Git & GitHub для QA
- GitHub Actions: Автоматизация с нуля
- Jenkins: Enterprise-level CI/CD
- Docker для Test Automation
- Интеграция тестов в Pipeline
- Test Reporting & Dashboards
- Notifications & Monitoring
- Реальный проект: Complete CI/CD Setup
- Ресурсы для изучения
🎯 Что такое CI/CD и почему это важно для QA?
Определения
CI (Continuous Integration):
- Разработчики часто коммитят код (несколько раз в день)
- Каждый коммит автоматически собирается
- Автоматические тесты запускаются при каждом коммите
- Быстрая обратная связь (< 10 минут)
CD (Continuous Delivery/Deployment):
- Автоматическая доставка в staging/production
- После прохождения всех тестов
- Минимальное ручное вмешательство
Традиционный процесс (без CI/CD)
День 1: Разработчик пишет код
День 2-3: Код лежит в ветке
День 4: Создается Pull Request
День 5: Code review
День 6: Merge в main
День 7: QA вручную запускает тесты
День 8: Находятся баги
День 9: Исправления
День 10: Релиз
Результат: 10 дней, много ручной работы, поздние находки багов
С CI/CD
Минута 0: Разработчик пушит код
Минута 1: CI автоматически:
- Собирает проект
- Запускает unit tests
- Запускает integration tests
- Запускает E2E tests
Минута 10: Результаты готовы
✅ Все тесты прошли → автоматический deploy на staging
❌ Тесты упали → уведомление в Slack
Результат: 10 минут, автоматизация, немедленная обратная связь
Роль QA в CI/CD
Традиционная роль QA:
- ❌ Ждет готовые билды
- ❌ Запускает тесты вручную
- ❌ Ищет баги после разработки
Modern QA Engineer (SDET):
- ✅ Создает автоматизированные тесты
- ✅ Интегрирует тесты в CI/CD
- ✅ Мониторит test stability
- ✅ Shift-left testing (тестирование на ранних стадиях)
🌳 Git & GitHub для QA
Базовые команды Git
Setup:
# Настройка пользователя
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
# Инициализация репозитория
git init
# Клонирование существующего
git clone https://github.com/username/repo.git
Ежедневная работа:
# Проверка статуса
git status
# Добавление файлов
git add . # Все файлы
git add tests/login.spec.js # Конкретный файл
# Коммит
git commit -m "Add login tests"
# Отправка на GitHub
git push origin main
# Получение изменений
git pull origin main
Работа с ветками:
# Создание новой ветки
git checkout -b feature/add-payment-tests
# Переключение между ветками
git checkout main
git checkout feature/add-payment-tests
# Список веток
git branch
# Удаление ветки
git branch -d feature/add-payment-tests
# Слияние ветки
git checkout main
git merge feature/add-payment-tests
Git Workflow для QA
Feature Branch Workflow:
# 1. Создаем ветку для новых тестов
git checkout -b feature/checkout-tests
# 2. Пишем тесты
# tests/checkout.spec.js
# 3. Коммитим часто
git add tests/checkout.spec.js
git commit -m "Add checkout validation tests"
# 4. Пушим в GitHub
git push origin feature/checkout-tests
# 5. Создаем Pull Request на GitHub
# 6. После review - merge в main
Commit Message Best Practices:
# ❌ Плохо
git commit -m "updates"
git commit -m "fix"
git commit -m "test"
# ✅ Хорошо
git commit -m "Add login tests for valid/invalid credentials"
git commit -m "Fix flaky test in checkout flow"
git commit -m "Update test data for payment methods"
git commit -m "Refactor Page Objects to use fixtures"
Conventional Commits для QA:
# Формат: <type>: <description>
git commit -m "test: add E2E tests for user registration"
git commit -m "fix: resolve timeout issue in API tests"
git commit -m "refactor: improve Page Object structure"
git commit -m "docs: update README with test execution guide"
git commit -m "chore: update Playwright to v1.40"
.gitignore для Test Projects
.gitignore:
# Node modules
node_modules/
package-lock.json
# Test results
test-results/
playwright-report/
screenshots/
videos/
logs/
allure-results/
allure-report/
# Environment variables
.env
.env.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Test data
test-data/local/
⚡ GitHub Actions: Автоматизация с нуля
Почему GitHub Actions?
Преимущества:
- ✅ Бесплатно для публичных репозиториев
- ✅ 2000 минут/месяц для приватных (free tier)
- ✅ Интеграция с GitHub из коробки
- ✅ Огромный marketplace workflows
- ✅ Простой YAML синтаксис
Базовая структура
.github/workflows/tests.yml:
name: Automated Tests
# Когда запускать
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
# Запуск каждый день в 9:00 UTC
- cron: '0 9 * * *'
workflow_dispatch: # Ручной запуск
# Что запускать
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Playwright Tests в GitHub Actions
.github/workflows/playwright.yml:
name: Playwright Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- 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 results
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
- name: Upload screenshots
uses: actions/upload-artifact@v3
if: failure()
with:
name: screenshots
path: screenshots/
Parallel Testing Matrix
Запуск тестов на разных браузерах параллельно:
name: Cross-Browser Testing
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v3
- name: Setup Node ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps ${{ matrix.browser }}
- name: Run tests on ${{ matrix.browser }}
run: npx playwright test --project=${{ matrix.browser }}
Sharding для быстрого выполнения
Разделение тестов на части:
name: Sharded Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run shard ${{ matrix.shard }} of 4
run: npx playwright test --shard=${{ matrix.shard }}/4
Environment Secrets
Работа с секретами:
name: Tests with Secrets
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
env:
API_KEY: ${{ secrets.API_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
BASE_URL: ${{ secrets.BASE_URL }}
run: npm test
Добавление секретов в GitHub:
- Repo → Settings → Secrets and variables → Actions
- New repository secret
- Name:
API_KEY, Value:your-secret-key
Deploy после успешных тестов
Complete pipeline:
name: Test and Deploy
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install and test
run: |
npm ci
npm test
# Только если тесты прошли
deploy:
needs: test
runs-on: ubuntu-latest
if: success()
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
run: |
echo "Deploying to staging..."
# Your deploy script
- name: Run smoke tests on staging
run: npm run test:smoke
Notifications
Slack notification:
name: Tests with Notifications
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm test
- name: Slack Notification on Success
if: success()
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: qa-notifications
SLACK_COLOR: good
SLACK_MESSAGE: '✅ All tests passed!'
SLACK_TITLE: Test Results
- name: Slack Notification on Failure
if: failure()
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_CHANNEL: qa-notifications
SLACK_COLOR: danger
SLACK_MESSAGE: '❌ Tests failed! Check logs.'
SLACK_TITLE: Test Results
🔧 Jenkins: Enterprise-level CI/CD
Почему Jenkins?
Когда использовать Jenkins:
- ✅ Enterprise environments
- ✅ On-premise infrastructure
- ✅ Сложные pipelines с множеством стадий
- ✅ Интеграция с legacy системами
- ✅ Большие команды
Когда использовать GitHub Actions:
- ✅ Small to medium teams
- ✅ Cloud-native approach
- ✅ GitHub-centric workflow
- ✅ Простые pipelines
Jenkins Installation
Docker способ (самый простой):
# Скачиваем и запускаем Jenkins
docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts
# Получаем initial admin password
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# Открываем http://localhost:8080
После установки:
- Вставляем admin password
- Install suggested plugins
- Create admin user
- Start using Jenkins!
Jenkinsfile для Playwright
Declarative Pipeline:
pipeline {
agent any
environment {
BASE_URL = 'https://staging.example.com'
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/username/test-automation.git'
}
}
stage('Install Dependencies') {
steps {
sh 'npm ci'
sh 'npx playwright install --with-deps'
}
}
stage('Run Tests') {
steps {
sh 'npx playwright test'
}
}
stage('Publish Results') {
steps {
publishHTML([
reportName: 'Playwright Report',
reportDir: 'playwright-report',
reportFiles: 'index.html',
keepAll: true,
alwaysLinkToLastBuild: true
])
}
}
}
post {
always {
junit 'test-results/*.xml'
archiveArtifacts artifacts: 'screenshots/**/*.png',
allowEmptyArchive: true
}
failure {
emailext(
subject: "❌ Tests Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: """
Build failed!
Job: ${env.JOB_NAME}
Build: ${env.BUILD_NUMBER}
URL: ${env.BUILD_URL}
""",
to: 'qa-team@example.com'
)
}
success {
echo '✅ All tests passed!'
}
}
}
Многоступенчатый Pipeline
Комплексный pipeline с разными типами тестов:
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'npm ci'
}
}
stage('Unit Tests') {
steps {
sh 'npm run test:unit'
}
}
stage('Integration Tests') {
parallel {
stage('API Tests') {
steps {
sh 'npm run test:api'
}
}
stage('Component Tests') {
steps {
sh 'npm run test:component'
}
}
}
}
stage('E2E Tests') {
steps {
sh 'npx playwright test'
}
}
stage('Deploy to Staging') {
when {
branch 'main'
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' }
}
steps {
sh './deploy-staging.sh'
}
}
stage('Smoke Tests on Staging') {
when {
branch 'main'
}
steps {
sh 'npm run test:smoke'
}
}
}
post {
always {
publishHTML([
reportName: 'Test Report',
reportDir: 'playwright-report',
reportFiles: 'index.html'
])
junit 'test-results/**/*.xml'
}
failure {
slackSend(
channel: '#qa-notifications',
color: 'danger',
message: "❌ Build Failed: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
)
}
success {
slackSend(
channel: '#qa-notifications',
color: 'good',
message: "✅ Build Successful: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
)
}
}
}
Jenkins Plugins для QA
Essential Plugins:
# Установка через Jenkins UI:
# Manage Jenkins → Manage Plugins → Available
1. HTML Publisher Plugin # Test reports
2. JUnit Plugin # Test results
3. Slack Notification Plugin # Notifications
4. Blue Ocean # Modern UI
5. Pipeline # Pipeline support
6. Git Plugin # Git integration
7. Docker Plugin # Docker integration
8. Allure Plugin # Beautiful reports
Scheduled Tests (Cron)
Запуск тестов по расписанию:
pipeline {
agent any
triggers {
// Каждый день в 9:00
cron('0 9 * * *')
// Каждый час
// cron('0 * * * *')
// Каждый понедельник в 8:00
// cron('0 8 * * 1')
}
stages {
stage('Nightly Tests') {
steps {
sh 'npx playwright test --project=full-suite'
}
}
}
}
🐳 Docker для Test Automation
Почему Docker для тестов?
Проблемы без Docker:
- ❌ “Works on my machine”
- ❌ Зависимость от environment
- ❌ Сложная настройка CI
- ❌ Несогласованные версии браузеров
С Docker:
- ✅ Consistent environment
- ✅ Easy setup
- ✅ Изолированное выполнение
- ✅ Версионирование окружения
Dockerfile для Playwright
Dockerfile:
FROM mcr.microsoft.com/playwright:v1.40.0-focal
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy test files
COPY . .
# Run tests by default
CMD ["npx", "playwright", "test"]
docker-compose.yml:
version: '3.8'
services:
tests:
build: .
environment:
- BASE_URL=https://staging.example.com
- CI=true
volumes:
- ./test-results:/app/test-results
- ./playwright-report:/app/playwright-report
- ./screenshots:/app/screenshots
Запуск тестов:
# Build image
docker build -t my-tests .
# Run tests
docker run --rm my-tests
# Run with docker-compose
docker-compose up --abort-on-container-exit
# Run specific test
docker run --rm my-tests npx playwright test tests/login.spec.js
Multi-stage Dockerfile (оптимизация)
# Stage 1: Dependencies
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
# Stage 2: Test dependencies
FROM mcr.microsoft.com/playwright:v1.40.0-focal AS test-deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Stage 3: Runtime
FROM mcr.microsoft.com/playwright:v1.40.0-focal
WORKDIR /app
# Copy dependencies
COPY --from=test-deps /app/node_modules ./node_modules
COPY . .
# Run tests
CMD ["npx", "playwright", "test"]
Docker в Jenkins
Jenkinsfile с Docker:
pipeline {
agent {
docker {
image 'mcr.microsoft.com/playwright:v1.40.0-focal'
args '-v $HOME/.npm:/root/.npm'
}
}
stages {
stage('Test') {
steps {
sh 'npm ci'
sh 'npx playwright test'
}
}
}
}
Docker Compose для интеграционных тестов
docker-compose.test.yml:
version: '3.8'
services:
# Ваше приложение
app:
build: ../app
ports:
- "3000:3000"
environment:
- NODE_ENV=test
- DB_HOST=db
depends_on:
- db
# Database
db:
image: postgres:15
environment:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
ports:
- "5432:5432"
# Тесты
tests:
build: .
environment:
- BASE_URL=http://app:3000
- DB_URL=postgresql://testuser:testpass@db:5432/testdb
depends_on:
- app
- db
command: sh -c "sleep 5 && npx playwright test"
Запуск:
docker-compose -f docker-compose.test.yml up --abort-on-container-exit
🔗 Интеграция тестов в Pipeline
Test Pyramid в CI/CD
Pipeline Stages:
┌─────────────────────────────────────────┐
│ 1. Unit Tests (30 сек) │
│ ✓ Fast │
│ ✓ No dependencies │
│ ✓ Run on every commit │
├─────────────────────────────────────────┤
│ 2. Integration Tests (2-5 мин) │
│ ✓ API tests │
│ ✓ Component tests │
│ ✓ Database tests │
├─────────────────────────────────────────┤
│ 3. E2E Tests (10-15 мин) │
│ ✓ Critical user flows │
│ ✓ Smoke tests │
│ ✓ Visual regression │
├─────────────────────────────────────────┤
│ 4. Full Regression (30-60 мин) │
│ ✓ Nightly builds only │
│ ✓ All test suites │
│ ✓ Multiple environments │
└─────────────────────────────────────────┘
Параллелизация тестов
package.json scripts:
{
"scripts": {
"test": "playwright test",
"test:unit": "jest",
"test:api": "newman run postman-collection.json",
"test:e2e": "playwright test tests/e2e",
"test:smoke": "playwright test tests/smoke --grep @smoke",
"test:parallel": "playwright test --workers=4",
"test:headed": "playwright test --headed",
"test:debug": "playwright test --debug"
}
}
GitHub Actions с параллелизацией:
name: Parallel Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
suite: [smoke, auth, checkout, products, admin]
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run ${{ matrix.suite }} tests
run: npx playwright test tests/${{ matrix.suite }}
Conditional Testing
Запуск тестов в зависимости от изменений:
name: Smart Testing
on: [push]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v3
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
frontend:
- 'src/frontend/**'
- 'tests/e2e/**'
backend:
- 'src/backend/**'
- 'tests/api/**'
test-frontend:
needs: detect-changes
if: needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm run test:e2e
test-backend:
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm run test:api
📊 Test Reporting & Dashboards
Playwright HTML Reporter
playwright.config.js:
export default {
reporter: [
['html', {
outputFolder: 'playwright-report',
open: 'never'
}],
['json', {
outputFile: 'test-results.json'
}],
['junit', {
outputFile: 'junit-results.xml'
}],
['list'] // Console output
],
};
Viewing reports в CI:
# GitHub Actions
- name: Upload HTML report
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Allure Reports
Самые красивые отчеты!
Installation:
npm install --save-dev @playwright/test allure-playwright
playwright.config.js:
export default {
reporter: [
['allure-playwright', {
detail: true,
outputFolder: 'allure-results',
suiteTitle: false
}]
],
};
Генерация отчета:
# Установка Allure CLI
npm install -g allure-commandline
# Запуск тестов
npx playwright test
# Генерация отчета
allure generate allure-results -o allure-report --clean
# Открытие отчета
allure open allure-report
В CI:
# GitHub Actions
- name: Run tests
run: npx playwright test
- name: Generate Allure Report
if: always()
run: |
npm install -g allure-commandline
allure generate allure-results -o allure-report --clean
- name: Deploy report to GitHub Pages
if: always()
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./allure-report
Custom Test Dashboard
dashboard.js - Simple Dashboard:
const fs = require('fs');
const path = require('path');
class TestDashboard {
constructor() {
this.results = [];
}
parseTestResults(resultsPath) {
const data = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
const stats = {
total: 0,
passed: 0,
failed: 0,
skipped: 0,
duration: 0,
tests: []
};
data.suites.forEach(suite => {
suite.specs.forEach(spec => {
spec.tests.forEach(test => {
stats.total++;
stats.duration += test.results[0].duration;
if (test.results[0].status === 'passed') {
stats.passed++;
} else if (test.results[0].status === 'failed') {
stats.failed++;
} else {
stats.skipped++;
}
stats.tests.push({
name: spec.title,
suite: suite.title,
status: test.results[0].status,
duration: test.results[0].duration,
error: test.results[0].error?.message
});
});
});
});
return stats;
}
generateHTML(stats) {
const passRate = ((stats.passed / stats.total) * 100).toFixed(1);
return `
<!DOCTYPE html>
<html>
<head>
<title>Test Dashboard</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
padding: 20px;
border-radius: 8px;
text-align: center;
}
.stat-card.total { background: #e3f2fd; }
.stat-card.passed { background: #c8e6c9; }
.stat-card.failed { background: #ffcdd2; }
.stat-card.skipped { background: #fff9c4; }
.stat-value {
font-size: 48px;
font-weight: bold;
margin: 10px 0;
}
.stat-label {
font-size: 14px;
color: #666;
text-transform: uppercase;
}
.progress-bar {
width: 100%;
height: 30px;
background: #e0e0e0;
border-radius: 15px;
overflow: hidden;
margin: 20px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4caf50, #8bc34a);
text-align: center;
line-height: 30px;
color: white;
font-weight: bold;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background: #f5f5f5;
font-weight: bold;
}
.status {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
}
.status.passed { background: #c8e6c9; color: #2e7d32; }
.status.failed { background: #ffcdd2; color: #c62828; }
.status.skipped { background: #fff9c4; color: #f57f17; }
</style>
</head>
<body>
<div class="container">
<h1>Test Execution Dashboard</h1>
<p>Generated: ${new Date().toLocaleString()}</p>
<div class="stats">
<div class="stat-card total">
<div class="stat-label">Total Tests</div>
<div class="stat-value">${stats.total}</div>
</div>
<div class="stat-card passed">
<div class="stat-label">Passed</div>
<div class="stat-value">${stats.passed}</div>
</div>
<div class="stat-card failed">
<div class="stat-label">Failed</div>
<div class="stat-value">${stats.failed}</div>
</div>
<div class="stat-card skipped">
<div class="stat-label">Skipped</div>
<div class="stat-value">${stats.skipped}</div>
</div>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${passRate}%">
${passRate}% Pass Rate
</div>
</div>
<h2>Test Results</h2>
<table>
<thead>
<tr>
<th>Suite</th>
<th>Test</th>
<th>Status</th>
<th>Duration</th>
<th>Error</th>
</tr>
</thead>
<tbody>
${stats.tests.map(test => `
<tr>
<td>${test.suite}</td>
<td>${test.name}</td>
<td><span class="status ${test.status}">${test.status}</span></td>
<td>${(test.duration / 1000).toFixed(2)}s</td>
<td>${test.error || '-'}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</body>
</html>
`;
}
generate(resultsPath, outputPath) {
const stats = this.parseTestResults(resultsPath);
const html = this.generateHTML(stats);
fs.writeFileSync(outputPath, html);
console.log(\`Dashboard generated: \${outputPath}\`);
return stats;
}
}
// Usage
const dashboard = new TestDashboard();
dashboard.generate('test-results.json', 'dashboard.html');
В package.json:
{
"scripts": {
"test:report": "npx playwright test && node dashboard.js"
}
}
🔔 Notifications & Monitoring
Slack Integration
Установка Slack App:
- Создайте Slack App: https://api.slack.com/apps
- Enable “Incoming Webhooks”
- Скопируйте Webhook URL
slack-notifier.js:
const axios = require('axios');
class SlackNotifier {
constructor(webhookUrl) {
this.webhookUrl = webhookUrl;
}
async sendTestResults(stats, buildUrl) {
const passRate = ((stats.passed / stats.total) * 100).toFixed(1);
const color = stats.failed === 0 ? 'good' : 'danger';
const message = {
text: stats.failed === 0
? '✅ All tests passed!'
: `❌ ${stats.failed} test(s) failed`,
attachments: [{
color: color,
title: 'Test Execution Report',
fields: [
{
title: 'Total Tests',
value: stats.total.toString(),
short: true
},
{
title: 'Passed',
value: `✅ ${stats.passed}`,
short: true
},
{
title: 'Failed',
value: `❌ ${stats.failed}`,
short: true
},
{
title: 'Pass Rate',
value: `${passRate}%`,
short: true
},
{
title: 'Duration',
value: `${(stats.duration / 1000 / 60).toFixed(1)} min`,
short: true
}
],
footer: 'Test Automation',
footer_icon: 'https://playwright.dev/img/playwright-logo.svg',
ts: Math.floor(Date.now() / 1000)
}]
};
if (buildUrl) {
message.attachments[0].actions = [{
type: 'button',
text: 'View Details',
url: buildUrl
}];
}
try {
await axios.post(this.webhookUrl, message);
console.log('✅ Slack notification sent');
} catch (error) {
console.error('❌ Failed to send Slack notification:', error.message);
}
}
async sendFailedTests(failedTests) {
if (failedTests.length === 0) return;
const message = {
text: '❌ Failed Tests Details',
attachments: failedTests.map(test => ({
color: 'danger',
title: test.name,
text: test.error || 'No error message',
fields: [
{
title: 'Suite',
value: test.suite,
short: true
},
{
title: 'Duration',
value: `${(test.duration / 1000).toFixed(2)}s`,
short: true
}
]
}))
};
try {
await axios.post(this.webhookUrl, message);
} catch (error) {
console.error('Failed to send details:', error.message);
}
}
}
module.exports = SlackNotifier;
В CI:
# GitHub Actions
- name: Send Slack Notification
if: always()
run: |
node scripts/slack-notifier.js
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
Email Notifications
email-notifier.js:
const nodemailer = require('nodemailer');
class EmailNotifier {
constructor(config) {
this.transporter = nodemailer.createTransport({
host: config.smtp.host,
port: config.smtp.port,
secure: true,
auth: {
user: config.smtp.user,
pass: config.smtp.password
}
});
}
async sendTestResults(stats, recipients) {
const passRate = ((stats.passed / stats.total) * 100).toFixed(1);
const subject = stats.failed === 0
? `✅ All Tests Passed (${passRate}%)`
: `❌ ${stats.failed} Tests Failed`;
const html = `
<h2>Test Execution Report</h2>
<table style="border-collapse: collapse;">
<tr>
<td style="padding: 10px; border: 1px solid #ddd;"><strong>Total Tests:</strong></td>
<td style="padding: 10px; border: 1px solid #ddd;">${stats.total}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #ddd;"><strong>Passed:</strong></td>
<td style="padding: 10px; border: 1px solid #ddd; color: green;">✅ ${stats.passed}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #ddd;"><strong>Failed:</strong></td>
<td style="padding: 10px; border: 1px solid #ddd; color: red;">❌ ${stats.failed}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #ddd;"><strong>Pass Rate:</strong></td>
<td style="padding: 10px; border: 1px solid #ddd;">${passRate}%</td>
</tr>
</table>
${stats.failed > 0 ? `
<h3>Failed Tests:</h3>
<ul>
${stats.tests
.filter(t => t.status === 'failed')
.map(t => `<li>${t.suite} - ${t.name}</li>`)
.join('')}
</ul>
` : ''}
`;
await this.transporter.sendMail({
from: '"Test Automation" <noreply@example.com>',
to: recipients.join(', '),
subject: subject,
html: html
});
}
}
module.exports = EmailNotifier;
Microsoft Teams Integration
teams-notifier.js:
const axios = require('axios');
class TeamsNotifier {
constructor(webhookUrl) {
this.webhookUrl = webhookUrl;
}
async sendTestResults(stats, buildUrl) {
const passRate = ((stats.passed / stats.total) * 100).toFixed(1);
const themeColor = stats.failed === 0 ? '00FF00' : 'FF0000';
const card = {
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": "Test Results",
"themeColor": themeColor,
"title": stats.failed === 0
? "✅ All Tests Passed"
: `❌ ${stats.failed} Tests Failed`,
"sections": [{
"activityTitle": "Test Execution Report",
"facts": [
{ "name": "Total Tests:", "value": stats.total },
{ "name": "Passed:", "value": `✅ ${stats.passed}` },
{ "name": "Failed:", "value": `❌ ${stats.failed}` },
{ "name": "Pass Rate:", "value": `${passRate}%` },
{ "name": "Duration:", "value": `${(stats.duration / 60000).toFixed(1)} min` }
]
}]
};
if (buildUrl) {
card.potentialAction = [{
"@type": "OpenUri",
"name": "View Report",
"targets": [{ "os": "default", "uri": buildUrl }]
}];
}
await axios.post(this.webhookUrl, card);
}
}
module.exports = TeamsNotifier;
🎯 Реальный проект: Complete CI/CD Setup
Project Structure
ecommerce-ci-cd/
├── .github/
│ └── workflows/
│ ├── pr-tests.yml # PR validation
│ ├── nightly-tests.yml # Full regression
│ └── deploy.yml # Deploy pipeline
├── tests/
│ ├── e2e/
│ ├── api/
│ └── smoke/
├── scripts/
│ ├── slack-notifier.js
│ ├── dashboard.js
│ └── cleanup.js
├── Dockerfile
├── docker-compose.yml
├── Jenkinsfile
├── playwright.config.js
└── package.json
Complete GitHub Actions Workflow
.github/workflows/complete-pipeline.yml:
name: Complete CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * *' # Nightly at midnight
workflow_dispatch:
env:
NODE_VERSION: '18'
jobs:
# Job 1: Lint & Static Analysis
lint:
name: Code Quality
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Check code formatting
run: npm run format:check
# Job 2: Unit Tests
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
# Job 3: API Tests
api-tests:
name: API Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Run API tests
run: npm run test:api
env:
API_URL: ${{ secrets.API_URL }}
API_KEY: ${{ secrets.API_KEY }}
# Job 4: E2E Tests (Sharded)
e2e-tests:
name: E2E Tests (Shard ${{ matrix.shard }})
runs-on: ubuntu-latest
needs: [unit-tests, api-tests]
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test --shard=${{ matrix.shard }}/4
env:
BASE_URL: ${{ secrets.STAGING_URL }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report-${{ matrix.shard }}
path: playwright-report/
retention-days: 30
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: screenshots-${{ matrix.shard }}
path: screenshots/
# Job 5: Generate Reports
report:
name: Generate Test Report
runs-on: ubuntu-latest
needs: e2e-tests
if: always()
steps:
- uses: actions/checkout@v3
- name: Download all artifacts
uses: actions/download-artifact@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Generate dashboard
run: node scripts/dashboard.js
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dashboard
# Job 6: Notify
notify:
name: Send Notifications
runs-on: ubuntu-latest
needs: [e2e-tests]
if: always()
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Send Slack notification
run: node scripts/slack-notifier.js
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
# Job 7: Deploy (only on main branch if all tests passed)
deploy:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [e2e-tests]
if: github.ref == 'refs/heads/main' && success()
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
run: |
echo "Deploying to staging..."
# Your deployment script
- name: Run smoke tests
run: npm run test:smoke
env:
BASE_URL: ${{ secrets.STAGING_URL }}
Complete Jenkinsfile
Jenkinsfile:
pipeline {
agent any
environment {
NODE_VERSION = '18'
STAGING_URL = credentials('staging-url')
API_KEY = credentials('api-key')
SLACK_WEBHOOK = credentials('slack-webhook')
}
options {
timeout(time: 1, unit: 'HOURS')
timestamps()
buildDiscarder(logRotator(numToKeepStr: '30'))
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Setup') {
steps {
sh """
node --version
npm --version
npm ci
"""
}
}
stage('Code Quality') {
parallel {
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('Format Check') {
steps {
sh 'npm run format:check'
}
}
}
}
stage('Unit Tests') {
steps {
sh 'npm run test:unit'
}
post {
always {
junit 'test-results/unit/*.xml'
}
}
}
stage('API Tests') {
steps {
sh 'npm run test:api'
}
post {
always {
junit 'test-results/api/*.xml'
}
}
}
stage('E2E Tests') {
steps {
sh '''
npx playwright install --with-deps
npx playwright test
'''
}
post {
always {
publishHTML([
reportName: 'Playwright Report',
reportDir: 'playwright-report',
reportFiles: 'index.html',
keepAll: true
])
junit 'test-results/e2e/*.xml'
archiveArtifacts artifacts: 'screenshots/**/*.png',
allowEmptyArchive: true
}
}
}
stage('Generate Dashboard') {
when {
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' }
}
steps {
sh 'node scripts/dashboard.js'
publishHTML([
reportName: 'Test Dashboard',
reportDir: 'dashboard',
reportFiles: 'index.html',
keepAll: true
])
}
}
stage('Deploy to Staging') {
when {
branch 'main'
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' }
}
steps {
sh './deploy-staging.sh'
}
}
stage('Smoke Tests on Staging') {
when {
branch 'main'
}
steps {
sh 'npm run test:smoke'
}
}
}
post {
always {
sh 'node scripts/slack-notifier.js'
}
failure {
emailext(
subject: "❌ Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
body: """
Build failed!
Job: ${env.JOB_NAME}
Build: ${env.BUILD_NUMBER}
URL: ${env.BUILD_URL}
Check the console output for details.
""",
to: 'qa-team@example.com'
)
}
success {
echo '✅ All stages completed successfully!'
}
}
}
🎓 Ресурсы для изучения
📺 YouTube Каналы
CI/CD:
-
TechWorld with Nana
- YouTube
- ⭐⭐⭐⭐⭐ Лучший канал по DevOps
- Jenkins, Docker, Kubernetes
-
FreeCodeCamp - Jenkins Full Course
- YouTube
- 4+ часа бесплатного контента
-
Automation Step by Step
- YouTube
- Jenkins для QA
Docker:
💻 Онлайн курсы
GitHub Actions:
-
“GitHub Actions - The Complete Guide” - Academind
- 💰 Udemy: $12.99
- 6+ часов
- 💰 Get Course - 85% OFF
-
GitHub Learning Lab (FREE!)
- 🆓 Интерактивные туториалы
- GitHub Skills
Jenkins:
-
“Jenkins From Zero To Hero” - Udemy
- 💰 $13.99
- Complete Jenkins guide
- 💰 Get Course
-
“Learn DevOps: CI/CD with Jenkins Pipelines” - Udemy
- 💰 $12.99
- Hands-on projects
- 💰 Get Course
Docker:
-
“Docker Mastery” - Bret Fisher
- 💰 Udemy: $12.99
- 19+ часов
- Industry standard
- 💰 Get Course
-
“Docker for QA Engineers” - Test Automation University
- 🆓 FREE
- TAU - Docker Course
LinkedIn Learning:
-
“Learning GitHub Actions”
- 1 месяц бесплатно
- 💰 LinkedIn Learning
-
“Continuous Integration and Continuous Delivery (CI/CD)”
- Overview курс
- LinkedIn Learning
📚 Книги
1. “Continuous Delivery” - Jez Humble, David Farley
- Библия CI/CD
- Must-read для всех
- 💰 Amazon - $45
2. “The Phoenix Project” - Gene Kim
- DevOps роман
- Понимание culture
- 💰 Amazon - $18
3. “Docker Deep Dive” - Nigel Poulton
- Comprehensive Docker guide
- 💰 Amazon - $25
🏆 Практические ресурсы
1. Play with Docker
- 🆓 Бесплатная Docker playground
- labs.play-with-docker.com
2. Katacoda/KillerCoda
- 🆓 Интерактивные сценарии
- Jenkins, Docker, Kubernetes
- killercoda.com
3. GitHub Actions Marketplace
- 10,000+ готовых actions
- github.com/marketplace
🎓 Сертификации
1. GitHub Actions Certification
- $99
- Официальная сертификация
- GitHub Certifications
2. Jenkins Engineer Certification
- $150
- CloudBees University
3. Docker Certified Associate
- $195
- Docker Certification
✅ Чек-лист: CI/CD Readiness
Git/GitHub:
- Понимаю Git workflow
- Умею работать с branches
- Могу создать Pull Request
- Знаю как делать code review
- Понимаю Git conflicts resolution
GitHub Actions:
- Написал базовый workflow
- Настроил matrix strategy
- Работал с secrets
- Умею использовать artifacts
- Настроил notifications
Jenkins:
- Установил Jenkins локально
- Написал Jenkinsfile
- Настроил Jenkins pipeline
- Знаком с Jenkins plugins
- Настроил scheduled builds
Docker:
- Написал Dockerfile
- Создал docker-compose
- Запускал тесты в контейнере
- Понимаю Docker networking
- Работал с Docker volumes
Reporting:
- Настроил HTML reports
- Интегрировал Allure
- Создал custom dashboard
- Настроил notifications
Общее:
- Понимаю CI/CD principles
- Могу объяснить test pyramid
- Знаю best practices
- Имею проект с CI/CD в GitHub
📍 Что дальше?
В следующей статье мы разберем Performance Testing:
- JMeter для load testing
- K6 для modern performance testing
- Gatling advanced scenarios
- Performance monitoring
- Analyzing results
Следующая статья: Статья 6: Performance Testing
💡 Финальный совет для Apple Interview
Что ценит Apple в CI/CD:
-
Automation First
- Все должно быть автоматизировано
- Минимум ручных шагов
-
Fast Feedback
- Тесты быстрые (< 10 мин)
- Parallel execution
- Smart test selection
-
Reliability
- Stable tests (no flaky tests)
- Retry mechanisms
- Clear failure reporting
-
Observability
- Good dashboards
- Metrics tracking
- Trend analysis
На интервью покажите:
- ✅ GitHub repo с CI/CD setup
- ✅ Jenkinsfile примеры
- ✅ Docker containerized tests
- ✅ Test reports/dashboards
- ✅ Notifications setup
Будьте готовы объяснить:
- Почему выбрали конкретный подход
- Как решаете flaky tests
- Как оптимизируете время выполнения
- Как мониторите качество
Была ли статья полезна? 👏
Вопросы? Пишите в комментариях!
Автор: AAnnayev — Senior SDET Tags: #CICD #Jenkins #GitHubActions #Docker #DevOps #Apple #QA
P.S. Полный код проекта с CI/CD: github.com/yourname/cicd-test-framework
P.P.S. Только 2 статьи до финала! Статья 6 (Performance) и Статья 7 (Apple Job)! 🚀