Skip to content

Releases: nwilbert/pytest-given

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 04 Oct 18:11

Fixed

  • The HTML report's phase hover outline in a parameter table now follows an attachment payload opened or closed under the pointer.

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 12:51

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 own xfailed status, 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-authoring skill asks for one scenario per rule and covers parametrized scenarios as decision tables, and the pytest-given-reviewing skill catches more ways scenarios, glossary rows, pins, tags and parameter tables can disagree.

Fixed

  • A @given fixture 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 @given generator 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 FileGlossary table 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 as report not written beside a report that was kept.
  • The tag-shadows-term lint finding counts a scenario once when it carries two spellings of the tag, such as Guest and guest.
  • pytest-given report -o report.markdown writes Markdown, as -o report.md does, instead of refusing the path as an HTML one.
  • pytest-given report --help mentions 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

Choose a tag to compare

@github-actions github-actions released this 27 Sep 09:53

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 --theme on pytest-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-123 goes under a ticket heading), 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 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.
  • Breaking. @scenario(story=) is now stories=.
  • 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.
  • 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.
  • 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 str narration name the parametrize case that differs. The varying-str error also suggests group_parametrized=False as 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 naming guest('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 a when that narrates setup while its body acts.
  • 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.

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 report reports a non-UTF-8 input file as an error instead of crashing with a traceback.
  • An unknown --given-source-link preset is reported under the flag the user typed, not under the given_source_link ini name.
  • A refused scenario on a run with no --given-* sink no longer reports itself under a "report not written" heading.
  • when_then(...) rejects a Template narration in a test body, as given/when/then already 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-term now catches a tag that collides with a term id through a non-ASCII character that lowercases into ASCII.
  • 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.
  • 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_rules when enabling dead-term, and ranks findings in one explicit order.
  • 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.
  • 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.

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 04 Sep 13:03

Added

  • pytest_given.PytestGivenWarning is a top-level export, and a step or
    attach() recorded in a test without @scenario now warns with it instead of
    pytest.PytestWarning.
  • --given-title=TEXT (or the given_title ini) names the report, replacing the
    rootdir name.
  • A parametrized scenario's parameter table now carries a typed column per varying
    value — param, derived, or attachment for 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-lint is a plain boolean flag: write --given-lint and
    --no-given-lint instead of --given-lint=true / --given-lint=false. Either
    form still overrides the given_lint ini for one run.

Authoring API

  • Breaking. Step narration must now be uniform across parametrize cases;
    these fail the run with PytestGivenError, 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 attach labels 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 new attachment column. The last needs
    @scenario(..., group_parametrized=False), giving each case its own scenario.

  • Breaking. attach() now takes a plain str label; a t-string label raises
    PytestGivenError — use an f-string.

  • Breaking. attach() called with no step open now raises PytestGivenError
    instead of silently discarding the payload; move the call inside the step it
    belongs to.

  • Breaking. activity(..., id=N) is now activity(..., activity_id=N); the
    Activity.id field itself is unchanged.

  • Breaking. FileGlossary is now a Glossary subclass rather than a wrapper
    around one, so its .glossary attribute is gone — use the FileGlossary
    itself wherever that attribute was passed.

  • A glossary term placed in an activity slot its declared kind forbids now raises
    PytestGivenError when activity(...) 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 FileGlossary whose 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 a str and non-int members with a
    TypeError.

  • @scenario now returns the test function itself rather than a wrapper, so the
    test keeps its own signature.

Plugin and run behavior

  • An unknown given_source_link preset is now a UsageError raised before the
    suite runs.
  • The collection-time @scenario checks now report as a UsageError instead of
    an INTERNALERROR traceback.
  • The narration lint summary prints each finding's location in its own column
    rather than appended to the message.
  • pytest-given with no subcommand, and pytest-given skills with 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.names becomes parameters.columns
    ({id, name, kind}), cells may hold an attachment object, placeholder parts
    gain column_id, term-ref parts lose param_column, and a grouped step's
    narration.text is the template rather than case 1's rendering.
  • Breaking (JSON report). A step no longer carries status or error;
    failure lives on the scenario and on the parameter table's cases. A consumer
    reading step.status should read scenario.status instead.
  • 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 @scenario title 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-structure lint rule; delete any
    given_lint_rules or given_lint_ignore entry 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 @given fixture scoped wider than function no longer loses its step when
    the first test to use it has no @scenario.

  • @given/@when/@then are signature-preserving, so a decorated helper stays
    callable to a type checker (was: "StepDecorated" not callable).

  • @given(...) above @pytest.fixture now raises and names the fix, instead of
    surfacing as fixture '<name>' not found.

  • @scenario(activities=...) is now typed int | Sequence[int] | None, so a
    bare activities=2 type-checks.

  • @scenario(activities=...) with an unknown id, or without story=, 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_then step in a test without @scenario now points its warning at the
    test rather than at pytest-given's own module.

Narration lint

  • A given_lint_ignore entry 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 off no 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 from tag-shadows-term.

Report

  • pytest-given report reports an unreadable input as an error instead of a
    traceback (a directory raised IsADirectoryError through the console script).
  • A JSON report with an out-of-range scenario status or term kind is
    rejected by name, instead of crashing a renderer with a bare KeyError.
  • 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-md path 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 report now discards a stale report when the render fails, not
    only when the write does.
  • pytest-given report --source-link is now validated on a --format md run
    instead of being accepted and ignored, and an unknown preset is reported under
    the name the user typed.
  • A git on 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/@then on an async def helper now records around the
    awaited body, and async generator fixtures are handled too.
  • The narration lint now inspects async def step helpers, whose bodies were
    invisible to every AST rule.
  • An explicit --given-source-link= now disables source links instead of falling
    through to the given_source_link ini.
  • 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 ...
Read more

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 08 Aug 15:33

First public release.

Added

  • @scenario decorator plus given / when / then step 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, Template objects, 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 via FileGlossary), Domain Stories, and story coverage.
  • Narration lint (--given-lint) with a configurable rule catalog via
    given_lint_rules and given_lint_ignore.
  • --given-source-link with vscode, cursor, zed, pycharm, and github
    presets for jumping from a report step to its source.
  • pytest-given console script: report to re-render a saved JSON report, and
    skills install to 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.