Performance Testing
Overview
Performance testing validates that your application meets speed, stability, and scalability requirements under expected and extreme conditions. It answers questions like "How many concurrent users can we handle?", "What's the 99th percentile response time?", and "Where does the system break?".
Performance Test Types
| Type |
Goal |
Pattern |
When to Use |
| Load |
Validate expected traffic |
Ramp to target VUs, sustain, ramp down |
Before release, capacity planning |
| Stress |
Find the breaking point |
Ramp beyond expected capacity |
Pre-launch, architecture validation |
| Spike |
Handle sudden traffic bursts |
Jump to high VUs instantly |
Flash sales, event-driven traffic |
| Soak |
Detect memory leaks / degradation |
Moderate load over hours |
After major changes, long-running services |
| Breakpoint |
Determine absolute maximum |
Continuously increase until failure |
Capacity planning, SLA definition |
Key Metrics
| Metric |
Description |
Typical Thresholds |
| Response Time (p50) |
Median latency |
< 200ms for APIs, < 1s for pages |
| Response Time (p95) |
95th percentile latency |
< 500ms for APIs, < 3s for pages |
| Response Time (p99) |
99th percentile latency |
< 1s for APIs, < 5s for pages |
| Throughput (RPS) |
Requests per second |
Application-specific |
| Error Rate |
% of failed requests |
< 1% under normal load |
| VU Concurrency |
Active virtual users |
Application-specific |
| TTFB |
Time to first byte |
< 200ms |
| Core Web Vitals (LCP) |
Largest Contentful Paint |
< 2.5s |
| Core Web Vitals (INP) |
Interaction to Next Paint |
< 200ms |
| Core Web Vitals (CLS) |
Cumulative Layout Shift |
< 0.1 |
Cross-Platform Tools
| Tool |
Language |
Strengths |
| k6 (Grafana) |
JavaScript |
Developer-friendly, CLI-native, thresholds, scenarios, k6 cloud, k6 browser |
| JMeter |
Java (GUI + CLI) |
Mature, GUI test plan builder, extensive protocol support, plugins |
| Gatling |
Scala / Java |
High performance, code-based DSL, detailed HTML reports |
| Artillery |
YAML + JS |
Simple YAML config, plugin ecosystem, serverless mode |
| Lighthouse |
CLI / Chrome |
Web performance audits, Core Web Vitals, accessibility, SEO |
k6 (Grafana)
Load Test with Stages, Thresholds, and Checks
// tests/performance/load-test.k6.js
import http from "k6/http";
import { check, sleep, group } from "k6";
export const options = {
stages: [
{ duration: "2m", target: 50 }, // Ramp up to 50 VUs
{ duration: "5m", target: 50 }, // Sustain 50 VUs
{ duration: "2m", target: 100 }, // Ramp up to 100 VUs
{ duration: "5m", target: 100 }, // Sustain 100 VUs
{ duration: "2m", target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: [
"p(50)<200", // 50th percentile under 200ms
"p(95)<500", // 95th percentile under 500ms
"p(99)<1000", // 99th percentile under 1s
],
http_req_failed: ["rate<0.01"], // Less than 1% errors
checks: ["rate>0.99"], // 99%+ checks pass
},
};
const BASE_URL = __ENV.BASE_URL || "http://localhost:3000";
export default function () {
group("Homepage flow", () => {
const homeRes = http.get(`${BASE_URL}/`);
check(homeRes, {
"homepage returns 200": (r) => r.status === 200,
"homepage loads under 500ms": (r) => r.timings.duration < 500,
});
const apiRes = http.get(`${BASE_URL}/api/products?limit=20`);
check(apiRes, {
"products API returns 200": (r) => r.status === 200,
"products returns array": (r) => Array.isArray(r.json()),
});
});
sleep(1); // Think time between iterations
}
k6 Scenarios (Advanced)
// tests/performance/scenarios.k6.js
import http from "k6/http";
import { check } from "k6";
export const options = {
scenarios: {
// Constant arrival rate — fixed RPS regardless of response time
constant_load: {
executor: "constant-arrival-rate",
rate: 100, // 100 RPS
timeUnit: "1s",
duration: "5m",
preAllocatedVUs: 50,
maxVUs: 200,
},
// Ramping VUs — gradual increase
ramping_users: {
executor: "ramping-vus",
startVUs: 0,
stages: [
{ duration: "2m", target: 50 },
{ duration: "3m", target: 50 },
{ duration: "1m", target: 0 },
],
},
// Spike test — sudden burst
spike: {
executor: "ramping-arrival-rate",
startRate: 10,
timeUnit: "1s",
stages: [
{ duration: "10s", target: 10 },
{ duration: "1m", target: 500 }, // Spike
{ duration: "10s", target: 10 }, // Recover
],
preAllocatedVUs: 200,
maxVUs: 500,
},
},
thresholds: {
http_req_duration: ["p(95)<500"],
http_req_failed: ["rate<0.01"],
},
};
export default function () {
const res = http.get(`${__ENV.BASE_URL}/api/health`);
check(res, { "status 200": (r) => r.status === 200 });
}
Running k6
# Basic run
k6 run tests/performance/load-test.k6.js
# With environment variables
k6 run tests/performance/load-test.k6.js --env BASE_URL=https://staging.example.com
# Output to multiple destinations
k6 run tests/performance/load-test.k6.js \
--out json=results.json \
--out influxdb=http://localhost:8086/k6
# k6 cloud (Grafana Cloud k6)
k6 cloud tests/performance/load-test.k6.js
Artillery
YAML Configuration Example
# tests/performance/artillery-config.yml
config:
target: "https://staging-api.example.com"
phases:
- name: "Warm up"
duration: 60 # seconds
arrivalRate: 5 # new virtual users per second
- name: "Ramp up"
duration: 120
arrivalRate: 5
rampTo: 50
- name: "Sustained load"
duration: 300
arrivalRate: 50
defaults:
headers:
Authorization: "Bearer {{ $processEnvironment.AUTH_TOKEN }}"
Content-Type: "application/json"
ensure:
thresholds:
- http.response_time.p95: 500
- http.response_time.p99: 1000
- http.codes.200: 95 # 95% of responses must be 200
plugins:
expect: {}
scenarios:
- name: "Browse and purchase flow"
flow:
- get:
url: "/api/products"
expect:
- statusCode: 200
- hasProperty: "body.length"
capture:
- json: "$[0].id"
as: "productId"
- think: 2
- get:
url: "/api/products/{{ productId }}"
expect:
- statusCode: 200
- think: 1
- post:
url: "/api/cart"
json:
productId: "{{ productId }}"
quantity: 1
expect:
- statusCode: 201
Running Artillery
# Install Artillery
npm install -g artillery
# Run test
artillery run tests/performance/artillery-config.yml
# Run with environment overrides
artillery run tests/performance/artillery-config.yml --target https://staging.example.com
# Generate HTML report
artillery run tests/performance/artillery-config.yml --output results.json
artillery report results.json --output report.html
# Quick one-liner smoke test
artillery quick --count 10 --num 5 https://staging-api.example.com/api/health
JMeter
Overview
Apache JMeter is a mature load testing tool with a GUI for building test plans and a CLI mode for CI execution.
Key Concepts
| Concept |
Description |
| Test Plan |
Root container for all test elements |
| Thread Group |
Defines VUs (threads), ramp-up time, loop count |
| Samplers |
HTTP Request, JDBC Request, FTP, etc. |
| Assertions |
Response assertions (status, body, duration) |
| Listeners |
Results viewers (Summary Report, Graph, JTL files) |
| Config Elements |
CSV Data Set, HTTP Header Manager, User Variables |
| Timers |
Think time between requests |
CLI Mode for CI
# Run test plan in non-GUI mode
jmeter -n -t test-plan.jmx -l results.jtl -e -o report/
# With properties
jmeter -n -t test-plan.jmx \
-Jthreads=100 \
-Jrampup=60 \
-Jduration=300 \
-Jhost=staging-api.example.com \
-l results.jtl
# Generate HTML report from results
jmeter -g results.jtl -o report/
GitHub Actions Integration
# .github/workflows/jmeter.yml
jobs:
performance-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run JMeter Tests
uses: rbhadti94/apache-jmeter-action@v0.5.0
with:
testFilePath: tests/performance/test-plan.jmx
outputReportsFolder: reports/
args: >
-Jthreads=50 -Jrampup=30 -Jduration=120
-Jhost=${{ secrets.STAGING_HOST }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: jmeter-report
path: reports/
Gatling
Overview
Gatling uses a code-based DSL (Scala or Java) for defining simulations, producing detailed HTML reports automatically.
Scala DSL Example
// src/test/scala/simulations/BasicSimulation.scala
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class BasicSimulation extends Simulation {
val httpProtocol = http
.baseUrl("https://staging-api.example.com")
.acceptHeader("application/json")
.authorizationHeader("Bearer ${authToken}")
val feeder = csv("test-data/users.csv").random
val browseScenario = scenario("Browse Products")
.feed(feeder)
.exec(
http("List Products")
.get("/api/products")
.check(status.is(200))
.check(jsonPath("$[0].id").saveAs("productId"))
)
.pause(1, 3)
.exec(
http("Get Product Detail")
.get("/api/products/${productId}")
.check(status.is(200))
)
setUp(
browseScenario.inject(
rampUsers(50).during(2.minutes),
constantUsersPerSec(10).during(5.minutes),
rampUsers(0).during(1.minute)
)
).protocols(httpProtocol)
.assertions(
global.responseTime.percentile(95).lt(500),
global.successfulRequests.percent.gt(99.0)
)
}
Running Gatling
# Run with Maven
mvn gatling:test
# Run with Gradle
gradle gatlingRun
# Run specific simulation
mvn gatling:test -Dgatling.simulationClass=simulations.BasicSimulation
Lighthouse
Overview
Lighthouse audits web performance, accessibility, best practices, and SEO. It measures Core Web Vitals and provides actionable improvement suggestions.
CLI Usage
# Install Lighthouse CLI
npm install -g lighthouse
# Run performance audit
lighthouse https://example.com \
--output json,html \
--output-path ./results/lighthouse \
--chrome-flags="--headless --no-sandbox"
# Performance-only audit
lighthouse https://example.com \
--only-categories=performance \
--output json \
--output-path ./results/perf.json
# Run with budget
lighthouse https://example.com \
--budget-path=budgets.json \
--output html
Performance Budget File
// budgets.json
[
{
"path": "/*",
"timings": [
{ "metric": "interactive", "budget": 3000 },
{ "metric": "first-contentful-paint", "budget": 1500 },
{ "metric": "largest-contentful-paint", "budget": 2500 }
],
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "total", "budget": 1000 }
]
}
]
CI Integration with Lighthouse CI
# .github/workflows/lighthouse.yml
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install -g @lhci/cli
- run: |
lhci autorun \
--collect.url=https://staging.example.com \
--collect.numberOfRuns=3 \
--assert.preset=lighthouse:recommended \
--assert.assertions.largest-contentful-paint=warn:2500 \
--assert.assertions.interactive=error:5000
CI Integration Patterns
When to Run Each Test Type
| Test Type |
Trigger |
Duration |
Gate |
| Smoke (minimal load) |
Every PR |
1-2 min |
Fail PR if errors |
| Load (expected traffic) |
Nightly or pre-release |
10-20 min |
Alert on threshold breach |
| Stress (beyond capacity) |
Pre-release |
20-30 min |
Report, don't gate |
| Soak (extended duration) |
Weekly or pre-release |
2-8 hours |
Alert on degradation |
| Lighthouse |
Every PR |
1-2 min |
Warn on budget violation |
k6 CI Pipeline Example
# .github/workflows/performance.yml
name: Performance Tests
on:
pull_request:
branches: [main]
schedule:
- cron: "0 2 * * *" # Nightly at 2 AM
jobs:
smoke-test:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: grafana/k6-action@v0.3.1
with:
filename: tests/performance/smoke.k6.js
env:
BASE_URL: ${{ secrets.STAGING_URL }}
load-test:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: grafana/k6-action@v0.3.1
with:
filename: tests/performance/load-test.k6.js
env:
BASE_URL: ${{ secrets.STAGING_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: k6-results
path: results/
Best Practices
Test Design
- Start with a smoke test (minimal load) to validate the test script works before scaling up.
- Use realistic think times (
sleep() / pause()) to simulate actual user behavior.
- Use data-driven tests with CSV feeders or dynamic data generation to avoid caching skew.
- Test the same scenario at different load levels: smoke, load, stress, spike.
Metrics and Thresholds
- Always define thresholds — tests without pass/fail criteria are just logs.
- Focus on percentiles (p95, p99), not averages — averages hide tail latency.
- Track error rate alongside response time — fast errors are still failures.
- Baseline before optimizing — run tests against a known-good build first.
CI Integration
- Run smoke tests on every PR (fast, catches regressions early).
- Run full load tests nightly or pre-release (comprehensive, takes time).
- Store results as artifacts for trend analysis over time.
- Set thresholds as CI gates: fail the pipeline if p95 exceeds the budget.
Infrastructure
- Run performance tests against a dedicated staging environment, not shared dev.
- Ensure the load generator has sufficient resources (CPU, network) to avoid bottlenecking the test tool itself.
- Use distributed load generation (k6 cloud, JMeter distributed mode) for large-scale tests.
- Monitor the system under test (CPU, memory, DB connections) alongside the k6/Artillery metrics.
Reporting
- Generate HTML reports for human review (Gatling, JMeter, Artillery all support this).
- Export machine-readable results (JSON, JTL) for trend tracking and dashboards.
- Compare results against previous runs to catch performance regressions.
- Document performance baselines and SLAs in the repository alongside the test scripts.
1---2name: performance-testing3description: |4 Use for load, stress, and scalability testing of applications and APIs. Covers k6 (Grafana), JMeter, Gatling, Artillery, and Lighthouse for web performance audits. Includes test type definitions, key metrics, thresholds, CI integration patterns, and performance budgets.5 USE FOR: k6, JMeter, Gatling, Artillery, Lighthouse, load testing, stress testing, performance benchmarks, Core Web Vitals, throughput testing, spike testing, soak testing, capacity planning, performance budgets6 DO NOT USE FOR: functional API testing (use api-testing), browser E2E tests (use e2e-testing), visual regression (use visual-testing)7license: MIT8metadata:9 displayName: "Performance Testing"10 author: "Tyler-R-Kendrick"11compatibility: claude, copilot, cursor12references:13 - title: "k6 Documentation (Grafana)"14 url: "https://grafana.com/docs/k6/latest/"15 - title: "Apache JMeter"16 url: "https://jmeter.apache.org"17 - title: "Gatling Documentation"18 url: "https://docs.gatling.io"19 - title: "Lighthouse Documentation (Chrome Developers)"20 url: "https://developer.chrome.com/docs/lighthouse/"21---2223# Performance Testing2425## Overview26Performance testing validates that your application meets speed, stability, and scalability requirements under expected and extreme conditions. It answers questions like "How many concurrent users can we handle?", "What's the 99th percentile response time?", and "Where does the system break?".2728## Performance Test Types2930| Type | Goal | Pattern | When to Use |31|------|------|---------|-------------|32| **Load** | Validate expected traffic | Ramp to target VUs, sustain, ramp down | Before release, capacity planning |33| **Stress** | Find the breaking point | Ramp beyond expected capacity | Pre-launch, architecture validation |34| **Spike** | Handle sudden traffic bursts | Jump to high VUs instantly | Flash sales, event-driven traffic |35| **Soak** | Detect memory leaks / degradation | Moderate load over hours | After major changes, long-running services |36| **Breakpoint** | Determine absolute maximum | Continuously increase until failure | Capacity planning, SLA definition |3738## Key Metrics3940| Metric | Description | Typical Thresholds |41|--------|-------------|-------------------|42| **Response Time (p50)** | Median latency | < 200ms for APIs, < 1s for pages |43| **Response Time (p95)** | 95th percentile latency | < 500ms for APIs, < 3s for pages |44| **Response Time (p99)** | 99th percentile latency | < 1s for APIs, < 5s for pages |45| **Throughput (RPS)** | Requests per second | Application-specific |46| **Error Rate** | % of failed requests | < 1% under normal load |47| **VU Concurrency** | Active virtual users | Application-specific |48| **TTFB** | Time to first byte | < 200ms |49| **Core Web Vitals (LCP)** | Largest Contentful Paint | < 2.5s |50| **Core Web Vitals (INP)** | Interaction to Next Paint | < 200ms |51| **Core Web Vitals (CLS)** | Cumulative Layout Shift | < 0.1 |5253---5455## Cross-Platform Tools5657| Tool | Language | Strengths |58|------|----------|-----------|59| **k6 (Grafana)** | JavaScript | Developer-friendly, CLI-native, thresholds, scenarios, k6 cloud, k6 browser |60| **JMeter** | Java (GUI + CLI) | Mature, GUI test plan builder, extensive protocol support, plugins |61| **Gatling** | Scala / Java | High performance, code-based DSL, detailed HTML reports |62| **Artillery** | YAML + JS | Simple YAML config, plugin ecosystem, serverless mode |63| **Lighthouse** | CLI / Chrome | Web performance audits, Core Web Vitals, accessibility, SEO |6465---6667## k6 (Grafana)6869### Load Test with Stages, Thresholds, and Checks7071```javascript72// tests/performance/load-test.k6.js73import http from "k6/http";74import { check, sleep, group } from "k6";7576export const options = {77 stages: [78 { duration: "2m", target: 50 }, // Ramp up to 50 VUs79 { duration: "5m", target: 50 }, // Sustain 50 VUs80 { duration: "2m", target: 100 }, // Ramp up to 100 VUs81 { duration: "5m", target: 100 }, // Sustain 100 VUs82 { duration: "2m", target: 0 }, // Ramp down83 ],84 thresholds: {85 http_req_duration: [86 "p(50)<200", // 50th percentile under 200ms87 "p(95)<500", // 95th percentile under 500ms88 "p(99)<1000", // 99th percentile under 1s89 ],90 http_req_failed: ["rate<0.01"], // Less than 1% errors91 checks: ["rate>0.99"], // 99%+ checks pass92 },93};9495const BASE_URL = __ENV.BASE_URL || "http://localhost:3000";9697export default function () {98 group("Homepage flow", () => {99 const homeRes = http.get(`${BASE_URL}/`);100 check(homeRes, {101 "homepage returns 200": (r) => r.status === 200,102 "homepage loads under 500ms": (r) => r.timings.duration < 500,103 });104105 const apiRes = http.get(`${BASE_URL}/api/products?limit=20`);106 check(apiRes, {107 "products API returns 200": (r) => r.status === 200,108 "products returns array": (r) => Array.isArray(r.json()),109 });110 });111112 sleep(1); // Think time between iterations113}114```115116### k6 Scenarios (Advanced)117118```javascript119// tests/performance/scenarios.k6.js120import http from "k6/http";121import { check } from "k6";122123export const options = {124 scenarios: {125 // Constant arrival rate — fixed RPS regardless of response time126 constant_load: {127 executor: "constant-arrival-rate",128 rate: 100, // 100 RPS129 timeUnit: "1s",130 duration: "5m",131 preAllocatedVUs: 50,132 maxVUs: 200,133 },134 // Ramping VUs — gradual increase135 ramping_users: {136 executor: "ramping-vus",137 startVUs: 0,138 stages: [139 { duration: "2m", target: 50 },140 { duration: "3m", target: 50 },141 { duration: "1m", target: 0 },142 ],143 },144 // Spike test — sudden burst145 spike: {146 executor: "ramping-arrival-rate",147 startRate: 10,148 timeUnit: "1s",149 stages: [150 { duration: "10s", target: 10 },151 { duration: "1m", target: 500 }, // Spike152 { duration: "10s", target: 10 }, // Recover153 ],154 preAllocatedVUs: 200,155 maxVUs: 500,156 },157 },158 thresholds: {159 http_req_duration: ["p(95)<500"],160 http_req_failed: ["rate<0.01"],161 },162};163164export default function () {165 const res = http.get(`${__ENV.BASE_URL}/api/health`);166 check(res, { "status 200": (r) => r.status === 200 });167}168```169170### Running k6171172```bash173# Basic run174k6 run tests/performance/load-test.k6.js175176# With environment variables177k6 run tests/performance/load-test.k6.js --env BASE_URL=https://staging.example.com178179# Output to multiple destinations180k6 run tests/performance/load-test.k6.js \181 --out json=results.json \182 --out influxdb=http://localhost:8086/k6183184# k6 cloud (Grafana Cloud k6)185k6 cloud tests/performance/load-test.k6.js186```187188---189190## Artillery191192### YAML Configuration Example193194```yaml195# tests/performance/artillery-config.yml196config:197 target: "https://staging-api.example.com"198 phases:199 - name: "Warm up"200 duration: 60 # seconds201 arrivalRate: 5 # new virtual users per second202 - name: "Ramp up"203 duration: 120204 arrivalRate: 5205 rampTo: 50206 - name: "Sustained load"207 duration: 300208 arrivalRate: 50209 defaults:210 headers:211 Authorization: "Bearer {{ $processEnvironment.AUTH_TOKEN }}"212 Content-Type: "application/json"213 ensure:214 thresholds:215 - http.response_time.p95: 500216 - http.response_time.p99: 1000217 - http.codes.200: 95 # 95% of responses must be 200218 plugins:219 expect: {}220221scenarios:222 - name: "Browse and purchase flow"223 flow:224 - get:225 url: "/api/products"226 expect:227 - statusCode: 200228 - hasProperty: "body.length"229 capture:230 - json: "$[0].id"231 as: "productId"232 - think: 2233 - get:234 url: "/api/products/{{ productId }}"235 expect:236 - statusCode: 200237 - think: 1238 - post:239 url: "/api/cart"240 json:241 productId: "{{ productId }}"242 quantity: 1243 expect:244 - statusCode: 201245```246247### Running Artillery248249```bash250# Install Artillery251npm install -g artillery252253# Run test254artillery run tests/performance/artillery-config.yml255256# Run with environment overrides257artillery run tests/performance/artillery-config.yml --target https://staging.example.com258259# Generate HTML report260artillery run tests/performance/artillery-config.yml --output results.json261artillery report results.json --output report.html262263# Quick one-liner smoke test264artillery quick --count 10 --num 5 https://staging-api.example.com/api/health265```266267---268269## JMeter270271### Overview272Apache JMeter is a mature load testing tool with a GUI for building test plans and a CLI mode for CI execution.273274### Key Concepts275276| Concept | Description |277|---------|-------------|278| **Test Plan** | Root container for all test elements |279| **Thread Group** | Defines VUs (threads), ramp-up time, loop count |280| **Samplers** | HTTP Request, JDBC Request, FTP, etc. |281| **Assertions** | Response assertions (status, body, duration) |282| **Listeners** | Results viewers (Summary Report, Graph, JTL files) |283| **Config Elements** | CSV Data Set, HTTP Header Manager, User Variables |284| **Timers** | Think time between requests |285286### CLI Mode for CI287288```bash289# Run test plan in non-GUI mode290jmeter -n -t test-plan.jmx -l results.jtl -e -o report/291292# With properties293jmeter -n -t test-plan.jmx \294 -Jthreads=100 \295 -Jrampup=60 \296 -Jduration=300 \297 -Jhost=staging-api.example.com \298 -l results.jtl299300# Generate HTML report from results301jmeter -g results.jtl -o report/302```303304### GitHub Actions Integration305306```yaml307# .github/workflows/jmeter.yml308jobs:309 performance-test:310 runs-on: ubuntu-latest311 steps:312 - uses: actions/checkout@v4313 - name: Run JMeter Tests314 uses: rbhadti94/apache-jmeter-action@v0.5.0315 with:316 testFilePath: tests/performance/test-plan.jmx317 outputReportsFolder: reports/318 args: >319 -Jthreads=50 -Jrampup=30 -Jduration=120320 -Jhost=${{ secrets.STAGING_HOST }}321 - uses: actions/upload-artifact@v4322 if: always()323 with:324 name: jmeter-report325 path: reports/326```327328---329330## Gatling331332### Overview333Gatling uses a code-based DSL (Scala or Java) for defining simulations, producing detailed HTML reports automatically.334335### Scala DSL Example336337```scala338// src/test/scala/simulations/BasicSimulation.scala339import io.gatling.core.Predef._340import io.gatling.http.Predef._341import scala.concurrent.duration._342343class BasicSimulation extends Simulation {344345 val httpProtocol = http346 .baseUrl("https://staging-api.example.com")347 .acceptHeader("application/json")348 .authorizationHeader("Bearer ${authToken}")349350 val feeder = csv("test-data/users.csv").random351352 val browseScenario = scenario("Browse Products")353 .feed(feeder)354 .exec(355 http("List Products")356 .get("/api/products")357 .check(status.is(200))358 .check(jsonPath("$[0].id").saveAs("productId"))359 )360 .pause(1, 3)361 .exec(362 http("Get Product Detail")363 .get("/api/products/${productId}")364 .check(status.is(200))365 )366367 setUp(368 browseScenario.inject(369 rampUsers(50).during(2.minutes),370 constantUsersPerSec(10).during(5.minutes),371 rampUsers(0).during(1.minute)372 )373 ).protocols(httpProtocol)374 .assertions(375 global.responseTime.percentile(95).lt(500),376 global.successfulRequests.percent.gt(99.0)377 )378}379```380381### Running Gatling382383```bash384# Run with Maven385mvn gatling:test386387# Run with Gradle388gradle gatlingRun389390# Run specific simulation391mvn gatling:test -Dgatling.simulationClass=simulations.BasicSimulation392```393394---395396## Lighthouse397398### Overview399Lighthouse audits web performance, accessibility, best practices, and SEO. It measures Core Web Vitals and provides actionable improvement suggestions.400401### CLI Usage402403```bash404# Install Lighthouse CLI405npm install -g lighthouse406407# Run performance audit408lighthouse https://example.com \409 --output json,html \410 --output-path ./results/lighthouse \411 --chrome-flags="--headless --no-sandbox"412413# Performance-only audit414lighthouse https://example.com \415 --only-categories=performance \416 --output json \417 --output-path ./results/perf.json418419# Run with budget420lighthouse https://example.com \421 --budget-path=budgets.json \422 --output html423```424425### Performance Budget File426427```json428// budgets.json429[430 {431 "path": "/*",432 "timings": [433 { "metric": "interactive", "budget": 3000 },434 { "metric": "first-contentful-paint", "budget": 1500 },435 { "metric": "largest-contentful-paint", "budget": 2500 }436 ],437 "resourceSizes": [438 { "resourceType": "script", "budget": 300 },439 { "resourceType": "total", "budget": 1000 }440 ]441 }442]443```444445### CI Integration with Lighthouse CI446447```yaml448# .github/workflows/lighthouse.yml449jobs:450 lighthouse:451 runs-on: ubuntu-latest452 steps:453 - uses: actions/checkout@v4454 - uses: actions/setup-node@v4455 with:456 node-version: 20457 - run: npm install -g @lhci/cli458 - run: |459 lhci autorun \460 --collect.url=https://staging.example.com \461 --collect.numberOfRuns=3 \462 --assert.preset=lighthouse:recommended \463 --assert.assertions.largest-contentful-paint=warn:2500 \464 --assert.assertions.interactive=error:5000465```466467---468469## CI Integration Patterns470471### When to Run Each Test Type472473| Test Type | Trigger | Duration | Gate |474|-----------|---------|----------|------|475| **Smoke** (minimal load) | Every PR | 1-2 min | Fail PR if errors |476| **Load** (expected traffic) | Nightly or pre-release | 10-20 min | Alert on threshold breach |477| **Stress** (beyond capacity) | Pre-release | 20-30 min | Report, don't gate |478| **Soak** (extended duration) | Weekly or pre-release | 2-8 hours | Alert on degradation |479| **Lighthouse** | Every PR | 1-2 min | Warn on budget violation |480481### k6 CI Pipeline Example482483```yaml484# .github/workflows/performance.yml485name: Performance Tests486on:487 pull_request:488 branches: [main]489 schedule:490 - cron: "0 2 * * *" # Nightly at 2 AM491492jobs:493 smoke-test:494 if: github.event_name == 'pull_request'495 runs-on: ubuntu-latest496 steps:497 - uses: actions/checkout@v4498 - uses: grafana/k6-action@v0.3.1499 with:500 filename: tests/performance/smoke.k6.js501 env:502 BASE_URL: ${{ secrets.STAGING_URL }}503504 load-test:505 if: github.event_name == 'schedule'506 runs-on: ubuntu-latest507 steps:508 - uses: actions/checkout@v4509 - uses: grafana/k6-action@v0.3.1510 with:511 filename: tests/performance/load-test.k6.js512 env:513 BASE_URL: ${{ secrets.STAGING_URL }}514 - uses: actions/upload-artifact@v4515 if: always()516 with:517 name: k6-results518 path: results/519```520521---522523## Best Practices524525### Test Design526- Start with a smoke test (minimal load) to validate the test script works before scaling up.527- Use realistic think times (`sleep()` / `pause()`) to simulate actual user behavior.528- Use data-driven tests with CSV feeders or dynamic data generation to avoid caching skew.529- Test the same scenario at different load levels: smoke, load, stress, spike.530531### Metrics and Thresholds532- Always define thresholds — tests without pass/fail criteria are just logs.533- Focus on percentiles (p95, p99), not averages — averages hide tail latency.534- Track error rate alongside response time — fast errors are still failures.535- Baseline before optimizing — run tests against a known-good build first.536537### CI Integration538- Run smoke tests on every PR (fast, catches regressions early).539- Run full load tests nightly or pre-release (comprehensive, takes time).540- Store results as artifacts for trend analysis over time.541- Set thresholds as CI gates: fail the pipeline if p95 exceeds the budget.542543### Infrastructure544- Run performance tests against a dedicated staging environment, not shared dev.545- Ensure the load generator has sufficient resources (CPU, network) to avoid bottlenecking the test tool itself.546- Use distributed load generation (k6 cloud, JMeter distributed mode) for large-scale tests.547- Monitor the system under test (CPU, memory, DB connections) alongside the k6/Artillery metrics.548549### Reporting550- Generate HTML reports for human review (Gatling, JMeter, Artillery all support this).551- Export machine-readable results (JSON, JTL) for trend tracking and dashboards.552- Compare results against previous runs to catch performance regressions.553- Document performance baselines and SLAs in the repository alongside the test scripts.