Repository navigation
Releases: nwilbert/pytest-given
Release list
v0.4.1
v0.4.0
Added
- Python 3.15 is supported.
- Hovering a phase in the HTML report's narration, or a column of its parameter table, highlights that phase in both.
- Scenarios that fail as expected (
xfail) get their ownxfailedstatus, with their reason, steps and error, in every report format.
Changed
- The parameter table orders its columns the way the narration first shows them, and shows its status column only when the cases differ in status.
- The HTML report's Stories view shows each scenario as a full card that expands in place, and a tag clicked there opens the Scenarios view filtered by it.
- The HTML report is visually tidied: term refs, the Glossary view, story coverage, narration spacing, hover states and the header row are restyled, and a parametrized scenario is marked by a second status bar.
- The bundled
pytest-given-authoringskill asks for one scenario per rule and covers parametrized scenarios as decision tables, and thepytest-given-reviewingskill catches more ways scenarios, glossary rows, pins, tags and parameter tables can disagree.
Fixed
- A
@givenfixture parametrized with an unhashable value, such as a list, no longer errors every test that requests it. - Editor source links build
{path}from pytest's rootdir instead of the working directory, so they no longer point at a doubled path when pytest runs from a subdirectory. - A scenario deep link stays working when the test's file or directory name contains
+,&,=or#. - A parametrize value of infinity or NaN reaches the JSON report as a string (
"inf","nan"), so the report stays valid JSON. - A
@givengenerator fixture that yields twice errors with pytest's own "more than one 'yield'" message, as it does without pytest-given, instead of passing silently. - An
Annotated[..., given(Template(...))]label whose placeholder is not a bare parametrize column name now fails its scenario with the fix, instead of crashing the HTML report or silently dropping the step. - A scenario failing on a pytest-given refusal raised from its test body points at the test's own line, not at pytest-given's.
- Error messages are clearer: a
FileGlossarytable error names the file, so does a glossary file that is a directory or not UTF-8, and a step nested across phases gets a concrete fix suggested. - The Markdown report's note below a parameter table names the row by its short parametrize values or its row number, instead of repeating multiline values.
- A failure inside the narration lint is summarized under
pytest-given: narration lint failed, no longer asreport not writtenbeside a report that was kept. - The
tag-shadows-termlint finding counts a scenario once when it carries two spellings of the tag, such asGuestandguest. pytest-given report -o report.markdownwrites Markdown, as-o report.mddoes, instead of refusing the path as an HTML one.pytest-given report --helpmentions Markdown output and says where each format goes without-o.- The HTML report's links keep a tag filter whose tag contains a comma, by repeating the parameter per tag or term (
#tag=a&tag=b), and copying a link no longer replaces the current page's entry in the browser history. - The HTML report no longer says "All Scenarios" when every status is filtered out, disables a status filter no scenario has, and the Glossary view says no terms match when every kind is unchecked.
- The HTML report's view tabs and search boxes show a visible keyboard focus ring, and a turned-off status filter keeps readable contrast.
- The HTML report's status filters wrap onto a second row instead of being cut off in a narrow sidebar.
- A parametrized scenario shows a skip reason only when every case was skipped, no longer its first case's reason beside cases that ran.
v0.3.0
Added
- 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.
- The HTML report has a dark theme, with a Light / Dark / System control in its header that each browser remembers.
--given-theme/given_theme(and--themeonpytest-given report) set whether the HTML report opens light, dark, or following the viewer's system.- Tags can be organized hierarchically: a
/in a tag nests it in the HTML report's Tags sidebar (ticket/ABC-123goes under aticketheading), and selecting the heading filters to every tag beneath it. - Projects can also install the bundled skills with library-skills (
uvx library-skills install --claude), alongside the skills of their other dependencies. - 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. - 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.
Changed
- Breaking. Story vocabulary follows Domain Storytelling: a story is made of sentences, and activity is the verb kind.
activity()is nowsentence(),path()is nowclause(), andGlossary.verb()is nowGlossary.activity(). A file glossary's kind column saysactivityinstead ofverb, 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. - Breaking.
@scenario(story=)is nowstories=. - Breaking. Pins refer to sentences by handle under
pins=instead of by number:given(..., activity=3)becomesgiven(..., pins=the_story[3]), and@scenario(activities=[2, 3])becomespins=[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 pinthe_story['name']. A pin no longer requires the scenario to bind the pinned sentence's story. - 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. Passpins=[]to a step or scenario to turn off narration matching without pinning anything. - 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.
- 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.
- Grouping errors for varying attachment labels and varying
strnarration name the parametrize case that differs. The varying-strerror also suggestsgroup_parametrized=Falseas a way out. - 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. - Story coverage ignores instances: a step narrating
guest, or any guest instance, now covers a sentence namingguest('Alice'). Sentences that differ only by instance can be told apart only with a pin. - 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.
- 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.
- 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 awhenthat narrates setup while its body acts. - The authoring, reviewing and navigating skills cover sentence handles,
pins=andstories=. The reviewing skill also checks that the test body exercises each sentence its scenario pins.
Fixed
- When rendering a report fails unexpectedly, the previous run's report is still discarded instead of staying on disk looking current.
- 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. pytest-given reportreports a non-UTF-8 input file as an error instead of crashing with a traceback.- An unknown
--given-source-linkpreset is reported under the flag the user typed, not under thegiven_source_linkini name. - A refused scenario on a run with no
--given-*sink no longer reports itself under a "report not written" heading. when_then(...)rejects aTemplatenarration in a test body, asgiven/when/thenalready do.- The Glossary view no longer lists a term's own name in another case (
guest.low) under Instances. - On native Windows, stories and glossary terms now record where they are declared, so the lint rules that depend on it run there too.
tag-shadows-termnow catches a tag that collides with a term id through a non-ASCII character that lowercases into ASCII.- A
dead-termfinding's message states the rule's actual criterion, including that a term ref in a@scenarioname keeps a term alive. - 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.
- 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_ruleswhen enablingdead-term, and ranks findings in one explicit order. - A pin on an
Annotatedgiven(...)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. - A pin on a
@givenfixture scoped wider thanfunctionnow counts in every scenario the fixture reaches, even when a test without@scenarioset it up first. The run used to fail with a bareAssertionError.
v0.2.0
Added
pytest_given.PytestGivenWarningis a top-level export, and a step or
attach()recorded in a test without@scenarionow warns with it instead of
pytest.PytestWarning.--given-title=TEXT(or thegiven_titleini) names the report, replacing the
rootdir name.- A parametrized scenario's parameter table now carries a typed column per varying
value —param,derived, orattachmentfor a varying attachment payload —
rather than one column per parametrize name. @scenario(group_parametrized=False)declines the grouping and emits each case
as its own scenario, titled by its parametrize id.- The HTML report's sidebar gains Terms as a third browse axis, and all three
axes — Tags, Terms, Modules — now filter the Scenarios view the same way, with
each active filter carried in the URL. - The sidebar can be ordered by group size as well as by name, and resized by
dragging its seam or with the arrow keys. - A selected activity in the Stories view offers Open in Scenarios, filtering
the Scenarios view down to the scenarios covering it.
Changed
CLI
- Breaking.
--given-lintis a plain boolean flag: write--given-lintand
--no-given-lintinstead of--given-lint=true/--given-lint=false. Either
form still overrides thegiven_lintini for one run.
Authoring API
-
Breaking. Step narration must now be uniform across parametrize cases;
these fail the run withPytestGivenError, writing no report, instead of
quietly reporting case 1:- a plain
str(usually an f-string) that renders differently per case; - a varying interpolation that is not a bare name (
t"{cup_size * 0.01}",
t"{m.balance}"); - a t-string narrating a parametrize name that no longer holds the case's
value — either a local rebound it, or the body mutated it in place; - a step whose set of
attachlabels differs between cases; - a glossary term ref that names a different term or reads differently between
cases, including one bound to a parametrize column; - passed cases that narrate different templates altogether.
Every one but the last is fixed by binding the varying part to a local and
narrating it with a t-string, keeping labels and term refs constant; varying
content belongs in the newattachmentcolumn. The last needs
@scenario(..., group_parametrized=False), giving each case its own scenario. - a plain
-
Breaking.
attach()now takes a plainstrlabel; a t-string label raises
PytestGivenError— use an f-string. -
Breaking.
attach()called with no step open now raisesPytestGivenError
instead of silently discarding the payload; move the call inside the step it
belongs to. -
Breaking.
activity(..., id=N)is nowactivity(..., activity_id=N); the
Activity.idfield itself is unchanged. -
Breaking.
FileGlossaryis now aGlossarysubclass rather than a wrapper
around one, so its.glossaryattribute is gone — use theFileGlossary
itself wherever that attribute was passed. -
A glossary term placed in an activity slot its declared kind forbids now raises
PytestGivenErrorwhenactivity(...)is built rather than at session finish. -
A glossary file whose table has a header and separator but no data rows now
says so, instead of reporting that no table was found. -
A
FileGlossarywhose columns are all named now skips a Markdown table that
carries none of those names, so a glossary file may hold prose tables beside
the glossary; a table carrying some of them still raises, as does any table
under an index-based column spec. -
@scenario(activities=...)now rejects astrand non-intmembers with a
TypeError. -
@scenarionow returns the test function itself rather than a wrapper, so the
test keeps its own signature.
Plugin and run behavior
- An unknown
given_source_linkpreset is now aUsageErrorraised before the
suite runs. - The collection-time
@scenariochecks now report as aUsageErrorinstead of
anINTERNALERRORtraceback. - The narration lint summary prints each finding's location in its own column
rather than appended to the message. pytest-givenwith no subcommand, andpytest-given skillswith no
subcommand, now print that parser's usage and exit 2 instead of the root help
and exit 1.
Report content (all formats)
- Breaking (JSON report).
parameters.namesbecomesparameters.columns
({id, name, kind}), cells may hold an attachment object, placeholder parts
gaincolumn_id, term-ref parts loseparam_column, and a grouped step's
narration.textis the template rather than case 1's rendering. - Breaking (JSON report). A step no longer carries
statusorerror;
failure lives on the scenario and on the parameter table's cases. A consumer
readingstep.statusshould readscenario.statusinstead. - The Markdown report now shows a scenario's failure reason — the message and the
failing frame — under the scenario, and under the parameter table for each
failed case.
HTML report
- The browse sidebar leads with Modules and renders them as a collapsible
package tree whose nodes filter by path prefix; it no longer lists individual
scenarios under each group. - The report's colors are retuned into one system — a term ref in a step or a
scenario title reads as a word under a light wash rather than a bordered pill
(the Glossary view keeps its pills), and column colors are generated per
column — and the sidebar, its filter chips and the attachment badges are
tidied along with it. - The report opens and filters substantially faster on large suites, and its file
is smaller — a term reference now points at its glossary entry instead of
repeating the entry's definition, which takes about 18% off a term-heavy
report. - A glossary term referenced only in a
@scenariotitle now contributes an
instance to the Glossary view, where it previously counted toward the term's
scenario tally while showing no instance.
Bundled skills
- The authoring and reviewing skills gain the report mechanics their rules depend
on, a symptom index, a completeness audit, the full lint rule catalog, and
guidance for sparser tagging.
Removed
- Breaking. The
divergent-case-structurelint rule; delete any
given_lint_rulesorgiven_lint_ignoreentry naming it, which would
otherwise fail config parsing.
Fixed
Authoring API
-
Parametrize cases that claim different step activities now say so, instead of
reporting the more drastic "a different step structure". -
A
@givenfixture scoped wider thanfunctionno longer loses its step when
the first test to use it has no@scenario. -
@given/@when/@thenare signature-preserving, so a decorated helper stays
callable to a type checker (was:"StepDecorated" not callable). -
@given(...)above@pytest.fixturenow raises and names the fix, instead of
surfacing asfixture '<name>' not found. -
@scenario(activities=...)is now typedint | Sequence[int] | None, so a
bareactivities=2type-checks. -
@scenario(activities=...)with an unknown id, or withoutstory=, is
rejected at the decorator rather than at collection. -
A
-k- or--deselect-narrowed run no longer fails on an authoring error in
a scenario it did not select. -
Glossary term handles are now hashable, and equal for the same term whichever
accessor produced them. -
A
when_thenstep in a test without@scenarionow points its warning at the
test rather than at pytest-given's own module.
Narration lint
-
A
given_lint_ignoreentry beginning with a Windows drive letter
(c:/repo/tests/t.py::test_x) is no longer rejected as an unknown rule prefix. -
A rule configured
offno longer runs; levels were applied only after every
rule had already produced its findings. -
A scenario tag with no ASCII alphanumerics (
tags=['日本語']) no longer takes
the run down with a traceback fromtag-shadows-term.
Report
pytest-given reportreports an unreadable input as an error instead of a
traceback (a directory raisedIsADirectoryErrorthrough the console script).- A JSON report with an out-of-range scenario
statusor termkindis
rejected by name, instead of crashing a renderer with a bareKeyError. - A
<br>inside an inline code span in a glossary definition renders as text
rather than as a line break. - The Terms browse axis no longer lists a term the selected glossary does not
hold.
Plugin and run behavior
- A
--given-json/--given-html/--given-mdpath that could not be a report
file is now refused before the suite runs, instead of a bare flag swallowing a
following test path and overwriting — or, on a failed run, deleting — it. pytest-given reportnow discards a stale report when the render fails, not
only when the write does.pytest-given report --source-linkis now validated on a--format mdrun
instead of being accepted and ignored, and an unknown preset is reported under
the name the user typed.- A
giton PATH that cannot be executed no longer fails the run. - A nested in-process pytest run that dies while parsing its arguments no longer
strands the outer session's captured rootdir, which silently dropped every
later step's source anchor. @given/@when/@thenon anasync defhelper now records around the
awaited body, and async generator fixtures are handled too.- The narration lint now inspects
async defstep helpers, whose bodies were
invisible to every AST rule. - An explicit
--given-source-link=now disables source links instead of falling
through to thegiven_source_linkini. - A finished scenario no longer leaves its collector — and every scenario and
step it recorded — reachable from a process-global for the rest of the process. - An error-level lint finding no longer overwrites a more specific exit ...
v0.1.0
First public release.
Added
@scenariodecorator plusgiven/when/thenstep blocks, usable as
both context managers and decorators, including on fixtures.- Self-contained interactive HTML report (
--given-html), Markdown report
(--given-md), and JSON report (--given-json). The HTML bundles Alpine.js
and needs no server or external assets. - Structured step text: plain strings,
Templateobjects, and t-strings
(PEP 750), with parameter interpolation
rendered as highlighted values. attach()for text and JSON attachments on a step.- Domain Storytelling support: ubiquitous-language glossaries (inline or
Markdown-backed viaFileGlossary), Domain Stories, and story coverage. - Narration lint (
--given-lint) with a configurable rule catalog via
given_lint_rulesandgiven_lint_ignore. --given-source-linkwithvscode,cursor,zed,pycharm, andgithub
presets for jumping from a report step to its source.pytest-givenconsole script:reportto re-render a saved JSON report, and
skills installto mirror the bundled agent skills into a project's
.claude/skills/.- Bundled authoring, navigating, and reviewing skills for AI agents, shipped in
the wheel and version-matched to the plugin.