Docs Allure Annotations in Scripts

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-k6 and 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).

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.

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_ok
    Java the JUnit unique id, com.acme.LoginTest#shouldLogIn, com.acme.LoginTest#shouldLogIn(java.lang.String), com.acme.LoginTest.shouldLogIn, LoginTest#shouldLogIn
    Go 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-pytest package is installed alongside the Mockarty
    plugin, it reads the same file and applies its own filter first. It only
    understands its package.Class#test selector spelling, so prefer plans that
    carry an id, 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, call allure.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.