feat(scenario): add context.assert() API for script assertions

- Add context.assert(fn, description) for function assertions
- Logs outcome on success/failure, throws on assertion failure
- Bump package version from 1.8.0 to 1.9.0
This commit is contained in:
Andrii Arsenin
2026-09-15 12:35:17 +03:00
parent 622dfdbfb0
commit 715afd2151
4 changed files with 45 additions and 1 deletions
+12
View File
@@ -2,6 +2,18 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 1.9.0 - 2026-09-15
Changes since 1.8.0:
### Added
- Added `context.assert(fn, description)` for scenario/snippet script steps: runs the given function, requires it to return `true`, logs the outcome (`log` on success, `error` on failure or exception), and throws to stop the step on failure.
### Changed
- Documented `context.assert` in `docs/scenario.md`.
## 1.8.0 - 2026-09-15 ## 1.8.0 - 2026-09-15
Changes since 1.7.1: Changes since 1.7.1:
+1
View File
@@ -35,6 +35,7 @@ Available in scope:
- `context.getStepOutput(order)`: prior step output by absolute order (`1`, `2`, ...) or relative (`-1` = previous step) - `context.getStepOutput(order)`: prior step output by absolute order (`1`, `2`, ...) or relative (`-1` = previous step)
- `context.dumpDom(selector?)`: simplified DOM snapshot - `context.dumpDom(selector?)`: simplified DOM snapshot
- `context.log(...args)`, `context.warn(...args)`, `context.error(...args)`: structured step logs - `context.log(...args)`, `context.warn(...args)`, `context.error(...args)`: structured step logs
- `context.assert(() => boolean, description)`: runs the arrow function and requires it to return `true`. Logs `description` (as a `log` entry on success, as an `error` entry on failure or exception), then throws to stop the step if the function returned anything other than `true` or threw
- `context.runSnippet(name, ...args)`: execute stored snippet code with the same context - `context.runSnippet(name, ...args)`: execute stored snippet code with the same context
- `context.getScenarioFiles(opts?)`: list files uploaded to the scenario (`limit`, `offset`) - `context.getScenarioFiles(opts?)`: list files uploaded to the scenario (`limit`, `offset`)
- `context.downloadFile(url, opts?)`: fetch `url` and save the result as a run artifact (requires a real scenario run — throws when invoked ad hoc, e.g. via the `exec_code` MCP tool). `opts` may include `method`, `headers`, `body`, `filename`. `url` also accepts a `data:` URI (`data:<mediaType>;base64,<data>` or `data:<mediaType>,<percent-encoded data>`) to save content generated in-script (e.g. a credential JWT) directly, without an actual network fetch. - `context.downloadFile(url, opts?)`: fetch `url` and save the result as a run artifact (requires a real scenario run — throws when invoked ad hoc, e.g. via the `exec_code` MCP tool). `opts` may include `method`, `headers`, `body`, `filename`. `url` also accepts a `data:` URI (`data:<mediaType>;base64,<data>` or `data:<mediaType>,<percent-encoded data>`) to save content generated in-script (e.g. a credential JWT) directly, without an actual network fetch.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "liqa", "name": "liqa",
"version": "1.8.0", "version": "1.9.0",
"private": true, "private": true,
"workspaces": [ "workspaces": [
"server", "server",
@@ -42,6 +42,15 @@ export interface ScriptContext {
log: (...args: unknown[]) => void; log: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void; warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void; error: (...args: unknown[]) => void;
/**
* Runs `fn` and requires it to return `true`. Logs the outcome under
* `description`, then throws if `fn` returned anything other than `true`
* or threw an exception.
*/
assert: (
fn: () => boolean | Promise<boolean>,
description: string,
) => Promise<void>;
/** Runs a named snippet with the same context, plus any extra positional args. */ /** Runs a named snippet with the same context, plus any extra positional args. */
runSnippet: (alias: string, ...args: unknown[]) => Promise<unknown>; runSnippet: (alias: string, ...args: unknown[]) => Promise<unknown>;
/** Returns metadata + absolute disk path for files attached to the current scenario. */ /** Returns metadata + absolute disk path for files attached to the current scenario. */
@@ -141,6 +150,28 @@ export class CodeExecutorService {
log: (...args: unknown[]) => scriptLog("log", toStr(args)), log: (...args: unknown[]) => scriptLog("log", toStr(args)),
warn: (...args: unknown[]) => scriptLog("warn", toStr(args)), warn: (...args: unknown[]) => scriptLog("warn", toStr(args)),
error: (...args: unknown[]) => scriptLog("error", toStr(args)), error: (...args: unknown[]) => scriptLog("error", toStr(args)),
assert: async (
fn: () => boolean | Promise<boolean>,
description: string,
): Promise<void> => {
let passed: boolean;
let failureReason: string | undefined;
try {
passed = (await fn()) === true;
} catch (err) {
passed = false;
failureReason = (err as Error).message;
}
if (passed) {
scriptLog("log", `Assertion passed: ${description}`);
return;
}
const message = failureReason
? `Assertion failed: ${description} (${failureReason})`
: `Assertion failed: ${description}`;
scriptLog("error", message);
throw new Error(message);
},
getStepOutput: getStepOutput ?? (() => Promise.resolve(null)), getStepOutput: getStepOutput ?? (() => Promise.resolve(null)),
getCredential: (alias: string): unknown => { getCredential: (alias: string): unknown => {
if (!(alias in credMap)) { if (!(alias in credMap)) {