-
Notifications
You must be signed in to change notification settings - Fork 9
Action reference
Used to logically combine an array of actions with a common on_success
/on_failure
or for visual/logical clarity when nesting with other boolean operators. In order for the and
action to succeed, all nested actions must succeed. Short-circuit evaluation is tracked and nested actions are processed sequentially, as soon as one action fails, subsequent actions will not be processed.
- and:
- action1
- action2
...
- actionN
Captures the body(text) of the HTTP response into the session using the provided session key. Useful for saving the contents for output or later processing/manipulation. Since the body
action operates against the response of an HTTP request, it is only valid when nested within a request's response
property.
- request:
# ...
response:
- body: session_key
# Now print out the body
- print: <% session_key %>
Aliases: equal
, equals
, greater_than
, greater_than_or_equal_to
, less_than
, less_than_or_equal_to
, not_equal
, not_equals
Compares each element in an array to the following element using the specified operator.
- compare:
array: [ item1, item2, ... itemN ]
operator: "=" # or <, >, <=, >=, !=
If no operator is specified, the default operator is "=".
Example:
Alias for merge
with override: false
The most common use case for defaults
is to set default values in your script that can be overridden by passing in different values on the command-line or from a data file.
- defaults:
user: John
category: books
- print: <% username %> likes <% category %>
Running this with normally will print:
John likes books
but if the --qs category=cars
cli option is passed in, it will print:
John likes cars
As defaults
is an alias for merge
, the above would be normalized to the following:
- merge:
data:
user: John
category: books
override: false
Causes the script to sleep for the specified number of milliseconds. The value should be a number or a numeric string value.
# Sleep for 300 milliseconds
- delay: 300
NOTE: delay
can be combined with the random interpolation pipe to select a random length of time to delay (useful for simulating user think-times).
- delay: <% '3000-5000' | random %> # Random delay between 3000 and 5000 milliseconds
- delay: <% '400' | random %> # Random delay between 1-400 milliseconds
Alias for print
Alias for compare
with operator: =
- equals: [ item1, item2, item3 ]
will be normalized into
- compare:
array: [ item1, item2, item3 ]
operator: ">="
The fail action causes an error to be thrown with a specific message. This is most useful when combined with if/on_success/on_failure or other conditional branching statements.
- fail: Your request failed with error message: <% error %>
NOTE: a literal boolean "false" value for an action will be normalized (converted) into a fail action with a default message. This may be of limited use, but the hyperpotamus unit tests for the boolean operators make use of this for validation.
- false # Will be converted to "- fail: Explicit false"
The function action allows you to specify a custom javascript function to be executed. The custom function can capture data, run complex validations, or modify other aspects of the current script flow.
The action can either be a direct javascript function value, or must have a function
property which is a javascript function. Javascript functions can be specified in YAML using the !!js/function syntax.
NOTE: JSON is incapable of representing literal functions, so in this case, YAML must be used.
The first parameter passed to your function is always the hyperpotamus context. The hyperpotamus context exposes information about the current request, response, session, and some other utility methods. (TODO - document the context object and available features.)
If the function has a single parameter, then it will be invoked synchronously. If the function returns void/null, the hyperpotamus engine will consider your invocation successful. Otherwise, any returned object will be considered an error which will be displayed to the user along with other diagnostic information.
If the function takes a second parameter, then your function is executed asynchronously. The additional parameter is the callback function that you need to invoke to signify completion of your function. In typical "node.js tradition", the first parameter to the callback is an error object. Leave it null to indicate successful execution.
Ideally error objects returned from custom functions should include a .message property for display to the user. Your error object may also specify a goto
property which is either the name of the next request to execute or one of the special jump_keys (SELF, END, NEXT). As a special case if a string value is returned as the error, it will be treated as the message.
The javascript context this
for the custom function will be bound to the action script element itself, so any additional properties on the action can be accessed from inside the function. This is a useful way to pass interpolated values or other "configuration" to your function.
External modules can be imported and used by your function using the optional imports
property. imports
should either be a string, array of strings, or an object with string property values. Each of the string values will be passed to a node.js require
statement and the corresponding value in the imports
property will be replaced with the imported module.
- !!js/function >
function(context) {
if(context.response.status > 200)
return "Unexpected status code";
}
- value_to_hash: <% session_value %>
imports:
crypto: crypto
function: !!js/function >
function(context, callback) {
var md5 = this.imports.crypto.createHash("MD5"); // from the imports property above
md5.update(this.value_to_hash); // the 'this' context is bound to the action element itself.
context.session["hash"] = md5.digest("hex");
callback();
}
}
NOTE: A common pitfall that I find when defining javascript functions in YAML is that tab-indentation is technically not allowed. I have put some workarounds in hyperpotamus to replace initial tabs with spaces, but YMMV. If you are getting strange errors from the YAML parser, this may be your problem.
NOTE: The function action is considered "unsafe" and so hyperpotamus must be set to allow unsafe actions.
Causes the script execution to jump to the specified named step or one of the special jump targets. Any step in a hyperpotamus script can be named (using a name
property). Names can help document what the steps in your script are doing, but more importantly, they also serve as the labels for goto
actions to target. Additionally the following special jump targets can also be the target of a goto
:
- SELF - Repeats the current step in the script
- NEXT - The following step in the script (skips past any remaining actions in the current step)
- END - Stop processing the script and exit
goto
can be used explicitly like so:
- actions:
goto: named_request
- actions:
fail: "This should never get executed"
- name: named_request
actions: noop
but it is also used as part of iterate
, on_success
, on_failure
options. Any action can have an on_success or on_failure property. This property can be a single action or an array of actions that are processed if the parent action succeeds or fails. As a shortcut, if the on_success
or on_failure
property values are a string, then this will be normalized to a goto statement with the string value as the target.
- fail: Fail for some reason
on_failure: END
# is equivalent to
- fail: Fail for some reason
on_failure:
goto: END
Alias for compare
with operator: >
- greater_than: [ 3, 2, 1 ]
will be normalized into
- compare:
array: [ 3, 2, 1 ]
operator: ">"
Alias for compare
with operator: >=
- greater_than_or_equal_to: [ 3, 2, 2, 1 ]
will be normalized into
- compare:
array: [ 3, 2, 2, 1 ]
operator: ">="
The headers action allows you to validate or capture content in HTTP response headers.
To match a regex (or capture named groups from a regex)
- headers:
header-name1: !!js/regexp /regex to match or (:<named_capture>capture)/igm
# is equivalent to
header-name1:
regex:
pattern: "regex to match or (:<named_capture>capture)"
options: igm # ignore-case, global-match, multi-line
To capture the full header value into a variable
- headers:
header-name1: variable_name
# is equivalent to
header-name1:
capture: variable_name
To make sure the header value matches some exact text
- headers:
header-name1:
text: Matching text
The if
action allows for a specific action to be executed and if the action succeeds, the then
actions will be executed. If the action fails, the optional else
actions will be executed.
The if
action to be tested can be a simple action which test for success or failure or it can be a boolean combination of other actions using and
, or
, or not
.
- if:
compare: [ <% username %>, "phillip" ]
then:
emit: "Welcome back!"
else:
- emit: "Not authorized."
- goto: END
The above is actually equivalent (but perhaps a little bit clearer) to
- compare: [ <% username %>, "phillip" ]
on_success:
emit: "Welcome back!"
on_failure:
- emit: "Not authorized."
- goto: END
The iterate action takes the name of an array variable (or an array of array variable names) and increments the current tracking index for each of the arrays. If all of the arrays were successfully iterated (meaning they had at least one more element in them), then the next
value is used as the target of a goto
statement. If any of the arrays were exhausted (or empty), then processing will continue with the next action/step in the script.
The default value for next
is SELF
, which will cause the current step to be repeated after advancing the array indices.
- iterate: users
- iterate: [ users, passwords ]
next: step_to_repeat
NOTE: The current index of an array being iterated is kept in a session variable called [array_name].index. Before the array is iterated, this value most likely does not exist. A non-existant value for the .index
property is treated as a 0-value (meaning the first element in the array). Each time the array is iterated, this value is incremented until the end of the array is reached. When the array has been exhausted (no more elements), the .index
variable is removed from the session. At that point, another iterate
action for the same array would start over at the beginning.
Iteration of an array is particularly useful when combined with the array access format specifier to print out the "current" item in an array.
- actions:
set:
product_id: [ '123', '456' ]
- request:
url : http://www.somesite.com/product_description?product_id=<% product_id | current | urlencode %>
response:
iterate: "product_id"
This would execute the second step once for each of the product_id
values.
The jquery action will execute a JQuery selector against the page contents, and for each matching element, capture one or more session items. The keys of the "capture" property-map are used to indicate to which session key you are capturing. The value of the capture keys can be either "@attribute", "text", "innerHTML", and "outerHTML". "html" is an alias for "outerHTML". If the value is a single element in an array, then all matched values will be captured into an array. If the value is not enclosed in an array, then only the last matching item on the page is captured.
{ jquery: "table.main td:nth-of-type(2) a", capture: { href : [ "@href" ], text : [ "text" ], html : "outerHTML" };
If this action were processed against a page that had three matches, the resulting href and text properties would have 3 elements, but the "html" element would only have the last matching element (because it is not surrounded with the '[ ]').
The json
action can be used to capture or check for the existence of an element in a JSON response. The json
action uses JSONPath to identify the target elements in the JSON.
To capture values or arrays of values, the json property should be a key/value collection. If the value is wrapped in array markers "[,]", then all instances of the value will be captured. Otherwise, only the first instance will be captured. Multiple key/value mappings can be captured in a single json
action instance.
- json:
keys: [ "$.nodes.*.key" ] # Captures an array of "key" properties
first_key: "$.nodes.*.key" # Captures only the first "key"
Or alternatively, a single expression can be passed as an assertion or test.
- json: "$.nodes.success[?(@=true)]"
By default, if the value does not exist in the JSON, an error is thrown. The key can be prefixed with a "?" to make the capture value optional and return blank if the value could not be found.
Alias for compare
with operator: <
- less_than: [ 1, 2, 3 ]
will be normalized into
- compare:
array: [ 1, 2, 3 ]
operator: "<"
Alias for compare
with operator: <=
- less_than_or_equal_to: [ 1, 2, 2, 3 ]
will be normalized into
- compare:
array: [ 1, 2, 2, 3 ]
operator: "<="
- body: captured_body
- md5: <% captured_body %>
Aliases: set
, defaults
merge
will merge (assign) the values of the .source
object into the specified .target
object preserving or overwriting existing values according to the .override
property. Nested objects and arrays will be merged recursively. If no value or reference is specified for the .target
property, then the root-level session scope is targeted. .override
defaults to false.
- merge:
source:
id: 12345
user: jsmith
pizza:
toppings: [ cheese, pepperoni ]
crust: deepdish
override: true
target: user.order
In this example, the properties under .source
would be assigned to the user.order object in the session, overwriting any existing values that overlap.
NOTE: There is currently a limitation that the target object must already exist in the session, even as an empty object.
The simplest (and most common) use-cases for merge
are through the use of the set
and defaults
aliases to assign session variables at the top-level session scope.
The defaults
and set
aliases set the .override
flag accordingly and always target the root session scope.
The optional .target
property may be an <%! object_reference %> or the path to an object within the session context.
This action doesn't do anything. Seriously. :) Occasionally that becomes useful. Sometimes it is useful just as a placeholder, sometimes it is useful when combining with other logical operators. An action of literal true
is equivalent to a noop
.
- true
# is equivalent to
- noop: "anything" # or anything that is not "falsy" in javascript
Used to reverse the success/failure of a nested action(s).
- not:
status: 500
Alias for compare
with operator: !=
- not_equals: [ true, false ]
will be normalized into
- compare:
array: [ true, false ]
operator: "!="
Succeeds if any of the nested actions are successful. Short-circuit evaluation is tracked, so as the nested actions are processed in sequence, as soon as any action succeeds, the rest of the actions will not be processed.
- or:
- status: 302
- text: redirect
The print action sends text content to the output stream. All output is sent to the default output stream unless a .channel
is specified (see below). emit
is an alias for print
.
- print: Hello world!
- emit: Hello <% name %>, today is <% day_of_week %>.
- emit: <% name %>,<% date %>,<% count %>
By default, the hyperpotamus command-line application echoes emit statements to stdout. If the --out command-line parameter is used to specify a filename, then the emit statements will be written to the specified file instead.
NOTE: Custom applications that use hyperpotamus as a library can listen for emit events on the processor and collect the data. There is a separate "channel" property on emit events that can optionally be specified in the action. When the emit callback event is passed, the value of the channel is passed as an additional parameter. This can allow for separate output streams for different purposes (i.e. errors, messages, and report values).
safe: false
Prompts the user for input values to be added to the session. Blocks the script waiting for user-input.
- prompt:
username: "Please enter your username"
password:
message: "Enter your password"
hidden: true
Unless required: true
is specified, the user will not be prompted for a value if the target context session variable is already set. This can be useful for writing interactive scripts where users are prompted for any needed values, but if the user already knows the variables, they can be passed in. There are quite a few options for the prompt configuration.
{
description: 'Enter your password', // Prompt displayed to the user. If not supplied name will be used.
type: 'string', // Specify the type of input to expect.
pattern: /^\w+$/, // Regular expression that input must be valid against.
message: 'Password must be letters', // Warning message to display if validation fails.
hidden: true, // If true, characters entered will either not be output to console or will be outputed using the `replace` string.
replace: '*', // If `hidden` is set it will replace each hidden character with the specified string.
default: 'lamepassword', // Default value to use if no value is entered.
required: true // If true, value entered must be non-empty.
before: function(value) { return 'v' + value; } // Runs before node-prompt callbacks. It modifies user's input
}
For more information, see the documentation for the prompt module
See named-regex for an example. The regex action will either validate that an HTTP response matches a given pattern or, if the pattern contains named groups, will also capture values into the session with keys named after the groups. If the /g (global) regex option is specified, the results will be captured as an array.
- regex: "/Welcome, (:<name>\w+)\./i"
NOTE: As a shortcut, a RegExp object can also be passed directly instead of an object. If the action is a literal string that starts and ends with "/" characters, it will also be interpreted as a regex.
- regex: !!js/regexp /Welcome, (:<name>\w+)\./
The request action sends an HTTP/HTTPS request and executes any actions in the .response
property once a response is received. For full details, see the Requests page.
The syntax for the request action is:
- request: {request_options}
response:
- {response_action1}
...
- {response_actionN}
The {request_options} can be a simple string (which will be interpreted as the URL), or it can be the full set of options as supported by the request module.
!! NOTE: If you are expecting to download/save binary files, please see this tip.
Causes the downloaded contents for the HTTP response body to be saved to the specified file. Any parent paths will automatically be created. Currently there are no safety checks on the filenames, so it his STRONGLY ENCOURAGED to perform some type of encoding on the filenames to prevent special characters. This also does not prevent users from putting in bad paths such as ".." so be careful which inputs you trust for this.
- save: "output/<% customer_name | urlencode %>.pdf"
Alias for merge
with override: true
Set values in the context session.
- set:
user: John
category: books
- print: <% username %> likes <% category %>
As set
is an alias for merge
, the above would be normalized to the following:
- merge:
data:
user: John
category: books
override: false
Because set
is an alias for merge
, one perhaps unexpected behavior for new users is that if the .source
has a sub-object and the .target
has a sub-object with the same name, any existing, but non-overlapping values on the target sub-object will remain. i.e. The entire sub-object is not replaced, but is merged with values from the .source
Verifies that the HTTP status code matches the expected value or one of the values in an array.
- status : 200
or
- status: [ 200, 204 ]
Ensures that the specified text shows up in the HTTP response.
- text: "Hello, <% name %>"
Executes an XPath expression against the HTTP response (if the returned format was valid XML) and stores the results in the session key. The key for any properties is the session key where the results will be saved. The right-hand side should be an XPath expression that will be evaluated against the response document. If the right-hand-side is wrapped in an array, all matches will be returned as an array, otherwise, only the first match is returned as a scalar.
- xpath:
username: "/user/@name"
order_ids: [ "/user/orders/@id" ]
Hyperpotamus Documentation - home