K6 browser response aka waitForResponse


How can I log browser_source during:

await page.goto(url, { waitUntil: 'load' }) ?

I see in the k6 logs:

ERRO[0003] Failed to load resource: the server responded with a status of 404 () browser_source=network line_number=0 source=browser stacktrace=“” url=“notalink://mysite.com/spa-static/image/logo.png”

browser_source=network is exactly what I need!
I want to add custom metrics for loading specific resources during page loading in browser.

I can’t use:

page.on('response', (response) => {
    if (response.url() === 'https://mysite.com/spa-static/image/logo.png') {
        console.error(`Response status for ${response.url()}: ${response.status()}`);


ERRO[0008] Uncaught (in promise) GoError: unknown page event: “response”, must be “console”

To provide rough compatibility with the Playwright API … and bla bla bla

It makes me really angry, because in Response from Browser-level APIs LIETERALLY nothing useful:

console.log("json", resp.json()):

ERRO[0006] Uncaught (in promise) GoError: unmarshalling response body to JSON: invalid character ‘<’ looking for beginning of value
running at notalink://github.com/grafana/xk6-browser/api.Response.JSON-fm (native)

in the remaining API’s also nothing valuable:

INFO[0006] request {} source=console
INFO[0006] statusText source=console
INFO[0006] securityDetails {“subject_name”:“notalink://mysite.com”,“issuer”:“R3”,“valid_from”:1697694733,“valid_to”:1705470732,“protocol”:“TLS 1.2”,“s_a_n_list”:[“*.mysite.com”,“notalink://mysite.com”]} source=console
INFO[0006] size {“headers”:214,“body”:3375} source=console
INFO[0006] status 200 source=console
INFO[0006] headerValue null source=console
INFO[0006] headers {“date”:“Thu, 07 Dec 2023 15:29:19 GMT”,“server”:“nginx”,“strict-transport-security”:“max-age=31536000; includeSubDomains; preload”,“vary”:“Accept-Encoding”,“content-encoding”:“gzip”,“content-type”:“text/html”} source=console
INFO[0006] headersArray [{“name”:“vary”,“value”:“Accept-Encoding”},{“name”:“content-encoding”,“value”:“gzip”},{“name”:“content-type”,“value”:“text/html”},{“name”:“date”,“value”:“Thu, 07 Dec 2023 15:29:19 GMT”},{“name”:“server”,“value”:“nginx”},{“name”:“strict-transport-security”,“value”:“max-age=31536000; includeSubDomains; preload”}] source=console
INFO[0006] headerValues [“”] source=console
INFO[0006] ok true source=console
INFO[0006] serverAddr {“ip_address”:“XX.XX.XXX.XXX”,“port”:XXX} source=console
INFO[0006] allHeaders {“server”:“nginx”,“strict-transport-security”:“max-age=31536000; includeSubDomains; preload”,“vary”:“Accept-Encoding”,“content-encoding”:“gzip”,“content-type”:“text/html”,“date”:“Thu, 07 Dec 2023 15:29:19 GMT”} source=console
INFO[0006] frame {} source=console
INFO[0006] body {} source=console
INFO[0006] url notalink://mysite.com/ source=console

If continue logging what was provided to us in the browser API - there are nothing suitable among the frankly non-working methods and empty objects.

I hope someone can tell me how to implement in k6 browser what is done in Playwright in this way:

const responsePromise = page.waitForResponse(response =>
  response.url() === 'https://example.com' && response.status() === 200

Hi @alexeybeleg,

Thanks for the comment and feature request, and apologies for the lateness in my reply.

I’d like to understand your use case so that we can prioritise the exact API that would benefit you the most. It looks like you want to check and record the responses of the “implicit” requests that the browser makes when navigating to the website under test. There are two APIs that you have mentioned that you would like to use:

  1. page.on('response')
  2. page.waitForResponse

With the first one, it is non-blocking and the response will be handled in the background as and when they arrive whilst allowing the test to carry on, although there’s no guarantee that the browser actually made the request.

With the second API, the test will block until the predicate returns true or the url matches, but you can be 100% sure that the browser made the request if the wait unblocks.

Could you explain why you might need to wait for the response before proceeding to the next step in the test? What do you want your test to do if the response is successful, and what would you want your test to do when the response is unsuccessful?

There is a work around to measure failed requests where failures are recorded when the status code is outside of the 200-399 range, which uses thresholds:

import { browser } from 'k6/experimental/browser';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
            type: 'chromium',
  thresholds: {
    'browser_http_req_failed{url:https://test.k6.io/static/css/site.css}': ['rate == 0'],

export default async function() {
  const context = browser.newContext();
  const page = context.newPage();

  await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });

In the output you should see something like:

     browser_http_req_failed............................: 0.00%  ✓ 0        ✗ 3  
     ✓ { url:https://test.k6.io/static/css/site.css }...: 0.00%  ✓ 0        ✗ 1

Which denotes that 1 request to https://test.k6.io/static/css/site.css was made and it did not fail.

Looking forward to your reply,

1 Like