Skip to main content

What is Tests as Code?

Normally you build evaluators in the Cekura dashboard and then run them. Tests as Code lets you skip that: write your test cases in a JSON file, commit it alongside the agent it tests, and run it straight from CI. Nothing in the file becomes an evaluator in your workspace. Your evaluator list stays exactly as you left it.

Why Use It?

Tests live with the code

A change to your agent’s prompt and the tests that cover it land in the same pull request, reviewed together.

Full history

Your version control shows who changed which test case and why — including on branches, where dashboard evaluators cannot follow.

No dashboard setup

New environment, new branch, fork of the repo: the tests come with it. Nothing to recreate.

Same results

Runs appear in Results exactly like any other run, with the same metrics, transcripts, and reports.
Use dashboard evaluators when non-engineers author or review tests, and when you want them reusable across scheduled jobs. Use Tests as Code when the tests belong to a codebase. They work side by side — one agent can have both.

Quickstart

1. Create a spec file

Save this as cekura.tests.json in your repository:
The $schema line is optional but gives you autocomplete and inline validation in most editors.
The file says what to test. Which agent to run it against, and how to reach that agent, are supplied by the request — so the same file works unchanged across agents, projects and environments.

2. Check it

Validate without running or spending anything:
You get back the run that would happen — each test case, the total run count, and the estimated cost:
personality.mode tells you which form was resolved — existing (referenced by ID), override (a base plus your changes), inline (defined from scratch), or project_default. Use it to confirm the file did what you meant before spending anything.

3. Run it

Drop ?dry_run=true and the runs start:
The response is the same shape as any evaluator run, so poll the returned id against the Results API for progress and success rate.

The request

POST /test_framework/v1/scenarios/run_scenarios_json/ Authenticate with X-CEKURA-API-KEY. The key’s own scope resolves the project, so no project_id is needed. * Send exactly one of spec or file.

Uploading the file instead of embedding it

Avoids $(cat …) quoting entirely, which is handy in shells where escaping gets awkward:

Writing a spec file

Structure at a glance

Note what is not in there: no agent_id, no channel, no credentials, no provider ids. Those are request parameters or agent configuration — see Choosing the run target. A file containing agent_id is rejected, so the split stays honest.

The envelope

A test case

* instructions is required for instruction cases; conditional_actions cases need conditional_actions and language instead.

Metrics must already exist

A spec references metrics, it cannot define them. Create them in the dashboard first, then point at them by numeric ID or by slug:
Prefer slugs — a file full of slugs can be pointed at a different project without editing, while numeric IDs are project-specific.
A referenced metric must be enabled for the agent you are testing. A metric that isn’t would not be evaluated, and the run would pass having checked nothing — so the API rejects it with an error naming the metric instead. Enable it for that agent in the dashboard, or drop it from the spec.

Personalities

Three forms, depending on how much control you want:
Any personality in your organization, or any Cekura-provided one.
Settable inline: name, prompt, language, accent, voice_id, voice_model, provider, speed, background_noise, end_call_enabled.
Personalities defined inline are used for that run only. No personality is created in your workspace — your personality list is untouched. Cekura manages the underlying voice and transport configuration, so a spec cannot reach those internals.
Interruption behaviour and the finer audio controls — interruption_level, generation_config, network_simulation, background_sound_volume, message_plan, start_speaking_plan, stop_speaking_plan — cannot be set inline, and a spec that tries is rejected rather than having them quietly ignored. Configure them on a personality in the dashboard and reference it with base, which carries all of them through.

Test data

Reference a saved test profile, or supply the data inline:
  • agent_variables reach the agent under test as dynamic variables at call time.
  • caller_variables are context for the simulated caller only, and never sent to your agent. Reference them in instructions as {{test_profile.<key>}}.
Like inline personalities, inline test data is used for that run only. No test profile is created — your test-profile list is untouched.

Defaults

Anything repeated across cases can move up into defaults:
A test case overrides defaults field by field. metrics lists from both are combined.

Choosing the run target

Two request parameters decide where and how a spec runs — neither belongs in the file:
Because the target lives in the request, the same committed file runs against staging and production, or across several agents, without edits. Use metric slugs rather than numeric IDs to make that fully portable, since IDs are project-specific. Anything the channel needs beyond that comes from the agent’s own configuration: a phone number for voice, a chat provider for text, ElevenLabs credentials and agent id for elevenlabs. If the agent isn’t configured for the channel you pick, the run is rejected with a message naming what’s missing. Other execution modes exist but are not accepted yet; passing one is rejected rather than run down an unintended path.

Reading validation errors

Every problem in a spec is reported at once, keyed by its location in the file, so one round trip tells you everything to fix:
Run the dry check on every commit and you catch these before anything is spent.

Using it in CI

Runs are asynchronous: the call returns once they are queued. To gate a merge on the outcome, poll the returned result until it reaches a terminal status and then check its success_rate. The GitHub Actions Tutorial covers the polling and gating pattern in full — the only difference is which endpoint starts the run.
Split it across two jobs: run the dry check on every push (free, fast, catches a broken file), and the real run only on the branches you actually want tested.

Starting from evaluators you already have

If your tests currently live in the dashboard, export them rather than rewriting them by hand: select your evaluators, choose Export, and pick Test suite spec (.json). The file it produces is a valid spec you can commit as-is.

Limits

Test cases from a spec are cleaned up automatically once no recent run needs them, and results keep their test-case names regardless. Inline personalities and test data never become rows at all, so nothing accumulates in your personality or test-profile lists.

Getting the schema

The machine-readable schema this endpoint accepts:
No API key needed — it is a public contract with no data of yours in it. Link it from your spec file as $schema for editor autocomplete, or feed it to a JSON Schema validator to check specs locally before they reach CI. The same document is published at a stable URL, which is what the $schema line in the examples above points at: