Trouble with PUT Request: Works in Postman but 400 Error in k6

Dear Grafana Community,

I am quite a rookie to k6, and I’m encountering an issue with my test script while trying to make a PUT request to a website. Interestingly, the request works perfectly fine in Postman, returning a 200 status code, indicating success.

However, when I try to replicate the same request using k6, I consistently receive a 400 Bad Request error.

I’ve double-checked the script and made sure that all parameters and headers are correctly set, but I’m still unable to pinpoint the source of the problem. Here is the k6 script:

import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { parseHTML } from 'k6/html';
import { randomString } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';

let csrfToken;

export let options = {
  insecureSkipTLSVerify: true,
  noConnectionReuse: false,
  vus: 1,
  duration: '5s',
  };
  
  export default function () {
    group('Login', function(){
      // --- Get CSRF Token ---
        let loginPageResponse = http.get('https://login);
      // Parse HTML content using k6's html module
        let parsedHTML = parseHTML(loginPageResponse.body);
      // Extract CSRF token directly from the HTML response using k6's HTML parsing
        csrfToken = parsedHTML.find('[name="__RequestVerificationToken"]').attr('value');
      // --- Login ---
        let loginResponse = http.post(
          'https://login',
          {
            UserName: '****',
            Password: '****',
            __RequestVerificationToken: csrfToken,
          },
          {
            headers: {
                "Content-Type": 'application/x-www-form-urlencoded'
            }
          }
        );

        check(loginResponse, {
        'login ok': (r) => r.status === 200,
        });
        
        sleep(1);
    });
  
    group('Create', function(){
        let vocab = http.post(https://something/Create',
        {
            Original: 'test_' + randomString(3),
            Corrected: 'test_' + randomString(3),
            __RequestVerificationToken: csrfToken,
        },
        {
        headers: {
            "Content-Type": 'application/x-www-form-urlencoded'
          },
        }
       );
      
        console.log('Response:', JSON.stringify(vocab));
        console.log('Response Body:', vocab.body)
  
      check(vocab, {
        'Vocabulary created successfully': (r) => r.status === 200,
      });
    });
}

Has anyone else experienced a similar issue with k6 or have any suggestions on how to troubleshoot and resolve this discrepancy between Postman and k6?

Any insights or assistance would be greatly appreciated!

Thank you."

Hi @grubthomas1 , welcome to the community forum!

You can use k6 run --http-debug=full script.js to see all the requests and responses.

1 Like

hello @bandorko, thank you for your answer!

Tried it before, but unfortunately for me it not says anything worthwhile, it is incomprehensible:

INFO[0007] Response: {"remote_ip":"194.39.46.239","remote_port":443,
"url":"https://../Create","status":400,"status_text":"400 Bad Request",
"proto":"HTTP/1.1","headers":{"X-Powered-By":"ASP.NET",
"Date":"Wed, 07 Feb 2024 08:53:47 GMT","Server":"Microsoft-IIS/10.0"},
"cookies":{},"body":"","timings":
{"duration":59.8316,"blocked":0,"looking_up":0,"connecting":0,
"tls_handshaking":0,"sending":0,"waiting":8.4367,"receiving":51.3949},
"tls_version":"tls1.3","tls_cipher_suite":"TLS_AES_128_GCM_SHA256",
"ocsp":{"produced_at":0,"this_update":0,"next_update":0,"revoked_at":0,
"revocation_reason":"","status":"unknown"},"error":"","error_code":1400,
"request":{"method":"POST","url":"https://../Create","headers":
{"Content-Type":["application/x-www-form-urlencoded"],
"User-Agent":["k6/0.49.0 (https://k6.io/)"],
"Cookie":[".AspNetCore.Antiforgery.4W4comVb2hg=CfDJ8ACdl1L3ffhGv0NqUSTirBCN0iorApBtPJGh_AY59tGO4T3xqc4dqqsQ0qjQdi05asq0N7hYhFHR0H-lMoww7Kat0EUBPorzw_2qbrE53xYTlgupkWi7muxN0KELkq-IH9_RIcvEVaCAHrHklNyr_D8; .AspNet.SharedCookie=CfDJ8ACdl1L3ffhGv0NqUSTirBDGARpCAlOGrgU8u6j2gUNHJ1d5misc5Difw7Z4jAS5lA1AxIDv4i0FdAtdByYBZk2O76zV4mAW13Wph7e0PyBH73lvhxOPXATX5Xw7LvCIYznpWSgujRc5uEvLbRfWzzpFEB8LBC4FgHzxac37K_Y7UX3T1tQYmBBB9tFM5qUl2_wlw9O0PtVd8kTN593WdVaiOEysZseHEaZTEGwV7eQYNd_xT7sE2VlNN3yJw_SXHWqco6_oaWcNLjgycuMPWQSVWhSc2GrZyvWoGSHToQ72ObkaHEfeNHRAQIdKFTcDyGGMj8rXe_NO2anJk-rrn07E6VBlhMxfVPUrnqkF8xr56Q5DW8eQ7RSG2YVfWFOgTz7nBvpeXsNZwuwBAitl8NywY72QKEOJVfc6pr1E0cLBFPREhOI4pxfLacIz_p27AZC-k23Bh6cVXPV5e9LXgZyp2-BTGk45NmzAxXjy2o2r6_sYO8kWZoQPfg16bAnKKlCt3YR_6V8eOtpKTneRQl4cTAWyp2cJ-8TUAN9I76fK0FCikhC4oNJmylO1ns5nAwKnWaeKNXm6OIncVYHrMy8iUWoaob-HaGPOMsp--HedccFPicIMbLHUJz35WFDUUo8ifn2TiIz9oBEjD8An4hGF-obJUKbyGs7nr7W0AkOnPIKKdiJuwlpAJJNzedjNYKqceOHd-Pb9FiVPBS9hsS_cnvhBwoVC1RJnvQYxXB4BwUi1dWIBNHC84oAbIxoAWRSc-MoWYCPjy7zApE7GmEEPBo_vCaR-Vp7vJm5Vs_xsnAwgZi6mqR3RZB3wp9LlMK7_i6k-YAy__nHx7Hvdqo4EK8menHsyXyDY5WPSKZqJf9guvtb5sDTSfsE84gYQ72FNYHHq2ngRuE1BodVEyA5zeOa7JR9qguERi8ctToZs3WAEDqQDPYVTUmuy--hPTDZ7hi2jkardQwUKRHSr2EttC8puirEnws1ftIu994fUlAg4Rq06sNsBF4F0riJ2xA"]},"body":"data=map%5BCorrected%3Atest_hft+Original%3Atest_qqu+__RequestVerificationToken%3ACfDJ8ACdl1L3ffhGv0NqUSTirBAc48tSOS14e_dQsR6EFf2NpZwvANFbNBkzmZpfTPATM5EoQTJxxC4I-OfIOsgiHCKcSERDsqPuby-hKEFCtRZku7aK47sk1ajcCKMo4bXQ36gRkhYzsr0M-Bi4peLLZAE%5D",
"cookies":{".AspNetCore.Antiforgery.4W4comVb2hg":[{"name":".AspNetCore.Antiforgery.4W4comVb2hg","value":"CfDJ8ACdl1L3ffhGv0NqUSTirBCN0iorApBtPJGh_AY59tGO4T3xqc4dqqsQ0qjQdi05asq0N7hYhFHR0H-lMoww7Kat0EUBPorzw_2qbrE53xYTlgupkWi7muxN0KELkq-IH9_RIcvEVaCAHrHklNyr_D8","replace":false}],".AspNet.SharedCookie":[{"name":".AspNet.SharedCookie","value":"CfDJ8ACdl1L3ffhGv0NqUSTirBDGARpCAlOGrgU8u6j2gUNHJ1d5misc5Difw7Z4jAS5lA1AxIDv4i0FdAtdByYBZk2O76zV4mAW13Wph7e0PyBH73lvhxOPXATX5Xw7LvCIYznpWSgujRc5uEvLbRfWzzpFEB8LBC4FgHzxac37K_Y7UX3T1tQYmBBB9tFM5qUl2_wlw9O0PtVd8kTN593WdVaiOEysZseHEaZTEGwV7eQYNd_xT7sE2VlNN3yJw_SXHWqco6_oaWcNLjgycuMPWQSVWhSc2GrZyvWoGSHToQ72ObkaHEfeNHRAQIdKFTcDyGGMj8rXe_NO2anJk-rrn07E6VBlhMxfVPUrnqkF8xr56Q5DW8eQ7RSG2YVfWFOgTz7nBvpeXsNZwuwBAitl8NywY72QKEOJVfc6pr1E0cLBFPREhOI4pxfLacIz_p27AZC-k23Bh6cVXPV5e9LXgZyp2-BTGk45NmzAxXjy2o2r6_sYO8kWZoQPfg16bAnKKlCt3YR_6V8eOtpKTneRQl4cTAWyp2cJ-8TUAN9I76fK0FCikhC4oNJmylO1ns5nAwKnWaeKNXm6OIncVYHrMy8iUWoaob-HaGPOMsp--HedccFPicIMbLHUJz35WFDUUo8ifn2TiIz9oBEjD8An4hGF-obJUKbyGs7nr7W0AkOnPIKKdiJuwlpAJJNzedjNYKqceOHd-Pb9FiVPBS9hsS_cnvhBwoVC1RJnvQYxXB4BwUi1dWIBNHC84oAbIxoAWRSc-MoWYCPjy7zApE7GmEEPBo_vCaR-Vp7vJm5Vs_xsnAwgZi6mqR3RZB3wp9LlMK7_i6k-YAy__nHx7Hvdqo4EK8menHsyXyDY5WPSKZqJf9guvtb5sDTSfsE84gYQ72FNYHHq2ngRuE1BodVEyA5zeOa7JR9qguERi8ctToZs3WAEDqQDPYVTUmuy--hPTDZ7hi2jkardQwUKRHSr2EttC8puirEnws1ftIu994fUlAg4Rq06sNsBF4F0riJ2xA",
"replace":false}]}}}```

–http-debug=full should print the requests in plain text not in json, I think this is the output of the JSON.stringify(vocab) line.

Are you sure, that the post call looks like as in your opening comment? Because what looks wierd to me is the request body.

"body":"data=map%5BCorrected%3Atest_hft+Original%3Atest_qqu+__RequestVerificationToken%3ACfDJ8ACdl1L3ffhGv0NqUSTirBAc48tSOS14e_dQsR6EFf2NpZwvANFbNBkzmZpfTPATM5EoQTJxxC4I-OfIOsgiHCKcSERDsqPuby-hKEFCtRZku7aK47sk1ajcCKMo4bXQ36gRkhYzsr0M-Bi4peLLZAE%5D"

There should be no data=map%5B in it

Missing single quote here? http.post('https://

Also, in the POST payload/body, did you try stringifying that parameter?

Sorry, I made some mistake when I answered your questions.

My POST Request looks like this:

    group('CreateVocab', function(){
        let vocab = http.post('https://.../Create',
        {
            Original: 'test',
            Corrected: 'test1',
            __RequestVerificationToken: csrfToken,
        },
        {headers: {
            "Content-Type": 'application/x-www-form-urlencoded'
          },
        });
  
      check(vocab, {
        'Vocabulary created successfully': (r) => r.status === 200,
      });
    });
}

Response with --http-debug=“full”

INFO[0007] Request:
POST /.../Create HTTP/1.1
Host: xy.xy
User-Agent: k6/0.49.0 (https://k6.io/)
Content-Length: 212
Content-Type: application/x-www-form-urlencoded
Cookie: .AspNetCore.Antiforgery.4W4comVb2hg=CfDJ8MnhrUUph4tBmYybU57MueP_zuRcs_wdib8rCszAealetb-BzBGhnaJyMVw3w94CMUNXlOr_6_mogMcTEVxETiscxTqv0QcJeet24SyfgUXp1b-ZIlGfWgsWyd822TK3NgzVXsyjzyjsBuMRTRZ_gqE; .AspNet.SharedCookie=CfDJ8MnhrUUph4tBmYybU57MueN_D5O-PxC0FSM56RaWeQSga98YCuAQpsN-37_r2SeW8qUF0e2x9csVplXmad3qbxE8E8v2JybBIq55di9qWjF7iCUe2YY46ox4R8g34jv9e7AJzlKc5blq_iOFO-eyHkpKZrOzP3rhlbE-FXELA9NqxUqHIhs0Ak1SKrVaTybYfq5FHDosvcbD1flfr76gE6SglUYvQ3fMavaYyWMFvJyeyszQrTaFyBb_mO_B1dPBNAMqu8cWzuvWInrw2YgDv7gWj5mJIiJWZ8IoPaVSi3tzsykm7prutMurX9Mj-lP89jL9ujb0ZmV5hwHf5hp_wmfeOnaIWlyyd0QDJKfVNsiVhbhVsY7IEAc_b_1Az4HaV9EuccNZXw0UjvAyz_JjDmbWrDZKzGoLjxasRhtYntsYF-fgf-wLKQwAfLzza6Ufj_rXqQqjCMUK9BSZkC7PunvOqb45jSnoeGNrxK_B8b1_iERrkVP3qUKM8yxZaGyxWBUcMxf3CHTEpsBlutxLgkA_Ir9YfaGTAgMJPjFJ_Ctwx0tnRA7Ugu1WBo1WXy_Xq38ZbLwut4yCnOeNy7PEFhS9TdhDZUlTBWcG9BCzbF22wn7C_B-7C0VE9o7J163Znp85NMztN5eb8rK-PKnVJY3KUOQyy-MpyfhY-M--_1nGUI6Wz3dNSL5EdIxzQTIcW8szEjM0tBlgrtwBB6XODp00kpNMHEB-woiK1q6YEyPlnolcNwJ-JULy3iGGqcjPPPxj3X_dfrkCCjpJasr9rdSnI-otEagCM_kJl6oCa8gmtzAZL-oZyJEUsZ0ZWM2d2j5qyUbmqu5DgQY0MDqldBNlYEXR0GEA4DnCvE1nJWrfbtQWx-aSCXO_6eOE2ls0htrwn82cc28b948zSb8MQOSk9xzvpBiAX7AwIfjMv9jAi6XTreTZDcaRz6jmF_-fbtOB76Cmsm8hGM2xHt-eznrxgrnEXQvSg5nNXzdpS0dgUCsVwYmMgcKKD95O0e0_1w
Accept-Encoding: gzip

Corrected=test1&Original=test&__RequestVerificationToken=CfDJ8MnhrUUph4tBmYybU57MueNGRAC2dDI6BYNO0zJIGNGbpXXBVNAUETxbrzrrAopY8obLUFWWT6KUovJ9qP2mDIVyNKiIK24T7OVn8lbgvpb6dhzkpN8_--0U5uO3jv7mRV1eAzks_88YAj0yfYZeLqE  group="::CreateVocab" iter=2 request_id=cf6dbb79-0c7e-4650-5ad9-132cbaa54f61 scenario=default source=http-debug vu=1
INFO[0007] Response:
HTTP/1.1 400 Bad Request
Transfer-Encoding: chunked
Date: Thu, 08 Feb 2024 12:40:01 GMT
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET```

Any idea would be greatly appreciated, thank you for your contribution!

Try this?

{
    "Original": "test",
    "Corrected": "test1",
    "__RequestVerificationToken": csrfToken
}

Are you positive that is the correct Content-Type header for this request? Do you need Accept?

You could try recording the original request with the k6 browser recorder and capturing it.

Hi!

Thank you for your answers, finally I figured the solution out.

Essentially the “Extract CSRF token directly from the HTML response using k6’s HTML parsing” was part of the first “Group” only so the second cannot use that info.
I copied that code to the second group as well, and now finally is working properly.

The k6 browser recorder was a big help in this issue.

Cheers!

1 Like