Appearance
Scripts
Yaade provides the ability to execute scripts written in JavaScript. There are three types of scripts:
- Request Scripts
- Run before a request is executed
- Response Scripts
- Run after a request is executed
- Job Scripts
- Can be scheduled periodically or run manually
While these three types of scripts share most of their functionality, there are some differences in the context in which they are executed. The following sections will provide an overview of the available commands and the execution order of the scripts.
Script Syntax
Scripts are defined in regular JavaScript. This means you can use all the features of JavaScript like variables, loops, and functions.
javascript
// valid commands
const x = 2 * 5
let y = "hello world"
const s = y.split(" ")
if (s.length === 2) {
y = `foo=${s[0]}`
}
const j = s.join("_")
for (let i = 0; i < 10; i++) {
x += i
}
But there are some commands that are blocked on purpose to prevent malicious behavior:
javascript
// invalid commands
import "..."
window.localStorage
console.log("hello world")
const cookies = document.cookie
alert("hello world")
WARNING
Because Yaade uses JavaScript template literals to interpolate environment variables, template literals inside your scripts might be overwritten by your environment if keys match. It is therefore generally discouraged to use them in scripts.
Special Commands
Yaade Scripts provide some special commands to interact with the environment, the request, and the response. The following sections will provide an overview of the available commands.
Set Environment Variables
The env.set
function allows you to set a variable in the currently selected environment.
javascript
env.set("hello", "world")
env.set("foo", "bar" + "buz")
Get Environment Variables
Just like you are able to set an environment variable env.get
allows you to get an existing variable.
javascript
const foo = env.get("bar")
JSON Path
The jp
function allows you to traverse a JSON object using a JSON-Path expression. Check out the original proposal or the jsonpath npm library for detailed examples.
INFO
jp only returns the first value that matches the JSON path expression
javascript
env.set("token", jp("$.access_token", res.body))
Logging
The log
function provides a basic way for logging. It logs into the console and prepends some information about execution environment, like request id and environment name. The structure of this info is [<script type>: <request id> - <environment>] <logging-content>
. When logging in job scripts, the logs are stored in the job history.
javascript
const o = {"foo": "bar"}
log("hello world", o)
// output: [Request Script: 2 - dev] hello world {foo: 'bar'}
DateTime
The DateTime
object allows you to work with dates and times in scripts. It is a direct reference to the DateTime
object of Luxon. Check out the Luxon example page for detailed examples.
javascript
const now = DateTime.utc().toISO()
// result: 2023-03-26T12:43:37.956Z
random-js
We support random-js
by referencing rand
. You can read the full random-js docs here. Below are some useful commands taken from their documentation.
rand.integer(min, max)
: Produce an integer within the inclusive range [min
,max
].min
can be at its minimum -9007199254740992 (2 ** 53).max
can be at its maximum 9007199254740992 (2 ** 53). The special number-0
is never returned.rand.real(min, max, inclusive)
: Produce a floating point number within the range [min
,max
) or [min
,max
]. Uses 53 bits of randomness.rand.die(sideCount)
: Same asr.integer(1, sideCount)
rand.uuid4()
: Produce a Universally Unique Identifier Version 4.rand.string(length)
: Produce a random string using numbers, uppercase and lowercase letters,_
, and-
of lengthlength
.rand.string(length, pool)
: Produce a random string using the provided stringpool
as the possible characters to choose from of lengthlength
.rand.hex(length)
orr.hex(length, false)
: Produce a random string comprised of numbers or the charactersabcdef
of lengthlength
.rand.hex(length, true)
: Produce a random string comprised of numbers or the charactersABCDEF
of lengthlength
.rand.date(start, end)
: Produce a randomDate
within the inclusive range of [start
,end
].start
andend
must both beDate
s.
Base64 encoding/decoding
You can encode and decode strings to and from base64.
- Encode to base64:
btoa("hello:world")
results inaGVsbG86d29ybGQ=
. - Decode a string from base64 use
atob('aGVsbG86d29ybGQ=')
results inhello:world
.
Execute another Request
A very powerful tool is to execute other requests from within a script. This gives you the ability to chain requests and build complex workflows. The signature of the command is very simple:
javascript
const exec: (requestId: number, envName?: string) => Promise<unknown>
Because exec
is asynchronous you can use await
to wait for the result of the command.
javascript
// A simple example to extract a JWT from the response of another request
try {
const res = await exec(15, env.name)
if (res.status === 200) {
env.set("token", jp("$.accessToken", res.body))
}
} catch (e) {
log(e)
}
INFO
The id of a request can be found by clicking on the options of a request in the sidebar and then clicking Copy ID
.
The environment name should match an existing environment in the collection of the request to be executed. To use the same name as the currently selected environment of the source request, use env.name
. If left blank, no environment will be used for interpolation.
INFO
Yaade also executes the request and response script of the target request. This means you can build a chain of request to be executed. Note that to prevent exec loops, a max. depth of 5 requests is supported before the exec command fails. It is also possible to manipulate the environment of the target request via the response script of the target request.
The result of a successful exec call is the response of the target request. It is the same object as the res
object in response scripts.
javascript
const res = await exec(12)
const headers = res.headers
const contentType = headers["Content-Type"]
const body = res.body // is of type string
const status = res.status
if (status === 200) {
env.set("type", contentType)
}
Tests
We use Jasmine to define test suites in scripts. Tests can only be used in response and job scripts.
The following example shows how to write a simple test suite:
javascript
describe('Create and retrieve an entity', function() {
it('should create an entity', async function() {
// post entity request
try {
const res = await exec(1783, env.name)
expect(res.status).toBe(201)
const id = jp("$.id", res.body)
env.set("id", id)
} catch (e) {
fail(e)
}
});
it('should retrieve the entity', async function() {
// get entity request (id is injected from the environment)
try {
const res = await exec(1784, env.name)
expect(res.status).toBe(200)
const resId = jp("$.id", res.body)
const envId = env.get("id")
expect(resId).toBe(envId)
} catch (e) {
fail(e)
}
});
});
Here are some basic Jasmine operations that you can use. If you want to use more advanced features, please refer to the Jasmine documentation.
javascript
describe("Basic Jasmine Test Suite", function() {
// 1. Simple Expectations
it("should check if true is true", function() {
expect(true).toBe(true);
});
it("should check if a number equals another number", function() {
var a = 10;
expect(a).toBe(10);
});
it("should check if two objects are equal", function() {
var obj1 = { name: "John", age: 30 };
var obj2 = { name: "John", age: 30 };
expect(obj1).toEqual(obj2);
});
// 2. Matchers
it("should check if a value is truthy", function() {
var a = true;
expect(a).toBeTruthy();
});
it("should check if a value is null", function() {
var a = null;
expect(a).toBeNull();
});
it("should check if an array contains a specific item", function() {
var fruits = ["apple", "banana", "mango"];
expect(fruits).toContain("banana");
});
// 3. Setup and Teardown
var value;
beforeEach(function() {
value = 42;
});
afterEach(function() {
value = 0;
});
it("should use the value set in beforeEach", function() {
expect(value).toBe(42);
});
it("should reset value after test runs", function() {
value = 100;
expect(value).toBe(100);
});
it("should reset value after the test has been run", function() {
expect(value).toBe(42); // Value should be reset by afterEach
});
});
Request Scripts
Request scripts are run before a request is executed. This makes them useful if you need to build your environment before executing a request. For example when a request needs a fresh access token you can use the request script to fetch the access token and put it into the environment to be used by the request. Request scripts are always executed in the browser of the calling user. To add a request script, select a request and go to the Request Script
tab.
Access the Request
The req
object exposes the request that will be sent. The request itself is immutable though and can only be changed by changing the environment. The request script is executed before the interpolation step, therefore the req
object won't contain the resolved environment variables.
javascript
const uri = req.uri // string of the request URI
const headers = req.headers // map of key value pairs
const body = req.body // body as string
const method = req.method // string of the request method for REST requests
Response Scripts
Response scripts are run after a request is executed. They are usually used to extract information from the response and validate it. You could for example use the response script to check if the status code was 200 or else throw an error. Response scripts are always executed in the browser of the calling user. To add a response script, select a request and go to the Response Script
tab.
Access the Response
The res
object exposes the response received when executing the request. It contains the body as a string, headers as an object with keys and values and status code as integer.
javascript
const headers = res.headers
const contentType = headers["Content-Type"]
const body = res.body
const status = res.status
if (status === 200) {
env.set("type", contentType)
}
Execution Order of Request and Response Scripts
There are four types of scripts that are executed in the following order:
- Collection-level Request Script
- Request Script
- Response Script
- Collection-level Response Script
As other requests can be executed in the request scripts, the scripts are executed in a depth-first manner. This means that if a request script executes another request, the request script of the target request is executed before the response script of the source request.
Example:
javascript
// Request Script of Request 1
await exec(2)
// ... more code
Now if request 1 is executed, the execution order is as follows:
- Collection-level Request Script of Request 1
- First part of Request Script of Request 1
- Collection-level Request Script of Request 2
- Request Script of Request 2
- Response Script of Request 2
- Collection--level Response Script of Request 2
- The rest of Request Script of Request 1
- Response Script of Request 1
- Collection-level Response Script of Request 1
Job Scripts
Job Scripts are server-side scripts that are executed in an isolated GraalVM JavaScript environment on the server. This allows for script execution without user interaction. Job scripts can be scheduled using cron expressions for recurring tasks such as smoke tests, cleanup jobs, or data processing.
Each run of a job script creates a result that contains the set of logs generated during the execution as well as a test suite. The result can be viewed in the job history.
Create a new Job Script
Just like a request, a job script is always part of a collection. To create a new job script, click on the ••• button in the collection sidebar and select New Job Script
. Enter the name of your script and click Create
.
Manual Run
You can manually run a job script by clicking the RUN
button in the top right corner of the script panel. This will execute the script and show the result in the job history.
Cron Scheduling
Job scripts can be scheduled using cron expressions. We use UNIX cron expressions to define the schedule of a job. The cron expression consists of five fields that represent the following:
- Seconds (0-59)
- Minutes (0-59)
- Hours (0-23)
- Day of the month (1-31)
- Month (1-12)
The following cron expression runs the job every day at 3:30 AM:
plaintext
30 3 * * *
┬ ┬ ┬ ┬ ┬
│ │ │ │ └───── Day of the week (any)
│ │ │ └─────────── Month (any)
│ │ └───────────────── Day of the month (any)
│ └─────────────────────── Hour (3 AM)
└───────────────────────────── Minute (30)
The following cron expression runs the job every Monday at 3:30 AM:
plaintext
30 3 * * 1
The following cron expression runs the job every 15 minutes:
plaintext
*/15 * * * *
To enable a scheduled job, click the ▶
play button. To disable the job, click the ⏸
pause button. Select the environment in which the job should be executed. Choose NO_ENV
if you don't need an environment.
Callback
You can register a callback that is executed after the job script has finished. This is useful when automating workflows. The callback function has access to the test report of the job script.
javascript
registerCallback(async (report) => {
env.set("report", JSON.stringify(report))
if (!report.success || report.jasmineReport?.status === "failed") {
// you can execute another request here, for example
// have a request that sends a notification to a slack channel
await exec(1790, env.name)
}
})
typescript
type TestReport = {
success: boolean;
executionTime: number;
jasmineReport: JasmineReport;
logs: Log[];
error: string | null;
envName: string;
};
type Log = {
message: string;
timestamp: string;
};
type JasmineReport = {
suites: JasmineSuite[];
status: string;
};
type JasmineSuite = {
id: string;
description: string;
fullName: string;
parentSuiteId: string | null;
failedExpectations: any[];
deprecationWarnings: any[];
duration: number;
properties: any | null;
status: string;
specs: JasmineSpec[];
};
type JasmineSpec = {
id: string;
description: string;
fullName: string;
parentSuiteId: string | null;
failedExpectations: JasmineExpectation[];
passedExpectations: JasmineExpectation[];
deprecationWarnings: any[];
pendingReason: string;
duration: number;
properties: any | null;
debugLogs: any | null;
status: string;
};
type JasmineExpectation = {
matcherName: string;
message: string;
stack: string;
passed: boolean;
};
Programmatic Run
You can run a script with an access token from an external source. The script is executed in the context of the user that created the token. Check the access token section for more information on how to create an access token.
bash
curl --request POST \
--url 'http://localhost:9338/api/v1/script/12/run?env=staging' \
--header 'Authorization: Bearer yaade_<access-token>'
# response: {"success": true, "logs": [{ ... }]}
The env
query parameter is optional. If it is not set, the script is executed in the default environment.
The response is the result of the script. Check the callback section of the script to see the specific response structure. The response status will be 200
only if the script was executed successfully and all the tests passed.
Ownership
Each time a job script is run as a cron script it is executed in the context of the owner of the script. A user can take ownership of a script by clicking the Take Ownership
button in the sidebar. A script can only execute requests that the owner has access to. When running a script manually or with an access token, the calling user is always the owner of the script.