Skip to main content

Behave Markdown Report

PyPI Python CI License

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?

  • 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

  1. Create or update behave.ini in your Behave project root:
[behave]
format = markdown
outfiles = report.md

[behave.formatters]
markdown = behave_modern_md_report.formatter:BehaveMarkdownFormatter
  1. Run Behave:
behave
  1. Open report.md in 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

  1. Register the formatter in behave.ini:
[behave.formatters]
stepcatalog = behave_modern_md_report.step_catalog_formatter:StepCatalogMarkdownFormatter
  1. Run Behave with the stepcatalog formatter:
behave -f stepcatalog -o step_catalog.md --no-skipped
  1. 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 userdata keys. To configure from the environment, pass the value with -D:

    behave -D bmr.title="$REPORT_TITLE"
    
  • environment.py hooks: setting context.config.userdata inside before_all has no effect on the report — Behave instantiates formatters (and parses options) before hooks run. Configure bmr.* in behave.ini or 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: always in GitLab).
  • Keep the report in the workspace for the shortest path to failure investigation.
  • Use bmr.include_environment = true in 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_lines to 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, Gherkin Rule groups (Behave 1.3.x), passing/failing/skipped/pending/undefined scenarios, attachments, and slow scenarios.
  • examples/demo_generator/ — a script that generates a synthetic demo-report.md without 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 Execution model 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

  1. Add a field to RenderOptions in renderer.py.
  2. Read it from userdata in RenderOptions.from_userdata().
  3. Use it in MarkdownRenderer to change rendering behaviour.
  4. 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)

Source distribution for behave-modern-md-report 1.3.0
File Size Uploaded
behave_modern_md_report-1.3.0.tar.gz 55.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for behave-modern-md-report 1.3.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

1.3.0 This release

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page