Behave Markdown Report
A modern, readable, CI/CD-friendly Markdown report formatter for the Behave BDD framework.
It generates a single report.md that you can publish directly to GitHub, GitHub Actions Summaries, GitLab, Azure DevOps, Jenkins, Bitbucket, wikis, or any Markdown viewer.
See a generated example report at examples/report.md.
See a generated step catalog at examples/step_catalog.md.
Table of Contents
- Why Markdown?
- Features
- Installation
- Quick start
- Usage
- Configuration
- Attachments
- CI/CD integration
- Examples
- Report layout
- Customizing
- Architecture
- Troubleshooting
- Development
- Changelog
- License
Why Markdown?
- Works everywhere — no JavaScript, no external assets, no HTML sanitization issues.
- CI-native — GitHub Actions, GitLab, and Azure DevOps render Markdown summaries out of the box.
- Diff-friendly — line-based changes make PR reviews easier.
- Portable — one file, easy to archive, email, or paste into an issue.
Features
- Single-file Markdown output with a table of contents and internal links.
- Executive summary showing passed, failed, and skipped counts with status icons.
- Feature summary table with links and durations.
- Tag statistics table with pass rate per tag.
- Failed scenarios section with error messages and tracebacks.
- Slowest scenarios top-10 list.
- Scenario details with collapsible sections, Gherkin backgrounds, data tables, and doc strings.
- Environment section with Python, Behave, OS, Git, and CI environment details (variables containing secrets are never included).
- Step catalog formatter that statically analyses
features/steps/and produces a Markdown catalog with patterns, parameters, docstrings, source code, and statistics. - Attachments rendered inline: images, JSON, XML, HTML, logs, and plain text.
- No heavy dependencies — only the Python standard library and
behave.
Installation
Install from PyPI:
pip install behave-modern-md-report
Or install from source:
git clone https://github.com/MathiasPaulenko/behave-modern-md-report.git
cd behave-modern-md-report
pip install -e .
For development, install with the extra dependencies:
pip install -e ".[dev]"
Quick start
Markdown report
- Create or update
behave.iniin your Behave project root:
[behave]
format = markdown
outfiles = report.md
[behave.formatters]
markdown = behave_modern_md_report.formatter:BehaveMarkdownFormatter
- Run Behave:
behave
- Open
report.mdin any Markdown viewer.
If you prefer not to register the short name, use the full formatter path:
behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
Step catalog
- Register the formatter in
behave.ini:
[behave.formatters]
stepcatalog = behave_modern_md_report.step_catalog_formatter:StepCatalogMarkdownFormatter
- Run Behave with the
stepcatalogformatter:
behave -f stepcatalog -o step_catalog.md --no-skipped
- Open
step_catalog.md— it contains a table of all step definitions, their patterns, parameters, docstrings, source code, and aggregate statistics.
You can also combine both formatters in a single run:
[behave]
format = markdown
stepcatalog
outfiles = report.md
step_catalog.md
Usage
Using a single feature file
behave -f markdown -o report.md features/login.feature
Combining with other formatters
Behave supports multiple formatters at once. For example, keep the console output while generating the Markdown report:
behave -f pretty -o /dev/null -f markdown -o report.md
On Windows use NUL instead of /dev/null:
behave -f pretty -o NUL -f markdown -o report.md
Output to stdout
Omit -o and the report is written to stdout instead of a file:
behave -f markdown
Available formatters
| Formatter name | Purpose |
|---|---|
markdown |
The Markdown test report |
stepcatalog |
A catalog of all step definitions in features/steps |
Both must be registered in the [behave.formatters] section of behave.ini
(or setup.cfg / tox.ini) — Behave does not discover custom formatters from
installed packages. Alternatively, always pass the fully qualified class path:
behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
Configuration
All rendering options are passed through Behave's userdata mechanism, so they
work from behave.ini, setup.cfg, tox.ini, or -D CLI flags.
Full behave.ini example
[behave]
format = markdown
outfiles = report.md
[behave.formatters]
markdown = behave_modern_md_report.formatter:BehaveMarkdownFormatter
[behave.userdata]
bmr.title = My Markdown Report
bmr.project_name = My Project
bmr.company = My Company
bmr.include_passed = true
bmr.include_skipped = true
bmr.include_traceback = true
bmr.include_environment = true
bmr.sort_failed_first = true
bmr.max_traceback_lines = 0
bmr.max_summary_features = 0
bmr.steps_dir = features/steps
bmr.catalog_title = Step Catalog
Options reference
| Option | Type | Default | Description |
|---|---|---|---|
bmr.title |
string | Behave Markdown Report |
Report title shown in the main heading. |
bmr.project_name |
string | "" |
Project name shown in the header. |
bmr.company |
string | "" |
Company name shown in the header. |
bmr.include_passed |
boolean | true |
Include passed scenarios in the collapsible details section. |
bmr.include_skipped |
boolean | true |
Include skipped scenarios in the collapsible details section. |
bmr.include_traceback |
boolean | true |
Render tracebacks for failed steps. |
bmr.include_environment |
boolean | true |
Render the environment section. |
bmr.sort_failed_first |
boolean | true |
Sort failed features first in the feature summary. |
bmr.max_traceback_lines |
integer | 0 |
Limit traceback lines; 0 means unlimited. |
bmr.max_summary_features |
integer | 0 |
Limit feature summary rows; 0 means unlimited. |
bmr.steps_dir |
string | features/steps |
Directory scanned by the stepcatalog formatter. |
bmr.catalog_title |
string | Step Catalog |
Title of the step catalog document. |
Boolean values accept true/false, yes/no, 1/0, or on/off.
Command line
behave -D bmr.title="Nightly Report" -D bmr.include_environment=false
Notes
-
Environment variables: Behave does not map env vars to
userdatakeys. To configure from the environment, pass the value with-D:behave -D bmr.title="$REPORT_TITLE"
-
environment.pyhooks: settingcontext.config.userdatainsidebefore_allhas no effect on the report — Behave instantiates formatters (and parses options) before hooks run. Configurebmr.*inbehave.inior with-D.
Disabling sections
Set the corresponding boolean option to false to hide a section — useful for
very large suites:
[behave.userdata]
bmr.include_environment = false
bmr.include_traceback = false
Attachments
Capture screenshots, logs, JSON, or files directly from your features/environment.py hooks.
Available helpers
from behave_modern_md_report import (
attach_file,
attach_json,
attach_screenshot,
attach_text,
log,
)
| Helper | Purpose |
|---|---|
log(context, message) |
Append a plain text line to the current step. |
attach_text(context, text, name="note.txt") |
Attach a text snippet. |
attach_json(context, data, name="data.json") |
Attach any JSON-serializable object. |
attach_file(context, path, name=None) |
Attach a file from disk. |
attach_screenshot(context, source, name="screenshot.png") |
Attach a screenshot image. |
All helpers are safe to call even when the Markdown formatter is not active: they simply do nothing.
Typical failure hook
def after_step(context, step):
if step.status == "failed":
log(context, f"Step failed: {step.name}")
attach_text(context, f"URL at failure: {getattr(context, 'url', 'unknown')}")
attach_json(context, {"status_code": getattr(context, 'status_code', None)})
attach_screenshot(context, context.driver, name="failure.png")
Behave's own context.embed(mime_type, data) is also supported and attaches to
the current step.
Screenshot sources
attach_screenshot accepts several source types:
- Selenium WebDriver:
attach_screenshot(context, context.driver) - Playwright Page:
attach_screenshot(context, context.page) - PIL Image:
attach_screenshot(context, image) - Raw bytes:
attach_screenshot(context, png_bytes) - File path:
attach_screenshot(context, "screenshots/failure.png")
The MIME type is inferred from the file extension or the source object.
Supported attachment types in the report
| MIME type | Rendering |
|---|---|
image/png, image/jpeg, image/webp, image/gif |
Inline base64 image |
application/json |
Fenced json code block |
application/xml, application/xhtml+xml |
Fenced xml code block |
text/html |
Fenced html code block |
text/plain |
Fenced text code block |
| Other text types | Fenced text code block |
| Other binary types | Name and MIME type labelled as "base64 encoded" (content is not embedded) |
Logs
Logs are rendered as a text code block inside the step. Output captured by
Behave itself (stdout, stderr, logging) is also shown for non-passed steps.
CI/CD integration
The report is a single plain-text file that can be archived, uploaded as an artifact, or pasted into CI summaries.
GitHub Actions
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install behave-modern-md-report
- run: behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
- uses: actions/upload-artifact@v4
if: always()
with:
name: behave-report
path: report.md
Post the report to the job summary (GitHub renders the Markdown directly):
- run: behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
- run: cat report.md >> "$GITHUB_STEP_SUMMARY"
if: always()
GitLab CI
test:
image: python:3.12
script:
- pip install behave-modern-md-report
- behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
artifacts:
when: always
paths:
- report.md
The report is stored as a downloadable job artifact. To publish it in the UI, host it with GitLab Pages or convert it to HTML.
Azure DevOps
steps:
- script: |
pip install behave-modern-md-report
behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
displayName: Run Behave tests
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: report.md
ArtifactName: behave-report
condition: always()
The report is stored as a downloadable build artifact. To display it in the UI, publish it with a Markdown/extension step or convert it to HTML.
Jenkins
Archive the report as an artifact:
post {
always {
archiveArtifacts artifacts: 'report.md', allowEmptyArchive: true
}
}
Use the HTML Publisher plugin or a Markdown viewer plugin to display the report in the build page.
Best practices
- Always archive the report even when tests fail (
if: always()in GitHub Actions,when: alwaysin GitLab). - Keep the report in the workspace for the shortest path to failure investigation.
- Use
bmr.include_environment = truein CI to capture Python version, OS, and environment variables. Variables that look like secrets (tokens, keys, passwords, credentials) are automatically excluded. - For large suites, set
bmr.max_traceback_linesto a reasonable number to keep the report readable. - Publish the report as a job summary when the platform supports it; this avoids downloading artifacts for quick checks.
Examples
The repository includes three examples:
examples/behave_project/— a complete Behave project that exercises all report sections: backgrounds, scenario outlines, GherkinRulegroups (Behave 1.3.x), passing/failing/skipped/pending/undefined scenarios, attachments, and slow scenarios.examples/demo_generator/— a script that generates a syntheticdemo-report.mdwithout running Behave.examples/step_catalog.md— a generated step catalog showing all step definitions, patterns, parameters, and source code.
Run the example project:
cd examples/behave_project
pip install -r requirements.txt
behave
It generates report.md and step_catalog.md in the same directory. Run a
subset of features with behave --tags=login (or checkout, smoke).
Generate the demo report:
python examples/demo_generator/generate_demo.py
Report layout
A generated report contains the following sections in order:
# My Report
## Table of Contents
## Executive Summary
## Statistics
## Feature Summary
## Tags
## Failed Scenarios
## Slowest Scenarios
## Scenario Details
## Environment
Each section is linked from the table of contents. Scenario details are wrapped in collapsible <details> blocks so the report stays readable even for large suites.
Step catalog layout
The step catalog (stepcatalog formatter) produces:
# 📋 Step Catalog
## Statistics
## Steps by Keyword
## Steps by File
## Step Definitions
Each step definition is wrapped in a collapsible <details> block showing its keyword, pattern, function name, location, parameters, docstring, and source code.
Customizing
RenderOptions
If you use the renderer outside of Behave, build a RenderOptions object:
from behave_modern_md_report import MarkdownRenderer, RenderOptions
options = RenderOptions(
title="Custom Report",
project_name="My Project",
company="My Company",
include_passed_scenarios=False,
include_traceback=True,
)
renderer = MarkdownRenderer(options)
markdown = renderer.render(execution)
Subclassing the renderer
For full control over the Markdown output, subclass MarkdownRenderer and
override the methods that generate each section:
from behave_modern_md_report.renderer import MarkdownRenderer
class CompactRenderer(MarkdownRenderer):
def _render_environment(self, md, execution):
# Skip the environment section entirely.
return
def _render_executive_summary(self, md, execution):
# Render a single-line summary instead of a table.
stats = execution.statistics
md.heading(2, f"{stats.passed} passed, {stats.failed} failed")
Adding custom sections
Override render and call the section methods in your own order. Remember to
compute the statistics first and to finish with md.join():
from behave_modern_md_report.markdown import MarkdownBuilder
from behave_modern_md_report import statistics as stats_mod
class CustomRenderer(MarkdownRenderer):
def render(self, execution):
stats_mod.compute(execution)
md = MarkdownBuilder()
self._render_header(md, execution)
self._render_executive_summary(md, execution)
self._render_feature_summary(md, execution)
self._render_failed_scenarios(md, execution)
md.heading(2, "Custom notes")
md.paragraph("This section is added by a custom renderer.")
self._render_environment(md, execution)
self._render_footer(md, execution)
return md.join()
All MarkdownBuilder methods take positional arguments — e.g.
md.heading(2, "Title"), md.paragraph("text").
Custom status icons
The status icon mapping is defined in renderer.py as _STATUS_ICONS. You can
replace it by monkey-patching before rendering:
from behave_modern_md_report import renderer
renderer._STATUS_ICONS["passed"] = "✔"
renderer._STATUS_ICONS["failed"] = "✘"
Use configuration options for common changes and renderer subclassing for layout changes, new sections, or custom Markdown formatting. Keep custom renderers in your own project or a separate package to avoid forking the formatter.
Architecture
Behave Markdown Report uses a clean, layered architecture:
Behave events
|
v
Formatter (formatter.py) -- adapts Behave lifecycle events
|
v
Collector (collector.py) -- builds the Execution model
|
v
Models (models.py) -- pure dataclasses
|
v
Statistics (statistics.py) -- aggregates and derived metrics
|
v
Renderer (renderer.py) -- Execution model -> Markdown
|
v
markdown.py helpers -- headings, tables, details blocks
|
v
report.md
| Layer | File | Responsibility |
|---|---|---|
| Formatter | formatter.py |
Adapts Behave events to the collector. |
| Collector | collector.py |
Builds the Execution model from Behave objects. |
| Models | models.py |
Pure dataclasses representing the test tree. |
| Statistics | statistics.py |
Computes aggregates and derived metrics. |
| Renderer | renderer.py |
Turns the Execution model into Markdown. |
| Markdown | markdown.py |
Low-level Markdown document helpers. |
| Step Scanner | step_scanner.py |
AST-based static analysis of step definitions. |
| Step Catalog | step_catalog_formatter.py |
Renders step catalog Markdown from scanned definitions. |
| Attach API | attach.py |
Convenience helpers for environment.py. |
Key design decisions:
- Behave-independent renderer: the renderer only knows the
Executionmodel and does not import Behave. - Real final statuses: the collector keeps references to the live Behave
objects and reads their final status/duration; it falls back to deriving
statuses only when Behave leaves them
untested. - Planned steps: steps are registered when Behave plans them and completed
on
result, so skipped/unexecuted steps still appear in the report. - Single-file output: one Markdown string written at the end of the run — trivial to archive, publish, or paste into CI summaries.
Troubleshooting
BAD_FORMAT=markdown (problem: LookupError)
The formatter is not registered. Add this to behave.ini (in the working directory):
[behave.formatters]
markdown = behave_modern_md_report.formatter:BehaveMarkdownFormatter
Alternatively, use the full module path on the CLI:
behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
Report is empty or says "Untested"
Ensure the formatter class is used by Behave and not a custom subclass that bypasses the collector. The example project in examples/behave_project/ demonstrates a working setup.
Development
See CONTRIBUTING.md for the full contributing guide, CODE_OF_CONDUCT.md for community rules, and SECURITY.md for the vulnerability reporting policy.
Install the development dependencies:
pip install -e ".[dev]"
# or
make install-dev
Run the checks:
make test # python -m pytest
make lint # python -m ruff check .
make typecheck # python -m mypy behave_modern_md_report
make format # python -m ruff check --fix . && python -m ruff format .
Build the package:
python -m build
Project conventions
- Python 3.11+, type hints everywhere, Google-style docstrings.
- Dataclasses for models (no Pydantic dependency).
- No external CSS/JS — the report is plain Markdown.
- Keep layers separated: formatter ↔ collector ↔ models ↔ statistics ↔ renderer.
Iterating on the report
The fastest loop is to regenerate the demo report and inspect it:
python examples/demo_generator/generate_demo.py
You can also run the example Behave project:
cd examples/behave_project
behave
Adding a new bmr.* option
- Add a field to
RenderOptionsinrenderer.py. - Read it from
userdatainRenderOptions.from_userdata(). - Use it in
MarkdownRendererto change rendering behaviour. - Document it in this README and add a unit test if the logic is non-trivial.
Releasing
Releases are fully automatic: bump version in pyproject.toml, update
CHANGELOG.md, commit and push to main. The Release workflow detects the
version bump, creates the tag, builds the distribution, publishes to PyPI via
Trusted Publishing, and creates a GitHub Release with the changelog notes.
Changelog
See CHANGELOG.md for the release history.
License
This project is licensed under the MIT License. See LICENSE for details.
Metadata
Release files for behave-modern-md-report 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| behave_modern_md_report-1.3.0.tar.gz | 55.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| behave_modern_md_report-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 93.3 kB
Release files / behave_modern_md_report-1.3.0.tar.gz
| Download URL | behave_modern_md_report-1.3.0.tar.gz |
|---|---|
| Size | 55.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4a9f05a069445c584afe672ba40a2b03417ac6706228bad3db28e07109c20785
|
|
BLAKE2b-256 checksum How to use checksums |
7f1764c54d12c55388ab4a27fe210b5fdd9c0066df578145311112d360a2b774
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / behave_modern_md_report-1.3.0-py3-none-any.whl
| Download URL | behave_modern_md_report-1.3.0-py3-none-any.whl |
|---|---|
| Size | 38.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
aba7bc992b1e705ef074e039be5675a3da28d8c4732f1e32130bdb265c97aeaa
|
|
BLAKE2b-256 checksum How to use checksums |
9fd3d51a9beebad9f29e26ac7916d0de01c54788e155aadd6094c5590592412a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency log