GithubHelp home page GithubHelp logo

grafana / har-to-k6 Goto Github PK

View Code? Open in Web Editor NEW
117.0 126.0 31.0 4.91 MB

JSON config representation of K6 script

License: Apache License 2.0

JavaScript 99.96% Dockerfile 0.04%
har k6-converter

har-to-k6's Introduction

har-to-k6 cover image

har-to-k6

Converts LI-HAR and HAR to K6 script.

GitHub Actions Status NPM Version NPM Weekly Downloads DockerHub



Content

Installation

Local Installation (recommended)

While possible to install globally, we recommend that you, if possible, add the converter to the node_modules of your test project using:

$ npm install --save har-to-k6

Note that this will require you to run the converter with npx har-to-k6 your-har-file or, if you are using an older version of npm, ./node_modules/.bin/har-to-k6 your-har-file.

Global Installation

$ npm install --global har-to-k6

Docker

$ docker pull grafana/har-to-k6:latest

Usage

CLI Usage

Npx

$ npx har-to-k6 archive.har -o my-k6-script.js

From node_modules

$ ./node_modules/.bin/har-to-k6 archive.har -o my-k6-script.js

Global

$ har-to-k6 archive.har -o my-k6-script.js

Docker

$ docker run grafana/har-to-k6:latest archive.har > my-k6-script.js

Programmatic Usage

Converting

const fs = require("fs");
const { liHARToK6Script } = require("har-to-k6");

async function run () {
  const archive = readArchive();
  const { main } = await liHARToK6Script(archive);
  fs.writeFileSync("./load-test.js", main);
}

Validating

Use validate() to run validation alone. Returns without error for a valid archive. Throws InvalidArchiveError for validation failure.

const { InvalidArchiveError, validate } = require("har-to-k6");

const archive = readArchive();
try {
  validate(archive);
} catch (error) {
  if (error instanceof InvalidArchiveError) {
    // Handle invalid archive
  } else {
    throw error;
  }
}

Browser Usage

har-to-k6 can be ran in the browser. This exposes the standard API under harToK6.

Importing as ES module

import { liHARToK6Script } from "har-to-k6";

CommonJS style

const { liHARToK6Script } = require("har-to-k6");

Using a <script> tag

Load standalone.js into your HTML page:

<html>
  <head>
    <title>HAR Converter</title>
    <script src="standalone.js"></script>
    <script src="index.js"></script>
  </head>
</html>

Example

The API is available:

async function run () {
    const archive = readArchive();
    harToK6.validate(archive);
    const { main } = await harToK6.liHARToK6Script(archive);
    displayResult(main);
}

Specifications

Credits

Thanks to bookmoons for creating this tool ๐ŸŽ‰

har-to-k6's People

Contributors

allansson avatar bookmoons avatar dependabot[bot] avatar e-fisher avatar edvinasdaugirdas avatar legander avatar mbocquet avatar robingustafsson avatar simskij avatar sniku avatar sumimakito avatar trevorah avatar vkarhaltsev avatar w1kman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

har-to-k6's Issues

Removal of jsonpath as direct dependency

Removal of jsonpath as direct dependency

Configurations utilizing VariableType JSONPath and CheckType JSONPath currently rely on jsonpath which is being provided by the compatibility layer
import { jsonpath } from "./compat.js".

There is a proposal to add this type of functionality to the K6 lib directly, making it accessible like
import { jsonpath } from "k6/encoding" (hypothetical example).
When jsonpath lands in K6 it can then be dropped as a direct dependency.

Error with built standalone.js

The following error is seen in the browser console when trying to use the standalone.js in website.

standalone.js:11 Uncaught (in promise) ReferenceError: regeneratorRuntime is not defined
    at Object.d (standalone.js:11:356755)
    at e.exports [as liHARToK6Script] (standalone.js:11:357262)
    at run ((index):48:36)
    at (index):52:3

This is the code used to create the website

<!DOCTYPE html>
<html lang="en">
<head>
  <title>k6 test</title>
</head>
<body>
    Check the console.
</body>

<script src="standalone.js"></script>
<script>
  async function run () {

    const config = {
      log: {
        entries: [
          {
            request: {
              method: "POST",
              url: "http://test.loadimpact.com/login",
              headers: [
                {
                  "name": "Content-Type",
                  "value": "application/json"
                }
              ],
              postData: {
                "mimeType": "application/json",
                "text": "{\"user\":\"admin\",\"password\":\"123\"}"
              },
            },
          }
        ],
      }
    };

    const { main } = await harToK6.liHARToK6Script(config);
    console.log(main);
  }

  run()
</script>

</html>

I tried both the pre-built standalone.js in the repo and building one myself with the webpack config in the repo.

Conflict on post requests with both params and text

How to reproduce:
Run

fetch('http://example.com', {
      method: 'POST',
      headers: new Headers({
               'Content-Type': 'application/x-www-form-urlencoded'
               }),
		body: 'foo0=bar0&foo1=bar1'
    })
    .then(res => res.json())
    .then(console.log)

in Chrome v78.0.3904.97 DevTools Console and Save Content as HAR the Network Tab.
Trying to convert it using the har-to-k6 converter results in the error
Post data conflict (0): specify 1 of params or text

Problem:
Now, from how I understand the HAR 1.2 specification, a HAR postData entry should only contain either a non-empty params or text. And according to https://github.com/loadimpact/har-to-k6/blob/master/src/validate/postData.js#L54, the converter implemented that specification correctly.

However, the Firefox and Chrome DevTools both export a POST request with an application/x-www-form-urlencoded encoded request body with parameters into a HAR entry with both params and text:

...
"postData": {
      "mimeType": "application/x-www-form-urlencoded",
      "text": "foo0=bar0&foo1=bar1",
      "params": [{
            "name": "foo0",
            "value": "bar0"
      },{
            "name": "foo1",
            "value": "bar1"
      }]
}
...

Since POST request bodies with parameters are fairly common, and Firefox and Chrome DevTools are probably fairly common tools to create HAR files, what's the appropriate way to use this converter on HAR files with POST bodies with parameters?

(Worst case, I'll have to prefix this converter with a script that sets the text element to an empty string for postData.)

Docker har-to-k6 not working (ENOENT: no such file or directory)

The har file exists, but the docker image is always returning a ENOENT error (e.g. using docker run grafana/har-to-k6:latest new-recording_19332.har > my-k6-script.js).

docker run grafana/har-to-k6:latest new-recording_19332.har > my-k6-script.js
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
Converting 'new-recording_19332.har'
Error reading archive file:
ENOENT: no such file or directory, open 'new-recording_19332.har', at path: new-recording_19332.har

OS Info:

ProductName:		macOS
ProductVersion:		14.1.1
BuildVersion:		23B81
Docker version 24.0.6, build ed223bc

How to retrieve the return of the setup function

Hello

I'm working on a script to automate the generation of a JS script from HAR, the data injection and the JS script execution.

I want my JS script to contain a main function and a setup function such as:

export function setup() {
  let vars = {};
  // read a csv file that contains my dataset
  // update vars with data
  return vars;
}

export default function main(vars) {
  const randomIndex = ...; // computed from vars and the strategy
  const username = vars[randomIndex].username;
 
  group(...)
}

My current approach is to edit the JS file with another script that adds manually the wanted lines.

Is there a way to update the HAR in order to get this structure by running har-to-k6 ?

(If it's not implemented yet, just give me the recipe, I'll try a fork and submit a PR if the result is interesting)

"Invalid header field" errors when converting exports from Chrome

First off, thanks a bunch for developing this amazing tool to convert HAR to k6!

I just tried using it on a HAR archive exported from Chrome and I got tons of errors related the invalid header field names. It seems as though the conversion is not trimming the header fields that are prefixed with a colon, e.g.,

:method
:path
:authority
:scheme

WARN[0001] Request Failed  error="Get https://furbaby.briantully.com/: net/http: invalid header field name \":method\""

WARN[0001] Request Failed  error="Get https://furbaby.briantully.com/: net/http: invalid header field name \":path\""

WARN[0001] Request Failed  error="Get https://furbaby.briantully.com/: net/http: invalid header field name \":authority\""

WARN[0006] Request Failed  error="Get https://furbaby.briantully.com/: net/http: invalid header field name \":scheme\""

I was able to get the k6 script to work by manually editing the code and removing the ":" from the beginning of the header fields, but I was wondering if this is a known issue and/or if it is unique to Chrome HAR exports.

Thanks again!
Brian

POST crashes server with "unknown transfer-encoding"

Using har-to-k6 and k6 results in one of the POST's crashing the server with an "unknown transfer-encoding" error. The HAR file does not contain any Content-Transfer-Encoding headers in the postData.text field yet har-to-k6 includes this header on all fields in the multipart/form-data body.

--enable-status-code-checks

We've been using k6 convert with the --only and the --enable-status-code-checks options. We now have a HAR where k6 convert does not convert a large portion of the HAR file. har-to-k6 does, but the lack of --only and --enable-status-code-checks options means we can't use it either. I believe --only is #39 so also please add --enable-status-code-checks.

PostData results with HTTP Status 415 โ€“ Unsupported Media Type

When the HAR file is converted to K6 JS and run, the response is HTTP 415.

HAR File

{
        "startedDateTime": "2023-04-28T18:56:21.054Z",
        "time": 237.21100005786866,
        "timings": {
          "blocked": 138.46699999260903,
          "dns": -1,
          "ssl": -1,
          "connect": -1,
          "send": 0.07799999999999999,
          "wait": 95.88000003886968,
          "receive": 2.7860000263899565
        },
        "request": {
          "method": "POST",
          "url": "http://127.0.0.1:9080/smc-web/getCaseCategorySearchPrefOption.do",
          "httpVersion": "http/1.1",
          "headers": [
            {
              "name": "Accept",
              "value": "text/html, */*; q=0.01"
            },
            {
              "name": "Accept-Encoding",
              "value": "gzip, deflate, br"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.9"
            },
            {
              "name": "Cache-Control",
              "value": "no-cache"
            },
            {
              "name": "Content-Length",
              "value": "58"
            },
            {
              "name": "Content-Type",
              "value": "application/json; charset=UTF-8"
            },
            {
              "name": "Cookie",
              "value": "JSESSIONID=44B0A5B8A4A18DD157B12BCF3EAF26C4; crowd.token_key=T6rMIuipjGwpg6Oqsv42lAAAAAAAAIACc21jeXBkZXY"
            },
            {
              "name": "Host",
              "value": "127.0.0.1:9080"
            },
            {
              "name": "Origin",
              "value": "http://127.0.0.1:9080"
            },
            {
              "name": "Pragma",
              "value": "no-cache"
            },
            {
              "name": "Proxy-Connection",
              "value": "keep-alive"
            },
            {
              "name": "Referer",
              "value": "http://127.0.0.1:9080/smc-web/"
            },
            {
              "name": "Sec-Fetch-Dest",
              "value": "empty"
            },
            {
              "name": "Sec-Fetch-Mode",
              "value": "cors"
            },
            {
              "name": "Sec-Fetch-Site",
              "value": "same-origin"
            },
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.58"
            },
            {
              "name": "X-Cypress-Is-XHR-Or-Fetch",
              "value": "xhr"
            },
            {
              "name": "X-Requested-With",
              "value": "XMLHttpRequest"
            },
            {
              "name": "sec-ch-ua",
              "value": "\"Chromium\";v=\"112\", \"Microsoft Edge\";v=\"112\", \"Not:A-Brand\";v=\"99\""
            },
            {
              "name": "sec-ch-ua-mobile",
              "value": "?0"
            },
            {
              "name": "sec-ch-ua-platform",
              "value": "\"Windows\""
            }
          ],
          "queryString": [],
          "cookies": [
            {
              "name": "jSESSIONID",
              "value": "44B0A5B8A4A18DD157B12BCF3EAF26C4",
              "httpOnly": false,
              "secure": false
            },
            {
              "name": "crowd.token_key",
              "value": "T6rMIuipjGwpg6Oqsv42lAAAAAAAAIACc21jeXBkZXY",
              "httpOnly": false,
              "secure": false
            }
          ],
          "headersSize": 0,
          "bodySize": 58,
          "postData": {
            "mimeType": "application/json; charset=UTF-8",
            "text": "{\"globalDataSource\":\"\",\"cupsId\":\"caseCategory.searchPref\"}",
            "params": []
          }
        },
        "response": {
          "status": 200,
          "statusText": "OK",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Connection",
              "value": "keep-alive"
            },
            {
              "name": "Content-Encoding",
              "value": "gzip"
            },
            {
              "name": "Keep-Alive",
              "value": "timeout=5"
            },
            {
              "name": "Transfer-Encoding",
              "value": "chunked"
            },
            {
              "name": "Vary",
              "value": "Accept-Encoding"
            },
            {
              "name": "access-control-allow-origin",
              "value": "*"
            },
            {
              "name": "cache-control",
              "value": "no-cache, no-store"
            },
            {
              "name": "content-disposition",
              "value": "inline;filename=f.txt"
            },
            {
              "name": "content-type",
              "value": "application/json;charset=UTF-8"
            },
            {
              "name": "date",
              "value": "Fri, 28 Apr 2023 18:56:20 GMT"
            },
            {
              "name": "expires",
              "value": "Thu, 01 Jan 1970 00:00:00 GMT"
            },
            {
              "name": "pragma",
              "value": "no-cache"
            },
            {
              "name": "x-content-type-options",
              "value": "nosniff"
            },
            {
              "name": "x-xss-protection",
              "value": "1; mode=block"
            }
          ],
          "cookies": [],
          "content": {
            "size": 23,
            "mimeType": "application/json",
            "compression": -36
          },
          "redirectURL": "",
          "headersSize": 459,
          "bodySize": 59,
          "_transferSize": 518
        },
        "cache": {},
        "serverIPAddress": "127.0.0.1",
        "_priority": "VeryHigh",
        "_resourceType": "XHR",
        "_webSocketMessages": [],
        "_eventSourceMessages": [],
        "connection": "329"
      },

JS

 response = http.post(
    'http://127.0.0.1:9080/smc-web/getCaseCategorySearchPrefOption.do',
    '{"globalDataSource":"","cupsId":"caseCategory.searchPref"}',
    {
      headers: {
        Accept: 'text/html, */*; q=0.01',
        'Accept-Encoding': 'gzip, deflate, br',
        'Accept-Language': 'en-US,en;q=0.9',
        'Cache-Control': 'no-cache',
        'Content-Type': 'application/json; charset=UTF-8',
        Cookie:
          'JSESSIONID=44B0A5B8A4A18DD157B12BCF3EAF26C4; crowd.token_key=T6rMIuipjGwpg6Oqsv42lAAAAAAAAIACc21jeXBkZXY',
        Host: '127.0.0.1:9080',
        Origin: 'http://127.0.0.1:9080',
        Pragma: 'no-cache',
        'Proxy-Connection': 'keep-alive',
        Referer: 'http://127.0.0.1:9080/smc-web/',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'same-origin',
        'User-Agent':
          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.58',
        'X-Cypress-Is-XHR-Or-Fetch': 'xhr',
        'X-Requested-With': 'XMLHttpRequest',
        'sec-ch-ua': '"Chromium";v="112", "Microsoft Edge";v="112", "Not:A-Brand";v="99"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
      },
    }
  )

Response

{
  "remote_ip": "127.0.0.1",
  "remote_port": 9080,
  "url": "http://127.0.0.1:9080/smc-web/getCaseCategorySearchPrefOption.do",
  "status": 415,
  "status_text": "415 ",
  "proto": "HTTP/1.1",
  "headers": {
    "Content-Type": "text/html;charset=utf-8",
    "Content-Language": "en",
    "Pragma": "no-cache",
    "X-Content-Type-Options": "nosniff",
    "Access-Control-Allow-Origin": "*",
    "Date": "Mon, 01 May 2023 16:32:26 GMT",
    "Cache-Control": "no-cache, no-store",
    "X-Xss-Protection": "1; mode=block",
    "Content-Length": "1087",
    "Expires": "Thu, 01 Jan 1970 00:00:00 GMT",
    "X-Frame-Options": "DENY"
  },
  "cookies": {},
  "body": "<!doctype html><html lang=\"en\"><head><title>HTTP Status 415 โ€“ Unsupported Media Type</title><style type=\"text/css\">h1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} h2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} h3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} body {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} b {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} p {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;} a {color:black;} a.name {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 415 โ€“ Unsupported Media Type</h1><hr class=\"line\" /><p><b>Type</b> Status Report</p><p><b>Description</b> The origin server is refusing to service the request because the payload is in a format not supported by this method on the target resource.</p><hr class=\"line\" /><h3>Apache Tomcat/9.0.12</h3></body></html>",
  "timings": {
      "duration": 15.0709,
      "blocked": 0,
      "looking_up": 0,
      "connecting": 0,
      "tls_handshaking": 0,
      "sending": 0,
      "waiting": 15.0709,
      "receiving": 0
    },
  "tls_version": "",
  "tls_cipher_suite": "",
  "ocsp": {
      "produced_at": 0,
      "this_update": 0,
      "next_update": 0,
      "revoked_at": 0,
      "revocation_reason": "",
      "status": ""
    },
  "error": "",
  "error_code": 1415,
  "request": {
      "method": "POST",
      "url": "http://127.0.0.1:9080/smc-web/getCaseCategorySearchPrefOption.do",
      "headers": {
        "User-Agent": [
          "k6/0.44.0 (https://k6.io/)"
        ],
        "Cookie": [
          "JSESSIONID=E1D4533BA6077DA4A8848CCE7206E688; crowd.token_key=ZRGfyAJLLiQGadn50rOc0gAAAAAAAIACc21jeXBkZXY"
        ]
      },
      "body": "{globalDataSource:\"\",cupsId:\"caseCategory.searchPref\"}",
      "cookies": {
        "crowd.token_key": [
          {
            "name": "crowd.token_key",
            "value": "ZRGfyAJLLiQGadn50rOc0gAAAAAAAIACc21jeXBkZXY",
            "replace": false
          }
        ],
        "JSESSIONID": [
          {
            "name": "JSESSIONID",
            "value": "E1D4533BA6077DA4A8848CCE7206E688",
            "replace": false
          }
        ]
      }
    }
}

Add Dockerfile

This tool is missing a Dockerfile, and a CI process that pushes new releases to the Docker Hub, which would be very useful for non-JS developers. Now, I either have to run it with npx (super slow and annoying) or I have to install it with npm install

Can't handle the charset attribute in a Content-Type header

How to reproduce:
Run

fetch('http://example.com', {
      method: 'POST',
      headers: new Headers({
               'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
               }),
		body: 'foo0=bar0&foo1=bar1'
    })
    .then(res => res.json())
    .then(console.log)

in Chrome v78.0.3904.97 DevTools Console and Save Content as HAR the Network Tab.
Trying to convert it using the har-to-k6 converter results in the error
InvalidPostDataType: Invalid structured post data MIME type (0): application/x-www-form-urlencoded; charset=UTF-8

Problem:
HAR 1.2 specification doesn't explicitly exclude charset attributes in the mimeType in the postData of requests. But since Chrome puts the content-type header in the mimeType too, it should be supported by the converter too.

Improved percent/URL-encoding

Currently, the script generator produces code like this for application/x-www-form-urlencoded POST data:

address: "Street%20Address%201"

But, because we URL-encode values behind-the-scenes at runtime, we end up sending this unless the user modifies the generated code:

Street%2520Address%25201

We should attempt a URL-decode when we know the POST data may be encoded. I'll take a look at this myself but thought I'd log it here as well for comments.

Improve UX of HAR converter's output scripts

With some refactoring and a few new features we can make the k6 HAR converter produce much more readable and user-friendly scripts without loosing anything in correctness or functionality. Some of the things I think can be changed:

  • move the long and ugly user-agent header to the global options object and remove it from each request (unless for some reason it's different than the global, though that should basically never happen with HAR files)
  • remove the unnecessary host header by default (i.e. for example by adding a new --remove-headers option in the HAR converter that by default has host in it)
  • remove the connection header when its value is keep-alive, since that's what the default HTTP 1.1 value is is that's what k6 does by default
  • remove the accept-encoding headers (arguable if we should remove it, but I think since it's super dependent on the HTTP client, we actually should clear it by default, because it's totally possible for a web-browser to put encodings there that we don't support in k6, thereby causing a bug)
  • unless users actually use the result (i.e. they use --correlate or something like that), don't save it in res
  • make any single-request http.batch() call to actually just use http.request() or even http.get()/http.post()/etc. calls
  • maybe remove the accept http header by default, since I think it doesn't usually do all that much and is almost universally ignored by servers, while adding messiness to the code
  • after this is implemented, we'd have a new k6 option that allows us to specify global headers (i.e. headers that we inject in every request). We currently do something like this, but it's restricted only to the userAgent, if we had the general option we can also use it for other headers that are basically the same for every request in a HAR file, like the Accept-Language (and Accept-Encoding, if we decide it's not worth removing it).
  • figure out repeating request options (like a single Referer header) and factor them out

Generally I'd like if generated scripts go from looking like this:

import { group, sleep } from 'k6';
import http from 'k6/http';

// Version: 1.3
// Creator: Load Impact URL test analyzer

export let options = {
  stages: [
    {
      "duration": "5m0s",
      "target": 25
    }
  ],
  maxRedirects: 0,
  discardResponseBodies: true,
};

export default function() {

  group("page_1 - http://test.loadimpact.com", function() {
    let req, res;
    req = [{
      "method": "get",
      "url": "http://test.loadimpact.com/",
      "params": {
        "headers": {
          "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
          "Connection": "keep-alive",
          "Accept-Encoding": "gzip, deflate",
          "Host": "test.loadimpact.com",
          "Accept-Language": "en-US",
          "Upgrade-Insecure-Requests": "1",
          "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36"
        }
      }
    }];
    res = http.batch(req);
    sleep(0.65);
    req = [{
      "method": "get",
      "url": "http://test.loadimpact.com/style.css",
      "params": {
        "headers": {
          "Accept": "text/css,*/*;q=0.1",
          "Connection": "keep-alive",
          "Accept-Encoding": "gzip, deflate",
          "Referer": "http://test.loadimpact.com/",
          "Host": "test.loadimpact.com",
          "Accept-Language": "en-US",
          "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36"
        }
      }
    },{
      "method": "get",
      "url": "http://test.loadimpact.com/images/logo.png",
      "params": {
        "headers": {
          "Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
          "Connection": "keep-alive",
          "Accept-Encoding": "gzip, deflate",
          "Referer": "http://test.loadimpact.com/style.css",
          "Host": "test.loadimpact.com",
          "Accept-Language": "en-US",
          "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36"
        }
      }
    }];
    res = http.batch(req);
    // Random sleep between 5s and 10s
    sleep(Math.floor(Math.random()*5+5));
  });
}

to something more like this:

import { group, sleep } from 'k6';
import http from 'k6/http';

// Version: 1.3
// Creator: Load Impact URL test analyzer

export let options = {
  stages: [
    {
      "duration": "5m0s",
      "target": 25
    }
  ],
  maxRedirects: 0,
  discardResponseBodies: true,
  headers: {
    "Accept-Language": "en-US",
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/62.0.3183.0 Safari/537.36",
  },
};

export default function () {
  group("page_1 - http://test.loadimpact.com", function () {
    http.get("http://test.loadimpact.com/", {
      "headers": {
        "Upgrade-Insecure-Requests": "1",
      }
    });
    sleep(0.65);

    http.batch([{
      "method": "get",
      "url": "http://test.loadimpact.com/style.css",
      "params": {
        "headers": {
          "Referer": "http://test.loadimpact.com/",
        }
      }
    }, {
      "method": "get",
      "url": "http://test.loadimpact.com/images/logo.png",
      "params": {
        "headers": {
          "Referer": "http://test.loadimpact.com/style.css",
        }
      }
    }]);

    // Random sleep between 5s and 10s
    sleep(Math.floor(Math.random() * 5 + 5));
  });
}

Support for per-request tags

Hello, I'm using har-to-k6 to test a flow in my company's app. In this flow there are multiple requests to a graphql endpoint where the endpoint url is always the same and what differs is the postData. Prior to using har-to-k6 I had the ability to add request tags which I used to add the gql operation, which would then propogate through to the json output of the k6 run.

Since the k6 script is now generated instead of hand written, I can't add these tags anymore.

I did try and add the tags onto the request body, but it seems like these params are only supported by k6/http. Is there a workaround to be able to set tags, or would that be a feature request?

mangled URL's

This fragment of HAR:

"request": {
          "method": "GET",
          "url": "https://example.com/api/layers/layer?assetId=bdaed40b-bed9-4b6f-abc4-56093ed1a43c&assetLayer=a9f5d33e-ab3b-4435-b3f6-427bc5aeb9c1&assetLayerConfiguration={%22Fabric%22:{%22assetId%22:%22bcff95a5-1b8c-4bad-a238-d8d6ca9c76ac%22},%22Collar%22:{%22assetId%22:%224c6b7595-083c-4905-a8f9-ae62d6a89c0e%22},%22Cuff%22:{%22assetId%22:%22acba39b1-05e7-43ae-bf80-1305a3a9277e%22},%22Pocket%22:{%22assetId%22:%2223eb7c46-b9da-4b68-80fa-d12971d46a93%22}}&orgId=0f556303-22e6-4015-b3b7-17438a673967",
          "queryString": [
            {
              "name": "assetId",
              "value": "bdaed40b-bed9-4b6f-abc4-56093ed1a43c"
            },
            {
              "name": "assetLayer",
              "value": "a9f5d33e-ab3b-4435-b3f6-427bc5aeb9c1"
            },
            {
              "name": "assetLayerConfiguration",
              "value": "{%22Fabric%22:{%22assetId%22:%22bcff95a5-1b8c-4bad-a238-d8d6ca9c76ac%22},%22Collar%22:{%22assetId%22:%224c6b7595-083c-4905-a8f9-ae62d6a89c0e%22},%22Cuff%22:{%22assetId%22:%22acba39b1-05e7-43ae-bf80-1305a3a9277e%22},%22Pocket%22:{%22assetId%22:%2223eb7c46-b9da-4b68-80fa-d12971d46a93%22}}"
            },
            {
              "name": "orgId",
              "value": "0f556303-22e6-4015-b3b7-17438a673967"
            },
          ],

gets converted into:

      response = http.get(
           "https://example.com/api/layers/layer?assetId=bdaed40b-bed9-4b6f-abc4-56093ed1a43c&assetLayer=a9f5d33e-ab3b-4435-b3f6-427bc5aeb9c1&assetLayerConfiguration=%7B%22Fabric%22%3A%7B%22assetId%22%3A%22bcff95a5-1b8c-4bad-a238-d8d6ca9c76ac%22%7D%2C%22Collar%22%3A%7B%22assetId%22%3A%224c6b7595-083c-4905-a8f9-ae62d6a89c0e%22%7D%2C%22Cuff%22%3A%7B%22assetId%22%3A%22acba39b1-05e7-43ae-bf80-1305a3a9277e%22%7D%2C%22Pocket%22%3A%7B%22assetId%22%3A%2223eb7c46-b9da-4b68-80fa-d12971d46a93%22%7D%7D&assetLayerConfiguration=%7B%2522Fabric%2522%3A%7B%2522assetId%2522%3A%2522bcff95a5-1b8c-4bad-a238-d8d6ca9c76ac%2522%7D%2C%2522Collar%2522%3A%7B%2522assetId%2522%3A%25224c6b7595-083c-4905-a8f9-ae62d6a89c0e%2522%7D%2C%2522Cuff%2522%3A%7B%2522assetId%2522%3A%2522acba39b1-05e7-43ae-bf80-1305a3a9277e%2522%7D%2C%2522Pocket%2522%3A%7B%2522assetId%2522%3A%252223eb7c46-b9da-4b68-80fa-d12971d46a93%2522%7D%7D&orgId=0f556303-22e6-4015-b3b7-17438a673967",

Notice how some query string parameters get added twice and the second time they're added they get double URI encoded.

ReferenceError: URI is not defined

Hello,

I try to use har-to-k6 to create a javascript. One of the code looks like this:

address = new URI(
  "https://azqamtell06/LoadReturn.htm"
);
address
  .addQuery("t", `${vars["VMName_25_Token"]}`)
  .addQuery("g", `${vars["VMName_25_GUID"]}`)
  .addQuery("pg", `${vars["VMName_25_PublicGUID"]}`)
  .addQuery("stoken", `${vars["VMName_25_SessionToken"]}`)

When I run the code, it gives following error:

ERRO[0063] ReferenceError: URI is not defined
at file:///C:/Users/776ec4fd-12f6-4f41-ae01-89ea8b1efd82.js:895:19(2431)
at github.com/loadimpact/k6/js/common.Bind.func1 (native)s
at main (file:///C:/Users/776ec4fd-12f6-4f41-ae01-89ea8b1efd82.js:28:17(17)) executor=per-vu-iterations scenario=default source=stacktrace

Removal of form-urlencoded as direct dependency

Configurations specifying postData mimeTypeapplication/x-www-form-urlencoded and a payload with multivalue parameter (eg search=kitten&search=puppy); and 2) there's a parameter with a variable (eg session=${session}) currently rely on form-urlencoded.
There is a proposal to support the Web API interface for composing URLs and query strings.
When support for this lands in K6 we can drop form-urlencoded as a dependency.

Feature Request: Replace custom parameters using regex while creating the k6 script.

Hey all

i am currently working with "har-to-k6" and it is a great tool that has helped me a lot. However, I would like to suggest a feature that I think could be a useful addition for many users.

Feature Description:

It would be very helpful to be able to include a list of user-defined parameters in the converter, which are automatically replaced by a regex during the creation of the k6 script.

saml_request_1 = "samlreq"="(.*)"
bearer_1 = Bearer\s([a-zA-Z0-9_\-\.]+)

The idea is that as soon as a response comes from the website and has a match with the user defined parameter, this should be remembered. All other values that are the same in a response should be replaced by the variable saml_request_1. If there is another match, the same should happen, but with a different key --> saml_request_2, saml_request_3, ...

Best Regards Achim

Post data conflict: params and text must be equal if both are provided

When POST data contains both "params" and "text", there is a check for their equivalence in validate/postData.js

This fails when escaped characters exist in the text data, as below.

Problem can be worked around by removing the check (does it have any purpose?)

        "params": [
          {
            "name": "page",
            "value": "SandBox"
          },
          {
            "name": "wikimarkup",
            "value": "line 1"
          }
        ],
        "text": "page=SandBox&wikimarkup=line%201"

Output from hacked version of code, having added this to error block:

  console.log(seralizeURLSearchParams(node.params))
  console.log(node.text)

har-to-k6 buggy.har -o buggy.js
Converting 'buggy.har'
page=SandBox&wikimarkup=line 1
page=SandBox&wikimarkup=line%201
Error converting archive:
Post data conflict (0): params and text must be equal if both are provided

buggy.har.gz

Thrown error does not provide data where the validation error has happened

In the current implementation we're throwing errors without returning any structured data to determine where the validation error has happened. That information is being returned via the error message, which is not always ideal.

Example: A user provided a HAR config with an invalid request url.

{
  "log": {
    "entries": [
      {
        "request": {
          "method": "POST",
          "url": true
        }
      }
    ]
  }
}

Such a HAR config would throw InvalidArchiveError with a message of Invalid request url (0): must be string and with a set property name equal to InvalidRequestUrl.

This implementation has a couple of issues:


The error message provides too much detail when not used in a CLI. Currently error messages almost always provide the index where the validation error happened. This probably leads to more confusion than actually helping the user if we're displaying error messages outside of a CLI.

A possible solution would be to remove the index part of the error message when validating HAR configs and only augmenting them with the index when used in a CLI.

This means the error message of our example would become:

  • Programmatic/Browser usage: Invalid request url must be string
  • CLI usage: Invalid request url must be string. Occurred in an entry with an index of 0.

Errors do not provide any structured data to determine where the validation error happened. Knowing where the validation error has happened would add more flexbility to the user to determine what to do with a given error.

Currently the only additional information that we provide is a name property (like InvalidRequestUrl or InvalidRequestQuery).

I'm suggesting extending the error object with two additional properties:

  • type - specifies the type of validation error. Since we're validating different types of data (like request, headers, cookies, queryString, etc.) it would be beneficial to know the type of validation error.
  • index - specifies the index of validation error. Usually this property would be a number. In some cases it would be a tuple [number, number]. For example, when throwing validation errors for headers we would like to know the indexes of both the entries and headers arrays respectively. With the addition of typeproperty or checking for the name property the user will know what type to expect from index.
  • path - pinpoints where the validation error has happened. Example: { ..., path: 'entries[4].request.headers[0].name'}.

Let me know your thoughts on the given suggestions. Thanks!

Duplicated query param when a "+" character is present

Steps to reproduce:
har-to-k6.cmd .\localhost.har -o loadtest.js

I generated this localhost.har file using the Dev Tools of Google Chrome Version 101.0.4951.67

The HAR file contains an URL with a query param which contains a space (encoded as +):

          "url": "http://api.example.com?foo=bar+baz.pdf",
          "queryString": [
            {
              "name": "foo",
              "value": "bar+baz.pdf"
            }
          ]

The output loadtest.js file contains a duplicated param:

...
export default function main() {
  let response

  response = http.get('http://api.example.com/?foo=bar+baz.pdf&foo=bar%2Bbaz.pdf')
  // should be:
  // response = http.get('http://api.example.com/?foo=bar+baz.pdf')
...
}

If I replace the + character by let's say - from the har file, it works as expected.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.