You can write your scenarios on files and then use those with chatterbox
.
Once you have your file ready, you use that with:
chatterbox -f scenario.yaml
You can specify indifferently a relative or an absolute path. If you want, you can also specify a relative path from where files are read.
chatterbox -p /scenarios -f scenario.yaml
chatterbox
works with the concept that the input yaml
provided is also
the template used to render the output.
The output yaml
keeps the same format of the input, eventually enriched.
Conceptually, the input is used by the chatterbox
's engine
to produce the final rendered output yaml
.
Each response in the input is inserted in the output
at the proper place. All the eventual modifications operated by JavaScript
functions are rendered in the output as well.
conversations:
- host: localhost:8080
requests:
- method: DELETE
uri: foo/bar
conversations:
- host: 'localhost:8080'
requests:
- method: DELETE
uri: foo/bar
response:
code: 401
rtt: 0
body: 'unauthorized'
stats:
requests: 1
categorization:
401: 1
stats:
conversations: 1
requests: 1
categorization:
401: 1
The chatterbox scenario format is pretty straightforward:
that is a yaml
with the properties for the conversation you want to realize.
At the root context, or scenario
context, you define an array of conversations
:
conversations:
- host: 'http://service1.host1.domain1'
requests:
- host: 'https://service2.host2.domain2'
requests:
This means that the tool can be used to send requests against multiple endpoints.
A conversation
is defined as an array of requests
(s):
requests:
- method: HEAD
tag: "one"
- method: GET
tag: "two"
- method: PUT
tag: "three"
- method: POST
tag: "four"
- method: DELETE
tag: "five"
A request
describes a single HTTP
request.
for : 1
auth : aws_v2
method : PUT
uri : foo
queryString: param=value
data: something
response : {}
You can repeat a request
for n
times specifying the for
attribute
in the request's context.
A rendered response
contains the response for a single request
.
code: 200
body: OK
headers:
response-foo-h1: "foo"
response-bar-h2: "bar"
Any conversation or request node in the output yaml
can be referenced in
any subsequent node.
There are currently 3 subscript syntaxes for accessing a conversations and requests:
-
Full explicit path
{{.conversations[i].requests[j].arbitrary.path}}
-
Quick path
{{.[i][j].arbitrary.path}}S
-
By user defined ID
{{id.arbitrary.path}}
The 1
and 2
reference a conversation or a request by its natural index
defined implicitly by the position resulted in the output yaml.
The 3
uses an id
property defined by the user for the conversation or the request.
At every context in the input is always possible to define
an out
node describing what should be rendered in the output.
Valid contexts are:
scenario
conversation
request
response
Suppose that this fragment is inserted inside a response
context in the
input:
out:
dump:
body: true
format:
body: json
This means that in the corresponding output context, the body
field
should be rendered and it should be rendered as json
.
Every time a scenario runs, a brand new JavaScript context is spawned
into V8
engine and it lasts for the whole scenario's life.
Generally speaking, all the fields in the input object can be scripted defining JavaScript functions that are executed to obtain a value.
For example, the queryString
attribute of a request
could be defined
as this:
auth: aws_v2
method: HEAD
uri: bar
queryString:
function: getQueryString
args: [bar, 41, false]
When the scenario runs, the queryString
attribute's value is
evaluated as a JavaScript function named: getQueryString
taking 3 parameters.
The getQueryString
function must be defined inside a file with
extension .js
and placed into the directory where chatterbox
reads
its inputs.
function getQueryString(p1, p2, p3) {
if(p3){
return "foo=default"
}
log(TLV.INF, "getQueryString", "Invoked with: " + p1 + "," + p2);
return "foo=" + p1 + p2;
}
At every context activation, chatterbox
can invoke, if defined, the corresponding
before
and after
handlers.
Valid contexts, where this mechanism works, are:
scenario
conversation
request
response
For example, to define these handlers in the scenario context:
before:
function: onScenarioBefore
args: [one, 2, three]
after:
function: onScenarioAfter
args: [1, two, 3]
conversations: []
You would also define the corresponding functions in the JavaScript:
function onScenarioBefore(outCtx, p1, p2, p3) {
log(TLV.INF, "onScenarioBefore", "parameter-1: " + p1);
log(TLV.INF, "onScenarioBefore", "parameter-2: " + p2);
log(TLV.INF, "onScenarioBefore", "parameter-3: " + p3);
//set an optional tag in the contextual object passed as first argument
outCtx.optionalTag = "my-custom-tag";
}
function onScenarioAfter(outCtx, p1, p2, p3) {
log(TLV.INF, "onScenarioAfter", "parameter-1: " + p1);
log(TLV.INF, "onScenarioAfter", "parameter-2: " + p2);
log(TLV.INF, "onScenarioAfter", "parameter-3: " + p3);
//read the optional tag we set before
log(TLV.INF, "onScenarioAfter", "optionalTag: " + outCtx.optionalTag);
}
Note these handlers are called by chatterbox
with the first argument:
outCtx
always set to the contextual yaml node where the handler is defined.
In the JavaScript environment you can access and manipulate a series of
global objects automatically set by chatterbox
.
Currently, you can access and manipulate the following objects:
out
This object represents the current state of the output. You can access any of its properties and also modify or add entries.
If during the execution of a scenario the control reaches
a JavaScript function: someFunction
; inside that, you can
manipulate the out
global object:
function someFunction() {
out.someTag1 = "myTag1";
out.someTag2 = 2;
log(TLV.INF, "someFunction", "out.someTag1: " + out.someTag1);
log(TLV.INF, "someFunction", "out.someTag2: " + out.someTag2);
}
This would have the effect to produce an output object enriched with:
conversations: []
someTag1: myTag1
someTag2: 2
In the JavaScript environment you can invoke a series of
functions automatically set by chatterbox
.
Currently, you can invoke the following functions:
log
let TLV = {
TRC: 0,
DBG: 1,
INF: 2,
WRN: 3,
ERR: 4,
CRI: 5,
OFF: 6,
}
function someFunction() {
log(TLV.INF, "someFunction", "lorem ipsum");
}
load
load("include.js")
assert
function someFunction(outCtx) {
assert("someFunction [code]", outCtx.code == 200);
}