Recording
Table of Contents
- Why I mostly hand-write scripts
- How: record a HAR
- How: convert it with har-to-k6
- How: clean up the result
- Tips
Why I mostly hand-write scripts
Unlike the JMeter track, where BlazeMeter + Fiddler recording is the normal starting point, I mostly hand-write the API scripts in this track. The Pizza Playground's API is small, and Swagger UI (http://127.0.0.1:8080/swagger-ui.html) documents every endpoint, so there is rarely more typing in http.get('/api/v1/menu') than in cleaning up a recording. A browser recording also carries noise a script does not want: static assets, analytics beacons, favicon requests, and every request with the exact ids from that one session baked in.
Recording still earns its place for a flow you don't know well yet, or one with enough steps that reconstructing it by hand risks missing a request. har-to-k6 is the tool for that conversion, and — unlike the rest of this track's own scripts — running it via npx is fine: it is a one-time conversion step outside k6/sample/, not part of the plain-JavaScript sample the scripts themselves have to stay within.
How: record a HAR
A HAR file is a recording of every request a browser made. Two ways to get one:
Option 1: Playwright codegen (my pick)
If you already have the Playwright track set up, codegen records the HAR for you while you click through the shop:
npx playwright codegen --save-har=recording.har --save-har-glob="**/{api/v1,pay}/**" http://localhost:3000/menu
- A browser opens on the shop. Do the flow: Customize a pizza → Add to cart → checkout (pickup) → Pay on the mock gateway page → wait for the confirmation.
- Close the browser.
recording.haris written when codegen exits.
Why I prefer it over DevTools:
--save-har-globfilters at record time.**/{api/v1,pay}/**keeps only the playground API and the mock gateway's pay page. No JavaScript bundles, fonts or images to delete afterwards.- You get a UI test for free. The same session also generates a Playwright test in the codegen window, so one recording feeds both the UI track and this one.
Option 2: Chrome DevTools
- Open DevTools → Network tab and make sure it is recording (red dot).
- Do the same flow in the browser.
- Right-click any request → Save all as HAR with content.
DevTools records everything the page loads, so expect to delete a lot more in the clean-up step.
How: convert it with har-to-k6
npx har-to-k6 recording.har -o recorded.js
info: Converting 'recording.har'
info: Wrote k6 script to 'recorded.js'
Here is the checkout flow recorded with Playwright and converted. Six requests, in the order the browser sent them, trimmed to the interesting lines:
/*
* Creator: Playwright 1.63.0
* Browser: chromium 153.0.8010.12
*/
import { sleep, group } from 'k6'
import http from 'k6/http'
export const options = {}
export default function main() {
let response
group('page@639276e401d2ad4f8b1ffbacd5d8c8f5 - Pizza Playground', function () {
response = http.get('http://localhost:8080/api/v1/menu', {
headers: {
'Accept-Encoding': 'gzip, deflate, br, zstd',
Connection: 'keep-alive',
Host: 'localhost:8080',
Origin: 'http://localhost:3000',
// ... 9 more browser headers
},
})
response = http.post(
'http://localhost:8080/api/v1/orders',
'{"type":"PICKUP","customer":{"name":"Mel","email":"customer01@playground.local","phone":"0123456789"},"items":[{"pizzaId":1,"sizeId":1,"crustId":1,"toppingIds":[],"quantity":1}],"autoPay":false}',
{ headers: { /* ... */ } }
)
response = http.get('http://localhost:8090/pay/bill_549c3572_1790579929540', { headers: { /* ... */ } })
response = http.post(
'http://localhost:8090/pay/bill_549c3572_1790579929540',
{
action: 'pay',
},
{ headers: { /* ... */ } }
)
response = http.get(
'http://localhost:8080/api/v1/payments/549c3572-e0b3-41f4-9036-9a49404056f2/status',
{ headers: { /* ... */ } }
)
response = http.get(
'http://localhost:8080/api/v1/orders/549c3572-e0b3-41f4-9036-9a49404056f2',
{ headers: { /* ... */ } }
)
})
// Automatically added sleep
sleep(1)
}
How: clean up the result
A raw har-to-k6 output is a starting point, not a script. Before it goes anywhere near this track's conventions:
- Strip the browser headers.
sec-ch-ua,User-Agent,Host,Refererand friends describe one browser, not your test. KeepContent-TypeandAuthorization. - Remove static and third-party calls. Anything for a stylesheet, image, font, analytics or tracking pixel — the playground itself has none of these on its API, but a UI-driven recording will.
- Replace hard-coded ids with correlation. The bill id and order id in the recording belong to that one session. Take
payUrlandorderIdfrom thePOST /ordersresponse instead, which is whatlib/flow.js'spayOnGatewayandwaitUntilPaiddo (see page 05). The same goes for pizza/size/crust ids:createOrderpicks them from the live/menuresponse. - Add
check()s. A recording only replays requests; it asserts nothing about the responses. - Add
nametags. Withouttags: { name: '...' }, every URL with a path parameter (/orders/{id}) shows up as a separate metric per id in the summary instead of grouped as one endpoint.
Do all of these and you end up with the sample's 03-order-flow.js: the same flow, with ids taken from each response, checks on every step and name tags. Run that, not the raw recording.
Tips
npxneeds network access to npm the first time — after that,har-to-k6is cached locally.- The recording says
localhost, the sample says127.0.0.1. The browser used the shop'slocalhostURLs. Swap inBASE_URLfromlib/config.jswhile cleaning up, so the script follows-e BASE_URL=...like the rest of the sample. - Don't trust a raw recording just because it runs green. Replayed as-is, the script above creates a new order but pays the recorded bill, so the new order stays UNPAID with 0% failed requests. Clean it up first, then check the result in the app (is the new order PAID?), not just
http_req_failed. - A HAR only records what a browser sends. Steps that redirect (like the mock gateway's
POST /pay/{billId}) may needredirects: 0restored by hand, the same waylib/flow.js'spayOnGatewaydoes — the browser follows the redirect, but a load script measuring the gateway hop should not.