Configuring WebPageTest API for Automated Testing
Programmatic test execution requires precise endpoint routing, deterministic payload construction, and strict CI/CD gating logic. This guide details the exact REST API v2 configuration for automated performance validation, bypassing UI overhead. The architecture integrates directly with Lighthouse CI & WebPageTest Integration to establish enterprise-grade metric collection and PR-blocking workflows.
Technical Baseline:
- REST API endpoint:
https://www.webpagetest.org/runtest.php - Authentication:
X-API-Keyheader or?key=query parameter - Response format:
application/json
CI Secret Injection & Rate Limit Enforcement
Hardcoding API keys in workflow YAML triggers immediate revocation. Inject credentials via CI environment variables and validate with a dry-run curl before test submission. Implement exponential backoff on 429 Too Many Requests responses to prevent pipeline starvation.
Public API limits enforce 10 requests/second and 1,000 tests/day. Private agents bypass these constraints but require explicit queue routing and dedicated infrastructure. Differentiate 401 Unauthorized (invalid key) from 403 Forbidden (rate-limited or IP-blocked) to route error handling correctly.
# GitHub Actions: Secure Secret Injection
env:
WPT_API_KEY: ${{ secrets.WPT_API_KEY }}
WPT_BASE_URL: https://www.webpagetest.org
steps:
- name: Validate API Key
run: |
curl -s -o /dev/null -w "%{http_code}" \
-H "X-API-Key: $WPT_API_KEY" \
"$WPT_BASE_URL/getLocations.php?f=json" | grep -q "200"
Retry Logic Specification:
- Initial delay:
5s - Backoff multiplier:
2x(5s → 10s → 20s) - Add
jitter(randomized ±1s) to prevent thundering herd collisions on shared agents.
Constructing Deterministic JSON Payloads
The POST /runtest.php endpoint requires strict field validation. Omitting location defaults to Dulles, VA; specify exact agent IDs for geographic consistency across CI runs. Set firstViewOnly=true to reduce execution time by ~60% while preserving Core Web Vitals accuracy.
For authenticated or multi-step flows, embed WPT scripting syntax in the script field. Route custom connectivity profiles through WebPageTest Private Instance Setup to enforce enterprise network simulation standards without relying on public throttling presets.
{
"url": "https://staging.example.com/checkout",
"location": "us-east-1-ec2:Chrome.Cable",
"runs": 1,
"firstViewOnly": true,
"connectivity": "Cable",
"script": "navigate https://staging.example.com/login\nsetValue name=email user@domain.com\nsetValue name=password securepass\nclickAndWait id=submit",
"blockAds": true,
"video": false,
"f": "json"
}
Connectivity Profile Matrix:
Cable: 5/1 Mbps, 28ms RTT3G: 1.6/0.768 Mbps, 300ms RTTCustom: Define viabwDown,bwUp,latencyparameters for precise throttling.
Asynchronous Result Polling & Timeout Handling
Test execution is asynchronous. Poll /jsonResult.php?test={testId} every 5 seconds. Parse statusCode: 200 (success), 100 (in progress), 0 (not found). Implement a hard timeout at 180 seconds to prevent CI pipeline hangs.
On success, extract data.median.firstView.* metrics. Handle Test ID not found by verifying agent queue depth and retrying submission. Queue saturation typically occurs during peak CI windows; stagger matrix jobs to distribute load.
#!/usr/bin/env bash
POLL_INTERVAL=5
MAX_RETRIES=36 # 180s total timeout
RETRY_COUNT=0
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
RESPONSE=$(curl -s "$WPT_BASE_URL/jsonResult.php?test=$TEST_ID&f=json")
STATUS=$(echo "$RESPONSE" | jq -r '.statusCode')
if [ "$STATUS" == "200" ]; then
echo "Test complete. Extracting metrics..."
LCP=$(echo "$RESPONSE" | jq -r '.data.median.firstView.LCP')
CLS=$(echo "$RESPONSE" | jq -r '.data.median.firstView.CLS')
exit 0
elif [ "$STATUS" == "100" ]; then
echo "Test in progress... ($((RETRY_COUNT * POLL_INTERVAL))s elapsed)"
sleep $POLL_INTERVAL
RETRY_COUNT=$((RETRY_COUNT + 1))
else
echo "Error: Unexpected status code $STATUS"
exit 1
fi
done
echo "Timeout exceeded. Failing pipeline."
exit 1
Implementing Exact Performance Budget Gating
Map parsed metrics to strict CI exit codes. Use exit 0 for pass, exit 1 for warning (non-blocking), exit 2 for fail (PR block). Enforce thresholds: LCP ≤ 2.5s, INP ≤ 200ms, CLS ≤ 0.1, SpeedIndex ≤ 2500ms. Integrate gating into GitHub Actions matrix strategies or Jenkins declarative pipelines.
Fail fast on metric regression >10% from baseline. Store historical baselines in a versioned JSON artifact. Compare current run against last_successful_run.json using jq diff logic.
#!/usr/bin/env bash
# Threshold Matrix
declare -A THRESHOLDS=( ["LCP"]=2500 ["INP"]=200 ["CLS"]=0.1 ["SpeedIndex"]=2500 )
# Baseline Comparison Logic
for METRIC in "${!THRESHOLDS[@]}"; do
CURRENT=$(echo "$RESPONSE" | jq -r ".data.median.firstView.$METRIC")
THRESHOLD=${THRESHOLDS[$METRIC]}
if (( $(echo "$CURRENT > $THRESHOLD" | bc -l) )); then
echo "FAIL: $METRIC ($CURRENT) exceeds threshold ($THRESHOLD)"
exit 2
fi
done
# Regression Check (>10% delta from baseline)
BASELINE_LCP=$(jq -r '.LCP' baseline.json)
if (( $(echo "$LCP > ($BASELINE_LCP * 1.1)" | bc -l) )); then
echo "REGRESSION: LCP increased by >10% from baseline"
exit 2
fi
echo "All budgets passed."
exit 0
Edge-Case Troubleshooting & Reproducible Debugging
Common API failures: 400 Bad Request (malformed JSON or unsupported connectivity profile), 409 Conflict (duplicate test ID collision), 503 Service Unavailable (agent queue saturation). Reproduce failures using curl -v -X POST with exact payload to isolate header vs body issues.
Enable debug=1 in payload to retrieve raw agent logs. Bypass CDN cache by appending ?wpt_bypass=1 to target URLs for deterministic runs. Verify agent availability before submission to avoid silent queue drops.
# Verbose Payload Submission
curl -v -X POST "$WPT_BASE_URL/runtest.php" \
-H "Content-Type: application/json" \
-H "X-API-Key: $WPT_API_KEY" \
-d @payload.json
# Agent Queue Depth Check
curl -s "$WPT_BASE_URL/getLocations.php?f=json" | jq '.[].pending'
# Deterministic URL Parameters
TARGET_URL="https://staging.example.com?wpt_bypass=1&nocache=1"
Diagnostic Checklist:
- Validate JSON schema with
jq empty payload.json - Confirm
locationmatches active agent pool - Check
connectivityagainst supported enum values - Verify
debug=1output for script syntax errors
Throughput Optimization & Cost Management
Balance statistical significance with CI velocity. runs=1 suffices for CI gating when combined with firstViewOnly=true. Parallelize submissions using CI matrix jobs (max 5 concurrent per API key). Deduplicate tests via testId caching to avoid redundant API calls on unchanged commits.
Calculate cost-per-run: public ($0.10/test) vs private infrastructure (fixed EC2/Spot pricing). Implement SHA-256 hashing of URL + payload to gate execution on code changes only.
# Payload Deduplication via SHA-256
PAYLOAD_HASH=$(echo -n "$TARGET_URL$(cat payload.json)" | sha256sum | awk '{print $1}')
if [ -f ".wpt_cache/$PAYLOAD_HASH.json" ]; then
echo "Cache hit. Reusing previous results."
RESPONSE=$(cat ".wpt_cache/$PAYLOAD_HASH.json")
else
# Submit test, poll, save to cache
curl -s -X POST ... > ".wpt_cache/$PAYLOAD_HASH.json"
fi
Cost & Velocity Formula:
Daily Cost = (tests_per_day × $0.10) + infra_overhead
Reduce overhead by caching static asset runs and gating only on PR diffs affecting layout, JS bundles, or image assets.