Artillery Performance Testing: Modern Load Testing with YAML Scenarios is a critical discipline in modern software quality assurance. According to Google research, as page load time increases from 1 to 3 seconds, the probability of bounce increases 32% (Google/SOASTA Research). According to Akamai, a 100ms delay in page load can decrease conversion rates by 7% (Akamai Performance Study). This guide covers practical approaches that QA teams can apply immediately: from core concepts and tooling to real-world implementation patterns. Whether you are building skills in this area or improving an existing process, you will find actionable techniques backed by industry experience. The goal is not just theoretical understanding but a working framework you can adapt to your team’s context, technology stack, and quality objectives.
TL;DR
- Define SLAs before writing tests — testing without targets produces meaningless data
- Run performance tests against production-like data volumes for reliable results
- Integrate lightweight performance regression tests into CI/CD to catch regressions early
Best for: Teams with defined performance SLAs or traffic growth Skip if: Static sites or internal tools with fewer than 100 concurrent users
Introduction to Artillery
Artillery is a modern, powerful load testing toolkit designed for developers. With YAML-based scenario definitions, built-in support for HTTP, WebSocket, and Socket.io, plus a rich plugin ecosystem, Artillery excels at testing modern, real-time applications.
For API testing fundamentals before load testing, review API Testing Mastery. Teams needing to validate API performance under load should also explore API Performance Testing. For CI/CD integration strategies, see CI/CD Pipeline Optimization for QA Teams and Jenkins Pipeline for Test Automation.
“Performance testing reveals system behavior under stress that no functional test ever will. I always say: run your load tests against production-like data volumes — synthetic data gives you false confidence.” — Yuri Kan, Senior QA Lead
Basic YAML Scenario
# load-test.yml
config:
target: "https://api.example.com"
phases:
- duration: 60
arrivalRate: 10 # 10 users per second
name: "Warm up"
- duration: 120
arrivalRate: 50 # Ramp to 50 users/sec
name: "Sustained load"
- duration: 30
arrivalRate: 100 # Peak load
name: "Spike test"
processor: "./flows.js" # Custom JavaScript logic
scenarios:
- name: "Browse and purchase"
flow:
- get:
url: "/api/products"
capture:
- json: "$[0].id"
as: "productId"
- post:
url: "/api/cart"
json:
productId: "{{ productId }}"
quantity: 1
capture:
- json: "$.cartId"
as: "cartId"
- post:
url: "/api/checkout"
json:
cartId: "{{ cartId }}"
paymentMethod: "credit_card"
expect:
- statusCode: 200
- contentType: json
- hasProperty: orderId
Advanced Features
Custom JavaScript Processor
// flows.js
module.exports = {
setAuthToken,
generateTestData,
validateResponse
};
function setAuthToken(requestParams, context, ee, next) {
// Login to get token
const request = require('request');
request.post({
url: `${context.vars.target}/auth/login`,
json: {
username: 'test@example.com',
password: 'password123'
}
}, (err, res, body) => {
if (!err && res.statusCode === 200) {
context.vars.authToken = body.token;
}
return next();
});
}
function generateTestData(context, events, done) {
// Generate random test data
context.vars.username = `user_${Date.now()}`;
context.vars.email = `${context.vars.username}@example.com`;
return done();
}
function validateResponse(requestParams, response, context, ee, next) {
// Custom validation logic
const body = JSON.parse(response.body);
if (body.status !== 'success') {
ee.emit('error', 'Invalid response status');
}
if (response.timings.phases.total > 1000) {
console.log(`Slow response: ${response.timings.phases.total}ms`);
}
return next();
}
WebSocket Testing
# websocket-test.yml
config:
target: "ws://localhost:8080"
phases:
- duration: 60
arrivalRate: 10
scenarios:
- engine: ws
flow:
# Connect to WebSocket
- send: '{"action": "subscribe", "channel": "prices"}'
# Wait for message
- think: 1
# Send message and expect response
- send: '{"action": "get_price", "symbol": "BTC"}'
- match:
- json: "$.symbol"
value: "BTC"
- json: "$.price"
exists: true
# Loop messages
- loop:
- send: '{"action": "ping"}'
- think: 5
count: 10
Plugin Ecosystem
Metrics Plugins
# artillery.yml with plugins
config:
target: "https://api.example.com"
plugins:
# Publish metrics to CloudWatch
cloudwatch:
region: "us-east-1"
namespace: "LoadTests"
# Publish to Datadog
datadog:
apiKey: "{{ $processEnvironment.DD_API_KEY }}"
tags:
- "env:production"
- "team:backend"
# Publish to Prometheus
publish-metrics:
- type: prometheus
pushgateway: "http://prometheus:9091"
phases:
- duration: 300
arrivalRate: 20
scenarios:
- flow:
- get:
url: "/api/metrics"
Expect Plugin
config:
target: "https://api.example.com"
plugins:
expect: {} # Enable expect plugin
scenarios:
- name: "API validation"
flow:
- get:
url: "/api/users/{{ userId }}"
expect:
- statusCode: 200
- contentType: json
- hasHeader: "x-request-id"
- equals:
- "{{ $. username}}"
- "john_doe"
- matchesRegexp:
- "{{ $.email }}"
- "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
CI/CD Integration
GitHub Actions
# .github/workflows/performance.yml
name: Performance Tests
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
workflow_dispatch:
jobs:
artillery-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Artillery
run: npm install -g artillery@latest
- name: Run Performance Test
run: |
artillery run \
--output report.json \
load-test.yml
- name: Generate HTML Report
run: artillery report report.json --output report.html
- name: Check SLO Compliance
run: |
artillery report report.json \
--assert \
"p95 < 500" \
"p99 < 1000" \
"errors.rate < 0.01"
- name: Upload Reports
if: always()
uses: actions/upload-artifact@v3
with:
name: performance-reports
path: |
report.json
report.html
GitLab CI/CD
# .gitlab-ci.yml
performance-test:
stage: test
image: node:18
script:
- npm install -g artillery
- artillery run --output results.json load-test.yml
- artillery report results.json
artifacts:
reports:
performance: results.json
paths:
- results.json
only:
- schedules
- web
Best Practices
1. Realistic Load Profiles
config:
phases:
# Gradual ramp-up
- duration: 120
arrivalRate: 1
rampTo: 50
name: "Ramp up"
# Sustained load
- duration: 600
arrivalRate: 50
name: "Sustained"
# Spike test
- duration: 60
arrivalRate: 200
name: "Spike"
# Cool down
- duration: 60
arrivalRate: 10
name: "Cool down"
2. Environment-Specific Configs
# Base config
config:
target: "{{ $processEnvironment.TARGET_URL }}"
phases:
- duration: "{{ $processEnvironment.DURATION }}"
arrivalRate: "{{ $processEnvironment.ARRIVAL_RATE }}"
# Run with environment variables
# TARGET_URL=https://api.example.com DURATION=300 ARRIVAL_RATE=50 artillery run test.yml
3. Custom Metrics
// custom-metrics.js
module.exports = { trackBusinessMetrics };
function trackBusinessMetrics(userContext, events, done) {
events.on('response', (data) => {
const body = JSON.parse(data.body);
// Track business-specific metrics
if (body.orderValue) {
events.emit('counter', 'orders.total', 1);
events.emit('histogram', 'orders.value', body.orderValue);
}
if (body.discountApplied) {
events.emit('counter', 'discounts.applied', 1);
}
});
return done();
}
Artillery vs Other Tools
| Feature | Artillery | JMeter | Locust | k6 |
|---|---|---|---|---|
| Config Format | YAML | GUI/XML | Python | JavaScript |
| Learning Curve | Low | Medium-High | Low (Python) | Low (JS) |
| WebSocket | Excellent | Limited | Manual | Good |
| Socket.io | Native | No | No | Limited |
| Plugins | Excellent | Extensive | Limited | Growing |
| CI/CD (as discussed in K6: Modern Load Testing with JavaScript for DevOps Teams) | Excellent | Good | Excellent | Excellent |
| Real-time Apps | Excellent | Poor | Good | Good |
| Distributed | Cloud (paid) | Built-in | Built-in | Cloud |
Conclusion
Artillery excels at testing modern applications with its YAML-based scenarios, excellent WebSocket/Socket.io support, and rich plugin ecosystem. Its developer-friendly approach and strong CI/CD integration make it ideal for teams practicing continuous performance testing (as discussed in Locust Python Load Testing: Complete Performance Testing Guide).
Choose Artillery when:
- Testing real-time applications (WebSocket, Socket.io)
- YAML configuration preferred over code
- Modern, developer-friendly tooling desired
- Rich plugin ecosystem valuable
- CI/CD integration is priority
Choose alternatives when:
- Distributed testing without cloud service needed (Locust, k6)
- GUI-based test creation preferred (JMeter)
- Custom Python logic essential (Locust)
- Advanced JavaScript scenarios needed (k6)
For teams building modern, real-time applications and practicing DevOps, Artillery provides an excellent balance of simplicity, power, and extensibility for performance testing.
Official Resources
FAQ
What is the difference between load and stress testing? Load testing validates behavior under expected peak traffic. Stress testing pushes beyond capacity to find breaking points and observe failure modes.
How do you choose performance test targets? Define targets based on SLAs, business requirements, and historical baseline data. Common targets: p95 response time < 500ms, error rate < 0.1%, throughput matching peak traffic projections.
What causes performance test results to be unreliable? Common issues: testing against non-production-like data volumes, testing from a single geographic location, not warming up caches, and running tests at different times of day.
How do you integrate performance testing into CI/CD? Add lightweight performance regression tests to your pipeline that complete in under 5 minutes, comparing key metrics against established baselines and failing builds on regressions.
See Also
- API Performance Testing
- Jest & Testing Library: Modern Component Testing for React Applications
- K6: Modern Load Testing with JavaScript for DevOps Teams - Master K6 for modern performance testing: JavaScript-based load testing, CI/CD…
- Master Jest and Testing Library for React component testing. Learn best…
- Comprehensive guide to API load testing strategies
- API Testing Mastery: From REST to Contract Testing - API testing fundamentals before performance validation
- CI/CD Pipeline Optimization for QA Teams - Integrate Artillery into your delivery pipelines
- Jenkins Pipeline for Test Automation - Run Artillery tests in Jenkins pipelines
- GraphQL Testing Guide - Performance test GraphQL APIs with Artillery
