Turning a Production .HAR Capture into a 7-Phase Latency Audit, Playwright Test, and k6 Load Script
When a web app feels sluggish in production or fails intermittently during a multi-step checkout flow, a browser HTTP Archive (.har) capture contains the ground truth. Yet most developers only open .har files to glance a
When a web app feels sluggish in production or fails intermittently during a multi-step checkout flow, a browser HTTP Archive (.har) capture contains the ground truth. Yet most developers only open .har files to glance at status codes, missing two massive productivity wins:
- Automated 7-Phase Network Bottleneck Diagnostics (distinguishing DNS/TLS handshakes from HTTP/1.1 connection pool starvation and backend TTFB stalls).
- Instant Replay Code Generation (converting the exact filtered network sequence into a runnable Playwright E2E test, a k6 load test script, or an OpenAPI 3.1 schema).
Let's look at how the HAR 1.2 timing specification works under the hood and how to transform raw network traces into executable tests.
1. Reading the 7 Timing Phases in entry.timings
Per the HAR 1.2 spec, every network entry records seven timing deltas (in milliseconds, where -1 indicates the phase did not apply, such as ssl on a reused keep-alive socket):
export interface HarTimingPhases {
blocked: number; // Waiting for an available TCP socket (6-connection browser limit!)
dns: number; // Domain Name System lookup time
connect: number; // TCP 3-way handshake (SYN / SYN-ACK / ACK)
ssl: number; // TLS 1.2 / 1.3 handshake negotiation
send: number; // Time spent transmitting request headers & body
wait: number; // Time to First Byte (TTFB) β server processing + network RTT
receive: number; // Time spent downloading the response payload
total: number;
}
When analyzing a waterfall across hundreds of requests, three patterns immediately pinpoint the bottleneck:
-
High
blocked(> 100 ms) withconnect: -1: The browser is hitting the 6-connections-per-origin limit on HTTP/1.1, or main-thread JavaScript is starving the network queue. Upgrading the origin to HTTP/2 or HTTP/3 (multiplexing over a single connection) eliminates theblockedqueue. -
High
wait(TTFB > 500 ms) with lowreceive: The bottleneck is strictly server-side (database query latency, cold-start serverless execution, or upstream microservice contention). -
High
receiveon JSON/JS responses: Checkresponse.headersforContent-Encoding: brorgzip. Ifresponse.bodySizeequalsresponse.content.sizeon a 500 KB payload, compression is missing at the CDN edge.
2. Generating Playwright E2E and k6 Load Scripts Directly from Filtered HAR Entries
Instead of manually writing page.request.post(...) or k6 http.batch(...) calls from scratch to reproduce a bug, you can filter a HAR capture down to the XHR/Fetch API calls (resourceType === 'fetch' || resourceType === 'xhr'), sanitize any live credentials into environment variable placeholders ({{BEARER_TOKEN_1}}), and compile the trace directly into test code:
// Auto-generated k6 load script from a sanitized HAR sequence:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10,
duration: '30s',
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const params = {
headers: {
'Accept': 'application/json',
'Authorization': __ENV.BEARER_TOKEN_1 || 'Bearer REDACTED',
},
};
const res = http.get('https://api.example.com/v1/orders?limit=25', params);
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
If you want to inspect 7-phase waterfalls, compare two HAR captures ("Before vs. After Deployment" regression diffing), or export sanitized traces to k6, Playwright, OpenAPI 3.1, Postman v2.1, or cURL/Fetch/Python without uploading your HAR file to any server, you can run the KivaroTools Client-Side HAR Analyzer & Sanitizer locally in your browser.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.