Metadata-Version: 2.5
Name: behave-modern-md-report
Version: 1.3.0
Summary: A modern, readable, CI/CD-friendly Markdown report formatter for Behave.
Project-URL: Homepage, https://github.com/MathiasPaulenko/behave-modern-md-report
Project-URL: Documentation, https://github.com/MathiasPaulenko/behave-modern-md-report/blob/main/README.md
Project-URL: Repository, https://github.com/MathiasPaulenko/behave-modern-md-report
Project-URL: Issues, https://github.com/MathiasPaulenko/behave-modern-md-report/issues
Author: Open Source Contributors
License: MIT
License-File: LICENSE
Keywords: bdd,behave,cicd,github,markdown,report,testing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Software Development :: Testing :: BDD
Requires-Python: >=3.11
Requires-Dist: behave>=1.2.6
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Description-Content-Type: text/markdown

# Behave Markdown Report

[![PyPI](https://img.shields.io/badge/pypi-behave--modern--md--report-blue)](https://pypi.org/p/behave-modern-md-report)
[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
[![CI](https://img.shields.io/badge/CI-GitHub%20Actions-brightgreen)](.github/workflows/ci.yml)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)

A modern, readable, CI/CD-friendly Markdown report formatter for the [Behave](https://github.com/behave/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`](examples/report.md).
See a generated step catalog at [`examples/step_catalog.md`](examples/step_catalog.md).

## Table of Contents

- [Why Markdown?](#why-markdown)
- [Features](#features)
- [Installation](#installation)
- [Quick start](#quick-start)
- [Usage](#usage)
- [Configuration](#configuration)
- [Attachments](#attachments)
- [CI/CD integration](#cicd-integration)
- [Examples](#examples)
- [Report layout](#report-layout)
- [Customizing](#customizing)
- [Architecture](#architecture)
- [Troubleshooting](#troubleshooting)
- [Development](#development)
- [Changelog](#changelog)
- [License](#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:

```bash
pip install behave-modern-md-report
```

Or install from source:

```bash
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:

```bash
pip install -e ".[dev]"
```

## Quick start

### Markdown report

1. Create or update `behave.ini` in your Behave project root:

```ini
[behave]
format = markdown
outfiles = report.md

[behave.formatters]
markdown = behave_modern_md_report.formatter:BehaveMarkdownFormatter
```

2. Run Behave:

```bash
behave
```

3. Open `report.md` in any Markdown viewer.

If you prefer not to register the short name, use the full formatter path:

```bash
behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
```

### Step catalog

1. Register the formatter in `behave.ini`:

```ini
[behave.formatters]
stepcatalog = behave_modern_md_report.step_catalog_formatter:StepCatalogMarkdownFormatter
```

2. Run Behave with the `stepcatalog` formatter:

```bash
behave -f stepcatalog -o step_catalog.md --no-skipped
```

3. 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:

```ini
[behave]
format = markdown
    stepcatalog
outfiles = report.md
    step_catalog.md
```

## Usage

### Using a single feature file

```bash
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:

```bash
behave -f pretty -o /dev/null -f markdown -o report.md
```

On Windows use `NUL` instead of `/dev/null`:

```powershell
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:

```bash
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:

```bash
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

```ini
[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

```bash
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`:

  ```bash
  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:

```ini
[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

```python
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

```python
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

```yaml
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):

```yaml
      - run: behave -f behave_modern_md_report.formatter:BehaveMarkdownFormatter -o report.md
      - run: cat report.md >> "$GITHUB_STEP_SUMMARY"
        if: always()
```

### GitLab CI

```yaml
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

```yaml
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:

```groovy
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:

```bash
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:

```bash
python examples/demo_generator/generate_demo.py
```

## Report layout

A generated report contains the following sections in order:

```markdown
# 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:

```markdown
# 📋 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:

```python
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:

```python
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()`:

```python
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:

```python
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:

```text
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):

```ini
[behave.formatters]
markdown = behave_modern_md_report.formatter:BehaveMarkdownFormatter
```

Alternatively, use the full module path on the CLI:

```bash
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](CONTRIBUTING.md) for the full contributing guide,
[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community rules, and
[SECURITY.md](SECURITY.md) for the vulnerability reporting policy.

Install the development dependencies:

```bash
pip install -e ".[dev]"
# or
make install-dev
```

Run the checks:

```bash
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:

```bash
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:

```bash
python examples/demo_generator/generate_demo.py
```

You can also run the example Behave project:

```bash
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](CHANGELOG.md) for the release history.

## License

This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
