Pumlhorse is great for clearly defining workflows or tests. As such, it is often beneficial to move more complex code to their own functions or subroutines, rather than adding it to the script steps. This improves script readability, code reusability, and collaboration. An easy way of thinking of it is separating the what from the how; the script says what needs to happen (test that a user can log in), and the functions define how that happens (create a user in the database, send an HTTP POST request, etc.)

These functions reside in modules which the script can then include. Pumlhorse modules are JavaScript files that look similar to this:

/* myModule.js */
function doSomethingCool() {
    //Do something cool

    .function('doSomethingCool', doSomethingCool);

This module could then be referenced in a Pumlhorse script.

name: Cool script
  - myModule # Loads our module
  - doSomethingCool


For asynchronous functions, Pumlhorse uses promises (as opposed to the Node callback pattern).

Module locations

Modules can be easily loaded from node_modules or puml_modules folders, or by a specific path. See Setting Up a Project for more information


The example above shows a fairly simple way of declaring functions, but they can get more complex. Pumlhorse allows you to use function or parameter names that aren’t possible in JavaScript. For example, the standard math module provides functions named +, -, *, and /. Similarly, the for loop function accepts the in parameter, which is a reserved keyword in JavaScript.

function doThing(val1, val2) {
    //Do something with val1 and val2

    .function('~myFunction~', ['~val1~', '~val2~', doThing]); //This is terrible, don't actually do this
name: Use crazy function names
  - myModule
  - ~myFunction~:
      ~val1~: 123
      ~val2~: 456
  - log: No really, don't name your functions like this


Functions can also use injectors, which are special values that are generated by Pumlhorse (i.e. they are not passed in explicitly in the script). Injectors are typically prefiex with a $ character.


This injector passes the current scope. In addition to providing the current variables, it also provides access to other functions.

Examples: prompt, if, loop functions


$all passes the parameters as a single object or array. This is handy for functions that take a dynamic set of parameters.

Examples: http.setDefaultHeaders, toJson, math operators.


$cancellationToken allows users to cancel long-running processes. For example, the http request methods use $cancellationToken. This object exposes the flag isCancellationRequested and the method onCancellationRequested(callback). Pumlhorse uses a handy utility function for injecting cancellation tokens into promises.


The $logger injector gives easy access to the logging functions.

function myFunction($logger) {
    $logger.debug('entering myFunction');
    //do something
    $logger.debug('ending myFunction');

Examples: http request methods, loop functions, wait

const CancellationToken = require('pumlhorse/lib/util/CancellationToken');

function doLongRunningProcess($cancellationToken) {
    return CancellationToken.await(functionThatReturnsPromise, $cancellationToken);

Custom Injectors

In addition to the standard injectors listed above, you can create your own injectors. The injector consists of a name and a function accepting a scope object.

    .injector('$dbConn', getDatabaseConnection)
    .function('addUser', addUser);

function getDatabaseConnection($scope) {
    if ($scope.__myDbConnection == null) {
        $scope.__myDbConnection = new DbConnection();

    return $scope.__myDbConnection;

function addUser(username, password, $dbConn) {
    return $dbConn.executeQuery(/* ... */);
name: Use injector
  - addUser:
      username: jsmith
      password: hunter2

Note that we did not have to pass a value for $dbConn. It is automatically passed for us.