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-Key header 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 RTT
  • 3G: 1.6/0.768 Mbps, 300ms RTT
  • Custom: Define via bwDown, bwUp, latency parameters 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:

  1. Validate JSON schema with jq empty payload.json
  2. Confirm location matches active agent pool
  3. Check connectivity against supported enum values
  4. Verify debug=1 output 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.