Sessions

Learn what session is and how to configure it

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 at all, a weight of 1 is assumed for all sessions. To deactivate a session, you can set its weight to 0 or comment it out.

definition.setSessionWeights({
  "new sign up": 3,
  "guest": 1,
  "disabled session": 0,
  // "also disabled: 42,
});

Note:

  • At least one session with a non-zero weight must exist.
  • The default weight of sessions is 1.
  • Use the Arrival Phase session_weights option to overwrite per phase.

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.

Helper Functions

To simulate think times of users in a session, the following helper functions are available wait, waitExp and waitUniform.

wait

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

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.

session.waitExp(23.1);

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){});

Note that the callback function accepts a context that must define the steps to run as part of the loop.

doWhile

While session.times() loops for a number of times, sometimes you need a bit more flexibility: session.doWhile() allows running the block repeatedly while the condition is true.

session.doWhile(function(context) { /* .. */ }, variable, comparator, value);

Note that unlike times() here the first parameter is the stepFunction, as it will always be executed once. The condition comes afterwards. This allows repeating the stepFunction based on results from the function execution itself. Valid comparators are: =, !=, >, >=, < and <= — for equal, not equal, greater than, greater or equal than, less than and less than or equal.

Here is an example:

// Not shown: Create a job and extract job id into variable "jobid"
session.doWhile(function(context){
  context.wait(5);
  context.get("/job/:jobid/status", {
    params: {
      "jobid": session.getVar("jobid"),
    },
    extraction: { jsonpath: {
      "status": "$.job.status",
    }}
  });
}, session.getVar("status"), "!=", "done");

This snippet performs a request to read the status of a job. The status variable is extracted and used to check if the job has already reached the done state. We use != here, since we don’t want to repeat the loop, if the state is equal to done. Note that we use wait() here as well to simulate some wait time.

Note that the callback function accepts a context that must define the steps to run as part of the loop.

if

For conditional statements 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) { });

Note that the callback function accepts a context that must define the steps to run as part of the condition.

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){});

Note that the callback function accepts a context that must define the steps to run as part of the loop.

Transactions

Transactions are a helper to measure the durations of multiple requests as a logic unit of execution. They are reported in the test run details.

session.transaction("outer", function(ctx) {
  ctx.get("/first/request");
  ctx.transaction("inner", function(ctx) {
    ctx.get("/inner");
  })
  ctx.get("/last/request");
});

The example above has one outer transaction containing three requests. The middle request is also wrapped in a separate inner transaction. This will count the request duration of the /inner request towards the inner and outer session.

Note that the callback function accepts a context that must define the steps to run as part of the transaction.

NOTE: StormForge also automatically creates a transaction at the top level of every session definition. Transaction data for sessions is shown separately in the report as “Session Timings”.

Session random branching

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.

chooseByWeight

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"); }],
]);

chooseByProbability

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.

Also, the callback function accepts a context that must define the steps to run as part of the branch.

withProbability

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

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 might run various combinations of branches. Also, the callback function accepts a context that must define the steps to run as part of the branch.

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.

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

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.

TLS Version Pinning

When performing requests over HTTPS each client selects the newest TLS version (TLS 1.3 at the moment). If you need to limit the selectable TLS versions, you can configure the tls_version option to mimic older mobile clients or applicances that do not have access to TLS 1.3.

session.setOption("tls_version", {
  min: "1.0",
  max: "1.2",
});
session.get("https://example.com/", { tag: "tls_12_request" });

This example will select TLS 1.2 (instead of 1.3) when performing the TLS handshake with the server to negotiate the TLS version (assuming the server provides TLS 1.2).

The following values are allowed for min and max:

  • 1.0
  • 1.1
  • 1.2
  • 1.3

By default min and max are set to the lower and upper TLS version limits for the performance testing environment you are using. Note that the platform limits will supersede any TLS version settings in session options. That is, you cannot pin a TLS version that is outside of the supported TLS versions for your environment.

Please contact us if you have specific TLS requirements not covered by these configuration options.

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). This returns a placeholder that can be used to represent the value.

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

The placeholder can be used in all places where you actually want to send the value. It will be replaced during the test run execution with the actual value. See the Javascript Runtime for more details.

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

Since our 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.

For more details, check out our Checks & Assertions Guide.

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.

For more details, check out our Checks & Assertions Guide.

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:

session.defaults.setAbortOnError(true);

The cookie jar API enables you to set cookies that will persist for the duration of a session, delete cookies that have been set manually or by remote servers, or reset the cookie jar and clear all set cookies. This API provides three functions to perform these tasks, all of which exist in the cookies sub-object of the session object: session.cookies.add( options ), session.cookies.delete( options ), and session.cookies.reset().

Adding cookies

session.cookies.add( options ) adds cookies to a session that will persist for the duration of the session unless deleted or reset. The options parameter supports the following fields:

Field Description
name Cookie name. Required.
domain Cookie domain. Required.
value Cookie value. Optional.
path Cookie path. Optional. Defaults to “/”
secure Security setting. Boolean, acceptable values are true and false. Optional, defaults to false. Secure cookies will not be sent on unsecure (http) connections.

Deleting cookies

session.cookies.delete( options ) deletes cookies from a session. These cookies may have been added manually using session.cookies.add( options), or set by a remote server during an earlier http(s) request. The options parameter supports the following fields:

Field Description
name Cookie name. Required.
domain Cookie domain. Required.
path Cookie path. Optional. Defaults to “/”

Please note that in order to sucessfully delete a cookie, all of the options parameters must match the cookie you are trying to delete. If delete is called with incorrect parameters or called on a nonexistent cookie, nothing will happen.

session.cookies.reset() clears the cookie jar, where all cookies for a session are stored. A reset will delete all cookies that have been set, whether manually or by a remote server.

Example

definition.session("cookiejar_example1", function(session) {
  // Add a cookie to the cookie jar for the session
  session.cookies.add({
      domain: "testapp.loadtest.party",
      name: "foo",
      value: "bar",
  });
  // Check to see if we set the cookie successfully
  session.get("http://testapp.loadtest.party/cookie/get?cookie=foo", {
      tag: "cookiejar_add_check",
  });
  // Delete the cookie we added
  session.cookies.delete({
      domain: "testapp.loadtest.party",
      name: "foo",
  });
  // Alternatively, you could use the reset function to delete all set cookies
  session.cookies.reset();

});

Functions

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"));

Functions: HMAC (SHA1, SHA256)

We support two functions to generate a HMAC: hmac_sha1 and hmac_sha256.

const key = "3031323334353637383941424344454630313233343536373839414243444546" // 0123456789ABCDEF0123456789ABCDEF hex encoded
session.invokeFunction("hmac_sha1", {
  message: "Hello World",
  hexkey: key,
  output: "sha1_hmac_result",
});
session.assert("hmac_sha1_check", session.getVar("sha1_hmac_result"), "=", "43ac45386f88434faa76f98972e2ddfe42bb7c4b");

session.invokeFunction("hmac_sha256", {
  message: "Hello World",
  hexkey: key,
  output: "sha256_hmac_result",
});
session.assert("hmac_sha256_check", session.getVar("sha256_hmac_result"), "=", "41a8df467d07c735dcbcaa2f2ef3937aefc8346325c567f79f7f654cbcb432eb");

// Use output_format to get base64 instead of hex encoded output
session.invokeFunction("hmac_sha256", {
  message: "Hello World",
  hexkey: "012345", // STILL hex encoded!
  output_format: "base64",
  output: "sha256_hmac_result",
});
session.assert("hmac_sha256_check", session.getVar("sha256_hmac_result"), "=", "Uz0HW/SSlDCaGBrgvU4FtSX0ZRy1H7PjL/nxjpjTdpY=");

This examples shows both function accepting three parameters: message, hexkey and output. The message is the input to the HMAC function, while the hexkey is a hex encoded representation of the key data. output is the variable that will be used to store the result, hex or base64 encoded. To use the result (e.g. in requests) use session.getVar(). You can also control the output encoding via the optional output_format parameter (defaults to hex), which you can set to hex or base64.

Functions: SHA256

If you need to hash some input, you can use the sha256 hash function. If you need other algorithms, tell us!

session.invokeFunction("sha256", {
  message: "Hello World!",
  output: "hashout",
});
session.assert("sha256_check", session.getVar("hashout"), "=", "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069");

You can also control the output encoding via the optional output_format parameter (defaults to hex), which you can set to hex or base64.

Last modified March 20, 2024