Sessions

Each test case has to have at least one session, which describes what a newly launched client will do. Every client that is launched will pick a session based on its configured likelihood.

Every session has

  1. a descriptive name and
  2. a step definition function

name is an identifier which you can freely choose but it has to be unique within one test case definition.

Within the step definition function the actual steps of a session are defined.

Example:

definition.session("new sign up", function(session) {
  session.get("/");
  session.post("/sign-up", {
    payload: { email: "foo@example.com" }
  });
  // ...
});

definition.session("guest", function(session) {
  // ...
});

Session Likelihood

There are two ways to configure the likelihood of a session. It describes how likely a session is going to be picked up by a newly started client.

If you don't specify anything explicitly, we assume that all sessions have an equal weight of 1, so each session is equally likely to be picked up.

Weights

You can configure the likelihood of a session to be picked up by weights. A session with weight of 3 is 3 times as likely to be selected compared to a session with weight of 1.

If you do not provide weights, a weight of 1 is assumed for all sessions.

definition.setSessionWeights({
  "new sign up": 3,
  "guest": 1
});

Note:

  • There has to be at least one session with a non-zero weight.
  • The default weight of sessions is 1.

Probabilities

The total sum of all probabilities has to be exactly 100.

definition.setSessionProbabilities({
  "new sign up": 94.5,
  "guest": 5.5
});

Steps

The step definition function's parameter is used to define session steps. The step definition function will operate on the given argument session.

Despite basic request steps you can implement simple control structures like wait times, conditions or loops:

session.get("/ping");
session.wait(0.5);
session.waitUniform(10, 20);
session.times(4, function(context){});
session.if(variableName, comparator, value, function(context){});
session.forEver(function(context){});

To see all available request types check out the request reference.

NOTE: times, if and forEver statements must have at least one sub-step.

wait

To simulate think times of users in a session StormForger provides wait, waitExp and waitUniform functions.

wait allows you to specify a static think time in seconds. To wait exactly 20.5 seconds, use

session.wait(20.5);

Static wait times can be used if you know that your API client does wait for a fixed period of time. If you want to simulate human think time behaviour, please check out waitExp.

waitExp

session.waitExp(23.1);

In the example above, the think time will be an exponential distribution with a mean equal to 23.1 seconds. This means that the think time varies within the probability distribution that describes the time between events.

We recommend to use waitExp for human think times.

waitUniform

To simulate think times of users in a more uniform way, you can use the waitUniform function with a min/max value.

session.waitUniform(0.9, 10);

The distribution will be uniform in the interval (in the example above) from 900 milliseconds to 10 seconds.

times

To model a loop you can use the times function.

session.times(4, function(context){});

if

For conditional statements StormForger offers the if function.

session.if(variableName, comparator, value, function(context){});

Valid comparators are: =, !=, >, >=, < and <= — for equal, not equal, greater than, greater or equal than, less than and less than or equal.

Example:

session.if(session.getVar("myVariable"), ">", 2, function(context) { });
session.if(session.getVar("otherVariable"), "!=", "", function(context) { });

forEver

If you need an endless loop you can use the forEver function. The loop will terminate when your defined test duration time is over.

session.forEver(function(context){});

Sophisticated Sessions

For example using data sources, you can do more sophisticated sessions like this:

// create a reference to the uploaded data source
var users = session.ds.loadStructured("authentication/admin_users.csv");

var user = session.ds.pickFrom(users);
var otherUser = session.ds.pickFrom(users);

session.post("/contacts/add", {
  headers: {
    "X-AwesomeApp-Token": user.get("api_token"),
    "X-AwesomeApp-Mail": user.get("mail"),
  },
  payload: {
    email: otherUser.email(),
    phone: "+4917" + nonce: session.ds.generate("random_number", { range: [1, 100000] }),
  }
});

Session random branching

If you need to select a different branch in a more random way, you can use session.chooseByWeight(), session.chooseByProbability() or session.withProbability(). Branches will be selected based on their weight or probability.

For example (weight):

session.chooseByWeight([
  [1.0, function(c) { c.get("/choose/1x"); }],
  [1.5, function(c) { c.get("/choose/1.5x"); }],
  [2.0, function(c) { c.get("/choose/2x"); }],
]);

Or probabilities:

session.chooseByProbability([
  [10, function(c) { c.get("/choose/10-percent"); }],
  [25, function(c) { c.get("/choose/25-percent"); }],
  [65, function(c) { c.get("/choose/65-percent"); }],
]);

session.withProbability(25, function(c) { c.get("/choose/25-percent-request"); });

Note that chooseByProbability MUST NOT have a sum larger than 100 between all probabilities. One branch will be picked from the various alternatives.

withProbability will run the given branch with the specified probability. In the example above, the /choose/25-percent-request request is performed for 25% of the sessions. Note that every session.withProbability() statement has a separate chance to be selected, so having multiple branches in the same session may execute various combinations of branches.

Session Options

You can set additional options for a specific session. Available are:

Rate limiting

Rate limiting allows controlling the bandwidth of each client for all requests that follow this option. You can specify both the download (ingress) and upload (egress) rate by calling session.withOption("rate_limit", option). The options parameter supports the following fields:

Field Description
ingress Sets the ingress rate limit in kibibytes per second (1 kibibytes = 1024 bytes)
ingress_burst Sets the maximum burst value for ingress traffic (in kibibyte)
egress Sets the egress rate limit in kibibyte per second
egress_burst Sets the maximum burst value for egress traffic (in kibibyte)
reset If set to true resets both the ingress and egress rate limiting. Must not be used with the ingress or egress option
rate Deprecated: Alias for the ingress parameter
burst Deprecated: Alias for the ingress_burst parameter

Example:

// Limit download transfer rate to 1024 kibibytes/sec and upload rate to 256 kibibytes/sec
session.setOption("rate_limit", {
  ingress: 1024,
  egress: 256,
});

// Only configure the download transfer rate to a typical 16 mbit DSL connection
session.setOption("rate_limit", { ingress: ((16*1024)/8) })

If you only need rate limiting for a few requests, you can reset the rate request as well. Subsequent requests won't be rate limited.

// Resets both the ingress and egress rate limit
session.setOption("rate_limit", { reset: true });

Deprecated Rate Limiting API

The following parameters were previous iterations on the rate limiting API and are now deprecated. The configure the ingress rate limiting only and are not recommended for new test cases.

// Ratelimit of 1 megabyte per second
session.setOption("rate_limit", {
  rate: 1024,
});

session.setOption("rate_limit", { rate: ((16*1024)/8) }); // A typical 16 mbit DSL connection

// To reset a currently active rate limiting, set `rate` to the string `"unlimited"`.
session.setOption("rate_limit", {
  rate: "unlimited",
});

TLS Client Certificates

You can use TLS client certificates e.g. for strong authentication with your application.

To specify the certificate, private key and password for private key, you can use the certificate option:

session.setOption("certificate", {
  "cert": session.ds.getRawFile("tls_client_certificate.pem"),
  "key": session.ds.getRawFile("tls_client_key.pem"),
  "key_password": "password",
});

Note that you have to provide the certificate and private key in PEM-encoded format as data source type raw, see data sources reference.

If you need to provide the certificate or private key more dynamically, you can also use cert_data and key_data respectively to pass PEM encoded data to setOption():

session.setOption("certificate", {
  "cert_data": session.getVar("certificate"),
  "key_data": "-----BEGIN PRIVATE KEY-----\n...",
  "key_password": "password",
});

Subsequent requests for this client will use this certificate for new TLS connections.

Note: This feature is currently in alpha and the API is subject to change. We would love to receive feedback.

Cookie Domain Mapping allows you to treat multiple domains as one with regards to cookie storage. This is useful when testing directly against your load-balancers or backends while skipping the CDN. In this case you might have to test against multiple target domains which in the production environment are served under one domain.

The cookie_domain_map option accepts a list of domains that should get rewritten when the client sees a Set-Cookie response header (without a specific domain part).

session.setOption("cookie_domain_map", {
  "upstream-backend.example.com": "www.example.com",
});
session.post("http://upstream-backend.example.com/login", { payload: "example-payload" });
session.get("http://www.example.com/my/profile"); // will see the cookie set by /login

This example configures a cookie domain map where the upstream-backend.example.com domain is treated as www.example.com in the cookiejar. Any Set-Cookie header received by the post() request is later available for all further requests to the upstream-backend.example.com and www.example.com domain. This also works the other way around as any future requests to upstream-backend.example.com will also use any cookies configured for www.example.com.

Note that configuring a cookie_domain_map shadows any preexisting cookies on the key domains (upstream-backend.example.com in the example above).

DNS Mapping

Note: This feature is currently in alpha and the API is subject to change. We would love to receive feedback.

DNS Mapping allows replacing the target of a request with a different hostname or IP address. This is useful in various situations:

  • The system under test has a different public DNS entry than used internally and configured on the load balancers
  • The origin instead of the CDN should be tested
  • The system under test has no public DNS record (but is available over an IP address and requires the Host header)
  • The system under test is only available over the IP address but requires a hostname for the TLS handshake (for Server Name Indication)

To configure DNS Mapping call session.setOption() with dns_map and a mapping of the domain names:

definition.setTargets(["https://www.example.com", "https://www2.example.com", "https://10.0.0.1", "https://origin.example.com"]);
session.setOption("dns_map", {
  "www.example.com": "10.0.0.1",
  "www2.example.com": "origin.example.com",
});
session.get("https://www.example.com/"); // request will be sent to 10.0.0.1
session.get("https://www2.example.com/"); // request will be sent to origin.example.com

When establishing a connection, the load generator will check the DNS Mapping. If the request hostname is found as a key in the mapping, it will instead connect to the configured replacement. The request itself is not changed (e.g. the Host header remains intact).

Note that both the request target and the mapped target need to be configured in the targetlist.

Variables

Each sessions have a set of variables. Besides some pre-defined variables, you can add your own via Content Extraction or session.setVar() (see below).

To access a variable, use session.getVar(varName).

If you need to print all variables with their current state, use session.dumpVars(). Checkout our debugging guide as well.

Pre-Defined Variables

Each session comes with set of pre-defined variables which you could use e.g. for logging or debugging purposes. Pre-defined variables can be accessed the same way other custom variables are accessed (see Content Extraction).

session.get("/ping?=" + session.getVar("client_id"));

Each session provides the following variables:

  • client_id: An unique ID for each client per session
  • test_run_uid: The unique ID for a test run instance (e.g. FQJpH0sA)
  • test_run_id: A sequenced integer ID scoped per test case
  • test_case_uid: The unique UID for a test case (e.g. 1mkpVJNC)
  • test_case_name: The test case name given by the user (e.g. black_friday_scenario)

Custom Variables defined via Content Extraction have precedence over Pre-Defined variables.

Custom Variables

You can also define your own variables with session.setVar(varName, value):

session.setVar("address", "New York, New York")

Calculating dynamic values for variables

Note: This feature is currently in beta. We would love to receive feedback.

Since StormForger DSL is not a direct scripting language, you cannot run arithmetic expressions to calculate dynamic values inside a session with pure JavaScript. calcInt() and calcFloat() are provided to allow for dynamic calculation of values on the fly instead:

var factor = session.ds.define("random_number", {name: "factor", range: [1, 15]});
var offset = 15123;

var bid = session.tools.calcInt("bidPrice", "%f * (1 + (%d / 100)) + %f", [
  session.getVar("price"), // first %f placeholder
  session.ds.generateFrom(factor), // %d placeholder
  offset // second %f placeholder
]);

session.post("/", {
  payload: JSON.stringify({
    bidPrice: session.getVar("bidPrice")
  })
});

calcInt() evaluate the given expression during a loadtest with the actual values and returns the result formatted as an integer (rounded); calcFloat() returns a float respectively.

The expression must be an arithmetic expression, which supports:

  • numbers (integer and float literals)
  • positional arguments: %f for float, %d for integers
  • parentheses: (1+(%f/100)

You can use %d and %f to define positional arguments to the expression. These provided arguments MUST be either a numerical literal or a variable (this includes datasources). For variables, the values are transformed to an interger (rounded) or float, if necessary.

Session Checks (OK/KO criteria)

You can set different counter for assertions or checks on your response data and (if needed) you can cancel sessions based on a criteria.

Checks

You can use session.check() to define OK/KO criteria and every match or mismatch will be counted and shown in the reportings.

In the following example will everything with an HTTP status code above 400 counted as KO and everything else as OK:

session.get("/products");
session.check("products_check", session.lastHttpStatus(), "<=", 400);

The first parameter is the name of the check, next the first value, the comparator (see all comparators here) and the last value.

Assertions

You can use assertions with session.assert(). The assertions are almost like the checks. But if the KO criteria matches, the session will be aborted.

Usage example:

session.get("/products");
session.assert("product_assert", session.lastHttpStatus(), "<=", 400);

This assertion causes a session abort, if the status is (in the example) above 400. Any session abort will be counted and is shown in the reports and any mismatch will be shown as OK.

Session Aborts

Session aborts can be used on different ways and are mainly used to abort a session for a matching criteria. All session aborts will be counted and are shown in the reports.

Abort as session method

You can use session.abort() to abort a session based on a given criteria, for example:

session.get("/token", {
  extraction: {
    jsonpath: {
      "accessToken": "authorization.token",
    }
  }
});

session.if(session.getVar("accessToken"), "=", "", function(c) {
  c.abort("no_access_token");
});

Abort as request option

You can use abort_on_error as option on your requests to abort the session, if the response HTTP status code is 400 or higher. For example:

session.get("/products", {
  abort_on_error: true
});

If you want to disable/enable all abort_on_error you have defined in your session, you can use a default setting (see more settings here):

session.defaults.setAbortOnError(true);

Functions

NOTE: The current function invocation pattern is a preview and may change in the future.

Some additional functions are available via the session.invokeFunction(name, options) helper. See below for details.

Function: Timestamp

The timestamp function allows to get the current unix timestamp (UTC) and store it in a variable.

session.invokeFunction("timestamp", {output: "ts"});
session.get("/?ts=:timestamp", {
  params: {
    "timestamp": session.getVar("ts"),
  }
});

This will send a request to /?ts=1576502911.

Function: UUID4

The uuid4 function generates a 128-bit long unique identifier in the form of five groups (8-4-4-4-12) of hexadecimal characters:

session.invokeFunction("uuid4", { output: "uuid4_result" });
session.get("/?uuid=:uuid", {
  params: {
    "uuid": session.getVar("uuid4_result"),
  }
});

This will send a request to /?uuid=5e31d2c6-6e44-4776-acce-2187eb1b8717.

Functions: Base64, URL, Query and HTML Encoder/Decoder

The following table list a number of functions that can be used with invokeFunction() that all map an input to an output variable.

Name Comment
base64_encode Encodes the input as a base64 string
base64_decode Decodes the base64 input string
html_escape Replaced all html tokens to be safe inside other HTML
html_unescape Decodes a html_escaped string
query_escape Escaped more characters to be safely used in a URL query or a form-encoded payload
query_unescape Decodes a query_escaped string
url_escape Escaped various characters to be safely used in an URL, e.g. the /, but not + or =
url_unescape Decodes a url_escaped string

All functions listed here take an input option, transform it and store it in the variable defined by output. Here is an example for base64_encode:

// base64_encode
session.setVar("foo", "World");
session.invokeFunction("base64_encode", {
  input: `Hello, ${session.getVar("foo")}!`,
  output: "b64_encode_result",
});
session.check("fun_b64_encode", session.getVar("b64_encode_result"), "=", "SGVsbG8sIFdvcmxkIQ==");

Function: ISO Date

The function format_timestamp_as_iso_date takes a unix timestamp and formats it as an ISO 8601 / RFC 3339 date string. Parameters timestamp and output are required. timezone is optional and defaults to the UTC timezone. timestamp and timezone can be dynamic.

session.invokeFunction("format_timestamp_as_iso_date", {
  timestamp: "1604330278", // Unix timestamp (required)
  timezone: "CET", // IANA Timezone, e.g. UTC (default), Europe/Berlin, CET, CEST (optional)
  output: "resultVarName", // Output variable (required)
});

The example above will store 2020-11-02T16:17:58+01:00 in the variable resultVarName and can be accessed via session.getVar("resultName"). Here is an example that calculates an ISO date 1 hour into the future:

session.invokeFunction("timestamp", {output: "current_timestamp"});
session.tools.calcInt("future_timestamp", "%d + (60 * 60 * 24)", [session.getVar("current_timestamp")]);
session.invokeFunction("format_timestamp_as_iso_date", {
  timestamp: session.getVar("future_timestamp"),
  output: "future",
});
session.log("Calculated future date: " + session.getVar("future"));
Icon Support Are you stuck? Or do you have any feedback? Get in touch with us – we are happy to help you.
Icon Schedule a demo Schedule a personal, customized demo. We'll show you around and introduce you to StormForger.
Icon Talk to a human To build and run reliable applications is complex – we know. Schedule a call and we’ll figure things out.

We are using cookies to give you the best online experience. If you continue to use this site, you agree to our use of cookies. By declining we will disable all but strictly required cookies. Please see our privacy policy for more details.


Accept Decline