Scripting support
Overview
Complex test scenarios demand the ability to write complex logic to handle them. This is where the keyword-driven approach starts to break. To solve certain types of problems, you must roll up your sleeves and start writing some good old-fashioned code.
OpenTest allows testers to use JavaScript code just about anywhere in a test, as necessary. The main techniques to embed JavaScript code in a test are:
Script actions.
Ad-hoc JavaScript expressions.
External JavaScript files.
Let’s review each one of these three techniques.
Script actions
Script actions can be used to insert blocks of JavaScript code anywhere in the test flow. The simplest form of a script action looks like this:
- script: var message = "Hello world!"
Most script blocks, however, consist of multiple lines of JavaScript code and typically include a description that will show up in test execution reports. To include larger code blocks we must use the YAML "|" (pipe) syntax that allows string values to span multiple lines:
- description: Click button1 trough button100
script: |
for (var buttonNo = 1; buttonNo <= 100; buttonNo++) {
$runAction("org.getopentest.selenium.Click", {
locator: { id: "button" + buttonNo }
});
}
JavaScript expressions
JavaScript expressions can be used to provide the values for test action arguments or property values in data files. Let’s consider the following example:
- description: Create a new blog post
action: org.getopentest.actions.HttpRequest
args:
url: $data("urls").jsonplaceholder + "/posts"
verb: POST
contentType: application/json
body: |
$json(
{
id: 123,
title: "My fist post"
}
)
The values of the url
and the body
arguments in the code above are both JavaScript expressions that will be evaluated and used to populate those two arguments.
The $script syntax
When the value of an action argument starts with a dollar-prefixed symbol (like $json
or $data
in the previous example), the test actor understands that the expression is JavaScript code, evaluates the expression and uses the result as the value for the argument.
Every now and then, JavaScript expressions must start with something other than a dollar-prefixed symbol. To let the test actor know that such an expression is JavaScript code and not just an ordinary string literal, we must prefix the expression with $script
followed by one or more whitespace characters (space, tab, end-of-line). For example:
- description: Type a random user name into the "username" textbox
action: org.getopentest.selenium.SendKeys
args:
locator: { id: username }
text: $script "user" + (1 + Math.floor(Math.random() * 10000))
External script files
When you need to reuse a piece of JavaScript code across multiple tests or macro actions, the best solution is to implement the logic as one or more JavaScript functions and save it in *.js
files in the scripts
directory of your test repo.
For example, let’s assume we need a JS function to extract the first n words from a string. We can create a function called excerpt
and place it in a helpers.js
in the scripts
directory:
function excerpt(text, wordCount) {
var words = text.split(' ');
words.splice(wordCount || 10, words.length - 1);
return words;
}
To use this function in a test, we first need to "include" the file by using the includes
syntax:
description: Include a JS file and call a function in it
includes: helpers.js
actors:
- actor: ACTOR1
segments:
- segment: 1
actions:
- script: |
var text = "Lorem ipsum dolor sit amet consectetur adipiscing elit"
$log("The first five words are " + excerpt(text, 5).join(", "));
To include multiple JS files, the value of the includes
property can be specified as an array of relative paths:
includes: [moment.min.js, underscore.min.js, helpers.js]
The files will be included in the order they are specified.
Advanced concepts
The OpenTest actor runs JavaScript code using the Nashorn JavaScript interpreter that comes with the Java 8 runtime. There’s a lot of information about Nashorn on the web, so we will not attempt to document its features here. The one thing we would like to touch on is the Java.type()
API that allows access to any Java class from within the JavaScript code.
The sample code below uses four classes from the Java runtime to read a text file and print it’s contents to the console. Please note that we had to store the Java String
type into a variable named JavaString
instead of simply String
, in order to avoid overriding the JavaScript String
type.
- script: |
var Files = Java.type("java.nio.file.Files");
var Paths = Java.type("java.nio.file.Paths");
var System = Java.type("java.lang.System");
var JavaString = Java.type("java.lang.String");
var content = new JavaString(Files.readAllBytes(Paths.get("C:/content.txt")), "UTF-8");
System.out.println(content);
The ability to access any Java runtime class from JavaScript gives testers unlimited power and flexibility. That being said, you should avoid the temptation to "translate" full Java applications to JavaScript. Complex logic is best written in Java, to benefit from static typing and advanced IDE features that increase efficiency and helps minimize human error.