JavaScript Tests

How to generate JavaScript Tests

JavaScript Tests can be created very easily and quickly, but you need some programming knowledge in JavaScript.

There are many templates available that require only minor customization. For example for SSL tests, HTTP tests, Tests calling OS commands (for example “ping”) and DNS Resolve tests.

If you have no programming knowledge or would like to record entire surfing sessions and run them as a test, we recommend using the HTTP Test Wizard.

Programming Guidelines and Performance

The JavaScript engine used is “Rhino”, which is very performant but somewhat limited in its JavaScript functionality.

There are two major limitations regarding programming Rhino JavaScripts:

  • Variables in loops must not be declared with const. ‘const’ means ‘const forever’ in Rhino. Use ’let’ or ‘var’ instead of ‘const’ in loops.
  • Optional function parameters are not supported. This means that function parameters cannot contain default values. For example, the function x(a = 5) is not valid. Workaround: In such a case, pass an object as function parameter.

We haven’t noticed any further limitations so far - everything else should work as expected.

On the other hand, the execution of JavaScript Test Jobs is extremely high-performant. JavaScript Load Tests with several hundred virtual users can be run on a single Measuring Agent. Additionally, Clusters of Measuring Agents are also supported.
 
This is due to Rhino itself, as hundreds of thousands of instructions are executed per second. It is also crucial that precompiled Java classes can be called directly as JavaScript functions during test execution.
All tests described below run within a defined test framework that requires a few basic prerequisites. This is because all tests can be executed both as a Load Test and as a Synthetic Monitoring Job.
We recommend taking a quick look at the RealLoad Developer Guide to better understand how the tests are structured.

JavaScript Test Editor

With the JavaScript Test Editor, you can create new tests and modify existing ones. You can also clone (deep-copy) a test.

You can access the editor from the Projects menu and the Tests menu:

“alt attribute”

“alt attribute”

There are already many templates for tests that you can use as a basis for creating a new test:

“alt attribute”

Running a First SSL Test

The following will create a test that checks whether an SSL server certificate has expired or is about to expire. This test also measures the TCP connection establishment time and the SSL handshake time – which, if a Synthetic Monitoring Job is configured accordingly, can trigger an alert if the sum these values is too high.

Click “New JavaScript Test” in the editor: “alt attribute”

Proceed as follows:

  • Select the project and resource set in which the test files will be saved. Alternatively, you can create a new project or resource set. We recommend creating a separate resource set for each test.
  • Select the appropriate test template.
  • Enter any JavaScript file name.
  • Enter the test name.

“alt attribute”

The test is now created and ready to run. You can also change the values ​​of “serverHost” and “warnExpiredCertificateHoursBefore”: “alt attribute”

Now click “Save & Execute” and select the Measuring Agent from which the test is executed: “alt attribute”

Then click “Start Test Jobs” and wait a few seconds until the test is completed: “alt attribute”

Then look at the log file of the job, which also contains the output of the JavaScript console: “alt attribute”

“alt attribute”

“alt attribute”

If you click the “Analyze Result” button of the load test job you will see also the TCP connection establishment time and the SSL handshake time: “alt attribute”

Now you might try what happens if you use a tyrannically high value for the expiration warning time

“alt attribute”

“alt attribute”

With the following test result: “alt attribute”

User Input Fields

As you may have already noticed, the hard-coded values ​​for “serverHost”, “serverPort” and “warnExpiredCertificateHoursBefore” in the test before are not practical, since a separate test would have to be created for each “serverHost”.

To make these values ​​dynamic, so-called User Input Fields can be used.

To create such User Input Fields click “Resource Files of Test” in the editor: “alt attribute”

Then click the User Input Fields Icon an call the wizard: “alt attribute”

Enter the User Input Fields and then click “Save File”: “alt attribute”

In “Select Resource Files of Test” select the corresponding file and click “Save” and “Close”: “alt attribute”

First only a console.log statement is added to the code to try out what happens when executing the test: “alt attribute”

“alt attribute”

“alt attribute”

The code of the test can now be modified as follows:

const serverHost = dkfqsRhinoTest.getUserInputFieldValue('host');
const serverPort = dkfqsRhinoTest.getUserInputFieldValue('port');
const warnExpiredCertificateHoursBefore = dkfqsRhinoTest.getUserInputFieldValue('warnExpiredBeforeHours');

“alt attribute”

That’s it

PKCS12 Client Certificates for SSL Test

The previously created SSL test doesn’t support PKCS12 client certificates. To implement this feature, follow these steps:

Click “Resource Files of Test” in the editor: “alt attribute”

Copy or upload the *.p12 file of the client certificate to the resource set of the test. Then set the password for the client certificate: “alt attribute”

“alt attribute”

In “Select Resource Files of Test” select the corresponding file and click “Save” and “Close”: “alt attribute”

In the editor, scroll down to where the SSL test is executed and add the following two lines of code. Replace the file name ‘fischer@dkfqa.com.p12 ’ by our own file:

const clientCertAuthKeyManagers = dkfqsRhinoTest.getClientCertificateAuthKeyManagersByFileName(testContext, 'fischer@dkfqa.com.p12');
sslPortTest.setClientAuthKeyManagers(clientCertAuthKeyManagers);

“alt attribute”

That’s it

HTTP Tests

There are several templates for HTTP tests. We recommend starting with the test template “A Generic HTTP Session” which is explained in more detail below.

If you create such a test from the template, you will see that the test consists of two components:

  • A specific part of the test, that specifies which HTTP requests are executed and how their results are checked.
  • A generic part that executes the test.

Note: There are no hard rules for extracting variables from HTTP responses and using them in subsequent HTTP requests. You can do this in both parts of the test.

Let us first consider the generic part of the test, which is implemented in the JavaScript functions declareStatistics and executeTest: “alt attribute”

Since there is nothing else to do at the moment, let’s look at the specific part of the test. This part of the test is located at the beginning of the source code: “alt attribute”

The specific part of this test only supports two Session Element Types:

  • pageBreak
  • httpRequest

These session element types will now be explained in more detail.

Object pageBreak

  • elementType : ‘pageBreak’
  • statisticId : Must be unique across all elements
  • sleepMillis : Delay time in milliseconds
  • pageDescription : Description

Object httpRequest

  • elementType : ‘httpRequest’
  • statisticId : Must be unique across all elements
  • httpMethod : The HTTP Method such as ‘GET’, ‘POST’, ‘HEAD’ …
  • url : The URL, inclusive query parameters
  • specificRequestHeaderFields : An array of HTTP request header fields that are additionally required for this HTTP request. The array elements are objects of { name : “…”, value: “…”}. It can also be an empty array. Never specify the header field “Content-Length”, this is assigned automatically by the HTTP client when required.
  • hasRequestContent : Boolean flag, specifies whether the HTTP request has a request content.
  • requestContent : Only considered if hasRequestContent === true. The request content as string.
  • requestContentType : Only considered if hasRequestContent === true. The request content type. Example ‘application/json’.
  • validHttpStatusCodes : An array of valid HTTP status codes of the HTTP response. Instead of the array, null can also be specified, then the HTTP status code will not be checked
  • onReceivedResponse : A callback function with the parameters (responseHeaderFields, responseContentType, responseContent)

Callback function onReceivedResponse(responseHeaderFields, responseContentType, responseContent)
The function parameters contain the received HTTP response. This function can be used to check whether the HTTP response is correct, and can also be used to extract variable values ​​from the response. The function must return an object as return value that is structured as follows:

  • responseOk : Required. A boolean flag. If this is false, an error is measured and the current session is aborted. if this is true, the total time of the HTTP call is measured (HTTP request time + HTTP response time).
  • errorMessage : Required if responseOk === false. The error message as string.
  • errorLog : Optional, only considered if responseOk === false. The error log as string which may contain also line terminators.
  • errorContext : Optional, only considered if responseOk === false. The error context as string which may contain also line terminators.

After you have created the code for a session element, you must add the object to the session using sessionElements.push(<element>)

That’s basically all there is to it.

PKCS12 Client Certificates for HTTP Tests

Client certificates for HTTP tests are configured in the same way as described under PKCS12 Client Certificates for SSL Test

Additionally, you must tell the HTTP client which host and port the client certificate applies to.

let clientCertAuthKeyManagers = dkfqsRhinoTest.getClientCertificateAuthKeyManagersByFileName(testContext, '<certificate-filename>');
let parseURL = ParseURL.newInstance(url_1);
let host = parseURL.getHost();
let port = parseURL.getPort();
httpClient.addClientAuthKeyManagers(host, port, clientCertAuthKeyManagers);	// set the PKCS12 client certificate for the specific host and port

Input Files

Input files contain data that are dynamically available during test execution. For example, an input file can contain user accounts, i.e., their login name and password. In contrast to User Input Fields which can only be added once per test, a test can use multiple input files.

We recommend creating the input file in your local environment and then uploading it to the test’s resource set. Alternatively, you can create and edit such a file directly in the test’s resource set.

Example:

# Accounts
Brown ; apr101995
Meier ; topsecret
Miller ; 1234
Smith ; Dogs&Cats$23

To upload an input file click “Resource Files of Test” in the editor. Then click the Upload Icon, upload the file and check the Checkbox of the Input File. After that configure how the lines of the input file are parsed: “alt attribute”

“alt attribute”

The following fields can be entered or checked:

  • Token Delimiter: This is usually a single character, but can be also a string.
  • Comment Tag: Lines which are starting with the comment tag are skipped. You can place also the comment tag within a line which means that the remaining part of the line is ignored. The comment tag is usually a single character, but can be also a string.
  • Cyclic Read: If not checked, then the Lib.getInputFileNextLineTokens(’<file-name>’) function will return null when no further line can be read (at eof). On the other hand, if checked, the file is re-read after the end of file was reached.
  • Randomize Lines: if checked then the order of the lines is randomized each time when the file is read.
  • Trim Values: If checked then whitespace characters are removed from the start and end of the extracted values (tokens).
  • Remove Double Quotes: If checked then double quotes are removed from the extracted values (tokens).

After testing the input file, click ‘Save Settings’ and don’t forget to click ‘Save’ in the ‘Select Resource Files of Test’ popup: “alt attribute”

Then you can enter inside the function executeTest(testContext, virtualUserNumber) the code for getting the tokens of a next line, such as:

let inputFileName = 'UserAccounts.txt';
let nextLineTokensArray = Lib.getInputFileNextLineTokens(inputFileName);
if (nextLineTokensArray === null) {
    console.log(`End of File reached for input file ${inputFileName} - user [${virtualUserNumber}] aborted`);
    // Lib.registerLoopFailed();
    break;
}

You may have also a look at the template Demonstration code for ‘additional job arguments’, ‘user input fields’ and ‘input files’.

Output Files

Output files are very easy to implement, you can simply write to these data line by line without having to declare the files beforehand: “alt attribute”

// the output file will be created if it does not exist yet
Lib.appendLineToOutputFile('out.txt', `user = ${virtualUserNumber}, url = ${url}, status code = ${httpResult.statusCode}`);  

“alt attribute”

Executing Tests using OS Commands

Executing operating system processes, or calling processes that execute operating system commands, is supported:

After starting the process, you can wait for its completion. The process output can then be interpreted and measured as a test result.

For example, a “ping” of a host “alt attribute”

Note that the OS command and the command line arguments must be passed as an array of strings.

// let osProcess = OsProcess.newInstance(['bash', '-c', 'ping -c 3 -W 5 -n ' + host]);
let osProcess = OsProcess.newInstance(['ping', '-c', '3', '-W', '5', '-n', host]);  // execute the ping directly
osProcess.setCollectStdout();
osProcess.start();
Lib.registerSampleStart(statisticId);   // 'All Purpose Interface'
osProcess.waitFor();
let errorLog = "";
let processStdoutLinesArray = osProcess.getStdoutLines();
let averagePingTime = null;
for (let line of processStdoutLinesArray) {
    errorLog = errorLog + line + '\n';
    console.log(line);
    if (line.includes('min/avg/max')) { // rtt min/avg/max/mdev = 0.045/0.075/0.091/0.021 ms
        averagePingTime = Math.round(Number(line.split('/')[4]));
        console.log(`averagePingTime = ${averagePingTime} ms`);
    }
}
if (averagePingTime === null) {
    Lib.addSampleError(statisticId, `ping to host ${host} failed`, "error", `ping failed`, errorLog, "");
    Lib.registerLoopFailed();  // report the loop as failed
    continue;   // abort the loop and continue with next loop
}
Lib.addSampleValue(statisticId, averagePingTime); // 'All Purpose Interface'
loopTime = loopTime + averagePingTime;

DNS Resolver Test

With such a test you can check the IPv4 and IPv6 addresses of a DNS hostname. This test also detects unwanted IP addresses and reports them.

Create a new test by selecting the template Get and verify the IPv4 and IPv6 addresses for a DNS hostname: “alt attribute”

Modify the upper part of the test:

  • dnsServer
  • hostname
  • expectedIPv4Addresses (an array of strings)
  • expectedIPv6Addresses (an array of strings that can also be empty)

That’s it