TypeError: Cannot read property 'headers' of undefined source=console

Hi! Trying to adapt this example to our project:

But for some reason some parts of it doesn’t work as expected…
First of all I can’t use template literals (Template literals (Template strings) - JavaScript | MDN) in such blocks:

  duration: '1s',
  vus: 1,
  iterations: 1,
  thresholds: {
    endpoint_data_sent: ['count < 2048'],
    `endpoint_data_sent{url:${__ENV.BASE_URL}/myEndpoint}`: ['count < 1024'],
    `endpoint_data_recv{url:${__ENV.BASE_URL}/myEndpoint}}`: ['count < 2048']
  },
  tags: {
    project_name: 'MyProject',
  }
};

for some reason…

And even if I hardcode all values in
thresholds
block, then I got such errors:

TypeError: Cannot read property 'headers' of undefined  source=console

And in the summary:


     █ 01. methodName1

       ✗ Exception raised "TypeError: Cannot read property 'headers' of undefined"
        ↳  0% — ✓ 0 / ✗ 1101

     project_name_main_loop_iters_counter...: 1101   1081.475049/s
     ...
     other metrics...

Though I can see that this particular transaction actually returns data:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json
Matched-Stub-Id: c27351fd-f162-4e80-a4ea-1a17003165fc
Server: Jetty(9.2.z-SNAPSHOT)

4a
{"result":{"status":"OK","reasonCode":0,"message":"Operation Successful"}}
0

  group="::01. methodName1" iter=1098 project_name=MyProject request_id=add5067e-fd62-4ff0-6e68-96a95771cf87 scenario=default source=http-debug vu=1

By the way, I’m testing via wiremock stubs if it actually matters.
What am I doing wrong with this test?

Hi @Crosby , welcome to the community forum :tada:

From the looks of it w/e you provide doesn’t have headers and given the code you’ve linked that can either be the response object or the response.request, given that both are unlikely as k6 will always populate this unless there is another exception I would guess that is what is happening.

Can you please provide a full working script which blows up as this is likely some minor misstyping somewhere. You can use https://httpbin.test.k6.io for the endpoints instead of your real endpoint.

Full script body:

import { describe } from 'https://jslib.k6.io/expect/0.0.5/index.js';
import { Httpx, Request, Get, Post } from 'https://jslib.k6.io/httpx/0.0.4/index.js';
import { randomIntBetween, randomItem } from "https://jslib.k6.io/k6-utils/1.1.0/index.js";
import { Counter } from 'k6/metrics';
import { SharedArray } from "k6/data";
import http from "k6/http";

// So we could see for what project all these metrics actually are:
let myCounter = new Counter('project_name_main_loop_iters_counter');

export let epDataSent = new Counter('endpoint_data_sent');
export let epDataRecv = new Counter('endpoint_data_recv');

export let options = {
  duration: '1s',
  vus: 1,
  iterations: 1,
  thresholds: {
    checks: [{threshold: 'rate == 1.00', abortOnFail: true}],

    // We can setup thresholds on these custom metrics, "count" means bytes in this case.
    endpoint_data_sent: ['count < 2048'],
    'endpoint_data_sent{url:https://httpbin.test.k6.io/post}': ['count < 1024'],
    'endpoint_data_recv{url:https://httpbin.test.k6.io/post}}': ['count < 2048'],
    'endpoint_data_sent{url:https://httpbin.test.k6.io/get}': ['count < 1024'],
    'endpoint_data_recv{url:https://httpbin.test.k6.io/get}': ['count < 2048']
  },
  tags: {
    project_name: 'MyProject',
  }
};

function sizeOfHeaders(hdrs) {
  return Object.keys(hdrs).reduce(
    (sum, key) => sum + key.length + hdrs[key].length,
    0,
  );
}

function trackDataMetricsPerURL(res) {
  // Add data points for sent and received data
  epDataSent.add(sizeOfHeaders(res.request.headers) + res.request.body.length, {
    url: res.url,
  });

  epDataRecv.add(sizeOfHeaders(res.headers) + res.body.length, {
    url: res.url,
  });
}

export default function testSuite() {

  myCounter.add(1);

  describe('01. methodName1', (t) => {
    let postmethodName1 = {
      method: "POST",
      url: `${__ENV.BASE_URL}/post`,
      body: JSON.stringify({
          "key1": "value1",
          "key2": "value2",
          "key3": "value3",
          "key4": "value4"
      }),
      params: {headers: {"Content-Type": "application/json"}}
    };

    let responses = http.batch([postmethodName1], {
      tags: {name: 'methodName1'},
    });
    trackDataMetricsPerURL(responses);
    console.log(`methodName1: responses: ${responses}`)

    responses.forEach(response => {
      t.expect(response.status).as("response status").toEqual(200)
        .and(response).toHaveValidJson()
    });
    
  })

  &&

  describe(`02. AccountmethodName2`, (t) => {

    let active_user = testdata[0]
    console.log(`02. AccountmethodName2: active_user: ${active_user["param"]}`)

    let getAccountmethodName2 = {
      method: "GET",
      url: `${__ENV.BASE_URL}/get?param=param-pam-pam`,
      params: {headers: {"Content-Type": "application/json"}}
    };

    let responses = http.batch([getAccountmethodName2], 
    {
      tags: {my_tag: "AccountmethodName2"},
    });
    trackDataMetricsPerURL(responses);

    responses.forEach(response => {
      t.expect(response.status).as("response status").toEqual(200)
        .and(response).toHaveValidJson()
    })
  })
}

This is how I run it:

export VUs=1; \
export DURATION=1s; \
k6 run -e BASE_URL="https://httpbin.test.k6.io" \
   --vus $VUs \
   --duration $DURATION \
   --verbose \
   --http-debug="full" \
   --summary-export=/Users/user/projects/qa-qc-t/perf-testing/k6.io/scenario-test.summary_VUs-$VUs_duration-$DURATION.json \
   --out statsd \
   --out json=/Users/user/projects/qa-qc-t/perf-testing/k6.io/scenario-test.report_VUs-$VUs_duration-$DURATION.json \
   --out csv=/Users/user/projects/qa-qc-t/perf-testing/k6.io/scenario-test.report_VUs-$VUs_duration-$DURATION.csv \
   /Users/user/projects/qa-qc-t/perf-testing/k6.io/scenario-test.js

And I still get this error:

TypeError: Cannot read property 'headers' of undefined

This is the response logs:

INFO[0003] Request:
POST /post HTTP/1.1
Host: httpbin.test.k6.io
User-Agent: k6/0.34.1 (https://k6.io/)
Content-Length: 65
Content-Type: application/json
Accept-Encoding: gzip

{"key1":"value1","key2":"value2","key3":"value3","key4":"value4"}  group="::01. methodName1" iter=0 project_name=MyProject request_id=b0e3fc2b-e6c2-4c62-46e1-1f058466942a scenario=default source=http-debug vu=1
...
INFO[0008] Response:
HTTP/2.0 200 OK
Content-Length: 568
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Tue, 05 Oct 2021 10:38:17 GMT
Server: gunicorn/19.9.0

{
  "args": {}, 
  "data": "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\",\"key4\":\"value4\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Content-Length": "65", 
    "Content-Type": "application/json", 
    "Host": "httpbin.test.k6.io", 
    "User-Agent": "k6/0.34.1 (https://k6.io/)", 
    "X-Amzn-Trace-Id": "Root=1-615c2b19-46522bee378352147d975ebd"
  }, 
  "json": {
    "key1": "value1", 
    "key2": "value2", 
    "key3": "value3", 
    "key4": "value4"
  }, 
  "origin": "95.57.108.193", 
  "url": "https://httpbin.test.k6.io/post"
}
  group="::01. methodName1" iter=0 project_name=MyProject request_id=b0e3fc2b-e6c2-4c62-46e1-1f058466942a scenario=default source=http-debug vu=1

Cannot wrap my head around with what am I actually doing wrong here…

http.batch returns an array of reponses while the trackDataMetricsPerURL takes 1 response. If you change the call to trackDataMetricsPerURL(responses[0]); as you have a single request it will work.
Alternatively, you can iterate over the responses or just not use http.batch also apparently jslib’s expect is eating up exceptions cc @pawel .

1 Like

Thank you so much! I can’t believe that I’ve completely forgot about array being returned!

ERRO[0000] SyntaxError: file:///Users/user/projects/MyProject/loadtests/minimalBaseScenario.js: Unexpected token (42:4)
  40 |     // If we want to only consider data points for a particular URL/endpoint we can filter by URL:
  41 |     // TODO: change URLs to project-related:
> 42 |     `endpoint_data_sent{url:${__ENV.BASE_URL}/myEndpoint}`: ['count < 1024'],
     |     ^
 at <internal/k6/compiler/lib/babel.min.js>:2:28542(109)

Could you, please, help with this bit? It is not possible to use template literals as keys in options.thresholds ?

Could you, please, help with this bit? It is not possible to use template literals as keys in options.thresholds ?

No, but you can’t just use a literal template as an object key, which is what you are doing here basically. This is as it isn’t just a simple value but something that needs to be computed, this is supported and is called computed property names and as the link says you just need to put [] around the whole key.

1 Like