Scripting
Both runners embed the Testing multi-language script engine for logic that doesn’t fit the declarative schema: custom validation, computed test data, side-band checks against files or databases.
v2: script: steps (kis test)
Section titled “v2: script: steps (kis test)”steps: - name: validate-order-totals script: language: js # default execute: | const r = response; // parsed body of the previous HTTP step const bad = r.items.filter(i => i.price <= 0); if (bad.length > 0) { test.fail("items with non-positive price: " + bad.length, 400); } test.set_variable("item_count", r.items.length); test.success("totals ok", 200); timeout: 10s # default 5s params: # extra variables merged over the scope max_items: 50Languages
Section titled “Languages”language: accepts js / javascript (goja, default), js:v8 / javascript-v8 / v8,
lua, starlark, cel / celgo, expr. (wasm appears in a comment but is rejected.)
function: selects an entry point for callable-contract languages (Starlark); it’s ignored for
JS.
execute: (inline source) and file: (path relative to the test YAML’s directory) are
mutually exclusive.
Injected variables
Section titled “Injected variables”Scripts see the entire test scope as top-level variables, plus params. When a previous HTTP
step exists in the test, they also get:
| Variable | Contents |
|---|---|
status_code | HTTP status of the last HTTP step |
response_body | raw body string |
response | JSON-parsed body |
headers | response headers, lower-cased keys |
duration_ms | last step duration |
The test.* API
Section titled “The test.* API”| Function | Description |
|---|---|
test.success(msg, code?) | mark the step passed (code defaults 200) |
test.fail(msg, code?) | mark the step failed (code defaults 400) |
test.skip(reason) | record a skip (reported as 200 + message) |
test.set_variable(k, v) | write to the test scope — visible to later steps/assertions |
test.get_variable(k) | read from scope |
test.log(args...) | write to the test log |
test.run_id() / test.test_name() / test.step_name() / test.suite_name() / test.suite_path() / test.source_file() | identity/context accessors |
test.now_unix_ms() / test.now_iso8601() | clocks |
test.uuid() / test.ulid() | id generators |
test.random_int(lo, hi) / test.random_string(n) | randomness |
test.env(name) | read an OS environment variable |
test.fixture(relpath) | read a file relative to the test’s directory |
test.sleep(ms) | sleep |
Flat aliases exist for legacy scripts: success, fail, set_variable, get_variable,
logwriter, log, print, println, uuid, ulid, sleep.
The eval.* namespace is also registered — comparison predicates and model-free metrics from
the Testing eval package (eval.equals, eval.json_path_equals, eval.f1, …), plus fail-fast
eval.must.* forms.
Pass/fail semantics
Section titled “Pass/fail semantics”test.fail(...)or a non-2xx code passed tosuccess/fail→ step failed.- A runtime error in the script → step errored.
test.skip(...)→ recorded as 200 with the skip message.- Script finishes without calling anything → 200, passed.
- Timeout (default 5s) fails the step with
[script: timed out after X]in the log.
Assertions on a script step evaluate against the scope + duration_ms only — do validation
inside the script, or set variables and assert on them.
Legacy: plugin: actions (test.svc tests)
Section titled “Legacy: plugin: actions (test.svc tests)”steps: - name: js-hello plugin: payload: language: js # js | js:v8 | cel | celgo execute: | logwriter("hello from the plugin engine"); success("ok", 200);Registered functions
Section titled “Registered functions”| Function | Description |
|---|---|
success(message, statuscode) | return success |
fail(message, statuscode) | return failure |
logwriter(args...) | write to the test log |
getdbconnection(url) | open a PostgreSQL connection (driver is hardcoded), returns a connection ID |
dbquery(id, query) | run a query → {result: [...rows]} |
dbexecute(id, query) | run a statement |
closedbconnection(id) | close the connection |
hash.string(data, algo) | hash a string — md5, sha1, sha256, sha384, sha512, sha3_256, sha3_384, sha3_512 |
hash.file(path, algo) | hash file contents |
fs.read(path) / fs.glob(pattern) | read a file / read all files matching a glob (via the Testing filesystem utility) |
DB example:
- name: verify-in-db plugin: payload: language: js execute: | const id = getdbconnection("postgres://user:pass@localhost:5432/db?sslmode=disable"); const out = dbquery(id, "SELECT count(*) AS n FROM users"); closedbconnection(id); if (out.result[0].n > 0) { success("rows present", 200); } else { fail("no rows", 500); }Validation expressions
Section titled “Validation expressions”Separate from plugin steps, rest: steps in the legacy format run response.validate
expressions in the language named by the step’s language: key — js, jq, or celgo — see
the legacy tests mode response-validation reference.
Security note
Section titled “Security note”Scripts run with real capabilities: filesystem reads, OS env access, and (legacy) database connections. Test definitions are code — review them like code, and don’t run untrusted collections.