Code Blocks

So far, we’ve just been working with basic HTTP steps and wait steps. Those work fine for test scripts that proceed in a linear fashion (as most of them should), but what if you need special control flow or conditional logic or looping? Loadster’s answer is JavaScript code blocks.

These code blocks are similar to the JavaScript Validation Rules and Capturing Rules, except that they can exist independently of any other step, and they don’t have to be run on a particular response.

Code blocks can exist anywhere in your script. In fact, you could make a script that’s just a single code block, and do everything in JavaScript.

Creating a capturing rule
Creating a capturing rule

Code blocks are more powerful than ordinary graphical scripting because you can do looping, conditionals, etc.

To add a code block to your script, select Add Code Block.

Creating a capturing rule
Creating a capturing rule

Variable & Function Scope

Code blocks are individually scoped to the v-user that is executing them. If you declare a variable or function for one v-user, it will exist only for that v-user, and not for others who are running the same load test.

Just like real human users, each v-user interacts with your web application independently of all the others.

Even for a single v-user, ordinary JavaScript variables (declared with let or const or var) within a code block might be undefined outside that code block. If you need a variable to persist between steps, set it on the v-user with user.setVariable("v", v) so that the value ${v} remains in scope throughout the script.

Global Objects

Beyond all the standard JavaScript language constructs, Loadster code blocks expose a few important objects specific to Loadster that you can use in your scripts.

User

The user object is global within the context of a single v-user. It represents the v-user currently executing the code block. This user object exposes the following methods:

// All the standard HTTP methods
user.get(url, args);
user.post(url, body, args);
user.put(url, body, args);
user.patch(url, body, args);
user.delete(url, args);
user.options(url, args);
user.trace(url, args);

// Get and set user variables
user.getVariable(name);
user.getVariable(name, 3); // get column 3, if the variable is multi-column dataset
user.setVariable(name, value);

// Wait a specified number of milliseconds (these are synonymous)
user.wait(milliseconds);
user.sleep(milliseconds);

It’s important to note that these methods are all synchronous, so there is no need to do a promise chain or callbacks or anything like that.

The actual processing is done behind the scenes by Loadster’s v-user anyway, so synchronous programming is not the dangerous practice it might seem to be if you come from a background in single-threaded JavaScript development.

Console

Code blocks also expose a simple console for logging:

console.log(message);

Messages written to the console show up in the script logs and aid you in debugging the script.

JSON

If you’re testing APIs, you’ll often need to parse JSON to look at specific properties. You can use the ordinary JSON.parse for this.

var simple = JSON.parse("{a: 1}"); // parse an arbitrary JSON string
var body = JSON.parse(response.bodyAsString); // parse the response body in a validator

XML

Since XML parsing isn’t a standard language feature of JavaScript, Loadster includes the open source xmldoc parser. There is additional documentation for this parser available on GitHub, but here’s a quick example:

var xml = XML.parse(response.bodyAsString);
var users = xml.childrenNamed("user");

String Formatting and Encoding/Decoding Functions

Loadster provides a built-in formats library to help you with encoding and decoding strings, and with generating timestamps, UUIDs, and random data. Here are some examples of inputs and outputs.

formats.uppercase("hello"); // HELLO
formats.lowercase("HELLO"); // hello
formats.urlencode("user@example.com"); // user%40example.com
formats.urldecode("user%40example.com"); // user@example.com
formats.base64encode("user:pass"); // dXNlcjpwYXNz
formats.base64decode("dXNlcjpwYXNz"); // user:pass
formats.xmlescape("11 > 10"); // 11 > 10
formats.xmlunescape("11 > 10"); // 11 > 10
formats.htmlescape("<p>"); // &lt;p&gt;
formats.htmlunescape("&lt;p&gt;"); // <p>
formats.timestamp("%Y-%M-%d"); // 2020-07-04
formats.randomalpha(12); // zmLkWMwtEhOD
formats.randomalphanumeric(10); // F6kEq53p3W
formats.randomnumeric(8); // 62331478
formats.uuid(); // 8ffdb4ef-9e87-4b58-9415-f4c454f0a2ee

Cryptographic and Hashing Functions

If you’re getting fancy, you might need to apply a hashing function to a piece of data (with an optional secret) to generate a hashed value that your server expects. Some of the more commonly hashes are included in Loadster’s built-in crypto library, which you can call from any code block.

crypto.hmacsha512base64("input", "secret"); // Ksle03F+BCxwZKX6fDGCMM022F4G+P+Dc9BMoX42Fingn0a38VH/OCo/SMWxkSFEbkXCWI8P8d6fdLBADa74Hw==
crypto.hmacsha384base64("input", "secret"); // is0XLFfl9kpaMnpDdiMkwOJ4eYP7ez481SOKgiu6p/mC4SXCJzeVtbuU0z6auD7F
crypto.hmacsha256base64("input", "secret"); // jYmF0Et6vTLLqjd5o9qgGeDSaaIq7BWvjnKW9wLMaMY=
crypto.hmacsha224base64("input", "secret"); // LDdw8G3Ykt8vLV9+8gXABot+TCB01il0Hy5S8A==
crypto.hmacsha1base64("input", "secret"); // MEQPNt3CgJu9TIsfN6boDXWIwwM=
crypto.hmacsha512hex("input", "secret"); // 2ac95ed3717e042c7064a5fa7c318230cd36d85e06f8ff8373d04ca17e361629e09f46b7f151ff382a3f48c5b19121446e45c2588f0ff1de9f74b0400daef81f
crypto.hmacsha384hex("input", "secret"); // 8acd172c57e5f64a5a327a43762324c0e2787983fb7b3e3cd5238a822bbaa7f982e125c2273795b5bb94d33e9ab83ec5
crypto.hmacsha256hex("input", "secret"); // 8d8985d04b7abd32cbaa3779a3daa019e0d269a22aec15af8e7296f702cc68c6
crypto.hmacsha224hex("input", "secret"); // 2c3770f06dd892df2f2d5f7ef205c0068b7e4c2074d629741f2e52f0
crypto.hmacsha1hex("input", "secret"); // 30440f36ddc2809bbd4c8b1f37a6e80d7588c303
crypto.md5("input"); // a43c1b0aa53a0c908810c06ab1ff3967 

Examples

There’s nothing like learning by example! Here are a few things you can do in code blocks.

HTTP Requests

The special user object (representing the current v-user) has methods for all of the common HTTP methods (GET, POST, etc).

// GET
user.get("https://slothereum.cc");

// GET with additional page resources
user.get("https://slothereum.cc", {
    resources: [
        "/style.css",
        "/favicon.ico"
    ]
});

// POST with a JSON body and custom header
user.post(
    "https://api.slothereum.cc/transactions",
    {
        source: "08c2d5",
        destination: "9a6bce",
        amount: 1.05
    },
    {
        headers: {
            "Content-Type": "application/json"
        }
    }
);

// POST with a form body
user.post(
    "https://admin.slothereum.cc/login",
    "username=sloth&password=chunk",
    {
        headers: {
            "Content-Type": "application/x-www-form-urlencoded"
        }
    }
);

// DELETE
user.delete("https://api.slothereum.cc/receipts/1133423");

Request Headers

You can pass custom request headers with each request, either as an object with key-value pairs, or in an array.

// Pass request headers in an object
user.get("https://slothereum.cc/api/profile", {
    headers: {
        "Accept": "application/json"
    }
);

// Pass request headers in an array
user.get("https://slothereum.cc/api/profile", {
    headers: [
        { name: "Accept", value: "application/json" }
    ]
);

Response Validation

Validators (similar to the Validation Rules that you can use with ordinary HTTP steps) call a JavaScript function to examine the response and return true if it’s valid, or false if it’s not.

Validator functions can be normal JavaScript functions or the newer ES2016+ arrow functions.

You can specify multiple validator functions for a single response.

// A POST with a JSON body and validator function that requires an HTTP 201 status
user.post(
    "https://api.slothereum.cc/transactions",
    {
        source: "08c2d5",
        destination: "9a6bce",
        amount: 1.05
    },
    {
        headers: [
            { name: "Content-Type", value: "application/json" }
        ],
        validators: [
            // regular function validator syntax
            function (response) {
                return response.status == 201;
            },
            // arrow function validator syntax works too!
            response => response.status === 201
        ]
    }
);

Response Capturing

Often, the server will send you some data that you need to save and use later in your script.

In code blocks, you can capture these from the response and store them using a validator function. Note that we use the validators for capturing too; there is no separate property for Capturing Rules as there is with ordinary HTTP steps.

Simply call user.setVariable(name, value) anywhere in your code block to set a user variable. These special user variables are available for the v-user’s entire iteration of the Loadster script, unlike ordinary JavaScript variables which are scoped to the individual code block and may not be available to subsequent steps.

// Create a random numeric string between 0-999999
let random = String(Math.round(1000000 * Math.random()))

// POST to register an account with the random username and capture a user_id
user.post(
    "https://admin.slothereum.cc/register",
    {
        username: "sloth_" + random,
        password: "chunk"
    },
    {
        headers: {
            "Accept": "application/json",
            "Content-Type": "application/json"
        },
        validators: [
            response => {
                var body = JSON.parse(response.bodyAsString);

                if (body.profile.user_id) {
                    user.setVariable("user_id", name); // save the user_id for later

                    return true; // valid response! treat as a success
                } else {
                    return false; // invalid response! treat as a failure
                }
            }
        ]
    }
);

Loops & Conditionals

Looping and conditionals can be done with all the usual JavaScript language constructs. This trivial example shows looping with a for loop, the % modulus function, an if statement, and setting and getting special user variables with variable interpolation of ${random}.

console.log("We're in a nonsensical code step.");
console.log("It exists only to demonstrates random numbers, user vars, and conditionals.");

// Set a user variable to a random integer from 0 to 19
user.setVariable("random", Math.floor(Math.random() * 20));

// Loop and make 20 requests, with a random value and random wait times
for (var i = 0; i < 20; i++) {
    if (i % 2 == 0) {
        user.get("https://example.com/evens/${random}");
    } else {
        user.get("https://example.com/odds/${random}");
    }

    user.wait(Math.random() * 2000);
}

Limitations

Keep in mind that code blocks aren’t actually running in your browser, so they don’t have access to the live DOM the way browser-based JavaScript does. Be careful not to confuse them with on-page JavaScript (jQuery, React, etc).

Rather, the scripts are executed on the load engine by your Loadster v-users, and they have access to certain special objects like user, response, console, etc.

Getting Help

If you get stuck, we’re happy to help! Please contact help@loadster.app and tell us all about it. We are always interested in how you’re using code blocks in Loadster.