Allure annotations in Mockarty scripts
Mockarty’s load and functional runners can emit results in the
Allure 2 format — the same one used by
allure-pytest, allure-java, allure-junit5, and the k6-allure
ecosystem. That means your stakeholders get rich, drill-down HTML reports
with steps, attachments, parameters, and a tree of features / stories /
epics, all from scripts you write today.
This guide is the user-facing reference for the annotation API you
sprinkle into JavaScript / Go / Python / Java scripts to make the report
useful. The annotations are optional — without them you still get a
correct, minimal report with pass/fail status and duration.
When to add Allure annotations
- You’re running performance scripts in CI and want a richer artefact than
the JUnit XML summary. - You’re migrating from
k6 + allure-k6and want a drop-in replacement. - Stakeholders (PMs, QA leads) read the HTML report and need to navigate
by Feature / Story / Owner rather than by raw URL. - You attach evidence (screenshots, payloads, headers) that’s tedious to
open from the JSON file.
Quick example (JavaScript / k6-compat)
Inside a Mockarty load script the allure global is injected
automatically. You can also import it explicitly from mockarty/allure
(the k6/allure path is accepted as an alias for portability).
import http from 'k6/http';
import { check } from 'k6';
// 'allure' is a global; or import it: import { allure } from 'mockarty/allure'
export const options = { vus: 10, duration: '30s' };
export default function () {
allure.feature('Checkout');
allure.story('Apply discount code');
allure.severity('critical');
allure.owner('payments-team');
allure.tag('regression');
allure.tmsLink('JIRA-1234', 'Apply discount');
allure.parameter('region', 'eu-west-1');
allure.step('login', () => {
const r = http.post('https://api.example.com/login', { user: 'alice' });
check(r, { 'login 200': (res) => res.status === 200 });
});
allure.step('apply discount', () => {
const r = http.post('https://api.example.com/cart/discount',
JSON.stringify({ code: 'SAVE10' }),
{ headers: { 'Content-Type': 'application/json' }});
allure.attach('response body', r.body, 'application/json');
check(r, { 'discount 200': (res) => res.status === 200 });
});
}
Run it with the Allure reporter and pass --allure-results-dir (or the
ALLURE_RESULTS_DIR environment variable) to control where the JSON is
written:
mockarty-cli perf run script.js --reporter allure --allure-results-dir ./results
allure generate ./results --clean -o ./report
allure generate is the third-party Allure CLI; Mockarty produces
conformant *-result.json documents so any version that handles Allure 2
will render them.
Annotation reference
The annotation surface mirrors allure-pytest / allure-js. Names are
identical so docs from those ecosystems apply too.
Steps
allure.step('descriptive name', () => {
// anything you do here — HTTP calls, asserts, etc. — gets attributed
// to this step, with start/stop times. Steps can nest.
});
If the callback throws, the step is marked failed; if it throws an
unexpected error type (e.g. a typo, ReferenceError) the step is marked
broken. Steps may nest arbitrarily — the report renders a tree.
Attachments
allure.attach('payload.json', body, 'application/json');
allure.attach('screenshot.png', pngBytes, 'image/png');
- The first argument is the display name in the report.
- The second is the content (string, ArrayBuffer, or Uint8Array).
- The third is the MIME type; the report uses it to pick a viewer
(JSON pretty-printer, image renderer, plain-text fallback).
Attachments are written next to the result JSON and referenced by source
file name in the JSON. The writer streams large attachments to disk;
there is no in-memory limit beyond what the JS heap permits, but for
multi-megabyte payloads consider sampling.
Hierarchy labels
allure.epic('Billing');
allure.feature('Checkout');
allure.story('Apply discount code');
allure.suite('payments-suite');
allure.parentSuite('e2e');
allure.subSuite('eu-region');
These drive the report’s left-hand navigation tree (Behaviors,
Suites).
Metadata labels
allure.severity('critical'); // blocker | critical | normal | minor | trivial
allure.owner('payments-team');
allure.tag('regression');
allure.label('framework', 'mockarty'); // free-form k/v
Severity colours the row in the report. Owner shows up as a chip. Tags
are searchable. The free-form label(key, value) lets you stamp anything
you want (host, build number, region, etc.) without a custom plugin.
Title and description
allure.title('Checkout: apply discount code to a 50€ cart');
allure.description('Verifies the SAVE10 promo at the EU pricing tier.');
title is the display name for the test in the report. description
accepts plain text or Markdown (the HTML report sanitises it).
Links
allure.link('https://example.com', 'docs', 'link');
allure.issue('JIRA-1234'); // type: issue
allure.tmsLink('TC-456', 'TestRail TC-456'); // type: tms
The type field maps to Allure’s link styling (issue / tms / link). If
you omit the name, the URL is used as the link text.
Parameters
allure.parameter('region', 'eu-west-1');
allure.parameter('vu', '10');
Parameters appear in the test header. The Allure report uses them to
group parameterised cases — the same test with different parameters
becomes a single row with N sub-runs.
Dynamic mutations
The allure.dynamic.* namespace lets you set values from inside a
step (for example, after computing them). The list of supported methods
is identical to the top-level surface:
allure.step('measure', () => {
const lat = measureLatency();
allure.dynamic.parameter('latency_ms', String(lat));
allure.dynamic.severity(lat > 1000 ? 'critical' : 'normal');
});
Allure result schema (wire format)
Mockarty writes <uuid>-result.json files that match the Allure 2 wire
format. The key fields:
| Field | Type | Purpose |
|---|---|---|
uuid |
string | Result identity; randomised per run. |
historyId |
string | Stable across runs; used to draw the “history” sparkline. |
testCaseId |
string | Optional stable identifier for the test case. |
fullName |
string | Fully-qualified test name (e.g. script.js#checkout). |
name |
string | Display name; set via allure.title() or auto-derived from the script. |
description |
string | Markdown / plain text body. |
descriptionHtml |
string | Pre-rendered HTML body (Mockarty doesn’t fill this; reserved). |
status |
enum | passed / failed / broken / skipped. |
statusDetails |
object | { message, trace, known, muted, flaky } for failed/broken cases. |
stage |
enum | Always finished in Mockarty (live-update writers reserve other values). |
start/stop |
int64 | Unix-millis timestamps. |
labels |
array | [{ name, value }, …] — feature/story/epic/severity/owner/tag/… |
links |
array | [{ name, url, type }, …] |
parameters |
array | [{ name, value }, …] |
steps |
array | Nested step tree; each step has the same status/start/stop shape. |
attachments |
array | [{ name, source, type }, …] — source is the filename on disk. |
Collection fields (labels, links, parameters, steps,
attachments) always serialise as [] rather than null to match
allure-pytest. That’s what makes interleaved Go / JS / Python results
readable in a single Allure report.
Canonical label keys
The report renders these label names with first-class chips and tree
navigation; everything else lands in the generic “labels” tab:
feature · story · epic · severity · owner · tag ·
suite · parentSuite · subSuite · host · thread ·
framework · language · package · testClass · testMethod ·
AS_ID.
Canonical link types
issue · tms · link (generic).
SDK examples
The same annotation surface is exposed in the language SDKs. Each SDK
writes Allure JSON locally — no server round-trip required.
Go
import (
"context"
"github.com/mockarty/mockarty-go/allure"
)
func TestCheckout(t *testing.T) {
ctx, scope := allure.NewScope(context.Background(),
allure.WithFeature("Checkout"),
allure.WithStory("Apply discount"),
allure.WithSeverity(allure.SeverityCritical),
)
defer scope.Finish()
scope.Step("login", func(ctx context.Context) error {
// … HTTP call …
return nil
})
scope.Step("apply discount", func(ctx context.Context) error {
body := []byte(`{"code":"SAVE10"}`)
scope.Attach("payload.json", "application/json", body)
return nil
})
}
Python
import allure # provided by the Mockarty Python SDK
@allure.feature("Checkout")
@allure.story("Apply discount")
@allure.severity("critical")
def test_checkout():
with allure.step("login"):
# HTTP call
pass
with allure.step("apply discount"):
body = b'{"code":"SAVE10"}'
allure.attach(body, name="payload.json", attachment_type=allure.MIME.JSON)
Java
import ru.mockarty.allure.Allure;
@Test
@AllureFeature("Checkout")
@AllureStory("Apply discount")
@AllureSeverity("critical")
void checkout() {
Allure.step("login", () -> { /* HTTP call */ });
Allure.step("apply discount", () -> {
Allure.attach("payload.json", "application/json",
"{\"code\":\"SAVE10\"}".getBytes());
});
}
The Java and Python SDK names are aligned with the official
allure-junit5 / allure-pytest packages so existing teams can copy
existing patterns.
CLI integration
The CLI accepts the Allure reporter on every run-style command:
mockarty-cli perf run script.js --reporter allure --allure-results-dir ./results
mockarty-cli test run flow.mockarty.json --reporter cli,allure:./results
mockarty-cli perf run script.js --reporter allure --allure-results-dir ./results
Multiple reporters can run side-by-side:
mockarty-cli perf run script.js \
--reporter cli \
--reporter json:./report.json \
--reporter junit:./junit.xml \
--reporter allure --allure-results-dir ./allure-results
After the run finishes:
allure generate ./allure-results --clean -o ./report
allure open ./report # opens a local browser
The allure CLI is third-party; install it from
allurereport.org/docs/install/.
Any version that handles Allure 2 results (>= 2.13) will render
Mockarty output.
Selective runs (test plans)
A test plan is a small JSON file listing exactly which tests should run.
It is how “re-run only the failed tests” works: you generate a plan, point
ALLURE_TESTPLAN_PATH at it, and the SDK runs only what the plan lists.
Mockarty generates plans for you:
# Only the failed/broken cases of a finished launch
mockarty-cli allure rerun-failed --launch <launch-id> --out ./testplan.json
# Only the tests impacted by a code change
mockarty-cli util ci impact --base origin/main --head HEAD \
--namespace my-team --allure-out ./testplan.json
Then run your suite with the variable set:
export ALLURE_TESTPLAN_PATH=$PWD/testplan.json
pytest tests/ # Python
./gradlew test # Java (JUnit 5)
go test ./... # Go
The Go, Python and Java SDKs all consume the file — no extra flags, no
configuration. The Python plugin, the JUnit 5 extension and the Go allure
package are activated by the dependency alone.
The file format
{
"version": "1.0",
"tests": [
{"id": 11111, "selector": "my.company.SimpleTest.simpleTestOne"},
{"selector": "tests/auth/test_login.py::test_ok"},
{"id": "CASE-9"}
]
}
Each entry needs an id, a selector, or both. A test runs when either
matches:
id— the test’s Allure id:@allure.id("777")in Python,
@AllureId("777")in Java,allure.WithAllureID("777")in Go. Mockarty’s
own bindings count too —@mockarty.testing.test_case("CASE-9")and
@TestCase("CASE-9")— so a Mockarty-native suite is addressable by its
test-case id without adding Allure annotations.selector— a unique name for the test. Every SDK accepts several
equivalent spellings so a plan written by any tool matches:Language Selectors accepted Python tests/auth/test_login.py::TestLogin::test_ok, the same without the[param]suffix,tests.auth.test_login.TestLogin#test_ok,tests.auth.test_login.TestLogin.test_okJava the JUnit unique id, com.acme.LoginTest#shouldLogIn,com.acme.LoginTest#shouldLogIn(java.lang.String),com.acme.LoginTest.shouldLogIn,LoginTest#shouldLogInGo TestLogin/happy,example.com/pkg::TestLogin/happy,example.com/pkg.TestLogin/happy,TestLogin#happy
What happens in every situation
| Situation | What the SDK does |
|---|---|
ALLURE_TESTPLAN_PATH not set |
Nothing changes — the whole suite runs. |
| Plan lists N tests | Only those run. The rest are skipped/deselected. |
| Plan lists tests, none match your suite | Nothing runs, and the run is flagged — never reported as a plain pass. |
Plan is empty ("tests": []) |
Nothing runs, and the run is reported as failed. An empty plan means “no test was selected”, so a green tick would be a lie. |
| File is missing, unreadable or not valid JSON | The run stops with an explicit error. It never silently falls back to running everything. |
MOCKARTY_TESTPLAN_MODE=off |
The plan is ignored and the whole suite runs — the deliberate escape hatch. |
The two failure rows are the point of the feature. Asking for 3 tests and
getting 3000 executed — or getting a green build that executed none — costs
CI time and hides real failures, so the SDKs refuse both.
Concretely:
| Language | Empty plan ("tests": []) |
Plan matched nothing | Broken or missing plan |
|---|---|---|---|
| Python | every test is deselected; pytest exits 5 (“no tests ran”) and prints why |
same — pytest exits 5 and prints why |
pytest exits 4 (usage error) before collecting anything |
| Go | every test is skipped; allure.TestMain exits 5 and prints why |
same — allure.TestMain exits 5 |
the test fails with the reason; allure.TestMain exits 4 |
| Java | discovery stops with a message saying the plan selected nothing — the build fails | zero tests are executed and the reason is printed; make it a failure with Gradle’s test { failOnNoDiscoveredTests = true } or Surefire’s <failIfNoTests>true</failIfNoTests> |
discovery stops with the parse/read error — the build fails |
Java is the one place where “the plan matched nothing” is not a hard failure
on its own: the JUnit Platform gives an adapter no way to fail a run after
discovery, so the SDK prints the reason and leaves the verdict to the build
tool’s own “no tests discovered” switch. An empty plan is a hard failure,
because that is detectable while filtering.
Go returns those exit codes through allure.TestMain, so wire it up once per
package:
func TestMain(m *testing.M) { os.Exit(allure.TestMain(m)) }
Without it, unselected tests are still skipped correctly, but go test reports
the usual ok for a run in which nothing executed.
Java also accepts system properties instead of environment variables —
-Dallure.testplan.path=... and -Dmockarty.testplan.mode=off — which is
often easier from Gradle or Maven. The environment variable wins when both
are set.
Notes
- A test plan is an additional filter. It combines with your normal
selection (pytest -k, Gradle--tests,go test -run): a test runs only
when both agree. - If the official
allure-pytestpackage is installed alongside the Mockarty
plugin, it reads the same file and applies its own filter first. It only
understands itspackage.Class#testselector spelling, so prefer plans that
carry anid, or that spell selectors that way, when both are installed. - Go filters tests that go through
allure.T(t, ...). For a suite that wants
selection without Allure reporting, callallure.SkipIfNotSelected(t)as
the first line of the test.
Uploading to Mockarty (Environment, Categories, Executor)
When you push results to Mockarty (mockarty-cli allure upload ./allure-results
or mockarty-cli allure watch -- <cmd>), the launch-scoped files Allure
adapters write next to the *-result.json files are processed too:
| File | What Mockarty does with it |
|---|---|
environment.properties / environment.xml |
Parsed into a key/value Environment panel shown on the run report’s Overview tab. |
categories.json |
Applied as a defect classifier — failed results are bucketed (e.g. “Product defects” vs “Test defects”) and shown on the report’s Categories tab. |
executor.json |
Surfaced as the CI Executor card (build name + link) on the Overview tab. |
These are launch-scoped (they describe the whole run, not one test), so they
are only uploaded when a launch is active — create one first:
eval "$(mockarty-cli allure launch create --launch-name "nightly $(date +%F)")"
mockarty-cli allure upload ./allure-results # results + environment/categories/executor
mockarty-cli allure launch close
launch close finalises the launch with the outcome Mockarty derives from the
results that actually arrived:
| What arrived | Recorded launch status |
|---|---|
| every result passed (or was skipped) | completed |
| at least one result failed | failed |
| no results at all | failed — nothing was reported, so the launch is not green |
If the pipeline was genuinely aborted, say so explicitly:
mockarty-cli allure launch close --abort # records the launch as cancelled
The command prints the recorded verdict so a CI log shows what was archived:
LAUNCH_CLOSED=8d0c9b2e-... status=completed results=42 failed=0
environment.properties accepts both the Java .properties form
(key=value lines, # comments) and the <environment><parameter> XML form —
Mockarty auto-detects which. categories.json follows the standard Allure
shape: an array of {name, matchedStatuses, messageRegex, traceRegex} rules,
matched first-rule-wins over the run’s failed/broken results.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Report is empty / “No results found” | allure-results directory is empty. Confirm the --allure-results-dir path matches the allure generate input. |
| Steps don’t nest | You forgot to pass a callback to allure.step — the no-callback form is an error. |
| Attachment shows as binary garbage | MIME type missing or wrong. Pass application/json, image/png, etc. explicitly. |
| Same test appears twice | Two scripts emit the same historyId. Either set allure.label('AS_ID', '<unique>') or rely on the auto-derived ID by changing the script name. |
feature/severity chips empty |
The label was set on a step but not on the test. Move it outside the allure.step(…) callback. |
| Report renders fine but no labels in the tree | The reporter dir contains old runs. allure generate --clean to drop them. |