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:

test-repo-path/scripts/helpers.js
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.