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 (as discussed in Gatling: High-Performance Load Testing with Scala DSL), and Socket.io, plus a rich plugin ecosystem, Artillery excels at testing modern, real-time applications.
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.