Knowledgebase

Scripting examples


Background

As developers and testers ourselves we know how important it is with examples when learning a new tool. Below is a list of example k6 test scripts show casing commonly asked for functionality.

Authentication/Authorization

Examples of various HTTP Authentication methods that can be used with k6. These, plus other examples can be found within the k6 GitHub Repo

Basic authentication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import encoding from "k6/encoding";
import http from "k6/http";
import { check } from "k6";

const username = "user",
      password = "passwd";

export default function() {
    // Passing username and password as part of URL will authenticate using HTTP Basic Auth
    let res = http.get(`http://${username}:${password}@httpbin.org/basic-auth/${username}/${password}`);

    // Verify response
    check(res, {
        "status is 200": (r) => r.status === 200,
        "is authenticated": (r) => r.json().authenticated === true,
        "is correct user": (r) => r.json().user === username
    });

    // Alternatively you can create the header yourself to authenticate using HTTP Basic Auth
    res = http.get(`http://httpbin.org/basic-auth/${username}/${password}`, { headers: { "Authorization": "Basic " + encoding.b64encode(`${username}:${password}`) }});

    // Verify response (checking the echoed data from the httpbin.org basic auth test API endpoint)
    check(res, {
        "status is 200": (r) => r.status === 200,
        "is authenticated": (r) => r.json().authenticated === true,
        "is correct user": (r) => r.json().user === username
    });
}

Digest authentication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import http from "k6/http";
import { check } from "k6";

const username = "user",
      password = "passwd";

export default function() {
    // Passing username and password as part of URL plus the auth option will authenticate using HTTP Digest authentication
    let res = http.get(`http://${username}:${password}@httpbin.org/digest-auth/auth/${username}/${password}`, {auth: "digest"});

    // Verify response (checking the echoed data from the httpbin.org digest auth test API endpoint)
    check(res, {
        "status is 200": (r) => r.status === 200,
        "is authenticated": (r) => r.json().authenticated === true,
        "is correct user": (r) => r.json().user === username
    });
}

NTLM authentication

1
2
3
4
5
6
7
8
9
import http from "k6/http";

const username = "user",
      password = "passwd";

export default function() {
    // Passing username and password as part of URL and then specifying "ntlm" as auth type will do the trick!
    let res = http.get(`http://${username}:${password}@example.com/`, { auth: "ntlm" });
}

AWS Signature v4 authentication

Requests to the AWS APIs requires a special type of auth, called AWS Signature Version 4. k6 doesn’t support this authentication mechanism out of the box, so we’ll have to resort to using a Node.js library called awsv4.js and Browserify (to make it work in k6).

There are a few of steps to make this work:

  1. Make sure you have the necessary prerequisites installed:
  2. Install the awsv4.js library:

    npm install aws4

  3. Run it through browserify:

    browserify node_modules/aws4/aws4.js -s aws4 > aws4.js

  4. Move the aws4.js file to the same folder as your script file and you’ll be able to import it into your test script:

    import aws4 from "./aws4.js"

Here’s an example script to list all the regions available in EC2. Note that the AWS access key and secret key needs to be provided through environment variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import http from "k6/http";
import {sleep} from "k6";

// Import browserified AWSv4 signature library
import aws4 from "./aws4.js";

// Get AWS credentials from environment variables
const AWS_CREDS = {
    accessKeyId: __ENV.AWS_ACCESSKEY,
    secretAccessKey: __ENV.AWS_SECRETKEY
};

export default function() {
    // Sign the AWS API request
    const signed = aws4.sign({
            service: 'ec2',
            path: '/?Action=DescribeRegions&Version=2014-06-15'
        }, AWS_CREDS);

    // Make the actual request to the AWS API including the "Authorization" header with the signature
    let res = http.get(`https://${signed.hostname}${signed.path}`, { headers: signed.headers });

    // Print the response
    console.log(res.body);

    sleep(1);
}

Cookies

As HTTP is a stateless protocol, cookies are used by server-side applications to persist data on client machines. This is used more or less everywhere on the web, commonly for user session tracking purposes. In k6 cookies are managed automatically by default, however there’re use cases where access to read and manipulate cookies are required.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import http from "k6/http";
import { check, group } from "k6";

export default function() {
    // Since this request redirects the `res.cookies` property won't contain the cookies
    let res = http.get("http://httpbin.org/cookies/set?name1=value1&name2=value2");
    check(res, {
        "status is 200": (r) => r.status === 200
    });

    // Make sure cookies have been added to VU cookie jar
    let vuJar = http.cookieJar();
    let cookiesForURL = vuJar.cookiesForURL(res.url);
    check(null, {
        "vu jar has cookie 'name1'": () => cookiesForURL.name1.length > 0,
        "vu jar has cookie 'name2'": () => cookiesForURL.name2.length > 0
    });
}

Logging all cookies in response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Example showing two methods how to log all cookies (with attributes) from a HTTP response.

import http from "k6/http";

function logCookie(cookie) {
    // Here we log the name and value of the cookie along with additional attributes.
    // For full list of attributes see: https://docs.k6.io/docs/cookies#section-properties-of-a-response-cookie-object
    console.log(`${cookie.name}: ${cookie.value}\n\tdomain: ${cookie.domain}\n\tpath: ${cookie.path}\n\texpires: ${cookie.expires}\n\thttpOnly: ${cookie.http_only}`);
}

export default function() {
    let res = http.get("https://www.google.com/");

    // Method 1: Use for-loop and check for non-inherited properties
    for (var name in res.cookies) {
        if (res.cookies.hasOwnProperty(name) !== undefined) {
            logCookie(res.cookies[name][0]);
        }
    }

    // Method 2: Use ES6 Map to loop over Object entries
    new Map(Object.entries(res.cookies)).forEach((v, k) => logCookie(v[0]) );
}

To set a cookie that should be sent with every request matching a particular domain, path etc. you’d do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import http from "k6/http";
import { check } from "k6";

export default function() {
    // Get VU cookie jar and add a cookie to it providing the parameters
    // that a request must match (domain, path, HTTPS or not etc.) 
    // to have the cookie attached to it when sent to the server.
    let jar = http.cookieJar();
    jar.set("https://httpbin.org/cookies", "my_cookie", "hello world",
            { domain: "httpbin.org", path: "/cookies", secure: true, max_age: 600 });

    // As the following request is matching the above cookie in terms of domain,
    // path, HTTPS (secure) and will happen within the specified "age" limit, the
    // cookie will be attached to this request.
    let res = http.get("https://httpbin.org/cookies");
    check(res, {
        "has status 200": (r) => r.status === 200,
        "has cookie 'my_cookie'": (r) => r.json().cookies.my_cookie !== null,
        "cookie has correct value": (r) => r.json().cookies.my_cookie == "hello world"
    });
}

Relevant k6 APIs:

Correlation

In a load testing scenario, correlation means extracting one or more values from the response of one request and then reusing them in subsequent requests. Often times this could be getting a token or some sort of ID necessary to fulfill a sequence of steps in a user journey.

The browser recording will for example capture things like CSRF tokens, VIEWSTATES etc. from your session. This type of data is likely to no longer be valid when you run your test, meaning you’ll need to handle the extraction of this data from the HTML/form to include it in subsequent requests. This issue is fairly common and can be handled with a little bit of scripting.

Extracting values/tokens from JSON response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import http from "k6/http";
import { check } from "k6";

export default function() {
    // Make a request that returns some JSON data
    let res = http.get("https://httpbin.org/json");

    // Extract data from that JSON data by first parsing it
    // using a call to "json()" and then accessing properties by
    // navigating the JSON data as a JS object with dot notation.
    let slide1 = res.json().slideshow.slides[0];
    check(slide1, {
        "slide 1 has correct title": (s) => s.title === "Wake up to WonderWidgets!",
        "slide 1 has correct type": (s) => s.type === "all"
    });

    // Now we could use the "slide1" variable in subsequent requests...
}

Relevant k6 APIs:

Extracting values/tokens from form fields

There are primarily two different ways you can choose from when deciding how to handle form submissions. Either you use the higher-level Response.submitForm([params]) API or you extract necessary hidden fields etc. and build a request yourself and then send it using the appropriate http.* family of APIs, like http.post(url, [body], [params]).

Extracting .NET ViewStates, CSRF tokens and other hidden input fields

Method 1 using the k6 HTML parsing and query APIs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import http from "k6/http";
import {sleep} from "k6";

export default function() {
    // Request page containing a form
    let res = http.get("https://test.loadimpact.com/my_messages.php");

    // Query the HTML for an input field named "redir"
    let elem = res.html().find('input[name=redir]');

    // Get the value of the attribute "value"
    let val = elem.attr('value');

    // Now you can use this extracted value in subsequent requests...

    sleep(1);
}

Method 2 using standard JS regex APIs:

Relevant k6 APIs:

Data files/Parameterization

Reading parameterization data from a CSV file

As k6 doesn’t support parsing CSV files out of the box, we’ll have to resort to using a Node.js library called Papa Parse and Browserify (to make it work in k6).

There are a few of steps to make this work:

  1. Make sure you have the necessary prerequisites installed:
  2. Install the Papa Parse library:

    npm install papaparse

  3. Run it through browserify:

    browserify node_modules/papaparse/papaparse.min.js -s papaparse > papaparse.js

  4. Move the papaparse.js file to the same folder as your script file and you’ll be able to import it into your test script:

    import papaparse from "./papaparse.js"

Here’s an example using Papa Parse to parse a CSV file of username/password pairs and using that data to login to the Load Impact test site:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
    Where contents of data.csv is:

    username,password
    admin,123
    test_user,1234
*/

import http from "k6/http";
import {check, sleep} from "k6";
import papaparse from "./papaparse.js";

// Load CSV file and parse it using Papa Parse
const csvData = papaparse.parse(open("./data.csv"), {header: true});

export default function() {
    // Now you can use the CSV data in your test logic below.
    // Below are some examples of how you can access the CSV data.

    // Loop through all username/password pairs
    csvData.data.forEach(userPwdPair => {
        console.log(JSON.stringify(userPwdPair));
    });

    // Pick a random username/password pair
    let randomUser = csvData.data[Math.floor(Math.random() * csvData.data.length)];
    console.log("Random user: ", JSON.stringify(randomUser));

    // Login to Load Impact test site using the random user
    let res = http.post("https://test.loadimpact.com/login.php", {login: randomUser.username, password: randomUser.password});
    check(res, {
        "login succeeded": (r) => r.status === 200 && r.body.indexOf("successfully authorized") !== -1
    });

    sleep(1);
}

Reading parameterization data from a JSON file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
    Where contents of data.json is:
    {
        "users": [
            { username: "test", password: "qwerty" },
            { username: "test", password: "qwerty" }
        ]
    }
*/

const data = JSON.parse(open("./data.json"));

export default function() {
    let user = data.users[0];
    console.log(data.users[0].username);
}

HTML

Filling in and submitting forms

One of the most tedious tasks when testing websites and apps is to get all the form filling to work. You have to get all the so called “correlations” (see above) correct which can take time, even with the help of a scenario recorder as the staring point for getting the basic user journey down into a re-playable test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import http from "k6/http";
import {sleep} from "k6";

export default function() {
    // Request page containing a form
    let res = http.get("https://httpbin.org/forms/post");
 
    // Now, submit form setting/overriding some fields of the form
    res = res.submitForm({
        formSelector: 'form[action="/post"]',
        fields: { custname: "test", extradata: "test2" },
        submitSelector: "mySubmit",
    });

    sleep(3);
}

Relevant k6 APIs:

HTTP/2

In k6 HTTP/2 is automatic. If the target system indicates that a connection can be upgraded from HTTP/1.1 to HTTP/2, k6 will do so automatically.

Making HTTP/2 requests

1
2
3
4
5
6
7
8
9
10
import http from "k6/http";
import { check } from "k6";

export default function() {
    let res = http.get("https://www.bbc.co.uk/");
    check(res, {
        "status is 200": (r) => r.status === 200,
        "protocol is HTTP/2": (r) => r.proto === "HTTP/2.0",
    });
}

SOAP

Althought k6 doesn’t have any builtin APIs for working with SOAP or XML data in general, you can still easily load test a SOAP based API by crafting SOAP messages and using the HTTP request APIs.

Making SOAP requests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import http from "k6/http";
import { check, sleep } from "k6";

const soapReqBody = `
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:hs="http://www.holidaywebservice.com/HolidayService_v2/">
    <soapenv:Body>
        <hs:GetHolidaysAvailable>
            <hs:countryCode>UnitedStates</hs:countryCode>
        </hs:GetHolidaysAvailable>
    </soapenv:Body>
</soapenv:Envelope>`;

export default function() {
    // When making a SOAP POST request we must not forget to set the content type to text/xml
    let res = http.post(
        "http://www.holidaywebservice.com/HolidayService_v2/HolidayService2.asmx?wsdl",
        soapReqBody,
        {headers: { 'Content-Type': 'text/xml' }});

    // Make sure the response is correct
    check(res, {
        'status is 200': (r) => r.status === 200,
        'black friday is present': (r) => r.body.indexOf('BLACK-FRIDAY') !== -1
    });

    sleep(1);
}

Uploads

Binary file upload

1
2
3
4
5
6
7
8
9
10
11
12
import http from "k6/http";
import { sleep } from "k6";

let binFile = open("/path/to/file.bin", "b");

export default function() {
  var data = {
    file: http.file(binFile, "test.bin")
  };
  var res = http.post("https://example.com/upload", data);
  sleep(3);
}

Relevant k6 APIs:

Creating a multipart request

With multipart requests you combine pieces of data with possibly different content types into one request body. A common scenario is for example a form with regular text input fields and a file field for uploading a file:

1
2
3
4
5
6
7
8
9
10
11
12
13
import http from "k6/http";
import { sleep } from "k6";

let file = open("/path/to/file.txt");

export default function() {
  var data = {
    field: "this is a standard form field",
    file: http.file(file, "test.txt")
  };
  var res = http.post("https://example.com/upload", data);
  sleep(3);
}

Relevant k6 APIs:

UUID

Generate v1 and v4 UUIDs

Universally unique identifier are handy in many scenarios, as k6 doesn’t have built-in support for UUIDs we’ll have to resort to using a Node.js library called uuid and Browserify (to make it work in k6).

There are a few of steps to make this work:

  1. Make sure you have the necessary prerequisites installed:
  2. Install the uuid library:

    npm install uuid

  3. Run it through browserify:

    browserify node_modules/uuid/index.js -s uuid > uuid.js

  4. Move the uuid.js file to the same folder as your script file and you’ll be able to import it into your test script:

    import uuid from "./uuid.js"

Here’s an example generating a v1 and v4 UUID:

1
2
3
4
5
6
7
8
9
10
11
import uuid from "./uuid.js";

export default function() {
    // Generate a UUID v1
    let uuid1 = uuid.v1();
    console.log(uuid1);

    // Generate a UUID v4
    let uuid4 = uuid.v4();
    console.log(uuid4);
}

WebSocket

Testing a WebSocket API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import ws from "k6/ws";
import { check } from "k6";

export default function() {
    var url = "ws://echo.websocket.org";
    var params = { "tags": { "my_tag": "hello" } };

    var res = ws.connect(url, params, function(socket) {
        socket.on('open', function open() {
            console.log('connected');

            socket.setInterval(function timeout() {
              socket.ping();
              console.log("Pinging every 1sec (setInterval test)");
            }, 1000);
        });

        socket.on('ping', function () {
            console.log("PING!");
        });

        socket.on('pong', function () {
            console.log("PONG!");
        });

        socket.on('close', function() {
            console.log('disconnected');
        });

        socket.setTimeout(function () {
            console.log('2 seconds passed, closing the socket');
            socket.close();
        }, 2000);
    });

    check(res, { "status is 101": (r) => r && r.status === 101 });
}