behave-modern-json-report
August 10, 2026 · View on GitHub
A modern JSON report formatter for Behave that generates a rich, structured execution model for reporting, analytics, dashboards, AI, CI/CD pipelines and custom integrations.
Why?
The built-in Behave JSON formatter produces a flat, HTML-oriented structure. This project creates the canonical execution model for Behave — a schema-versioned, stable, extensible JSON format designed as an API, not as a rendering target.
It is the data foundation for:
- HTML / Markdown / Console reports
- AI-powered test analysis
- Dashboards and analytics platforms
- Historical execution comparison
- Trace viewers
- Custom integrations
Features
- Schema-versioned JSON output (
schemaVersion: "1.2.0") - Stable unique identifiers for every entity (execution, feature, scenario, step, attachment, error)
- Structured errors — type, message, traceback, location (never raw strings)
- Attachments — image, JSON, XML, HTML, PDF, video, text, binary; embedded or external
- Attachment helpers —
attach_file,attach_text,attach_json,attach_screenshot,logforenvironment.pyhooks - Step-level logs
- Gherkin backgrounds — shared background steps captured at feature, rule, and scenario level
- Rule support — Gherkin v6 / Behave 1.3.x rules as first-class
Ruleentities with background, tags, location, and nested scenarios - Scenario outlines —
isOutlineandoutlineNamefields, plusexampleTagsfor tags on Example blocks - Example keyword — Gherkin v6
Examplekeyword recognised as scenario outline - Expanded statuses —
passed,failed,skipped,undefined,pending,untested,error,hook_error,cleanup_error,xfailed,xpassed - Rich statistics — pass rate, counts, duration, error count, total attachments/logs, slowest step, avg scenario, common exception type, per-tag breakdown
- Rich environment — Python, Behave, platform, OS, hostname, CI provider, cwd, command, user, CPU count, memory, git branch/commit/remote
- Arbitrary metadata — inject domain-specific context via
[behave.userdata]withmjr.*keys - Cucumber JSON format —
CucumberJSONFormatteroutputs de facto Cucumber JSON for compatibility with cucumber-reporting, ReportPortal, Jenkins plugins, and more - Zero Behave dependency in the serializer — the JSON model is portable
- JSON Schema validation with helpful error messages
- Configurable — pretty/compact, embed/exclude attachments, exclude passed scenarios
- Production-ready — 174 tests, lint, type-check, CI
Installation
pip install behave-modern-json-report[behave]
For validation support:
pip install behave-modern-json-report[validate]
For enhanced environment detection (memory info):
pip install behave-modern-json-report[env]
For development:
pip install behave-modern-json-report[dev]
Quick Start
As a Behave formatter (modern JSON)
# Short format name (via entry point)
behave --format modern-json --outfile report.json
# Full module path (always works)
behave --format behave_modern_json_report:ModernJSONFormatter --outfile report.json
As a Behave formatter (Cucumber JSON)
# Short format name (via entry point)
behave --format cucumber-json --outfile cucumber.json
# Full module path (always works)
behave --format behave_modern_json_report:CucumberJSONFormatter --outfile cucumber.json
The Cucumber JSON format is compatible with tools that consume Cucumber JSON reports:
- cucumber-reporting (Jenkins plugin)
- multiple-cucumber-html-reporter
- ReportPortal, Allure, and other CI/CD integrations
With metadata via behave.ini
[behave]
format = behave_modern_json_report:ModernJSONFormatter
outfile = report.json
[behave.userdata]
mjr.project_name = My Project
mjr.branch = dev
mjr.team = qa
mjr.environment = staging
mjr.build_id = 42
All keys prefixed with mjr. are automatically injected into the report's metadata block. mjr.project_name is used as the project name. The prefix is stripped in the output:
{
"execution": { "projectName": "My Project" },
"metadata": { "data": { "branch": "dev", "team": "qa", "environment": "staging", "build_id": "42" } }
}
You can also pass metadata via CLI:
behave --userdata "mjr.branch=hotfix,mjr.build_id=99" --format behave_modern_json_report:ModernJSONFormatter --outfile report.json
Programmatically
from behave_modern_json_report import serialize, SerializerOptions
from behave_modern_json_report.collector import Collector
collector = Collector(project_name="my-app", metadata={"branch": "main"})
# Feed Behave events to the collector...
# collector.start_feature(feature)
# collector.start_scenario(scenario)
# collector.start_step(step)
# collector.end_step(step)
# collector.end_scenario(scenario)
# collector.end_feature(feature)
report = collector.finalize()
json_str = serialize(report, options=SerializerOptions(pretty=True))
Validation
from behave_modern_json_report import validate_json
with open("report.json") as f:
result = validate_json(f.read())
if not result:
for error in result.errors:
print(f"{error.path}: {error.message}")
Attachments in environment.py
from behave_modern_json_report import attach_file, attach_screenshot, attach_text, attach_json, log
def after_step(context, step):
if step.status == "failed":
# Screenshot from Selenium, Playwright, bytes, or file path
attach_screenshot(context, context.driver, name="failure.png")
# Attach arbitrary text
attach_text(context, f"URL: {context.url}", name="url.txt")
# Attach JSON data
attach_json(context, {"url": context.url, "status": step.status})
# Attach a file from disk
attach_file(context, "/tmp/dump.html", name="page.html")
# Log a message to the step
log(context, f"Failure at {context.url}", level="ERROR")
JSON Structure
{
"schemaVersion": "1.2.0",
"execution": {
"executionId": "exec-...",
"projectName": "my-app",
"startTime": "2026-06-30T14:20:00.000Z",
"endTime": "2026-06-30T14:20:01.500Z",
"duration": 1.5,
"status": "passed",
"command": "behave ...",
"workingDirectory": "/home/user/project"
},
"statistics": {
"features": 1,
"scenarios": 3,
"steps": 12,
"passed": 12,
"failed": 0,
"skipped": 0,
"undefined": 0,
"pending": 0,
"passRate": 1.0,
"duration": 1.5,
"errorCount": 0,
"totalAttachments": 0,
"totalLogs": 0,
"slowestStepDuration": 0.2,
"avgScenarioDuration": 0.5,
"commonExceptionType": null,
"byTag": {
"smoke": { "count": 3, "duration": 1.5, "passed": 3, "failed": 0 }
}
},
"environment": {
"pythonVersion": "3.12.3",
"behaveVersion": "1.2.6",
"platform": "linux",
"os": "Linux",
"osVersion": "6.5.0",
"hostname": "build-agent-01",
"ciProvider": "github-actions",
"cwd": "/home/user/project",
"command": "behave --format modern-json",
"user": "tester",
"cpuCount": 8,
"memoryMb": 16384,
"gitBranch": "main",
"gitCommit": "abc1234",
"gitRemote": "origin"
},
"features": [
{
"id": "feature-...",
"name": "Calculator",
"tags": ["smoke"],
"status": "passed",
"duration": 1.5,
"scenarios": [
{
"id": "scenario-...",
"name": "Add two numbers",
"featureId": "feature-...",
"status": "passed",
"duration": 0.5,
"steps": [
{
"id": "step-...",
"keyword": "Given",
"text": "I have entered 5 into the calculator",
"status": "passed",
"duration": 0.1
}
]
}
],
"rules": [
{
"id": "rule-...",
"name": "Addition",
"featureId": "feature-...",
"status": "passed",
"duration": 0.3,
"scenarios": []
}
]
}
],
"metadata": {
"data": {
"browser": "Chrome",
"environment": "QA",
"branch": "main"
}
}
}
See examples/golden-report.json for a complete example.
Configuration
SerializerOptions controls the output:
from behave_modern_json_report import SerializerOptions
opts = SerializerOptions(
pretty=True, # Indented JSON
include_environment=True, # Include environment block
include_attachments=True, # Include attachment metadata
embed_attachments=True, # Embed attachment content inline
exclude_passed_scenarios=False, # Drop passed scenarios (failure-only)
indent=2, # Indentation level
sort_keys=False, # Sort keys lexicographically
ensure_ascii=False, # Escape non-ASCII characters
)
Architecture
Behave Events → Collector → Execution Model → Serializer → JSON
Only collector.py and formatter.py depend on Behave. The model, serializer, validator and statistics modules are pure Python.
See docs/architecture.md for details.
Documentation
Project Structure
behave-modern-json-report/
├── behave_modern_json_report/
│ ├── __init__.py
│ ├── formatter.py # Modern JSON Formatter API entrypoint
│ ├── cucumber_formatter.py # Cucumber JSON Formatter API entrypoint
│ ├── cucumber_serializer.py # Model → Cucumber JSON (no Behave dep)
│ ├── collector.py # Behave events → model (only Behave dep)
│ ├── serializer.py # Model → JSON (no Behave dep)
│ ├── schema.py # Schema version constants
│ ├── validator.py # JSON Schema + runtime validation
│ ├── models.py # Execution model dataclasses
│ ├── statistics.py # Statistics aggregator
│ ├── environment.py # Runtime environment detection
│ ├── attach.py # High-level attachment helpers for hooks
│ ├── utils.py # IDs, timing, status, MIME helpers
│ └── schemas/
│ └── execution.schema.json
├── examples/
│ ├── behave_project/ # Real behave project example
│ │ ├── behave.ini
│ │ ├── run.py
│ │ └── features/
│ ├── calculator.feature
│ └── golden-report.json
├── docs/
├── tests/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── release.yml
├── pyproject.toml
├── Makefile
├── README.md
├── LICENSE
└── CHANGELOG.md
Testing
python -m pytest tests/ -v
Test suites:
- Unit tests — utils, statistics, environment, attachments
- Schema validation tests — golden report, invalid reports
- Serialization tests — all model fields, options, backgrounds, rules
- Cucumber serializer tests — status mapping, embeddings, output, backgrounds, outlines
- Regression tests — collector lifecycle, formatter output, status aggregation, serialization filtering,
_overall_status - Golden JSON tests — structural stability
License
MIT — see LICENSE.