A Groovy-based integration that generates Allure Report compatible results directly during Apache JMeter test execution.
⚠️ Not suitable for load testing — designed for functional/API test reporting.
- Java 11+
- Docker & Docker Compose (optional, for containerized runs)
Allure CLI is downloaded automatically by Gradle — no manual installation required.
git clone <repo-url>
cd jallure
# Assemble self-contained groovy scripts from modular classes
./gradlew assembleJallureScriptsAfter this step you will have:
build/assembled-groovy/jallure.groovy— main reporter
# Step 1: Run the example test plan
./gradlew jmeterRun
# Step 2: Generate and open the Allure report
./gradlew allureServe# Step 1: Launch JMeter with the example test plan
./gradlew jmeterGui
# Step 2: Press the **Start** (▶) button in JMeter to execute the test
# Step 3: After the run, generate and open the Allure report
./gradlew allureServeThe task patches JMX paths automatically and opens JMeter GUI with the example test plan pre-loaded.
By default,
jmeterRun/jmeterGuiusesrc/test/resources/allure-jmeter-example.jmx.
# Assemble scripts (if you changed source classes)
./gradlew assembleJallureScripts
# Run any JMX file (paths inside it will be auto-patched)
./gradlew jmeterRun -PjmxFile=/path/to/your-test.jmx
# Then view the report as usual
./gradlew allureServe# Step 1: Assemble scripts (Docker copies them from build/assembled-groovy/)
./gradlew assembleJallureScripts
# Step 2: Build image and run container
docker compose -f docker/docker-compose.yml up --build
# Step 3: Generate and open the Allure report from container results
./gradlew allureServeResults will appear in build/allure-results/.
Note: Files created by Docker are owned by
root. To clean them up later, usesudo rm -rf build/allure-results/.
├── build.gradle # Gradle build configuration
├── settings.gradle
├── gradlew / gradlew.bat / gradle/wrapper/
├── src/
│ ├── main/groovy/
│ │ └── io/github/forcasic/jmeter/allure/
│ │ ├── JsonUtils.groovy # JSON escaping & content-type utilities
│ │ ├── JMeterContext.groovy # JMeter binding wrapper
│ │ ├── AttachmentWriter.groovy # File I/O for Allure attachments
│ │ ├── AllureResultBuilder.groovy# Allure 2.x JSON builder
│ │ └── AllureReporter.groovy # Main reporter logic
│ ├── main/resources/jmeter/ # Custom JMeter config
│ └── test/
│ ├── groovy/.../allure/ # Spock unit & integration tests
│ └── resources/
│ ├── allure-jmeter-example.jmx # Example test plan
│ └── expected_results.json # Reference state for validation
├── build/
│ └── assembled-groovy/ # Auto-generated self-contained scripts
│ └── jallure.groovy
├── docker/
│ ├── Dockerfile # Docker image for JMeter + Allure
│ ├── docker-compose.yml # Docker Compose for local run
│ └── .dockerignore
├── docs/ # Documentation
│ ├── jallure_usage.md # Detailed usage instructions
│ ├── test_composition_guide.md # JMX composition guide
│ └── project_structure.md # Project structure & troubleshooting
├── README.md
├── NOTICE # Attribution notice
└── LICENSE
Important: The canonical source is the modular classes under src/main/groovy/io/github/forcasic/jmeter/allure/. The assembled scripts in build/assembled-groovy/ are auto-generated by ./gradlew assembleJallureScripts.
- Attach
jallure.groovyas a JSR223 Assertion to your JMeter samplers. - Set Allure annotations via JMeter variables (
vars.put("allure.name", "...")). - Use parameters:
start,continue,stop, or leave empty for solo mode. - The script writes Allure 2.x
*-result.jsonfiles and attachments in real time.
See docs/jallure_usage.md for detailed usage instructions.
When a multi-step test case does not receive a stop (e.g. the thread crashed or the stop sampler was skipped), its allure.label.* variables remain in JMeter's vars. The next test case may inadvertently inherit them.
Why the reporter cannot fix this automatically: jallure.groovy runs as a JSR223 Assertion (after the HTTP sampler), while annotations are declared in a JSR223 Sampler (before the HTTP sampler). By the time the reporter sees a new start, vars already contains a mix of old leaked labels and new intentional ones — the code cannot tell them apart.
Recommended workaround: Add a one-time init sampler at the start of your ThreadGroup that stores a clearLabels closure, then call it inside any declaration sampler that must start with a clean label state:
// Init sampler (run once per thread)
vars.putObject("allure.clearLabels", { ->
Set copy = new HashSet(vars.entrySet())
for (Iterator iter = copy.iterator(); iter.hasNext();) {
def var = iter.next()
String key = var.getKey()
if (key.startsWith("allure.label") || key.startsWith("allure.links") || key.startsWith("allure.label.AS_ID")) {
vars.remove(key)
}
}
})// Inside "Declare allure annotations" for affected cases
vars.getObject("allure.clearLabels")?.call()
vars.put("allure.name", "My Case")
// ... set remaining labels- Multi-step test cases with
start/continue/stop - Automatic request/response attachments
- Assertion results as sub-steps
- Support for labels, links, parameters, severity, tags, owner, layer, issues
- Step-level parameters via
parameters=[var1,var2]modifier - Feature suffix via
allure.feature.suffixvariable - Sensitive header redaction (Authorization, X-Api-Token)
- Binary content detection (PDF, images, Office docs, etc.)
- Critical mode (stop thread on failure)
| Task | Description |
|---|---|
./gradlew test |
Compile test sources (no unit tests currently) |
./gradlew jmeterRun |
Download JMeter and run example JMX (non-GUI) |
./gradlew jmeterRun -PjmxFile=... |
Run your own JMX file |
./gradlew jmeterGui |
Download JMeter and open GUI with example JMX |
./gradlew jmeterGui -PjmxFile=... |
Open GUI with your own JMX file |
./gradlew allureGenerate |
Generate Allure HTML report |
./gradlew allureOpen |
Open Allure report in browser |
./gradlew allureServe |
Serve Allure report via local web-server |
./gradlew validateAllureResults |
Validate generated Allure results |
./gradlew integrationTest |
Full pipeline: JMeter run + validation |
./gradlew cleanAllure |
Delete generated Allure results and reports |
After intentional behavioral changes, regenerate src/test/resources/expected_results.json:
./gradlew jmeterRun
# Then run the Python snippet below to regenerate expected_results.json
python3 - <<'PY'
import json, glob, os
results_dir = 'build/allure-results'
tests = []
for fp in sorted(glob.glob(os.path.join(results_dir, '*-result.json'))):
d = json.load(open(fp))
test = {
'name': d.get('name', ''),
'feature': '',
'story': '',
'mode': 'multi' if len(d.get('steps', [])) > 1 else 'solo',
'main_steps': len(d.get('steps', [])),
'steps': []
}
for l in d.get('labels', []):
if l['name'] in ('feature', 'story'):
test[l['name']] = l['value']
for s in d.get('steps', []):
test['steps'].append({
'name_pattern': s.get('name', '').split(':')[0] if ':' in s.get('name', '') else s.get('name', '')[:30],
'sub_steps': len(s.get('steps', [])),
'has_attachments': len(s.get('attachments', [])) >= 2
})
tests.append(test)
with open('src/test/resources/expected_results.json', 'w') as f:
json.dump({'_comment': 'Auto-generated', 'total_tests': len(tests), 'expected_tests': tests}, f, indent=2, ensure_ascii=False)
print(f'Updated {len(tests)} tests')
PYApache License 2.0 — see LICENSE.
Forcasic