Непреривне навантажувальне тестування в CI/CD пайплайні

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Непреривне навантажувальне тестування в CI/CD пайплайні
Середній
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1358
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1250
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    956
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    947

Неперервне навантажувальне тестування в CI/CD

При кожному деплої в продакшен ризик внести регресію продуктивності — високий. Один неоптимальний SQL-запит або забута N+1 проблема можуть збільшити час відповіді на 50%, і користувач піде до конкурентів. Ми інтегруємо автоматичні навантажувальні тести прямо у ваш CI/CD пайплайн, щоб ловити такі регресії до потрапляння в master. Результат — ви впевнені, що кожен коміт не ламає SLA по latency і throughput.

Налаштуємо smoke-тести з пороговими значеннями, які перетворюють пайплайн на gatekeeper: якщо p95 latency перевищив 500ms або відсоток помилок вище 1% — пайплайн падає, і розробник отримує сповіщення. Це не заміна повноцінному навантажувальному тестуванню, а швидкий запобіжник.

Проблеми, які вирішуємо

  • Регресія продуктивності після деплою — навіть мікрозміна в коді може сповільнити критичний endpoint. Без автоматичних тестів ви дізнаєтеся про проблему тільки після скарг користувачів або алертів моніторингу. Ми налаштовуємо baseline-порівняння: кожен тест запускається проти стабільної гілки, і при відхиленні >20% пайплайн блокується.
  • Неефективні запити та вузькі місця — наші скрипти емулюють типові користувацькі сценарії (перегляд списку, створення поста, пошук). Якщо запит почав виконуватися довше — ми бачимо це на графіках метрик.
  • Відсутність культури performance-first — розробники часто не думають про продуктивність на етапі code review. Continuous Load Testing робить performance видимим: кожен PR супроводжується коментарем з результатами тестів (p95, error rate).

Інструменти та їх місце в CI

k6 — найкращий вибір для CI: JS-скрипти, вбудована статистика, threshold-based pass/fail, нативна інтеграція з GitHub Actions і GitLab CI. Детальніше в k6 documentation.

Artillery — YAML-конфігурація, зручний для опису сценаріїв без коду.

Gatling — Scala/Java, детальні HTML-звіти, зручний для Java-команд.

Базовий k6 скрипт

// tests/performance/api-smoke.js
import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate, Trend } from 'k6/metrics'

// Кастомні метрики
const errorRate = new Rate('errors')
const postCreateDuration = new Trend('post_create_duration')

export const options = {
  // Профіль навантаження для CI: швидко, не руйнівно
  stages: [
    { duration: '30s', target: 10 },  // розігрів
    { duration: '1m',  target: 10 },  // стійке навантаження
    { duration: '10s', target: 0 },   // охолодження
  ],

  // Пайплайн зламається якщо поріг не досягнуто
  thresholds: {
    http_req_duration: [
      'p(95)<500',   // p95 < 500мс
      'p(99)<1000',  // p99 < 1000мс
    ],
    errors: ['rate<0.01'],            // помилок < 1%
    http_req_failed: ['rate<0.01'],   // HTTP помилок < 1%
    post_create_duration: ['p(95)<800'],
  }
}

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'
const AUTH_TOKEN = __ENV.AUTH_TOKEN

export function setup() {
  // Один раз: отримати токен або підготувати дані
  const res = http.post(`${BASE_URL}/api/auth/login`, JSON.stringify({
    email: '[email protected]',
    password: 'testpassword'
  }), { headers: { 'Content-Type': 'application/json' } })

  return { token: res.json('token') }
}

export default function(data) {
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${data.token || AUTH_TOKEN}`
  }

  // Сценарій 1: список постів (70% трафіку)
  const postsList = http.get(`${BASE_URL}/api/posts?limit=20`, { headers })
  check(postsList, {
    'posts list: status 200': (r) => r.status === 200,
    'posts list: has items': (r) => r.json('data').length > 0
  })
  errorRate.add(postsList.status !== 200)

  sleep(Math.random() * 0.5)  // випадкова пауза 0-500мс

  // Сценарій 2: створення поста (20% трафіку)
  if (Math.random() < 0.2) {
    const start = Date.now()
    const createPost = http.post(`${BASE_URL}/api/posts`, JSON.stringify({
      title: `Test post ${Date.now()}`,
      content: 'Load test content'
    }), { headers })

    postCreateDuration.add(Date.now() - start)
    check(createPost, {
      'create post: status 201': (r) => r.status === 201,
    })
    errorRate.add(createPost.status !== 201)
  }

  sleep(0.3)
}

Як налаштувати пороги продуктивності в k6?

Пороги (thresholds) — ключовий механізм для автоматичного fail пайплайну. В опціях скрипта ми задаємо допустимі межі: наприклад, http_req_duration: ['p(95)<500', 'p(99)<1000']. Це означає, що 95% запитів мають виконуватися швидше 500 мс, а 99% — швидше 1 секунди. Якщо поріг порушено — тест завершується помилкою, і CI зупиняє деплой.

Ми також використовуємо кастомні метрики для конкретних бізнес-операцій (наприклад, час створення поста) і виставляємо окремі пороги на них. Так ви точно знаєте, що критична функціональність не деградує.

GitHub Actions інтеграція

# .github/workflows/performance.yml
name: Performance Tests

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

jobs:
  performance:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: testdb
          POSTGRES_PASSWORD: testpass
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s

    steps:
      - uses: actions/checkout@v4

      - name: Start application
        run: |
          docker compose -f docker-compose.test.yml up -d api
          npx wait-on http://localhost:3000/health --timeout 60000

      - name: Run k6 smoke test
        uses: grafana/[email protected]
        with:
          filename: tests/performance/api-smoke.js
          flags: --out json=results.json
        env:
          BASE_URL: http://localhost:3000
          K6_PROMETHEUS_RW_SERVER_URL: ${{ secrets.PROMETHEUS_URL }}

      - name: Parse results
        if: always()
        run: |
          # Показати summary в PR коментарі
          jq -r '.metrics | {
            p95: .http_req_duration["p(95)"],
            p99: .http_req_duration["p(99)"],
            errors: .http_req_failed.rate
          }' results.json

      - name: Comment PR with results
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs')
            const results = JSON.parse(fs.readFileSync('results.json'))
            const p95 = results.metrics.http_req_duration['p(95)'].toFixed(0)
            const errorRate = (results.metrics.http_req_failed.rate * 100).toFixed(2)

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## Performance Test Results\n\n| Metric | Value | Threshold |\n|--------|-------|-----------|\n| p95 latency | ${p95}ms | <500ms |\n| Error rate | ${errorRate}% | <1% |`
            })

Baseline порівняння між деплоями

#!/bin/bash
# scripts/compare-performance.sh

CURRENT_BRANCH=$(git branch --show-current)
BASELINE_BRANCH="main"

# Тест поточного коду
k6 run --out json=current.json tests/performance/api-smoke.js

# Переключитися на baseline
git stash
git checkout $BASELINE_BRANCH
docker compose up -d --build api
sleep 10
k6 run --out json=baseline.json tests/performance/api-smoke.js

# Порівняння
node - <<'EOF'
const current = require('./current.json')
const baseline = require('./baseline.json')

const metrics = ['http_req_duration']
for (const m of metrics) {
  const cp95 = current.metrics[m]['p(95)']
  const bp95 = baseline.metrics[m]['p(95)']
  const delta = ((cp95 - bp95) / bp95 * 100).toFixed(1)

  if (cp95 > bp95 * 1.2) {  // регресія > 20%
    console.error(`REGRESSION: ${m} p95 degraded by ${delta}%`)
    process.exit(1)
  }
  console.log(`${m} p95: ${cp95}ms vs ${bp95}ms baseline (${delta}%)`)
}
EOF

# Повернутися на поточну гілку
git checkout $CURRENT_BRANCH
git stash pop

Artillery для опису сценаріїв

# tests/performance/user-journey.yml
config:
  target: "{{ $processEnvironment.BASE_URL }}"
  phases:
    - duration: 60
      arrivalRate: 5
      rampTo: 20
      name: "Ramp up"
    - duration: 120
      arrivalRate: 20
      name: "Sustained load"

  ensure:
    thresholds:
      - http.response_time.p95: 500
      - http.request_rate: 15

scenarios:
  - name: "Browse and purchase"
    weight: 70
    flow:
      - get:
          url: "/api/products"
          expect:
            - statusCode: 200
      - post:
          url: "/api/cart"
          json:
            productId: "{{ $randomInt(1, 100) }}"
            quantity: 1

  - name: "Search only"
    weight: 30
    flow:
      - get:
          url: "/api/search?q={{ $randomString(5) }}"

Порівняння інструментів навантажувального тестування

Характеристика k6 Artillery Gatling
Мова скриптів JavaScript YAML Scala/Java
Нативна CI-інтеграція Так (GitHub Actions, GitLab CI) Так (через NPM) Так (Maven/Gradle)
Вбудовані threshold Так Так (через ensure) Так
Генерація звітів JSON, Prometheus, HTML JSON, HTML HTML (детальні)
Продуктивність Висока (Go) Середня (Node.js) Висока (JVM)
Ліцензія Open Source (AGPL) Open Source (MPL) Open Source (ALv2)

Чому варто використовувати k6 для CI?

k6 має вбудовану підтримку CI/CD: він працює як CLI-утиліта, не потребує графічного інтерфейсу, а результати можна виводити в JSON для подальшої обробки. На відміну від Gatling (вимагає Scala/Java та генерації HTML-звітів) або Artillery (YAML-конфіги, але менше метрик), k6 надає гнучкі метрики та просту інтеграцію з GitHub Actions через готові action. Для команд, які вже використовують JavaScript, поріг входу мінімальний.

Процес роботи

  1. Аналітика — визначаємо критичні ендпоінти та користувацькі сценарії.
  2. Проектування — розробляємо навантажувальні сценарії, налаштовуємо профілі навантаження (ramp-up, sustained).
  3. Реалізація — пишемо скрипти (k6, Artillery або Gatling), інтегруємо їх в CI.
  4. Тестування — запускаємо smoke-тести на staging, коригуємо thresholds.
  5. Деплой — включаємо тести в пайплайн, налаштовуємо автоматичні коментарі в PR.

Строки та що входить

Налаштування базового набору k6 smoke-тестів з порогами та інтеграцією в CI займає від 1 до 2 робочих днів. Якщо потрібне порівняння з baseline та кастомні метрики — розширюємо до 3-5 днів. До складу робіт входить:

  • Скрипти навантажувальних тестів (2-3 сценарії)
  • Конфігурація thresholds
  • Інтеграція з GitHub Actions або GitLab CI (YAML pipeline)
  • PR-коментар з результатами тестів
  • Документація по запуску та підтримці
  • Консультація команди щодо інтерпретації результатів

Ми гарантуємо, що тести не заважатимуть основному процесу розробки: вони запускаються паралельно і займають не більше 5 хвилин.

Чому обирають нас

  • Більше 5 років досвіду в навантажувальному тестуванні та CI/CD
  • 50+ успішних проектів — від стартапів до enterprise
  • Використовуємо тільки перевірені інструменти (k6, Grafana, Prometheus)
  • Надаємо гарантію на коректну роботу тестів протягом місяця після впровадження

Зв'яжіться з нами, щоб обговорити ваш проект: отримайте консультацію з інтеграції Continuous Load Testing у ваш пайплайн.

Чому юніт-тести важливі, але не панацея?

Баг, знайдений юніт-тестом, коштує хвилини виправлення. Той самий баг у продакшені — години інциденту, компенсації та втрата довіри. На проекті інтернет-магазину помилка в розрахунку знижки пройшла ручне тестування, потрапила в прод і за 4 години обробила 37 замовлень за нульовою ціною. Автотест на граничні випадки розрахунку зловив би її при першому ж push. Оцініть свій проект — ми проведемо аудит поточного покриття і дамо рекомендації.

Jest — стандарт для JavaScript/TypeScript, але юніт-тести виправдані тільки там, де є ізольована логіка: функції трансформації, валідатори, бізнес-правила, утиліти. Тестувати React-компоненти через Jest + Testing Library правильно для поведінкових тестів: «кнопка з'являється після завантаження», «форма показує помилку при порожньому email». Снепшот-тести (toMatchSnapshot) — пастка: вони ламаються при будь-якій зміні верстки і стають шумом, який розробники оновлюють не дивлячись. Покриття коду (code coverage) — погана метрика якості: 80% coverage можна отримати тестами, які нічого не перевіряють. Coverage показує, що код виконався, а не те, що він працює правильно.

Критерій Jest Vitest
Швидкість для великих проектів Середня (Babel-трансформація) В 10–20 разів швидше (ES modules)
Інтеграція з Vite Через плагін Нативна
Монорепозиторії Вимагає конфігурації З коробки

Vitest як альтернатива Jest для Vite-проектів: в 10–20 разів швидше завдяки нативним ES modules без трансформації через Babel. Для монорепозиторіїв з тисячами тестів різниця у швидкості відчутна. Детальніше про юніт-тестування.

Як налаштувати E2E тести, які не будуть flaky?

Playwright обійшов Cypress за ключовими параметрами: нативна підтримка multi-tab, multi-origin, iframe; паралельне виконання на рівні тестів; WebKit, Firefox, Chromium з коробки; немає iframe для додатку — тести працюють в реальному браузері.

Playwright codegen записує дії та генерує тест — хороша точка старту, але згенерований код потрібно рефакторити. Локатори за text content крихкі: getByRole('button', { name: 'Оформить заказ' }) — стійкіше, ніж locator('.btn-primary').

Page Object Model — стандарт організації E2E тестів. Кожна сторінка — окремий клас з методами замість прямих локаторів. Коли кнопка переїхала з хедера в сайдбар — міняємо в одному місці, не шукаємо по всіх тестах.

Як уникнути flaky тестів? Типова проблема — flaky tests. Причини: race condition між запитом і рендером, анімації без очікування, залежність від зовнішніх API. Рішення: `page.waitForResponse()` замість `page.waitForTimeout()`, мокування зовнішніх API через `page.route()`.
// Погано
await page.click('#submit');
await page.waitForTimeout(2000);
await expect(page.locator('.success')).toBeVisible();

// Добре
await page.click('#submit');
await page.waitForResponse(resp =>
  resp.url().includes('/api/orders') && resp.status() === 201
);
await expect(page.getByRole('alert', { name: /заказ создан/i })).toBeVisible();

Наші інженери гарантують стабільність тестів у CI. Документація Playwright — основний інструмент на проектах з мільйонами користувачів.

Навантажувальне тестування з k6

k6 — інструмент для навантажувального тестування з JavaScript API. Сценарії пишуться як код, версіонуються в git, запускаються в CI. Три основних сценарії:

  • Spike test — різке зростання навантаження: 0 → 1000 користувачів за 30 секунд. Імітує запуск рекламної кампанії. Показує здатність системи реагувати на піки.
  • Soak test — стабільне навантаження на 2–4 години. Виявляє memory leaks, connection pool exhaustion, деградацію продуктивності.
  • Stress test — навантаження вище розрахункової (150–200% від очікуваного піку). Показує точку відмови та graceful degradation.

Порогові значення:

thresholds: {
  http_req_duration: ['p95<500', 'p99<1000'],
  http_req_failed: ['rate<0.01'],
}

p95 < 500ms означає: 95% запитів відповідають швидше півсекунди. Якщо поріг не виконується — k6 завершується з кодом помилки, CI-пайплайн падає.

На одному проекті інтернет-магазину ми виявили деградацію API на 4-й годині тесту: p95 зріс з 200ms до 2s через витік з'єднань. Після оптимізації клієнт заощадив значну суму на інцидентах та зайвих ресурсах. Отримайте аналогічний аудит вашого проекту — замовте навантажувальне тестування.

Як Core Web Vitals впливають на ранжування?

Google використовує Core Web Vitals у ранжуванні. Lighthouse CLI в CI-пайплайні: при кожному деплої перевіряємо, що LCP < 2.5s, CLS < 0.1, INP < 200ms. Детальніше про веб-продуктивність. Реальні проблеми, які Lighthouse знаходить:

  • Hero image без атрибутів width/height: CLS 0.35 при завантаженні.
  • JavaScript-бандл 2.1MB синхронно блокує парсинг: INP 450ms.
  • Шрифти без font-display: swap: невидимий текст до завантаження шрифту (FOIT).
  • Неоптимізований hero image 4MB: LCP 8.2s.

Lighthouse CI (lhci) зберігає історію метрик і надсилає коментар до PR з деградацією. За даними Google, 53% користувачів залишають сайт при завантаженні довше 3 секунд — наші тести запобігають таким втратам.

Піраміда тестування в проекті

Рівень Інструмент Кількість Швидкість
Юніт Vitest/Jest Багато (тисячі) <5 хв
Інтеграція Vitest + supertest Середня 5–15 хв
E2E Playwright Мало (happy path) 10–30 хв
Навантаження k6 За розкладом 30–60 хв
Продуктивність Lighthouse CI При кожному деплої 5 хв

Що входить в роботу?

  • Аудит поточного покриття та визначення критичних user flows.
  • Написання unit-тестів для ключової бізнес-логіки, інтеграційних тестів для API, E2E для сценаріїв користувача.
  • Налаштування паралельного виконання в CI (sharded workers для Playwright).
  • Навантажувальне тестування зі звітом та рекомендаціями.
  • Документація за тест-кейсами, навчання вашої команди роботі з тестами.
  • Гарантійна підтримка 1 місяць після впровадження.

Процес роботи

  1. Аналітика — аудит поточного тестування, виявлення слабких місць, визначення пріоритетів.
  2. Проектування — вибір інструментів, написання тест-плану, узгодження.
  3. Реалізація — написання тестів, інтеграція в CI.
  4. Тестування — прогін всіх рівнів, аналіз результатів, виправлення помилок.
  5. Деплой — запуск в прод, моніторинг метрик, навчання команди.

Терміни

Налаштування повного тест-пайплайна (Jest + Playwright + k6 + Lighthouse CI) з нуля: 2–4 тижні. Покриття E2E-тестами існуючого проекту (20–30 сценаріїв): 3–6 тижнів. Навантажувальне тестування зі звітом та рекомендаціями: 1–2 тижні. Вартість розраховується індивідуально після аудиту.

Готові обговорити ваш проект? Залиште заявку — ми проведемо аудит поточного тестування безкоштовно і запропонуємо план з економією до 60% часу на інциденти. Отримайте консультацію з тестування веб-додатків — напишіть нам.