pytest-given 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_given-0.1.0 → pytest_given-0.3.0}/.gitignore +8 -0
- pytest_given-0.3.0/AGENTS.md +127 -0
- pytest_given-0.3.0/CHANGELOG.md +360 -0
- pytest_given-0.3.0/GLOSSARY.md +108 -0
- pytest_given-0.3.0/PKG-INFO +131 -0
- pytest_given-0.3.0/README.md +103 -0
- pytest_given-0.3.0/THIRD-PARTY-LICENSES +137 -0
- pytest_given-0.3.0/docs/site/configuration/narration-lint.md +51 -0
- pytest_given-0.3.0/noxfile.py +606 -0
- pytest_given-0.3.0/pyproject.toml +193 -0
- {pytest_given-0.1.0/src/pytest_given/skills_data → pytest_given-0.3.0/src/pytest_given/.agents/skills}/pytest-given-authoring/SKILL.md +17 -8
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-authoring/references/api.md +61 -0
- {pytest_given-0.1.0/src/pytest_given/skills_data → pytest_given-0.3.0/src/pytest_given/.agents/skills}/pytest-given-authoring/references/domain-storytelling.md +12 -11
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-authoring/references/glossaries.md +82 -0
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-authoring/references/scenarios.md +81 -0
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-authoring/references/stories.md +77 -0
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-navigating/SKILL.md +41 -0
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-navigating/references/report-json.md +112 -0
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-reviewing/SKILL.md +69 -0
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-reviewing/references/pairs.md +64 -0
- pytest_given-0.3.0/src/pytest_given/.agents/skills/pytest-given-reviewing/references/story-coverage.md +24 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/__init__.py +7 -5
- pytest_given-0.3.0/src/pytest_given/capture/__init__.py +89 -0
- pytest_given-0.3.0/src/pytest_given/capture/collector.py +469 -0
- pytest_given-0.3.0/src/pytest_given/capture/discovery.py +68 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/capture/file_glossary.py +30 -38
- pytest_given-0.3.0/src/pytest_given/capture/glossary.py +261 -0
- pytest_given-0.3.0/src/pytest_given/capture/kind_inference.py +113 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/capture/markdown_glossary.py +57 -27
- pytest_given-0.3.0/src/pytest_given/capture/process_state.py +70 -0
- pytest_given-0.3.0/src/pytest_given/capture/scenario.py +158 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/capture/source.py +99 -115
- pytest_given-0.3.0/src/pytest_given/capture/steps.py +396 -0
- pytest_given-0.3.0/src/pytest_given/capture/story.py +420 -0
- pytest_given-0.3.0/src/pytest_given/capture/template.py +221 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/capture/traceback.py +33 -46
- pytest_given-0.3.0/src/pytest_given/cli/__init__.py +32 -0
- pytest_given-0.3.0/src/pytest_given/cli/report.py +125 -0
- pytest_given-0.1.0/src/pytest_given/cli.py → pytest_given-0.3.0/src/pytest_given/cli/skills.py +44 -55
- pytest_given-0.3.0/src/pytest_given/grouping/__init__.py +10 -0
- pytest_given-0.3.0/src/pytest_given/grouping/attachments.py +143 -0
- pytest_given-0.3.0/src/pytest_given/grouping/checks.py +406 -0
- pytest_given-0.3.0/src/pytest_given/grouping/columns.py +268 -0
- pytest_given-0.3.0/src/pytest_given/grouping/context.py +104 -0
- pytest_given-0.3.0/src/pytest_given/grouping/group.py +102 -0
- pytest_given-0.3.0/src/pytest_given/grouping/percase.py +91 -0
- pytest_given-0.3.0/src/pytest_given/grouping/templatize.py +265 -0
- pytest_given-0.3.0/src/pytest_given/lint/__init__.py +25 -0
- pytest_given-0.3.0/src/pytest_given/lint/ast_rules.py +485 -0
- pytest_given-0.3.0/src/pytest_given/lint/base.py +65 -0
- pytest_given-0.3.0/src/pytest_given/lint/config.py +178 -0
- pytest_given-0.3.0/src/pytest_given/lint/runner.py +41 -0
- pytest_given-0.3.0/src/pytest_given/lint/runtime_rules.py +180 -0
- pytest_given-0.3.0/src/pytest_given/lint/summary.py +45 -0
- pytest_given-0.3.0/src/pytest_given/model/__init__.py +162 -0
- pytest_given-0.3.0/src/pytest_given/model/errors.py +47 -0
- pytest_given-0.3.0/src/pytest_given/model/narration.py +122 -0
- pytest_given-0.3.0/src/pytest_given/model/runtime.py +67 -0
- pytest_given-0.3.0/src/pytest_given/model/schema.py +436 -0
- pytest_given-0.3.0/src/pytest_given/model/serde.py +377 -0
- pytest_given-0.3.0/src/pytest_given/model/steps.py +43 -0
- pytest_given-0.3.0/src/pytest_given/model/text.py +50 -0
- pytest_given-0.3.0/src/pytest_given/plugin/__init__.py +43 -0
- pytest_given-0.3.0/src/pytest_given/plugin/collection.py +79 -0
- pytest_given-0.3.0/src/pytest_given/plugin/fixtures.py +231 -0
- pytest_given-0.3.0/src/pytest_given/plugin/options.py +248 -0
- pytest_given-0.3.0/src/pytest_given/plugin/runtest.py +242 -0
- pytest_given-0.3.0/src/pytest_given/plugin/session.py +338 -0
- pytest_given-0.3.0/src/pytest_given/plugin/state.py +139 -0
- pytest_given-0.3.0/src/pytest_given/report/__init__.py +40 -0
- pytest_given-0.3.0/src/pytest_given/report/coverage.py +166 -0
- pytest_given-0.3.0/src/pytest_given/report/glossary_view.py +372 -0
- pytest_given-0.3.0/src/pytest_given/report/html_renderer.py +503 -0
- pytest_given-0.3.0/src/pytest_given/report/inline_markdown.py +52 -0
- pytest_given-0.3.0/src/pytest_given/report/md_renderer.py +267 -0
- pytest_given-0.3.0/src/pytest_given/report/palette.py +204 -0
- pytest_given-0.3.0/src/pytest_given/report/sinks.py +209 -0
- pytest_given-0.3.0/src/pytest_given/report/slugs.py +107 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/report/source_link.py +60 -42
- pytest_given-0.3.0/src/pytest_given/report/story_view.py +170 -0
- pytest_given-0.3.0/src/pytest_given/report/templates/_macros.html.j2 +155 -0
- pytest_given-0.3.0/src/pytest_given/report/templates/app.js +925 -0
- pytest_given-0.3.0/src/pytest_given/report/templates/fonts/source-code-pro-latin-wght-normal.woff2 +0 -0
- pytest_given-0.3.0/src/pytest_given/report/templates/fonts/source-sans-3-latin-wght-normal.woff2 +0 -0
- pytest_given-0.3.0/src/pytest_given/report/templates/logo.svg +1 -0
- pytest_given-0.3.0/src/pytest_given/report/templates/report.html.j2 +648 -0
- pytest_given-0.3.0/src/pytest_given/report/templates/styles.css +1391 -0
- pytest_given-0.3.0/src/pytest_given/report/text.py +8 -0
- pytest_given-0.3.0/src/pytest_given/report/theme.py +30 -0
- pytest_given-0.3.0/tests/conftest.py +35 -0
- pytest_given-0.3.0/tests/integration/test_cli.py +394 -0
- pytest_given-0.3.0/tests/integration/test_plugin.py +3100 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/test_plugin_file_glossary.py +4 -4
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/test_plugin_lint.py +177 -77
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/test_plugin_session_isolation.py +89 -2
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/test_skills_cli.py +19 -2
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/ubiquitous_language.py +43 -19
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_collector.py +281 -125
- pytest_given-0.3.0/tests/unit/capture/test_discovery.py +119 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_file_glossary.py +32 -47
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_glossary.py +96 -77
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_kind_inference.py +83 -50
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_markdown_glossary.py +53 -16
- pytest_given-0.3.0/tests/unit/capture/test_rootdir_isolation.py +17 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_source.py +27 -11
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_step_descriptor.py +479 -221
- pytest_given-0.3.0/tests/unit/capture/test_story.py +871 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_template.py +105 -79
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/test_traceback_parser.py +22 -46
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/lint/test_ast_rules.py +327 -145
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/lint/test_config.py +95 -26
- pytest_given-0.3.0/tests/unit/lint/test_runtime_rules.py +329 -0
- pytest_given-0.3.0/tests/unit/lint/test_summary.py +73 -0
- pytest_given-0.3.0/tests/unit/model/test_narration.py +15 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/model/test_schema.py +161 -105
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/model/test_serde.py +372 -69
- pytest_given-0.3.0/tests/unit/model/test_steps.py +34 -0
- pytest_given-0.3.0/tests/unit/model/test_text.py +22 -0
- pytest_given-0.3.0/tests/unit/report/test_coverage.py +624 -0
- pytest_given-0.3.0/tests/unit/report/test_glossary_view.py +495 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/test_html_renderer.py +633 -180
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/test_md_renderer.py +312 -41
- pytest_given-0.3.0/tests/unit/report/test_palette.py +153 -0
- pytest_given-0.3.0/tests/unit/report/test_sinks.py +226 -0
- pytest_given-0.3.0/tests/unit/report/test_slugs.py +138 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/test_source_link.py +177 -80
- pytest_given-0.3.0/tests/unit/report/test_story_view.py +462 -0
- pytest_given-0.3.0/tests/unit/report/test_theme.py +22 -0
- pytest_given-0.3.0/tests/unit/test_grouping.py +2860 -0
- pytest_given-0.3.0/tests/unit/test_percase.py +223 -0
- pytest_given-0.3.0/tests/unit/test_plugin.py +593 -0
- pytest_given-0.3.0/tests/unit/test_plugin_traceback.py +45 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/test_skills_data.py +20 -8
- pytest_given-0.3.0/tests/unit/test_skills_scripts.py +100 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/uv.lock +209 -5
- pytest_given-0.1.0/AGENTS.md +0 -104
- pytest_given-0.1.0/CHANGELOG.md +0 -42
- pytest_given-0.1.0/GLOSSARY.md +0 -94
- pytest_given-0.1.0/PKG-INFO +0 -510
- pytest_given-0.1.0/README.md +0 -482
- pytest_given-0.1.0/THIRD-PARTY-LICENSES +0 -26
- pytest_given-0.1.0/noxfile.py +0 -352
- pytest_given-0.1.0/pyproject.toml +0 -127
- pytest_given-0.1.0/src/pytest_given/capture/__init__.py +0 -48
- pytest_given-0.1.0/src/pytest_given/capture/collector.py +0 -369
- pytest_given-0.1.0/src/pytest_given/capture/decorators.py +0 -502
- pytest_given-0.1.0/src/pytest_given/capture/glossary.py +0 -258
- pytest_given-0.1.0/src/pytest_given/capture/kind_inference.py +0 -102
- pytest_given-0.1.0/src/pytest_given/capture/story.py +0 -303
- pytest_given-0.1.0/src/pytest_given/capture/template.py +0 -180
- pytest_given-0.1.0/src/pytest_given/lint/__init__.py +0 -47
- pytest_given-0.1.0/src/pytest_given/lint/ast_rules.py +0 -395
- pytest_given-0.1.0/src/pytest_given/lint/base.py +0 -77
- pytest_given-0.1.0/src/pytest_given/lint/config.py +0 -137
- pytest_given-0.1.0/src/pytest_given/lint/runtime_rules.py +0 -212
- pytest_given-0.1.0/src/pytest_given/model/__init__.py +0 -84
- pytest_given-0.1.0/src/pytest_given/model/errors.py +0 -2
- pytest_given-0.1.0/src/pytest_given/model/ids.py +0 -32
- pytest_given-0.1.0/src/pytest_given/model/schema.py +0 -326
- pytest_given-0.1.0/src/pytest_given/model/serde.py +0 -263
- pytest_given-0.1.0/src/pytest_given/plugin.py +0 -958
- pytest_given-0.1.0/src/pytest_given/report/__init__.py +0 -10
- pytest_given-0.1.0/src/pytest_given/report/aggregations.py +0 -382
- pytest_given-0.1.0/src/pytest_given/report/cli.py +0 -68
- pytest_given-0.1.0/src/pytest_given/report/coverage.py +0 -175
- pytest_given-0.1.0/src/pytest_given/report/html_renderer.py +0 -370
- pytest_given-0.1.0/src/pytest_given/report/inline_markdown.py +0 -35
- pytest_given-0.1.0/src/pytest_given/report/md_renderer.py +0 -164
- pytest_given-0.1.0/src/pytest_given/report/templates/app.js +0 -438
- pytest_given-0.1.0/src/pytest_given/report/templates/report.html.j2 +0 -606
- pytest_given-0.1.0/src/pytest_given/report/templates/styles.css +0 -1013
- pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-authoring/references/api.md +0 -62
- pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-authoring/references/glossaries.md +0 -68
- pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-authoring/references/scenarios.md +0 -29
- pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-authoring/references/stories.md +0 -53
- pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-navigating/SKILL.md +0 -40
- pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-navigating/references/report-json.md +0 -69
- pytest_given-0.1.0/src/pytest_given/skills_data/pytest-given-reviewing/SKILL.md +0 -47
- pytest_given-0.1.0/tests/conftest.py +0 -3
- pytest_given-0.1.0/tests/integration/test_cli.py +0 -178
- pytest_given-0.1.0/tests/integration/test_plugin.py +0 -1869
- pytest_given-0.1.0/tests/unit/capture/test_story.py +0 -648
- pytest_given-0.1.0/tests/unit/lint/test_runtime_rules.py +0 -285
- pytest_given-0.1.0/tests/unit/model/test_ids.py +0 -21
- pytest_given-0.1.0/tests/unit/report/test_aggregations.py +0 -762
- pytest_given-0.1.0/tests/unit/report/test_coverage.py +0 -535
- pytest_given-0.1.0/tests/unit/test_plugin.py +0 -609
- {pytest_given-0.1.0 → pytest_given-0.3.0}/LICENSE.md +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/conftest.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/py.typed +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/src/pytest_given/report/templates/alpine.min.js +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/__init__.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/integration/__init__.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/__init__.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/capture/__init__.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/lint/__init__.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/model/__init__.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/model/test_errors.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/__init__.py +0 -0
- {pytest_given-0.1.0 → pytest_given-0.3.0}/tests/unit/report/test_inline_markdown.py +0 -0
|
@@ -21,3 +21,11 @@ docs/superpowers/plans/
|
|
|
21
21
|
.claude/settings.local.json
|
|
22
22
|
.playwright-mcp/
|
|
23
23
|
.mcp.json
|
|
24
|
+
|
|
25
|
+
# Documentation site: build output and the copies docs_build stages into docs_dir
|
|
26
|
+
/site/
|
|
27
|
+
/docs/site/examples/
|
|
28
|
+
/docs/site/assets/pytest-given-diagram-full.svg
|
|
29
|
+
/docs/site/assets/fonts/source-sans-3-latin-wght-normal.woff2
|
|
30
|
+
/docs/site/assets/fonts/source-code-pro-latin-wght-normal.woff2
|
|
31
|
+
/docs/site/assets/logo.svg
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Agents
|
|
2
|
+
|
|
3
|
+
## Project overview
|
|
4
|
+
|
|
5
|
+
See [README.md](README.md) for the pitch and quick start, and the [documentation site](https://nwilbert.github.io/pytest-given/latest/) (sources under `docs/site/`) for the public API and CLI flags. The rest of this document is contributor-facing.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync --group dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**All Python invocations go through `uv run`** — `uv run pytest …`, `uv run python -m …`, `uv run nox …`. There is no system `python` on PATH; bare `python` / `pytest` calls will fail. This applies to one-off commands (running a single test file, REPL exploration) too, not just nox sessions.
|
|
14
|
+
|
|
15
|
+
**Never prepend `cd <path> &&` to commands.** The working directory is already set to the project root; the `cd` is redundant and triggers a permission prompt.
|
|
16
|
+
|
|
17
|
+
## Quality gates
|
|
18
|
+
|
|
19
|
+
`uv run nox` runs the default gate — `format`, `lint`, `mypy`, `test`, `coverage` (a 100% target), `audit` (a `pip-audit` of the locked dependencies). **Run it (or at minimum `uv run nox -s format lint mypy test`) before every commit.** The sessions below are on-demand; list them all with `uv run nox -l`.
|
|
20
|
+
|
|
21
|
+
- `uv run nox -s examples` regenerates the JSON, HTML, and Markdown files under `examples/coffeeshop/`, `examples/hotel-booking/`, and `examples/file-glossary-booking/`. Run after changes to the renderer, templates, plugin output schema, or any example test file, and commit the updated outputs.
|
|
22
|
+
- `uv run nox -s self_report` regenerates `examples/self-report/` — pytest-given applied to its own backend tests (see [Writing self-report scenarios](#writing-self-report-scenarios)). Run after decorating more tests or changing decorated ones, and commit the updated outputs.
|
|
23
|
+
- **Only commit a regenerated report when its *content* actually changed.**
|
|
24
|
+
- Every regeneration rewrites `commit_sha` (to current HEAD, including the SHA-pinned source-link URLs), `timestamp`, and `duration_ms` in the JSON and HTML, so a report your change didn't really touch still shows a diff — `git checkout` those files rather than committing the noise.
|
|
25
|
+
- **Read the `.md` diff first**: the Markdown carries none of those fields, so it is the behavioral delta of your change in prose. An unchanged `.md` doesn't by itself prove the JSON/HTML are noise-only (glossary and story data never surface in the Markdown); a shifted source line does show up, in the `relpath:line::test_name` anchor under every heading.
|
|
26
|
+
- Regenerate only the reports a change can affect — `examples` narrates `examples/**`, `self_report` narrates `tests/**`. A shifted line number in a decorated backend test is therefore a real self-report change worth committing, even when no example changed.
|
|
27
|
+
- Both regeneration sessions run the narration lint (`--given-lint`; see [Narration lint](docs/site/configuration/narration-lint.md) and the [design spec](docs/specs/2026-07-05-narration-lint-design.md)). `self_report` fails on any lint error — a real gate, keep the backend suite lint-clean. `examples` tolerates exit 1 for its intentional failures, which masks the lint exit code, so read the printed "narration lint" summary there. A step the lint mis-flags goes on the `given_lint_ignore` list (stale entries fail the run); the rule catalog and ignore mechanics are in the [authoring skill](src/pytest_given/.agents/skills/pytest-given-authoring/references/scenarios.md) under "Mechanical counterparts", and the honest-two-phase test an ignored `missing-phase` has to pass under "Phase structure".
|
|
28
|
+
- `uv run nox -s benchmark` generates the large-scenarios suite and renders its JSON + HTML into `benchmarks/` (gitignored). Run it when a change could move report-generation cost; `benchmarks/bench.py` does size sweeps and cProfile runs directly.
|
|
29
|
+
- `uv run nox -s build` builds the wheel + sdist and verifies them as a consumer would — `py.typed`, templates and bundled skills present, a real scenario run from a throwaway install, and [library-skills](https://library-skills.io) discovery of every bundled skill. The in-repo suite imports from `src/`, so this session is the only thing that catches a packaging regression. CI and the release workflow both run it.
|
|
30
|
+
- `uv run nox -s docs_build` builds the documentation site under `site/` with `zensical build --strict`, after copying the four example reports, the full-size diagram and the report package's two upright fonts into gitignored spots under `docs/site/` (Zensical builds every file in `docs/site/` and cannot exclude any, so those copies are how single-sourced files get in; `CHANGELOG.md` instead reaches the site through a `pymdownx.snippets` include in `docs/site/changelog.md`, between its `site` markers, and the Home-page diagram is inlined the same way from `docs/site/assets/pytest-given-diagram.svg` so page CSS can theme it). The README shows the same diagram as `docs/pytest-given-diagram.svg`, a derived copy with the font embedded and the colour tokens resolved, which the Home page also links to as its full-size view: after editing the source SVG or its tokens, run `uv run nox -s diagram` and commit the result. Run it after changing anything under `docs/site/`; a broken cross-page link fails it. Preview with `uv run --group docs zensical serve` afterwards. The site self-hosts its fonts (`docs/site/assets/fonts/`, latin subsets from Fontsource, OFL notices in `LICENSES.txt` there; the upright faces are the report package's own under `src/pytest_given/report/templates/fonts/`, staged in by the build, only the italics live in the site tree) so a visitor's browser contacts no third party — keep `font = false` in `zensical.toml` and don't `@import` a font CDN; a new face gets its `.woff2` and copyright line added there. `docs_deploy` is the CI-only counterpart that publishes with mike — see [docs/releasing.md](docs/releasing.md#documentation-site).
|
|
31
|
+
|
|
32
|
+
## Releasing
|
|
33
|
+
|
|
34
|
+
Releases go to PyPI via a manually dispatched [Release workflow](.github/workflows/release.yml), authenticated with Trusted Publishing (no tokens anywhere) and always rehearsed on TestPyPI first. The step-by-step checklist, including the version bump and CHANGELOG section it needs, lives in [docs/releasing.md](docs/releasing.md).
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
|
|
38
|
+
`src/pytest_given/` is five library subpackages plus two entry points, with a
|
|
39
|
+
strict dependency direction (convention, not lint-enforced):
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
model/ the leaf — schema, serde, errors, shared text rules
|
|
43
|
+
capture/ lint/ report/ each on model/ only, never on each other
|
|
44
|
+
grouping/ on model/ + capture/
|
|
45
|
+
plugin/ cli/ the entry points; may import all five, and hold
|
|
46
|
+
nothing the five could
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
What no filename tells you:
|
|
50
|
+
|
|
51
|
+
- `grouping/` — the parametrize pass: a scenario's cases collapsed into one
|
|
52
|
+
narrated tree plus a parameter table, refusing the authoring forms that would
|
|
53
|
+
make that tree lie. Runs at session finish *before* the sinks are written, so
|
|
54
|
+
a bare `pytest` failing on one is the point, not a side effect.
|
|
55
|
+
- `plugin/__init__.py` is the hook surface pluggy registers — the `pytest11`
|
|
56
|
+
entry point is the package, and pluggy scans a module's attributes for
|
|
57
|
+
`pytest_*` names, so the re-exports there *are* the registration.
|
|
58
|
+
- `capture/` imports pytest nowhere, so all of it is unit-testable without a
|
|
59
|
+
session; a stray step warns with `model.PytestGivenWarning`, not pytest's.
|
|
60
|
+
- `Glossary` exists twice on purpose: `model/schema.py` holds the storage the
|
|
61
|
+
report carries and serde rebuilds, and `capture/glossary.py` subclasses it
|
|
62
|
+
with the registration API, which needs a caller source location that the leaf
|
|
63
|
+
may not reach for. `pytest_given.Glossary` is the subclass; everything
|
|
64
|
+
internal annotates the base. `LookupGlossary` sits between them and owns the
|
|
65
|
+
`g['Guest']` read-back both user-facing glossaries share.
|
|
66
|
+
- Each package exposes the *whole* job, not its parts: `report.emit_sinks`
|
|
67
|
+
(render → write → discard-on-failure, so a failure leaves no half-written
|
|
68
|
+
report) and `lint.run_lint`. Both entry points reach the sinks through
|
|
69
|
+
`emit_sinks`, which is what keeps `pytest-given report` behaving like the
|
|
70
|
+
plugin; the lint is the plugin's alone, since the CLI re-renders a report
|
|
71
|
+
rather than running a suite.
|
|
72
|
+
|
|
73
|
+
`tests/` splits `unit/` (no pytest session needed) from `integration/`, which drives the plugin end to end through `pytester` inner runs (enabled by the root `conftest.py`). Narration written inside an inner run belongs to *that* run's collector — only the outer, decorated test reaches the self-report.
|
|
74
|
+
|
|
75
|
+
The public API, including the step-text authoring forms (t-string vs `Template` vs plain string), is re-exported from `__init__.py` and documented in the skill's [references/api.md](src/pytest_given/.agents/skills/pytest-given-authoring/references/api.md).
|
|
76
|
+
|
|
77
|
+
## Handling report output
|
|
78
|
+
|
|
79
|
+
Outputs are opt-in; a bare `uv run pytest` writes nothing. The workflow for reading a run's narration (`--given-md`), querying the JSON report with `jq` by tag/term/status, re-rendering a saved run, and the bare-flag-order trap lives in the [navigating skill](src/pytest_given/.agents/skills/pytest-given-navigating/SKILL.md).
|
|
80
|
+
|
|
81
|
+
## Report testing
|
|
82
|
+
|
|
83
|
+
Any change to `report/templates/` (Jinja, CSS, `app.js`) or the `narration` filter in `html_renderer.py` **must** be Playwright-verified before commit. The project has no JS-side UI tests, and Python tests on rendered HTML cannot stand in: a substring match passes on a broken Alpine expression or a malformed `:class` binding. So:
|
|
84
|
+
|
|
85
|
+
- **No Python tests that pin frontend markup** (class names, wrapper structure, inline-handler shape, SVG strings) — they check implementation, not behavior, and rot on every renderer refactor. Python tests stay on the renderer's data-shaped contract (which `data-param` value, which scenario IDs, which counts).
|
|
86
|
+
- **No TDD for frontend changes**: apply the change, regenerate `examples/`, drive it in Playwright, capture the result.
|
|
87
|
+
- Open e.g. `examples/coffeeshop/coffeeshop.html` (regenerate via `uv run nox -s examples`) with the Playwright MCP server, check `browser_console_messages` for errors after init, then drive the changed surface (hover, click, URL hash). Use `browser_snapshot` (not screenshots) to read page content and interact with elements.
|
|
88
|
+
- Desktop only — assume a minimum viewport width of ~900px.
|
|
89
|
+
- Traceback display and header metadata formatting are known limitations, not current priorities.
|
|
90
|
+
- Never save Playwright screenshots into the project directory — use the session scratchpad or omit the `filename` parameter.
|
|
91
|
+
|
|
92
|
+
**Setup and known traps** (`.mcp.json`, the `file://` page cache, browser installs) live in [docs/playwright-setup.md](docs/playwright-setup.md). `.mcp.json` is read at **session start**, so check that the `browser_*` tools exist before planning a task that ends in Playwright verification.
|
|
93
|
+
|
|
94
|
+
## Writing self-report scenarios
|
|
95
|
+
|
|
96
|
+
The narration rules live in the **`pytest-given-authoring` skill**, whose canonical source is [src/pytest_given/.agents/skills/](src/pytest_given/.agents/skills/pytest-given-authoring/SKILL.md) — every link in this document points there. The copy under `.claude/skills/` is a mirror: after editing the canonical copy, regenerate it with `uv run pytest-given skills install` and commit both (a sync test fails otherwise).
|
|
97
|
+
|
|
98
|
+
**The skill is documentation with the same sync duty as the site** — downstream agents read it instead of the site, version-matched from the wheel. A change to the public API surface or its rules updates the site page under `docs/site/` *and* [references/api.md](src/pytest_given/.agents/skills/pytest-given-authoring/references/api.md); a change to narration/lint semantics updates [references/scenarios.md](src/pytest_given/.agents/skills/pytest-given-authoring/references/scenarios.md) and friends. No check catches prose drift between the site and the skill, so ask "does the skill need this too?" on every user-facing change; only the `python` blocks are covered — `tests/unit/test_skills_scripts.py` runs each against a report built from the model. The reviewing skill restates authoring rules as rubrics, so they share the same duty: its layer 2 mirrors `scenarios.md` ("Keeping it truthful", "Expected raises", "Vocabulary and tags"), and its layer 4 mirrors `glossaries.md` ("Keeping the glossary honest") and `stories.md` — change one, change both.
|
|
99
|
+
|
|
100
|
+
Narration drifts like any other prose — the lint catches several ways step text and body can part company, not all of them — which is why [Quality gates](#quality-gates) has you read the regenerated `.md` rather than trust the text.
|
|
101
|
+
Before merging changes to narrated tests, run the `pytest-given-reviewing` skill: it layers the lint, a semantic audit of step text against step bodies, and a hygiene pass over glossary, tags and stories.
|
|
102
|
+
|
|
103
|
+
What is specific to this repo's self-report:
|
|
104
|
+
|
|
105
|
+
- The glossary handle is `pg` — `GLOSSARY.md` loaded as a `FileGlossary` in `tests/conftest.py` via `tests/ubiquitous_language.py`. Term-rename mechanics live under [Conventions](#conventions); regeneration and lint gating under [Quality gates](#quality-gates).
|
|
106
|
+
- **New or changed user-facing behavior needs a scenario, not just a test** — otherwise the behavior is invisible in the report. Decorate the test that best *states* the rule, one per rule, not per branch; the edge cases around it stay plain. Two gaps to check for: a rule the [CHANGELOG](CHANGELOG.md) announces that no scenario names, and a [GLOSSARY.md](GLOSSARY.md) row *asserting* behavior (`Templatize`, `Parameter table`) that no scenario demonstrates.
|
|
107
|
+
|
|
108
|
+
## Conventions
|
|
109
|
+
|
|
110
|
+
- Use the canonical vocabulary from [GLOSSARY.md](GLOSSARY.md) in prose as well as code — docs, skill references, and specs say the official term (`term ref`, not a paraphrase like "narrated term"). Naming and rename mechanics live in the skill's [references/glossaries.md](src/pytest_given/.agents/skills/pytest-given-authoring/references/glossaries.md); here, a rename lands in one commit (glossary row, `pg\[` references, implementation naming) plus a regenerated `uv run nox -s self_report`. Adding a term is safe, but still regenerate.
|
|
111
|
+
- **Docstrings only for what the code cannot say** — a non-obvious invariant, a constraint that makes the shape necessary, a trap for the next reader — and a line or two at most. No signature restatement, no implementation narration, no `X is the only caller of Y` cross-references (they rot fastest; the reader can grep). Prefer a precise name to a sentence explaining a vague one. When you change a module, delete the sentences that have stopped being true rather than repairing them.
|
|
112
|
+
- Avoid `Any` — use precise types, generics, `TYPE_CHECKING` imports, or `ContextVar[T]` over untyped `threading.local`.
|
|
113
|
+
- Use `NewType` for domain-specific IDs (e.g., `NodeId`) and PEP 695 `type` statements for aliases. Avoid raw complex types like `dict[str, tuple[list[str], list[Any]]]` — introduce named types instead.
|
|
114
|
+
- Only module-level imports — no inline/function-level imports.
|
|
115
|
+
- **pytest config lives in `[tool.pytest]`** — native TOML mode since pytest 8.4/9.0, where lists are real arrays rather than newline-separated strings. `[tool.pytest.ini_options]` is the legacy INI-compat table; **never add it alongside** — pytest raises `UsageError` if both are present. New `addini` options take native types (a `type='linelist'` ini takes a TOML array).
|
|
116
|
+
- Cross-platform: plugin and tests must pass on native Windows, macOS, Linux, and WSL (Linux interpreter over a `/mnt/<drive>` Windows checkout). Never hardcode a path separator or assume POSIX semantics — go through `pathlib`, `as_posix()` for stored/serialized paths, resolve before comparing. Path-form folding is confined to `capture/source.py` (mechanics in its docstrings; the why, and how to run one checkout from both Windows and WSL without the venvs colliding, in [docs/wsl-development.md](docs/wsl-development.md)). Only tests asserting WSL `/mnt`-absolute behavior may `skipif(sys.platform == 'win32', …)` — native-Windows pathlib reads `/mnt/<drive>` as drive-relative, not absolute — everything else passes on all four targets.
|
|
117
|
+
- Relative imports inside the package throughout — `from .schema import Scenario` for siblings, `from ..model import Scenario` across subpackages (always through the subpackage root, never into its submodules). Tests use absolute imports and may reach into any internal path. The dependency direction those imports must respect is under [Architecture](#architecture).
|
|
118
|
+
- Prefer `assert` over `# pragma: no cover` for invariant guards. Asserts document the invariant and fail loudly if violated; pragmas hide the line and silently bail. Reserve `# pragma: no cover` for code that genuinely cannot be exercised by a test (e.g. `if __name__ == '__main__':` script entry).
|
|
119
|
+
- JSON reports written by older pytest-given versions are not supported: `pytest-given report` may drop or misread their fields. Don't add readers, migrations, or stale-shape errors for them.
|
|
120
|
+
- Step-down rule: callers before callees, public before private. Read each file top-down from high-level API to implementation details.
|
|
121
|
+
- TDD: write tests first (except frontend changes — see [Report testing](#report-testing)).
|
|
122
|
+
- Commit messages: single line, no co-author trailers, no leading file/area labels like `TODO:` or `README:` — just describe the change ("note example cleanup as todo", not "TODO: note example cleanup"). Conventional-commit-style scope prefixes like `docs:` / `examples:` / `renderer:` are fine when they add information.
|
|
123
|
+
- Keep commits coherent: each commit should represent one logical change. Don't split "do X", "tests for X", and "review-fixup for X" into separate commits — squash them before pushing. Don't bundle unrelated changes either.
|
|
124
|
+
- **A user-facing change adds its `CHANGELOG.md` entry in the same commit**, under `## [Unreleased]`, in the fitting Keep a Changelog category (each category appears at most once per version — extend the existing heading rather than adding a second one). User-facing = public API, CLI flags, report output, lint rules, bundled skills; internal work (refactors, tests, CI, contributor docs) gets no entry.
|
|
125
|
+
- **One sentence per entry**, written for someone upgrading: name the symbol, flag, or surface and say what changed — no rationale, measurements, or before/after detail. Only a breaking change earns more: the migration it needs. Visual and interaction polish gets one collective bullet per release ("the sidebar and its chips are visually tidied"), never a bullet per restyled element; accessibility fixes stay on their own line. When in doubt, the shorter entry is the right one.
|
|
126
|
+
- Plan files under `docs/superpowers/plans/` are scratch artifacts — never commit them. Spec files under `docs/specs/` are committed.
|
|
127
|
+
- New specs land under `docs/specs/proposed/`. When a spec's implementation lands, `git mv` it up one level into `docs/specs/` in the same commit, and fix its relative links in the same edit — a `../`-prefixed link to a sibling spec resolves into `docs/` once the file moves. `ls docs/specs/proposed` is the canonical list of outstanding design work.
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
The release workflow reads the section matching the version in `pyproject.toml`
|
|
9
|
+
and uses it as the GitHub Release body, so each version needs a heading of the
|
|
10
|
+
form `## [x.y.z] - YYYY-MM-DD`.
|
|
11
|
+
|
|
12
|
+
<!-- --8<-- [start:site] -->
|
|
13
|
+
|
|
14
|
+
## [0.3.0] - 2026-09-27
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- A documentation site at <https://nwilbert.github.io/pytest-given/> has a user guide, configuration and CLI reference, and the example reports. The bundled skills link to it, and the CLI help names the page on source-link templates.
|
|
19
|
+
- The HTML report has a dark theme, with a Light / Dark / System control in its header that each browser remembers.
|
|
20
|
+
- `--given-theme` / `given_theme` (and `--theme` on `pytest-given report`) set whether the HTML report opens light, dark, or following the viewer's system.
|
|
21
|
+
- Tags can be organized hierarchically: a `/` in a tag nests it in the HTML report's Tags sidebar (`ticket/ABC-123` goes under a `ticket` heading), and selecting the heading filters to every tag beneath it.
|
|
22
|
+
- Projects can also install the bundled skills with [library-skills](https://library-skills.io) (`uvx library-skills install --claude`), alongside the skills of their other dependencies.
|
|
23
|
+
- One scenario can document a flow that spans several stories: `@scenario(stories=[a, b])` lists it under each story in the Stories tab, and the Scenarios view's sentence filter names the story when a report has several.
|
|
24
|
+
- A sentence can be named (`sentence(..., name='checkout')`), so a pin can refer to it by name and survives reordering the story. The Stories timeline shows the name beside the sentence's coverage.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Breaking.** Story vocabulary follows Domain Storytelling: a story is made of *sentences*, and *activity* is the verb kind. `activity()` is now `sentence()`, `path()` is now `clause()`, and `Glossary.verb()` is now `Glossary.activity()`. A file glossary's kind column says `activity` instead of `verb`, the report's `#activity-filter=` link parameter is now `#sentence-filter=`, and the HTML report says *Sentence* and *Activities* where it said *Activity* and *Verbs*.
|
|
29
|
+
- **Breaking.** `@scenario(story=)` is now `stories=`.
|
|
30
|
+
- **Breaking.** Pins refer to sentences by handle under `pins=` instead of by number: `given(..., activity=3)` becomes `given(..., pins=the_story[3])`, and `@scenario(activities=[2, 3])` becomes `pins=[the_story[2], the_story[3]]`. `activity_id=` is gone, because sentences are numbered by position; to pin a sentence independently of its position, name it and pin `the_story['name']`. A pin no longer requires the scenario to bind the pinned sentence's story.
|
|
31
|
+
- **Breaking.** A scenario's `pins=` now sets its coverage outright: it covers exactly those sentences plus its steps' pins, with no narration matching. `activities=` only narrowed which sentences narration could cover. Pass `pins=[]` to a step or scenario to turn off narration matching without pinning anything.
|
|
32
|
+
- The HTML report is restyled. It embeds Source Sans 3 and Source Code Pro, so it looks the same on every machine and offline. Lists share one surface instead of a card per row, Given/When/Then sit in a gutter beside the steps, and sidebar labels and counts use sentence case. Step narration and story sentences are set larger and darker than the surrounding labels and counts, at the same line spacing, so a scenario takes no more room.
|
|
33
|
+
- The HTML report strips source comments from its inlined stylesheet and script, which saves about 29 KB per report and offsets part of the size the embedded fonts add.
|
|
34
|
+
- Grouping errors for varying attachment labels and varying `str` narration name the parametrize case that differs. The varying-`str` error also suggests `group_parametrized=False` as a way out.
|
|
35
|
+
- A story that no scenario covers now shows up in the report as uncovered, where before it was missing. The one exception is a story declared before the session started, such as one in a module still imported from an earlier `pytest.main()` in the same process: it is left out with a warning.
|
|
36
|
+
- Story coverage ignores instances: a step narrating `guest`, or any guest instance, now covers a sentence naming `guest('Alice')`. Sentences that differ only by instance can be told apart only with a pin.
|
|
37
|
+
- The authoring skill advises writing generic verbs (*searches for*, *adds*) as plain strings in sentences rather than glossary terms, keeping the glossary to domain vocabulary. The hotel-booking example follows it.
|
|
38
|
+
- The authoring skill explains how coverage matching works (instances are ignored, and two sentences whose terms nest always cover together) and how to check coverage in the JSON report. The reviewing skill checks coverage from the report instead of re-deriving it.
|
|
39
|
+
- The reviewing skill can list each scenario's narration beside its test's source for side-by-side review, and checks that a scenario demonstrates every rule the project's changelog announces. Both skills flag `match=` pins that use alternation, and a `when` that narrates setup while its body acts.
|
|
40
|
+
- The authoring, reviewing and navigating skills cover sentence handles, `pins=` and `stories=`. The reviewing skill also checks that the test body exercises each sentence its scenario pins.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- When rendering a report fails unexpectedly, the previous run's report is still discarded instead of staying on disk looking current.
|
|
45
|
+
- A `#scenario=` deep link now opens the right scenario when two node ids differ only in a character the slug folds, and no longer breaks the URL.
|
|
46
|
+
- `pytest-given report` reports a non-UTF-8 input file as an error instead of crashing with a traceback.
|
|
47
|
+
- An unknown `--given-source-link` preset is reported under the flag the user typed, not under the `given_source_link` ini name.
|
|
48
|
+
- A refused scenario on a run with no `--given-*` sink no longer reports itself under a "report not written" heading.
|
|
49
|
+
- `when_then(...)` rejects a `Template` narration in a test body, as `given`/`when`/`then` already do.
|
|
50
|
+
- The Glossary view no longer lists a term's own name in another case (`guest.low`) under Instances.
|
|
51
|
+
- On native Windows, stories and glossary terms now record where they are declared, so the lint rules that depend on it run there too.
|
|
52
|
+
- `tag-shadows-term` now catches a tag that collides with a term id through a non-ASCII character that lowercases into ASCII.
|
|
53
|
+
- A `dead-term` finding's message states the rule's actual criterion, including that a term ref in a `@scenario` name keeps a term alive.
|
|
54
|
+
- The navigating skill shows the failure messages of a parametrized scenario's cases, where it printed an empty message. It also starts from a committed or CI-published report when one exists, instead of always rerunning the suite.
|
|
55
|
+
- The reviewing skill lints the whole suite, where a selection failed on the project's ignore entries as stale, keeps the project's own `given_lint_rules` when enabling `dead-term`, and ranks findings in one explicit order.
|
|
56
|
+
- A pin on an `Annotated` `given(...)` label now takes effect instead of being silently dropped, replacing the pins of the fixture step it relabels (`pins=[]` clears them). A pin on a step fixture's own `@given(...)` label also takes effect now.
|
|
57
|
+
- A pin on a `@given` fixture scoped wider than `function` now counts in every scenario the fixture reaches, even when a test without `@scenario` set it up first. The run used to fail with a bare `AssertionError`.
|
|
58
|
+
|
|
59
|
+
## [0.2.0] - 2026-09-04
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- `pytest_given.PytestGivenWarning` is a top-level export, and a step or
|
|
64
|
+
`attach()` recorded in a test without `@scenario` now warns with it instead of
|
|
65
|
+
`pytest.PytestWarning`.
|
|
66
|
+
- `--given-title=TEXT` (or the `given_title` ini) names the report, replacing the
|
|
67
|
+
rootdir name.
|
|
68
|
+
- A parametrized scenario's parameter table now carries a typed column per varying
|
|
69
|
+
value — `param`, `derived`, or `attachment` for a varying attachment payload —
|
|
70
|
+
rather than one column per parametrize name.
|
|
71
|
+
- `@scenario(group_parametrized=False)` declines the grouping and emits each case
|
|
72
|
+
as its own scenario, titled by its parametrize id.
|
|
73
|
+
- The HTML report's sidebar gains **Terms** as a third browse axis, and all three
|
|
74
|
+
axes — Tags, Terms, Modules — now filter the Scenarios view the same way, with
|
|
75
|
+
each active filter carried in the URL.
|
|
76
|
+
- The sidebar can be ordered by group size as well as by name, and resized by
|
|
77
|
+
dragging its seam or with the arrow keys.
|
|
78
|
+
- A selected activity in the Stories view offers **Open in Scenarios**, filtering
|
|
79
|
+
the Scenarios view down to the scenarios covering it.
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
|
|
83
|
+
#### CLI
|
|
84
|
+
|
|
85
|
+
- **Breaking.** `--given-lint` is a plain boolean flag: write `--given-lint` and
|
|
86
|
+
`--no-given-lint` instead of `--given-lint=true` / `--given-lint=false`. Either
|
|
87
|
+
form still overrides the `given_lint` ini for one run.
|
|
88
|
+
|
|
89
|
+
#### Authoring API
|
|
90
|
+
|
|
91
|
+
- **Breaking.** Step narration must now be uniform across parametrize cases;
|
|
92
|
+
these fail the run with `PytestGivenError`, writing no report, instead of
|
|
93
|
+
quietly reporting case 1:
|
|
94
|
+
|
|
95
|
+
- a plain `str` (usually an f-string) that renders differently per case;
|
|
96
|
+
- a varying interpolation that is not a bare name (`t"{cup_size * 0.01}"`,
|
|
97
|
+
`t"{m.balance}"`);
|
|
98
|
+
- a t-string narrating a parametrize name that no longer holds the case's
|
|
99
|
+
value — either a local rebound it, or the body mutated it in place;
|
|
100
|
+
- a step whose set of `attach` labels differs between cases;
|
|
101
|
+
- a glossary term ref that names a different term or reads differently between
|
|
102
|
+
cases, including one bound to a parametrize column;
|
|
103
|
+
- passed cases that narrate different templates altogether.
|
|
104
|
+
|
|
105
|
+
Every one but the last is fixed by binding the varying part to a local and
|
|
106
|
+
narrating it with a t-string, keeping labels and term refs constant; varying
|
|
107
|
+
content belongs in the new `attachment` column. The last needs
|
|
108
|
+
`@scenario(..., group_parametrized=False)`, giving each case its own scenario.
|
|
109
|
+
- **Breaking.** `attach()` now takes a plain `str` label; a t-string label raises
|
|
110
|
+
`PytestGivenError` — use an f-string.
|
|
111
|
+
- **Breaking.** `attach()` called with no step open now raises `PytestGivenError`
|
|
112
|
+
instead of silently discarding the payload; move the call inside the step it
|
|
113
|
+
belongs to.
|
|
114
|
+
- **Breaking.** `activity(..., id=N)` is now `activity(..., activity_id=N)`; the
|
|
115
|
+
`Activity.id` field itself is unchanged.
|
|
116
|
+
- **Breaking.** `FileGlossary` is now a `Glossary` subclass rather than a wrapper
|
|
117
|
+
around one, so its `.glossary` attribute is gone — use the `FileGlossary`
|
|
118
|
+
itself wherever that attribute was passed.
|
|
119
|
+
- A glossary term placed in an activity slot its declared kind forbids now raises
|
|
120
|
+
`PytestGivenError` when `activity(...)` is built rather than at session finish.
|
|
121
|
+
- A glossary file whose table has a header and separator but no data rows now
|
|
122
|
+
says so, instead of reporting that no table was found.
|
|
123
|
+
- A `FileGlossary` whose columns are all named now skips a Markdown table that
|
|
124
|
+
carries none of those names, so a glossary file may hold prose tables beside
|
|
125
|
+
the glossary; a table carrying some of them still raises, as does any table
|
|
126
|
+
under an index-based column spec.
|
|
127
|
+
- `@scenario(activities=...)` now rejects a `str` and non-`int` members with a
|
|
128
|
+
`TypeError`.
|
|
129
|
+
- `@scenario` now returns the test function itself rather than a wrapper, so the
|
|
130
|
+
test keeps its own signature.
|
|
131
|
+
|
|
132
|
+
#### Plugin and run behavior
|
|
133
|
+
|
|
134
|
+
- An unknown `given_source_link` preset is now a `UsageError` raised before the
|
|
135
|
+
suite runs.
|
|
136
|
+
- The collection-time `@scenario` checks now report as a `UsageError` instead of
|
|
137
|
+
an `INTERNALERROR` traceback.
|
|
138
|
+
- The narration lint summary prints each finding's location in its own column
|
|
139
|
+
rather than appended to the message.
|
|
140
|
+
- `pytest-given` with no subcommand, and `pytest-given skills` with no
|
|
141
|
+
subcommand, now print that parser's usage and exit 2 instead of the root help
|
|
142
|
+
and exit 1.
|
|
143
|
+
|
|
144
|
+
#### Report content (all formats)
|
|
145
|
+
|
|
146
|
+
- **Breaking (JSON report).** `parameters.names` becomes `parameters.columns`
|
|
147
|
+
(`{id, name, kind}`), cells may hold an attachment object, placeholder parts
|
|
148
|
+
gain `column_id`, term-ref parts lose `param_column`, and a grouped step's
|
|
149
|
+
`narration.text` is the template rather than case 1's rendering.
|
|
150
|
+
- **Breaking (JSON report).** A step no longer carries `status` or `error`;
|
|
151
|
+
failure lives on the scenario and on the parameter table's cases. A consumer
|
|
152
|
+
reading `step.status` should read `scenario.status` instead.
|
|
153
|
+
- The Markdown report now shows a scenario's failure reason — the message and the
|
|
154
|
+
failing frame — under the scenario, and under the parameter table for each
|
|
155
|
+
failed case.
|
|
156
|
+
|
|
157
|
+
#### HTML report
|
|
158
|
+
|
|
159
|
+
- The browse sidebar leads with **Modules** and renders them as a collapsible
|
|
160
|
+
package tree whose nodes filter by path prefix; it no longer lists individual
|
|
161
|
+
scenarios under each group.
|
|
162
|
+
- The report's colors are retuned into one system — a term ref in a step or a
|
|
163
|
+
scenario title reads as a word under a light wash rather than a bordered pill
|
|
164
|
+
(the Glossary view keeps its pills), and column colors are generated per
|
|
165
|
+
column — and the sidebar, its filter chips and the attachment badges are
|
|
166
|
+
tidied along with it.
|
|
167
|
+
- The report opens and filters substantially faster on large suites, and its file
|
|
168
|
+
is smaller — a term reference now points at its glossary entry instead of
|
|
169
|
+
repeating the entry's definition, which takes about 18% off a term-heavy
|
|
170
|
+
report.
|
|
171
|
+
- A glossary term referenced only in a `@scenario` title now contributes an
|
|
172
|
+
instance to the Glossary view, where it previously counted toward the term's
|
|
173
|
+
scenario tally while showing no instance.
|
|
174
|
+
|
|
175
|
+
#### Bundled skills
|
|
176
|
+
|
|
177
|
+
- The authoring and reviewing skills gain the report mechanics their rules depend
|
|
178
|
+
on, a symptom index, a completeness audit, the full lint rule catalog, and
|
|
179
|
+
guidance for sparser tagging.
|
|
180
|
+
|
|
181
|
+
### Removed
|
|
182
|
+
|
|
183
|
+
- **Breaking.** The `divergent-case-structure` lint rule; delete any
|
|
184
|
+
`given_lint_rules` or `given_lint_ignore` entry naming it, which would
|
|
185
|
+
otherwise fail config parsing.
|
|
186
|
+
|
|
187
|
+
### Fixed
|
|
188
|
+
|
|
189
|
+
#### Authoring API
|
|
190
|
+
|
|
191
|
+
- Parametrize cases that claim different step activities now say so, instead of
|
|
192
|
+
reporting the more drastic "a different step structure".
|
|
193
|
+
|
|
194
|
+
- A `@given` fixture scoped wider than `function` no longer loses its step when
|
|
195
|
+
the first test to use it has no `@scenario`.
|
|
196
|
+
- `@given`/`@when`/`@then` are signature-preserving, so a decorated helper stays
|
|
197
|
+
callable to a type checker (was: `"StepDecorated" not callable`).
|
|
198
|
+
- `@given(...)` above `@pytest.fixture` now raises and names the fix, instead of
|
|
199
|
+
surfacing as `fixture '<name>' not found`.
|
|
200
|
+
- `@scenario(activities=...)` is now typed `int | Sequence[int] | None`, so a
|
|
201
|
+
bare `activities=2` type-checks.
|
|
202
|
+
- `@scenario(activities=...)` with an unknown id, or without `story=`, is
|
|
203
|
+
rejected at the decorator rather than at collection.
|
|
204
|
+
- A `-k`- or `--deselect`-narrowed run no longer fails on an authoring error in
|
|
205
|
+
a scenario it did not select.
|
|
206
|
+
- Glossary term handles are now hashable, and equal for the same term whichever
|
|
207
|
+
accessor produced them.
|
|
208
|
+
- A `when_then` step in a test without `@scenario` now points its warning at the
|
|
209
|
+
test rather than at pytest-given's own module.
|
|
210
|
+
|
|
211
|
+
#### Narration lint
|
|
212
|
+
|
|
213
|
+
- A `given_lint_ignore` entry beginning with a Windows drive letter
|
|
214
|
+
(`c:/repo/tests/t.py::test_x`) is no longer rejected as an unknown rule prefix.
|
|
215
|
+
|
|
216
|
+
- A rule configured `off` no longer runs; levels were applied only after every
|
|
217
|
+
rule had already produced its findings.
|
|
218
|
+
- A scenario tag with no ASCII alphanumerics (`tags=['日本語']`) no longer takes
|
|
219
|
+
the run down with a traceback from `tag-shadows-term`.
|
|
220
|
+
|
|
221
|
+
#### Report
|
|
222
|
+
|
|
223
|
+
- `pytest-given report` reports an unreadable input as an error instead of a
|
|
224
|
+
traceback (a directory raised `IsADirectoryError` through the console script).
|
|
225
|
+
- A JSON report with an out-of-range scenario `status` or term `kind` is
|
|
226
|
+
rejected by name, instead of crashing a renderer with a bare `KeyError`.
|
|
227
|
+
- A `<br>` inside an inline code span in a glossary definition renders as text
|
|
228
|
+
rather than as a line break.
|
|
229
|
+
- The Terms browse axis no longer lists a term the selected glossary does not
|
|
230
|
+
hold.
|
|
231
|
+
|
|
232
|
+
#### Plugin and run behavior
|
|
233
|
+
|
|
234
|
+
- A `--given-json`/`--given-html`/`--given-md` path that could not be a report
|
|
235
|
+
file is now refused before the suite runs, instead of a bare flag swallowing a
|
|
236
|
+
following test path and overwriting — or, on a failed run, deleting — it.
|
|
237
|
+
- `pytest-given report` now discards a stale report when the *render* fails, not
|
|
238
|
+
only when the write does.
|
|
239
|
+
- `pytest-given report --source-link` is now validated on a `--format md` run
|
|
240
|
+
instead of being accepted and ignored, and an unknown preset is reported under
|
|
241
|
+
the name the user typed.
|
|
242
|
+
- A `git` on PATH that cannot be executed no longer fails the run.
|
|
243
|
+
- A nested in-process pytest run that dies while parsing its arguments no longer
|
|
244
|
+
strands the outer session's captured rootdir, which silently dropped every
|
|
245
|
+
later step's source anchor.
|
|
246
|
+
- `@given`/`@when`/`@then` on an `async def` helper now records around the
|
|
247
|
+
awaited body, and async generator fixtures are handled too.
|
|
248
|
+
- The narration lint now inspects `async def` step helpers, whose bodies were
|
|
249
|
+
invisible to every AST rule.
|
|
250
|
+
- An explicit `--given-source-link=` now disables source links instead of falling
|
|
251
|
+
through to the `given_source_link` ini.
|
|
252
|
+
- A finished scenario no longer leaves its collector — and every scenario and
|
|
253
|
+
step it recorded — reachable from a process-global for the rest of the process.
|
|
254
|
+
- An error-level lint finding no longer overwrites a more specific exit code, so
|
|
255
|
+
an interrupted or nothing-collected run keeps reporting as one.
|
|
256
|
+
- A report that cannot be written into an unwritable directory now reports
|
|
257
|
+
through the terminal summary instead of escaping as a bare traceback.
|
|
258
|
+
- `pytest-given report` and `pytest-given skills install` now report a failed
|
|
259
|
+
write as a CLI error, and a failed `report` write discards the previous run's
|
|
260
|
+
report rather than leaving it to read as current.
|
|
261
|
+
- `pytest-given report` now reports a bad input file — missing, unparsable, or
|
|
262
|
+
JSON that is not a pytest-given report — as a CLI error instead of a traceback,
|
|
263
|
+
as does an unknown `--source-link` preset.
|
|
264
|
+
- A fixture that raises after its `yield` now fails the scenario it tore down,
|
|
265
|
+
instead of leaving it green in a report pytest counted as an error.
|
|
266
|
+
- An error-level narration-lint finding now counts as an error in the run's
|
|
267
|
+
summary line.
|
|
268
|
+
- Every failure building or writing a report — a suite reaching two glossaries, a
|
|
269
|
+
term used in incompatible slots, an unusable source-link template, an
|
|
270
|
+
unwritable output path — now surfaces as a terminal summary and a failing exit
|
|
271
|
+
code, where only grouping errors did.
|
|
272
|
+
- The sinks are now rendered in full before any is written, and a failure on
|
|
273
|
+
either side discards all of them.
|
|
274
|
+
|
|
275
|
+
#### Report content (all formats)
|
|
276
|
+
|
|
277
|
+
- A parametrized scenario whose cases all skipped now keeps its skip reason
|
|
278
|
+
instead of reporting `skipped` with none.
|
|
279
|
+
- A parameter row whose cell count disagreed with the table's columns silently
|
|
280
|
+
truncated in the HTML report; it now fails the same way it already did in
|
|
281
|
+
Markdown.
|
|
282
|
+
|
|
283
|
+
- A parametrized scenario now keeps its place in source order instead of moving
|
|
284
|
+
below every unparametrized one.
|
|
285
|
+
- A glossary term written as a code span keeps the markup inside it, so
|
|
286
|
+
`` `a*b*c` `` canonicalizes to `a*b*c`.
|
|
287
|
+
- A parameter-table cell now reads the way the step pointing at it read, carrying
|
|
288
|
+
the interpolation's own format spec and, under `indirect=True`, the bound test
|
|
289
|
+
argument.
|
|
290
|
+
- A `Template` narration's `text` is now what its parts render, so the report's
|
|
291
|
+
search box and `jq` queries match what the page displays.
|
|
292
|
+
- The grouped step tree now comes from the first case that *passed*, where a
|
|
293
|
+
skipped case 1 used to render an empty tree.
|
|
294
|
+
- A parametrize value that is a glossary term instance now narrates as its
|
|
295
|
+
display rather than the whole `Glossary` dataclass repr — in a step's
|
|
296
|
+
`Template` slot, in a scenario name, and in an
|
|
297
|
+
`Annotated[..., given(Template(...))]` parameter label.
|
|
298
|
+
|
|
299
|
+
#### HTML report
|
|
300
|
+
|
|
301
|
+
- Two test files sharing a basename across directories no longer abort the HTML
|
|
302
|
+
report; the scenarios' `#scenario=` slugs gain directory components instead.
|
|
303
|
+
- A `#view=stories`, `#view=glossary` or `#term=` link opened against a report
|
|
304
|
+
that has no such tab now falls back to the Scenarios view.
|
|
305
|
+
- The Glossary view's kind headings and their term counts now follow the search
|
|
306
|
+
and definition filters, and a filter matching nothing says so.
|
|
307
|
+
- Content reaching past a scenario card's right edge is no longer clipped: a wide
|
|
308
|
+
parameter table and an attachment payload scroll, a source path and a
|
|
309
|
+
traceback's frame location wrap, and the source link no longer overlaps the
|
|
310
|
+
card's last element.
|
|
311
|
+
- Jumping to a scenario from a story activity, or to a term's scenarios from the
|
|
312
|
+
Glossary tab, now clears filters that would hide the target; the filters in a
|
|
313
|
+
`#scenario=` deep link still win.
|
|
314
|
+
- Accent-colored text and the parametrize column colors now meet WCAG AA, and
|
|
315
|
+
term kinds stay distinguishable for red-green color blindness.
|
|
316
|
+
- The report is operable from the keyboard: status pills, browse-axis and
|
|
317
|
+
browse-tree rows, tag pills, story sidebar entries, activity and attachment
|
|
318
|
+
badges, and every expand/collapse chevron are now real buttons, and the view
|
|
319
|
+
tabs report which one is selected.
|
|
320
|
+
- A step pinned with `given(..., activity=N)` now covers an activity regardless
|
|
321
|
+
of its term count; an under-anchored activity previously still rendered as
|
|
322
|
+
"not coverage-tracked".
|
|
323
|
+
|
|
324
|
+
#### Bundled skills
|
|
325
|
+
|
|
326
|
+
- The bundled skills are corrected against the shipped behavior.
|
|
327
|
+
|
|
328
|
+
## [0.1.0] - 2026-08-08
|
|
329
|
+
|
|
330
|
+
First public release.
|
|
331
|
+
|
|
332
|
+
### Added
|
|
333
|
+
|
|
334
|
+
- `@scenario` decorator plus `given` / `when` / `then` step blocks, usable as
|
|
335
|
+
both context managers and decorators, including on fixtures.
|
|
336
|
+
- Self-contained interactive HTML report (`--given-html`), Markdown report
|
|
337
|
+
(`--given-md`), and JSON report (`--given-json`). The HTML bundles Alpine.js
|
|
338
|
+
and needs no server or external assets.
|
|
339
|
+
- Structured step text: plain strings, `Template` objects, and t-strings
|
|
340
|
+
([PEP 750](https://peps.python.org/pep-0750/)), with parameter interpolation
|
|
341
|
+
rendered as highlighted values.
|
|
342
|
+
- `attach()` for text and JSON attachments on a step.
|
|
343
|
+
- Domain Storytelling support: ubiquitous-language glossaries (inline or
|
|
344
|
+
Markdown-backed via `FileGlossary`), Domain Stories, and story coverage.
|
|
345
|
+
- Narration lint (`--given-lint`) with a configurable rule catalog via
|
|
346
|
+
`given_lint_rules` and `given_lint_ignore`.
|
|
347
|
+
- `--given-source-link` with `vscode`, `cursor`, `zed`, `pycharm`, and `github`
|
|
348
|
+
presets for jumping from a report step to its source.
|
|
349
|
+
- `pytest-given` console script: `report` to re-render a saved JSON report, and
|
|
350
|
+
`skills install` to mirror the bundled agent skills into a project's
|
|
351
|
+
`.claude/skills/`.
|
|
352
|
+
- Bundled authoring, navigating, and reviewing skills for AI agents, shipped in
|
|
353
|
+
the wheel and version-matched to the plugin.
|
|
354
|
+
|
|
355
|
+
[Unreleased]: https://github.com/nwilbert/pytest-given/compare/v0.3.0...HEAD
|
|
356
|
+
[0.3.0]: https://github.com/nwilbert/pytest-given/compare/v0.2.0...v0.3.0
|
|
357
|
+
[0.2.0]: https://github.com/nwilbert/pytest-given/compare/v0.1.0...v0.2.0
|
|
358
|
+
[0.1.0]: https://github.com/nwilbert/pytest-given/releases/tag/v0.1.0
|
|
359
|
+
|
|
360
|
+
<!-- --8<-- [end:site] -->
|