Modern spreadsheet report formatters for Behave BDD — generate CSV, XLSX, and ODS execution reports with multi-sheet layouts, conditional formatting, and automatic trend history.
- Features
- Requirements
- Installation
- Quick Start
- Format Comparison
- Report Structure
- CLI Usage
- Configuration Options
- Available Columns
- Automatic History & Trends
- Programmatic Usage
- Architecture
- Ecosystem
- Contributing
- Security
- Changelog
- License
| Feature | Description | |
|---|---|---|
| 📊 | Three output formats | CSV (stdlib, zero dependencies), XLSX (via openpyxl), and ODS (via odfpy) |
| 📑 | Multi-sheet workbooks | Summary, Details, Failures, and Trends sheets in XLSX and ODS |
| 🎨 | Conditional formatting | Color-coded pass/fail cells for at-a-glance status reading |
| 📈 | Automatic trend history | Every run is persisted to a JSON file and visualized in a Trends sheet |
| ⚙️ | Configurable columns | Choose which columns appear in the Details sheet via userdata |
| 🔒 | Atomic history writes | Corruption-proof file I/O using temp file + os.replace |
| ✅ | 100% test coverage | Fully tested across Python 3.11, 3.12, 3.13, and 3.14 on Linux, Windows, and macOS |
| 🪶 | Zero required dependencies | CSV works with Python stdlib alone; XLSX and ODS are opt-in extras |
- Python 3.11 or higher
- Behave >= 1.2.6 (for running BDD tests)
openpyxl >= 3.1(optional, for XLSX output)odfpy >= 1.4(optional, for ODS output)
pip install behave-modern-sheets-report| Extra | Packages | When to use |
|---|---|---|
[behave] |
behave>=1.2.6 |
You need Behave installed alongside the formatter |
[xlsx] |
openpyxl>=3.1 |
You want XLSX output |
[ods] |
odfpy>=1.4 |
You want ODS output |
[dev] |
pytest, ruff, mypy, build, twine |
Local development |
Install with multiple extras:
pip install "behave-modern-sheets-report[xlsx,ods]"For full development setup:
git clone https://github.com/MathiasPaulenko/behave-modern-sheets-report.git
cd behave-modern-sheets-report
pip install -e ".[dev,xlsx,ods]"
pre-commit install-
The formatters are registered as
behave.formattersentry points, so they are discoverable bybehave-runnerand other tools automatically. If you use plainbehave, register them in yourbehave.ini:[behave.formatters] csv-modern = behave_modern_sheets_report.csv_formatter:CSVFormatter xlsx-modern = behave_modern_sheets_report.xlsx_formatter:XLSXFormatter ods-modern = behave_modern_sheets_report.ods_formatter:ODSFormatter
-
Run Behave with any (or all) of the formatters:
behave -f xlsx-modern -o report.xlsx
-
Open
report.xlsxin Excel, LibreOffice, or Google Sheets.
| Feature | CSV | XLSX | ODS |
|---|---|---|---|
| Dependency | stdlib | openpyxl |
odfpy |
| Multi-sheet | Single file | Yes | Yes |
| Conditional formatting | — | Cell fills | Cell styles |
| Auto-filters | — | Yes | — |
| Freeze panes | — | Yes | — |
| Bold headers | — | Yes | Yes |
| Trends sheet | — | Yes | Yes |
| Install extra | — | [xlsx] |
[ods] |
XLSX and ODS reports contain up to four sheets:
| Sheet | Content | When present |
|---|---|---|
| Summary | One row per feature with totals, pass rate, and duration | Always |
| Details | One row per scenario with configurable columns | Always |
| Failures | Failed scenarios only — error message, type, traceback, file, line | Always (empty if no failures) |
| Trends | Historical run entries with pass rate evolution over time | When history exists |
CSV reports produce a single flat file with one row per scenario (equivalent to the Details sheet).
| Status | Cell fill |
|---|---|
| Passed | Green (#C6EFCE) |
| Failed | Red (#FFC7CE) |
| Skipped | Yellow (#FFEB9C) |
The formatters are registered as behave.formatters entry points in pyproject.toml, making them discoverable by behave-runner and compatible tools. When using plain behave, register them in behave.ini:
[behave.formatters]
csv-modern = behave_modern_sheets_report.csv_formatter:CSVFormatter
xlsx-modern = behave_modern_sheets_report.xlsx_formatter:XLSXFormatter
ods-modern = behave_modern_sheets_report.ods_formatter:ODSFormatterGenerate reports:
behave -f csv-modern -o report.csv
behave -f xlsx-modern -o report.xlsx
behave -f ods-modern -o report.odsMultiple formatters can be used simultaneously to produce all formats in a single run:
behave -f csv-modern -o report.csv -f xlsx-modern -o report.xlsx -f ods-modern -o report.odsYou can also set userdata options directly in behave.ini:
[behave.userdata]
report_columns = feature,scenario,status,duration,error
report_only_failed = false
report_max_history = 50All options are passed via Behave's userdata (command-line -D or behave.ini):
| Option | Type | Default | Description |
|---|---|---|---|
report_columns |
CSV string | feature,scenario,status,duration,tags,error |
Columns shown in Details sheet |
report_only_failed |
bool | false |
Show only failed scenarios in Details |
report_delimiter |
string | comma |
CSV delimiter (comma, semicolon, tab) |
report_clear_history |
bool | false |
Clear history before appending current run |
report_history_path |
string | .behave-sheets-history.json |
Path to history JSON file |
report_max_history |
int | 100 |
Maximum history entries to retain |
Generate an XLSX report with only failed scenarios and a custom column set:
behave -f xlsx-modern -o report.xlsx \
-D report_only_failed=true \
-D report_columns=feature,scenario,status,error \
-D report_max_history=50Use a semicolon delimiter for CSV (useful in European locales):
behave -f csv-modern -o report.csv -D report_delimiter=semicolonClear history before a fresh run (e.g. after changing the test suite):
behave -f xlsx-modern -o report.xlsx -D report_clear_history=trueStore history in a custom location (e.g. outside the working directory):
behave -f xlsx-modern -o report.xlsx -D report_history_path=/tmp/behave-history.jsonThe report_columns option accepts any combination of the following column names (comma-separated):
| Column | Description |
|---|---|
feature |
Feature name |
scenario |
Scenario name |
status |
Scenario status (passed, failed, skipped, undefined) |
duration |
Execution time (human-readable, e.g. 1.234s, 12ms) |
tags |
Scenario tags (semicolon-separated) |
error |
Error message with type if available (e.g. AssertionError [...]) |
error_type |
Exception type name |
traceback |
Full traceback string |
steps |
Total step count |
passed_steps |
Number of passed steps |
failed_steps |
Number of failed steps |
skipped_steps |
Number of skipped steps |
file |
Feature file path |
line |
Line number in the feature file |
rule |
Gherkin rule name (empty if none) |
is_outline |
true if scenario outline example row, false otherwise |
feature_tags |
Feature-level tags (semicolon-separated) |
background_steps |
Number of background steps executed before the scenario |
has_data_table |
true if any step includes a Gherkin data table |
has_docstring |
true if any step includes a Gherkin docstring |
Default columns: feature,scenario,status,duration,tags,error
Every run is automatically appended to a JSON history file (.behave-sheets-history.json by default). The history feeds the Trends sheet in XLSX and ODS reports, showing pass rate evolution across runs.
report_history_path— custom location for the history file.report_clear_history— clears all previous entries before appending the current run (useful for fresh starts).report_max_history— limits the number of retained entries (oldest are dropped).
History is managed atomically (write to .tmp, then os.replace) to prevent corruption.
The history file is a JSON array of entry objects:
[
{
"run_id": "run_a1b2c3d4e5f6",
"timestamp": "2025-01-15T10:30:00.123456+00:00",
"total_features": 3,
"total_scenarios": 15,
"passed": 14,
"failed": 1,
"skipped": 0,
"undefined": 0,
"pass_rate": 93.3,
"duration": 2.45
}
]You can use the collector, writers, and history directly without Behave:
from behave_modern_sheets_report import Collector, CSVWriter, XLSXWriter, History
from pathlib import Path
# Collect results from Behave events
collector = Collector()
collector.start_feature(feature_obj)
collector.start_scenario(scenario_obj)
collector.start_step(step_obj)
collector.end_step(step_obj)
collector.end_scenario()
collector.end_feature()
run_summary = collector.finalize()
# Write CSV (no extra dependencies needed)
with open("report.csv", "w", newline="") as f:
CSVWriter.write(run_summary, f)
# Write XLSX (requires openpyxl)
history = History(max_entries=50)
trends = history.append(run_summary)
XLSXWriter.write(run_summary, Path("report.xlsx"), trends=trends)This is useful for integrating the report generation into custom test runners or CI pipelines.
Behave Runner
│
├── CSVFormatter ──► Collector ──► RunSummary ──► CSVWriter ──► report.csv
├── XLSXFormatter ──► Collector ──► RunSummary ──► XLSXWriter ──► report.xlsx
└── ODSFormatter ───► Collector ──► RunSummary ──► ODSWriter ───► report.ods
│
└──► History ──► .behave-sheets-history.json
│
└──► Trends sheet
Collector processes Behave events (feature, scenario, step, result) and builds a RunSummary. Writers serialize the summary into the target format. History persists run metrics between executions for trend analysis.
- Pure data models —
models.pyhas zero external dependencies. Dataclasses can be serialized to any format without importing Behave, openpyxl, or odfpy. - Single Behave integration point —
Collectoris the only module that touches Behave objects. Everything else operates on pure dataclasses, making the writers and history fully testable without Behave. - Opt-in dependencies — CSV works with stdlib alone.
openpyxlandodfpyare only imported when their respective writer is called. - Atomic file I/O — History writes to a
.tmpfile first, then usesos.replacefor an atomic swap. This prevents corruption if the process is killed mid-write.
Part of the behave-modern-* formatter family:
| Package | Formats | Status |
|---|---|---|
behave-modern-json-report |
JSON | ✅ |
behave-modern-html-report |
HTML | ✅ |
behave-modern-md-report |
Markdown | ✅ |
behave-modern-console-report |
Console (rich terminal) | ✅ |
behave-modern-sheets-report |
CSV, XLSX, ODS | this package |
Contributions are welcome! Please read the Contributing Guide and our Code of Conduct before submitting pull requests.
make lint # ruff check + format check
make typecheck # mypy --strict
make test # pytest with verbose output
make format # ruff auto-fix + format
make build # build sdist + wheelIf you discover a security vulnerability, please refer to our Security Policy for responsible disclosure instructions.
See CHANGELOG.md for release history and notable changes.
MIT — see LICENSE for full text.
Made with care by Mathias Paulenko