Skip to main content

Recording

Table of Contents

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
  1. 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.
  2. Close the browser. recording.har is written when codegen exits.

Why I prefer it over DevTools:

  • --save-har-glob filters 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​

  1. Open DevTools → Network tab and make sure it is recording (red dot).
  2. Do the same flow in the browser.
  3. 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, Referer and friends describe one browser, not your test. Keep Content-Type and Authorization.
  • 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 payUrl and orderId from the POST /orders response instead, which is what lib/flow.js's payOnGateway and waitUntilPaid do (see page 05). The same goes for pizza/size/crust ids: createOrder picks them from the live /menu response.
  • Add check()s. A recording only replays requests; it asserts nothing about the responses.
  • Add name tags. Without tags: { 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​

  • npx needs network access to npm the first time — after that, har-to-k6 is cached locally.
  • The recording says localhost, the sample says 127.0.0.1. The browser used the shop's localhost URLs. Swap in BASE_URL from lib/config.js while 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 need redirects: 0 restored by hand, the same way lib/flow.js's payOnGateway does — the browser follows the redirect, but a load script measuring the gateway hop should not.